refactor(i18n): 사용자 문구 참조를 중앙화
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { copy } from '@/shared/i18n';
|
||||
import { useFocusStats } from '@/features/stats';
|
||||
|
||||
const StatSection = ({
|
||||
@@ -42,45 +43,46 @@ const formatMinutes = (minutes: number) => {
|
||||
};
|
||||
|
||||
export const StatsOverviewWidget = () => {
|
||||
const { stats } = copy;
|
||||
const { summary, isLoading, error, source, refetch } = useFocusStats();
|
||||
const todayItems = [
|
||||
{
|
||||
id: 'today-focus',
|
||||
label: '오늘 집중 시간',
|
||||
label: stats.todayFocus,
|
||||
value: formatMinutes(summary.today.focusMinutes),
|
||||
delta: source === 'api' ? 'API' : 'Mock',
|
||||
delta: source === 'api' ? stats.apiLabel : stats.mockLabel,
|
||||
},
|
||||
{
|
||||
id: 'today-cycles',
|
||||
label: '완료한 사이클',
|
||||
value: `${summary.today.completedCycles}회`,
|
||||
label: stats.completedCycles,
|
||||
value: `${summary.today.completedCycles}${stats.countUnit}`,
|
||||
delta: `${summary.today.completedCycles > 0 ? '+' : ''}${summary.today.completedCycles}`,
|
||||
},
|
||||
{
|
||||
id: 'today-entry',
|
||||
label: '입장 횟수',
|
||||
value: `${summary.today.sessionEntries}회`,
|
||||
delta: source === 'api' ? '동기화됨' : '임시값',
|
||||
label: stats.sessionEntries,
|
||||
value: `${summary.today.sessionEntries}${stats.countUnit}`,
|
||||
delta: source === 'api' ? stats.syncedApi : stats.temporary,
|
||||
},
|
||||
];
|
||||
const weeklyItems = [
|
||||
{
|
||||
id: 'week-focus',
|
||||
label: '최근 7일 집중 시간',
|
||||
label: stats.last7DaysFocus,
|
||||
value: formatMinutes(summary.last7Days.focusMinutes),
|
||||
delta: source === 'api' ? '실집계' : '목업',
|
||||
delta: source === 'api' ? stats.actualAggregate : stats.mockAggregate,
|
||||
},
|
||||
{
|
||||
id: 'week-best-day',
|
||||
label: '최고 몰입일',
|
||||
label: stats.bestDay,
|
||||
value: summary.last7Days.bestDayLabel,
|
||||
delta: formatMinutes(summary.last7Days.bestDayFocusMinutes),
|
||||
},
|
||||
{
|
||||
id: 'week-consistency',
|
||||
label: '연속 달성',
|
||||
value: `${summary.last7Days.streakDays}일`,
|
||||
delta: summary.last7Days.streakDays > 0 ? '유지 중' : '시작 전',
|
||||
label: stats.streak,
|
||||
value: `${summary.last7Days.streakDays}${stats.dayUnit}`,
|
||||
delta: summary.last7Days.streakDays > 0 ? stats.streakActive : stats.streakStart,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -88,12 +90,12 @@ export const StatsOverviewWidget = () => {
|
||||
<div className="min-h-screen bg-[radial-gradient(circle_at_18%_0%,rgba(167,204,237,0.45),transparent_50%),radial-gradient(circle_at_88%_8%,rgba(191,219,254,0.4),transparent_42%),linear-gradient(170deg,#f8fafc_0%,#eef4fb_52%,#e9f1fa_100%)] text-brand-dark">
|
||||
<div className="mx-auto w-full max-w-6xl px-4 pb-10 pt-6 sm:px-6">
|
||||
<header className="mb-6 flex items-center justify-between rounded-xl border border-brand-dark/12 bg-white/72 px-4 py-3 backdrop-blur-sm">
|
||||
<h1 className="text-xl font-semibold">Stats</h1>
|
||||
<h1 className="text-xl font-semibold">{stats.title}</h1>
|
||||
<Link
|
||||
href="/app"
|
||||
className="rounded-lg border border-brand-dark/16 px-3 py-1.5 text-xs text-brand-dark/82 transition hover:bg-white/90"
|
||||
>
|
||||
허브로
|
||||
{copy.common.hub}
|
||||
</Link>
|
||||
</header>
|
||||
|
||||
@@ -102,13 +104,13 @@ export const StatsOverviewWidget = () => {
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<div>
|
||||
<p className="text-xs font-medium text-brand-dark/72">
|
||||
{source === 'api' ? 'API 통계 사용 중' : 'API 실패로 mock 통계 표시 중'}
|
||||
{source === 'api' ? stats.sourceApi : stats.sourceMock}
|
||||
</p>
|
||||
{error ? (
|
||||
<p className="mt-1 text-xs text-rose-500">{error}</p>
|
||||
) : (
|
||||
<p className="mt-1 text-xs text-brand-dark/56">
|
||||
{isLoading ? '통계를 불러오는 중이에요.' : '화면 진입 시 최신 요약을 동기화합니다.'}
|
||||
{isLoading ? stats.loading : stats.synced}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -119,16 +121,16 @@ export const StatsOverviewWidget = () => {
|
||||
}}
|
||||
className="rounded-lg border border-brand-dark/16 px-3 py-1.5 text-xs text-brand-dark/82 transition hover:bg-white/90"
|
||||
>
|
||||
새로고침
|
||||
{stats.refresh}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<StatSection title="오늘" items={todayItems} />
|
||||
<StatSection title="최근 7일" items={weeklyItems} />
|
||||
<StatSection title={stats.today} items={todayItems} />
|
||||
<StatSection title={stats.last7Days} items={weeklyItems} />
|
||||
|
||||
<section className="space-y-3">
|
||||
<h2 className="text-lg font-semibold text-brand-dark">집중 흐름 그래프</h2>
|
||||
<h2 className="text-lg font-semibold text-brand-dark">{stats.chartTitle}</h2>
|
||||
<div className="rounded-xl border border-dashed border-brand-dark/18 bg-white/65 p-5">
|
||||
<div className="h-52 rounded-lg border border-brand-dark/12 bg-[linear-gradient(180deg,rgba(148,163,184,0.15),rgba(148,163,184,0.04))] p-4">
|
||||
{summary.trend.length > 0 ? (
|
||||
@@ -141,7 +143,7 @@ export const StatsOverviewWidget = () => {
|
||||
<div
|
||||
className="w-full rounded-md bg-brand-primary/55"
|
||||
style={{ height: `${barHeight}%` }}
|
||||
title={`${point.date} · ${point.focusMinutes}분`}
|
||||
title={stats.barTitle(point.date, point.focusMinutes)}
|
||||
/>
|
||||
<span className="text-[10px] text-brand-dark/56">
|
||||
{point.date.slice(5)}
|
||||
@@ -154,8 +156,8 @@ export const StatsOverviewWidget = () => {
|
||||
</div>
|
||||
<p className="mt-3 text-xs text-brand-dark/56">
|
||||
{summary.trend.length > 0
|
||||
? 'trend 응답으로 간단한 막대 그래프를 렌더링합니다.'
|
||||
: 'trend 응답이 비어 있어 플레이스홀더 상태입니다.'}
|
||||
? stats.chartWithTrend
|
||||
: stats.chartWithoutTrend}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user