공통코드 QUIZ_ANSWER로 O·X 선택지를 조회하던 것을 되돌린다(f2af4f2). 정답여부는 화면
고정값이고, 코드로 받는 것은 각 줄의 「사용자에게 노출할 문구」뿐이다 — O는 QUIZ_ANSWER_O,
X는 QUIZ_ANSWER_X로 목록이 서로 다르다.
Co-Authored-By: Claude Opus 5
O와 X의 문구 목록이 서로 달라 그룹이 둘로 갈린다 — QUIZ_ANSWER_O · QUIZ_ANSWER_X.
정답 코드명을 접미로 붙여 그룹 이름을 만들므로, 정답 선택지가 늘어도 그룹만 등록하면
화면은 손대지 않는다.
기타는 코드가 아니라 직접 입력을 여는 자리라 프론트가 목록 뒤에 붙이고, 고르면 사용자가
적은 문구가 전송된다. 서버 검증도 하드코딩 표 대신 같은 그룹을 조회해 대조한다.
Co-Authored-By: Claude Opus 5
학교·학년 코드표 조회(`fetchSchoolGradeCodes`)가 검증 인자로 들어가면서 try/catch **밖**에
놓였다. 저장 호출만 감싸고 있었으니 코드 조회가 실패하면 예외가 Server Action을 그대로 빠져나가
`Cannot read properties of undefined (reading 'stack')`로 끝난다.
실패는 실제로 일어난다 — 백엔드는 인가 실패도 200이 아닌 401에 봉투를 실어 돌려주고
(확인: `GET /api/v1/mngr/code/list/COM_SCHUL_CD` → `{"code":401,"message":"인가된 사용자가
아닙니다."}`), 세션 쿠키는 살아 있어도 액세스 토큰이 먼저 만료될 수 있다. 화면을 열 때는
멀쩡하다가 저장할 때 터지는 이유가 이것이다.
여섯 개 쓰기 액션의 본문을 `runWrite`로 감싸 코드표 조회·업로드·저장이 모두 한 울타리 안에서
일어나게 한다(게시판 액션과 같은 방식). `redirect()`는 예외로 흐름을 끊으므로 울타리 밖에 둔다.
이제 같은 실패가 폼 아래 메시지로 보인다.
함께 고친 것:
- 코드가 한 건도 없는 그룹이 `data: null`로 오면 예외 대신 빈 목록으로 다룬다. 학년(`GRD_CD`)은
학교에 따라 결과가 빌 수 있어 이것을 오류로 보면 화면 전체가 멎는다.
- `/contents`에 error·loading 경계를 둔다(다른 화면과 같은 구성). 없으면 조회 실패가 세그먼트
경계 없이 위로 튀어 같은 종류의 불투명한 오류로 보인다.
Co-Authored-By: Claude Opus 5
학교급을 프론트 상수 표(ELEM/MIDD/HIGH/ALL + 학년 수)로 들고 있었다. 그 표를 지우고 공통코드를
조회해 쓴다 — 1:1문의 진행상태(BBS_ANS_CD), 꾸미기 아이템 카테고리(ITEM_CATE_CD)와 같은 방식이다.
학년은 학교마다 다르므로 `GET /api/v1/mngr/code/list/GRD_CD?atrbNm={학교코드}`로 학교별로
조회한다(백엔드 매퍼가 `ATRB_NM1 = #{atrbNm}`로 거른다). 상세 목록 SQL이 `ATRB_NM1`을 select하지
않아 한 번에 받아 화면에서 가를 수 없어 학교 수만큼 왕복하고, `fetchSchoolGradeCodes()`가 그
왕복을 한 곳에 모아 `cache()`로 중복을 막는다.
저장에는 `GRD_CD` 코드값을 보낸다 — 화면의 학년 칩이 코드값을 그대로 hidden 필드에 싣고,
Server Action은 저장 직전에 코드표를 다시 조회해 그 학교에 없는 학년 코드를 거른다(화면이 보낸
값을 믿지 않는 기존 규칙과 같다).
"전체연령은 학년 선택 불가"가 하드코딩에서 데이터로 바뀌었다 — 학년 코드가 하나도 없는 학교는
칩을 그리지 않고 검증도 학년을 요구하지 않는다.
확인: 학교 전환 시 학년 칩이 그 학교 것으로 갈리고 이전 선택이 비워지는 것, 다중 선택이
`GRD001,GRD003`처럼 코드값으로 실리는 것, 학년 코드가 없는 학교는 칩이 0개인 것을 임시 페이지로
확인한 뒤 지웠다.
Co-Authored-By: Claude Opus 5
기획 SYS_COD_001(1842:17507)과 팝업 4종(1842:16056·16122·16179·16262),
시안 관리자페이지(QsFVFFUKGH68xAPVkF3SJ9) 5402:13399.
한 화면에 목록이 둘이다. 좌측 공통코드를 행 전체로 눌러 고르면 우측 상세코드가 그 그룹으로
갱신된다(기획 ④). 선택·검색·두 목록의 페이지가 모두 URL에 실려, 갱신은 서버가 다시 그린다 —
공통코드를 바꾸면 상세 페이지가 1로 돌아가는 규칙은 buildCommonCodeHref가 강제한다(호출부가
잊어도 어긋나지 않게).
**검색·페이징을 서버(RSC)에서 한다.** 백엔드가 두 목록 모두 전체를 반환하고 그룹 검색도
LIKE가 아닌 완전일치라, 부분일치 필터와 페이지 자르기를 page.tsx가 맡는다(사용자 확정 사항).
전체를 받으므로 총건수는 정확해 다른 목록의 하한값 보정이 여기엔 필요 없다.
URL의 comCd가 목록에 없으면 첫 행으로 대체한다 — 검색으로 걸러졌거나 삭제된 코드가 주소에
남아 있으면 우측이 영영 비어 보인다.
분류코드(시안의 CMS 셀렉트)는 저장되지 않는다. TB_SYS_COM_CD에 해당 컬럼이 없어(관리자용·
공용 두 매퍼와 후보 컬럼명까지 확인), 코드ID 접두사를 채워 주는 입력 보조로만 쓴다 — 시안
목록이 전부 CMS+3자리라 그 작명 규칙을 화면이 거드는 것이다(사용자 확정 사항).
설명 열은 한 줄 말줄임이다. 시안 셀에는 말줄임 스타일이 없지만(전문 + word-break) 프레임이
44로 고정이라 Figma가 넘침을 잘라 보여준다 — CSS는 감싸므로 그대로 두면 자유 입력인 설명
때문에 행 높이가 제각각이 된다. 잘린 글자는 title로 남긴다.
@fox: FoxHeadingGroup 신설(시안 3002:7177). 화면에 여럿 설 수 있는 구역 제목이라
FoxPageHeader(h1·breadcrumb)와 역할이 다르고, 제목 태그를 고를 수 있다. 아이템 목록 시안에도
숨김으로 들어 있어 재사용된다.
백엔드 요청 2건(시안대로 두고 보고, 사용자 확정 사항)
- 상세 목록 조회 SQL이 DTL_CD_EXPLN을 select하지 않아 상세코드설명이 늘 비어 온다.
- 상세 등록 INSERT에도 없어 등록 시 입력한 설명이 저장되지 않는다(수정은 정상).
검증 — 시안 대조 실측: 패널 2단 gap 40, 열 폭 공통 80/120/160/156/120/120 · 상세
80/120/120/196/120/120, 행 높이 44(+테두리 1), 검색 320, 패널 제목 26px #1e2124 h2.
동작: 분류코드 SYS 선택 시 코드ID 004→SYS004, 다시 CMS 선택 시 CMS004(접두사 교체),
상세코드 등록 팝업의 코드ID는 읽기 전용·name 없음(제출 제외)에 hidden comCd로 전달,
정렬번호 기본값 11(기존 10개 다음).
Co-Authored-By: Claude Opus 5
`GET /api/v1/mngr/code/list/ITEM_CATE_CD`(사용자 안내)로 카테고리를 가져온다. 종전에는
코드테이블이 정비되기 전이라 프론트에 임시 코드(CATE01 계절 / CATE02 축하 / CATE03 시즌)를
두고 있었다 — 그 상수를 지웠다.
- lib/domain/common-code.ts — 코드 그룹 도메인. 코드 그룹 ID를 한곳에 모아 화면이 문자열을
직접 적지 않게 한다.
- lib/data/repositories/common-code-repository.ts — 조회. `cache()`로 감싸 한 요청 안에서
화면(선택지 그리기)과 Server Action(입력값 검증)이 같은 목록을 한 번만 받아 쓰게 한다.
그리는 데 쓴 목록과 검증에 쓴 목록이 반드시 같아진다는 점이 중요하다.
- 정렬은 백엔드 순서를 그대로 쓴다 — SQL이 아이템 목록과 같은 이중 역순 패턴이라 화면에서
다시 정렬하면 다른 화면과 어긋난다.
- 코드 한 줄이 깨져도(코드값 없음) 그 줄만 건너뛴다. 목록 조회의 fail-fast와 다른 판단인데,
선택지 하나 때문에 화면 전체를 못 쓰게 만들 이유가 없기 때문이다.
검증도 상수 대신 조회 결과를 쓴다. `validateDecorationItemCreate/Update`가 허용 코드 목록을
**인자로** 받고(domain 계층은 통신을 하지 않는다), Server Action이 저장 직전에 다시 조회해
넘긴다 — 화면이 보낸 코드를 그대로 믿으면 코드테이블에 없는 값이 저장된다.
카테고리 표기도 단순해졌다. 목록 SQL이 코드테이블을 조인해 `itemCateNm`을 내려 주므로
프론트 목록에서 이름을 찾던 두 번째 경로가 사라지고, 코드가 지워졌을 때만 코드값으로 떨어진다.
Co-Authored-By: Claude Opus 5