제출 ${index + 1}
+-
+
- 성함
- ${escapeHtml(submission.guestName || "-")} +
- 연락처
- ${escapeHtml(submission.guestPhone || "-")} +
- 제출 시간
- ${escapeHtml(formatDateTime(submission.uploadedAt))} +
- 파일 수
- ${files.length}개 +
- ${fileItems || `
- 파일 기록이 없어. `}
diff --git a/src/server.js b/src/server.js index 5af6469..2bceef0 100644 --- a/src/server.js +++ b/src/server.js @@ -129,7 +129,7 @@ function page(title, body) { :root { color-scheme: light; --ink:#17202a; --muted:#667085; --line:#d6dde7; --bg:#f6f8fb; --panel:#fff; --accent:#0f766e; --danger:#b42318; } * { box-sizing: border-box; } body { margin:0; font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background:var(--bg); color:var(--ink); } - main { width:min(960px, calc(100% - 32px)); margin:32px auto; } + main { width:min(1100px, calc(100% - 32px)); margin:32px auto; } .panel { background:var(--panel); border:1px solid var(--line); border-radius:8px; padding:20px; } h1 { font-size:24px; margin:0 0 18px; } h2 { font-size:18px; margin:26px 0 12px; } @@ -137,6 +137,7 @@ function page(title, body) { input, textarea, select { width:100%; border:1px solid var(--line); border-radius:6px; padding:10px 12px; font:inherit; background:#fff; } input[type=file] { padding:9px; } button, .button { display:inline-flex; align-items:center; justify-content:center; gap:6px; border:0; border-radius:6px; padding:10px 14px; background:var(--accent); color:#fff; font-weight:650; cursor:pointer; text-decoration:none; } + .button.secondary { background:#475467; } button.danger { background:var(--danger); } .row { display:grid; grid-template-columns: 1fr 1fr; gap:14px; } .actions { display:flex; gap:10px; flex-wrap:wrap; margin-top:16px; } @@ -147,6 +148,13 @@ function page(title, body) { .notice { border:1px solid #bae6fd; background:#ecfeff; padding:12px; border-radius:6px; margin:12px 0; } .error { border-color:#fecaca; background:#fff1f2; } .upload-box { border:2px dashed var(--line); border-radius:8px; background:#fff; padding:18px; } + .stack { display:flex; flex-direction:column; gap:8px; } + .submission { border:1px solid var(--line); border-radius:8px; padding:14px; margin-top:12px; background:#fff; } + .submission h3 { margin:0 0 8px; font-size:16px; } + .submission dl { display:grid; grid-template-columns:90px 1fr; gap:6px 12px; margin:0; } + .submission dt { color:var(--muted); } + .submission dd { margin:0; } + .file-list-admin { margin:10px 0 0; padding-left:18px; color:var(--muted); } @media (max-width:720px) { main { width:calc(100% - 20px); margin:16px auto; } .row { grid-template-columns:1fr; } .table { font-size:14px; } } @@ -264,13 +272,42 @@ async function appendUploadMetadata(targetDir, { guestName, guestPhone, files }) guestPhone, files: files.map((file) => ({ storedName: file.filename, - originalName: file.originalname, + originalName: Buffer.from(file.originalname, "latin1").toString("utf8"), size: file.size })) }; await fsp.appendFile(path.join(targetDir, "upload-metadata.jsonl"), `${JSON.stringify(entry)}\n`, "utf8"); } +async function readUploadMetadata(folderName) { + const metadataPath = path.join(uploadsDir, folderName, "upload-metadata.jsonl"); + const content = await fsp.readFile(metadataPath, "utf8").catch((error) => { + if (error.code === "ENOENT") return ""; + throw error; + }); + return content + .split(/\r?\n/) + .filter(Boolean) + .map((line) => { + try { + return JSON.parse(line); + } catch { + return null; + } + }) + .filter(Boolean); +} + +function formatDateTime(value) { + const date = new Date(value); + if (Number.isNaN(date.getTime())) return "-"; + return new Intl.DateTimeFormat("ko-KR", { + timeZone: "Asia/Seoul", + dateStyle: "medium", + timeStyle: "short" + }).format(date); +} + app.get("/", (req, res) => res.redirect("/admin")); app.get("/admin/login", (req, res) => { @@ -316,9 +353,12 @@ app.get("/admin", requireAdmin, async (req, res) => {
해당 링크를 찾을 수 없어.
돌아가기저장 위치: ${escapeHtml(path.join(uploadsDir, item.folderName))}
+제출 정보 파일: ${escapeHtml(path.join(uploadsDir, item.folderName, "upload-metadata.jsonl"))}
+ ${rows || `