feat(project): 업무 클릭 시 페이지 이동 없이 그 자리에서 팝업 편집

- 좌측 트리(프로젝트 상세 페이지): 이미 로드된 업무 데이터로 즉시 팝업 —
  더 이상 페이지를 새로고침하며 달력 탭으로 넘어가지 않음
- 홈 화면 카드/내 업무 리스트: 홈에도 업무편집 모달을 추가해 페이지 이동 없이
  팝업으로 편집(담당자·단계는 해당 프로젝트를 API 로 조회해 채움, 첨부·댓글 포함)
- 교차 프로젝트 업무 편집 시 담당자 select 가 현재 프로젝트 멤버만 보이던 문제 수정
  (해당 업무의 프로젝트 멤버를 다시 조회)
- 완료 프로젝트 숨기기 토글을 인라인 style.display 로 직접 제어하도록 변경
  ([hidden] 속성 + 클래스 규칙 조합이 기대대로 안 먹히는 경우 대비, 원인 불문 확실히 동작)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 09:16:39 +09:00
parent ef05f9d362
commit 1aad9844dd
7 changed files with 310 additions and 18 deletions
+11
View File
@@ -408,6 +408,16 @@ async def index(request: Request) -> HTMLResponse:
my_groups[pid] = g
g["tasks"].append(t)
my_projects = list(my_groups.values())
# 홈 화면 카드/내업무에 노출된 업무 전체(id→업무) — 클릭 시 페이지 이동 없이
# 팝업으로 편집하기 위해 필요한 원본 데이터를 그대로 클라이언트에 넘긴다.
home_tasks: dict[str, Any] = {}
for p in tree:
for t in p.get("task_preview", []):
home_tasks[str(t["id"])] = t
for t in my_tasks:
home_tasks[str(t["id"])] = t
return render_template(
request,
"project/index.html",
@@ -421,6 +431,7 @@ async def index(request: Request) -> HTMLResponse:
"done_projects": done_projects,
"my_projects": my_projects,
"avatars": avatars,
"home_tasks": home_tasks,
"today": today_kst().isoformat(),
},
)
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %}
{% block head_extra %}
<link rel="stylesheet" href="/static/project.css?v=20260915c" />
<link rel="stylesheet" href="/static/project.css?v=20260915e" />
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
{% endblock %}
@@ -45,5 +45,5 @@
{% endif %}
</div>
<script src="/static/project.js?v=20260915b" defer></script>
<script src="/static/project.js?v=20260915d" defer></script>
{% endblock %}
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %}
{% block head_extra %}
<link rel="stylesheet" href="/static/project.css?v=20260915c" />
<link rel="stylesheet" href="/static/project.css?v=20260915e" />
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
{% endblock %}
@@ -49,7 +49,7 @@
<ul class="pj-project-tasklist">
{% for t in p.task_preview %}
<li class="pj-project-taskrow {% if t.completed_at %}is-done{% endif %}">
<a href="/project/p/{{ p.id }}?task={{ t.id }}">
<a href="/project/p/{{ p.id }}?task={{ t.id }}" data-task-id="{{ t.id }}" class="pj-task-popup-link">
<span class="material-symbols-outlined pj-project-task-ic">{% if t.completed_at %}task_alt{% else %}radio_button_unchecked{% endif %}</span>
<span class="pj-project-task-title">{{ t.title }}</span>
{% if t.stage_name %}<span class="pj-project-task-stage">{{ t.stage_name }}</span>{% endif %}
@@ -138,7 +138,7 @@
<ul class="pj-mytree-tasks">
{% for t in p.tasks %}
<li class="pj-mytree-task {% if t.completed_at %}is-done{% endif %}">
<a href="/project/p/{{ t.project_id }}?task={{ t.id }}">
<a href="/project/p/{{ t.project_id }}?task={{ t.id }}" data-task-id="{{ t.id }}" class="pj-task-popup-link">
<span class="material-symbols-outlined pj-mytree-ic" style="color: {{ t.project_color }};">{% if t.completed_at %}task_alt{% else %}radio_button_unchecked{% endif %}</span>
<span class="pj-mytree-body">
<span class="pj-mytree-title">{{ t.title }}</span>
@@ -210,8 +210,74 @@
</div>
{% endif %}
<!-- 업무 편집 모달 — 카드/내 업무에서 클릭 시 페이지 이동 없이 팝업으로 편집 -->
<div class="pj-modal" id="pj-modal-task" hidden>
<div class="pj-modal-card">
<h3 id="pj-task-modal-title">업무 편집</h3>
<input type="hidden" id="pj-t-id" />
<input type="hidden" id="pj-t-project-id" />
<label>제목<input type="text" id="pj-t-title" placeholder="업무 제목" /></label>
<label>설명<textarea id="pj-t-desc" rows="2"></textarea></label>
<div class="pj-form-row">
<label>담당자
<select id="pj-t-assignee"><option value="">불러오는 중…</option></select>
</label>
<label>단계
<select id="pj-t-stage"><option value="">불러오는 중…</option></select>
</label>
</div>
<div class="pj-form-row">
<label>우선순위
<select id="pj-t-priority">
<option value="low">낮음</option>
<option value="normal" selected>보통</option>
<option value="high">높음</option>
</select>
</label>
<label>시작일<input type="date" id="pj-t-start" /></label>
<label>마감일<input type="date" id="pj-t-due" /></label>
</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-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-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 class="pj-modal-actions">
<button type="button" class="pj-btn pj-btn-danger" id="pj-delete-task">삭제</button>
<span style="flex:1"></span>
<a class="pj-btn" id="pj-t-open-project" href="#" target="_self">프로젝트에서 열기</a>
<button type="button" class="pj-btn" data-close>취소</button>
<button type="button" class="pj-btn pj-btn-primary" id="pj-save-task">저장</button>
</div>
</div>
</div>
<script id="pj-home-data" type="application/json">
{{ home_tasks | tojson }}
</script>
<script>
window.PJ_COLORS = {{ ["#4573d2","#37a3a3","#62a420","#e8a33d","#e8384f","#aa62e3","#f06a6a","#5a6772"] | tojson }};
</script>
<script src="/static/project.js?v=20260915b" defer></script>
<script src="/static/project.js?v=20260915d" defer></script>
{% endblock %}
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %}
{% block head_extra %}
<link rel="stylesheet" href="/static/project.css?v=20260915c" />
<link rel="stylesheet" href="/static/project.css?v=20260915e" />
<!-- 구글 머티리얼 심볼(담당자 아이콘 등) — self-host -->
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
<!-- 타임라인 vis-timeline — self-host -->
@@ -291,5 +291,5 @@
</script>
<script src="/static/vendor/vis-timeline/vis-timeline-graph2d.min.js"></script>
<script src="/static/project.js?v=20260915b" defer></script>
<script src="/static/project.js?v=20260915d" defer></script>
{% endblock %}