summaryrefslogtreecommitdiff
path: root/lib/vendor-document-list/ship
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendor-document-list/ship')
-rw-r--r--lib/vendor-document-list/ship/enhanced-documents-table.tsx184
-rw-r--r--lib/vendor-document-list/ship/send-to-shi-button.tsx108
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">