HQ Mode — 재생 리포트 (apps/space /admin/playback)
SPEC #175 (#172 FU-2) 도입. 실 HQ_MANAGER 직접 로그인 표면(apps/space · space.linkmusic.io).
Overview
본사(HQ_MANAGER)가 산하 매장 음악 재생을 일 / 주 / 월 / 커스텀 4 탭으로 조망하는 전용
페이지다. #172 로 playback_daily_rollup(매장×일 KST: played_ms_total·played_ms_trust·
track_count·track_count_trust)에 데이터가 쌓이지만 조회 화면이 없던 갭을 메운다. 롤업 위에
조회 endpoint(getHqPlaybackSummary)를 얹고 그 위에 리포트 UI 를 그린다. BE 변경 없이 각 탭이
같은 endpoint 를 적절한 bucket·from·to 로 호출한다.
- 집계 축(D2): 상단 전사 KPI(총 재생시간·곡수·신탁 비율) + 하단 매장별 테이블(전 탭 공유).
- 표시(D3): 일별 추이 차트(커스텀 SVG·라이브러리 없음) + 숫자 표. 일 탭만 차트 생략(하루라 막대 1개 무의미).
- 신탁/비신탁 분리: 재생시간·곡수를 신탁(TRUST)과 일반으로 나눠 표기.
- 이번 제외: 준수율(영업시간 대비)은 FU-1(영업시간 저장) 선행 필요 → 후속에 열 추가. CSV 내보내기·KOMCA 내보내기(FU-3)도 별개 후속.
시안: 전용 시안 부재. 운영사
apps/adminsignup-trend.tsx·stat-dist-card.tsx(막대 추이·차트 라이브러리 없음) 패턴을 미러하되 신탁/일반 스택 막대로 확장. atom-grounded(design-debt 등재).
페이지 구조 (/admin/playback)
page.tsx(server 셸, force-dynamic) + playback-report-client.tsx(client). 본사 모드 셸
layout(/admin/layout.tsx)이 role 가드 + HQShell 을 제공하므로 별도 refresh-aware 가드 없이
client 영역만 렌더한다(감사(#067) 페이지 미러). 조회 파라미터는 client state(단일 페이지라
deep-link 가치 낮음). BFF 토큰은 generated apiFetch 가 /api/backend/... 경유로 서버 전용 보존.
기간 컨트롤 — 4 탭 (hq-playback-tab-{day|week|month|custom})
상단에 공용 Tabs atom 으로 일 / 주 / 월 / 커스텀 탭을 두고, 선택된 탭에 맞는 컨트롤을 아래에
노출한다. 각 탭은 자기 상태를 독립 보유하며 진입 시 아래 기본값을 쓴다. KST 날짜 산술은 UTC 자정
기준으로 결정적 계산(주=월요일 시작·월=1일~말일).
| 탭 | 컨트롤 | 조회 파라미터 | 추이 차트 | 기본값 |
|---|---|---|---|---|
| 일 | 날짜 피커 1개 <input type="date">(hq-playback-day) | bucket=DAY, from=to=선택일 | 생략(하루 막대 1개 무의미) | 오늘(KST) |
| 주 | 최근 12주 드롭다운(hq-playback-week-select, 라벨 M/D ~ M/D·월~일) | bucket=DAY, from=월요일, to=일요일 | 그 주 일별(7) | 이번 주 |
| 월 | 최근 12개월 드롭다운(hq-playback-month-select, 라벨 YYYY년 M월) | bucket=DAY, from=1일, to=말일 | 그 달 일별 | 이번 달 |
| 커스텀 | bucket 토글(일별 DAY·주별 WEEK·월별 MONTH·hq-playback-bucket-{DAY|WEEK|MONTH}) + <input type="date"> from/to(hq-playback-from·hq-playback-to) | 선택 bucket·범위 | 선택 bucket 별 | bucket=DAY · 최근 30일(to=오늘·from=to−29) |
- 사전 검증(주로 커스텀 탭):
from<=to· 범위 상한 366일(BE 검증 최대 1년과 일치)을 클라가 미리 reject(frontend.md §8) — 위반 시 fetch 를 막고(enabled:false) warn 배너(hq-playback-range-error)로 안내(불필요한 400 호출 절약). 일/주/월 탭은 경계가 항상 유효하다.
전사 KPI (total)
3 카드 — 총 재생시간(ms→“N시간 M분”·hq-playback-kpi-duration, 하위 신탁 재생시간)·총 재생 곡수
(hq-playback-kpi-tracks, 하위 신탁 곡수)·신탁 비율(played_ms_trust/played_ms_total %·hq-playback-kpi-trust).
로딩 중엔 ”—” 대시.
추이 차트 (series)
주/월/커스텀 탭에서만 렌더하고 일 탭은 생략한다(하루라 막대 1개뿐이라 무의미 — 섹션 자체를
그리지 않음). PlaybackTrendChart(playback-trend-chart.tsx, hq-playback-chart) — 버킷별 재생시간을 커스텀 SVG
<rect> 스택 막대로 그린다. 하단=신탁(primary)·상단=일반(primary soft tint) 스택, viewBox height=100
(최댓값 대비 %)·preserveAspectRatio="none" 가로 stretch. 각 버킷 열은 <g>(hq-playback-chart-bar-{i})
<title>(hover tooltip: 전체 라벨·총/신탁/일반 재생시간·곡수). x축 라벨은 SVG 밖 HTML(왜곡 회피, 버킷 많으면 최대 ~8개로 thinning). a11y:role="img"+ 요약 aria-label +figcaptionsr-only 데이터 요약 + 범례 (신탁/일반). 빈 series(또는 전부 0) → 빈 상태(hq-playback-chart-empty). 로딩 → “불러오는 중…”(hq-playback-trend-loading).
매장별 테이블 (stores)
hq-playback-stores-table — 매장(상세 링크 /admin/stores/{storeId} · hq-playback-store-link-{storeId})·재생시간·
신탁 재생시간·곡수·신탁 곡수. 재생시간 desc 는 BE 고정 정렬(FE 재정렬 없음). 빈 상태
(hq-playback-stores-empty)·로딩(hq-playback-stores-loading). 폐점 매장 포함(과거 재생분 보존).
폴링·에러
- 폴링: 리포트(실시간 아님)라
refetchIntervalInBackground:false명시(frontend.md §17 — refetchInterval 미설정, 의도만 남김). - 에러: fetch 실패 시 공용
ErrorState(hq-playback-error, 재시도=refetch). 401/403/5xx/네트워크(BACKEND_UNREACHABLE) 분리 매핑(본사 감사 idiom 미러).
진입점
- 대시보드 “재생 상태” 카드(
PlaybackStatusCard, HQ Mode 대시보드)에 [기간별 보기] 링크(hq-dash-playback-report-link→/admin/playback). “지금”(카드)과 “누적 조망”(리포트)의 두 축. - 본사 사이드바(
HQSidebar) — 대시보드 바로 아래 재생 리포트 항목(hq-nav-playback, iconBarChart3).
API
GET /api/v1/hq/playback/summary?bucket&from&to → HqPlaybackSummaryResponse.
hqId 토큰 주체 도출(타 본사 0)·claim↔DB 재검증 403·미인증 401. 상세는 endpoints.
Followups
- 준비율(영업시간 대비) — FU-1(영업시간 매장 저장) 선행 후 응답·테이블에 열 추가.
- CSV 내보내기 — 운영사 통계 CSV 패턴 미러(이번 제외).
- 신탁 KOMCA 내보내기(FU-3) — 외부 규격 게이트(별개).
- 매장별 페이지네이션 — 수백 매장×1년 대범위 성능 필요 시.