"use client"; import { useEffect, useState } from "react"; import Link from "next/link"; import { useRouter, useParams } from "next/navigation"; import { FolderKanban, LayoutDashboard, ListChecks, SunMoon } from "lucide-react"; // 프로젝트별 폼 제출 내역을 조회해서 보여주는 페이지 컴포넌트. // - URL 의 slug 파라미터를 기준으로 /api/projects/[slug]/submissions 에 요청을 보낸다. // - 401 이면 로그인 페이지로 이동시키고, 404/500 계열 에러는 한국어 에러 메시지로 안내한다. // - 응답 payload 의 name/email/phone/birthdate 는 개별 컬럼으로, 나머지는 "기타 필드" 텍스트로 렌더링한다. interface SubmissionItem { id: string; createdAt: string; projectSlug: string; // 서버에서 복호화된 폼 필드 전체가 payload 로 내려온다. payload: Record; } type PageStatus = "idle" | "loading" | "error"; export default function ProjectSubmissionsPage() { const router = useRouter(); const params = useParams() as { slug?: string } | null; const slug = (params?.slug ?? "").toString(); const [submissions, setSubmissions] = useState([]); const [status, setStatus] = useState("idle"); const [errorMessage, setErrorMessage] = useState(null); const [isMenuOpen, setIsMenuOpen] = useState(false); // 마운트 시 현재 slug 기준으로 폼 제출 내역을 불러온다. useEffect(() => { if (!slug) return; let cancelled = false; const load = async () => { try { setStatus("loading"); setErrorMessage(null); const res = await fetch(`/api/projects/${encodeURIComponent(slug)}/submissions`); if (!res.ok) { if (cancelled) return; if (res.status === 401) { setStatus("error"); setErrorMessage("폼 제출 내역을 조회하려면 로그인이 필요합니다. 다시 로그인해 주세요."); router.push("/login"); return; } if (res.status === 404) { setStatus("error"); setErrorMessage( "프로젝트를 찾을 수 없거나 접근 권한이 없습니다. 프로젝트 소유자 계정으로 다시 로그인해 주세요.", ); 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; }; }, [slug]); // payload 에서 name/email/phone/birthdate 를 제외한 나머지 필드를 "기타" 영역으로 모아 보여준다. const renderOtherFields = (payload: Record): 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"); }; const handleLogout = async () => { try { const res = await fetch("/api/auth/logout", { method: "POST", }); if (!res.ok) { setErrorMessage("로그아웃 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요."); return; } router.push("/login"); } catch { setErrorMessage("로그아웃 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요."); } }; const handleToggleTheme = () => { if (typeof document === "undefined") { return; } const root = document.documentElement; if (!root) { return; } root.classList.toggle("dark"); }; return (

폼 제출 내역

프로젝트에 연결된 폼으로 수집된 제출 데이터를 확인할 수 있습니다.

{slug}

{isMenuOpen && (
)}
{status === "error" && errorMessage && (

{errorMessage}

)} {status === "loading" && (

폼 제출 내역을 불러오는 중입니다...

)} {submissions.length === 0 && status === "idle" && !errorMessage && (

아직 수집된 폼 제출 내역이 없습니다.

)} {submissions.length > 0 && ( {submissions.map((item) => { const payload = (item.payload ?? {}) as Record; 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 ( ); })}
제출 시각 이름 이메일 전화번호 생년월일 기타 필드
{new Date(item.createdAt).toLocaleString()} {name} {email} {phone} {birthdate} {renderOtherFields(payload)}
)}
); }