카테고리·노출 상태·등록일 기간은 고를 때마다 조회하던 것을 초안으로만 두고 [적용] 한 번에
보낸다(사용자 확정). 업체명 검색은 검색 상자가 그대로 바로 반영한다.
등록일 규칙 — 시작일은 오늘을 넘지 못하고 종료일은 시작일보다 앞설 수 없다. 피커의 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
백엔드가 821f655에서 insert에 @RequestBody를 붙였다. 종전에는 등록만 어노테이션이 없어
form이었고 수정만 JSON이었는데, 이제 둘 다 JSON이다. 공지사항·FAQ 신규 등록이 415로
실패하던 원인이다.
같은 어긋남이 더 있는지 관리자 쓰기 API 전부를 대조했다 — 공통코드 상세·금칙어·앱 메뉴·
콘텐츠(쇼츠·만화)는 여전히 어노테이션이 없고, 꾸미기아이템·관리자 회원은 @ParameterObject라
form이 맞다. 가맹점 등록만 계약이 어긋난 채인데 호출부가 없다(등록 차단 중).
Co-Authored-By: Claude Opus 5
칸·열·검색·필터는 IS_BOARD_CATEGORY_ENABLED에 걸어 뒀는데 검증만 빠져 있었다. 화면에
구분 칸이 없어 값이 오지 않으므로 공지사항 등록이 「구분을 선택해 주세요」로 막혔다.
검증도 같은 플래그를 보게 한다 — 호출부가 아니라 검증기 안에서 본다. Server Action은 UI를
거치지 않고 호출될 수 있어, 스위치가 한 곳에 있어야 되살릴 때도 어긋나지 않는다.
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
공통코드 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
매퍼 includeWhere가 요구하는 searchCondition(1=아이디, 2=이름)을 보내고, VO에서
개명된 searchBno로 번호를 싣는다. searchLoginId 특례는 중복이라 뺐다. 응답에 새로
생긴 bno로 번호 열도 채운다. 매퍼가 includeWhere를 아직 include하지 않는 것은
백엔드 몫으로 남는다.
Co-Authored-By: Claude Fable 5
백엔드가 학습자의 1:1문의 등록 시 관리자 전원에게 알림을 적재하는데, 관리자 페이지에
그것을 볼 자리가 없었다. 종 버튼이 눌러도 아무 일이 없던 상태다.
목록은 레이아웃이 서버에서 조회해 내려보낸다 — 클라이언트가 나중에 부르면 첫 페인트에
비었다가 채워지고 토큰도 브라우저로 내려간다(사이드바 메뉴와 같은 이유).
조회가 실패해도 던지지 않는다. 헤더는 모든 화면에 붙어 있어 알림 하나 때문에 화면
전체가 죽으면 안 된다 — 못 가져오면 빈 목록이다.
읽음 처리는 붙이지 않았다. 백엔드 컨트롤러가 경로변수도 readYn도 VO에 담지 않아 어느
행도 갱신되지 않는다(보고함).
Co-Authored-By: Claude Opus 5
앞선 수정이 쿼리 파서만 고치고 주소 조립부를 빼놨다. 목록 하단 페이지 링크를 만들 때
`BOARD_SEARCH_FIELDS[boardType][0].value`를 읽어 1:1문의에서 터졌고, 콘텐츠 쪽에도
같은 줄이 있었다.
두 곳 다 기본값을 옵셔널로 읽고, 검색 대상이 비어 있으면 주소에 싣지 않는다.
Co-Authored-By: Claude Opus 5
검색을 감추며 대상 목록을 빈 배열로 만들었는데, 쿼리 파서가 기본값으로 `fields[0].value`를
읽고 있어 금융OX퀴즈 화면이 렌더 중에 터졌다. 1:1문의도 같은 형태였다.
목록이 비면 빈 문자열로 떨어뜨린다 — 검색 상자가 없으니 검색어도 늘 비어 있어 거르는
쪽에는 영향이 없다.
Co-Authored-By: Claude Opus 5
검색어를 넣으면 목록이 통째로 죽고 있었다. 백엔드 조건이 이름이 바뀌기 전 컬럼
(QUIZ_ANSWER·QUIZ_QUESTION)을 보고 있어 SQL 오류가 난다. 값을 보내지 않고 화면에서도
감춘다.
난이도도 같이 감춘다 — 백엔드가 searchLevelCd를 받기만 하고 조건에 쓰지 않아 골라도
목록이 그대로였다.
둘 다 SQL이 고쳐지면 되살리면 된다.
Co-Authored-By: Claude Opus 5
목록 쿼리가 거를 수 있는 것은 제목(searchCondition=1)과 노출여부(RLS_YN) 둘뿐이다.
그 둘은 백엔드로 넘기고 페이징도 맡긴다 — 별도 카운트 쿼리가 있다. 지금까지는 전체를
받아 프론트에서 거르고 잘랐다.
거르지 못하는 것은 감춘다(사용자 확정) — 내용·질문자명·전화번호·답변내용 검색과
유형·답변여부 필터다. 골라도 걸러지지 않으면 고장으로 보인다. 1:1문의는 남는 대상이
하나도 없어 검색 상자 자체를 감춘다.
Co-Authored-By: Claude Opus 5
백엔드에 검색이 생겼다 — `searchCondition` 1=이름 2=아이디 3=전화번호로 매퍼가 컬럼을
고르고, 별도 카운트 쿼리로 전체 건수를 센다. 지금까지는 전체(최대 1만 건)를 받아
프론트에서 거르고 잘랐다.
이름순만 예외로 남긴다. 백엔드에 정렬 파라미터가 없어 그때는 여전히 전체를 받는다.
Co-Authored-By: Claude Opus 5
백엔드가 TB_COM_ADBK를 LEFT JOIN해 adbkList(parentNm·parentTelNo)로
보호자를 내려주기 시작했다. 한 학생에 여러 명이 붙을 수 있어 도메인 타입을
guardians 배열로 바꾸고, 목록의 보호자이름·보호자연락처 칸과 조회 팝업의
「보호자 이름 / 연락처」를 모두 줄을 나눠 그린다.
같은 개편에서 VO의 userTelno가 userTelNo로 바뀌어 있었다. 매핑이 옛 이름을
읽고 있어 휴대전화번호 칸이 늘 비어 있었다 — 새 이름으로 맞춘다.
Co-Authored-By: Claude Opus 5