/** * 관리자 회원 도메인 타입 — 순수 데이터 표현, 외부 의존 없음. * * 데이터 출처는 백엔드(edupay-backend)의 `GET /api/v1/mngr/admin/pagination`이며, * `lib/data/repositories/admin-member-repository.ts`가 응답을 이 타입으로 매핑한다. * * **`null`의 의미는 "백엔드가 아직 주지 않는 항목"이다.** 목록 응답 VO(MngrAdminVo)가 담는 값은 * 식별자·이름·역할·계정상태 계열뿐이라 시안(ADM_ADM_101)의 휴대전화번호·이메일·생성일은 전부 * `null`로 채워지고 화면에서 `-`로 표시된다. 화면 컬럼은 그대로 유지한다 — 백엔드가 필드를 * 추가하면 Repository의 매핑만 늘리면 값이 그대로 채워진다. * * 등록/수정/삭제 API가 아직 없어 그 경로는 mock으로 동작한다(`lib/data/mock/admin-member-store.ts`). * mock이 만들어 낸 행만 휴대전화번호·이메일·메뉴 권한 값을 실제로 갖는다. */ /** 역할 코드 — 백엔드 `admRoleCd`. 코드 체계를 백엔드가 확정하지 않아 문자열로 다룬다. */ export type AdminRoleCode = string; export type AdminMember = { /** 내부 식별자 — 백엔드 `admUserId`. 목록 행의 key이자 수정/삭제의 입력값이다. */ id: string; /** 백엔드 `admNm`. */ name: string; /** 백엔드 `loginId`. 등록 후에는 변경할 수 없다(시안 ADM_ADM_103_p ①). */ loginId: string; /** 백엔드 목록 응답에 없다 — mock으로 등록한 행에만 값이 있다. */ phoneNumber: string | null; /** 백엔드 목록 응답에 없다 — mock으로 등록한 행에만 값이 있다. */ email: string | null; /** 백엔드 `admRoleCd`. 표기용 이름은 `formatAdminRoleLabel`이 만든다. */ roleCode: AdminRoleCode; /** * 접근 가능 메뉴 코드 목록(시안 ADM_ADM_102_p ③ "메뉴 선택", 다중 선택). * 백엔드 목록 응답에 없어 mock으로 등록/수정한 행에만 값이 있다 — 빈 배열은 "미지정"이다. */ menuCodes: string[]; /** ISO 형식(YYYY-MM-DD) 문자열. 백엔드 목록 응답에 없다(정렬에만 쓰이고 select되지 않는다). */ createdAt: string | null; /** 백엔드 `acctLockYn` — 계정 잠김 여부. 시안의 "잠김여부"는 이 값의 반대(활성/비활성)다. */ isLocked: boolean | null; /** 백엔드 `useYn` — 사용 여부. */ isActive: boolean | null; /** 백엔드 `loginFailCnt` — 로그인 연속 실패 횟수. 잠김 사유 판단용이라 목록에는 노출하지 않는다. */ loginFailCount: number | null; }; /** 값이 없는 항목의 화면 표기. 표·팝업이 같은 문자를 쓰도록 여기 한 곳에 둔다. */ export const EMPTY_FIELD_PLACEHOLDER = '-'; /** 값이 없으면 `-`, 있으면 문자열로 표기한다. */ export function formatOptionalValue(value: string | number | null): string { return value === null ? EMPTY_FIELD_PLACEHOLDER : String(value); } /** * 역할 코드 → 화면 표기 이름. * * **백엔드에 역할 코드 목록을 주는 API도, 코드 상수도 없다** — `TB_ADM_USER.ADM_ROLE_CD`를 * 그대로 내려줄 뿐이고 코드값의 정의는 어디에도 없다(백엔드 저장소 전체 검색으로 확인). * 시안(ADM_ADM_101)이 요구하는 표기는 "최고관리자/일반관리자" 둘이고, 실제로 관측된 코드는 * 로그인 토큰의 `admRoleCd` 클레임에서 본 `ROLE_SYSTEM` 하나뿐이다(`lib/domain/admin-user.ts`). * * 그래서 아래 표는 **잠정 매핑**이며, 표에 없는 코드는 임의의 이름을 지어내지 않고 코드를 그대로 * 노출한다 — 잘못된 역할 이름을 보여주는 것보다 낯선 코드를 보여주는 편이 낫다(권한 화면이라 * 표기 오류의 대가가 크다). 시안의 "역할 선택" 목록은 원래 [시스템관리 > 역할관리]가 등록한 * 역할을 가져와야 하는데 그 화면도 API도 아직 없어, 지금은 이 표가 곧 선택지다. * * 백엔드가 역할 코드 API를 내면 이 표를 지우고 그 응답으로 대체한다. */ const ADMIN_ROLE_LABEL_BY_CODE: Record = { ROLE_SYSTEM: '최고관리자', ROLE_ADMIN: '일반관리자', }; export const ADMIN_ROLE_OPTIONS: ReadonlyArray<{ value: AdminRoleCode; label: string; }> = Object.entries(ADMIN_ROLE_LABEL_BY_CODE).map(([value, label]) => ({ value, label, })); /** 시안의 "역할 선택" 기본값 — 잠정 매핑의 첫 항목(최고관리자). */ export const DEFAULT_ADMIN_ROLE_CODE: AdminRoleCode = 'ROLE_SYSTEM'; /** 역할 코드를 화면 표기로 바꾼다. 모르는 코드는 코드 그대로 노출한다(위 주석 참조). */ export function formatAdminRoleLabel(roleCode: AdminRoleCode): string { return ADMIN_ROLE_LABEL_BY_CODE[roleCode] ?? roleCode; } /** * 시안(ADM_ADM_103_p ③)의 "잠김여부" 표기. 항목은 활성/비활성이고, **잠기지 않은 계정이 활성**이다 * — 백엔드 필드가 `acctLockYn`(잠김 여부)이라 의미가 뒤집혀 있어 이 변환을 한 곳에 모아 둔다. */ export const ADMIN_LOCK_STATUS_OPTIONS: ReadonlyArray<{ value: string; label: string; }> = [ { value: 'false', label: '활성' }, { value: 'true', label: '비활성' }, ]; export function formatAdminLockStatusLabel(isLocked: boolean | null): string { if (isLocked === null) { return EMPTY_FIELD_PLACEHOLDER; } return isLocked ? '비활성' : '활성'; } /** * 접근 가능 메뉴 목록(시안 ADM_ADM_102_p ③ "메뉴 선택"). * * **백엔드에 관리자 메뉴 API가 없어 mock 카탈로그다.** 값은 시안 사이드바의 대메뉴 구성을 그대로 * 옮겼다 — 실제 메뉴 API가 생기면 이 상수를 지우고 그 응답으로 대체한다. */ export const ADMIN_MENU_OPTIONS: ReadonlyArray<{ value: string; label: string; }> = [ { value: 'MEMBER', label: '회원정보관리' }, { value: 'ADMIN', label: '관리자정보관리' }, { value: 'DECO_ITEM', label: '꾸미기아이템관리' }, { value: 'BOARD', label: '게시판관리(고객센터)' }, { value: 'POINT', label: '포인트관리' }, { value: 'CONTENTS', label: '콘텐츠관리' }, { value: 'SYSTEM', label: '시스템관리' }, ]; /** 선택된 메뉴 코드를 화면 표기로 바꾼다. 하나도 없으면 `-`. */ export function formatAdminMenuLabels(menuCodes: string[]): string { if (menuCodes.length === 0) { return EMPTY_FIELD_PLACEHOLDER; } return menuCodes .map( (code) => ADMIN_MENU_OPTIONS.find((option) => option.value === code)?.label ?? code ) .join(', '); }