/** * 학생 회원 도메인 타입 — 순수 데이터 표현, 외부 의존 없음. * * 데이터 출처는 백엔드(edupay-backend)의 `GET /api/v1/mngr/user/pagination`이며, * `lib/data/repositories/student-member-repository.ts`가 응답을 이 타입으로 매핑한다. * * **`null`의 의미는 "백엔드가 아직 주지 않는 항목"이다.** 현재 응답이 담고 있는 값은 * 식별자·이름 계열(`userId`/`loginId`/`userNm`) 넷뿐이라 나머지 항목은 전부 `null`로 채워지고 * 화면에서 `-`로 표시된다(화면 컬럼은 유지 — 백엔드가 필드를 추가하면 Repository의 매핑만 * 늘리면 그대로 채워진다). 값이 "비어 있다"와 "제공되지 않는다"를 굳이 구분하지 않는 이유는, * 조회 전용 화면에서 둘 다 사용자에게는 `-`로 같은 의미이기 때문이다. * * 조회 전용 화면(등록/수정/삭제 없음)의 데이터라 모든 필드를 읽기 전용으로 취급한다 * (수정 가능 여부는 UI 계층의 책임이지 타입 자체의 제약은 아니다). */ export type StudentMember = { /** 내부 식별자 — 백엔드 `userId`. 목록 행의 key이자 향후 단건 조회의 입력값이다. */ id: string; /** 화면에 노출되는 회원코드. 백엔드에 전용 필드가 없어 현재는 `userId`를 그대로 쓴다. */ memberCode: string; /** 백엔드 `userNm`. */ name: string; /** 백엔드 `loginId`. */ loginId: string; phoneNumber: string | null; email: string | null; /** 회원 역할 — 본 화면(학생 회원 목록)에서는 항상 '학생'이다. */ role: string; schoolName: string | null; grade: number | null; classNumber: number | null; studentNumber: number | null; guardianName: string | null; guardianPhoneNumber: string | null; /** ISO 형식(YYYY-MM-DD) 문자열. */ joinedAt: string | null; /** ISO 형식(YYYY-MM-DD) 문자열. */ birthDate: string | null; /** 사용여부. 백엔드가 값도 변경 API도 제공하지 않아 현재는 항상 null이다. */ isActive: boolean | null; }; /** 값이 없는 항목의 화면 표기. 표·조회 팝업이 같은 문자를 쓰도록 여기 한 곳에 둔다. */ export const EMPTY_FIELD_PLACEHOLDER = '-'; /** 값이 없으면 `-`, 있으면 문자열로 표기한다. */ export function formatOptionalValue( value: string | number | null ): string { return value === null ? EMPTY_FIELD_PLACEHOLDER : String(value); } /** * "학년/반/번호" 합성 표기. 표와 조회 팝업이 같은 규칙을 쓰도록 한 곳에 둔다. * 세 값 중 하나라도 없으면 부분 문장("1학년 -반 -번")을 만들지 않고 통째로 `-`로 표기한다 — * 셋이 함께여야 의미가 성립하는 한 덩어리이기 때문이다. */ export function formatGradeClassNumber(member: StudentMember): string { const { grade, classNumber, studentNumber } = member; if (grade === null || classNumber === null || studentNumber === null) { return EMPTY_FIELD_PLACEHOLDER; } return `${grade}학년 ${classNumber}반 ${studentNumber}번`; }