From e27d13b6ec5556774a909aa3f6cb33ac1bedea5f Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 3 Apr 2026 00:44:27 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=A7=88=EB=AC=B8=EC=A7=80=20=EC=A0=9C?= =?UTF-8?q?=EC=B6=9C=20=EA=B8=B0=EB=8A=A5=20+=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20=EC=9D=91=EB=8B=B5=20=EA=B4=80=EB=A6=AC=20+=20ifram?= =?UTF-8?q?e=20=EB=AF=B8=EB=A6=AC=EB=B3=B4=EA=B8=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 질문지 HTML에 제출/임시저장 JavaScript 추가 (localStorage 임시저장, API 제출) - questionnaire_responses 테이블 마이그레이션 (005) - /api/questionnaire/submit POST 엔드포인트 - 관리자 질문지 응답 목록/상세/상태변경 페이지 및 API - 관리자 문서 미리보기를 fetch+srcdoc 방식으로 변경 (X-Frame-Options 우회) Co-Authored-By: Claude Sonnet 4.6 --- CONTENT/ebay-tool-questionnaire.html | 266 +++++++++++++++++- app/admin/components/AdminSidebar.tsx | 10 + app/admin/documents/page.tsx | 36 ++- app/admin/questionnaire/page.tsx | 256 +++++++++++++++++ app/api/admin/questionnaire/[id]/route.ts | 69 +++++ app/api/admin/questionnaire/route.ts | 32 +++ app/api/questionnaire/submit/route.ts | 41 +++ next.config.ts | 8 - .../005_questionnaire_responses.sql | 24 ++ 9 files changed, 720 insertions(+), 22 deletions(-) create mode 100644 app/admin/questionnaire/page.tsx create mode 100644 app/api/admin/questionnaire/[id]/route.ts create mode 100644 app/api/admin/questionnaire/route.ts create mode 100644 app/api/questionnaire/submit/route.ts create mode 100644 supabase/migrations/005_questionnaire_responses.sql diff --git a/CONTENT/ebay-tool-questionnaire.html b/CONTENT/ebay-tool-questionnaire.html index 3bea634..0941eb1 100644 --- a/CONTENT/ebay-tool-questionnaire.html +++ b/CONTENT/ebay-tool-questionnaire.html @@ -127,6 +127,94 @@ padding-bottom: 2px; } + .client-info .field-input { + flex: 1; + border: none; + border-bottom: 1px solid #cbd5e1; + min-height: 24px; + padding: 2px 4px; + font-family: inherit; + font-size: 14px; + color: #1e293b; + background: transparent; + outline: none; + transition: border-color 0.2s; + } + + .client-info .field-input:focus { + border-bottom-color: #1a56db; + } + + .client-info .field-input::placeholder { + color: #cbd5e1; + } + + /* ── Submit Section ── */ + .submit-section { + margin-top: 32px; + text-align: center; + } + + .submit-btn { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 14px 48px; + background: #1a56db; + color: #ffffff; + border: none; + border-radius: 8px; + font-family: inherit; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s; + } + + .submit-btn:hover { + background: #1e40af; + } + + .submit-btn:disabled { + background: #94a3b8; + cursor: not-allowed; + } + + .submit-msg { + margin-top: 12px; + font-size: 14px; + line-height: 1.6; + } + + .submit-msg.success { + color: #16a34a; + } + + .submit-msg.error { + color: #dc2626; + } + + .save-draft-btn { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 10px 24px; + background: #f1f5f9; + color: #475569; + border: 1px solid #e2e8f0; + border-radius: 8px; + font-family: inherit; + font-size: 14px; + font-weight: 500; + cursor: pointer; + margin-right: 12px; + transition: background 0.2s; + } + + .save-draft-btn:hover { + background: #e2e8f0; + } + /* ── Section ── */ .section { margin-bottom: 32px; @@ -412,20 +500,20 @@
- 고객명 - + 고객명 * +
연락처 - +
- 이메일 - + 이메일 * +
작성일 - +
@@ -690,6 +778,17 @@ + +
+ + +
+
+ + + \ No newline at end of file diff --git a/app/admin/components/AdminSidebar.tsx b/app/admin/components/AdminSidebar.tsx index 0210bdf..528230d 100644 --- a/app/admin/components/AdminSidebar.tsx +++ b/app/admin/components/AdminSidebar.tsx @@ -68,6 +68,16 @@ const NAV_ITEMS = [ ), }, + { + href: '/admin/questionnaire', + label: '질문지 응답', + icon: ( + + + + ), + }, { href: '/admin/marketing', label: '마케팅 에셋', diff --git a/app/admin/documents/page.tsx b/app/admin/documents/page.tsx index c8b08c7..2c120ab 100644 --- a/app/admin/documents/page.tsx +++ b/app/admin/documents/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; interface Document { id: string; @@ -47,6 +47,19 @@ const STATUS_CONFIG: Record = { export default function AdminDocumentsPage() { const [previewDoc, setPreviewDoc] = useState(null); + const [previewHtml, setPreviewHtml] = useState(''); + const [previewLoading, setPreviewLoading] = useState(false); + + // iframe src 대신 fetch + srcdoc 방식으로 X-Frame-Options 우회 + useEffect(() => { + if (!previewDoc) { setPreviewHtml(''); return; } + setPreviewLoading(true); + fetch(`/api/admin/documents/${previewDoc.fileName}`) + .then(res => res.ok ? res.text() : Promise.reject('문서를 불러올 수 없습니다')) + .then(html => setPreviewHtml(html)) + .catch(() => setPreviewHtml('

문서를 불러올 수 없습니다.

')) + .finally(() => setPreviewLoading(false)); + }, [previewDoc]); return (
@@ -126,13 +139,20 @@ export default function AdminDocumentsPage() {
- {/* iframe */} -