feat(project): 홈 프로젝트 카드 수정 버튼 + 완료 프로젝트 비활성/숨기기

- 카드에 수정(연필) 버튼: 이름·설명·기간·색상·완료 토글 + 삭제 (관리자)
- 프로젝트 상태에 'completed' 추가, update_project status 허용
- 완료 프로젝트: 흐리게 비활성 + 홈 목록 맨 끝으로 정렬
- '완료 N개 숨기기' 토글 버튼(localStorage 기억)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-25 16:34:09 +09:00
parent e36fcb1a34
commit 17f2d56231
5 changed files with 165 additions and 35 deletions
+2
View File
@@ -319,6 +319,8 @@ async def index(request: Request) -> HTMLResponse:
include_archived=False,
member_email=None if admin else user["email"],
)
# 완료된 프로젝트는 비활성으로 맨 끝에 (홈 카드 정렬). 안정 정렬.
projects = sorted(projects, key=lambda p: 1 if p.get("status") == "completed" else 0)
tree = _build_tree(projects)
my_tasks = st.list_tasks(assignee_email=user["email"], limit=500)
# 내 업무를 프로젝트별로 묶어 트리로 표시
+1 -1
View File
@@ -22,7 +22,7 @@ PRIORITIES: tuple[str, ...] = ("low", "normal", "high")
PRIORITY_LABELS: dict[str, str] = {"low": "낮음", "normal": "보통", "high": "높음"}
# 프로젝트 상태
PROJECT_STATUSES: tuple[str, ...] = ("active", "archived")
PROJECT_STATUSES: tuple[str, ...] = ("active", "completed", "archived")
# 멤버 역할
MEMBER_ROLES: tuple[str, ...] = ("manager", "member")
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %}
{% block head_extra %}
<link rel="stylesheet" href="/static/project.css?v=20260625q" />
<link rel="stylesheet" href="/static/project.css?v=20260625r" />
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
{% endblock %}
@@ -14,6 +14,12 @@
<div class="pj-section-head">
<h2>프로젝트</h2>
<div class="pj-toolbar-right">
{% set done_count = tree | selectattr('status', 'equalto', 'completed') | list | length %}
{% if done_count %}
<button type="button" class="pj-btn" id="pj-toggle-done" data-count="{{ done_count }}">
완료 {{ done_count }}개 숨기기
</button>
{% endif %}
<a class="pj-bell" href="/project/inbox" title="알림센터">
<span class="material-symbols-outlined">notifications</span>
<span class="pj-bell-badge" id="pj-bell-badge" hidden>0</span>
@@ -34,19 +40,35 @@
</div>
{% endif %}
<div class="pj-project-grid">
<div class="pj-project-grid" id="pj-project-grid">
{% for p in tree %}
<a class="pj-project-card" href="/project/p/{{ p.id }}"
style="--pj-color: {{ p.color }}">
<span class="pj-project-dot"></span>
<div class="pj-project-body">
<div class="pj-project-name">{{ p.name }}</div>
{% if p.description %}<div class="pj-project-desc">{{ p.description }}</div>{% endif %}
<div class="pj-project-meta">
{% if p.due_date %}<span class="pj-chip">마감 {{ p.due_date }}</span>{% endif %}
<div class="pj-project-card {% if p.status == 'completed' %}is-done{% endif %}"
style="--pj-color: {{ p.color }}"
data-id="{{ p.id }}"
data-name="{{ p.name }}"
data-desc="{{ p.description or '' }}"
data-start="{{ p.start_date or '' }}"
data-due="{{ p.due_date or '' }}"
data-color="{{ p.color }}"
data-status="{{ p.status or 'active' }}">
<a class="pj-project-cardlink" href="/project/p/{{ p.id }}">
<span class="pj-project-dot"></span>
<div class="pj-project-body">
<div class="pj-project-name">{{ p.name }}
{% if p.status == 'completed' %}<span class="pj-chip pj-chip-sm">완료</span>{% endif %}
</div>
{% if p.description %}<div class="pj-project-desc">{{ p.description }}</div>{% endif %}
<div class="pj-project-meta">
{% if p.due_date %}<span class="pj-chip">마감 {{ p.due_date }}</span>{% endif %}
</div>
</div>
</div>
</a>
</a>
{% if is_admin %}
<button type="button" class="pj-icon-btn pj-card-edit" title="프로젝트 수정">
<span class="material-symbols-outlined">edit</span>
</button>
{% endif %}
</div>
{% endfor %}
</div>
</section>
@@ -111,10 +133,36 @@
</div>
</div>
</div>
<!-- 프로젝트 수정 모달 -->
<div class="pj-modal" id="pj-modal-edit" hidden>
<div class="pj-modal-card">
<h3>프로젝트 수정</h3>
<input type="hidden" id="pj-e-id" />
<label>이름<input type="text" id="pj-e-name" placeholder="프로젝트 이름" /></label>
<label>설명<textarea id="pj-e-desc" rows="2"></textarea></label>
<div class="pj-form-row">
<label>시작일<input type="date" id="pj-e-start" /></label>
<label>마감일<input type="date" id="pj-e-due" /></label>
</div>
<label>색상
<span class="pj-color-palette" id="pj-e-colors"></span>
</label>
<label class="pj-time-toggle">
<input type="checkbox" id="pj-e-done" /> 프로젝트 완료 (목록 끝으로 이동·비활성)
</label>
<div class="pj-modal-actions">
<button type="button" class="pj-btn pj-btn-danger" id="pj-delete-project">삭제</button>
<span style="flex:1"></span>
<button type="button" class="pj-btn" data-close>취소</button>
<button type="button" class="pj-btn pj-btn-primary" id="pj-save-edit">저장</button>
</div>
</div>
</div>
{% endif %}
<script>
window.PJ_COLORS = {{ ["#4573d2","#37a3a3","#62a420","#e8a33d","#e8384f","#aa62e3","#f06a6a","#5a6772"] | tojson }};
</script>
<script src="/static/project.js?v=20260625q" defer></script>
<script src="/static/project.js?v=20260625r" defer></script>
{% endblock %}