fix(project): 완료 프로젝트 숨기기 버튼 동작 안 하던 CSS 특이도 문제 수정

- .pj-project-grid[hidden] 규칙 추가 — display:grid 규칙과 특이도가 같아
  [hidden] 속성이 무시되던 버그
- 홈 카드 업무 미리보기에 단계(stage_name) 칩 표시
- 홈 화면 날짜 표기를 yy/mm/dd(요일) 형식으로 통일(프로젝트 마감·업무 마감·내 업무 기간)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 09:02:31 +09:00
parent a09ceee7a5
commit ef05f9d362
5 changed files with 34 additions and 8 deletions
+19
View File
@@ -191,6 +191,20 @@ def _render_config_needed(request: Request, user: dict[str, Any]) -> HTMLRespons
) )
_WEEKDAYS_KR = ["", "", "", "", "", "", ""]
def _fmt_date_kr(d: str | None) -> str:
"""'YYYY-MM-DD''yy/mm/dd(요일)'. 형식이 아니면 원본 그대로."""
if not d:
return ""
try:
dd = date.fromisoformat(d[:10])
except (ValueError, TypeError):
return d
return dd.strftime("%y/%m/%d") + f"({_WEEKDAYS_KR[dd.weekday()]})"
def _build_tree(projects: list[dict[str, Any]]) -> list[dict[str, Any]]: def _build_tree(projects: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""평면 프로젝트 목록 → parent_id 기준 트리(children 키).""" """평면 프로젝트 목록 → parent_id 기준 트리(children 키)."""
by_id: dict[int, dict[str, Any]] = {} by_id: dict[int, dict[str, Any]] = {}
@@ -367,13 +381,18 @@ async def index(request: Request) -> HTMLResponse:
) )
d = t.get("due_date") d = t.get("due_date")
t["due_overdue"] = bool(d and d[:10] < today_iso and not t.get("completed_at")) t["due_overdue"] = bool(d and d[:10] < today_iso and not t.get("completed_at"))
t["due_label"] = _fmt_date_kr(d)
p["task_total"] = total p["task_total"] = total
p["task_done"] = len(done_tasks) p["task_done"] = len(done_tasks)
p["task_percent"] = round(len(done_tasks) * 100 / total) if total else 0 p["task_percent"] = round(len(done_tasks) * 100 / total) if total else 0
p["task_preview"] = ordered_tasks[:8] p["task_preview"] = ordered_tasks[:8]
p["task_more"] = max(0, len(ordered_tasks) - 8) p["task_more"] = max(0, len(ordered_tasks) - 8)
p["due_label"] = _fmt_date_kr(p.get("due_date"))
my_tasks = st.list_tasks(assignee_email=user["email"], limit=500) my_tasks = st.list_tasks(assignee_email=user["email"], limit=500)
for t in my_tasks:
t["start_label"] = _fmt_date_kr(t.get("start_date"))
t["due_label"] = _fmt_date_kr(t.get("due_date"))
# 내 업무를 프로젝트별로 묶어 트리로 표시 # 내 업무를 프로젝트별로 묶어 트리로 표시
my_groups: dict[int, dict[str, Any]] = {} my_groups: dict[int, dict[str, Any]] = {}
for t in my_tasks: for t in my_tasks:
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %} {% extends "erp_base.html" %}
{% block head_extra %} {% block head_extra %}
<link rel="stylesheet" href="/static/project.css?v=20260915b" /> <link rel="stylesheet" href="/static/project.css?v=20260915c" />
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" /> <link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
{% endblock %} {% endblock %}
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %} {% extends "erp_base.html" %}
{% block head_extra %} {% block head_extra %}
<link rel="stylesheet" href="/static/project.css?v=20260915b" /> <link rel="stylesheet" href="/static/project.css?v=20260915c" />
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" /> <link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
{% endblock %} {% endblock %}
@@ -29,7 +29,7 @@
</div> </div>
{% if p.description %}<div class="pj-project-desc">{{ p.description }}</div>{% endif %} {% if p.description %}<div class="pj-project-desc">{{ p.description }}</div>{% endif %}
<div class="pj-project-meta"> <div class="pj-project-meta">
{% if p.due_date %}<span class="pj-chip">마감 {{ p.due_date }}</span>{% endif %} {% if p.due_date %}<span class="pj-chip">마감 {{ p.due_label }}</span>{% endif %}
{% if p.task_total %}<span class="pj-chip">업무 {{ p.task_done }}/{{ p.task_total }}</span>{% endif %} {% if p.task_total %}<span class="pj-chip">업무 {{ p.task_done }}/{{ p.task_total }}</span>{% endif %}
</div> </div>
{% if p.task_total %} {% if p.task_total %}
@@ -52,6 +52,7 @@
<a href="/project/p/{{ p.id }}?task={{ t.id }}"> <a href="/project/p/{{ p.id }}?task={{ t.id }}">
<span class="material-symbols-outlined pj-project-task-ic">{% if t.completed_at %}task_alt{% else %}radio_button_unchecked{% endif %}</span> <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> <span class="pj-project-task-title">{{ t.title }}</span>
{% if t.stage_name %}<span class="pj-project-task-stage">{{ t.stage_name }}</span>{% endif %}
{% if t.assignee_display %} {% if t.assignee_display %}
<span class="pj-project-task-assignee"> <span class="pj-project-task-assignee">
{% if avatars.get(t.assignee_email | lower) %} {% if avatars.get(t.assignee_email | lower) %}
@@ -61,7 +62,7 @@
</span> </span>
{% endif %} {% endif %}
{% if t.due_date %} {% if t.due_date %}
<span class="pj-project-task-due {% if t.due_overdue %}is-overdue{% endif %}">{{ t.due_date[:10] }}</span> <span class="pj-project-task-due {% if t.due_overdue %}is-overdue{% endif %}">{{ t.due_label }}</span>
{% endif %} {% endif %}
</a> </a>
</li> </li>
@@ -143,9 +144,9 @@
<span class="pj-mytree-title">{{ t.title }}</span> <span class="pj-mytree-title">{{ t.title }}</span>
{% if t.start_date or t.due_date %} {% if t.start_date or t.due_date %}
<span class="pj-mytree-dates"> <span class="pj-mytree-dates">
{% if t.start_date and t.due_date %}{{ t.start_date }} ~ {{ t.due_date }} {% if t.start_date and t.due_date %}{{ t.start_label }} ~ {{ t.due_label }}
{% elif t.due_date %}~ {{ t.due_date }} {% elif t.due_date %}~ {{ t.due_label }}
{% else %}{{ t.start_date }} ~{% endif %} {% else %}{{ t.start_label }} ~{% endif %}
</span> </span>
{% endif %} {% endif %}
</span> </span>
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %} {% extends "erp_base.html" %}
{% block head_extra %} {% block head_extra %}
<link rel="stylesheet" href="/static/project.css?v=20260915b" /> <link rel="stylesheet" href="/static/project.css?v=20260915c" />
<!-- 구글 머티리얼 심볼(담당자 아이콘 등) — self-host --> <!-- 구글 머티리얼 심볼(담당자 아이콘 등) — self-host -->
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" /> <link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
<!-- 타임라인 vis-timeline — self-host --> <!-- 타임라인 vis-timeline — self-host -->
+6
View File
@@ -34,6 +34,7 @@
@media (max-width: 980px) { .pj-home { grid-template-columns: 1fr; } } @media (max-width: 980px) { .pj-home { grid-template-columns: 1fr; } }
.pj-project-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 14px; align-items: start; } .pj-project-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 14px; align-items: start; }
.pj-project-grid[hidden] { display: none; }
.pj-project-card { .pj-project-card {
position: relative; border: 1px solid #e7e9ec; border-radius: 14px; position: relative; border: 1px solid #e7e9ec; border-radius: 14px;
background: #fff; transition: box-shadow .14s, transform .1s; background: #fff; transition: box-shadow .14s, transform .1s;
@@ -72,6 +73,11 @@
font-size: 12.5px; font-weight: 600; flex: 1; min-width: 0; font-size: 12.5px; font-weight: 600; flex: 1; min-width: 0;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
} }
.pj-project-task-stage {
flex: 0 0 auto; font-size: 10px; font-weight: 700; color: #6b7280;
background: #eef0f2; border-radius: 999px; padding: 1px 7px;
max-width: 70px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pj-project-task-assignee { .pj-project-task-assignee {
display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: #6b7280; display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: #6b7280;
flex: 0 0 auto; max-width: 90px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 0 0 auto; max-width: 90px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;