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
... | ... | @@ -10,6 +10,7 @@ |
| 10 | 10 |
import { FoxToggleSwitch } from '@fox/core/components/fox-toggle-switch';
|
| 11 | 11 |
import { formatPhoneNumber } from '@/lib/domain/phone-number';
|
| 12 | 12 |
import {
|
| 13 |
+ EMPTY_FIELD_PLACEHOLDER, |
|
| 13 | 14 |
formatGradeClassNumber, |
| 14 | 15 |
formatOptionalValue, |
| 15 | 16 |
type StudentMember, |
... | ... | @@ -62,6 +63,11 @@ |
| 62 | 63 |
// 스위치를 넘겨도 라벨이 그대로다. 저장하면 이 값이 폼에 실려 나간다. |
| 63 | 64 |
const [isActive, setIsActive] = useState(member.isActive ?? false); |
| 64 | 65 |
|
| 66 |
+ // 학년·반이 하나도 없으면 `-`가 오는데, 그때는 "학교 / -"가 아니라 학교만 보여야 한다. |
|
| 67 |
+ const formattedGradeClass = formatGradeClassNumber(member); |
|
| 68 |
+ const gradeClassNumber = |
|
| 69 |
+ formattedGradeClass === EMPTY_FIELD_PLACEHOLDER ? null : formattedGradeClass; |
|
| 70 |
+ |
|
| 65 | 71 |
// 저장에 성공하면 닫는다. 목록은 Server Action의 revalidatePath가 다시 그린다. |
| 66 | 72 |
useEffect(() => {
|
| 67 | 73 |
if (state.status === 'success') {
|
... | ... | @@ -98,12 +104,7 @@ |
| 98 | 104 |
{
|
| 99 | 105 |
key: 'school', |
| 100 | 106 |
term: '학교 / 학년·반·번호', |
| 101 |
- description: joinPair( |
|
| 102 |
- member.schoolName, |
|
| 103 |
- formatGradeClassNumber(member) === '-' |
|
| 104 |
- ? null |
|
| 105 |
- : formatGradeClassNumber(member) |
|
| 106 |
- ), |
|
| 107 |
+ description: joinPair(member.schoolName, gradeClassNumber), |
|
| 107 | 108 |
}, |
| 108 | 109 |
{
|
| 109 | 110 |
key: 'isActive', |
--- app/(protected)/(basic)/students/_components/student-list.tsx
+++ app/(protected)/(basic)/students/_components/student-list.tsx
... | ... | @@ -14,7 +14,10 @@ |
| 14 | 14 |
FoxDownloadSimpleIcon, |
| 15 | 15 |
} from '@fox/core/icons'; |
| 16 | 16 |
import { formatPhoneNumber } from '@/lib/domain/phone-number';
|
| 17 |
-import type { StudentMember } from '@/lib/domain/student-member';
|
|
| 17 |
+import {
|
|
| 18 |
+ formatGradeClassNumber, |
|
| 19 |
+ type StudentMember, |
|
| 20 |
+} from '@/lib/domain/student-member'; |
|
| 18 | 21 |
import {
|
| 19 | 22 |
STUDENT_MEMBERS_EXCEL_PATH, |
| 20 | 23 |
STUDENT_MEMBER_PAGE_SIZE_OPTIONS, |
... | ... | @@ -40,16 +43,6 @@ |
| 40 | 43 |
/** 값이 없으면 시안처럼 자리를 비우지 않고 `-`로 채운다. */ |
| 41 | 44 |
function text(value: string | number | null | undefined) {
|
| 42 | 45 |
return value === null || value === undefined || value === '' ? '-' : String(value); |
| 43 |
-} |
|
| 44 |
- |
|
| 45 |
-/** 학년·반·번호는 시안이 "2학년 5반 7번" 한 칸으로 묶는다. */ |
|
| 46 |
-function formatClass(member: StudentMember) {
|
|
| 47 |
- const parts = [ |
|
| 48 |
- member.grade === null ? null : `${member.grade}학년`,
|
|
| 49 |
- member.classNumber === null ? null : `${member.classNumber}반`,
|
|
| 50 |
- member.studentNumber === null ? null : `${member.studentNumber}번`,
|
|
| 51 |
- ].filter(Boolean); |
|
| 52 |
- return parts.length > 0 ? parts.join(' ') : '-';
|
|
| 53 | 46 |
} |
| 54 | 47 |
|
| 55 | 48 |
/** |
... | ... | @@ -114,7 +107,7 @@ |
| 114 | 107 |
width: 160, |
| 115 | 108 |
render: (row) => text(row.schoolName), |
| 116 | 109 |
}, |
| 117 |
- { key: 'classInfo', header: '학년/반/번호', width: 160, render: formatClass },
|
|
| 110 |
+ { key: 'classInfo', header: '학년/반/번호', width: 160, render: formatGradeClassNumber },
|
|
| 118 | 111 |
{
|
| 119 | 112 |
key: 'guardianName', |
| 120 | 113 |
header: '보호자이름', |
--- lib/domain/student-member.ts
+++ lib/domain/student-member.ts
... | ... | @@ -52,15 +52,19 @@ |
| 52 | 52 |
|
| 53 | 53 |
/** |
| 54 | 54 |
* "학년/반/번호" 합성 표기. 표와 조회 팝업이 같은 규칙을 쓰도록 한 곳에 둔다. |
| 55 |
- * 세 값 중 하나라도 없으면 부분 문장("1학년 -반 -번")을 만들지 않고 통째로 `-`로 표기한다 —
|
|
| 56 |
- * 셋이 함께여야 의미가 성립하는 한 덩어리이기 때문이다. |
|
| 55 |
+ * |
|
| 56 |
+ * **있는 값만 이어 붙인다.** 백엔드 `TB_COM_USER`에는 GRADE·CLS_NO만 있고 학생 번호 컬럼이 |
|
| 57 |
+ * 아예 없어(코드베이스 전체 VO가 그 둘만 쓴다) 번호는 영영 오지 않는다. 셋이 다 있어야 |
|
| 58 |
+ * 표기하던 종전 규칙으로는 실제로 오는 학년·반까지 함께 묻혔다. |
|
| 59 |
+ * |
|
| 60 |
+ * 하나도 없으면 `-`다. |
|
| 57 | 61 |
*/ |
| 58 | 62 |
export function formatGradeClassNumber(member: StudentMember): string {
|
| 59 |
- const { grade, classNumber, studentNumber } = member;
|
|
| 63 |
+ const parts = [ |
|
| 64 |
+ member.grade === null ? null : `${member.grade}학년`,
|
|
| 65 |
+ member.classNumber === null ? null : `${member.classNumber}반`,
|
|
| 66 |
+ member.studentNumber === null ? null : `${member.studentNumber}번`,
|
|
| 67 |
+ ].filter((part): part is string => part !== null); |
|
| 60 | 68 |
|
| 61 |
- if (grade === null || classNumber === null || studentNumber === null) {
|
|
| 62 |
- return EMPTY_FIELD_PLACEHOLDER; |
|
| 63 |
- } |
|
| 64 |
- |
|
| 65 |
- return `${grade}학년 ${classNumber}반 ${studentNumber}번`;
|
|
| 69 |
+ return parts.length > 0 ? parts.join(' ') : EMPTY_FIELD_PLACEHOLDER;
|
|
| 66 | 70 |
} |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?