i18n 적용
This commit is contained in:
+44
-28
@@ -4,6 +4,8 @@ import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { FolderKanban, LayoutDashboard, ListChecks, SunMoon } from "lucide-react";
|
||||
import { useAppLocale } from "@/features/i18n/LocaleProvider";
|
||||
import { getDashboardMessages } from "@/features/i18n/messages/dashboard";
|
||||
|
||||
// 대시보드 페이지 (/dashboard)
|
||||
// - /api/dashboard/overview 에서 요약/프로젝트별 통계를 가져와 카드 형태로 렌더링한다.
|
||||
@@ -35,6 +37,8 @@ type PageStatus = "idle" | "loading" | "error";
|
||||
|
||||
export default function DashboardPage() {
|
||||
const router = useRouter();
|
||||
const locale = useAppLocale();
|
||||
const t = getDashboardMessages(locale);
|
||||
|
||||
const [summary, setSummary] = useState<SummaryStats | null>(null);
|
||||
const [projects, setProjects] = useState<ProjectStatsItem[]>([]);
|
||||
@@ -59,13 +63,13 @@ export default function DashboardPage() {
|
||||
|
||||
if (res.status === 401) {
|
||||
setStatus("error");
|
||||
setErrorMessage("대시보드를 보려면 로그인이 필요합니다. 다시 로그인해 주세요.");
|
||||
setErrorMessage(t.errorUnauthorized);
|
||||
router.push("/login");
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus("error");
|
||||
setErrorMessage("대시보드 데이터를 불러오는 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.");
|
||||
setErrorMessage(t.errorGeneric);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -87,9 +91,7 @@ export default function DashboardPage() {
|
||||
} catch {
|
||||
if (!cancelled) {
|
||||
setStatus("error");
|
||||
setErrorMessage(
|
||||
"대시보드 데이터를 불러오는 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.",
|
||||
);
|
||||
setErrorMessage(t.errorGeneric);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -141,8 +143,8 @@ export default function DashboardPage() {
|
||||
<main className="min-h-screen flex flex-col bg-slate-100 text-slate-900 dark:bg-slate-950 dark:text-slate-50">
|
||||
<header className="border-b border-slate-200 px-6 py-4 flex items-center justify-between bg-white/80 backdrop-blur dark:border-slate-800 dark:bg-slate-950/80">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">대시보드</h1>
|
||||
<p className="text-sm text-slate-500 mt-1 dark:text-slate-400">프로젝트와 폼 제출 현황을 한눈에 확인할 수 있는 요약 화면입니다.</p>
|
||||
<h1 className="text-2xl font-bold tracking-tight">{t.title}</h1>
|
||||
<p className="text-sm text-slate-500 mt-1 dark:text-slate-400">{t.description}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<nav className="inline-flex items-center gap-1 rounded-full border border-slate-200 bg-white/80 px-1.5 py-1 shadow-sm dark:border-slate-700 dark:bg-slate-900/80">
|
||||
@@ -152,21 +154,21 @@ export default function DashboardPage() {
|
||||
className="inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-semibold bg-sky-600 text-white shadow-sm hover:bg-sky-700"
|
||||
>
|
||||
<LayoutDashboard className="w-4 h-4" aria-hidden="true" />
|
||||
<span>대시보드</span>
|
||||
<span>{t.navDashboard}</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/projects"
|
||||
className="inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-medium text-slate-700 hover:bg-slate-100 hover:text-sky-700 dark:text-slate-200 dark:hover:bg-slate-800 dark:hover:text-sky-200"
|
||||
>
|
||||
<FolderKanban className="w-4 h-4" aria-hidden="true" />
|
||||
<span>프로젝트 목록</span>
|
||||
<span>{t.navProjects}</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/projects/submissions"
|
||||
className="inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-medium text-emerald-700 hover:bg-emerald-50 hover:text-emerald-800 dark:text-emerald-200 dark:hover:bg-emerald-900/60 dark:hover:text-emerald-100"
|
||||
>
|
||||
<ListChecks className="w-4 h-4" aria-hidden="true" />
|
||||
<span>전체 제출 내역</span>
|
||||
<span>{t.navSubmissions}</span>
|
||||
</Link>
|
||||
</nav>
|
||||
<button
|
||||
@@ -175,7 +177,7 @@ export default function DashboardPage() {
|
||||
onClick={handleToggleTheme}
|
||||
>
|
||||
<SunMoon className="w-4 h-4" aria-hidden="true" />
|
||||
<span>테마 전환</span>
|
||||
<span>{t.themeToggleLabel}</span>
|
||||
</button>
|
||||
<div className="relative">
|
||||
<button
|
||||
@@ -185,7 +187,7 @@ export default function DashboardPage() {
|
||||
setIsMenuOpen((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
메뉴
|
||||
{t.menuLabel}
|
||||
</button>
|
||||
{isMenuOpen && (
|
||||
<div className="absolute right-0 mt-1 w-32 rounded border border-slate-200 bg-white shadow-lg z-20 dark:border-slate-700 dark:bg-slate-900/95">
|
||||
@@ -197,7 +199,7 @@ export default function DashboardPage() {
|
||||
void handleLogout();
|
||||
}}
|
||||
>
|
||||
로그아웃
|
||||
{t.logoutLabel}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -211,7 +213,7 @@ export default function DashboardPage() {
|
||||
)}
|
||||
|
||||
{status === "loading" && (
|
||||
<p className="text-xs text-slate-400">대시보드 데이터를 불러오는 중입니다...</p>
|
||||
<p className="text-xs text-slate-400">{t.loadingText}</p>
|
||||
)}
|
||||
|
||||
{/* 상단 요약 위젯 영역 */}
|
||||
@@ -220,7 +222,9 @@ export default function DashboardPage() {
|
||||
data-testid="dashboard-summary-total-projects"
|
||||
className="rounded-xl border border-slate-200 bg-white/80 px-4 py-3 flex flex-col gap-1 shadow-sm dark:border-slate-800 dark:bg-slate-900/70"
|
||||
>
|
||||
<span className="text-[11px] font-medium text-slate-500 dark:text-slate-400">프로젝트 수</span>
|
||||
<span className="text-[11px] font-medium text-slate-500 dark:text-slate-400">
|
||||
{t.summaryTotalProjectsLabel}
|
||||
</span>
|
||||
<span className="text-2xl font-bold tracking-tight">{safeSummary.totalProjects}</span>
|
||||
</div>
|
||||
|
||||
@@ -228,7 +232,9 @@ export default function DashboardPage() {
|
||||
data-testid="dashboard-summary-total-submissions"
|
||||
className="rounded-xl border border-slate-200 bg-white/80 px-4 py-3 flex flex-col gap-1 shadow-sm dark:border-slate-800 dark:bg-slate-900/70"
|
||||
>
|
||||
<span className="text-[11px] font-medium text-slate-500 dark:text-slate-400">전체 폼 제출 수</span>
|
||||
<span className="text-[11px] font-medium text-slate-500 dark:text-slate-400">
|
||||
{t.summaryTotalSubmissionsLabel}
|
||||
</span>
|
||||
<span className="text-2xl font-bold tracking-tight">{safeSummary.totalSubmissions}</span>
|
||||
</div>
|
||||
|
||||
@@ -236,7 +242,9 @@ export default function DashboardPage() {
|
||||
data-testid="dashboard-summary-today-submissions"
|
||||
className="rounded-xl border border-slate-200 bg-white/80 px-4 py-3 flex flex-col gap-1 shadow-sm dark:border-slate-800 dark:bg-slate-900/70"
|
||||
>
|
||||
<span className="text-[11px] font-medium text-slate-500 dark:text-slate-400">오늘 제출 수</span>
|
||||
<span className="text-[11px] font-medium text-slate-500 dark:text-slate-400">
|
||||
{t.summaryTodaySubmissionsLabel}
|
||||
</span>
|
||||
<span className="text-2xl font-bold tracking-tight">{safeSummary.todaySubmissions}</span>
|
||||
</div>
|
||||
|
||||
@@ -244,7 +252,9 @@ export default function DashboardPage() {
|
||||
data-testid="dashboard-summary-last7days-submissions"
|
||||
className="rounded-xl border border-slate-200 bg-white/80 px-4 py-3 flex flex-col gap-1 shadow-sm dark:border-slate-800 dark:bg-slate-900/70"
|
||||
>
|
||||
<span className="text-[11px] font-medium text-slate-500 dark:text-slate-400">최근 7일 제출 수</span>
|
||||
<span className="text-[11px] font-medium text-slate-500 dark:text-slate-400">
|
||||
{t.summaryLast7DaysSubmissionsLabel}
|
||||
</span>
|
||||
<span className="text-2xl font-bold tracking-tight">{safeSummary.last7DaysSubmissions}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -255,12 +265,12 @@ export default function DashboardPage() {
|
||||
className="mt-2 rounded-xl border border-slate-200 bg-white/80 px-4 py-3 shadow-sm dark:border-slate-800 dark:bg-slate-900/70"
|
||||
>
|
||||
<div className="flex items-baseline justify-between mb-2">
|
||||
<h2 className="text-sm font-semibold text-slate-800 dark:text-slate-200">최근 제출 추이 (일별)</h2>
|
||||
<span className="text-[10px] text-slate-500 dark:text-slate-500">최근 활동이 있는 날짜만 표시됩니다.</span>
|
||||
<h2 className="text-sm font-semibold text-slate-800 dark:text-slate-200">{t.chartTitle}</h2>
|
||||
<span className="text-[10px] text-slate-500 dark:text-slate-500">{t.chartSubtitle}</span>
|
||||
</div>
|
||||
|
||||
{dailySubmissions.length === 0 ? (
|
||||
<p className="text-[11px] text-slate-500 dark:text-slate-400">아직 일별 제출 내역이 없습니다.</p>
|
||||
<p className="text-[11px] text-slate-500 dark:text-slate-400">{t.chartEmptyLabel}</p>
|
||||
) : (
|
||||
<div className="flex items-end gap-2 text-[10px]">
|
||||
{dailySubmissions.map((item) => (
|
||||
@@ -283,10 +293,12 @@ export default function DashboardPage() {
|
||||
|
||||
{/* 프로젝트 카드 리스트 영역 */}
|
||||
<div className="mt-2">
|
||||
<h2 className="text-base font-semibold mb-2 text-slate-800 dark:text-slate-100">프로젝트별 제출 현황</h2>
|
||||
<h2 className="text-base font-semibold mb-2 text-slate-800 dark:text-slate-100">
|
||||
{t.sectionProjectsTitle}
|
||||
</h2>
|
||||
|
||||
{projects.length === 0 && status === "idle" && !errorMessage && (
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400">아직 생성된 프로젝트가 없거나 제출 내역이 없습니다.</p>
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400">{t.sectionProjectsEmpty}</p>
|
||||
)}
|
||||
|
||||
{projects.length > 0 && (
|
||||
@@ -294,7 +306,7 @@ export default function DashboardPage() {
|
||||
{projects.map((project) => {
|
||||
const lastSubmitted = project.lastSubmissionAt
|
||||
? new Date(project.lastSubmissionAt).toLocaleString()
|
||||
: "제출 내역 없음";
|
||||
: t.projectLatestSubmissionEmpty;
|
||||
|
||||
return (
|
||||
<article
|
||||
@@ -314,9 +326,13 @@ export default function DashboardPage() {
|
||||
|
||||
<div className="mt-1 flex items-center justify-between text-[11px] text-slate-700 dark:text-slate-300">
|
||||
<span>
|
||||
총 제출 <span className="font-semibold">{project.totalSubmissions}</span>건
|
||||
{t.projectTotalSubmissionsPrefix}
|
||||
<span className="font-semibold">{project.totalSubmissions}</span>
|
||||
{t.projectTotalSubmissionsSuffix}
|
||||
</span>
|
||||
<span className="text-slate-500 dark:text-slate-400">
|
||||
{t.projectLatestSubmissionPrefix} {lastSubmitted}
|
||||
</span>
|
||||
<span className="text-slate-500 dark:text-slate-400">최근 제출: {lastSubmitted}</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 flex items-center gap-2 text-[11px]">
|
||||
@@ -324,13 +340,13 @@ export default function DashboardPage() {
|
||||
href={`/projects/${encodeURIComponent(project.slug)}/submissions`}
|
||||
className="text-emerald-700 hover:text-emerald-900 underline-offset-2 hover:underline dark:text-emerald-300 dark:hover:text-emerald-100"
|
||||
>
|
||||
폼 제출 내역 보기
|
||||
{t.projectViewSubmissionsLink}
|
||||
</Link>
|
||||
<Link
|
||||
href={`/p/${encodeURIComponent(project.slug)}`}
|
||||
className="text-sky-700 hover:text-sky-900 underline-offset-2 hover:underline dark:text-sky-300 dark:hover:text-sky-200"
|
||||
>
|
||||
퍼블릭 페이지 열기
|
||||
{t.projectOpenPublicPageLink}
|
||||
</Link>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
Reference in New Issue
Block a user