diff options
Diffstat (limited to 'components/form-data/form-data-table.tsx')
| -rw-r--r-- | components/form-data/form-data-table.tsx | 189 |
1 files changed, 182 insertions, 7 deletions
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" /> |
