diff options
| author | joonhoekim <26rote@gmail.com> | 2025-03-25 15:55:45 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-03-25 15:55:45 +0900 |
| commit | 1a2241c40e10193c5ff7008a7b7b36cc1d855d96 (patch) | |
| tree | 8a5587f10ca55b162d7e3254cb088b323a34c41b /components/vendor-data/vendor-data-container.tsx | |
initial commit
Diffstat (limited to 'components/vendor-data/vendor-data-container.tsx')
| -rw-r--r-- | components/vendor-data/vendor-data-container.tsx | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/components/vendor-data/vendor-data-container.tsx b/components/vendor-data/vendor-data-container.tsx new file mode 100644 index 00000000..69c22b79 --- /dev/null +++ b/components/vendor-data/vendor-data-container.tsx @@ -0,0 +1,231 @@ +"use client" + +import * as React from "react" +import { TooltipProvider } from "@/components/ui/tooltip" +import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable" +import { cn } from "@/lib/utils" +import { ProjectSwitcher } from "./project-swicher" +import { Sidebar } from "./sidebar" +import { useParams, usePathname, useRouter } from "next/navigation" +import { getFormsByContractItemId, type FormInfo } from "@/lib/forms/services" +import { Separator } from "@/components/ui/separator" + +interface PackageData { + itemId: number + itemName: string +} + +interface ContractData { + contractId: number + contractName: string + packages: PackageData[] +} + +interface ProjectData { + projectId: number + projectCode: string + projectName: string + contracts: ContractData[] +} + +interface VendorDataContainerProps { + projects: ProjectData[] + defaultLayout?: number[] + defaultCollapsed?: boolean + navCollapsedSize: number + children: React.ReactNode +} + +function getTagIdFromPathname(path: string): number | null { + // 태그 패턴 검사 (/tag/123) + const tagMatch = path.match(/\/tag\/(\d+)/) + if (tagMatch) return parseInt(tagMatch[1], 10) + + // 폼 패턴 검사 (/form/123/...) + const formMatch = path.match(/\/form\/(\d+)/) + if (formMatch) return parseInt(formMatch[1], 10) + + return null +} +export function VendorDataContainer({ + projects, + defaultLayout = [20, 80], + defaultCollapsed = false, + navCollapsedSize, + children +}: VendorDataContainerProps) { + const pathname = usePathname() + const router = useRouter() + const tagIdNumber = getTagIdFromPathname(pathname) + + const [isCollapsed, setIsCollapsed] = React.useState(defaultCollapsed) + // 폼 로드 요청 추적 + const lastRequestIdRef = React.useRef(0) + + // 기본 상태 + const [selectedProjectId, setSelectedProjectId] = React.useState(projects[0]?.projectId || 0) + const [selectedContractId, setSelectedContractId] = React.useState( + projects[0]?.contracts[0]?.contractId || 0 + ) + // URL에서 들어온 tagIdNumber를 우선으로 설정하기 위해 초기에 null로 두고, 뒤에서 useEffect로 세팅 + const [selectedPackageId, setSelectedPackageId] = React.useState<number | null>(null) + + const [formList, setFormList] = React.useState<FormInfo[]>([]) + const [selectedFormCode, setSelectedFormCode] = React.useState<string | null>(null) + const [isLoadingForms, setIsLoadingForms] = React.useState(false) + + // 현재 선택된 프로젝트/계약/패키지 + const currentProject = projects.find((p) => p.projectId === selectedProjectId) ?? projects[0] + const currentContract = currentProject?.contracts.find((c) => c.contractId === selectedContractId) + ?? currentProject?.contracts[0] + + const isTagOrFormRoute = pathname.includes("/tag/") || pathname.includes("/form/") + const currentPackageName = isTagOrFormRoute + ? currentContract?.packages.find((pkg) => pkg.itemId === selectedPackageId)?.itemName || "None" + : "None" + + // 폼 목록에서 고유한 폼 이름만 추출 + const formNames = React.useMemo(() => { + return [...new Set(formList.map((form) => form.formName))] + }, [formList]) + + // (1) 새로고침 시 URL 파라미터(tagIdNumber) → selectedPackageId 세팅 + // URL에 tagIdNumber가 있으면 그걸 우선으로, 아니면 기존 로직대로 '첫 번째 패키지' 사용 + React.useEffect(() => { + if (!currentContract) return + + if (tagIdNumber) { + setSelectedPackageId(tagIdNumber) + } else { + // tagIdNumber가 없으면, 현재 계약의 첫 번째 패키지로 + if (currentContract.packages?.length) { + setSelectedPackageId(currentContract.packages[0].itemId) + } else { + setSelectedPackageId(null) + } + } + }, [tagIdNumber, currentContract]) + + // (2) 프로젝트 변경 시 계약 초기화 + React.useEffect(() => { + if (currentProject?.contracts.length) { + setSelectedContractId(currentProject.contracts[0].contractId) + } else { + setSelectedContractId(0) + } + }, [currentProject]) + + // // (3) selectedPackageId 바뀔 때 URL도 같이 업데이트 + // React.useEffect(() => { + // if (!selectedPackageId) return + // const basePath = pathname.includes("/partners/") + // ? "/partners/vendor-data/tag/" + // : "/vendor-data/tag/" + + // router.push(`${basePath}${selectedPackageId}`) + // }, [selectedPackageId, router, pathname]) + + // (4) 패키지 ID가 정해질 때마다 폼 로딩 + React.useEffect(() => { + const packageId = getTagIdFromPathname(pathname) + + if (packageId) { + setSelectedPackageId(packageId) + + // URL에서 패키지 ID를 얻었을 때 즉시 폼 로드 + loadFormsList(packageId); + } else if (currentContract?.packages?.length) { + setSelectedPackageId(currentContract.packages[0].itemId) + } + }, [pathname, currentContract]) + + // 폼 로드 함수를 컴포넌트 내부에 정의하고 재사용 + const loadFormsList = async (packageId: number) => { + if (!packageId) return; + + setIsLoadingForms(true); + try { + const result = await getFormsByContractItemId(packageId); + setFormList(result.forms || []); + } catch (error) { + console.error("폼 로딩 오류:", error); + setFormList([]); + } finally { + setIsLoadingForms(false); + } + }; + // 핸들러들 + function handleSelectContract(projId: number, cId: number) { + setSelectedProjectId(projId) + setSelectedContractId(cId) + } + + function handleSelectPackage(itemId: number) { + setSelectedPackageId(itemId) + } + + function handleSelectForm(formName: string) { + const form = formList.find((f) => f.formName === formName) + if (form) { + setSelectedFormCode(form.formCode) + } + } + + return ( + <TooltipProvider delayDuration={0}> + <ResizablePanelGroup direction="horizontal" className="h-full"> + <ResizablePanel + defaultSize={defaultLayout[0]} + collapsedSize={navCollapsedSize} + collapsible + minSize={15} + maxSize={25} + onCollapse={() => setIsCollapsed(true)} + onResize={() => setIsCollapsed(false)} + className={cn(isCollapsed && "min-w-[50px] transition-all duration-300 ease-in-out")} + > + <div + className={cn( + "flex h-[52px] items-center justify-center gap-2", + isCollapsed ? "h-[52px]" : "px-2" + )} + > + <ProjectSwitcher + isCollapsed={isCollapsed} + projects={projects} + selectedContractId={selectedContractId} + onSelectContract={handleSelectContract} + /> + </div> + <Separator /> + <Sidebar + isCollapsed={isCollapsed} + packages={currentContract?.packages || []} + selectedPackageId={selectedPackageId} + onSelectPackage={handleSelectPackage} + forms={formList} + selectedForm={ + selectedFormCode + ? formList.find((f) => f.formCode === selectedFormCode)?.formName || null + : null + } + onSelectForm={handleSelectForm} + isLoadingForms={isLoadingForms} + className="hidden lg:block" + /> + </ResizablePanel> + + <ResizableHandle withHandle /> + + <ResizablePanel defaultSize={defaultLayout[1]} minSize={40}> + <div className="p-4 h-full overflow-auto flex flex-col"> + <div className="flex items-center justify-between mb-4"> + <h2 className="text-lg font-bold">Package: {currentPackageName}</h2> + </div> + {children} + </div> + </ResizablePanel> + </ResizablePanelGroup> + </TooltipProvider> + ) +}
\ No newline at end of file |
