feat(cupang): 달력을 2개월(현재+다음 달) 뷰로 개편
- index 라우터가 두 달치 출고를 합쳐(중복 id 제거) 달 별 격자 2개를 생성 - 상단 바: ‹ › 이동 + 기간 제목 + [오늘], 달마다 머리글/월 출고 합계 배지 - 셀 UI 정리: 오늘=검은 원, 선택=링, 출고일=건수/센터수 태그, 타월은 흐리게 - 범례 추가, 1200px 이하에서는 두 달을 세로로 배치 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -129,11 +129,18 @@ async def index(request: Request) -> HTMLResponse:
|
||||
store, user = guard
|
||||
|
||||
year, month = _ym(request)
|
||||
next_y, next_m = (year + 1, 1) if month == 12 else (year, month + 1)
|
||||
prev_y, prev_m = (year - 1, 12) if month == 1 else (year, month - 1)
|
||||
|
||||
# 달력은 현재 달 + 다음 달 2개월을 함께 보여준다.
|
||||
# 취소된 묶음은 달력·목록 어디에도 보이지 않는다(soft delete = 삭제로 취급).
|
||||
shipments = [
|
||||
s for s in store.list_shipments(year=year, month=month)
|
||||
if s.get("status") != "취소"
|
||||
]
|
||||
merged: dict[Any, dict[str, Any]] = {}
|
||||
for y, m in ((year, month), (next_y, next_m)):
|
||||
for s in store.list_shipments(year=y, month=m):
|
||||
if s.get("status") == "취소":
|
||||
continue
|
||||
merged[s["id"]] = s
|
||||
shipments = list(merged.values())
|
||||
|
||||
# 달력 칸에는 출고 건수와 센터 수만 보여준다.
|
||||
counts: dict[str, dict[str, Any]] = {}
|
||||
@@ -147,11 +154,12 @@ async def index(request: Request) -> HTMLResponse:
|
||||
for cell in counts.values():
|
||||
cell["centers"] = len(cell["centers"])
|
||||
|
||||
# 선택 날짜 (기본: 오늘이 이번 달이면 오늘, 아니면 1일)
|
||||
# 선택 날짜 (기본: 오늘이 보이는 두 달 안이면 오늘, 아니면 첫 달 1일)
|
||||
sel = request.query_params.get("date") or ""
|
||||
today = today_kst()
|
||||
if not sel:
|
||||
sel = today.isoformat() if (today.year == year and today.month == month) else f"{year:04d}-{month:02d}-01"
|
||||
in_view = (today.year, today.month) in ((year, month), (next_y, next_m))
|
||||
sel = today.isoformat() if in_view else f"{year:04d}-{month:02d}-01"
|
||||
|
||||
# 선택일의 묶음 — 달력과 같은 기준(출고일)
|
||||
sel_shipments = [s for s in shipments if s.get("ship_date") == sel]
|
||||
@@ -172,27 +180,39 @@ async def index(request: Request) -> HTMLResponse:
|
||||
s["total_boxes"] = sum(it["boxes"] for it in s["items"])
|
||||
|
||||
cal = _calendar.Calendar(firstweekday=6) # 일요일 시작
|
||||
weeks = cal.monthdatescalendar(year, month)
|
||||
cal_weeks = [
|
||||
[
|
||||
{
|
||||
"date": d.isoformat(),
|
||||
"day": d.day,
|
||||
"in_month": d.month == month,
|
||||
"is_today": d == today,
|
||||
"is_selected": d.isoformat() == sel,
|
||||
"is_sunday": d.weekday() == 6,
|
||||
"is_saturday": d.weekday() == 5,
|
||||
"is_holiday": is_holiday(d),
|
||||
"counts": counts.get(d.isoformat(), {}),
|
||||
}
|
||||
for d in week
|
||||
]
|
||||
for week in weeks
|
||||
]
|
||||
|
||||
prev_y, prev_m = (year - 1, 12) if month == 1 else (year, month - 1)
|
||||
next_y, next_m = (year + 1, 1) if month == 12 else (year, month + 1)
|
||||
def _build_month(y: int, m: int) -> dict[str, Any]:
|
||||
weeks = [
|
||||
[
|
||||
{
|
||||
"date": d.isoformat(),
|
||||
"day": d.day,
|
||||
"in_month": d.month == m,
|
||||
"is_today": d == today,
|
||||
"is_selected": d.isoformat() == sel,
|
||||
"is_sunday": d.weekday() == 6,
|
||||
"is_saturday": d.weekday() == 5,
|
||||
"is_holiday": is_holiday(d),
|
||||
"counts": counts.get(d.isoformat(), {}),
|
||||
}
|
||||
for d in week
|
||||
]
|
||||
for week in cal.monthdatescalendar(y, m)
|
||||
]
|
||||
ship_total = sum(
|
||||
int(c.get("ship") or 0)
|
||||
for day, c in counts.items()
|
||||
if day[:7] == f"{y:04d}-{m:02d}"
|
||||
)
|
||||
return {
|
||||
"year": y,
|
||||
"month": m,
|
||||
"label": f"{y}년 {m}월",
|
||||
"weeks": weeks,
|
||||
"ship_total": ship_total,
|
||||
}
|
||||
|
||||
months = [_build_month(year, month), _build_month(next_y, next_m)]
|
||||
|
||||
return render_template(
|
||||
request,
|
||||
@@ -202,13 +222,14 @@ async def index(request: Request) -> HTMLResponse:
|
||||
"is_admin": is_admin(user),
|
||||
"nav_items": build_erp_nav(user, active="cupang"),
|
||||
"page_title": "쿠팡 밀크런",
|
||||
"page_subtitle": f"{year}년 {month}월 출고 일정",
|
||||
"page_subtitle": f"{year}년 {month}월 · {next_y}년 {next_m}월 출고 일정",
|
||||
"year": year,
|
||||
"month": month,
|
||||
"prev_y": prev_y, "prev_m": prev_m,
|
||||
"next_y": next_y, "next_m": next_m,
|
||||
"today_iso": today.isoformat(),
|
||||
"weekdays": ["일", "월", "화", "수", "목", "금", "토"],
|
||||
"cal_weeks": cal_weeks,
|
||||
"months": months,
|
||||
"selected_date": sel,
|
||||
"sel_shipments": sel_shipments,
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901f" />{% endblock %}
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901g" />{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="cpg">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901f" />{% endblock %}
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901g" />{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="cpg">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901f" />{% endblock %}
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901g" />{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="cpg">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901f" />{% endblock %}
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901g" />{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="cpg">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901f" />{% endblock %}
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901g" />{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="cpg">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901f" />{% endblock %}
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901g" />{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="cpg">
|
||||
@@ -19,40 +19,62 @@
|
||||
</div>
|
||||
|
||||
<div class="cpg-layout">
|
||||
<!-- ── 왼쪽: 큰 월간 달력 ── -->
|
||||
<!-- ── 왼쪽: 2개월 월간 달력 ── -->
|
||||
<div class="erp-card cpg-cal-card">
|
||||
<div class="cpg-cal-head">
|
||||
<a class="erp-btn erp-btn-outline cpg-nav-btn"
|
||||
href="/cupang/?year={{ prev_y }}&month={{ prev_m }}">‹</a>
|
||||
<h2 class="cpg-cal-title">{{ year }}년 {{ month }}월</h2>
|
||||
<a class="erp-btn erp-btn-outline cpg-nav-btn"
|
||||
href="/cupang/?year={{ next_y }}&month={{ next_m }}">›</a>
|
||||
<div class="cpg-mcal-bar">
|
||||
<div class="cpg-mcal-nav">
|
||||
<a class="cpg-mcal-navbtn" title="이전 달"
|
||||
href="/cupang/?year={{ prev_y }}&month={{ prev_m }}">‹</a>
|
||||
<a class="cpg-mcal-navbtn" title="다음 달"
|
||||
href="/cupang/?year={{ next_y }}&month={{ next_m }}">›</a>
|
||||
</div>
|
||||
<h2 class="cpg-mcal-range">{{ months[0].label }} – {{ months[1].label }}</h2>
|
||||
<a class="cpg-mcal-todaybtn" href="/cupang/">오늘</a>
|
||||
</div>
|
||||
|
||||
<div class="cpg-cal-grid">
|
||||
{% for wd in weekdays %}
|
||||
<div class="cpg-cal-wd {% if loop.index0 == 0 %}cpg-sun{% elif loop.index0 == 6 %}cpg-sat{% endif %}">{{ wd }}</div>
|
||||
{% endfor %}
|
||||
<div class="cpg-mcal-months">
|
||||
{% for m in months %}
|
||||
<section class="cpg-mcal{% if loop.first %} is-current{% endif %}">
|
||||
<header class="cpg-mcal-h">
|
||||
<span class="cpg-mcal-mname">{{ m.month }}월</span>
|
||||
<span class="cpg-mcal-myear">{{ m.year }}</span>
|
||||
{% if m.ship_total %}
|
||||
<span class="cpg-mcal-total">출고 {{ m.ship_total }}건</span>
|
||||
{% endif %}
|
||||
</header>
|
||||
|
||||
{% for week in cal_weeks %}
|
||||
{% for cell in week %}
|
||||
<a class="cpg-cal-cell
|
||||
{% if not cell.in_month %}cpg-out{% endif %}
|
||||
{% if cell.is_today %}cpg-today{% endif %}
|
||||
{% if cell.is_selected %}cpg-selected{% endif %}
|
||||
{% if cell.is_sunday or cell.is_holiday %}cpg-red{% elif cell.is_saturday %}cpg-blue{% endif %}"
|
||||
href="/cupang/?year={{ year }}&month={{ month }}&date={{ cell.date }}">
|
||||
<span class="cpg-cal-day">{{ cell.day }}</span>
|
||||
<span class="cpg-cal-badges">
|
||||
{% if cell.counts.ship %}
|
||||
<span class="erp-badge erp-badge-inverse cpg-mini">출고 {{ cell.counts.ship }}건</span>
|
||||
<span class="erp-badge erp-badge-neutral cpg-mini">센터 {{ cell.counts.centers }}곳</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
<div class="cpg-mcal-grid">
|
||||
{% for wd in weekdays %}
|
||||
<div class="cpg-mcal-wd {% if loop.index0 == 0 %}is-sun{% elif loop.index0 == 6 %}is-sat{% endif %}">{{ wd }}</div>
|
||||
{% endfor %}
|
||||
|
||||
{% for week in m.weeks %}
|
||||
{% for cell in week %}
|
||||
<a class="cpg-mcal-cell
|
||||
{% if not cell.in_month %}is-out{% endif %}
|
||||
{% if cell.is_today %}is-today{% endif %}
|
||||
{% if cell.is_selected %}is-selected{% endif %}
|
||||
{% if cell.counts.ship %}has-ship{% endif %}
|
||||
{% if cell.is_sunday or cell.is_holiday %}is-red{% elif cell.is_saturday %}is-blue{% endif %}"
|
||||
href="/cupang/?year={{ year }}&month={{ month }}&date={{ cell.date }}"
|
||||
title="{{ cell.date }}{% if cell.counts.ship %} — 출고 {{ cell.counts.ship }}건 / 센터 {{ cell.counts.centers }}곳{% endif %}">
|
||||
<span class="cpg-mcal-day">{{ cell.day }}</span>
|
||||
{% if cell.counts.ship %}
|
||||
<span class="cpg-mcal-tag">{{ cell.counts.ship }}건<em>{{ cell.counts.centers }}곳</em></span>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="cpg-mcal-legend">
|
||||
<span><i class="cpg-lg-dot"></i>출고 있는 날</span>
|
||||
<span><i class="cpg-lg-today"></i>오늘</span>
|
||||
<span><i class="cpg-lg-sel"></i>선택한 날</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── 오른쪽: 선택일 출고 묶음 리스트 ── -->
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901f" />{% endblock %}
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901g" />{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="cpg">
|
||||
|
||||
Reference in New Issue
Block a user