시안(tbl-basic·tbl-list)의 가운데 기본 대신 A List Apart·Material·Carbon·GOV.UK가 공통으로
쓰는 규칙을 따른다(사용자 확정, 되돌리기 쉽게 별도 브랜치).
- FoxTable list·basic의 기본 정렬을 start로. form은 이미 start.
- 비교하는 숫자(포인트·가격·메뉴 건수·정렬순서·메뉴순번)는 end.
- 이미지·체크박스·아이콘·펼침 토글·관리 버튼·알약 배지는 center. 관리 열은 화면마다
start/center/end로 갈리던 것을 center로 통일.
- 식별값(번호·학년/반/번호)·날짜·상태 표시(글자)는 기본(왼쪽) — 기존 center 지정을 뺀다.
- 머리칸은 본문 열 정렬을 따른다(FoxListContainer가 이미 같은 align을 준다).
Co-Authored-By: Claude Opus 5
@fox 목록 표는 셀을 이미 text-align: center로 두는데, FoxButtonGroup이 블록 레벨 flex라
셀 폭을 채우고 기본값이 justify-content: flex-start다. 그래서 align="center"를 준 두 화면
(관리자 회원·관리자 메뉴)만 가운데였고 나머지는 왼쪽에 붙어 있었다.
- 행 관리 묶음 다섯 곳에 align="center"를 준다(콘텐츠·꾸미기아이템·게시판·포인트기준·공통코드).
- 시스템 메뉴는 자체 flex div를 쓰고 있어 다른 화면과 같은 FoxButtonGroup으로 바꾼다.
- 가맹점은 묶음 없이 span에 aria-label만 있었다. FoxButtonGroup으로 감싸고 가맹점명을
각 버튼 라벨에 넣는다 — 비의미 요소의 aria-label보다 확실하게 읽힌다(공통코드와 같은 방식).
버튼 하나뿐인 칸(금칙어·포인트 적립)은 손대지 않았다. FoxIconButton이 inline-flex라
셀의 text-align만으로 이미 가운데다(실측 0px).
Co-Authored-By: Claude Opus 5
목록 쿼리가 거를 수 있는 것은 제목(searchCondition=1)과 노출여부(RLS_YN) 둘뿐이다.
그 둘은 백엔드로 넘기고 페이징도 맡긴다 — 별도 카운트 쿼리가 있다. 지금까지는 전체를
받아 프론트에서 거르고 잘랐다.
거르지 못하는 것은 감춘다(사용자 확정) — 내용·질문자명·전화번호·답변내용 검색과
유형·답변여부 필터다. 골라도 걸러지지 않으면 고장으로 보인다. 1:1문의는 남는 대상이
하나도 없어 검색 상자 자체를 감춘다.
Co-Authored-By: Claude Opus 5
백엔드에 담을 자리가 없다 — 등록·수정 요청 VO에 필드가 없고 응답의 bbsCd는 구분이
아니라 유형이다. 그래서 골라도 저장되지 않고 조회하면 늘 비어 있었으며, 검색에 쓰면
값이 항상 null이라 목록이 통째로 비었다.
등록 팝업의 칸·목록의 열·검색 대상·필터를 모두 `IS_BOARD_CATEGORY_ENABLED` 하나에
걸어 뒀다. 백엔드에 필드가 생기면 그 한 줄만 true로 바꾸면 된다.
Co-Authored-By: Claude Opus 5
폼이 든 모달이 바깥 클릭으로 닫히면 적어 둔 값이 통째로 사라진다. 조회 모달도
표를 훑다 여백을 눌러 닫히는 일이 잦다.
알림·확인(feedback-host)과 중복 로그인 안내는 그대로 뒀다 — 읽고 넘기는 자리라
바깥 클릭이 곧 취소다.
Co-Authored-By: Claude Opus 5
Tailwind가 프로젝트에서 빠진 뒤에도 클래스만 남아 아무 스타일도 받지 못하던 자리들이
있었고, 전역 스피너는 만들어만 두고 어디서도 호출하지 않았다. 두 손질이 같은 파일들을
건드려 한 커밋으로 둔다.
Tailwind 제거:
- components/ui의 옛 Tailwind 레이어 16개 삭제. 그중 field·icon-button만 실제로 쓰여
각각 FoxTextArea·FoxFormLabel과 @fox 아이콘 버튼 마크업으로 옮기고 지웠다.
- error.tsx 다섯 곳이 같은 모양이라 PageError로 모은다(PageLoading과 같은 이유).
- 홈·Empty 레이아웃·게시판 첨부 링크는 module.scss로, 행 관리 버튼 묶음은 FoxButtonGroup로.
- 테마 토글은 FoxIconButton을 쓰지 못한다 — label이 필수라 aria-label이 늘 붙고, 그러면
테마별로 바뀌는 안쪽 글자를 스크린리더가 읽지 못한다. @fox 아이콘 버튼의 마크업 계약을
그대로 따라 같은 스타일을 받게 했다.
스피너 호출:
- useBlockingAction — 스피너를 켠 채 도는 transition. 삭제처럼 확인 얼럿이 닫힌 뒤
진행돼 표시가 행 아이콘 하나뿐이던 자리 11곳에 붙였다. 버튼이 스스로 "저장 중"을
보여주는 자리(중복확인·주소검색·엑셀 업로드)는 그대로 뒀다.
- 스피너 막도 토스트와 같은 이유로 popover(top layer)에 얹는다 — 모달 안에서 시작한
동작도 덮어야 한다.
- loading.tsx를 (basic) 한 곳으로 모은다. 다섯 화면에만 있던 것을 모든 하위 화면이
받도록 하고, 새 화면을 추가해도 자동으로 따라오게 한다.
Co-Authored-By: Claude Opus 5
문의는 제목 없이 내용만 온다. 목록의 대표 칸은 내용 발췌(60자 말줄임)로, 답변
팝업의 질문제목 필드는 제거(질문내용 칸이 이미 있다), 삭제 확인 문구는 빈 제목
대신 내용 발췌 30자, 검색 대상에서 제목을 뺀다.
Co-Authored-By: Claude Opus 5
백엔드가 BBS_CD를 코드관리 BBS_FAQ_CD와 조인해 이름(bbsFaqNm)까지 주기 시작했다.
bbsCd는 구분이 아니라 유형이었다 — 구분 배지에 유형 코드를 넣던 매핑을 바로잡고,
FAQ 유형은 서버가 준 이름을 그대로, 1:1 질문유형은 코드로 채운다.
구분은 백엔드에 값이 없어(협의 중) 자리만 두고 '-'로 비운다. 쓰기의 유형도
bbsTypeCd 임시 이름 대신 실제 컬럼(bbsCd)으로 보낸다 — 요청 VO에 필드가 추가되는
즉시 저장까지 동작한다.
노출기간은 조회까지 뚫려 수정 팝업 프리로드가 실값으로 돌고, PUSH_YN은 여전히
SQL 어디에도 없어 앱푸쉬만 남은 결함으로 유지된다.
Co-Authored-By: Claude Opus 5
`MODULE_BBS`가 저장소 목록에 없어 임시 상수로 빼 두었는데(백엔드가 설정을 못 찾으면 기본
저장소로 조용히 폴백한다) 이제 「게시글 스토리지」로 추가돼 다른 모듈과 같은 자리로 옮긴다.
허용 확장자는 이 저장소만의 목록을 받지 못해 전역 화이트리스트(`Globals.fileUpload.Extensions`)를
그대로 둔다 — 화면이 먼저 막지 않게 하기 위해서다. 저장소 목록이 더 좁으면 백엔드가 사유를
돌려주고 그 문구가 화면에 그대로 나간다. 실제 목록을 받으면 그 한 줄만 바꾼다.
첨부 입력에 `accept`와 확장자 사전 검증이 붙는다 — 지금까지는 아무 파일이나 고를 수 있었고
확장자가 틀리면 백엔드가 사유 없이 실패했다.
Co-Authored-By: Claude Opus 5
findBoardPostById가 있는데 아무도 부르지 않고 목록 행으로 폼을 채우고 있었다.
행은 화면에 떠 있는 동안 낡을 수 있고, 그대로 저장하면 남의 수정을 덮는다.
없어진 게시물이면 얼럿으로 알리고 팝업을 열지 않는다.
Co-Authored-By: Claude Opus 5
Fox*Icon 컴포넌트 141곳을 fox-ico 클래스로 바꾸고 옛 SVG 모듈을 지웠다.
아이콘 상자 규칙에는 상자 크기를 그대로 둔 채 font-size만 더했다 — 손으로 그린
인라인 svg가 든 자리도 같이 쓰이기 때문이다.
사이드바 메뉴 아이콘의 화이트리스트가 사라졌다. 폰트는 통째로 하나라 이름이 늘어도
번들이 커지지 않아, 백엔드가 주는 kebab 이름을 PascalCase로 바꿔 붙이기만 하면 된다.
@fox 13.3MB → 6.4MB.
Co-Authored-By: Claude Opus 5
유형을 프론트 상수 표(SIGNUP/PAYMENT/…)로 치환하고 있었다. 그 표를 지우고 공통코드
`BBS_FAQ_CD`를 조회해 상세코드명으로 옮긴다 — 1:1문의 진행상태(BBS_ANS_CD), 학교·학년
(COM_SCHUL_CD·GRD_CD)과 같은 방식이다.
목록의 「유형」 열과 등록·수정 팝업의 유형 선택지, Server Action의 허용 코드 검증이 모두 같은
응답을 본다. 페이지가 한 번 조회해 목록으로 내리고, Server Action은 저장 직전에 다시 조회한다
(화면이 보낸 코드를 그대로 믿지 않기 위해서다).
목록 URL의 `type` 필터만 예외로 뒀다 — `parseBoardPostQuery`는 도메인 계층이라 코드 조회를 할 수
없어 FAQ에서는 값을 그대로 받는다. 모르는 코드면 결과가 비는 것으로 끝나고, 저장 검증은 위에서
코드표로 한다.
코드가 한 건도 없으면 유형 칸을 그리지 않고 검증도 요구하지 않는다 — 빈 셀렉트를 보여 주고
"유형을 선택해 주세요"로 막으면 저장할 방법이 없어진다.
확인: 임시 페이지로 목록의 FAQ01→회원가입&로그인 치환, 표에 없는 코드(ZZZ99)는 코드 그대로
노출, 등록 팝업 선택지가 코드명으로 뜨고 고르면 hidden 필드에 코드값(FAQ01)이 실리는 것을
확인한 뒤 지웠다.
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
두 가지가 겹쳐 세 토글이 모두 먹히지 않았다.
1. 폼과 Server Action의 값 규약이 어긋났다. 팝업을 @fox로 옮기며 토글을 value="true"로 두었는데
게시판 액션의 readBoolean은 'Y'만 참으로 본다 — 켜도 항상 false가 되어 'N'으로 저장됐다.
꾸미기 아이템 쪽 규약을 그대로 가져온 것이 원인이라 게시판 규약('Y')에 맞췄다.
2. 수정 PUT을 쿼리스트링으로 보내고 있었다. 백엔드 update는 @RequestBody(JSON)라 본문이 비어
바인딩이 되지 않고, 통과하더라도 전 필드가 null이라 UPDATE의 가드가
모두 건너뛴다 — 수정으로는 어떤 값도 바뀌지 않았다. 1:1문의 답변 저장도 같은 경로였다.
등록 POST는 컨트롤러가 form 바인딩이라 그대로 둔다.
검증 — 팝업 FormData 실측: 기본 상태 isVisible='Y'(켜짐)·isPinned/isPushEnabled 없음(꺼짐),
두 토글을 켜면 셋 다 'Y'로 실린다(액션의 'Y' 규약과 일치).
TODO(백엔드): 앱푸쉬는 이 수정으로도 저장되지 않는다 — PUSH_YN이 MngrBbsMapper.xml의
INSERT·UPDATE·SELECT 어디에도 없다.
Co-Authored-By: Claude Opus 5