From 2c02afd48a4d9276a4f5c132e088540a578d0972 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
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 +++++++++++++++++++++++++++++--
 1 file changed, 182 insertions(+), 7 deletions(-)

(limited to 'components/form-data/form-data-table.tsx')

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<GenericData[]>([]);
 
+  const [formStats, setFormStats] = React.useState<FormStatusByVendor | null>(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 (
     <>
+      <div className="mb-6">
+        <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-6">
+          {/* Tag Count */}
+          <Card>
+            <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+              <CardTitle className="text-sm font-medium">
+                Total Tags
+              </CardTitle>
+              <FileText className="h-4 w-4 text-muted-foreground" />
+            </CardHeader>
+            <CardContent>
+              <div className="text-2xl font-bold">
+                {isLoadingStats ? (
+                  <span className="animate-pulse">-</span>
+                ) : (
+                  formStats?.tagCount || 0
+                )}
+              </div>
+              <p className="text-xs text-muted-foreground">
+                Total Tag Count
+              </p>
+            </CardContent>
+          </Card>
+
+          {/* Completion Rate */}
+          <Card>
+            <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+              <CardTitle className="text-sm font-medium">
+                Completion
+              </CardTitle>
+              <Target className="h-4 w-4 text-muted-foreground" />
+            </CardHeader>
+            <CardContent>
+              <div className="text-2xl font-bold">
+                {isLoadingStats ? (
+                  <span className="animate-pulse">-</span>
+                ) : (
+                  `${formStats?.completionRate || 0}%`
+                )}
+              </div>
+              <p className="text-xs text-muted-foreground">
+                {formStats ? `${formStats.completedFields} / ${formStats.totalFields}` : '-'}
+              </p>
+            </CardContent>
+          </Card>
+
+          {/* Completed Fields */}
+          <Card>
+            <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+              <CardTitle className="text-sm font-medium">
+                Completed
+              </CardTitle>
+              <CheckCircle2 className="h-4 w-4 text-green-600" />
+            </CardHeader>
+            <CardContent>
+              <div className="text-2xl font-bold text-green-600">
+                {isLoadingStats ? (
+                  <span className="animate-pulse">-</span>
+                ) : (
+                  formStats?.completedFields || 0
+                )}
+              </div>
+              <p className="text-xs text-muted-foreground">
+                Completed Fields
+              </p>
+            </CardContent>
+          </Card>
+
+          {/* Remaining Fields */}
+          <Card>
+            <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+              <CardTitle className="text-sm font-medium">
+                Remaining
+              </CardTitle>
+              <Clock className="h-4 w-4 text-muted-foreground" />
+            </CardHeader>
+            <CardContent>
+              <div className="text-2xl font-bold">
+                {isLoadingStats ? (
+                  <span className="animate-pulse">-</span>
+                ) : (
+                  (formStats?.totalFields || 0) - (formStats?.completedFields || 0)
+                )}
+              </div>
+              <p className="text-xs text-muted-foreground">
+                Remaining Fields
+              </p>
+            </CardContent>
+          </Card>
+
+          {/* Upcoming (7 days) */}
+          <Card>
+            <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+              <CardTitle className="text-sm font-medium">
+                Upcoming
+              </CardTitle>
+              <AlertCircle className="h-4 w-4 text-yellow-600" />
+            </CardHeader>
+            <CardContent>
+              <div className="text-2xl font-bold text-yellow-600">
+                {isLoadingStats ? (
+                  <span className="animate-pulse">-</span>
+                ) : (
+                  formStats?.upcomingCount || 0
+                )}
+              </div>
+              <p className="text-xs text-muted-foreground">
+                Due in 7 Days
+              </p>
+            </CardContent>
+          </Card>
+
+          {/* Overdue */}
+          <Card>
+            <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+              <CardTitle className="text-sm font-medium">
+                Overdue
+              </CardTitle>
+              <AlertCircle className="h-4 w-4 text-red-600" />
+            </CardHeader>
+            <CardContent>
+              <div className="text-2xl font-bold text-red-600">
+                {isLoadingStats ? (
+                  <span className="animate-pulse">-</span>
+                ) : (
+                  formStats?.overdueCount || 0
+                )}
+              </div>
+              <p className="text-xs text-muted-foreground">
+                Overdue
+              </p>
+            </CardContent>
+          </Card>
+        </div>
+      </div>
+
       <ClientDataTable
         data={tableData}
         columns={columns}
@@ -661,8 +836,8 @@ export default function DynamicTable({
               <Button variant="outline" size="sm" disabled={isAnyOperationPending}>
                 {(isSyncingTags || isLoadingTags) ? (
                   <Loader className="mr-2 size-4 animate-spin" aria-hidden="true" />
-                ):
-                <TagsIcon className="size-4" />}
+                ) :
+                  <TagsIcon className="size-4" />}
                 {t("buttons.tagOperations")}
               </Button>
             </DropdownMenuTrigger>
@@ -679,8 +854,8 @@ export default function DynamicTable({
                   {t("buttons.getTags")}
                 </DropdownMenuItem>
               )}
-              <DropdownMenuItem 
-                onClick={() => setAddTagDialogOpen(true)} 
+              <DropdownMenuItem
+                onClick={() => setAddTagDialogOpen(true)}
                 disabled={isAnyOperationPending || isAddTagDisabled}
               >
                 <Plus className="mr-2 h-4 w-4" />
-- 
cgit v1.2.3

