제목·경로 표시·구성을 「가맹몰 상세 정보」 하나로 두고, 저장 액션은 리다이렉트 대신 결과를
돌려준다. 처음 저장하면 토스트를 띄우고 그 가맹점 주소로만 옮겨 가 화면은 그대로이고
(바로 메뉴 추가 가능), 이후 저장은 그 자리에서 새로고침한다(사용자 확정). 이미지 칸은
저장으로 파일 id가 바뀌면 다시 마운트해 서버 값과 맞춘다.
Co-Authored-By: Claude Opus 5
dev 로그로 확인 — POST /common/file/upload/MODULE_VOUCHER(·/list)가 68~95KB PNG에도
500을 돌려줘 상세 등록이 막혔다. 저장소 설정 표에 바우처 행이 없고 코드상 기본 저장소
(FILE_STORAGE) 폴백도 dev DB에 행이 없어 거기서 터진다. 한 장은 콘텐츠 섬네일
저장소(MODULE_CONTENT_THUMB), 여러 장 묶음은 4컷만화 저장소(MODULE_TOON_CONTENT)를
빌려 쓴다 — 바우처 행이 생기면 file-module.ts의 두 ID만 바꾼다.
Co-Authored-By: Claude Opus 5
[+ 업체 등록]은 /system/merchant-details/new로 간다 — 업체명 자리에서 상세가 없는 가맹점을
고르면 연락처·주소가 채워지고 나머지는 같은 폼이다(사용자 확정). 행의 [수정]은 언제나 수정
화면이 되도록 목록에는 상세가 등록된 가맹점만 세운다 — 백엔드 목록에 그 조건이 없어
Repository가 전 페이지를 받아 거르고 화면 쪽 페이지로 자른다(TODO 백엔드). 상세가 없는
가맹점의 수정 주소로 들어오면 등록 화면으로 보내 미리 골라 둔다.
Co-Authored-By: Claude Opus 5
/api/v1/mngr/voc/product 계약을 origin/develop 22f67cb로 재확인했다 — 등록은 가맹점만
있으면 받지만 목록·단건·수정·삭제는 상세와 INNER JOIN이라 상세 저장이 먼저여야 한다.
등록 저장 뒤 목록으로 나가면 [수정]을 다시 눌러야 메뉴를 붙일 수 있었으므로, 등록만
같은 화면(수정 모드)에 남긴다. 목록 SQL에 ORDER BY가 없어 노출 순서 오름차순으로
정렬한다.
Co-Authored-By: Claude Opus 5
알림에 대상 컬럼이 없어(TB_COM_NTCN: 제목·내용·읽음뿐) 제목으로 가른다 — 지금 쌓이는
알림은 학습자 1:1문의 등록 하나라 1:1문의 게시판(/boards/inquiries)으로 보낸다. 누르면
드롭다운을 닫고, 링크는 hover·focus 표시를 갖는다. 모르는 알림은 종전처럼 누를 수 없다.
내용 줄은 두 줄로 잘라 요약처럼 보이게 하고 전체는 title로 남긴다. 다만 백엔드가
내용을 고정 문구("학습자가 1:1 문의 게시판에 게시글을 등록하였습니다")로 넣고 있어
실제 게시글 요약은 CmmBbsServiceImpl.insert가 ntcnCn에 글 본문을 실어 줘야 나온다.
Co-Authored-By: Claude Opus 5
타일 아래 「추가 이미지 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
백엔드에 회원코드 전용 필드가 없어 userId를 그대로 노출하던 열이다. 도메인 필드와
매핑도 함께 지운다 — 그리는 곳이 이 열뿐이라 남겨 두면 죽은 데이터가 된다.
행의 key로 쓰는 id(userId)는 그대로 둔다.
Co-Authored-By: Claude Opus 5
백엔드 insert에 @RequestBody가 붙었는데(MngrCntntsQuizApiController) 우리는 여전히 form으로
보내고 있어 등록이 HttpMediaTypeNotSupportedException으로 실패했다. 수정과 같은 JSON으로
통일하고 form 경로를 지운다. 쇼츠·만화 insert는 아직 어노테이션이 없어 그대로 form이다.
노출 문구 필드도 실제 계약으로 맞춘다 — answerMessageList(가칭)가 아니라 descrList이고
칸이 cntntsQuizDescr 하나뿐이다. 코드가 아니라 문구 자체를 싣는다: 학습자 앱이 이 값을
그대로 말풍선에 띄운다(CmmBotChatServiceImpl). 코드를 골랐으면 그 코드명이, 기타면 직접
적은 값이 저장된다.
단건 조회가 descrList를 돌려주므로 수정 화면이 저장된 문구를 되살린다. 코드명과 맞는 것이
있으면 그 코드로, 없으면 기타 + 직접 입력으로 돌린다.
Co-Authored-By: Claude Opus 5