@@ -10,6 +10,7 @@ import {
1010 type AdminEventPatchRequest ,
1111} from "@/api/adminEvent" ;
1212import { useState } from "react" ;
13+ import styles from "@/styles/AdminEvents.module.css" ;
1314
1415const EMPTY_FORM = {
1516 title : "" ,
@@ -351,157 +352,201 @@ export default function AdminEventsPage() {
351352 [ "location" , "장소" , "text" ] ,
352353 [ "applyLink" , "신청 링크" , "text" ] ,
353354
354- [ "tags" , "태그, 쉼표 구분" , "text" ] ,
355+ [ "tags" , "태그 ( 쉼표 구분) " , "text" ] ,
355356 ] ;
356357
357358 return (
358- < main
359- style = { {
360- width : "100vw" ,
361- height : "100dvh" ,
362- overflowY : "auto" ,
363- boxSizing : "border-box" ,
364- maxWidth : 1080 ,
365- margin : "0 auto" ,
366- padding : 24 ,
367- } }
368- >
369- < h1 > 행샤 어드민</ h1 >
370-
371- < section style = { { display : "flex" , gap : 8 , flexWrap : "wrap" } } >
372- < input
373- value = { eventId }
374- onChange = { ( e ) => setEventId ( e . currentTarget . value ) }
375- placeholder = "행사 ID"
376- />
377-
378- < button type = "button" onClick = { handleLoad } disabled = { isLoading } >
379- 불러오기
380- </ button >
381-
382- < button type = "button" onClick = { handlePatch } disabled = { isLoading } >
383- 수정
384- </ button >
385-
386- < button type = "button" onClick = { handleDelete } disabled = { isLoading } >
387- 단건 삭제
388- </ button >
389-
390- < button type = "button" onClick = { handleDeleteAll } disabled = { isLoading } >
391- 전체 삭제
392- </ button >
393-
394- < button type = "button" onClick = { handleCreate } disabled = { isLoading } >
395- 신규 생성
396- </ button >
397- </ section >
398-
399- < section style = { { marginTop : 20 } } >
400- < h2 > JSON 파일 Sync</ h2 >
401-
402- < input
403- type = "file"
404- accept = ".json,application/json"
405- onChange = { ( e ) => {
406- setSelectedFile ( e . currentTarget . files ?. [ 0 ] ?? null ) ;
407- } }
408- />
409-
410- < button
411- type = "button"
412- onClick = { handleSyncFile }
413- disabled = { isLoading }
414- style = { { marginLeft : 8 } }
415- >
416- sync-file 업로드
417- </ button >
418- </ section >
419-
420- < section style = { { marginTop : 20 } } >
421- < h2 > adminOverriddenFields lock/unlock</ h2 >
422-
423- < p style = { { margin : "8px 0" } } >
424- 마지막 응답 기준 lock: { overrideFields . length > 0 ? overrideFields . join ( ", " ) : "없음" }
425- </ p >
426-
427- < div
428- style = { {
429- display : "grid" ,
430- gridTemplateColumns : "repeat(4, minmax(0, 1fr))" ,
431- gap : 8 ,
432- } }
433- >
434- { OVERRIDABLE_FIELDS . map ( ( { key, label } ) => (
435- < label key = { key } style = { { display : "flex" , gap : 6 } } >
436- < input
437- type = "checkbox"
438- checked = { selectedOverrideFields . includes ( key ) }
439- onChange = { ( ) => toggleOverrideField ( key ) }
440- />
441- { label } ({ key } )
442- </ label >
443- ) ) }
359+ < div className = { styles . page } >
360+ < div className = { styles . inner } >
361+ < h1 className = { styles . pageTitle } > 행샤 어드민</ h1 >
362+
363+ < div className = { styles . layout } >
364+ { /* ── 왼쪽 패널 ── */ }
365+ < div className = { styles . leftPanel } >
366+ { /* 행사 관리 */ }
367+ < div className = { styles . card } >
368+ < p className = { styles . cardTitle } > 행사 관리</ p >
369+ < div className = { styles . idBar } >
370+ < input
371+ className = { styles . idInput }
372+ value = { eventId }
373+ onChange = { ( e ) => setEventId ( e . currentTarget . value ) }
374+ placeholder = "행사 ID"
375+ />
376+
377+ < button
378+ type = "button"
379+ className = { `${ styles . btn } ${ styles . btnPrimary } ` }
380+ onClick = { handleLoad }
381+ disabled = { isLoading }
382+ >
383+ 불러오기
384+ </ button >
385+
386+ < button
387+ type = "button"
388+ className = { `${ styles . btn } ${ styles . btnPrimary } ` }
389+ onClick = { handlePatch }
390+ disabled = { isLoading }
391+ >
392+ 수정
393+ </ button >
394+
395+ < button
396+ type = "button"
397+ className = { `${ styles . btn } ${ styles . btnPrimary } ` }
398+ onClick = { handleCreate }
399+ disabled = { isLoading }
400+ >
401+ 신규 생성
402+ </ button >
403+
404+ < div className = { styles . divider } />
405+
406+ < button
407+ type = "button"
408+ className = { `${ styles . btn } ${ styles . btnDanger } ` }
409+ onClick = { handleDelete }
410+ disabled = { isLoading }
411+ >
412+ 단건 삭제
413+ </ button >
414+
415+ < button
416+ type = "button"
417+ className = { `${ styles . btn } ${ styles . btnDanger } ` }
418+ onClick = { handleDeleteAll }
419+ disabled = { isLoading }
420+ >
421+ 전체 삭제
422+ </ button >
423+ </ div >
424+ </ div >
425+
426+ { /* 피드백 메시지 */ }
427+ { message && < p className = { styles . message } > { message } </ p > }
428+
429+ { /* JSON 파일 Sync */ }
430+ < div className = { styles . card } >
431+ < p className = { styles . cardTitle } > JSON 파일 Sync</ p >
432+ < div className = { styles . fileRow } >
433+ < label className = { styles . fileLabel } >
434+ 파일 선택
435+ < input
436+ type = "file"
437+ accept = ".json,application/json"
438+ className = { styles . fileInput }
439+ onChange = { ( e ) => {
440+ setSelectedFile ( e . currentTarget . files ?. [ 0 ] ?? null ) ;
441+ } }
442+ />
443+ </ label >
444+
445+ < span className = { styles . fileName } >
446+ { selectedFile ? selectedFile . name : "선택된 파일 없음" }
447+ </ span >
448+
449+ < button
450+ type = "button"
451+ className = { `${ styles . btn } ${ styles . btnSecondary } ` }
452+ onClick = { handleSyncFile }
453+ disabled = { isLoading }
454+ >
455+ 업로드
456+ </ button >
457+ </ div >
458+ </ div >
459+
460+ { /* adminOverriddenFields lock/unlock */ }
461+ < div className = { `${ styles . card } ${ styles . cardGrow } ` } >
462+ < p className = { styles . cardTitle } > Override Fields</ p >
463+
464+ < div className = { styles . overrideMeta } >
465+ < span className = { styles . overrideMetaLabel } > 현재 lock</ span >
466+ { overrideFields . length > 0 ? (
467+ overrideFields . map ( ( f ) => (
468+ < span key = { f } className = { styles . lockedBadge } >
469+ { f }
470+ </ span >
471+ ) )
472+ ) : (
473+ < span className = { styles . overrideMetaLabel } > 없음</ span >
474+ ) }
475+ </ div >
476+
477+ < div className = { styles . checkboxGrid } >
478+ { OVERRIDABLE_FIELDS . map ( ( { key, label } ) => {
479+ const checked = selectedOverrideFields . includes ( key ) ;
480+ return (
481+ < label
482+ key = { key }
483+ className = { `${ styles . checkboxLabel } ${ checked ? styles . checkboxLabelChecked : "" } ` }
484+ >
485+ < input
486+ type = "checkbox"
487+ className = { styles . checkboxNative }
488+ checked = { checked }
489+ onChange = { ( ) => toggleOverrideField ( key ) }
490+ />
491+ { label }
492+ < span className = { styles . checkboxKey } > ({ key } )</ span >
493+ </ label >
494+ ) ;
495+ } ) }
496+ </ div >
497+
498+ < div className = { styles . overrideActions } >
499+ < button
500+ type = "button"
501+ className = { `${ styles . btn } ${ styles . btnLock } ` }
502+ onClick = { ( ) => handleUpdateOverrides ( "lock" ) }
503+ disabled = { isLoading }
504+ >
505+ 선택 필드 lock
506+ </ button >
507+
508+ < button
509+ type = "button"
510+ className = { `${ styles . btn } ${ styles . btnSecondary } ` }
511+ onClick = { ( ) => handleUpdateOverrides ( "unlock" ) }
512+ disabled = { isLoading }
513+ >
514+ 선택 필드 unlock
515+ </ button >
516+ </ div >
517+ </ div >
518+ </ div >
519+
520+ { /* ── 오른쪽 패널 ── */ }
521+ < div className = { styles . rightPanel } >
522+ < div className = { styles . formCard } >
523+ < p className = { styles . cardTitle } > 행사 데이터</ p >
524+ < div className = { styles . formGrid } >
525+ { fields . map ( ( [ key , label , type ] ) => (
526+ < div key = { key } className = { styles . formField } >
527+ < label className = { styles . formLabel } > { label } </ label >
528+ < input
529+ type = { type }
530+ className = { styles . formInput }
531+ value = { form [ key ] }
532+ onChange = { ( e ) => updateForm ( key , e . currentTarget . value ) }
533+ />
534+ </ div >
535+ ) ) }
536+
537+ < div className = { `${ styles . formField } ${ styles . formFieldFull } ` } >
538+ < label className = { styles . formLabel } > 상세 HTML</ label >
539+ < textarea
540+ className = { styles . formTextarea }
541+ value = { form . mainContentHtml }
542+ onChange = { ( e ) => updateForm ( "mainContentHtml" , e . currentTarget . value ) }
543+ />
544+ </ div >
545+ </ div >
546+ </ div >
547+ </ div >
444548 </ div >
445-
446- < div style = { { display : "flex" , gap : 8 , marginTop : 12 } } >
447- < button
448- type = "button"
449- onClick = { ( ) => handleUpdateOverrides ( "lock" ) }
450- disabled = { isLoading }
451- >
452- 선택 필드 lock
453- </ button >
454-
455- < button
456- type = "button"
457- onClick = { ( ) => handleUpdateOverrides ( "unlock" ) }
458- disabled = { isLoading }
459- >
460- 선택 필드 unlock
461- </ button >
462- </ div >
463- </ section >
464-
465- { message && < p style = { { marginTop : 16 } } > { message } </ p > }
466-
467- < section
468- style = { {
469- display : "grid" ,
470- gridTemplateColumns : "repeat(2, minmax(0, 1fr))" ,
471- gap : 12 ,
472- marginTop : 20 ,
473- } }
474- >
475- { fields . map ( ( [ key , label , type ] ) => (
476- < label
477- key = { key }
478- style = { { display : "flex" , flexDirection : "column" , gap : 4 } }
479- >
480- { label }
481- < input
482- type = { type }
483- value = { form [ key ] }
484- onChange = { ( e ) => updateForm ( key , e . currentTarget . value ) }
485- />
486- </ label >
487- ) ) }
488-
489- < label
490- style = { {
491- display : "flex" ,
492- flexDirection : "column" ,
493- gap : 4 ,
494- gridColumn : "1 / -1" ,
495- } }
496- >
497- 상세 HTML
498- < textarea
499- value = { form . mainContentHtml }
500- onChange = { ( e ) => updateForm ( "mainContentHtml" , e . currentTarget . value ) }
501- rows = { 12 }
502- />
503- </ label >
504- </ section >
505- </ main >
549+ </ div >
550+ </ div >
506551 ) ;
507- }
552+ }
0 commit comments