Files
page-builder/src/app/projects/submissions/page.tsx
T
jaybe a5b432fb7b
CI / test (push) Failing after 2m54s
CI / e2e (push) Has been skipped
CI / pr_and_merge (push) Has been skipped
TDD,E2E 개선, 미적용 스타일 개선
2025-12-07 09:52:42 +09:00

172 lines
6.2 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
interface SubmissionItem {
id: string;
createdAt: string;
projectSlug: string;
projectTitle: string | null;
payload: Record<string, unknown>;
}
type PageStatus = "idle" | "loading" | "error";
export default function AllProjectSubmissionsPage() {
const router = useRouter();
const [submissions, setSubmissions] = useState<SubmissionItem[]>([]);
const [status, setStatus] = useState<PageStatus>("idle");
const [errorMessage, setErrorMessage] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
const load = async () => {
try {
setStatus("loading");
setErrorMessage(null);
const res = await fetch("/api/projects/submissions");
if (!res.ok) {
if (cancelled) return;
if (res.status === 401) {
setStatus("error");
setErrorMessage("폼 제출 내역을 조회하려면 로그인이 필요합니다. 다시 로그인해 주세요.");
router.push("/login");
return;
}
setStatus("error");
setErrorMessage("폼 제출 내역을 불러오는 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.");
return;
}
const data = (await res.json()) as SubmissionItem[];
if (!cancelled) {
setSubmissions(Array.isArray(data) ? data : []);
setStatus("idle");
setErrorMessage(null);
}
} catch {
if (!cancelled) {
setStatus("error");
setErrorMessage("폼 제출 내역을 불러오는 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.");
}
}
};
void load();
return () => {
cancelled = true;
};
}, []);
const renderOtherFields = (payload: Record<string, unknown>): string => {
const knownKeys = new Set(["name", "email", "phone", "birthdate"]);
const entries = Object.entries(payload).filter(([key]) => !knownKeys.has(key));
if (entries.length === 0) {
return "-";
}
return entries
.map(([key, value]) => {
const stringValue = typeof value === "string" ? value : JSON.stringify(value);
return `${key}: ${stringValue}`;
})
.join("\n");
};
return (
<main className="min-h-screen flex flex-col bg-slate-950 text-slate-50">
<header className="border-b border-slate-800 px-6 py-4 flex items-center justify-between bg-slate-950/80 backdrop-blur">
<div>
<h1 className="text-xl font-semibold">전체 제출 내역</h1>
<p className="text-xs text-slate-400">로그인한 계정의 모든 프로젝트에 대한 제출 데이터를 확인할 있습니다.</p>
</div>
<div className="text-xs text-slate-300 flex flex-col items-end gap-1">
<a
href="/projects"
role="link"
className="text-[11px] text-sky-400 hover:text-sky-300 underline-offset-2 hover:underline"
onClick={(event) => {
event.preventDefault();
router.push("/projects");
}}
>
프로젝트 목록
</a>
</div>
</header>
<section className="flex-1 px-6 py-4 overflow-auto">
{status === "error" && errorMessage && (
<p className="text-xs text-red-300 mb-3">{errorMessage}</p>
)}
{status === "loading" && (
<p className="text-xs text-slate-400 mb-3"> 제출 내역을 불러오는 중입니다...</p>
)}
{submissions.length === 0 && status === "idle" && !errorMessage && (
<p className="text-xs text-slate-400">아직 수집된 제출 내역이 없습니다.</p>
)}
{submissions.length > 0 && (
<table className="w-full text-xs text-left border-collapse mt-2">
<thead>
<tr className="border-b border-slate-800 text-slate-400">
<th className="py-2 pr-4">제출 시각</th>
<th className="py-2 pr-4">프로젝트</th>
<th className="py-2 pr-4">Slug</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>
</tr>
</thead>
<tbody>
{submissions.map((item) => {
const payload = (item.payload ?? {}) as Record<string, unknown>;
const nameValue = payload["name"];
const emailValue = payload["email"];
const phoneValue = payload["phone"];
const birthdateValue = payload["birthdate"];
const name = typeof nameValue === "string" ? nameValue : "";
const email = typeof emailValue === "string" ? emailValue : "";
const phone = typeof phoneValue === "string" ? phoneValue : "";
const birthdate = typeof birthdateValue === "string" ? birthdateValue : "";
return (
<tr key={item.id} className="border-b border-slate-900 hover:bg-slate-900/60">
<td className="py-2 pr-4 text-slate-300 text-[11px]">
{new Date(item.createdAt).toLocaleString()}
</td>
<td className="py-2 pr-4 text-slate-100">{item.projectTitle ?? "-"}</td>
<td className="py-2 pr-4 text-slate-300">{item.projectSlug}</td>
<td className="py-2 pr-4 text-slate-100">{name}</td>
<td className="py-2 pr-4 text-slate-300">{email}</td>
<td className="py-2 pr-4 text-slate-300">{phone}</td>
<td className="py-2 pr-4 text-slate-300">{birthdate}</td>
<td className="py-2 pr-4 text-slate-300 whitespace-pre-wrap">
{renderOtherFields(payload)}
</td>
</tr>
);
})}
</tbody>
</table>
)}
</section>
</main>
);
}