export const ADMIN_MEMBERS_PATH = '/admins'; export const ADMIN_MEMBERS_EXCEL_PATH = `${ADMIN_MEMBERS_PATH}/excel`; export type AdminMemberSearchField = 'name' | 'loginId' | 'phoneNumber'; export const ADMIN_MEMBER_SEARCH_FIELD_OPTIONS: ReadonlyArray<{ value: AdminMemberSearchField; label: string; }> = [ { value: 'name', label: '회원명' }, { value: 'loginId', label: 'ID' }, { value: 'phoneNumber', label: '휴대전화번호' }, ]; export type AdminMemberSortOption = 'createdAt' | 'name'; export const ADMIN_MEMBER_SORT_OPTIONS: ReadonlyArray<{ value: AdminMemberSortOption; label: string; }> = [ { value: 'createdAt', label: '생성일순' }, { value: 'name', label: '이름순' }, ]; export const ADMIN_MEMBER_PAGE_SIZE_OPTIONS = [10, 30, 50] as const; export type AdminMemberPageSize = (typeof ADMIN_MEMBER_PAGE_SIZE_OPTIONS)[number]; export const DEFAULT_ADMIN_MEMBER_SEARCH_FIELD: AdminMemberSearchField = 'name'; export const DEFAULT_ADMIN_MEMBER_SORT: AdminMemberSortOption = 'createdAt'; export const DEFAULT_ADMIN_MEMBER_PAGE_SIZE: AdminMemberPageSize = 10; const DEFAULT_PAGE = 1; const MAX_KEYWORD_LENGTH = 100; export type AdminMemberQuery = { searchField: AdminMemberSearchField; keyword: string; sort: AdminMemberSortOption; page: number; pageSize: AdminMemberPageSize; }; type RawSearchParams = Record; function readParam(params: RawSearchParams, key: string): string | undefined { const value = params[key]; return Array.isArray(value) ? value[0] : value; } function isAdminMemberSearchField( value: string | undefined ): value is AdminMemberSearchField { return ( value !== undefined && ADMIN_MEMBER_SEARCH_FIELD_OPTIONS.some((option) => option.value === value) ); } function isAdminMemberSortOption( value: string | undefined ): value is AdminMemberSortOption { return ( value !== undefined && ADMIN_MEMBER_SORT_OPTIONS.some((option) => option.value === value) ); } function isAdminMemberPageSize(value: number): value is AdminMemberPageSize { return (ADMIN_MEMBER_PAGE_SIZE_OPTIONS as readonly number[]).includes(value); } export function parseAdminMemberQuery( searchParams: RawSearchParams ): AdminMemberQuery { const searchFieldRaw = readParam(searchParams, 'searchField'); const sortRaw = readParam(searchParams, 'sort'); const keywordRaw = readParam(searchParams, 'keyword'); const pageRaw = Number(readParam(searchParams, 'page')); const pageSizeRaw = Number(readParam(searchParams, 'pageSize')); return { searchField: isAdminMemberSearchField(searchFieldRaw) ? searchFieldRaw : DEFAULT_ADMIN_MEMBER_SEARCH_FIELD, keyword: (keywordRaw ?? '').trim().slice(0, MAX_KEYWORD_LENGTH), sort: isAdminMemberSortOption(sortRaw) ? sortRaw : DEFAULT_ADMIN_MEMBER_SORT, page: Number.isInteger(pageRaw) && pageRaw > 0 ? pageRaw : DEFAULT_PAGE, pageSize: isAdminMemberPageSize(pageSizeRaw) ? pageSizeRaw : DEFAULT_ADMIN_MEMBER_PAGE_SIZE, }; } export function buildAdminMemberHref( query: AdminMemberQuery, overrides: Partial = {} ): string { const merged = { ...query, ...overrides }; const params = new URLSearchParams(); if (merged.searchField !== DEFAULT_ADMIN_MEMBER_SEARCH_FIELD) { params.set('searchField', merged.searchField); } if (merged.keyword) { params.set('keyword', merged.keyword); } if (merged.sort !== DEFAULT_ADMIN_MEMBER_SORT) { params.set('sort', merged.sort); } if (merged.pageSize !== DEFAULT_ADMIN_MEMBER_PAGE_SIZE) { params.set('pageSize', String(merged.pageSize)); } if (merged.page !== DEFAULT_PAGE) { params.set('page', String(merged.page)); } const queryString = params.toString(); return queryString ? `${ADMIN_MEMBERS_PATH}?${queryString}` : ADMIN_MEMBERS_PATH; }