/** * 전화번호 표기. * * 백엔드는 하이픈 없이 숫자만 저장한다(예: `01011233400`). 시안은 `010-1123-3400`으로 * 끊어 보여주므로 화면에 나가기 직전에 여기서 끊는다 — 저장된 값 자체는 건드리지 않는다. * * 도메인마다 두지 않고 한 파일에 모은 이유는 학생·관리자·보호자가 같은 규칙을 써야 하기 * 때문이다. */ /** 자릿수별 묶음 규칙. 앞자리가 `02`인 서울 번호만 지역번호가 두 자리다. */ function splitGroups(digits: string): string[] | null { if (digits.startsWith('02')) { if (digits.length === 9) { return [digits.slice(0, 2), digits.slice(2, 5), digits.slice(5)]; } if (digits.length === 10) { return [digits.slice(0, 2), digits.slice(2, 6), digits.slice(6)]; } return null; } if (digits.length === 11) { return [digits.slice(0, 3), digits.slice(3, 7), digits.slice(7)]; } if (digits.length === 10) { return [digits.slice(0, 3), digits.slice(3, 6), digits.slice(6)]; } // 지역번호 없이 저장된 국번+번호. if (digits.length === 8) { return [digits.slice(0, 4), digits.slice(4)]; } return null; } /** * 하이픈을 넣어 돌려준다. * * - 이미 하이픈이 들어 있으면 그대로 둔다 — 백엔드가 형식을 갖춰 준 값을 다시 끊지 않는다. * - 자릿수가 알려진 규칙에 맞지 않으면 **원본을 그대로** 돌려준다. 임의로 끊으면 잘못된 * 번호를 그럴듯하게 보여주게 되므로, 판단이 서지 않을 때는 손대지 않는다. * - 값이 없으면 null 그대로다. `-` 표기는 호출부의 `formatOptionalValue`가 맡는다. */ export function formatPhoneNumber(value: string | null): string | null { if (value === null) { return null; } const trimmed = value.trim(); if (trimmed === '' || trimmed.includes('-')) { return trimmed === '' ? null : trimmed; } const digits = trimmed.replace(/\D/g, ''); const groups = digits.length === trimmed.length ? splitGroups(digits) : null; return groups === null ? trimmed : groups.join('-'); } /** * 휴대전화 앞자리 목록 — `FoxPhoneNumber`의 앞자리 셀렉트에 넘긴다. * * **넘기지 않으면 그 셀렉트에 고를 것이 하나도 없다**(@fox의 기본값이 빈 배열이다). 그때도 칸에는 * placeholder "010"이 떠서 고른 것처럼 보이는데 값은 빈 문자열이라, 가운데·끝자리를 다 채워도 * 8자리만 제출돼 "휴대전화 번호를 정확히 입력해 주세요."로 막힌다. */ export const MOBILE_PHONE_PREFIXES: readonly string[] = [ '010', '011', '016', '017', '018', '019', ]; /** * 앞자리 기본값. **화면이 이 값으로 시작해야 한다** — 셀렉트를 비워 두면 placeholder "010"이 * 고른 것처럼 보이는데 값은 비어 있어, 사용자가 앞자리를 건드리지 않고 뒷자리만 채우면 8자리가 * 제출된다(실제로 그래서 등록이 막혔다). */ export const DEFAULT_MOBILE_PHONE_PREFIX = MOBILE_PHONE_PREFIXES[0];