백엔드가 TB_COM_ADBK를 LEFT JOIN해 adbkList(parentNm·parentTelNo)로
보호자를 내려주기 시작했다. 한 학생에 여러 명이 붙을 수 있어 도메인 타입을
guardians 배열로 바꾸고, 목록의 보호자이름·보호자연락처 칸과 조회 팝업의
「보호자 이름 / 연락처」를 모두 줄을 나눠 그린다.
같은 개편에서 VO의 userTelno가 userTelNo로 바뀌어 있었다. 매핑이 옛 이름을
읽고 있어 휴대전화번호 칸이 늘 비어 있었다 — 새 이름으로 맞춘다.
Co-Authored-By: Claude Opus 5
폼이 든 모달이 바깥 클릭으로 닫히면 적어 둔 값이 통째로 사라진다. 조회 모달도
표를 훑다 여백을 눌러 닫히는 일이 잦다.
알림·확인(feedback-host)과 중복 로그인 안내는 그대로 뒀다 — 읽고 넘기는 자리라
바깥 클릭이 곧 취소다.
Co-Authored-By: Claude Opus 5
`formatGradeClassNumber`가 학년·반·번호 셋 중 하나라도 없으면 통째로 `-`를 돌려주고 있었다.
백엔드 `TB_COM_USER`에는 GRADE·CLS_NO만 있고 학생 번호 컬럼이 아예 없어(코드베이스 전체 VO가
그 둘만 쓴다) 번호가 영영 오지 않으므로, 실제로 오는 학년·반까지 함께 묻혔다.
있는 값만 이어 붙이도록 바꿨다. 하나도 없을 때만 `-`다.
같은 규칙이 두 곳에 따로 있던 것도 정리했다 — 목록은 자체 `formatClass`로 부분 표기를 이미
하고 있어서 팝업과 결과가 달랐다. 목록이 도메인 함수를 쓰도록 바꿔 한 곳으로 모았다.
팝업은 `-`가 오면 "학교 / -"가 아니라 학교만 보이도록 그대로 걸러 낸다.
검증 — 2·5·7 → "2학년 5반 7번", 2·5·null → "2학년 5반", 2·null·null → "2학년",
null·5·null → "5반", 셋 다 null → "-". 화면에서도 "폭스고등학교 / 2학년 5반"으로 나온다.
Co-Authored-By: Claude Opus 5
백엔드는 `USER_TELNO`를 하이픈 없이 숫자열로 준다(`01012345678`). 시안은 `010-1234-5678`로
끊어 보여주므로 화면에 나가기 직전에 끊는다 — 저장된 값 자체는 건드리지 않는다.
lib/domain/phone-number.ts를 새로 뒀다. 학생·관리자·보호자가 같은 규칙을 써야 해서 도메인마다
두지 않고 한 곳에 모았다(지금은 학생 화면만 쓴다).
규칙 — 휴대전화 11자리는 3-4-4, 10자리는 3-3-4, 서울(02)은 9자리 2-3-4·10자리 2-4-4,
지역번호 없는 8자리는 4-4다. **판단이 서지 않으면 손대지 않는다**: 이미 하이픈이 있거나
자릿수가 규칙에 맞지 않으면 원본을 그대로 돌려준다 — 임의로 끊으면 잘못된 번호를 그럴듯하게
보여주게 된다.
적용 — 목록의 휴대전화번호·보호자연락처, 조회 팝업의 휴대전화 번호·보호자 연락처.
검증 — 함수를 직접 돌려 확인했다. 01011233400→010-1123-3400, 0111234567→011-123-4567,
0212345678→02-1234-5678, 021234567→02-123-4567, 12345678→1234-5678, 이미 하이픈 있는 값과
123·14자리는 원본 유지, 빈 문자열·공백은 null. 화면에서도 01012345678이 010-1234-5678로,
보호자가 010-2222-1234로 나온다.
Co-Authored-By: Claude Opus 5
백엔드를 최신화(develop 924db37 → 4d98756)하고 확인하니 막고 있던 두 가지가 모두 풀렸다.
- `GET /api/v1/mngr/user/pagination`이 4개 필드에서 18개로 늘었다 — userTelno·userEmlAddr·
userType·schNm·grade·clsNo·birth·useYn이 실린다.
- `PUT /api/v1/mngr/user/{userId}/{useYn}` 사용여부 변경 API가 생겼다.
- (덤) `GET /api/v1/mngr/user/{userId}` 단건 조회도 생겼다. 지금은 쓰지 않는다 — 목록 행이
이미 갖고 있는 값으로 팝업을 채우므로 재조회가 필요 없다.
이에 맞춰 이식했다.
- Repository: toStudentMember가 전화·이메일·학교·학년·반·생년월일·사용여부를 실제 값으로
채운다. `useYn`은 'Y'/'N' 문자열이라 boolean으로 옮기고, 값이 없으면 판단하지 않고 null이다.
보호자·학생번호·가입일은 응답에 여전히 없어 `-`로 남는다(SQL·VO 어디에도 컬럼이 없다).
- Repository: updateStudentMemberUseYn 추가. 값과 대상이 모두 경로에 실리고 본문은 쓰지 않는다.
- Server Action: 미지원 반환을 걷어내고 실제 호출로 바꿨다. 성공하면 목록을 revalidate한다.
- 팝업: 저장 성공 시 닫는 동작을 되살렸다.
backend-fetch에 `canHaveEmptyBody`를 더했다 — 이 PUT은 인터페이스에 `ApiResponseVO` 반환으로
문서화돼 있지만 구현이 `void`이고 `ApiResponseVO.success(null)`을 만들어 놓고 버려서, 본문 없는
200이 나간다. 봉투를 파싱하기 전에 빈 본문을 갈라 성공으로 받는다. 백엔드가 봉투를 돌려주도록
고쳐도 그대로 동작한다.
감사 컬럼(LAST_MDFR_*)은 보내지 않는다 — 매퍼가 쓰지만 백엔드 CrudLogInterceptor가 UPDATE마다
자동으로 채운다(확인함).
Co-Authored-By: Claude Opus 5
토글은 백엔드에 사용여부 값도 변경 API도 없어 `disabled`로 막아 두었었다. 화면에서는 움직이게
해 달라는 결정에 따라 잠금을 풀었다(사용자 확정).
- 토글 상태를 이 컴포넌트가 갖는다. 종전에는 라벨을 prop에서 계산해(`member.isActive ? …`)
잠금을 풀어도 스위치만 움직이고 글자는 그대로였을 것이다 — 함께 고쳤다.
- 저장 버튼의 잠금도 풀었다. 다만 Server Action은 여전히 미지원을 알리므로, 그 사유를
모달 foot 왼쪽(utility 슬롯)에 `role="alert"`로 띄운다. 종전에는 title 속성에만 있어
눈에 띄지 않았다.
- 성공 시 닫는 useEffect를 지웠다. 지금 이 액션은 성공을 돌려주지 않는다 — 변경 API가 생겨
성공 경로가 살아나면 그때 함께 되살린다.
한계 — 토글 값은 화면에만 머문다. 저장되지 않고 목록에도 반영되지 않으며 팝업을 닫으면
사라진다. 실제 저장이 필요하면 백엔드 API가 생기거나 mock 오버레이(lib/data/mock)를 두어야
한다.
검증 — 비활성↔활성으로 체크와 라벨이 함께 바뀌고(트랙 #256ef4), 저장 버튼이 활성이다.
오류 문구는 세션이 필요한 Server Action 응답이라 프리뷰에서는 경로만 확인했다.
Co-Authored-By: Claude Opus 5
FoxModal의 `contents`가 `align-items: flex-start`인 flex 열이라, 그 안의 블록 요소가 내용
너비로 줄어들었다. 조회 목록은 줄마다 아래에 구분선을 긋기 때문에 폭이 줄면 선이 중간에서
끊긴 것처럼 보인다. 실측으로 contents 318에 폼과 목록이 169였다.
- FoxDescriptionList에 `align-self: stretch`를 줬다. flex 부모가 `flex-start`여도 스스로
가로를 채운다 — 컴포넌트가 어디에 놓이든 폭을 보장한다.
- 학생 조회 팝업의 폼 래퍼에도 폭을 줬다. 목록의 부모가 이 폼이라, 폼이 줄면 그 안에서
100%를 잡아도 소용이 없다.
검증 — 폼·목록·8줄 전부 318로 contents와 같아졌다(종전 169).
Co-Authored-By: Claude Opus 5
mock 학생 회원 생성기를 제거하고 GET /api/v1/mngr/user/pagination에 연결한다.
백엔드 저장소(develop b742bb4)의 실제 구현을 읽고 계약을 확정했다.
- searchCondition은 "1"(이름)·"2"(아이디)·"3"(휴대전화)만 유효하다. 목록에 없는 값을
보내면 조건 없이 전체가 반환되므로 지원되지 않는 학교명·회원코드 검색은 제거했다.
- 유효한 페이징 파라미터는 pageIndex·recordCountPerPage 둘뿐이다(서버가 offset을 직접
계산해 나머지를 덮어쓴다).
- 응답의 totalCount는 전체 건수가 아니라 현재 페이지 행 수다(백엔드에 count 쿼리 없음).
그대로 믿으면 가득 찬 페이지 뒤 데이터에 접근할 수 없어 하한값으로 보정하고, 확정되지
않은 건수는 화면에 "N명 이상"으로 표기한다.
- 응답이 주지 않는 항목(휴대전화·이메일·학교·학년/반·보호자·가입일·사용여부)은 열을
유지한 채 '-'로 표시한다. 정렬 파라미터와 사용여부 변경 API가 없어 해당 컨트롤은
비활성으로 둔다.