diff options
Diffstat (limited to 'lib/vendor-document-list/ship')
| -rw-r--r-- | lib/vendor-document-list/ship/enhanced-documents-table.tsx | 184 | ||||
| -rw-r--r-- | lib/vendor-document-list/ship/send-to-shi-button.tsx | 108 |
2 files changed, 279 insertions, 13 deletions
diff --git a/lib/vendor-document-list/ship/enhanced-documents-table.tsx b/lib/vendor-document-list/ship/enhanced-documents-table.tsx index 24ab42fb..cae0fe06 100644 --- a/lib/vendor-document-list/ship/enhanced-documents-table.tsx +++ b/lib/vendor-document-list/ship/enhanced-documents-table.tsx @@ -1,4 +1,4 @@ -// simplified-documents-table.tsx - 최적화된 버전 +// simplified-documents-table.tsx - Project Code 필터 기능 추가 "use client" import React from "react" @@ -13,7 +13,8 @@ import { getUserVendorDocuments, getUserVendorDocumentStats } from "@/lib/vendor import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar" import { toast } from "sonner" import { Badge } from "@/components/ui/badge" -import { FileText } from "lucide-react" +import { Button } from "@/components/ui/button" +import { FileText, FileInput, FileOutput, FolderOpen, Building2 } from "lucide-react" import { Label } from "@/components/ui/label" import { DataTable } from "@/components/data-table/data-table" @@ -21,6 +22,15 @@ import { SimplifiedDocumentsView } from "@/db/schema" import { getSimplifiedDocumentColumns } from "./enhanced-doc-table-columns" import { EnhancedDocTableToolbarActions } from "./enhanced-doc-table-toolbar-actions" +// 🔥 Project Code 필터를 위한 Select 컴포넌트 import 추가 +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" + // DrawingKind별 설명 매핑 const DRAWING_KIND_INFO = { B3: { @@ -60,17 +70,61 @@ export function SimplifiedDocumentsTable({ const { data, pageCount, total, drawingKind, vendorInfo } = React.useMemo(() => documentResult, [documentResult]) const { stats, totalDocuments, primaryDrawingKind } = React.useMemo(() => statsResult, [statsResult]) + // 🔥 B4 필터 상태 추가 + const [b4FilterType, setB4FilterType] = React.useState<'all' | 'gtt_deliverable' | 'shi_input'>('all') + + // 🔥 Project Code 필터 상태 추가 + const [selectedProjectCode, setSelectedProjectCode] = React.useState<string>('all') + + // 🔥 고유한 Project Code 목록 추출 및 카운트 메모이제이션 + const projectCodeStats = React.useMemo(() => { + const projectMap = new Map<string, number>() + + data.forEach(doc => { + const projectCode = doc.projectCode || 'Unknown' + projectMap.set(projectCode, (projectMap.get(projectCode) || 0) + 1) + }) + + // 정렬된 배열로 변환 (프로젝트 코드 알파벳순) + return Array.from(projectMap.entries()) + .sort((a, b) => a[0].localeCompare(b[0])) + .map(([code, count]) => ({ code, count })) + }, [data]) + // 🔥 데이터 로드 콜백을 useCallback으로 최적화 const handleDataLoaded = React.useCallback((loadedData: SimplifiedDocumentsView[]) => { onDataLoaded?.(loadedData) }, [onDataLoaded]) - // 🔥 데이터가 로드되면 콜백 호출 (의존성 최적화) + // 🔥 B4 및 Project Code 필터링된 데이터 메모이제이션 + const filteredData = React.useMemo(() => { + let result = data + + // B4 필터 적용 + if (b4FilterType !== 'all') { + if (b4FilterType === 'gtt_deliverable') { + result = result.filter(doc => doc.drawingMoveGbn === '도면입수') + } else if (b4FilterType === 'shi_input') { + result = result.filter(doc => doc.drawingMoveGbn === '도면제출') + } + } + + // Project Code 필터 적용 + if (selectedProjectCode !== 'all') { + result = result.filter(doc => + (doc.projectCode || 'Unknown') === selectedProjectCode + ) + } + + return result + }, [data, b4FilterType, selectedProjectCode]) + + // 🔥 데이터가 로드되면 콜백 호출 (필터링된 데이터 사용) React.useEffect(() => { - if (data && handleDataLoaded) { - handleDataLoaded(data) + if (filteredData && handleDataLoaded) { + handleDataLoaded(filteredData) } - }, [data, handleDataLoaded]) + }, [filteredData, handleDataLoaded]) // 🔥 상태들을 안정적으로 관리 const [rowAction, setRowAction] = React.useState<DataTableRowAction<SimplifiedDocumentsView> | null>(null) @@ -81,7 +135,7 @@ export function SimplifiedDocumentsTable({ () => getSimplifiedDocumentColumns({ setRowAction, }), - [] // setRowAction은 항상 동일한 함수이므로 의존성에서 제외 + [] ) // 🔥 필터 필드들을 메모이제이션 @@ -238,7 +292,7 @@ export function SimplifiedDocumentsTable({ const getRowId = React.useCallback((originalRow: SimplifiedDocumentsView) => String(originalRow.documentId), []) const { table } = useDataTable({ - data, + data: filteredData, columns, pageCount, enablePinning: true, @@ -260,6 +314,21 @@ export function SimplifiedDocumentsTable({ return activeDrawingKind ? DRAWING_KIND_INFO[activeDrawingKind] : null }, [activeDrawingKind]) + // 🔥 B4 문서 통계 계산 + const b4Stats = React.useMemo(() => { + if (!hasB4Documents) return null + + const gttDeliverableCount = data.filter(doc => + doc.drawingKind === 'B4' && doc.drawingMoveGbn === '도면입수' + ).length + + const shiInputCount = data.filter(doc => + doc.drawingKind === 'B4' && doc.drawingMoveGbn === '도면제출' + ).length + + return { gttDeliverableCount, shiInputCount } + }, [data, hasB4Documents]) + return ( <div className="w-full space-y-4"> {/* DrawingKind 정보 간단 표시 */} @@ -270,12 +339,107 @@ export function SimplifiedDocumentsTable({ </div> <div className="flex items-center gap-2"> <Badge variant="outline"> - {total} documents + {filteredData.length} documents </Badge> </div> </div> )} + {/* 🔥 필터 섹션 - Project Code 필터와 B4 필터를 함께 배치 */} + <div className="space-y-3"> + {/* Project Code 필터 드롭다운 */} + <div className="flex items-center gap-3 p-4 bg-muted/30 rounded-lg"> + <div className="flex items-center gap-2"> + <Building2 className="h-4 w-4 text-muted-foreground" /> + <Label className="text-sm font-medium">Project:</Label> + </div> + <Select + value={selectedProjectCode} + onValueChange={setSelectedProjectCode} + > + <SelectTrigger className="w-[200px]"> + <SelectValue placeholder="Select a project" /> + </SelectTrigger> + <SelectContent> + <SelectItem value="all"> + <div className="flex items-center justify-between w-full"> + <span>All Projects</span> + <Badge variant="secondary" className="ml-2"> + {data.length} + </Badge> + </div> + </SelectItem> + {projectCodeStats.map(({ code, count }) => ( + <SelectItem key={code} value={code}> + <div className="flex items-center justify-between w-full"> + <span className="font-mono">{code}</span> + <Badge variant="secondary" className="ml-2"> + {count} + </Badge> + </div> + </SelectItem> + ))} + </SelectContent> + </Select> + + {selectedProjectCode !== 'all' && ( + <Button + variant="ghost" + size="sm" + onClick={() => setSelectedProjectCode('all')} + className="h-8" + > + Clear filter + </Button> + )} + </div> + + {/* B4 필터 버튼 - 기존 코드 유지 */} + {hasB4Documents && b4Stats && ( + <div className="flex items-center gap-2 p-4 bg-muted/50 rounded-lg"> + <Label className="text-sm font-medium">Document Type:</Label> + <div className="flex gap-2"> + <Button + variant={b4FilterType === 'all' ? 'default' : 'outline'} + size="sm" + onClick={() => setB4FilterType('all')} + className="gap-2" + > + <FileText className="h-4 w-4" /> + All + <Badge variant="secondary" className="ml-1"> + {b4Stats.gttDeliverableCount + b4Stats.shiInputCount} + </Badge> + </Button> + <Button + variant={b4FilterType === 'gtt_deliverable' ? 'default' : 'outline'} + size="sm" + onClick={() => setB4FilterType('gtt_deliverable')} + className="gap-2" + > + <FileInput className="h-4 w-4" /> + GTT Deliverable + <Badge variant="secondary" className="ml-1"> + {b4Stats.gttDeliverableCount} + </Badge> + </Button> + <Button + variant={b4FilterType === 'shi_input' ? 'default' : 'outline'} + size="sm" + onClick={() => setB4FilterType('shi_input')} + className="gap-2" + > + <FileOutput className="h-4 w-4" /> + SHI Input Document + <Badge variant="secondary" className="ml-1"> + {b4Stats.shiInputCount} + </Badge> + </Button> + </div> + </div> + )} + </div> + {/* 테이블 */} <div className="overflow-x-auto"> <DataTable table={table} compact> @@ -287,7 +451,7 @@ export function SimplifiedDocumentsTable({ <EnhancedDocTableToolbarActions table={table} projectType="ship" - b4={hasB4Documents} + b4={hasB4Documents && b4FilterType === 'gtt_deliverable'} /> </DataTableAdvancedToolbar> </DataTable> diff --git a/lib/vendor-document-list/ship/send-to-shi-button.tsx b/lib/vendor-document-list/ship/send-to-shi-button.tsx index 52874702..7bb85710 100644 --- a/lib/vendor-document-list/ship/send-to-shi-button.tsx +++ b/lib/vendor-document-list/ship/send-to-shi-button.tsx @@ -325,21 +325,123 @@ export function SendToSHIButton({ <div className="space-y-3"> <Separator /> + {/* 전체 통계 */} <div className="grid grid-cols-3 gap-4 text-sm"> <div className="text-center"> <div className="text-muted-foreground">{t('shiSync.labels.pending')}</div> - <div className="font-medium text-orange-600">{t('shiSync.labels.itemCount', { count: totalStats.totalPending })}</div> + <div className="font-medium text-orange-600"> + {t('shiSync.labels.itemCount', { count: totalStats.totalPending })} + </div> </div> <div className="text-center"> <div className="text-muted-foreground">{t('shiSync.labels.synced')}</div> - <div className="font-medium text-emerald-600 dark:text-emerald-400">{t('shiSync.labels.itemCount', { count: totalStats.totalSynced })}</div> + <div className="font-medium text-emerald-600 dark:text-emerald-400"> + {t('shiSync.labels.itemCount', { count: totalStats.totalSynced })} + </div> </div> <div className="text-center"> <div className="text-muted-foreground">{t('shiSync.labels.failed')}</div> - <div className="font-medium text-destructive">{t('shiSync.labels.itemCount', { count: totalStats.totalFailed })}</div> + <div className="font-medium text-destructive"> + {t('shiSync.labels.itemCount', { count: totalStats.totalFailed })} + </div> </div> </div> + {/* EntityType별 상세 통계 추가 */} + {totalStats.entityTypeDetailsTotals && ( + <> + <Separator className="my-2" /> + <div className="space-y-2"> + <div className="text-sm font-medium flex items-center gap-2"> + {t('shiSync.labels.detailsByType')} + <Badge variant="outline" className="text-xs"> + {t('shiSync.labels.experimental')} + </Badge> + </div> + + <div className="space-y-1 text-xs"> + {/* Document 통계 */} + {totalStats.entityTypeDetailsTotals.document && ( + <div className="flex items-center justify-between p-2 rounded bg-muted/50"> + <span className="font-medium"> + {t('shiSync.labels.documents')} + </span> + <div className="flex gap-3 text-xs"> + {totalStats.entityTypeDetailsTotals.document.pending > 0 && ( + <span className="text-orange-600"> + {totalStats.entityTypeDetailsTotals.document.pending} {t('shiSync.labels.pendingShort')} + </span> + )} + {totalStats.entityTypeDetailsTotals.document.synced > 0 && ( + <span className="text-emerald-600"> + {totalStats.entityTypeDetailsTotals.document.synced} {t('shiSync.labels.syncedShort')} + </span> + )} + {totalStats.entityTypeDetailsTotals.document.failed > 0 && ( + <span className="text-destructive"> + {totalStats.entityTypeDetailsTotals.document.failed} {t('shiSync.labels.failedShort')} + </span> + )} + </div> + </div> + )} + + {/* Revision 통계 */} + {totalStats.entityTypeDetailsTotals.revision && ( + <div className="flex items-center justify-between p-2 rounded bg-muted/50"> + <span className="font-medium"> + {t('shiSync.labels.revisions')} + </span> + <div className="flex gap-3 text-xs"> + {totalStats.entityTypeDetailsTotals.revision.pending > 0 && ( + <span className="text-orange-600"> + {totalStats.entityTypeDetailsTotals.revision.pending} {t('shiSync.labels.pendingShort')} + </span> + )} + {totalStats.entityTypeDetailsTotals.revision.synced > 0 && ( + <span className="text-emerald-600"> + {totalStats.entityTypeDetailsTotals.revision.synced} {t('shiSync.labels.syncedShort')} + </span> + )} + {totalStats.entityTypeDetailsTotals.revision.failed > 0 && ( + <span className="text-destructive"> + {totalStats.entityTypeDetailsTotals.revision.failed} {t('shiSync.labels.failedShort')} + </span> + )} + </div> + </div> + )} + + {/* Attachment 통계 */} + {totalStats.entityTypeDetailsTotals.attachment && ( + <div className="flex items-center justify-between p-2 rounded bg-muted/50"> + <span className="font-medium"> + {t('shiSync.labels.attachments')} + </span> + <div className="flex gap-3 text-xs"> + {totalStats.entityTypeDetailsTotals.attachment.pending > 0 && ( + <span className="text-orange-600"> + {totalStats.entityTypeDetailsTotals.attachment.pending} {t('shiSync.labels.pendingShort')} + </span> + )} + {totalStats.entityTypeDetailsTotals.attachment.synced > 0 && ( + <span className="text-emerald-600"> + {totalStats.entityTypeDetailsTotals.attachment.synced} {t('shiSync.labels.syncedShort')} + </span> + )} + {totalStats.entityTypeDetailsTotals.attachment.failed > 0 && ( + <span className="text-destructive"> + {totalStats.entityTypeDetailsTotals.attachment.failed} {t('shiSync.labels.failedShort')} + </span> + )} + </div> + </div> + )} + </div> + </div> + </> + )} + {/* 계약별 상세 상태 */} {contractStatuses.length > 1 && ( <div className="space-y-2"> |
