임동욱 임동욱 08-18
fix: 조회 팝업의 "학교 / 학년·반·번호"에 학년·반이 빠지던 문제
`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 
@1c5f4efb7e6c69a6a2132d0a1b7d9be0124543be
app/(protected)/(basic)/students/_components/student-detail-modal.tsx
--- app/(protected)/(basic)/students/_components/student-detail-modal.tsx
+++ app/(protected)/(basic)/students/_components/student-detail-modal.tsx
@@ -10,6 +10,7 @@
 import { FoxToggleSwitch } from '@fox/core/components/fox-toggle-switch';
 import { formatPhoneNumber } from '@/lib/domain/phone-number';
 import {
+  EMPTY_FIELD_PLACEHOLDER,
   formatGradeClassNumber,
   formatOptionalValue,
   type StudentMember,
@@ -62,6 +63,11 @@
   // 스위치를 넘겨도 라벨이 그대로다. 저장하면 이 값이 폼에 실려 나간다.
   const [isActive, setIsActive] = useState(member.isActive ?? false);
 
+  // 학년·반이 하나도 없으면 `-`가 오는데, 그때는 "학교 / -"가 아니라 학교만 보여야 한다.
+  const formattedGradeClass = formatGradeClassNumber(member);
+  const gradeClassNumber =
+    formattedGradeClass === EMPTY_FIELD_PLACEHOLDER ? null : formattedGradeClass;
+
   // 저장에 성공하면 닫는다. 목록은 Server Action의 revalidatePath가 다시 그린다.
   useEffect(() => {
     if (state.status === 'success') {
@@ -98,12 +104,7 @@
     {
       key: 'school',
       term: '학교 / 학년·반·번호',
-      description: joinPair(
-        member.schoolName,
-        formatGradeClassNumber(member) === '-'
-          ? null
-          : formatGradeClassNumber(member)
-      ),
+      description: joinPair(member.schoolName, gradeClassNumber),
     },
     {
       key: 'isActive',
app/(protected)/(basic)/students/_components/student-list.tsx
--- app/(protected)/(basic)/students/_components/student-list.tsx
+++ app/(protected)/(basic)/students/_components/student-list.tsx
@@ -14,7 +14,10 @@
   FoxDownloadSimpleIcon,
 } from '@fox/core/icons';
 import { formatPhoneNumber } from '@/lib/domain/phone-number';
-import type { StudentMember } from '@/lib/domain/student-member';
+import {
+  formatGradeClassNumber,
+  type StudentMember,
+} from '@/lib/domain/student-member';
 import {
   STUDENT_MEMBERS_EXCEL_PATH,
   STUDENT_MEMBER_PAGE_SIZE_OPTIONS,
@@ -40,16 +43,6 @@
 /** 값이 없으면 시안처럼 자리를 비우지 않고 `-`로 채운다. */
 function text(value: string | number | null | undefined) {
   return value === null || value === undefined || value === '' ? '-' : String(value);
-}
-
-/** 학년·반·번호는 시안이 "2학년 5반 7번" 한 칸으로 묶는다. */
-function formatClass(member: StudentMember) {
-  const parts = [
-    member.grade === null ? null : `${member.grade}학년`,
-    member.classNumber === null ? null : `${member.classNumber}반`,
-    member.studentNumber === null ? null : `${member.studentNumber}번`,
-  ].filter(Boolean);
-  return parts.length > 0 ? parts.join(' ') : '-';
 }
 
 /**
@@ -114,7 +107,7 @@
       width: 160,
       render: (row) => text(row.schoolName),
     },
-    { key: 'classInfo', header: '학년/반/번호', width: 160, render: formatClass },
+    { key: 'classInfo', header: '학년/반/번호', width: 160, render: formatGradeClassNumber },
     {
       key: 'guardianName',
       header: '보호자이름',
lib/domain/student-member.ts
--- lib/domain/student-member.ts
+++ lib/domain/student-member.ts
@@ -52,15 +52,19 @@
 
 /**
  * "학년/반/번호" 합성 표기. 표와 조회 팝업이 같은 규칙을 쓰도록 한 곳에 둔다.
- * 세 값 중 하나라도 없으면 부분 문장("1학년 -반 -번")을 만들지 않고 통째로 `-`로 표기한다 —
- * 셋이 함께여야 의미가 성립하는 한 덩어리이기 때문이다.
+ *
+ * **있는 값만 이어 붙인다.** 백엔드 `TB_COM_USER`에는 GRADE·CLS_NO만 있고 학생 번호 컬럼이
+ * 아예 없어(코드베이스 전체 VO가 그 둘만 쓴다) 번호는 영영 오지 않는다. 셋이 다 있어야
+ * 표기하던 종전 규칙으로는 실제로 오는 학년·반까지 함께 묻혔다.
+ *
+ * 하나도 없으면 `-`다.
  */
 export function formatGradeClassNumber(member: StudentMember): string {
-  const { grade, classNumber, studentNumber } = member;
+  const parts = [
+    member.grade === null ? null : `${member.grade}학년`,
+    member.classNumber === null ? null : `${member.classNumber}반`,
+    member.studentNumber === null ? null : `${member.studentNumber}번`,
+  ].filter((part): part is string => part !== null);
 
-  if (grade === null || classNumber === null || studentNumber === null) {
-    return EMPTY_FIELD_PLACEHOLDER;
-  }
-
-  return `${grade}학년 ${classNumber}반 ${studentNumber}번`;
+  return parts.length > 0 ? parts.join(' ') : EMPTY_FIELD_PLACEHOLDER;
 }
Add a comment
List