// components/form-data-stat/form-data-stat-table.tsx "use client"; import * as React from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { RefreshCw, Check, ChevronsUpDown } from "lucide-react"; import { type ColumnDef } from "@tanstack/react-table"; import { ClientDataTableColumnHeaderSimple } from "@/components/client-data-table/data-table-column-simple-header"; import { ClientDataTable } from "@/components/client-data-table/data-table"; import { cn } from "@/lib/utils"; import { toast } from "sonner"; import type { DataTableAdvancedFilterField } from "@/types/table"; import { Progress } from "@/components/ui/progress"; import { getVendorFormStatus, getProjectsWithContracts } from "@/lib/forms/stat"; import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover"; import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem } from "@/components/ui/command"; import { createFilterFn } from "@/components/client-data-table/table-filters"; // 타입 정의 interface VendorFormStatus { vendorId: number; vendorName: string; formCount: number; tagCount: number; totalFields: number; completedFields: number; completionRate: number; } interface Project { id: number; projectCode: string; projectName: string; } interface VendorFormStatusTableProps { initialData?: VendorFormStatus[]; } // 완료율에 따른 색상 반환 const getCompletionColor = (rate: number) => { if (rate >= 80) return "text-green-600"; if (rate >= 50) return "text-yellow-600"; if (rate >= 20) return "text-orange-600"; return "text-red-600"; }; // 완료율에 따른 Badge variant 반환 const getCompletionBadgeVariant = (rate: number): "default" | "secondary" | "destructive" | "outline" => { if (rate >= 80) return "default"; if (rate >= 50) return "secondary"; if (rate >= 20) return "outline"; return "destructive"; }; // 프로젝트 선택 컴포넌트 function ProjectSelectorWithContracts({ selectedProject, onProjectSelect, projects, isLoading }: { selectedProject: Project | null; onProjectSelect: (project: Project | null) => void; projects: Project[]; isLoading: boolean; }) { const [open, setOpen] = React.useState(false); const [searchTerm, setSearchTerm] = React.useState(""); // 검색어로 필터링 const filteredProjects = React.useMemo(() => { if (!searchTerm.trim()) return projects; const lowerSearch = searchTerm.toLowerCase(); return projects.filter( project => project.projectCode.toLowerCase().includes(lowerSearch) || project.projectName.toLowerCase().includes(lowerSearch) ); }, [projects, searchTerm]); const handleSelectProject = (project: Project | null) => { onProjectSelect(project); setOpen(false); }; return ( 검색 결과가 없습니다 {isLoading ? (
로딩 중...
) : ( {/* 전체 옵션 추가 */} handleSelectProject(null)} > 전체 프로젝트 {/* 프로젝트 목록 */} {filteredProjects.map((project) => ( handleSelectProject(project)} > {project.projectCode} - {project.projectName} ))} )}
); } export function VendorFormStatusTable({ initialData = [], }: VendorFormStatusTableProps) { const [data, setData] = React.useState(initialData); const [isRefreshing, setIsRefreshing] = React.useState(false); const [selectedProject, setSelectedProject] = React.useState(null); const [projects, setProjects] = React.useState([]); const [isLoadingProjects, setIsLoadingProjects] = React.useState(false); // 프로젝트 목록 로드 React.useEffect(() => { const loadProjects = async () => { setIsLoadingProjects(true); try { const projectsWithContracts = await getProjectsWithContracts(); setProjects(projectsWithContracts); // 프로젝트가 하나만 있으면 자동 선택 if (projectsWithContracts.length === 1) { setSelectedProject(projectsWithContracts[0]); } } catch (error) { console.error("프로젝트 목록 로드 오류:", error); toast.error("프로젝트 목록을 불러오는데 실패했습니다."); } finally { setIsLoadingProjects(false); } }; loadProjects(); }, []); // 프로젝트 변경 시 데이터 로드 React.useEffect(() => { handleRefresh(); }, [selectedProject]); // 데이터 새로고침 const handleRefresh = React.useCallback(async () => { setIsRefreshing(true); try { const result = await getVendorFormStatus(selectedProject?.id); setData(result); if (selectedProject) { toast.success(`${selectedProject.projectCode} 데이터를 새로고침했습니다.`); } else { toast.success("전체 프로젝트 데이터를 새로고침했습니다."); } } catch (error) { console.error("Refresh error:", error); toast.error("새로고침 중 오류가 발생했습니다."); } finally { setIsRefreshing(false); } }, [selectedProject]); // 컬럼 정의 (기존과 동일) const columns: ColumnDef[] = React.useMemo(() => [ { accessorKey: "vendorName", header: ({ column }) => , filterFn: createFilterFn("text"), cell: ({ row }) => (
{row.original.vendorName}
), size: 200, enablePinning: true, meta: { excelHeader: "벤더명" }, }, { accessorKey: "formCount", header: ({ column }) => , filterFn: createFilterFn("number"), cell: ({ row }) => (
{row.original.formCount}
), size: 100, meta: { excelHeader: "Form 개수" }, }, { accessorKey: "tagCount", header: ({ column }) => , filterFn: createFilterFn("number"), cell: ({ row }) => (
{row.original.tagCount}
), size: 100, meta: { excelHeader: "Tag 개수" }, }, { accessorKey: "totalFields", header: ({ column }) => , filterFn: createFilterFn("number"), cell: ({ row }) => (
{row.original.totalFields.toLocaleString()}
), size: 100, meta: { excelHeader: "전체 필드" }, }, { accessorKey: "completedFields", header: ({ column }) => , filterFn: createFilterFn("number"), cell: ({ row }) => (
{row.original.completedFields.toLocaleString()}
), size: 100, meta: { excelHeader: "완료 필드" }, }, { accessorKey: "completionRate", header: ({ column }) => , filterFn: createFilterFn("number"), cell: ({ row }) => { const rate = row.original.completionRate; return (
{rate.toFixed(1)}%
); }, size: 180, meta: { excelHeader: "완료율" }, }, { id: "progress", header: "진행 상태", filterFn: createFilterFn("number"), cell: ({ row }) => { const { completedFields, totalFields } = row.original; return (
{completedFields} / {totalFields}
); }, size: 120, meta: { excelHeader: "진행 상태" }, }, ], []); // 필터 필드 정의 const advancedFilterFields: DataTableAdvancedFilterField[] = [ { id: "vendorName", label: "벤더명", type: "text" }, { id: "formCount", label: "Form 개수", type: "number" }, { id: "tagCount", label: "Tag 개수", type: "number" }, { id: "completionRate", label: "완료율", type: "number" }, ]; // 요약 통계 const summaryStats = React.useMemo(() => { const totalVendors = data.length; const totalForms = data.reduce((sum, v) => sum + v.formCount, 0); const totalTags = data.reduce((sum, v) => sum + v.tagCount, 0); const avgCompletionRate = data.length > 0 ? data.reduce((sum, v) => sum + v.completionRate, 0) / data.length : 0; const fullCompletedVendors = data.filter(v => v.completionRate === 100).length; return { totalVendors, totalForms, totalTags, avgCompletionRate, fullCompletedVendors, }; }, [data]); return (
{/* 프로젝트 선택 */} 프로젝트 선택
{selectedProject && (
선택된 프로젝트: {selectedProject.projectCode} - {selectedProject.projectName}
)}
{/* 요약 카드 */}
전체 벤더
{summaryStats.totalVendors}
전체 Form
{summaryStats.totalForms.toLocaleString()}
전체 Tag
{summaryStats.totalTags.toLocaleString()}
평균 완료율
{summaryStats.avgCompletionRate.toFixed(1)}%
완료 벤더
{summaryStats.fullCompletedVendors} / {summaryStats.totalVendors}
{/* 데이터 테이블 */}
벤더별 Form 입력 현황 {selectedProject && ( ({selectedProject.projectCode}) )}
{data.length > 0 ? ( ) : (
{selectedProject ? "선택한 프로젝트에 대한 데이터가 없습니다." : "데이터가 없습니다."}
)}
); }