본 프로젝트 사이드바처럼 상단에 FoxInput 검색창을 두고, 그 아래 테마 컨트롤을
가운데 세웠다. 제목·부제는 지웠다.
검색은 라벨과 id를 함께 본다(FoxButton / fox-button). 분류 이름이 걸리면 그 아래는
통째로 남기고, 검색 중에는 접힘을 무시한다 — 걸린 항목이 접힌 가지에 숨으면 안 된다.
섹션 개수도 걸린 것만 센다.
Co-Authored-By: Claude Opus 5
지어낸 값(`MODULE_CNTNTS`)을 쓰고 있었다. 저장소 설정에 없는 id를 보내도 업로드가 실패하지
않고 기본 저장소로 조용히 폴백하기 때문에(`EgovFileMngUtil.parseFileInf`) 지금까지 드러나지
않았을 뿐이다. 받은 실제 목록 8종으로 상수를 바꾼다.
id만 두지 않고 설명·허용 확장자를 함께 담았다. 화면의 `accept`를 `fileModuleAccept()`로 그
목록에서 만들고, Server Action이 업로드 직전에 `isAllowedFileExtension()`으로 한 번 더 본다 —
확장자가 틀리면 백엔드는 사유 없이 실패하므로 어느 파일이 왜 안 되는지 여기서 알려 준다.
업로드 지점별로 저장소가 갈린다:
- 콘텐츠 썸네일(쇼츠·만화) → MODULE_CONTENT_THUMB
- 만화 컷 이미지 → MODULE_TOON_CONTENT
- 금융쇼츠 영상 → MODULE_VOD_CONTENT
- 꾸미기 아이템 썸네일 → MODULE_USER_ITEM (기존과 같음)
꾸미기 아이템의 화면 제한은 JPG·PNG로 그대로 둔다 — 저장소는 gif·svg도 받지만 그 화면 시안이
안내문에 JPG/PNG를 못박고 있어 저장소 허용 범위를 상한으로만 쓴다.
게시판 첨부용 저장소는 목록에 없다. `MODULE_BBS`를 그대로 두되 상수를 분리하고 TODO를 달았다 —
지금은 기본 저장소로 저장되고 있다.
Co-Authored-By: Claude Opus 5
FoxSegmentedControl은 inline-flex라 내용만큼만 넓은데, 상단을 세로 flex로 만들면서
기본값 align-items: stretch가 216px로 늘렸다. 마지막 칸은 구분선이 투명이라 남은
78px이 "다크" 칸의 일부처럼 보였다.
Co-Authored-By: Claude Opus 5
`video/pagination` 컨트롤러가 `ApiResponseVO`가 아니라 `null`을 돌려준다. 그러면 Spring이
봉투를 쓰지 않고 **본문이 통째로 빈** 200을 내려보내는데, `backendFetch`가 곧바로
`response.json()`을 불러 "Unexpected end of JSON input"으로 끊겼다.
`data: null`은 이미 허용해 두었지만 그것은 봉투 안의 data가 null인 경우고, 이번 건은 봉투
자체가 없는 경우라 걸리지 않았다. 목록 조회에 `canHaveEmptyBody`를 켜서 본문을 먼저 글자로
읽고 비어 있으면 빈 목록으로 다룬다.
백엔드가 서비스를 붙이면 이 분기는 자연히 지나가지 않는다.
Co-Authored-By: Claude Opus 5
콘텐츠관리는 게시판(bbs)이 아니라 별도 API다. 쇼츠·만화는 `TB_COM_CNTNTS`를 공유하고
(`/api/v1/mngr/cntntns/{video|toon}`), 퀴즈는 테이블도 경로도 따로다(`/api/v1/mngr/quiz`).
그래서 Repository도 그 경계대로 둘로 나눴다.
등록·수정은 팝업이 아니라 별도 화면이다 — 기획의 화면ID·화면경로(등록 < 금융쇼츠 < 콘텐츠관리)와
디자인 시안이 모두 전체 화면이고, 항목 수가 팝업에 담기지 않는다.
목록 화면은 디자인 시안이 없어 디자인 시스템과 기존 목록 화면의 흐름을 따랐다 — 정렬은 기획의
두 버튼 대신 다른 목록과 같은 정렬 셀렉트로, 학교급·학년·난이도는 필터 칩으로 놓았다.
정답여부는 O·X 두 줄을 모두 그리되 정답으로 고른 줄에서만 노출 문구를 정한다(기획 ③). 기획은
두 줄 다 입력칸을 그리고 아닌 쪽을 흐리게, 디자인 시안은 고른 쪽에만 컨트롤을 그렸는데 비활성으로
두면 두 시안이 같은 화면이 된다.
백엔드 미비 사항은 코드에 이슈로 남겼다. 요약:
- 등록·수정·삭제·단건조회 API가 3종 모두 없다. Repository는 다른 관리 화면과 같은 규약으로
미리 맞춰 두었고, 단건은 그 API가 생길 때까지 목록에서 찾는다.
- `cntntns` 경로 오타, `video/pagination`의 `return null`, toon 매퍼의 `BEN_WORD_NM` 복붙,
키워드 조인으로 인한 count 불일치.
- 학교급·학년·주제·전달메시지·정답 노출문구를 담을 컬럼이 없다. 코드값도 정의되지 않아
잠정 상수로 두고 TODO를 달았다.
확인: 임시 페이지로 세 폼과 두 목록을 렌더링해 항목 구성, 학교급 전환 시 학년 칩 잠금(전체연령),
학년 다중선택, 정답여부 「기타」 선택 시 직접입력 노출을 확인한 뒤 페이지를 지웠다.
Co-Authored-By: Claude Opus 5
admin-sidebar(시안 snb-area 3041:11860)의 규칙을 그대로 옮겼다. 240px 폭,
회색→옅은 파랑 그라데이션, 1depth 알약(multiply)·2depth 흰 카드·3depth 들여쓰기.
@fox 폴더만 복사해도 하니스가 따라가야 해서 app SCSS를 참조하지 않고 옮겨 적었다.
깊이를 맞추려고 트리도 본 프로젝트와 같은 3depth 접힘으로 바꿨다. 상위 분류가 없는
토큰 섹션(Size·Theme·Primitive)은 자식 없는 1depth라 바로 선택된다. 펼침은
useSidebarNav와 같이 "선택 항목에서 파생된 기본값 + 사용자가 누른 값" 2층이다.
시안에 없어 남긴 것: 선택 가능한 행의 개수 배지(캐럿 자리)와 focus-visible 윤곽선.
Co-Authored-By: Claude Opus 5
본문이 길어지면 문서 전체가 스크롤되어 사이드바까지 함께 밀려 올라갔다. shell을 뷰포트 높이로
못박고 overflow를 끊은 뒤, 두 칸이 각자 overflow-y를 갖게 했다(AdminShell과 같은 방식).
좁은 화면에서는 종전처럼 위아래로 쌓이고 문서가 스크롤된다.
사이드바를 디자인 시스템으로 옮겼다 — 손으로 그린 테마 전환 버튼 묶음을 FoxSegmentedControl로,
항목 옆 개수를 FoxBadge로 바꿨다. 대체된 .themeSwitch·.themeButton·.navCount 스타일은 지웠다.
검증 — 1600×900 실측: shell 900·overflow hidden, 문서 전체 스크롤 없음, 사이드바(3237)·
본문(9432) 각각 자체 스크롤.
Co-Authored-By: Claude Opus 5
`FoxStatusIndicator`는 색이 성공/경고 둘뿐이라 「답변대기·처리중·답변완료·보류」를
`answeredAt`의 유무로만 갈라 두 색에 욱여넣고 있었다. 상태 수만큼 색을 줄 수 있는
`FoxBadge`(pastel/md — 같은 표의 「구분」 배지와 같은 계열)로 바꾼다.
색은 코드값이 아니라 **코드명**으로 고른다. `BBS_ANS_CD`의 실제 코드값을 아직 확인하지
못했고(백엔드 소스에 없고 DB 코드테이블에만 있다), 코드값을 맞게 적어도 코드관리 화면에서
언제든 바뀔 수 있다. 반면 코드명은 화면에 그대로 보이는 값이라 「완료」·「대기」 같은 낱말로
읽는 편이 실제로 맞을 확률이 높다. 「미처리」가 「처리」보다 먼저 걸리도록 순서를 두었고,
걸리는 낱말이 없으면 `neutral`로 떨어진다 — 뜻을 지어내지 않는다.
코드값이 비어 있으면 배지 없이 `-`만 둔다(빈 배지를 그리지 않기 위해서다).
확인: 임시 페이지로 답변대기=warning, 처리중=information, 답변완료=success, 보류=danger,
모르는 값=neutral, 빈 값=`-`를 렌더링해 확인 후 페이지를 지웠다.
Co-Authored-By: Claude Opus 5
상태를 프론트 상수 표(`WAIT`/`ING`/`DONE`)로 치환하고 있었다. 그 표를 지우고 공통코드
`BBS_ANS_CD`를 조회해 코드명으로 옮긴다 — 꾸미기 아이템 카테고리(`ITEM_CATE_CD`)와 같은 방식이다.
목록의 "상태" 열과 답변 팝업의 진행상태 선택지, Server Action의 허용 코드 검증이 모두 같은
응답을 본다. 페이지가 한 번 조회해 목록으로 내리고, Server Action은 저장 직전에 다시 조회한다
(화면이 보낸 코드를 그대로 믿지 않기 위해서다 — 아이템 카테고리와 같은 근거).
`ANSWER_STATUS_DONE`만 상수로 남는다. "답변완료일 때만 FO 노출·답변일 표시"라는 판단이 특정
코드값을 알아야 하는데 BBS_ANS_CD의 실제 값을 아직 확인하지 못해 TODO로 표시해 두었다.
Co-Authored-By: Claude Opus 5
1:1문의 컬럼이 공지·FAQ와 같은 `actionsColumn('inquiry')`를 쓰고 있어 연필을 누르면 **글 수정
폼**이 열렸다. 관리자는 질문 글을 수정하지 않는다 — 전용 `InquiryRowActions`가 이미 있었는데
컬럼에 연결되지 않아 쓰이지 않고 있었다. 연결하고, 그 버튼도 무스타일 `components/ui/button`
에서 다른 목록과 같은 `FoxIconButton`(연필·휴지통)으로 바꿨다.
답변 팝업은 기획(1676:18693)대로 @fox로 다시 그렸다 — FoxModal·FoxInput·FoxTextArea·FoxSelect.
기획의 "게시물의 항목 (조회 전용)" 구분 제목, 질문자명/작성일·이메일/전화번호 2열, 진행상태
안내 문구를 그대로 옮겼다. 답변내용에는 글자수 카운터가 붙는다.
## 곁들여 고친 것
첨부 상한 상수를 `file-repository`(server-only)에서 도메인으로 옮겼다. 지난 커밋에서 클라이언트
컴포넌트가 그 상수를 가져오게 만들어 `next/headers`가 클라이언트 번들로 끌려들어 갔고, dev에서
"You're importing a module that depends on next/headers"로 빌드가 깨졌다.
첨부 입력은 Tailwind 제거 이후 무스타일이라 토큰으로 파일선택 버튼 모양을 맞췄다.
Co-Authored-By: Claude Opus 5