From c62ec046327fd388ebce04571b55910747e69a3b Mon Sep 17 00:00:00 2001 From: dujinkim Date: Tue, 9 Sep 2025 10:32:34 +0000 Subject: (정희성, 최겸, 대표님) formatDate 변경 등 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/form-data-stat/form-data-stat-table.tsx | 261 +++++++++++++++++---- 1 file changed, 219 insertions(+), 42 deletions(-) (limited to 'components/form-data-stat/form-data-stat-table.tsx') diff --git a/components/form-data-stat/form-data-stat-table.tsx b/components/form-data-stat/form-data-stat-table.tsx index fe59785d..a56a4e88 100644 --- a/components/form-data-stat/form-data-stat-table.tsx +++ b/components/form-data-stat/form-data-stat-table.tsx @@ -1,10 +1,11 @@ +// 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 } from "lucide-react"; +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"; @@ -12,17 +13,25 @@ import { cn } from "@/lib/utils"; import { toast } from "sonner"; import type { DataTableAdvancedFilterField } from "@/types/table"; import { Progress } from "@/components/ui/progress"; -import { getVendorFormStatus } from "@/lib/forms/stat"; +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"; // 타입 정의 interface VendorFormStatus { vendorId: number; vendorName: string; - formCount: number; // 벤더가 가진 form 개수 - tagCount: number; // 벤더가 가진 tag 개수 - totalFields: number; // 입력해야 하는 총 필드 개수 - completedFields: number; // 입력 완료된 필드 개수 - completionRate: number; // 완료율 (%) + formCount: number; + tagCount: number; + totalFields: number; + completedFields: number; + completionRate: number; +} + +interface Project { + id: number; + projectCode: string; + projectName: string; } interface VendorFormStatusTableProps { @@ -45,35 +54,164 @@ const getCompletionBadgeVariant = (rate: number): "default" | "secondary" | "des 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(); + const result = await getVendorFormStatus(selectedProject?.id); setData(result); - toast.success("데이터를 새로고침했습니다."); + if (selectedProject) { + toast.success(`${selectedProject.projectCode} 데이터를 새로고침했습니다.`); + } else { + toast.success("전체 프로젝트 데이터를 새로고침했습니다."); + } } catch (error) { console.error("Refresh error:", error); toast.error("새로고침 중 오류가 발생했습니다."); } finally { setIsRefreshing(false); } - }, []); + }, [selectedProject]); - // 초기 데이터 로드 - React.useEffect(() => { - if (initialData.length === 0) { - handleRefresh(); - } - }, []); - - // 컬럼 정의 + // 컬럼 정의 (기존과 동일) const columns: ColumnDef[] = React.useMemo(() => [ { accessorKey: "vendorName", @@ -197,6 +335,39 @@ export function VendorFormStatusTable({ return (
+ {/* 프로젝트 선택 */} + + + 프로젝트 선택 + + +
+
+ +
+ +
+ {selectedProject && ( +
+ 선택된 프로젝트: {selectedProject.projectCode} - {selectedProject.projectName} +
+ )} +
+
+ {/* 요약 카드 */}
@@ -249,33 +420,39 @@ export function VendorFormStatusTable({
- 벤더별 Form 입력 현황 - + + 벤더별 Form 입력 현황 + {selectedProject && ( + + ({selectedProject.projectCode}) + + )} +
- + {data.length > 0 ? ( + + ) : ( +
+ {selectedProject + ? "선택한 프로젝트에 대한 데이터가 없습니다." + : "데이터가 없습니다."} +
+ )}
-- cgit v1.2.3