/** * 단가 목록 클라이언트 컴포넌트 * * IntegratedListTemplateV2 공통 템플릿 활용 */ 'use client'; import { useState, useMemo } from 'react'; import { useRouter } from 'next/navigation'; import { DollarSign, Package, AlertCircle, CheckCircle2, Plus, Edit, History, RefreshCw, } 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 { IntegratedListTemplateV2, type TabOption, type TableColumn, type StatCard, } from '@/components/templates/IntegratedListTemplateV2'; import { ListMobileCard, InfoField } from '@/components/organisms/ListMobileCard'; import type { PricingListItem, ItemType } from './types'; import { ITEM_TYPE_LABELS, ITEM_TYPE_COLORS } from './types'; interface PricingListClientProps { initialData: PricingListItem[]; } export function PricingListClient({ initialData, }: PricingListClientProps) { const router = useRouter(); const [data] = useState(initialData); const [searchTerm, setSearchTerm] = useState(''); const [activeTab, setActiveTab] = useState('all'); const [selectedItems, setSelectedItems] = useState>(new Set()); const [currentPage, setCurrentPage] = useState(1); const pageSize = 20; // 필터링된 데이터 const filteredData = useMemo(() => { let result = [...data]; // 탭 필터 if (activeTab !== 'all') { result = result.filter(item => item.itemType === activeTab); } // 검색 필터 if (searchTerm) { const search = searchTerm.toLowerCase(); result = result.filter(item => item.itemCode.toLowerCase().includes(search) || item.itemName.toLowerCase().includes(search) || (item.specification?.toLowerCase().includes(search) ?? false) ); } return result; }, [data, activeTab, searchTerm]); // 페이지네이션된 데이터 const paginatedData = useMemo(() => { const start = (currentPage - 1) * pageSize; return filteredData.slice(start, start + pageSize); }, [filteredData, currentPage, pageSize]); // 통계 계산 const totalStats = useMemo(() => { const totalAll = data.length; const totalFG = data.filter(d => d.itemType === 'FG').length; const totalPT = data.filter(d => d.itemType === 'PT').length; const totalSM = data.filter(d => d.itemType === 'SM').length; const totalRM = data.filter(d => d.itemType === 'RM').length; const totalCS = data.filter(d => d.itemType === 'CS').length; const registered = data.filter(d => d.status !== 'not_registered').length; const notRegistered = totalAll - registered; const finalized = data.filter(d => d.isFinal).length; return { totalAll, totalFG, totalPT, totalSM, totalRM, totalCS, registered, notRegistered, finalized }; }, [data]); // 금액 포맷팅 const formatPrice = (price?: number) => { if (price === undefined || price === null) return '-'; return `${price.toLocaleString()}원`; }; // 품목 유형 Badge 렌더링 const renderItemTypeBadge = (type: string) => { const colors = ITEM_TYPE_COLORS[type as ItemType]; const label = ITEM_TYPE_LABELS[type as ItemType] || type; if (!colors) { return {label}; } return ( {label} ); }; // 상태 Badge 렌더링 const renderStatusBadge = (item: PricingListItem) => { if (item.status === 'not_registered') { return 미등록; } if (item.isFinal) { return 확정; } if (item.status === 'active') { return 활성; } if (item.status === 'inactive') { return 비활성; } return 초안; }; // 마진율 Badge 렌더링 const renderMarginBadge = (marginRate?: number) => { if (marginRate === undefined || marginRate === null || marginRate === 0) { return -; } const colorClass = marginRate >= 30 ? 'bg-green-50 text-green-700 border-green-200' : marginRate >= 20 ? 'bg-blue-50 text-blue-700 border-blue-200' : marginRate >= 10 ? 'bg-orange-50 text-orange-700 border-orange-200' : 'bg-red-50 text-red-700 border-red-200'; return ( {marginRate.toFixed(1)}% ); }; // 네비게이션 핸들러 const handleRegister = (item: PricingListItem) => { // item_type_code는 품목 정보에서 자동으로 가져오므로 URL에 포함하지 않음 router.push(`/sales/pricing-management/create?itemId=${item.itemId}&itemCode=${item.itemCode}`); }; const handleEdit = (item: PricingListItem) => { router.push(`/sales/pricing-management/${item.id}/edit`); }; const handleHistory = (item: PricingListItem) => { // TODO: 이력 다이얼로그 열기 console.log('이력 조회:', item.id); }; // 체크박스 전체 선택/해제 const toggleSelectAll = () => { if (selectedItems.size === paginatedData.length && paginatedData.length > 0) { setSelectedItems(new Set()); } else { const allIds = new Set(paginatedData.map((item) => item.id)); setSelectedItems(allIds); } }; // 개별 체크박스 선택/해제 const toggleSelection = (itemId: string) => { const newSelected = new Set(selectedItems); if (newSelected.has(itemId)) { newSelected.delete(itemId); } else { newSelected.add(itemId); } setSelectedItems(newSelected); }; // 탭 옵션 const tabs: TabOption[] = [ { value: 'all', label: '전체', count: totalStats.totalAll, color: 'gray' }, { value: 'FG', label: '제품', count: totalStats.totalFG, color: 'purple' }, { value: 'PT', label: '부품', count: totalStats.totalPT, color: 'orange' }, { value: 'SM', label: '부자재', count: totalStats.totalSM, color: 'green' }, { value: 'RM', label: '원자재', count: totalStats.totalRM, color: 'blue' }, { value: 'CS', label: '소모품', count: totalStats.totalCS, color: 'gray' }, ]; // 통계 카드 const stats: StatCard[] = [ { label: '전체 품목', value: totalStats.totalAll, icon: Package, iconColor: 'text-blue-600' }, { label: '단가 등록', value: totalStats.registered, icon: DollarSign, iconColor: 'text-green-600' }, { label: '미등록', value: totalStats.notRegistered, icon: AlertCircle, iconColor: 'text-orange-600' }, { label: '확정', value: totalStats.finalized, icon: CheckCircle2, iconColor: 'text-purple-600' }, ]; // 테이블 컬럼 정의 const tableColumns: TableColumn[] = [ { key: 'rowNumber', label: '번호', className: 'w-[60px] text-center' }, { key: 'itemType', label: '품목유형', className: 'min-w-[100px]' }, { key: 'itemCode', label: '품목코드', className: 'min-w-[120px]' }, { key: 'itemName', label: '품목명', className: 'min-w-[150px]' }, { key: 'specification', label: '규격', className: 'min-w-[100px]', hideOnMobile: true }, { key: 'unit', label: '단위', className: 'min-w-[60px]', hideOnMobile: true }, { key: 'purchasePrice', label: '매입단가', className: 'min-w-[100px] text-right', hideOnTablet: true }, { key: 'processingCost', label: '가공비', className: 'min-w-[80px] text-right', hideOnTablet: true }, { key: 'salesPrice', label: '판매단가', className: 'min-w-[100px] text-right' }, { key: 'marginRate', label: '마진율', className: 'min-w-[80px] text-right', hideOnMobile: true }, { key: 'effectiveDate', label: '적용일', className: 'min-w-[100px]', hideOnMobile: true }, { key: 'status', label: '상태', className: 'min-w-[80px]' }, { key: 'actions', label: '작업', className: 'w-[120px] text-right' }, ]; // 테이블 행 렌더링 const renderTableRow = (item: PricingListItem, index: number, globalIndex: number) => { const isSelected = selectedItems.has(item.id); return ( toggleSelection(item.id)} /> {globalIndex} {renderItemTypeBadge(item.itemType)} {item.itemCode} {item.itemName} {item.specification || '-'} {item.unit || '-'} {formatPrice(item.purchasePrice)} {formatPrice(item.processingCost)} {formatPrice(item.salesPrice)} {renderMarginBadge(item.marginRate)} {item.effectiveDate ? new Date(item.effectiveDate).toLocaleDateString('ko-KR') : '-'} {renderStatusBadge(item)}
{item.status === 'not_registered' ? ( ) : ( <> {item.currentRevision > 0 && ( )} )}
); }; // 모바일 카드 렌더링 const renderMobileCard = ( item: PricingListItem, index: number, globalIndex: number, isSelected: boolean, onToggle: () => void ) => { return ( {item.itemCode} {renderItemTypeBadge(item.itemType)} } statusBadge={renderStatusBadge(item)} isSelected={isSelected} onToggleSelection={onToggle} onCardClick={() => item.status !== 'not_registered' ? handleEdit(item) : handleRegister(item)} infoGrid={
{item.specification && ( )} {item.unit && ( )}
} actions={ isSelected ? (
{item.status === 'not_registered' ? ( ) : ( <> {item.currentRevision > 0 && ( )} )}
) : undefined } /> ); }; // 헤더 액션 const headerActions = ( ); // 페이지네이션 const totalPages = Math.ceil(filteredData.length / pageSize); return ( title="단가 목록" description="품목별 매입단가, 판매단가 및 마진을 관리합니다" icon={DollarSign} headerActions={headerActions} stats={stats} searchValue={searchTerm} onSearchChange={setSearchTerm} searchPlaceholder="품목코드, 품목명, 규격 검색..." tabs={tabs} activeTab={activeTab} onTabChange={setActiveTab} tableColumns={tableColumns} data={paginatedData} totalCount={filteredData.length} allData={filteredData} selectedItems={selectedItems} onToggleSelection={toggleSelection} onToggleSelectAll={toggleSelectAll} getItemId={(item) => item.id} renderTableRow={renderTableRow} renderMobileCard={renderMobileCard} pagination={{ currentPage, totalPages, totalItems: filteredData.length, itemsPerPage: pageSize, onPageChange: setCurrentPage, }} /> ); } export default PricingListClient;