import type { CommonCode } from '@/lib/domain/common-code'; /** * 가맹몰 상세 등록·수정 입력 규칙 — 순수 검증만 담는다. * * 기획 BO-MAL-002 ①의 필수 항목 중 업체명·연락처·우편번호는 가맹점 원본에서 오는 읽기 전용 * 값이라 여기서 검사하지 않는다. 남는 필수는 카테고리·노출 상태·바우처 사용·대표이미지다. */ export const DESCRIPTION_MAX_LENGTH = 200; export const CLOSED_DAYS_MAX_LENGTH = 100; export const SORT_ORDER_MAX = 9999; const TIME_PATTERN = /^([01]\d|2[0-3]):[0-5]\d$/; export type MerchantDetailValues = { frcsNo: string; categoryCode: string; description: string; openTime: string; closeTime: string; closedDays: string; isVisible: boolean; isVoucherUsable: boolean; /** 빈 값이면 null — 기획 ⑦ 미입력 시 등록일 내림차순. */ sortOrder: number | null; thumbnailFileId: string; imageFileId: string; }; export type MerchantDetailFormErrors = Partial< Record< | 'frcsNo' | 'categoryCode' | 'description' | 'openTime' | 'closeTime' | 'closedDays' | 'sortOrder' | 'thumbnail' | 'images', string > >; export type MerchantDetailFormState = | { status: 'idle' } | { status: 'error'; message?: string; errors?: MerchantDetailFormErrors } | { status: 'success' }; export const INITIAL_MERCHANT_DETAIL_FORM_STATE: MerchantDetailFormState = { status: 'idle', }; type ValidationResult = | { ok: true; values: MerchantDetailValues } | { ok: false; errors: MerchantDetailFormErrors }; export function validateMerchantDetail( values: MerchantDetailValues, categories: readonly CommonCode[] ): ValidationResult { const errors: MerchantDetailFormErrors = {}; if (!categories.some((code) => code.code === values.categoryCode)) { errors.categoryCode = '카테고리를 선택해 주세요.'; } const description = values.description.trim(); if (description.length > DESCRIPTION_MAX_LENGTH) { errors.description = `한줄 소개는 ${DESCRIPTION_MAX_LENGTH}자 이내로 입력해 주세요.`; } const openTime = values.openTime.trim(); const closeTime = values.closeTime.trim(); if (openTime && !TIME_PATTERN.test(openTime)) { errors.openTime = '영업 시작 시간 형식이 올바르지 않습니다.'; } if (closeTime && !TIME_PATTERN.test(closeTime)) { errors.closeTime = '영업 종료 시간 형식이 올바르지 않습니다.'; } const closedDays = values.closedDays.trim(); if (closedDays.length > CLOSED_DAYS_MAX_LENGTH) { errors.closedDays = `휴무일은 ${CLOSED_DAYS_MAX_LENGTH}자 이내로 입력해 주세요.`; } if ( values.sortOrder !== null && (!Number.isInteger(values.sortOrder) || values.sortOrder < 0 || values.sortOrder > SORT_ORDER_MAX) ) { errors.sortOrder = `노출 순서는 0 이상 ${SORT_ORDER_MAX} 이하의 숫자로 입력해 주세요.`; } if (!values.thumbnailFileId) { errors.thumbnail = '대표이미지를 등록해 주세요.'; } if (Object.keys(errors).length > 0) { return { ok: false, errors }; } return { ok: true, values: { ...values, description, openTime, closeTime, closedDays }, }; }