export const POINT_ACCOUNTS_PATH = '/points/accumulated'; export type PointAccountSearchField = 'loginId' | 'studentName'; export const POINT_ACCOUNT_SEARCH_FIELD_OPTIONS: ReadonlyArray<{ value: PointAccountSearchField; label: string; }> = [ { value: 'loginId', label: '아이디' }, { value: 'studentName', label: '이름' }, ]; /** 필터 셀렉트의 "전체" 값 — URL에서는 생략되고 화면에서는 이 값이 선택된다. */ export const FILTER_ALL = 'all'; export const GRADE_OPTIONS = [1, 2, 3] as const; export const CLASS_OPTIONS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] as const; export const NUMBER_OPTIONS = Array.from({ length: 30 }, (_, index) => index + 1); /** 상세내역 팝업 안의 페이지 크기(기획 PTS10300001). */ export const POINT_HISTORY_PAGE_SIZE = 5; export const POINT_ACCOUNT_PAGE_SIZE_OPTIONS = [10, 30, 50] as const; export type PointAccountPageSize = (typeof POINT_ACCOUNT_PAGE_SIZE_OPTIONS)[number]; export const DEFAULT_POINT_ACCOUNT_SEARCH_FIELD: PointAccountSearchField = 'loginId'; export const DEFAULT_POINT_ACCOUNT_PAGE_SIZE: PointAccountPageSize = 10; const DEFAULT_PAGE = 1; const MAX_KEYWORD_LENGTH = 100; const MAX_SCHOOL_NAME_LENGTH = 100; export type PointAccountQuery = { schoolName: string; grade: number | null; classNo: number | null; studentNo: number | null; searchField: PointAccountSearchField; keyword: string; page: number; pageSize: PointAccountPageSize; }; type RawSearchParams = Record; function readParam(params: RawSearchParams, key: string): string | undefined { const value = params[key]; return Array.isArray(value) ? value[0] : value; } function readNumberIn( params: RawSearchParams, key: string, allowed: readonly number[] ): number | null { const value = Number(readParam(params, key)); return Number.isInteger(value) && allowed.includes(value) ? value : null; } function isPointAccountSearchField( value: string | undefined ): value is PointAccountSearchField { return ( value !== undefined && POINT_ACCOUNT_SEARCH_FIELD_OPTIONS.some((option) => option.value === value) ); } function isPointAccountPageSize(value: number): value is PointAccountPageSize { return (POINT_ACCOUNT_PAGE_SIZE_OPTIONS as readonly number[]).includes(value); } export function parsePointAccountQuery( searchParams: RawSearchParams ): PointAccountQuery { const searchFieldRaw = readParam(searchParams, 'searchField'); const keywordRaw = readParam(searchParams, 'keyword'); const schoolNameRaw = readParam(searchParams, 'schoolName'); const pageRaw = Number(readParam(searchParams, 'page')); const pageSizeRaw = Number(readParam(searchParams, 'pageSize')); return { schoolName: (schoolNameRaw ?? '').trim().slice(0, MAX_SCHOOL_NAME_LENGTH), grade: readNumberIn(searchParams, 'grade', GRADE_OPTIONS), classNo: readNumberIn(searchParams, 'classNo', CLASS_OPTIONS), studentNo: readNumberIn(searchParams, 'studentNo', NUMBER_OPTIONS), searchField: isPointAccountSearchField(searchFieldRaw) ? searchFieldRaw : DEFAULT_POINT_ACCOUNT_SEARCH_FIELD, keyword: (keywordRaw ?? '').trim().slice(0, MAX_KEYWORD_LENGTH), page: Number.isInteger(pageRaw) && pageRaw > 0 ? pageRaw : DEFAULT_PAGE, pageSize: isPointAccountPageSize(pageSizeRaw) ? pageSizeRaw : DEFAULT_POINT_ACCOUNT_PAGE_SIZE, }; } export function buildPointAccountHref( query: PointAccountQuery, overrides: Partial = {} ): string { const merged = { ...query, ...overrides }; const params = new URLSearchParams(); if (merged.schoolName) { params.set('schoolName', merged.schoolName); } if (merged.grade !== null) { params.set('grade', String(merged.grade)); } if (merged.classNo !== null) { params.set('classNo', String(merged.classNo)); } if (merged.studentNo !== null) { params.set('studentNo', String(merged.studentNo)); } if (merged.searchField !== DEFAULT_POINT_ACCOUNT_SEARCH_FIELD) { params.set('searchField', merged.searchField); } if (merged.keyword) { params.set('keyword', merged.keyword); } if (merged.pageSize !== DEFAULT_POINT_ACCOUNT_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 ? `${POINT_ACCOUNTS_PATH}?${queryString}` : POINT_ACCOUNTS_PATH; }