타일 아래 「추가 이미지 N」 캡션과 휴지통 아이콘을 지우고 「삭제」 글자(xsm 텍스트 버튼)만
둔다. 「추가 이미지」 캡션은 맨 끝 [+ 추가] 타일 아래에만 붙고, 타일 안은 시안대로
"+ 추가" 한 줄이라 N/11 카운트도 뺀다.
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
상단에 [+ 업체 등록]을 두고, 행 관리는 상세 유무와 관계없이 [수정][비활성|노출][삭제] 세 개로
통일한다(사용자 확정 — 동작보다 시안 우선). 상세는 기존 가맹점에만 붙어 여기서 업체를 만들
수 없으므로 [업체 등록]은 가맹점 관리로 가라는 안내만 띄운다. 상세 없는 행의 [비활성]·[삭제]는
백엔드가 NOT_FOUND로 거절해 사유가 토스트로 올라온다.
Co-Authored-By: Claude Opus 5
초기화는 카테고리·노출 상태·기간·업체명을 모두 비우고 첫 페이지로 돌아간다(초안과 URL을
함께 되돌린다).
행 선택 체크박스가 상세가 있는 행에만 그려져, 상세가 하나도 없는 목록에서는 기능이 통째로
사라졌다(사용자 지적). 모든 행에서 고를 수 있게 하고, 일괄 비활성·삭제는 그중 상세가 있는
행만 처리한다 — 없는 행은 백엔드가 NOT_FOUND로 거절하므로 보내기 전에 거른다.
Co-Authored-By: Claude Opus 5
카테고리·노출 상태·등록일 기간은 고를 때마다 조회하던 것을 초안으로만 두고 [적용] 한 번에
보낸다(사용자 확정). 업체명 검색은 검색 상자가 그대로 바로 반영한다.
등록일 규칙 — 시작일은 오늘을 넘지 못하고 종료일은 시작일보다 앞설 수 없다. 피커의 max/min으로
막고, 어기는 값은 clampDateRange가 경계로 당긴다. URL 파싱도 같은 함수를 쓴다 — 주소로 직접
넣은 값도 규칙을 지킨다.
오늘 날짜는 page.tsx가 정해 prop으로 내린다. 클라이언트에서 계산하면 서버·브라우저의
시간대·자정 경계에서 hydration이 어긋난다.
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
가맹점: 조회 SQL이 USE_YN을 select하지 않고 MngrVocVo에도 필드가 없어 값이 오지 않는데
`!== 'N'`으로 읽어 늘 「노출」이었다. 「미노출」로 걸러 낸 행까지 전부 「노출」로 보였다.
값이 없으면 판단하지 않고(null) 목록·모달 모두 고르지 않은 상태로 둔다 — 브랜드명·메모·
등록일과 같은 처리다(사용자 확정). 신규 등록만 「노출」로 시작한다.
회원: 학생·관리자 모두 상태를 바꿀 수는 있는데 목록에 그 열이 없어 확인할 자리가 없었다.
학생은 사용여부(useYn), 관리자는 잠김여부(acctLockYn의 반대)를 열로 넣는다. 값이 없는 행은
`-`다.
Co-Authored-By: Claude Opus 5
칸과 함께 도메인 필드·검증·저장 파라미터·응답 매핑까지 걷어낸다 — 그리는 곳이 이 칸뿐이라
남겨 두면 죽은 데이터가 된다.
MENU_EXPLAN 컬럼은 백엔드에 그대로 있다. update가 동적 SET()
이라 보내지 않으면 기존 값이 유지되고, 신규는 NULL로 들어간다 — 메뉴권한(roleId)과 같은
방식이다.
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
백엔드가 등록을 자체 저장에서 BizPlay 외부 가맹점 등록 API 위임으로 바꿨다
(edupay-backend 5fa9d34). `POST /api/v1/mngr/voc`가 form에서 @RequestBody(JSON)가 되고
bizPlayVocApiService.insertVoc를 부르며, 예금주명·입금은행코드·입금계좌번호·업태코드가
새로 필수가 됐다. 지금 등록 폼은 그 값을 받지 않아 계약이 맞지 않는다.
- 목록의 [등록] 버튼을 비활성화한다(엑셀등록과 같은 방식). 시안의 자리는 그대로 둔다.
- 모달을 여는 상태·렌더를 걷어내 폼에 닿을 수 없게 한다.
- saveMerchantAction도 저장 직전에서 끊는다 — Server Action은 UI를 거치지 않고도 호출되므로
버튼만 비활성화하면 막은 것이 아니다. 세션 확인과 입력 검증은 그대로 두어 다시 열 때
createMerchant 호출만 되살리면 되게 했다.
다시 열 때: createMerchant를 새 계약(JSON, MngrVocInsertReqVo)으로 바꾸고 폼에 정산 계좌
항목을 추가한 뒤, _actions.ts의 마지막 return을 try/catch + createMerchant로 되돌린다.
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