From 2c02afd48a4d9276a4f5c132e088540a578d0972 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Tue, 30 Sep 2025 10:08:53 +0000 Subject: (대표님) 폼리스트, spreadjs 관련 변경사항, 벤더문서 뷰 쿼리 수정, 이메일 템플릿 추가 등 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/form-data/form-data-table.tsx | 189 ++- components/form-data/spreadJS-dialog copy.tsx | 1514 +++++++++++++++++++++++++ components/form-data/spreadJS-dialog.tsx | 725 ++++++------ 3 files changed, 2077 insertions(+), 351 deletions(-) create mode 100644 components/form-data/spreadJS-dialog copy.tsx (limited to 'components/form-data') diff --git a/components/form-data/form-data-table.tsx b/components/form-data/form-data-table.tsx index a2645679..591ba66a 100644 --- a/components/form-data/form-data-table.tsx +++ b/components/form-data/form-data-table.tsx @@ -19,14 +19,19 @@ import { Upload, Plus, Tag, - TagsIcon, + TagsIcon, FileOutput, Clipboard, Send, GitCompareIcon, RefreshCcw, Trash2, - Eye + Eye, + FileText, + Target, + CheckCircle2, + AlertCircle, + Clock } from "lucide-react"; import { toast } from "sonner"; import { @@ -54,6 +59,13 @@ import { SEDPCompareDialog } from "./sedp-compare-dialog"; import { DeleteFormDataDialog } from "./delete-form-data-dialog"; import { TemplateViewDialog } from "./spreadJS-dialog"; import { fetchTemplateFromSEDP } from "@/lib/forms/sedp-actions"; +import { FormStatusByVendor, getFormStatusByVendor } from "@/lib/forms/stat"; +import { + Card, + CardContent, + CardHeader, + CardTitle +} from "@/components/ui/card"; interface GenericData { [key: string]: unknown; @@ -99,6 +111,33 @@ export default function DynamicTable({ const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false); const [deleteTarget, setDeleteTarget] = React.useState([]); + const [formStats, setFormStats] = React.useState(null); + const [isLoadingStats, setIsLoadingStats] = React.useState(true); + + + React.useEffect(() => { + const fetchFormStats = async () => { + try { + setIsLoadingStats(true); + // getFormStatusByVendor 서버 액션 직접 호출 + const data = await getFormStatusByVendor(projectId, formCode); + + if (data && data.length > 0) { + setFormStats(data[0]); + } + } catch (error) { + console.error("Failed to fetch form stats:", error); + toast.error("통계 데이터를 불러오는데 실패했습니다."); + } finally { + setIsLoadingStats(false); + } + }; + + if (projectId && formCode) { + fetchFormStats(); + } + }, [projectId, formCode]); + // Update tableData when dataJSON changes React.useEffect(() => { setTableData(dataJSON); @@ -180,7 +219,7 @@ export default function DynamicTable({ setPackageCode(''); } }; - + getPackageCode(); }, [contractItemId]) // Get project code when component mounts @@ -633,6 +672,142 @@ export default function DynamicTable({ return ( <> +
+
+ {/* Tag Count */} + + + + Total Tags + + + + +
+ {isLoadingStats ? ( + - + ) : ( + formStats?.tagCount || 0 + )} +
+

+ Total Tag Count +

+
+
+ + {/* Completion Rate */} + + + + Completion + + + + +
+ {isLoadingStats ? ( + - + ) : ( + `${formStats?.completionRate || 0}%` + )} +
+

+ {formStats ? `${formStats.completedFields} / ${formStats.totalFields}` : '-'} +

+
+
+ + {/* Completed Fields */} + + + + Completed + + + + +
+ {isLoadingStats ? ( + - + ) : ( + formStats?.completedFields || 0 + )} +
+

+ Completed Fields +

+
+
+ + {/* Remaining Fields */} + + + + Remaining + + + + +
+ {isLoadingStats ? ( + - + ) : ( + (formStats?.totalFields || 0) - (formStats?.completedFields || 0) + )} +
+

+ Remaining Fields +

+
+
+ + {/* Upcoming (7 days) */} + + + + Upcoming + + + + +
+ {isLoadingStats ? ( + - + ) : ( + formStats?.upcomingCount || 0 + )} +
+

+ Due in 7 Days +

+
+
+ + {/* Overdue */} + + + + Overdue + + + + +
+ {isLoadingStats ? ( + - + ) : ( + formStats?.overdueCount || 0 + )} +
+

+ Overdue +

+
+
+
+
+ {(isSyncingTags || isLoadingTags) ? (