File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
/**
* 가맹몰 상세 — 기존 가맹점(`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;
/** 백엔드 `frcsTypeCdNm` — 카테고리 이름. 코드표를 다시 뒤지지 않아도 되게 백엔드가 붙여 준다. */
categoryLabel: string | null;
/** 백엔드 상세 `useYn`. 상세가 없는 가맹점은 null이다. */
isVisible: boolean | null;
/** 백엔드 `productCnt` — 등록된 메뉴 수. 상세가 없는 가맹점은 null이다. */
productCount: number | 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;
};