오류 수정
CI / test (push) Failing after 5m41s
CI / e2e (push) Has been skipped
CI / pr_and_merge (push) Has been skipped

This commit is contained in:
2025-12-12 18:04:31 +09:00
parent 6804665b95
commit 4840a530b6
205 changed files with 1802 additions and 9887 deletions
+29 -24
View File
@@ -4,6 +4,9 @@ import { useEffect, useState } from "react";
import Link from "next/link";
import { useRouter, useParams } from "next/navigation";
import { FolderKanban, LayoutDashboard, ListChecks, SunMoon } from "lucide-react";
import { useAppLocale } from "@/features/i18n/LocaleProvider";
import { getDashboardMessages } from "@/features/i18n/messages/dashboard";
import { getSubmissionsMessages } from "@/features/i18n/messages/submissions";
// 프로젝트별 폼 제출 내역을 조회해서 보여주는 페이지 컴포넌트.
// - URL 의 slug 파라미터를 기준으로 /api/projects/[slug]/submissions 에 요청을 보낸다.
@@ -25,6 +28,10 @@ export default function ProjectSubmissionsPage() {
const params = useParams() as { slug?: string } | null;
const slug = (params?.slug ?? "").toString();
const locale = useAppLocale();
const t = getDashboardMessages(locale);
const m = getSubmissionsMessages(locale);
const [submissions, setSubmissions] = useState<SubmissionItem[]>([]);
const [status, setStatus] = useState<PageStatus>("idle");
const [errorMessage, setErrorMessage] = useState<string | null>(null);
@@ -48,21 +55,19 @@ export default function ProjectSubmissionsPage() {
if (res.status === 401) {
setStatus("error");
setErrorMessage("폼 제출 내역을 조회하려면 로그인이 필요합니다. 다시 로그인해 주세요.");
setErrorMessage(m.errorUnauthorized);
router.push("/login");
return;
}
if (res.status === 404) {
setStatus("error");
setErrorMessage(
"프로젝트를 찾을 수 없거나 접근 권한이 없습니다. 프로젝트 소유자 계정으로 다시 로그인해 주세요.",
);
setErrorMessage(m.errorGeneric);
return;
}
setStatus("error");
setErrorMessage("폼 제출 내역을 불러오는 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.");
setErrorMessage(m.errorGeneric);
return;
}
@@ -76,7 +81,7 @@ export default function ProjectSubmissionsPage() {
} catch {
if (!cancelled) {
setStatus("error");
setErrorMessage("폼 제출 내역을 불러오는 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.");
setErrorMessage(m.errorGeneric);
}
}
};
@@ -112,13 +117,13 @@ export default function ProjectSubmissionsPage() {
});
if (!res.ok) {
setErrorMessage("로그아웃 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.");
setErrorMessage(m.errorLogout);
return;
}
router.push("/login");
} catch {
setErrorMessage("로그아웃 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.");
setErrorMessage(m.errorLogout);
}
};
@@ -139,8 +144,8 @@ export default function ProjectSubmissionsPage() {
<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-xs text-slate-500 dark:text-slate-400"> .</p>
<h1 className="text-2xl font-bold tracking-tight">{m.headerTitle}</h1>
<p className="text-xs text-slate-500 dark:text-slate-400">{m.headerDescription}</p>
<p className="mt-1 text-[11px] font-mono text-slate-500 dark:text-slate-400">{slug}</p>
</div>
<div className="flex items-center gap-3 text-sm">
@@ -150,21 +155,21 @@ export default function ProjectSubmissionsPage() {
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"
>
<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
@@ -173,7 +178,7 @@ export default function ProjectSubmissionsPage() {
onClick={handleToggleTheme}
>
<SunMoon className="w-4 h-4" aria-hidden="true" />
<span> </span>
<span>{t.themeToggleLabel}</span>
</button>
<div className="relative">
<button
@@ -183,7 +188,7 @@ export default function ProjectSubmissionsPage() {
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">
@@ -195,7 +200,7 @@ export default function ProjectSubmissionsPage() {
void handleLogout();
}}
>
{t.logoutLabel}
</button>
</div>
)}
@@ -209,23 +214,23 @@ export default function ProjectSubmissionsPage() {
)}
{status === "loading" && (
<p className="text-xs text-slate-500 mb-3 dark:text-slate-400"> ...</p>
<p className="text-xs text-slate-500 mb-3 dark:text-slate-400">{m.loadingText}</p>
)}
{submissions.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">{m.emptyText}</p>
)}
{submissions.length > 0 && (
<table className="w-full text-xs text-left border-collapse mt-2">
<thead>
<tr className="border-b border-slate-200 text-slate-600 dark:border-slate-800 dark:text-slate-400">
<th className="py-2 pr-4"> </th>
<th className="py-2 pr-4"></th>
<th className="py-2 pr-4"></th>
<th className="py-2 pr-4"></th>
<th className="py-2 pr-4"></th>
<th className="py-2 pr-4"> </th>
<th className="py-2 pr-4">{m.tableHeaderCreatedAt}</th>
<th className="py-2 pr-4">{m.tableHeaderName}</th>
<th className="py-2 pr-4">{m.tableHeaderEmail}</th>
<th className="py-2 pr-4">{m.tableHeaderPhone}</th>
<th className="py-2 pr-4">{m.tableHeaderBirthdate}</th>
<th className="py-2 pr-4">{m.tableHeaderOtherFields}</th>
</tr>
</thead>
<tbody>