/** * 단가배포 목록 클라이언트 컴포넌트 * * UniversalListPage 공통 템플릿 활용 * - 탭 없음, 통계 카드 없음 * - 상태 필터: filterConfig (SELECT 드롭다운) */ 'use client'; import { useState, useEffect, useCallback, useMemo } from 'react'; import { useRouter } from 'next/navigation'; import { FileText, FilePlus } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Checkbox } from '@/components/ui/checkbox'; import { TableRow, TableCell } from '@/components/ui/table'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { UniversalListPage, type UniversalListConfig, type TableColumn, type FilterFieldConfig, type SelectionHandlers, type RowClickHandlers, } from '@/components/templates/UniversalListPage'; import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard'; import { toast } from 'sonner'; import type { PriceDistributionListItem, DistributionStatus } from './types'; import { DISTRIBUTION_STATUS_LABELS, DISTRIBUTION_STATUS_STYLES, } from './types'; import { getPriceDistributionList, createPriceDistribution, deletePriceDistribution, } from './actions'; export function PriceDistributionList() { const router = useRouter(); const [data, setData] = useState([]); const [isLoading, setIsLoading] = useState(true); const [showRegisterDialog, setShowRegisterDialog] = useState(false); const [isRegistering, setIsRegistering] = useState(false); const pageSize = 20; // 날짜 범위 상태 (최근 30일) const today = new Date(); const thirtyDaysAgo = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000); const [startDate, setStartDate] = useState(thirtyDaysAgo.toISOString().split('T')[0]); const [endDate, setEndDate] = useState(today.toISOString().split('T')[0]); // 데이터 로드 const loadData = useCallback(async () => { setIsLoading(true); try { const listResult = await getPriceDistributionList(); if (listResult.success && listResult.data) { setData(listResult.data.items); } } finally { setIsLoading(false); } }, []); useEffect(() => { loadData(); }, [loadData]); // 검색 필터 const searchFilter = (item: PriceDistributionListItem, search: string) => { const s = search.toLowerCase(); return ( item.distributionNo.toLowerCase().includes(s) || item.distributionName.toLowerCase().includes(s) || item.author.toLowerCase().includes(s) ); }; // 상태 Badge 렌더링 const renderStatusBadge = (status: DistributionStatus) => { const style = DISTRIBUTION_STATUS_STYLES[status]; const label = DISTRIBUTION_STATUS_LABELS[status]; return ( {label} ); }; // 등록 핸들러 const handleRegister = async () => { setIsRegistering(true); try { const result = await createPriceDistribution(); if (result.success && result.data) { toast.success('단가배포가 등록되었습니다.'); setShowRegisterDialog(false); router.push(`/master-data/price-distribution/${result.data.id}`); } else { toast.error(result.error || '등록에 실패했습니다.'); } } catch { toast.error('등록 중 오류가 발생했습니다.'); } finally { setIsRegistering(false); } }; // 행 클릭 → 상세 const handleRowClick = (item: PriceDistributionListItem) => { router.push(`/master-data/price-distribution/${item.id}`); }; // 상태 필터 설정 const filterConfig: FilterFieldConfig[] = [ { key: 'status', label: '상태', type: 'single', options: [ { value: 'initial', label: '최초작성' }, { value: 'revision', label: '보이수정' }, { value: 'finalized', label: '최종확정' }, ], }, ]; // 커스텀 필터 함수 const customFilterFn = (items: PriceDistributionListItem[], filterValues: Record) => { const status = filterValues.status as string; if (!status || status === '') return items; return items.filter((item) => item.status === status); }; // 테이블 컬럼 const tableColumns: TableColumn[] = useMemo(() => [ { key: 'rowNumber', label: '번호', className: 'w-[60px] text-center' }, { key: 'distributionNo', label: '단가배포번호', className: 'min-w-[120px]' }, { key: 'distributionName', label: '단가배포명', className: 'min-w-[150px]' }, { key: 'status', label: '상태', className: 'min-w-[100px]' }, { key: 'author', label: '작성자', className: 'min-w-[100px]' }, { key: 'createdAt', label: '등록일', className: 'min-w-[120px]' }, ], []); // 테이블 행 렌더링 const renderTableRow = ( item: PriceDistributionListItem, index: number, globalIndex: number, handlers: SelectionHandlers & RowClickHandlers ) => { const { isSelected, onToggle } = handlers; return ( handleRowClick(item)} > e.stopPropagation()}> {globalIndex} {item.distributionNo} {item.distributionName} {renderStatusBadge(item.status)} {item.author} {new Date(item.createdAt).toLocaleDateString('ko-KR')} ); }; // 모바일 카드 렌더링 const renderMobileCard = ( item: PriceDistributionListItem, index: number, globalIndex: number, handlers: SelectionHandlers & RowClickHandlers ) => { const { isSelected, onToggle } = handlers; return ( {item.distributionNo} } statusBadge={renderStatusBadge(item.status)} isSelected={isSelected} onToggleSelection={onToggle} onCardClick={() => handleRowClick(item)} infoGrid={
} /> ); }; // 헤더 액션 const headerActions = () => ( ); // UniversalListPage 설정 const listConfig: UniversalListConfig = { title: '단가배포 목록', description: '단가표 기준 거래처별 단가 배포를 관리합니다', icon: FileText, basePath: '/master-data/price-distribution', idField: 'id', actions: { getList: async () => ({ success: true, data: data, totalCount: data.length, }), deleteBulk: async (ids: string[]) => { const result = await deletePriceDistribution(ids); return { success: result.success, error: result.error }; }, }, columns: tableColumns, headerActions, filterConfig, // 날짜 범위 필터 + 프리셋 버튼 dateRangeSelector: { enabled: true, showPresets: true, startDate, endDate, onStartDateChange: setStartDate, onEndDateChange: setEndDate, dateField: 'createdAt', }, searchPlaceholder: '단가배포번호, 단가배포명, 작성자 검색...', itemsPerPage: pageSize, clientSideFiltering: true, searchFilter, customFilterFn, renderTableRow, renderMobileCard, }; return ( <> config={listConfig} initialData={data} initialTotalCount={data.length} /> {/* 단가배포 등록 확인 다이얼로그 */} 알림 새로운 단가배포 버전을 등록하시겠습니까? 현재 단가표 기준으로 자동 생성됩니다. 취소 {isRegistering ? '등록 중...' : '등록'} ); } export default PriceDistributionList;