기획 BO-MAL-003(2805:1760)은 등록일시·등록자·최종 수정·현재 상태 네 쌍을 나란히 두는데,
FoxDescriptionList가 세로로 쌓고 줄마다 구분선을 그어 어긋났다. 목록에 inline 변형
(layout="inline")을 더해 쌍을 가로로 놓고 구분선을 뺀다. 이름은 label-sm·subtle, 값은
body-sm으로 시안 실측(12/13px)을 따르고, 쌍 사이 60px는 토큰에 없어 gap-11로 맞춘다.
Co-Authored-By: Claude Opus 5
FoxPageHeader에 description을 더한다 — 가맹몰 시안(FOX-PAY_2.0 2805:635)이 제목 아래
한 줄을 두는데 원 시안(page-tit-area)엔 그 자리가 없었다. 글자는 FoxHeadingGroup의
설명과 같은 토큰(body-md · font-neutral-subtle)이고 FoxListContainer가 그대로 넘긴다.
문구는 시안 그대로다 — 목록(BO-MAL-001) / 등록(BO-MAL-002) / 수정(BO-MAL-003) /
메뉴 등록·수정(BO-MAL-004).
Co-Authored-By: Claude Opus 5
가맹몰 상세 등록 화면의 카드들이 title/description을 넘기는데도 children이 있으면
FoxCard가 머리말을 버려 시안(FOX-PAY_2.0 2805:635)의 카드 제목·설명이 보이지 않았다.
contents를 header(gap 2)·body로 나누고 그 사이를 gap 5로 띄운다. 제목·설명만 있는
기존 카드는 모양이 같다.
Co-Authored-By: Claude Opus 5
기획 BO-MAL-001~004(지역상생몰 > 업체관리)를 「가맹몰 상세 관리」로 옮겼다. 백엔드
(15d409a)의 상세는 기존 가맹점(frcsNo)에 1:1로 붙는 구조라 업체를 새로 만들지 않는다 —
목록에 가맹점 전체를 보이고 행에서 [상세 등록]으로 들어가며, 업체명·연락처·주소는 가맹점
원본을 읽기 전용으로 보여 준다(사용자 확정).
- 목록: 업체명·카테고리·노출상태·등록일 검색, 체크박스 일괄 비활성·삭제, 행 관리
- 등록·수정(/[frcsNo]): 기본 정보 + 이미지(대표 1 + 추가 11) + 메뉴 표 + 등록 정보
- 메뉴 등록·수정(/[frcsNo]/products): 이미지·메뉴명·가격·설명·배지·노출·순서
- @fox FoxSortableList 신설 — 네이티브 DnD + 방향키, 상태 없이 onReorder만 (사용자 확정)
- 배지는 코드를 쉼표로 이어 frcsBadge에 싣는다(BEST,MAIN,NEW,SALE — 사용자 확정)
- 파일 저장소 모듈은 바우처용이 없어 잠정 MODULE_VOUCHER (사용자 확정)
백엔드가 아직 주지 않는 값(노출 상태·카테고리·수정 프리필·기간 검색·메뉴 건수·바우처 사용)은
화면을 시안대로 두고 그대로 보낸다(사용자 확정). 목록의 노출 토글만은 예외다 — PUT이 모든
컬럼을 덮어써 단건 조회가 비어 있는 동안 값 없이 보내면 상세가 지워지므로, 조회 결과에
대표이미지가 없으면 거부하고 수정 화면으로 안내한다.
추가 이미지 묶음은 fileSn이 1..n으로 채번되어 순서·삭제·추가 어느 것이든 통째로 다시 올린다.
저장된 장은 백엔드에서 다시 읽어 합친다. 묶음의 장 수를 묻는 API가 없어 HEAD로 더듬는다.
Co-Authored-By: Claude Opus 5
호버 규칙은 있었는데 색을 ``에 주고 있었다. 칸이 불투명한 바탕(surface-neutral-
default)을 그려서 그 위를 덮어, 누를 수 있는 줄에 마우스를 올려도 아무 변화가 없었다.
고른 줄이 제 색을 지키도록 그 줄은 비켜 간다 — 둘 다 자식 선택자라 순서로만 갈려서,
안 비키면 고른 줄에 마우스를 올릴 때 파란색이 회색으로 죽는다.
Co-Authored-By: Claude Opus 5
`inset-inline: 0`으로 양쪽을 트리거에 묶어 놓아, "기타"처럼 짧은 값이 든 좁은
셀렉트에서는 "초등학교"가 글자 단위로 두 줄이 됐다. 목록의 폭은 트리거가 아니라
항목이 정해야 한다.
트리거보다 좁아지지는 않게 두고(min-inline-size: 100%) 가장 긴 항목에 맞춰
늘어나게 한다. 넓은 셀렉트는 그대로다.
Co-Authored-By: Claude Opus 5
시안 scroll(7341:2675)이다. 컴포넌트가 아니라 스크롤이 생기는 요소에 입히는
외형이라 `fox.scroll()` 믹스인과 `.fox-scroll` 클래스로 냈다.
둘레 4px 여백은 트랙 padding이 아니라 막대의 투명 테두리로 만든다 — padding으로
주면 막대가 트랙 끝까지 가지 못한다. Chrome 121+가 표준 속성을 보면 웹킷 규칙을
통째로 무시하므로 Firefox 대비는 @supports로 갈라 놓았다.
이미 스크롤이 있던 @fox 컴포넌트 넷(셀렉트 목록·칩 셀렉트·모달·파일목록)에 적용했다.
Co-Authored-By: Claude Opus 5
세 가지가 겹쳤다. 파일업로드 칸이 `flex-shrink: 0`이라 좁은 그릇에서 버티고,
모달 안 form이 `align-items: flex-start`에 걸려 폭을 안 채우고, 모달은
`overflow-x: hidden`이라 넘친 만큼 그대로 잘렸다. 400px 모달에서 191px가 잘렸다.
칸은 늘어나지는 않고 줄어들기만 하게 두어 넓은 자리에서는 시안의 320px를 지킨다.
팝업은 md로 올려 그 320px가 그대로 들어간다.
Co-Authored-By: Claude Opus 5
컷당 구성이 시안과 달랐다. 시안은 테두리 컨테이너 안 회색 판(#E6E8EA) 위에 흰 컷 카드
(radius 12·neutral/lv1 그림자)가 4열로 깔리고, 카드마다 **점선 상자 자체가 파일선택 버튼**
(CameraPlus 중앙, 높이 172)이며 아래에 「N컷 · 초기화 · 삭제」가 붙는다. 「컷 추가」는 컨테이너
흰 푸터에 있다. 기존 구현은 테두리 박스 안에 작은 132×74 썸네일 + 파일선택 버튼이었고
초기화가 없었다.
FoxFileUpload에 `thumbnailPicker` 변형을 추가했다 — 썸네일 상자만 그리고 상자를 파일선택
버튼으로 쓴다(파일선택 버튼·설명·삭제 오버레이 없음). 시안의 이 패턴을 담을 형태가 image
모드에 없었다. 비우기·삭제는 호출부가 바깥에 만든다는 계약을 주석으로 못박았다.
초기화는 그 컷의 React key를 갈아 끼워 새로 마운트한다 — 비제어 FoxFileUpload가 들고 있는
파일과 네이티브 입력을 밖에서 비울 방법이 그것뿐이다. 초기화하면 기존 첨부 유지용
`cutFileId`도 함께 비워져 저장 시 그 컷이 빠진다.
확인(임시 페이지): 상자 클릭 대상 aria-label 「1컷 이미지 선택」, 파일 주입 → 미리보기 표시,
초기화 → 미리보기·네이티브 입력 모두 비워짐, 컷 추가 → 5컷에서 삭제 활성, 삭제 → 4컷에서
비활성. 컨테이너 테두리 #B1B8BE·radius 12·판 #E6E8EA·카드 그림자·점선 #CDD1D5·높이 172가
계산값으로 시안과 일치.
Co-Authored-By: Claude Fable 5
Figma의 grid 값은 아트보드 좌표였다. 폭은 아트보드 폭이고, 여백은 그 안에서 가운데로 맞추기
위한 offset이다 — PC의 `grid-margin-sm`이 420px인 이유가 (1920-1080)/2였다. CSS에서는 둘 다
뜻이 달라 그대로 쓸 수 없었다.
폭은 흔한 브레이크포인트로 반올림한다(사용자 확정 사항).
PC 1552 / 1080 / 800 → 1440 / 1024 / 768
모바일 348 / 320 / 280 → 360 / 320 / 280
같은 수를 `$breakpoints`로도 낸다. 그래야 "이 폭에서 레이아웃이 갈린다"와 "컨테이너가 여기까지
넓어진다"가 어긋나지 않는다. `xsm`(768)은 기존 `$breakpoint-pc`와 같은 값이라 그 믹스인은
그대로 쓸 수 있다.
여백은 아트보드 offset이 아니라 페이지 좌우 여백으로 바꾼다 — 모바일 16px · PC 48px. 컨테이너가
좁아진다고 여백이 넓어지지는 않으므로 세 단계가 한 값을 공유하고, `full`만 0(가장자리까지)이다.
가운데 정렬은 `margin-inline: auto`가 맡는다.
미디어 쿼리는 `var()`를 해석하지 못하므로 `fox.from(xsm|sm|default)` 믹스인을 함께 연다 —
`fox.pc`/`fox.mobile`과 같은 이유다. 모르는 이름은 컴파일 때 잡는다.
치환은 변환기(`build-tokens.py`)가 들고 있어 재export해도 유지된다.
확인: `fox.from(sm)`·`fox.from(default)`가 각각 `@media (min-width: 1024px)`·`1440px`로
컴파일되고, 모르는 이름은 "알 수 없는 브레이크포인트"로 끊긴다. 산출 CSS 변수도 새 값으로 나온다.
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
`grid-wrap-*`는 Figma 아트보드 폭이 그대로 들어온 값이다. `full`은 "부모 폭을 채운다"는 뜻인데
모바일 38rem(380px)·PC 192rem(1920px)으로 잡혀 있어 그보다 좁은 창에서는 넘치고 넓은 창에서는
덜 찬다. 두 모드 모두 `100%`로 바꾼다.
나머지 세 단계(default·sm·xsm)는 그대로 둔다 — 최대 폭 단계로는 쓸 수 있는 값이고, 유일한
소비처인 `_fox-snackbar.scss`도 `max-inline-size`로 쓰고 있다.
생성 파일만 고치면 다음 재export에서 지워지므로 변환기(`build-tokens.py`)에 `DEV_OVERRIDES`를
두고 거기서 치환한다. 시안 값을 CSS에서 그대로 쓸 수 없는 토큰이 더 나오면 같은 표에 붙인다.
Co-Authored-By: Claude Opus 5
디자인팀 요구다. 클래스명 하나로 쓰고 이름은 Figma의 `ico/House`를 대소문자까지
그대로 담는다. 굵기별 @font-face가 따로라 화면이 쓰는 굵기만 내려받는다.
기존 SVG 컴포넌트에서 뽑은 path로 구워 그림이 같다는 것이 데이터 수준에서 보장된다
(글리프 대조 결과 불일치 0.97%, 전부 외곽선 안티에일리어싱).
duotone은 글리프 하나가 두 색을 못 가져 배경겹을 별도 글리프로 넣고 CSS가 겹친다.
Co-Authored-By: Claude Opus 5
사용자 지시 — "주석이 너무 많다, 주석은 TODO나 이슈사항만 적어줘".
이번 작업에서 내가 쓴 설명 주석을 지웠다(23개 파일, 633줄 삭제 / 14줄 추가). 파일·함수 설명,
동작 요약, 결정 근거, 시안 번호, 검증 방법은 전부 뺐다 — 코드·커밋 메시지·README가 할 일이다.
남긴 것은 없으면 다음 사람이 사고를 낼 자리 다섯 줄뿐이다.
- 등록은 form(@ParameterObject), 수정은 JSON(@RequestBody)로 갈려 있음
- 백엔드 totalCount가 전체가 아니라 현재 페이지 행 수
- 관리자별 메뉴 권한이 백엔드에 없어 저장되지 않음
- 파일 업로드 moduleId는 틀려도 실패하지 않고 기본 저장소로 폴백 (+ 모듈 조회 API TODO)
- `
**FoxPhoneNumber** — `unit` 모드에서 `prefixOptions`가 비면 앞자리를 셀렉트가 아니라 입력 칸으로
그린다. 항목 없는 셀렉트는 placeholder "010"이 이미 고른 값처럼 보이는데 실제 값은 비어 있어,
뒷자리만 채운 번호가 조용히 만들어졌다(관리자 등록이 막힌 원인). 세 칸을 한 map으로 돌게
정리하면서 combine 분기와 모양을 맞췄다.
**FoxEmail** — 시안(form-email 5543:9940)은 줄이 나뉜다: [아이디] @ [도메인] 한 줄, 그 아래
도메인 드롭다운, 그 아래 확인 버튼. 종전에는 넷을 한 줄에 두고 넘치면 접히게 했는데, 좁은 곳에서
드롭다운이 도메인 칸보다 먼저 접혀 순서가 어긋났다(320px 팝업에서 세 줄로 흩어졌다).
`__body`(4px 간격) 안에 form-group·select·btn을 쌓고, 칸의 `min-inline-size: 24rem`을 0으로 바꿔
좁은 폭에서도 두 칸이 나눠 갖게 했다. 라벨의 필수 표시(`requirement`)도 다른 폼 컴포넌트와 같은
규약으로 추가했다.
**관리자 등록 팝업** — 손으로 짜 두었던 이메일 칸(아이디·도메인·직접입력 상태 세 개)을 `FoxEmail`
하나로 바꿨다. 시안 5227:2036 그대로이고, 아이디 placeholder는 "ID"(종전 "1234-5678"은 전화번호
칸에서 복사된 값이었다), 도메인은 첫 항목이 골라진 채로 시작한다.
검증 — 시안 대조 실측: 아이디 149×40 · 도메인 149×40(readOnly, "naver.com") · 셀렉트 320×40 ·
그룹↔셀렉트 간격 4 · 라벨 "이메일*"(시안 149/149/320/4와 일치).
동작: 직접입력 고르면 도메인 칸의 readOnly가 풀린다. prefixOptions 없이 쓴 FoxPhoneNumber는
앞자리가 입력 칸이 되고 "02"를 적으면 값에 실린다(종전에는 아무리 눌러도 빈 값이었다).
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