File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
'use client';
import { useRouter } from 'next/navigation';
import { FoxButton } from '@fox/core/components/fox-button';
import {
FoxListContainer,
type FoxListColumn,
} from '@fox/core/components/fox-list-container';
import { FoxSelect } from '@fox/core/components/fox-select';
import { FoxSelectText } from '@fox/core/components/fox-select-text';
import { FoxStatusIndicator } from '@fox/core/components/fox-status-indicator';
import type { CommonCode } from '@/lib/domain/common-code';
import {
formatAddressSummary,
formatCategoryLabel,
formatOptionalValue,
formatVisibilityLabel,
type Merchant,
} from '@/lib/domain/merchant';
import {
MERCHANTS_EXCEL_PATH,
MERCHANT_PAGE_SIZE_OPTIONS,
MERCHANT_SEARCH_FIELD_OPTIONS,
MERCHANT_VISIBILITY_OPTIONS,
buildMerchantHref,
type MerchantPageSize,
type MerchantQuery,
type MerchantSearchField,
} from '@/lib/domain/merchant-query';
import { MerchantRowActions } from './merchant-row-actions';
import styles from './merchant-filter.module.scss';
interface MerchantListProps {
items: Merchant[];
/** 업종 코드표(`FRCS_TPBIZ_CD` 다음 `FRCS_CD`) — 업종 열과 상세 모달이 이름을 찾는다. */
businessCodes: CommonCode[];
query: MerchantQuery;
currentPage: number;
totalPages: number;
totalCount: number;
}
/** 기획 MCH_001 ④ — 정렬은 최근등록순 하나뿐이라 선택지가 아니라 표시다. */
const SORT_OPTIONS = [{ value: 'recent', label: '최근등록순' }];
export function MerchantList({
items,
businessCodes,
query,
currentPage,
totalPages,
totalCount,
}: MerchantListProps) {
const router = useRouter();
function go(patch: Partial<MerchantQuery>) {
router.replace(buildMerchantHref(query, { ...patch, page: 1 }));
}
const columns: FoxListColumn<Merchant>[] = [
{
key: 'no',
header: '번호',
width: 80,
render: (_row, index) => (currentPage - 1) * query.pageSize + index + 1,
},
{ key: 'code', header: '가맹점코드', width: 140 },
{
key: 'brandName',
header: '브랜드명',
width: 120,
render: (row) => formatOptionalValue(row.brandName),
},
{ key: 'name', header: '가맹점명', width: 240, emphasis: true },
{
key: 'categoryCode',
header: '업종',
width: 100,
render: (row) => formatCategoryLabel(businessCodes, row.categoryCode),
},
{
key: 'phoneNumber',
header: '전화번호',
width: 160,
render: (row) => formatOptionalValue(row.phoneNumber),
},
{
key: 'address',
header: '주소',
width: 280,
render: formatAddressSummary,
},
{
key: 'isVisible',
header: '사용여부',
width: 120,
render: (row) =>
row.isVisible === null ? (
formatVisibilityLabel(row.isVisible)
) : (
<FoxStatusIndicator
type={row.isVisible ? 'success' : 'danger'}
label={formatVisibilityLabel(row.isVisible)}
/>
),
},
{
key: 'createdAt',
header: '등록일',
width: 140,
render: (row) => formatOptionalValue(row.createdAt),
},
{
key: 'actions',
header: '관리',
width: 120,
render: (row) => <MerchantRowActions merchant={row} businessCodes={businessCodes} />,
},
];
return (
<>
<FoxListContainer<Merchant>
title="가맹몰 관리"
breadcrumb={[
{ label: '홈', href: '/' },
{ label: '관리자관리' },
{ label: '가맹몰 관리' },
]}
caption="가맹몰 목록"
columns={columns}
rows={items}
rowKey={(row) => row.id}
totalCount={totalCount}
emptyMessage="검색 결과가 없습니다."
sorts={
<>
<FoxSelectText
size="sm"
ariaLabel="정렬"
options={SORT_OPTIONS}
value="recent"
/>
<FoxSelectText
size="sm"
ariaLabel="페이지 크기"
options={MERCHANT_PAGE_SIZE_OPTIONS.map((size) => ({
value: String(size),
label: `${size}개씩`,
}))}
value={String(query.pageSize)}
onValueChange={(value) =>
go({ pageSize: Number(value) as MerchantPageSize })
}
/>
</>
}
filter={
<div className={styles.filter}>
<div className={styles.select}>
<FoxSelect
size="md"
ariaLabel="사용여부"
options={[
{ value: '', label: '사용여부: 전체' },
...MERCHANT_VISIBILITY_OPTIONS.map((option) => ({
value: option.value,
label: option.label,
})),
]}
value={query.visibility}
onValueChange={(value) => go({ visibility: value })}
/>
</div>
</div>
}
searchFields={MERCHANT_SEARCH_FIELD_OPTIONS.map((option) => ({
value: option.value,
label: option.label,
}))}
searchField={query.searchField}
keyword={query.keyword}
searchPlaceholder="검색어를 입력하세요."
toolbarActions={
<>
{/* TODO(기획): 엑셀등록 양식·검증 규칙이 정해지지 않았다. 규칙이 나오면 금칙어
화면처럼 업로드 모달을 붙인다. */}
<FoxButton
type="secondary"
size="md"
leadingIcon={
<i className="fox-ico fox-ico-UploadSimple" aria-hidden="true" />
}
label="엑셀등록"
disabled
/>
{/* 파일 응답이라 라우터가 아니라 네이티브 GET 폼으로 보낸다. */}
<form action={MERCHANTS_EXCEL_PATH} method="get">
<FoxButton
type="default"
size="md"
htmlType="submit"
label="엑셀다운로드"
/>
</form>
{/* TODO(백엔드): 등록이 자체 저장에서 BizPlay 외부 가맹점 등록 API 위임으로 바뀌었다
(`POST /api/v1/mngr/voc`가 @RequestBody JSON이 되고 bizPlayVocApiService.insertVoc를
부른다). 예금주명·입금은행코드·입금계좌번호·업태코드가 새로 필요한데 이 폼은 받지
않는다. 항목이 정해지면 MerchantFormModal과 함께 다시 연다. */}
<FoxButton
type="primary"
size="md"
leadingIcon={
<i className="fox-ico fox-ico-Plus" aria-hidden="true" />
}
label="등록"
disabled
/>
</>
}
page={currentPage}
totalPages={totalPages}
buildHref={(patch) =>
buildMerchantHref(query, { page: Number(patch.page ?? 1) })
}
onQueryChange={(patch) => {
if (patch.keyword !== undefined || patch.searchField !== undefined) {
go({
keyword: String(patch.keyword ?? ''),
searchField: (patch.searchField ??
query.searchField) as MerchantSearchField,
});
}
}}
/>
</>
);
}