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 { useState } from 'react';
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 {
formatAddressSummary,
formatCategoryLabel,
formatOptionalValue,
formatVisibilityLabel,
type Merchant,
} from '@/lib/domain/merchant';
import {
MERCHANTS_EXCEL_PATH,
MERCHANTS_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 { MerchantFormModal } from './merchant-form-modal';
import { MerchantRowActions } from './merchant-row-actions';
import styles from './merchant-filter.module.scss';
interface MerchantListProps {
items: Merchant[];
query: MerchantQuery;
currentPage: number;
totalPages: number;
totalCount: number;
}
/** 기획 MCH_001 ④ — 정렬은 최근등록순 하나뿐이라 선택지가 아니라 표시다. */
const SORT_OPTIONS = [{ value: 'recent', label: '최근등록순' }];
export function MerchantList({
items,
query,
currentPage,
totalPages,
totalCount,
}: MerchantListProps) {
const router = useRouter();
const [isCreateOpen, setIsCreateOpen] = useState(false);
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(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) => (
<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} />,
},
];
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>
<FoxButton
type="primary"
size="md"
leadingIcon={
<i className="fox-ico fox-ico-Plus" aria-hidden="true" />
}
label="등록"
onAction={() => setIsCreateOpen(true)}
/>
<FoxButton
type="default"
size="md"
label="초기화"
onAction={() => router.replace(MERCHANTS_PATH)}
/>
</>
}
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,
});
}
}}
/>
{isCreateOpen && (
<MerchantFormModal onClose={() => setIsCreateOpen(false)} />
)}
</>
);
}