summaryrefslogtreecommitdiff
path: root/components/form-data/form-data-table.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-09-30 10:08:53 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-09-30 10:08:53 +0000
commit2c02afd48a4d9276a4f5c132e088540a578d0972 (patch)
treee5efdd3f48fad33681c139a4c58481f4514fb38e /components/form-data/form-data-table.tsx
parent19794b32a6e3285fdeda7519ededdce451966f3d (diff)
(대표님) 폼리스트, spreadjs 관련 변경사항, 벤더문서 뷰 쿼리 수정, 이메일 템플릿 추가 등
Diffstat (limited to 'components/form-data/form-data-table.tsx')
-rw-r--r--components/form-data/form-data-table.tsx189
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" />