feat(project): 업무 모달 2단 레이아웃 + 프로젝트연결 즉시선택 + 하위업무 즉시배정
연결된 프로젝트: "+추가" 버튼 없이 프로젝트/세션 셀렉트를 바로 노출, 모달 열릴 때 목록 미리 로드. 하위업무 추가행에 담당자/마감일 입력 추가해 생성과 동시에 배정 가능. 모달을 넓히고(780px) 메타/설명(좌)·하위업무/첨부/연결/ 댓글(우) 2단 그리드로 재배치, 우측 칼럼만 자체 스크롤 시켜 전체 스크롤 최소화. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}
|
||||
<link rel="stylesheet" href="/static/project.css?v=20260916c" />
|
||||
<link rel="stylesheet" href="/static/project.css?v=20260916d" />
|
||||
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
|
||||
{% endblock %}
|
||||
|
||||
@@ -45,5 +45,5 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script src="/static/project.js?v=20260916e" defer></script>
|
||||
<script src="/static/project.js?v=20260916f" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}
|
||||
<link rel="stylesheet" href="/static/project.css?v=20260916c" />
|
||||
<link rel="stylesheet" href="/static/project.css?v=20260916d" />
|
||||
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
|
||||
{% endblock %}
|
||||
|
||||
@@ -239,7 +239,7 @@
|
||||
|
||||
<!-- 업무 편집 모달 — 카드/내 업무에서 클릭 시 페이지 이동 없이 팝업으로 편집 -->
|
||||
<div class="pj-modal" id="pj-modal-task" hidden>
|
||||
<div class="pj-modal-card">
|
||||
<div class="pj-modal-card pj-task-modal-card">
|
||||
<!-- 아사나처럼 큰 제목은 아래 무테 입력칸이 대신한다 — 이 표시는 작은
|
||||
모드 안내(새 업무/업무 편집)만 한다. -->
|
||||
<h3 id="pj-task-modal-title" class="pj-modal-kicker">업무 편집</h3>
|
||||
@@ -247,94 +247,101 @@
|
||||
<input type="hidden" id="pj-t-project-id" />
|
||||
<input type="text" id="pj-t-title" class="pj-t-title-input" placeholder="업무 제목" />
|
||||
|
||||
<!-- 메타 정보 — 담당자·세션·우선순위·일정을 작은 칩 형태로 한 줄에 -->
|
||||
<div class="pj-meta-row">
|
||||
<div class="pj-meta-field">
|
||||
<span class="pj-meta-label">담당자</span>
|
||||
<select id="pj-t-assignee"><option value="">불러오는 중…</option></select>
|
||||
</div>
|
||||
<div class="pj-meta-field">
|
||||
<span class="pj-meta-label">세션</span>
|
||||
<select id="pj-t-stage"><option value="">불러오는 중…</option></select>
|
||||
</div>
|
||||
<div class="pj-meta-field">
|
||||
<span class="pj-meta-label">우선순위</span>
|
||||
<select id="pj-t-priority">
|
||||
<option value="low">낮음</option>
|
||||
<option value="normal" selected>보통</option>
|
||||
<option value="high">높음</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pj-meta-row">
|
||||
<div class="pj-meta-field">
|
||||
<span class="pj-meta-label">시작일</span>
|
||||
<input type="date" id="pj-t-start" />
|
||||
</div>
|
||||
<div class="pj-meta-field">
|
||||
<span class="pj-meta-label">마감일</span>
|
||||
<input type="date" id="pj-t-due" />
|
||||
</div>
|
||||
</div>
|
||||
<label class="pj-time-toggle">
|
||||
<input type="checkbox" id="pj-t-usetime" /> 시간 지정 (체크 안 하면 종일)
|
||||
</label>
|
||||
<div class="pj-form-row" id="pj-time-row">
|
||||
<label>시작 시간<input type="time" id="pj-t-stime" disabled /></label>
|
||||
<label>마감 시간<input type="time" id="pj-t-dtime" disabled /></label>
|
||||
</div>
|
||||
|
||||
<!-- 설명 -->
|
||||
<div class="pj-desc-toolbar">
|
||||
<span class="pj-extra-label">설명</span>
|
||||
<label class="pj-upload-btn">이미지 추가<input type="file" id="pj-desc-img-input" accept="image/*" hidden /></label>
|
||||
<span class="pj-muted-sm" id="pj-desc-hint"></span>
|
||||
</div>
|
||||
<div class="pj-desc-editor" id="pj-t-desc" contenteditable="true" data-placeholder="설명을 입력하세요… (이미지는 붙여넣기 또는 위 버튼으로 추가)"></div>
|
||||
|
||||
<div class="pj-task-extra" id="pj-subtask-block">
|
||||
<div class="pj-extra-head">
|
||||
<span class="material-symbols-outlined pj-gicon pj-gicon-sm">checklist</span> 하위 업무
|
||||
</div>
|
||||
<ul class="pj-subtask-list" id="pj-subtask-list"></ul>
|
||||
<div class="pj-subtask-form">
|
||||
<input type="text" id="pj-subtask-input" placeholder="하위 업무 추가…" />
|
||||
<button type="button" class="pj-btn pj-btn-primary" id="pj-subtask-add">추가</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pj-task-extra" id="pj-attach-block">
|
||||
<div class="pj-extra-head">
|
||||
<span class="material-symbols-outlined pj-gicon pj-gicon-sm">attach_file</span> 첨부파일
|
||||
<label class="pj-upload-btn">
|
||||
파일 추가<input type="file" id="pj-attach-input" hidden />
|
||||
<div class="pj-modal-body">
|
||||
<div class="pj-modal-col-main">
|
||||
<!-- 메타 정보 — 담당자·세션·우선순위·일정을 작은 칩 형태로 한 줄에 -->
|
||||
<div class="pj-meta-row">
|
||||
<div class="pj-meta-field">
|
||||
<span class="pj-meta-label">담당자</span>
|
||||
<select id="pj-t-assignee"><option value="">불러오는 중…</option></select>
|
||||
</div>
|
||||
<div class="pj-meta-field">
|
||||
<span class="pj-meta-label">세션</span>
|
||||
<select id="pj-t-stage"><option value="">불러오는 중…</option></select>
|
||||
</div>
|
||||
<div class="pj-meta-field">
|
||||
<span class="pj-meta-label">우선순위</span>
|
||||
<select id="pj-t-priority">
|
||||
<option value="low">낮음</option>
|
||||
<option value="normal" selected>보통</option>
|
||||
<option value="high">높음</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pj-meta-row">
|
||||
<div class="pj-meta-field">
|
||||
<span class="pj-meta-label">시작일</span>
|
||||
<input type="date" id="pj-t-start" />
|
||||
</div>
|
||||
<div class="pj-meta-field">
|
||||
<span class="pj-meta-label">마감일</span>
|
||||
<input type="date" id="pj-t-due" />
|
||||
</div>
|
||||
</div>
|
||||
<label class="pj-time-toggle">
|
||||
<input type="checkbox" id="pj-t-usetime" /> 시간 지정 (체크 안 하면 종일)
|
||||
</label>
|
||||
<div class="pj-form-row" id="pj-time-row">
|
||||
<label>시작 시간<input type="time" id="pj-t-stime" disabled /></label>
|
||||
<label>마감 시간<input type="time" id="pj-t-dtime" disabled /></label>
|
||||
</div>
|
||||
|
||||
<!-- 설명 -->
|
||||
<div class="pj-desc-toolbar">
|
||||
<span class="pj-extra-label">설명</span>
|
||||
<label class="pj-upload-btn">이미지 추가<input type="file" id="pj-desc-img-input" accept="image/*" hidden /></label>
|
||||
<span class="pj-muted-sm" id="pj-desc-hint"></span>
|
||||
</div>
|
||||
<div class="pj-desc-editor" id="pj-t-desc" contenteditable="true" data-placeholder="설명을 입력하세요… (이미지는 붙여넣기 또는 위 버튼으로 추가)"></div>
|
||||
</div>
|
||||
<ul class="pj-attach-list" id="pj-attach-list"></ul>
|
||||
</div>
|
||||
<div class="pj-task-extra" id="pj-links-block">
|
||||
<div class="pj-extra-head">
|
||||
<span class="material-symbols-outlined pj-gicon pj-gicon-sm">link</span> 연결된 프로젝트
|
||||
<button type="button" class="pj-upload-btn" id="pj-link-add-btn">+ 프로젝트에 추가</button>
|
||||
</div>
|
||||
<div class="pj-link-chips" id="pj-link-chips"></div>
|
||||
<div class="pj-link-form" id="pj-link-form" hidden>
|
||||
<select id="pj-link-project"><option value="">프로젝트 선택…</option></select>
|
||||
<select id="pj-link-stage"><option value="">세션 선택…</option></select>
|
||||
<button type="button" class="pj-btn pj-btn-primary" id="pj-link-save">추가</button>
|
||||
<button type="button" class="pj-btn" id="pj-link-cancel">취소</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 댓글(활동) — 아사나처럼 맨 아래 -->
|
||||
<div class="pj-task-extra" id="pj-comment-block">
|
||||
<div class="pj-extra-head">
|
||||
<span class="pj-cmt-emoji">💬</span> 댓글
|
||||
</div>
|
||||
<ul class="pj-comment-list" id="pj-comment-list"></ul>
|
||||
<div class="pj-comment-form">
|
||||
<input type="text" id="pj-comment-input" placeholder="댓글 입력…" />
|
||||
<button type="button" class="pj-btn pj-btn-primary" id="pj-comment-send">등록</button>
|
||||
|
||||
<div class="pj-modal-col-side">
|
||||
<div class="pj-task-extra" id="pj-subtask-block">
|
||||
<div class="pj-extra-head">
|
||||
<span class="material-symbols-outlined pj-gicon pj-gicon-sm">checklist</span> 하위 업무
|
||||
</div>
|
||||
<ul class="pj-subtask-list" id="pj-subtask-list"></ul>
|
||||
<div class="pj-subtask-form">
|
||||
<input type="text" id="pj-subtask-input" placeholder="하위 업무 제목…" />
|
||||
<select id="pj-subtask-add-assignee"><option value="">(미지정)</option></select>
|
||||
<input type="date" id="pj-subtask-add-due" />
|
||||
<button type="button" class="pj-btn pj-btn-primary" id="pj-subtask-add">추가</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pj-task-extra" id="pj-attach-block">
|
||||
<div class="pj-extra-head">
|
||||
<span class="material-symbols-outlined pj-gicon pj-gicon-sm">attach_file</span> 첨부파일
|
||||
<label class="pj-upload-btn">
|
||||
파일 추가<input type="file" id="pj-attach-input" hidden />
|
||||
</label>
|
||||
</div>
|
||||
<ul class="pj-attach-list" id="pj-attach-list"></ul>
|
||||
</div>
|
||||
<div class="pj-task-extra" id="pj-links-block">
|
||||
<div class="pj-extra-head">
|
||||
<span class="material-symbols-outlined pj-gicon pj-gicon-sm">link</span> 연결된 프로젝트
|
||||
</div>
|
||||
<div class="pj-link-chips" id="pj-link-chips"></div>
|
||||
<div class="pj-link-form" id="pj-link-form">
|
||||
<select id="pj-link-project"><option value="">프로젝트 선택…</option></select>
|
||||
<select id="pj-link-stage"><option value="">세션 선택…</option></select>
|
||||
<button type="button" class="pj-btn pj-btn-primary" id="pj-link-save">추가</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 댓글(활동) — 아사나처럼 맨 아래 -->
|
||||
<div class="pj-task-extra" id="pj-comment-block">
|
||||
<div class="pj-extra-head">
|
||||
<span class="pj-cmt-emoji">💬</span> 댓글
|
||||
</div>
|
||||
<ul class="pj-comment-list" id="pj-comment-list"></ul>
|
||||
<div class="pj-comment-form">
|
||||
<input type="text" id="pj-comment-input" placeholder="댓글 입력…" />
|
||||
<button type="button" class="pj-btn pj-btn-primary" id="pj-comment-send">등록</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pj-modal-actions">
|
||||
<button type="button" class="pj-btn pj-btn-danger" id="pj-delete-task">삭제</button>
|
||||
<span style="flex:1"></span>
|
||||
@@ -366,5 +373,5 @@
|
||||
<script>
|
||||
window.PJ_COLORS = {{ ["#4573d2","#37a3a3","#62a420","#e8a33d","#e8384f","#aa62e3","#f06a6a","#5a6772"] | tojson }};
|
||||
</script>
|
||||
<script src="/static/project.js?v=20260916e" defer></script>
|
||||
<script src="/static/project.js?v=20260916f" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}
|
||||
<link rel="stylesheet" href="/static/project.css?v=20260916c" />
|
||||
<link rel="stylesheet" href="/static/project.css?v=20260916d" />
|
||||
<!-- 구글 머티리얼 심볼(담당자 아이콘 등) — self-host -->
|
||||
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
|
||||
<!-- 타임라인 vis-timeline — self-host -->
|
||||
@@ -157,116 +157,122 @@
|
||||
|
||||
<!-- 업무 모달 -->
|
||||
<div class="pj-modal" id="pj-modal-task" hidden>
|
||||
<div class="pj-modal-card">
|
||||
<div class="pj-modal-card pj-task-modal-card">
|
||||
<!-- 아사나처럼 큰 제목은 아래 무테 입력칸이 대신한다 — 이 표시는 작은
|
||||
모드 안내(새 업무/업무 편집)만 한다. -->
|
||||
<h3 id="pj-task-modal-title" class="pj-modal-kicker">새 업무</h3>
|
||||
<input type="hidden" id="pj-t-id" />
|
||||
<input type="text" id="pj-t-title" class="pj-t-title-input" placeholder="업무 제목" />
|
||||
|
||||
<!-- 메타 정보 — 담당자·세션·우선순위·일정을 작은 칩 형태로 한 줄에 -->
|
||||
<div class="pj-meta-row">
|
||||
<div class="pj-meta-field">
|
||||
<span class="pj-meta-label">담당자</span>
|
||||
<select id="pj-t-assignee">
|
||||
<option value="">(미지정)</option>
|
||||
{% for m in members %}
|
||||
<option value="{{ m.user_email }}" data-name="{{ m.user_email.split('@')[0] }}">
|
||||
{{ m.user_email.split('@')[0] }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="pj-meta-field">
|
||||
<span class="pj-meta-label">세션</span>
|
||||
<select id="pj-t-stage">
|
||||
{% for s in stages %}
|
||||
<option value="{{ s.id }}">{{ s.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="pj-meta-field">
|
||||
<span class="pj-meta-label">우선순위</span>
|
||||
<select id="pj-t-priority">
|
||||
<option value="low">낮음</option>
|
||||
<option value="normal" selected>보통</option>
|
||||
<option value="high">높음</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pj-meta-row">
|
||||
<div class="pj-meta-field">
|
||||
<span class="pj-meta-label">시작일</span>
|
||||
<input type="date" id="pj-t-start" />
|
||||
</div>
|
||||
<div class="pj-meta-field">
|
||||
<span class="pj-meta-label">마감일</span>
|
||||
<input type="date" id="pj-t-due" />
|
||||
</div>
|
||||
</div>
|
||||
<label class="pj-time-toggle">
|
||||
<input type="checkbox" id="pj-t-usetime" /> 시간 지정 (체크 안 하면 종일)
|
||||
</label>
|
||||
<div class="pj-form-row" id="pj-time-row">
|
||||
<label>시작 시간<input type="time" id="pj-t-stime" disabled /></label>
|
||||
<label>마감 시간<input type="time" id="pj-t-dtime" disabled /></label>
|
||||
</div>
|
||||
|
||||
<!-- 설명 -->
|
||||
<div class="pj-desc-toolbar">
|
||||
<span class="pj-extra-label">설명</span>
|
||||
<label class="pj-upload-btn">이미지 추가<input type="file" id="pj-desc-img-input" accept="image/*" hidden /></label>
|
||||
<span class="pj-muted-sm" id="pj-desc-hint"></span>
|
||||
</div>
|
||||
<div class="pj-desc-editor" id="pj-t-desc" contenteditable="true" data-placeholder="설명을 입력하세요… (이미지는 붙여넣기 또는 위 버튼으로 추가)"></div>
|
||||
|
||||
<!-- 하위 업무 (기존 업무 편집 시에만) -->
|
||||
<div class="pj-task-extra" id="pj-subtask-block" hidden>
|
||||
<div class="pj-extra-head">
|
||||
<span class="material-symbols-outlined pj-gicon pj-gicon-sm">checklist</span> 하위 업무
|
||||
</div>
|
||||
<ul class="pj-subtask-list" id="pj-subtask-list"></ul>
|
||||
<div class="pj-subtask-form">
|
||||
<input type="text" id="pj-subtask-input" placeholder="하위 업무 추가…" />
|
||||
<button type="button" class="pj-btn pj-btn-primary" id="pj-subtask-add">추가</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 첨부파일 (기존 업무 편집 시에만) -->
|
||||
<div class="pj-task-extra" id="pj-attach-block" hidden>
|
||||
<div class="pj-extra-head">
|
||||
<span class="material-symbols-outlined pj-gicon pj-gicon-sm">attach_file</span> 첨부파일
|
||||
<label class="pj-upload-btn">
|
||||
파일 추가<input type="file" id="pj-attach-input" hidden />
|
||||
<div class="pj-modal-body">
|
||||
<div class="pj-modal-col-main">
|
||||
<!-- 메타 정보 — 담당자·세션·우선순위·일정을 작은 칩 형태로 한 줄에 -->
|
||||
<div class="pj-meta-row">
|
||||
<div class="pj-meta-field">
|
||||
<span class="pj-meta-label">담당자</span>
|
||||
<select id="pj-t-assignee">
|
||||
<option value="">(미지정)</option>
|
||||
{% for m in members %}
|
||||
<option value="{{ m.user_email }}" data-name="{{ m.user_email.split('@')[0] }}">
|
||||
{{ m.user_email.split('@')[0] }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="pj-meta-field">
|
||||
<span class="pj-meta-label">세션</span>
|
||||
<select id="pj-t-stage">
|
||||
{% for s in stages %}
|
||||
<option value="{{ s.id }}">{{ s.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="pj-meta-field">
|
||||
<span class="pj-meta-label">우선순위</span>
|
||||
<select id="pj-t-priority">
|
||||
<option value="low">낮음</option>
|
||||
<option value="normal" selected>보통</option>
|
||||
<option value="high">높음</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pj-meta-row">
|
||||
<div class="pj-meta-field">
|
||||
<span class="pj-meta-label">시작일</span>
|
||||
<input type="date" id="pj-t-start" />
|
||||
</div>
|
||||
<div class="pj-meta-field">
|
||||
<span class="pj-meta-label">마감일</span>
|
||||
<input type="date" id="pj-t-due" />
|
||||
</div>
|
||||
</div>
|
||||
<label class="pj-time-toggle">
|
||||
<input type="checkbox" id="pj-t-usetime" /> 시간 지정 (체크 안 하면 종일)
|
||||
</label>
|
||||
</div>
|
||||
<ul class="pj-attach-list" id="pj-attach-list"></ul>
|
||||
</div>
|
||||
<div class="pj-form-row" id="pj-time-row">
|
||||
<label>시작 시간<input type="time" id="pj-t-stime" disabled /></label>
|
||||
<label>마감 시간<input type="time" id="pj-t-dtime" disabled /></label>
|
||||
</div>
|
||||
|
||||
<!-- 연결된 프로젝트 — 멀티호밍(아사나식). 기존 업무 편집 시에만 -->
|
||||
<div class="pj-task-extra" id="pj-links-block" hidden>
|
||||
<div class="pj-extra-head">
|
||||
<span class="material-symbols-outlined pj-gicon pj-gicon-sm">link</span> 연결된 프로젝트
|
||||
<button type="button" class="pj-upload-btn" id="pj-link-add-btn">+ 프로젝트에 추가</button>
|
||||
<!-- 설명 -->
|
||||
<div class="pj-desc-toolbar">
|
||||
<span class="pj-extra-label">설명</span>
|
||||
<label class="pj-upload-btn">이미지 추가<input type="file" id="pj-desc-img-input" accept="image/*" hidden /></label>
|
||||
<span class="pj-muted-sm" id="pj-desc-hint"></span>
|
||||
</div>
|
||||
<div class="pj-desc-editor" id="pj-t-desc" contenteditable="true" data-placeholder="설명을 입력하세요… (이미지는 붙여넣기 또는 위 버튼으로 추가)"></div>
|
||||
</div>
|
||||
<div class="pj-link-chips" id="pj-link-chips"></div>
|
||||
<div class="pj-link-form" id="pj-link-form" hidden>
|
||||
<select id="pj-link-project"><option value="">프로젝트 선택…</option></select>
|
||||
<select id="pj-link-stage"><option value="">세션 선택…</option></select>
|
||||
<button type="button" class="pj-btn pj-btn-primary" id="pj-link-save">추가</button>
|
||||
<button type="button" class="pj-btn" id="pj-link-cancel">취소</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 댓글(활동) — 아사나처럼 맨 아래. 기존 업무 편집 시에만 -->
|
||||
<div class="pj-task-extra" id="pj-comment-block" hidden>
|
||||
<div class="pj-extra-head">
|
||||
<span class="pj-cmt-emoji">💬</span> 댓글
|
||||
</div>
|
||||
<ul class="pj-comment-list" id="pj-comment-list"></ul>
|
||||
<div class="pj-comment-form">
|
||||
<input type="text" id="pj-comment-input" placeholder="댓글 입력…" />
|
||||
<button type="button" class="pj-btn pj-btn-primary" id="pj-comment-send">등록</button>
|
||||
<div class="pj-modal-col-side">
|
||||
<!-- 하위 업무 (기존 업무 편집 시에만) -->
|
||||
<div class="pj-task-extra" id="pj-subtask-block" hidden>
|
||||
<div class="pj-extra-head">
|
||||
<span class="material-symbols-outlined pj-gicon pj-gicon-sm">checklist</span> 하위 업무
|
||||
</div>
|
||||
<ul class="pj-subtask-list" id="pj-subtask-list"></ul>
|
||||
<div class="pj-subtask-form">
|
||||
<input type="text" id="pj-subtask-input" placeholder="하위 업무 제목…" />
|
||||
<select id="pj-subtask-add-assignee"><option value="">(미지정)</option></select>
|
||||
<input type="date" id="pj-subtask-add-due" />
|
||||
<button type="button" class="pj-btn pj-btn-primary" id="pj-subtask-add">추가</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 첨부파일 (기존 업무 편집 시에만) -->
|
||||
<div class="pj-task-extra" id="pj-attach-block" hidden>
|
||||
<div class="pj-extra-head">
|
||||
<span class="material-symbols-outlined pj-gicon pj-gicon-sm">attach_file</span> 첨부파일
|
||||
<label class="pj-upload-btn">
|
||||
파일 추가<input type="file" id="pj-attach-input" hidden />
|
||||
</label>
|
||||
</div>
|
||||
<ul class="pj-attach-list" id="pj-attach-list"></ul>
|
||||
</div>
|
||||
|
||||
<!-- 연결된 프로젝트 — 멀티호밍(아사나식). 기존 업무 편집 시에만 -->
|
||||
<div class="pj-task-extra" id="pj-links-block" hidden>
|
||||
<div class="pj-extra-head">
|
||||
<span class="material-symbols-outlined pj-gicon pj-gicon-sm">link</span> 연결된 프로젝트
|
||||
</div>
|
||||
<div class="pj-link-chips" id="pj-link-chips"></div>
|
||||
<div class="pj-link-form" id="pj-link-form">
|
||||
<select id="pj-link-project"><option value="">프로젝트 선택…</option></select>
|
||||
<select id="pj-link-stage"><option value="">세션 선택…</option></select>
|
||||
<button type="button" class="pj-btn pj-btn-primary" id="pj-link-save">추가</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 댓글(활동) — 아사나처럼 맨 아래. 기존 업무 편집 시에만 -->
|
||||
<div class="pj-task-extra" id="pj-comment-block" hidden>
|
||||
<div class="pj-extra-head">
|
||||
<span class="pj-cmt-emoji">💬</span> 댓글
|
||||
</div>
|
||||
<ul class="pj-comment-list" id="pj-comment-list"></ul>
|
||||
<div class="pj-comment-form">
|
||||
<input type="text" id="pj-comment-input" placeholder="댓글 입력…" />
|
||||
<button type="button" class="pj-btn pj-btn-primary" id="pj-comment-send">등록</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -320,5 +326,5 @@
|
||||
</script>
|
||||
|
||||
<script src="/static/vendor/vis-timeline/vis-timeline-graph2d.min.js"></script>
|
||||
<script src="/static/project.js?v=20260916e" defer></script>
|
||||
<script src="/static/project.js?v=20260916f" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
+25
-2
@@ -310,6 +310,18 @@
|
||||
.pj-modal { position: fixed; inset: 0; background: rgba(20,22,26,.46); display: flex; align-items: center; justify-content: center; z-index: 1000; }
|
||||
.pj-modal[hidden] { display: none; }
|
||||
.pj-modal-card { background: #fff; border-radius: 16px; padding: 22px; width: min(480px, 92vw); box-shadow: 0 20px 60px rgba(0,0,0,.25); max-height: 90vh; overflow-y: auto; }
|
||||
/* 업무 편집 팝업만 넓게 — 메타/설명(좌) + 하위업무·첨부·연결·댓글(우) 2단.
|
||||
우측 칼럼만 자체 스크롤 시켜 팝업 전체 스크롤은 웬만하면 안 생기게 한다. */
|
||||
.pj-task-modal-card { width: min(780px, 96vw); }
|
||||
.pj-modal-body { display: grid; grid-template-columns: 1.15fr .85fr; gap: 0 26px; align-items: start; }
|
||||
.pj-modal-col-main, .pj-modal-col-side { min-width: 0; }
|
||||
.pj-modal-col-side { max-height: 66vh; overflow-y: auto; padding-right: 4px; }
|
||||
.pj-modal-col-side .pj-task-extra:first-child { margin-top: 0; padding-top: 0; border-top: none; }
|
||||
@media (max-width: 720px) {
|
||||
.pj-modal-body { grid-template-columns: 1fr; }
|
||||
.pj-modal-col-side { max-height: none; overflow-y: visible; padding-right: 0; }
|
||||
.pj-modal-col-side .pj-task-extra:first-child { border-top: 1px solid #eef0f2; margin-top: 14px; padding-top: 12px; }
|
||||
}
|
||||
.pj-modal-card h3 { margin: 0 0 16px; font-size: 17px; }
|
||||
.pj-modal-hint { margin: -6px 0 14px; font-size: 12px; color: #6b7280; }
|
||||
|
||||
@@ -484,8 +496,19 @@
|
||||
flex: 0 0 auto; width: 110px !important; font-size: 12px !important; padding: 4px 6px !important; margin: 0 !important;
|
||||
}
|
||||
.pj-subtask-due { flex: 0 0 auto; width: 128px !important; font-size: 12px !important; padding: 4px 6px !important; margin: 0 !important; }
|
||||
.pj-subtask-form { display: flex; gap: 6px; }
|
||||
.pj-subtask-form input { flex: 1; padding: 7px 10px; border: 1px solid #d8dce2; border-radius: 8px; font-size: 13px; font-family: inherit; }
|
||||
.pj-subtask-form { display: flex; gap: 6px; flex-wrap: wrap; }
|
||||
.pj-subtask-form input[type="text"] {
|
||||
flex: 1 1 120px; width: auto !important; min-width: 90px; margin: 0 !important;
|
||||
padding: 7px 10px !important; border: 1px solid #d8dce2 !important; border-radius: 8px; font-size: 13px !important; font-family: inherit;
|
||||
}
|
||||
#pj-subtask-add-assignee {
|
||||
flex: 0 0 auto; width: 96px !important; margin: 0 !important;
|
||||
padding: 6px 4px !important; border: 1px solid #d8dce2 !important; border-radius: 8px; font-size: 12px !important; font-family: inherit;
|
||||
}
|
||||
#pj-subtask-add-due {
|
||||
flex: 0 0 auto; width: 118px !important; margin: 0 !important;
|
||||
padding: 6px !important; border: 1px solid #d8dce2 !important; border-radius: 8px; font-size: 12px !important; font-family: inherit;
|
||||
}
|
||||
|
||||
/* ── 연결된 프로젝트(멀티호밍) 칩 ── */
|
||||
.pj-link-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
|
||||
|
||||
+21
-11
@@ -1983,6 +1983,8 @@
|
||||
const listEl = el("pj-subtask-list");
|
||||
if (!listEl) return null;
|
||||
const input = el("pj-subtask-input");
|
||||
const addAssignee = el("pj-subtask-add-assignee");
|
||||
const addDue = el("pj-subtask-add-due");
|
||||
const addBtn = el("pj-subtask-add");
|
||||
let taskId = null, projectId = null, canDeleteFn = function () { return false; }, onChange = null;
|
||||
|
||||
@@ -2056,10 +2058,17 @@
|
||||
const title = (input.value || "").trim();
|
||||
if (!title) return;
|
||||
if (!taskId) { alert("먼저 업무를 저장한 뒤 하위 업무를 추가할 수 있습니다."); return; }
|
||||
const assigneeOpt = addAssignee ? addAssignee.selectedOptions[0] : null;
|
||||
try {
|
||||
const res = await api("POST", "/project/api/projects/" + projectId + "/tasks",
|
||||
{ title: title, parent_task_id: taskId });
|
||||
const res = await api("POST", "/project/api/projects/" + projectId + "/tasks", {
|
||||
title: title, parent_task_id: taskId,
|
||||
assignee_email: addAssignee ? addAssignee.value : "",
|
||||
assignee_name: assigneeOpt ? assigneeOpt.textContent : "",
|
||||
due_date: addDue ? (addDue.value || null) : null,
|
||||
});
|
||||
input.value = "";
|
||||
if (addAssignee) addAssignee.value = "";
|
||||
if (addDue) addDue.value = "";
|
||||
const optionsHtml = await membersOptionsHtml(projectId);
|
||||
const empty = listEl.querySelector(".pj-empty-sm");
|
||||
if (empty) empty.remove();
|
||||
@@ -2076,6 +2085,11 @@
|
||||
taskId = opts.taskId; projectId = opts.projectId;
|
||||
canDeleteFn = opts.canDelete || function () { return false; };
|
||||
onChange = opts.onChange || null;
|
||||
if (addAssignee && projectId) {
|
||||
membersOptionsHtml(projectId).then(function (html) {
|
||||
addAssignee.innerHTML = "<option value=''>(미지정)</option>" + html;
|
||||
});
|
||||
}
|
||||
},
|
||||
render: render,
|
||||
};
|
||||
@@ -2085,12 +2099,9 @@
|
||||
function initLinksBlock() {
|
||||
const chipsEl = el("pj-link-chips");
|
||||
if (!chipsEl) return null;
|
||||
const addBtn = el("pj-link-add-btn");
|
||||
const form = el("pj-link-form");
|
||||
const projSel = el("pj-link-project");
|
||||
const stageSel = el("pj-link-stage");
|
||||
const saveBtn = el("pj-link-save");
|
||||
const cancelBtn = el("pj-link-cancel");
|
||||
let taskId = null, homeProjectId = null, onChange = null;
|
||||
|
||||
function chipEl(link) {
|
||||
@@ -2117,8 +2128,7 @@
|
||||
links.forEach(function (l) { chipsEl.appendChild(chipEl(l)); });
|
||||
}
|
||||
|
||||
if (addBtn) addBtn.addEventListener("click", async function () {
|
||||
form.hidden = false;
|
||||
async function loadProjects() {
|
||||
projSel.innerHTML = "<option value=''>불러오는 중…</option>";
|
||||
stageSel.innerHTML = "<option value=''>프로젝트를 먼저 선택하세요</option>";
|
||||
try {
|
||||
@@ -2130,8 +2140,7 @@
|
||||
return '<option value="' + p.id + '">' + escapeHtml(p.name) + "</option>";
|
||||
}).join("");
|
||||
} catch (e) { projSel.innerHTML = "<option value=''>불러오기 실패</option>"; }
|
||||
});
|
||||
if (cancelBtn) cancelBtn.addEventListener("click", function () { form.hidden = true; });
|
||||
}
|
||||
if (projSel) projSel.addEventListener("change", async function () {
|
||||
const pid = parseInt(projSel.value, 10);
|
||||
if (!pid) { stageSel.innerHTML = "<option value=''>프로젝트를 먼저 선택하세요</option>"; return; }
|
||||
@@ -2150,7 +2159,8 @@
|
||||
try {
|
||||
await api("POST", "/project/api/tasks/" + taskId + "/links",
|
||||
{ project_id: pid, stage_id: stageSel.value ? parseInt(stageSel.value, 10) : null });
|
||||
form.hidden = true;
|
||||
projSel.value = "";
|
||||
stageSel.innerHTML = "<option value=''>프로젝트를 먼저 선택하세요</option>";
|
||||
if (onChange) onChange();
|
||||
} catch (e) { alert("추가 실패: " + e.message); }
|
||||
});
|
||||
@@ -2158,7 +2168,7 @@
|
||||
return {
|
||||
setContext: function (opts) {
|
||||
taskId = opts.taskId; homeProjectId = opts.projectId; onChange = opts.onChange || null;
|
||||
form.hidden = true;
|
||||
loadProjects();
|
||||
},
|
||||
render: render,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user