'use client'; /** * 재고현황 목록 - UniversalListPage 마이그레이션 * * 기존 IntegratedListTemplateV2 → UniversalListPage config 기반으로 변환 * - 서버 사이드 페이지네이션 (getStocks API) * - 통계 카드 (getStockStats API) * - 품목유형별 탭 필터 (getStockStatsByType API) * - 테이블 푸터 (요약 정보) */ import { useState, useEffect, useMemo, useCallback } from 'react'; import { useRouter } from 'next/navigation'; import { Package, CheckCircle2, Clock, AlertCircle, Download, Eye, } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { TableCell, TableRow } from '@/components/ui/table'; import { Checkbox } from '@/components/ui/checkbox'; import { UniversalListPage, type UniversalListConfig, type SelectionHandlers, type RowClickHandlers, type TabOption, type StatCard, type ListParams, } from '@/components/templates/UniversalListPage'; import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard'; import { getStocks, getStockStats, getStockStatsByType } from './actions'; import { ITEM_TYPE_LABELS, ITEM_TYPE_STYLES, STOCK_STATUS_LABELS } from './types'; import { isNextRedirectError } from '@/lib/utils/redirect-error'; import type { StockItem, StockStats, ItemType } from './types'; // 페이지당 항목 수 const ITEMS_PER_PAGE = 20; export function StockStatusList() { const router = useRouter(); // ===== 통계 및 품목유형별 통계 (외부 관리) ===== const [stockStats, setStockStats] = useState(null); const [typeStats, setTypeStats] = useState>({}); const [totalItems, setTotalItems] = useState(0); // 초기 통계 로드 useEffect(() => { const loadStats = async () => { try { const [statsResult, typeStatsResult] = await Promise.all([ getStockStats(), getStockStatsByType(), ]); if (statsResult.success && statsResult.data) { setStockStats(statsResult.data); } if (typeStatsResult.success && typeStatsResult.data) { setTypeStats(typeStatsResult.data); } } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[StockStatusList] loadStats error:', error); } }; loadStats(); }, []); // ===== 행 클릭 핸들러 ===== const handleRowClick = useCallback( (item: StockItem) => { router.push(`/ko/material/stock-status/${item.id}`); }, [router] ); // ===== 엑셀 다운로드 ===== const handleExcelDownload = useCallback(() => { console.log('엑셀 다운로드'); // TODO: 엑셀 다운로드 기능 구현 }, []); // ===== 통계 카드 ===== const stats: StatCard[] = useMemo( () => [ { label: '전체 품목', value: `${stockStats?.totalItems || 0}종`, icon: Package, iconColor: 'text-gray-600', }, { label: '정상 재고', value: `${stockStats?.normalCount || 0}종`, icon: CheckCircle2, iconColor: 'text-green-600', }, { label: '재고 부족', value: `${stockStats?.lowCount || 0}종`, icon: Clock, iconColor: 'text-orange-600', }, { label: '재고 없음', value: `${stockStats?.outCount || 0}종`, icon: AlertCircle, iconColor: 'text-red-600', }, ], [stockStats] ); // ===== 탭 옵션 (기본 탭 + 품목유형별 통계) ===== const tabs: TabOption[] = useMemo(() => { // 기본 탭 정의 (Item 모델의 MATERIAL_TYPES: RM, SM, CS) const defaultTabs: { value: string; label: string }[] = [ { value: 'all', label: '전체' }, { value: 'RM', label: '원자재' }, { value: 'SM', label: '부자재' }, { value: 'CS', label: '소모품' }, ]; return defaultTabs.map((tab) => { if (tab.value === 'all') { return { ...tab, count: stockStats?.totalItems || 0 }; } const stat = typeStats[tab.value]; const count = typeof stat?.count === 'number' ? stat.count : 0; return { ...tab, count }; }); }, [typeStats, stockStats?.totalItems]); // ===== 테이블 푸터 ===== const tableFooter = useMemo(() => { const lowStockCount = stockStats?.lowCount || 0; return ( 총 {totalItems}종 / 재고부족 {lowStockCount}종 ); }, [totalItems, stockStats?.lowCount]); // ===== UniversalListPage Config ===== const config: UniversalListConfig = useMemo( () => ({ // 페이지 기본 정보 title: '재고 목록', description: '재고현황 관리', icon: Package, basePath: '/material/stock-status', // ID 추출 idField: 'id', // API 액션 (서버 사이드 페이지네이션) actions: { getList: async (params?: ListParams) => { try { const result = await getStocks({ page: params?.page || 1, perPage: params?.pageSize || ITEMS_PER_PAGE, itemType: params?.tab !== 'all' ? (params?.tab as ItemType) : undefined, search: params?.search || undefined, }); if (result.success) { // 통계 및 품목유형별 통계 다시 로드 const [statsResult, typeStatsResult] = await Promise.all([ getStockStats(), getStockStatsByType(), ]); if (statsResult.success && statsResult.data) { setStockStats(statsResult.data); } if (typeStatsResult.success && typeStatsResult.data) { setTypeStats(typeStatsResult.data); } // totalItems 업데이트 (푸터용) setTotalItems(result.pagination.total); return { success: true, data: result.data, totalCount: result.pagination.total, totalPages: result.pagination.lastPage, }; } return { success: false, error: result.error }; } catch (error) { if (isNextRedirectError(error)) throw error; return { success: false, error: '데이터 로드 중 오류가 발생했습니다.' }; } }, }, // 테이블 컬럼 columns: [ { key: 'no', label: '번호', className: 'w-[60px] text-center' }, { key: 'itemCode', label: '품목코드', className: 'min-w-[120px]' }, { key: 'itemName', label: '품목명', className: 'min-w-[200px]' }, { key: 'itemType', label: '품목유형', className: 'w-[100px]' }, { key: 'unit', label: '단위', className: 'w-[60px] text-center' }, { key: 'stockQty', label: '재고량', className: 'w-[80px] text-center' }, { key: 'safetyStock', label: '안전재고', className: 'w-[80px] text-center' }, { key: 'lot', label: 'LOT', className: 'w-[100px] text-center' }, { key: 'status', label: '상태', className: 'w-[60px] text-center' }, { key: 'location', label: '위치', className: 'w-[60px] text-center' }, ], // 서버 사이드 페이지네이션 clientSideFiltering: false, itemsPerPage: ITEMS_PER_PAGE, // 검색 searchPlaceholder: '품목코드, 품목명 검색...', // 탭 설정 tabs, defaultTab: 'all', // 통계 카드 stats, // 테이블 푸터 tableFooter, // 헤더 액션 (엑셀 다운로드) headerActions: () => ( ), // 테이블 행 렌더링 renderTableRow: ( item: StockItem, index: number, globalIndex: number, handlers: SelectionHandlers & RowClickHandlers ) => { return ( handleRowClick(item)} > e.stopPropagation()}> {globalIndex} {item.itemCode} {item.itemName} {ITEM_TYPE_LABELS[item.itemType]} {item.unit} {item.stockQty} {item.safetyStock}
{item.lotCount}개 {item.lotDaysElapsed > 0 && ( {item.lotDaysElapsed}일 경과 )}
{item.status ? ( {STOCK_STATUS_LABELS[item.status]} ) : ( - )} {item.location}
); }, // 모바일 카드 렌더링 renderMobileCard: ( item: StockItem, index: number, globalIndex: number, handlers: SelectionHandlers & RowClickHandlers ) => { return ( handleRowClick(item)} headerBadges={ <> #{globalIndex} {item.itemCode} } title={item.itemName} statusBadge={ {ITEM_TYPE_LABELS[item.itemType]} } infoGrid={
0 ? ` (${item.lotDaysElapsed}일 경과)` : ''}`} />
} actions={ handlers.isSelected && (
) } /> ); }, }), [tabs, stats, tableFooter, handleRowClick, handleExcelDownload] ); return ; }