import { MERCHANT_CATEGORY_OPTIONS } from '@/lib/domain/merchant'; export const MERCHANTS_PATH = '/system/merchants'; /** 엑셀 다운로드 — 파일 응답이라 라우트 핸들러가 받는다(기획 MCH_001 ②). */ export const MERCHANTS_EXCEL_PATH = `${MERCHANTS_PATH}/excel`; /** 필터 셀렉트의 "전체" 값 — URL에서는 생략된다. */ export const FILTER_ALL = ''; export type MerchantSearchField = 'name' | 'businessNumber' | 'address'; export const MERCHANT_SEARCH_FIELD_OPTIONS: ReadonlyArray<{ value: MerchantSearchField; label: string; }> = [ { value: 'name', label: '가맹점명' }, { value: 'businessNumber', label: '사업자번호' }, { value: 'address', label: '주소' }, ]; export const MERCHANT_VISIBILITY_OPTIONS = [ { value: 'visible', label: '노출' }, { value: 'hidden', label: '미노출' }, ] as const; export const MERCHANT_PAGE_SIZE_OPTIONS = [10, 30, 50] as const; export type MerchantPageSize = (typeof MERCHANT_PAGE_SIZE_OPTIONS)[number]; export const DEFAULT_MERCHANT_SEARCH_FIELD: MerchantSearchField = 'name'; export const DEFAULT_MERCHANT_PAGE_SIZE: MerchantPageSize = 10; const DEFAULT_PAGE = 1; const MAX_KEYWORD_LENGTH = 100; export type MerchantQuery = { category: string; visibility: string; searchField: MerchantSearchField; keyword: string; page: number; pageSize: MerchantPageSize; }; type RawSearchParams = Record; function readParam(params: RawSearchParams, key: string): string { const value = params[key]; return (Array.isArray(value) ? value[0] : value) ?? ''; } function readOneOf( params: RawSearchParams, key: string, allowed: readonly { value: string }[] ): string { const raw = readParam(params, key); return allowed.some((option) => option.value === raw) ? raw : FILTER_ALL; } function isSearchField(value: string): value is MerchantSearchField { return MERCHANT_SEARCH_FIELD_OPTIONS.some((option) => option.value === value); } function isPageSize(value: number): value is MerchantPageSize { return (MERCHANT_PAGE_SIZE_OPTIONS as readonly number[]).includes(value); } export function parseMerchantQuery(searchParams: RawSearchParams): MerchantQuery { const searchField = readParam(searchParams, 'searchField'); const page = Number(readParam(searchParams, 'page')); const pageSize = Number(readParam(searchParams, 'pageSize')); return { category: readOneOf(searchParams, 'category', MERCHANT_CATEGORY_OPTIONS), visibility: readOneOf( searchParams, 'visibility', MERCHANT_VISIBILITY_OPTIONS ), searchField: isSearchField(searchField) ? searchField : DEFAULT_MERCHANT_SEARCH_FIELD, keyword: readParam(searchParams, 'keyword').trim().slice(0, MAX_KEYWORD_LENGTH), page: Number.isInteger(page) && page > 0 ? page : DEFAULT_PAGE, pageSize: isPageSize(pageSize) ? pageSize : DEFAULT_MERCHANT_PAGE_SIZE, }; } export function buildMerchantHref( query: MerchantQuery, overrides: Partial = {} ): string { const merged = { ...query, ...overrides }; const params = new URLSearchParams(); if (merged.category) { params.set('category', merged.category); } if (merged.visibility) { params.set('visibility', merged.visibility); } if (merged.searchField !== DEFAULT_MERCHANT_SEARCH_FIELD) { params.set('searchField', merged.searchField); } if (merged.keyword) { params.set('keyword', merged.keyword); } if (merged.pageSize !== DEFAULT_MERCHANT_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 ? `${MERCHANTS_PATH}?${queryString}` : MERCHANTS_PATH; }