Skip to content

Commit f78677a

Browse files
authored
Merge pull request #199 from CSE-Shaco/develop
fix(manito): request 직렬화 문제 해결
2 parents 17939cd + f7ef596 commit f78677a

File tree

1 file changed

+177
-177
lines changed

1 file changed

+177
-177
lines changed

src/app/manitto/admin/page.jsx

Lines changed: 177 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default function ManitoAdminPage() {
1313
// 세션 관리용
1414
const [sessions, setSessions] = useState([]); // [{id, code, name, createdAt,...}]
1515
const [newSessionCode, setNewSessionCode] = useState('');
16-
const [newSessionName, setNewSessionName] = useState('');
16+
const [newSessionTitle, setNewSessionTitle] = useState('');
1717
const [loadingSessions, setLoadingSessions] = useState(false);
1818

1919
// 파일
@@ -58,28 +58,28 @@ export default function ManitoAdminPage() {
5858
const handleCreateSession = async () => {
5959
resetMessages();
6060
const code = newSessionCode.trim();
61-
const name = newSessionName.trim();
61+
const title = newSessionTitle.trim();
6262

6363
if (!code) {
6464
setError('세션 코드를 입력해 주세요.');
6565
return;
6666
}
67-
if (!name) {
67+
if (!title) {
6868
setError('세션 이름을 입력해 주세요.');
6969
return;
7070
}
7171

7272
try {
7373
setLoadingSessions(true);
74-
const res = await apiClient.post('/admin/manito/sessions', {code, name});
74+
const res = await apiClient.post('/admin/manito/sessions', {code, name: title});
7575
const created = res.data?.data;
7676

7777
// 세션 목록 갱신
7878
await fetchSessions();
7979
// 공통 sessionCode 에도 세팅
8080
setSessionCode(code);
8181
setNewSessionCode('');
82-
setNewSessionName('');
82+
setNewSessionTitle('');
8383
setMessage(`세션이 생성되었습니다. (code: ${code})`);
8484
} catch (e) {
8585
console.error(e);
@@ -188,184 +188,184 @@ export default function ManitoAdminPage() {
188188
};
189189

190190
return (<div className="dark flex flex-col max-w-3xl mx-auto min-h-[100svh] py-16 px-6">
191-
<h1 className="font-bold mb-6 text-3xl text-white">마니또 관리(Admin)</h1>
192-
193-
{/* 공통 설정 + 세션 등록/선택 */}
194-
<Card className="mb-6 bg-default-100 dark:bg-zinc-900 border border-zinc-800">
195-
<CardHeader className="flex flex-col items-start gap-1">
196-
<h2 className="text-xl font-semibold text-white">공통 설정 · 세션 관리</h2>
197-
<p className="text-xs text-zinc-400">
198-
세션 단위로 참가자/매칭/암호문을 관리합니다.
199-
<br/>
200-
먼저 세션을 생성한 뒤, 해당 세션을 선택하고 아래 단계를 진행하세요.
201-
</p>
202-
</CardHeader>
191+
<h1 className="font-bold mb-6 text-3xl text-white">마니또 관리(Admin)</h1>
192+
193+
{/* 공통 설정 + 세션 등록/선택 */}
194+
<Card className="mb-6 bg-default-100 dark:bg-zinc-900 border border-zinc-800">
195+
<CardHeader className="flex flex-col items-start gap-1">
196+
<h2 className="text-xl font-semibold text-white">공통 설정 · 세션 관리</h2>
197+
<p className="text-xs text-zinc-400">
198+
세션 단위로 참가자/매칭/암호문을 관리합니다.
199+
<br/>
200+
먼저 세션을 생성한 뒤, 해당 세션을 선택하고 아래 단계를 진행하세요.
201+
</p>
202+
</CardHeader>
203+
<Divider className="border-zinc-800"/>
204+
<CardBody className="gap-4 text-white">
205+
{/* 현재 사용 세션 코드 (직접 입력/수정 가능) */}
206+
<Input
207+
label="현재 사용 중인 세션 코드"
208+
placeholder="예: WINTER_2025"
209+
value={sessionCode}
210+
onChange={(e) => setSessionCode(e.target.value)}
211+
variant="bordered"
212+
classNames={{
213+
label: 'text-zinc-300',
214+
input: 'text-white',
215+
inputWrapper: 'bg-zinc-900 border-zinc-700 group-data-[focus=true]:border-zinc-400',
216+
}}
217+
/>
218+
203219
<Divider className="border-zinc-800"/>
204-
<CardBody className="gap-4 text-white">
205-
{/* 현재 사용 세션 코드 (직접 입력/수정 가능) */}
206-
<Input
207-
label="현재 사용 중인 세션 코드"
208-
placeholder="예: WINTER_2025"
209-
value={sessionCode}
210-
onChange={(e) => setSessionCode(e.target.value)}
211-
variant="bordered"
212-
classNames={{
213-
label: 'text-zinc-300',
214-
input: 'text-white',
215-
inputWrapper: 'bg-zinc-900 border-zinc-700 group-data-[focus=true]:border-zinc-400',
216-
}}
217-
/>
218-
219-
<Divider className="border-zinc-800"/>
220-
221-
{/* 새 세션 생성 */}
222-
<div className="space-y-2">
223-
<p className="text-sm text-zinc-300 font-semibold">새 세션 등록</p>
224-
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
225-
<Input
226-
label="세션 코드"
227-
placeholder="예: WINTER_2025"
228-
value={newSessionCode}
229-
onChange={(e) => setNewSessionCode(e.target.value)}
230-
variant="bordered"
231-
classNames={{
232-
label: 'text-zinc-300',
233-
input: 'text-white',
234-
inputWrapper: 'bg-zinc-900 border-zinc-700 group-data-[focus=true]:border-zinc-400',
235-
}}
236-
/>
237-
<Input
238-
label="세션 이름"
239-
placeholder="예: 2025 겨울 마니또"
240-
value={newSessionName}
241-
onChange={(e) => setNewSessionName(e.target.value)}
242-
variant="bordered"
243-
classNames={{
244-
label: 'text-zinc-300',
245-
input: 'text-white',
246-
inputWrapper: 'bg-zinc-900 border-zinc-700 group-data-[focus=true]:border-zinc-400',
247-
}}
248-
/>
249-
</div>
250-
<Button
251-
color="primary"
252-
variant="flat"
253-
size="sm"
254-
onPress={handleCreateSession}
255-
isLoading={loadingSessions}
256-
className="mt-1"
257-
>
258-
새 세션 생성
259-
</Button>
260-
</div>
261220

262-
{/* 세션 목록 */}
263-
<Divider className="border-zinc-800 my-4"/>
264-
<div className="space-y-2">
265-
<p className="text-sm text-zinc-300 font-semibold">세션 목록</p>
266-
<div className="max-h-40 overflow-auto space-y-1 text-sm">
267-
{loadingSessions && (<p className="text-xs text-zinc-400">세션 목록을 불러오는 중...</p>)}
268-
{!loadingSessions && sessions.length === 0 && (<p className="text-xs text-zinc-500">
269-
등록된 세션이 없습니다. 위에서 새 세션을 생성해 주세요.
270-
</p>)}
271-
{sessions.map((s) => (<div
272-
key={s.id ?? s.code}
273-
className="flex items-center justify-between py-1 border-b border-zinc-800/40"
274-
>
275-
<div className="flex flex-col">
276-
<span className="font-medium text-zinc-100">
277-
{s.name || '(이름 없음)'}
278-
</span>
279-
<span className="text-xs text-zinc-400">
280-
code: {s.code}
281-
</span>
282-
</div>
283-
<Button
284-
size="sm"
285-
variant={sessionCode === s.code ? 'solid' : 'flat'}
286-
color="secondary"
287-
onPress={() => setSessionCode(s.code)}
288-
>
289-
사용
290-
</Button>
291-
</div>))}
292-
</div>
221+
{/* 새 세션 생성 */}
222+
<div className="space-y-2">
223+
<p className="text-sm text-zinc-300 font-semibold">새 세션 등록</p>
224+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
225+
<Input
226+
label="세션 코드"
227+
placeholder="예: WINTER_2025"
228+
value={newSessionCode}
229+
onChange={(e) => setNewSessionCode(e.target.value)}
230+
variant="bordered"
231+
classNames={{
232+
label: 'text-zinc-300',
233+
input: 'text-white',
234+
inputWrapper: 'bg-zinc-900 border-zinc-700 group-data-[focus=true]:border-zinc-400',
235+
}}
236+
/>
237+
<Input
238+
label="세션 이름"
239+
placeholder="예: 2025 겨울 마니또"
240+
value={newSessionTitle}
241+
onChange={(e) => setNewSessionTitle(e.target.value)}
242+
variant="bordered"
243+
classNames={{
244+
label: 'text-zinc-300',
245+
input: 'text-white',
246+
inputWrapper: 'bg-zinc-900 border-zinc-700 group-data-[focus=true]:border-zinc-400',
247+
}}
248+
/>
293249
</div>
294-
</CardBody>
295-
</Card>
296-
297-
{/* 1단계: 참가자 CSV 업로드 */}
298-
<Card className="mb-6 bg-default-100 dark:bg-zinc-900 border border-zinc-800">
299-
<CardHeader className="flex flex-col items-start gap-1">
300-
<h2 className="text-lg font-semibold text-white">
301-
1단계 · 참가자 CSV 업로드 & 매칭 생성
302-
</h2>
303-
<p className="text-xs text-zinc-400">
304-
CSV 헤더: <code>studentId,name,pin</code> · 업로드 후, 서버에서 매칭을 생성하고
305-
<br/>
306-
<code>giverStudentId,giverName,receiverStudentId,receiverName</code> CSV를
307-
바로 다운로드합니다.
308-
</p>
309-
</CardHeader>
310-
<Divider className="border-zinc-800"/>
311-
<CardBody className="gap-4 text-white">
312-
<input
313-
type="file"
314-
accept=".csv"
315-
onChange={handleParticipantsFileChange}
316-
className="text-sm text-zinc-300"
317-
/>
318250
<Button
319251
color="primary"
320252
variant="flat"
321-
onPress={handleUploadParticipants}
322-
isLoading={loadingParticipants}
323-
isDisabled={!sessionCode.trim() || !participantsFile || loadingParticipants}
324-
className="mt-2"
253+
size="sm"
254+
onPress={handleCreateSession}
255+
isLoading={loadingSessions}
256+
className="mt-1"
325257
>
326-
참가자 CSV 업로드 & 매칭 CSV 다운로드
258+
새 세션 생성
327259
</Button>
328-
</CardBody>
329-
</Card>
330-
331-
{/* 2단계: 암호문 CSV 업로드 */}
332-
<Card className="mb-6 bg-default-100 dark:bg-zinc-900 border border-zinc-800">
333-
<CardHeader className="flex flex-col items-start gap-1">
334-
<h2 className="text-lg font-semibold text-white">
335-
2단계 · 암호문(encryptedManitto) CSV 업로드
336-
</h2>
337-
<p className="text-xs text-zinc-400">
338-
클라이언트에서 매칭 CSV를 기반으로 암호화한 결과를 업로드합니다.
339-
<br/>
340-
CSV 헤더 예시: <code>studentId,encryptedManitto</code>
341-
</p>
342-
</CardHeader>
343-
<Divider className="border-zinc-800"/>
344-
<CardBody className="gap-4 text-white">
345-
<input
346-
type="file"
347-
accept=".csv"
348-
onChange={handleEncryptedFileChange}
349-
className="text-sm text-zinc-300"
350-
/>
351-
<Button
352-
color="secondary"
353-
variant="flat"
354-
onPress={handleUploadEncrypted}
355-
isLoading={loadingEncrypted}
356-
isDisabled={!sessionCode.trim() || !encryptedFile || loadingEncrypted}
357-
className="mt-2"
358-
>
359-
암호문 CSV 업로드
360-
</Button>
361-
</CardBody>
362-
</Card>
363-
364-
{(message || error) && (<Card className="bg-default-100 dark:bg-zinc-900 border border-zinc-800">
365-
<CardBody className="text-sm">
366-
{message && (<p className="text-emerald-400 whitespace-pre-line">{message}</p>)}
367-
{error && <p className="text-red-400 whitespace-pre-line">{error}</p>}
368-
</CardBody>
369-
</Card>)}
370-
</div>);
260+
</div>
261+
262+
{/* 세션 목록 */}
263+
<Divider className="border-zinc-800 my-4"/>
264+
<div className="space-y-2">
265+
<p className="text-sm text-zinc-300 font-semibold">세션 목록</p>
266+
<div className="max-h-40 overflow-auto space-y-1 text-sm">
267+
{loadingSessions && (<p className="text-xs text-zinc-400">세션 목록을 불러오는 중...</p>)}
268+
{!loadingSessions && sessions.length === 0 && (<p className="text-xs text-zinc-500">
269+
등록된 세션이 없습니다. 위에서 새 세션을 생성해 주세요.
270+
</p>)}
271+
{sessions.map((s) => (<div
272+
key={s.id ?? s.code}
273+
className="flex items-center justify-between py-1 border-b border-zinc-800/40"
274+
>
275+
<div className="flex flex-col">
276+
<span className="font-medium text-zinc-100">
277+
{s.name || '(이름 없음)'}
278+
</span>
279+
<span className="text-xs text-zinc-400">
280+
code: {s.code}
281+
</span>
282+
</div>
283+
<Button
284+
size="sm"
285+
variant={sessionCode === s.code ? 'solid' : 'flat'}
286+
color="secondary"
287+
onPress={() => setSessionCode(s.code)}
288+
>
289+
사용
290+
</Button>
291+
</div>))}
292+
</div>
293+
</div>
294+
</CardBody>
295+
</Card>
296+
297+
{/* 1단계: 참가자 CSV 업로드 */}
298+
<Card className="mb-6 bg-default-100 dark:bg-zinc-900 border border-zinc-800">
299+
<CardHeader className="flex flex-col items-start gap-1">
300+
<h2 className="text-lg font-semibold text-white">
301+
1단계 · 참가자 CSV 업로드 & 매칭 생성
302+
</h2>
303+
<p className="text-xs text-zinc-400">
304+
CSV 헤더: <code>studentId,name,pin</code> · 업로드 후, 서버에서 매칭을 생성하고
305+
<br/>
306+
<code>giverStudentId,giverName,receiverStudentId,receiverName</code> CSV를
307+
바로 다운로드합니다.
308+
</p>
309+
</CardHeader>
310+
<Divider className="border-zinc-800"/>
311+
<CardBody className="gap-4 text-white">
312+
<input
313+
type="file"
314+
accept=".csv"
315+
onChange={handleParticipantsFileChange}
316+
className="text-sm text-zinc-300"
317+
/>
318+
<Button
319+
color="primary"
320+
variant="flat"
321+
onPress={handleUploadParticipants}
322+
isLoading={loadingParticipants}
323+
isDisabled={!sessionCode.trim() || !participantsFile || loadingParticipants}
324+
className="mt-2"
325+
>
326+
참가자 CSV 업로드 & 매칭 CSV 다운로드
327+
</Button>
328+
</CardBody>
329+
</Card>
330+
331+
{/* 2단계: 암호문 CSV 업로드 */}
332+
<Card className="mb-6 bg-default-100 dark:bg-zinc-900 border border-zinc-800">
333+
<CardHeader className="flex flex-col items-start gap-1">
334+
<h2 className="text-lg font-semibold text-white">
335+
2단계 · 암호문(encryptedManitto) CSV 업로드
336+
</h2>
337+
<p className="text-xs text-zinc-400">
338+
클라이언트에서 매칭 CSV를 기반으로 암호화한 결과를 업로드합니다.
339+
<br/>
340+
CSV 헤더 예시: <code>studentId,encryptedManitto</code>
341+
</p>
342+
</CardHeader>
343+
<Divider className="border-zinc-800"/>
344+
<CardBody className="gap-4 text-white">
345+
<input
346+
type="file"
347+
accept=".csv"
348+
onChange={handleEncryptedFileChange}
349+
className="text-sm text-zinc-300"
350+
/>
351+
<Button
352+
color="secondary"
353+
variant="flat"
354+
onPress={handleUploadEncrypted}
355+
isLoading={loadingEncrypted}
356+
isDisabled={!sessionCode.trim() || !encryptedFile || loadingEncrypted}
357+
className="mt-2"
358+
>
359+
암호문 CSV 업로드
360+
</Button>
361+
</CardBody>
362+
</Card>
363+
364+
{(message || error) && (<Card className="bg-default-100 dark:bg-zinc-900 border border-zinc-800">
365+
<CardBody className="text-sm">
366+
{message && (<p className="text-emerald-400 whitespace-pre-line">{message}</p>)}
367+
{error && <p className="text-red-400 whitespace-pre-line">{error}</p>}
368+
</CardBody>
369+
</Card>)}
370+
</div>);
371371
}

0 commit comments

Comments
 (0)