/** * 가맹몰 상세 — 기존 가맹점(`TB_COM_VOUCHER`)에 1:1로 붙는 앱 노출용 정보(`TB_COM_VOUCHER_DTL`). * * 기획(BO-MAL-001~003 「지역상생몰 > 업체관리」)은 업체를 새로 만드는 화면이지만, 백엔드 * `POST /api/v1/mngr/voc/detail`은 이미 있는 가맹점(`frcsNo`)에만 상세를 붙인다. 그래서 * 「관리자관리 > 가맹몰 상세 관리」로 옮기며 업체명·연락처·주소는 가맹점 원본에서 읽기 전용으로 * 가져오고, 상세 항목만 등록·수정한다(사용자 확정). */ /** * 가맹점 한 건 + 상세 요약. 백엔드 목록(feature/voc)은 상세가 있는 가맹점만 주므로 거기서 온 행은 * `hasDetail`이 참이고, 등록 화면의 선택지(가맹점 API에서 온 것)만 거짓이다. */ export type MerchantDetailSummary = { /** 백엔드 `frcsNo` — 가맹점 코드이자 상세의 키. */ id: string; name: string; phoneNumber: string | null; address: string | null; addressDetail: string | null; postalCode: string | null; /** 백엔드 `thumbAtchFileId` — 대표 이미지. */ thumbnailFileId: string | null; /** Repository가 만든 이미지 URL. 화면은 파일 id를 URL로 바꾸는 법을 모른다. */ thumbnailUrl: string | null; /** 백엔드 `frcsTypeCd` — 카테고리 코드. */ categoryCode: string | null; /** 백엔드 상세 `useYn`. 상세가 없는 가맹점은 null이다. */ isVisible: boolean | null; /** 상세 등록일(`frstRegDtStr`, YYYY-MM-DD). */ registeredAt: string | null; /** 상세 행이 있는지 — 목록 행은 참, 등록 선택지는 거짓. */ hasDetail: boolean; }; /** 상세 화면이 받는 한 건. */ export type MerchantDetail = MerchantDetailSummary & { /** 백엔드 `frcsCn` — 한줄 소개. */ description: string | null; /** 백엔드 `startHour` / `endHour` — "HH:mm". */ openTime: string | null; closeTime: string | null; /** 백엔드 `offDayCn`. */ closedDays: string | null; /** 백엔드 `atchFileId` — 추가 이미지 묶음(한 id 아래 fileSn 1..n). */ imageFileId: string | null; /** 묶음 안의 이미지 URL, fileSn 순. 백엔드에 파일 수를 묻는 API가 없어 Repository가 더듬는다. */ imageUrls: string[]; sortOrder: number | null; /** 백엔드 `frstRgtrNm` / `lastMdfrNm` — 수정 화면의 「등록 정보」 구역. */ registeredBy: string | null; modifiedAt: string | null; modifiedBy: string | null; }; export const EMPTY_FIELD_PLACEHOLDER = '-'; export function formatOptionalValue( value: string | number | null | undefined ): string { return value === null || value === undefined || value === '' ? EMPTY_FIELD_PLACEHOLDER : String(value); } /** 기획 BO-MAL-001 ② — 노출 상태는 노출중·비활성 2종. */ export const MERCHANT_DETAIL_VISIBILITY_OPTIONS = [ { value: 'Y', label: '노출중' }, { value: 'N', label: '비활성' }, ] as const; export function formatMerchantDetailStatusLabel(row: MerchantDetailSummary): string { if (!row.hasDetail) { return '미등록'; } if (row.isVisible === null) { return EMPTY_FIELD_PLACEHOLDER; } return row.isVisible ? '노출중' : '비활성'; } /** 기획 BO-MAL-002 ① — 바우처 사용 가능 여부. 백엔드에 담을 자리가 아직 없다(보고함). */ export const MERCHANT_VOUCHER_USE_OPTIONS = [ { value: 'Y', label: '사용 가능' }, { value: 'N', label: '사용 불가' }, ] as const; /** 기획 BO-MAL-002 ⑤ — 대표 1장 + 추가 최대 11장(앱 슬라이더 최대 12장). */ export const MAX_EXTRA_IMAGES = 11; /** 기획 BO-MAL-002 ⑤ — 장당 5MB 이하. */ export const MAX_IMAGE_BYTES = 5 * 1024 * 1024; /** 상세 이미지 한 장의 화면 표현. 저장된 것은 `fileSn`으로, 새로 고른 것은 파일로 온다. */ export type MerchantImageSlot = { key: string; previewUrl: string | null; /** 저장된 이미지면 묶음 안의 번호(1..n). 새 파일은 null. */ fileSn: number | null; };