diff --git a/app/modules/project/router.py b/app/modules/project/router.py
index 6b85579..bb88aef 100644
--- a/app/modules/project/router.py
+++ b/app/modules/project/router.py
@@ -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]]:
"""평면 프로젝트 목록 → parent_id 기준 트리(children 키)."""
by_id: dict[int, dict[str, Any]] = {}
@@ -367,13 +381,18 @@ async def index(request: Request) -> HTMLResponse:
)
d = t.get("due_date")
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_done"] = len(done_tasks)
p["task_percent"] = round(len(done_tasks) * 100 / total) if total else 0
p["task_preview"] = 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)
+ 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]] = {}
for t in my_tasks:
diff --git a/app/modules/project/templates/project/inbox.html b/app/modules/project/templates/project/inbox.html
index 86d84b8..a9cb03e 100644
--- a/app/modules/project/templates/project/inbox.html
+++ b/app/modules/project/templates/project/inbox.html
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %}
{% block head_extra %}
-
+
{% endblock %}
diff --git a/app/modules/project/templates/project/index.html b/app/modules/project/templates/project/index.html
index f557814..1c7f947 100644
--- a/app/modules/project/templates/project/index.html
+++ b/app/modules/project/templates/project/index.html
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %}
{% block head_extra %}
-
+
{% endblock %}
@@ -29,7 +29,7 @@
{% if p.description %}
{{ p.description }}
{% endif %}
- {% if p.due_date %}마감 {{ p.due_date }}{% endif %}
+ {% if p.due_date %}마감 {{ p.due_label }}{% endif %}
{% if p.task_total %}업무 {{ p.task_done }}/{{ p.task_total }}{% endif %}
{% if p.task_total %}
@@ -52,6 +52,7 @@
{% if t.completed_at %}task_alt{% else %}radio_button_unchecked{% endif %}
{{ t.title }}
+ {% if t.stage_name %}{{ t.stage_name }}{% endif %}
{% if t.assignee_display %}
{% if avatars.get(t.assignee_email | lower) %}
@@ -61,7 +62,7 @@
{% endif %}
{% if t.due_date %}
- {{ t.due_date[:10] }}
+ {{ t.due_label }}
{% endif %}
@@ -143,9 +144,9 @@
{{ t.title }}
{% if t.start_date or t.due_date %}
- {% if t.start_date and t.due_date %}{{ t.start_date }} ~ {{ t.due_date }}
- {% elif t.due_date %}~ {{ t.due_date }}
- {% else %}{{ t.start_date }} ~{% endif %}
+ {% if t.start_date and t.due_date %}{{ t.start_label }} ~ {{ t.due_label }}
+ {% elif t.due_date %}~ {{ t.due_label }}
+ {% else %}{{ t.start_label }} ~{% endif %}
{% endif %}
diff --git a/app/modules/project/templates/project/project.html b/app/modules/project/templates/project/project.html
index cea81e5..bf26ec3 100644
--- a/app/modules/project/templates/project/project.html
+++ b/app/modules/project/templates/project/project.html
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %}
{% block head_extra %}
-
+
diff --git a/app/static/project.css b/app/static/project.css
index 5cecdb8..42b0d3a 100644
--- a/app/static/project.css
+++ b/app/static/project.css
@@ -34,6 +34,7 @@
@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[hidden] { display: none; }
.pj-project-card {
position: relative; border: 1px solid #e7e9ec; border-radius: 14px;
background: #fff; transition: box-shadow .14s, transform .1s;
@@ -72,6 +73,11 @@
font-size: 12.5px; font-weight: 600; flex: 1; min-width: 0;
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 {
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;