디자인팀 확정. thead 칸은 열의 align을 받지 않고(FoxTableCell·FoxListContainer) 스타일이
가운데로 고정한다 — 본문 행의 머리칸(form 레이블 열)은 그대로 왼쪽. 휴대전화번호·전화번호·
연락처·보호자연락처 열은 긴 글로 보아 align=start.
Co-Authored-By: Claude Opus 5
가맹몰 추가 이미지는 타일 자체를 잡아 좌우로 옮기고 하단 [저장] 한 번에 반영하는 방식이다
(사용자 확정). 왼쪽 위 손잡이 버튼을 지우고 li가 draggable·tabindex=0이 되어 마우스는
타일을, 키보드는 초점을 둔 항목을 방향키로 옮긴다. 본문 안 버튼·입력에서 올라오는 키는
받지 않는다.
Co-Authored-By: Claude Opus 5
기획 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
컷당 구성이 시안과 달랐다. 시안은 테두리 컨테이너 안 회색 판(#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
예시안 안에 손으로 그린 SVG 도형이 셋 남아 있었다(DemoIcon·DemoStar·DemoCalendar). 이 화면이
곧 사용법 안내라, 버튼·칩·셀렉트 슬롯에 SVG가 들어가 있으면 "아이콘은 이렇게 넣는다"로 잘못
읽힌다 — 실제로 혼동이 생겼다. 셋 다 `fox-ico` 클래스로 바꾼다(CaretRight·Star·CalendarBlank).
호출부 69곳은 그대로다.
폰트 아이콘은 `width`/`height`가 아니라 `font-size`가 크기를 정한다. FoxSelect 힌트 아이콘
15곳의 `style`도 함께 바꿨다.
아이콘 슬롯 prop 주석도 고친다 — "`currentColor`로 그린 SVG여야 한다"가 다섯 컴포넌트에 남아
있어 같은 혼동을 계속 만든다. 마크업 계약을 그대로 적는다.
`DemoThumb`(320×180)만 SVG로 둔다 — 아이콘이 아니라 카드·모달의 사진 자리를 채우는 그림이다.
확인: 예시안의 버튼 아이콘이 "Fox Icons Regular" 16×16으로 그려지고 글리프가 실린다.
칩의 달력도 CalendarBlank로 나온다. 예시안 어디에도 `
시안에 ico/*가 지정된 자리를 Figma에서 확인해 그 글리프로 바꿨다. 시안에서 확인이
안 되는 두 개는 손그림을 캔버스로 렌더해 후보 글리프와 픽셀 대조했다 —
tag의 택 표시는 Tag/Fill(불일치 0.03%), chip의 체크는 Check/Bold(0.19%)로
손그림이 그 글리프를 그대로 옮긴 것이었다.
남긴 것 넷은 아이콘이 아니다. 툴팁 꼬리, 체크박스·라디오의 표시(시안이 별도
컴포넌트로 둔 것), 단계 표시의 체크(시안이 선으로 준 것), 그리고 Tailwind 잔재에
기대고 있는 components/ui 캐럿이다.
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
디자인팀 요구다. 클래스명 하나로 쓰고 이름은 Figma의 `ico/House`를 대소문자까지
그대로 담는다. 굵기별 @font-face가 따로라 화면이 쓰는 굵기만 내려받는다.
기존 SVG 컴포넌트에서 뽑은 path로 구워 그림이 같다는 것이 데이터 수준에서 보장된다
(글리프 대조 결과 불일치 0.97%, 전부 외곽선 안티에일리어싱).
duotone은 글리프 하나가 두 색을 못 가져 배경겹을 별도 글리프로 넣고 CSS가 겹친다.
Co-Authored-By: Claude Opus 5
시안 관리자페이지(QsFVFFUKGH68xAPVkF3SJ9) A_BOA 목록 5227:5058 · 등록 팝업 5227:5848.
공지사항·1:1문의·FAQ 세 화면이 같은 뼈대를 쓴다.
stngId를 실제 값으로 바꿨다. 종전 TEMP_STNG_ID_* 임시값이라 목록이 늘 비어 있었다
(/api/v1/common/bbs/stng/list 응답, typeSe가 셋 다 'NOT'이라 구분에 쓸 수 없어 하드코딩).
목록 — FoxListContainer + 시안의 필터 줄(새로고침 아이콘 + 전체/노출/미노출 알약 탭).
구분은 FoxBadge(neutral), 사용여부는 FoxStatusIndicator, 첨부는 클립 아이콘, 관리는 테두리
아이콘 버튼. 세 화면이 열 정의만 다르고 나머지는 공유한다.
등록·수정 팝업 — FoxModal(md=560, 시안 폭)에 구분·제목·내용(FoxTextArea)·첨부파일
(FoxFileUpload default)·상단고정·게시기간·사용여부·앱푸쉬. 공지사항 전용 항목은 그 게시판에서만
그린다. 죽은 components/ui 대신 전부 @fox다.
옛 표·툴바·필터바 컴포넌트 5개는 지웠다(참조 0).
@fox: FoxTextArea에 requirement 추가 — 다른 폼 컴포넌트와 같은 규약.
TODO(백엔드): 구분(bbsCd)은 조회로만 오고 등록·수정 요청 VO에 필드가 없어 저장되지 않는다.
TODO: 게시기간이 네이티브 date 입력이다 — 시안은 YYYY.MM.DD 형식의 전용 필드이고 @fox에
날짜 선택 조각이 없다.
TODO: 목록 정렬이 백엔드 고정(등록일 오름차순)이라 "최근등록순" 셀렉트가 표시만 한다.
검증 — 시안 대조 실측: 모달 560px, 첨부 [파일선택] 버튼 100px 정상 노출, 목록 8열·필터 알약·
페이지네이션 정상.
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
## 원인
`FoxFileUpload`가 파일을 고른 **직후 네이티브 입력을 비웠다**.
const handlePicked = (event) => {
takeFiles(Array.from(event.target.files ?? []));
event.target.value = ""; // ← 같은 파일 재선택 때문에
};
`input.value = ""`는 `input.files`까지 비운다. 폼 제출에 실리는 것은 React 상태가 아니라 그
`files`이므로, `name`을 줘도 FormData에는 **빈 File(size 0)**만 남았다 — `name` prop 설명이
"주면 고른 파일이 폼 제출에 그대로 실린다"고 약속한 것과 정반대다. 끌어다 놓은 파일은 애초에
입력을 거치지 않아 마찬가지였다.
그래서 꾸미기 아이템 등록은 이미지를 아무리 골라도 Server Action이 파일을 못 받아
"썸네일 이미지를 등록해 주세요."만 반복했다.
## 고침
비우는 대신 **지금 고른 목록으로 입력을 덮는다**(`DataTransfer`). 두 가지가 함께 해결된다 —
제출에 실리고, 목록을 비웠을 때는 입력도 비어 같은 파일을 다시 고를 수 있다. 삭제도 같은 경로를
지나므로 썸네일을 지우면 입력에서도 빠진다.
확인: `name="imageFile"`인 FoxFileUpload를 폼에 넣고 파일을 고른 뒤 제출하니
`name=thumb.png size=1234`가 나온다(종전에는 `name= size=0`). 삭제 후 제출하면 다시 `size=0`이다.
## 곁들여
시안이 "JPG, PNG 파일만 가능 … 최대 5MB"라고 못박아 두었는데 화면이 그걸 재지 않았다. 백엔드
저장소 설정에도 확장자·크기 제한이 있지만 걸리면 "이미지를 업로드하지 못했습니다."라는 뭉뚱그린
문구만 남는다. 규칙과 안내문을 `decoration-item-form.ts` 한곳에 두고 업로드 직전에 재도록 했다.
Co-Authored-By: Claude Opus 5