diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-08-14 11:54:47 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-08-14 11:54:47 +0000 |
| commit | 969c25b56f6d29d7ffa4bc2ce04c5fb4e5846b34 (patch) | |
| tree | 551d335e850e6163792ded0e7a75fa41d96d612a /components/vendor-data/vendor-data-container copy.tsx | |
| parent | dd20ba9785cdbd3d61f6b014d003d3bd9646ad13 (diff) | |
(대표님) 정규벤더등록, 벤더문서관리, 벤더데이터입력, 첨부파일관리
Diffstat (limited to 'components/vendor-data/vendor-data-container copy.tsx')
| -rw-r--r-- | components/vendor-data/vendor-data-container copy.tsx | 464 |
1 files changed, 464 insertions, 0 deletions
diff --git a/components/vendor-data/vendor-data-container copy.tsx b/components/vendor-data/vendor-data-container copy.tsx new file mode 100644 index 00000000..5b4967b5 --- /dev/null +++ b/components/vendor-data/vendor-data-container copy.tsx @@ -0,0 +1,464 @@ +"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 { usePathname, useRouter, useSearchParams, useParams } from "next/navigation" +import { getFormsByContractItemId, type FormInfo } from "@/lib/forms/services" +import { Separator } from "@/components/ui/separator" +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs" +import { ScrollArea } from "@/components/ui/scroll-area" +import { Button } from "@/components/ui/button" +import { FormInput } from "lucide-react" +import { Skeleton } from "@/components/ui/skeleton" +import { selectedModeAtom } from '@/atoms' +import { useAtom } from 'jotai' + +interface PackageData { + itemId: number + itemName: string +} + +interface ContractData { + contractId: number + contractName: string + packages: PackageData[] +} + +interface ProjectData { + projectId: number + projectCode: string + projectName: string + projectType: string + contracts: ContractData[] +} + +interface VendorDataContainerProps { + projects: ProjectData[] + defaultLayout?: number[] + defaultCollapsed?: boolean + navCollapsedSize: number + children: React.ReactNode +} + +function getTagIdFromPathname(path: string | null): number | null { + if (!path) return 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 searchParams = useSearchParams() + const params = useParams() + const currentLng = params?.lng as string || 'en' + + const tagIdNumber = getTagIdFromPathname(pathname) + + // 기본 상태 + const [selectedProjectId, setSelectedProjectId] = React.useState(projects[0]?.projectId || 0) + const [isCollapsed, setIsCollapsed] = React.useState(defaultCollapsed) + const [selectedContractId, setSelectedContractId] = React.useState( + projects[0]?.contracts[0]?.contractId || 0 + ) + 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] + + // 프로젝트 타입 확인 - ship인 경우 항상 ENG 모드 + const isShipProject = currentProject?.projectType === "ship" + + const [selectedMode, setSelectedMode] = useAtom(selectedModeAtom) + + // URL에서 모드 추출 (ship 프로젝트면 무조건 ENG로, 아니면 URL 또는 기본값) + const modeFromUrl = searchParams?.get('mode') + const initialMode = isShipProject + ? "ENG" + : (modeFromUrl === "ENG" || modeFromUrl === "IM") ? modeFromUrl : "IM" + + // 모드 초기화 (기존의 useState 초기값 대신) + React.useEffect(() => { + setSelectedMode(initialMode as "IM" | "ENG") + }, [initialMode, setSelectedMode]) + + const isTagOrFormRoute = pathname ? (pathname.includes("/tag/") || pathname.includes("/form/")) : false + 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]) + + // URL에서 현재 폼 코드 추출 + const getCurrentFormCode = (path: string): string | null => { + const segments = path.split("/").filter(Boolean) + const formIndex = segments.indexOf("form") + if (formIndex !== -1 && segments[formIndex + 2]) { + return segments[formIndex + 2] + } + return null + } + + const currentFormCode = React.useMemo(() => { + return pathname ? getCurrentFormCode(pathname) : null + }, [pathname]) + + const createUrlWithLng = (path: string, queryParams?: Record<string, string>) => { + // 기본 경로에 언어 파라미터 포함 + const basePath = `/${currentLng}${path.startsWith('/') ? path : '/' + path}` + + if (queryParams) { + const searchParams = new URLSearchParams(queryParams) + return `${basePath}?${searchParams.toString()}` + } + + return basePath + } + + // URL에서 모드가 변경되면 상태도 업데이트 (ship 프로젝트가 아닐 때만) + React.useEffect(() => { + if (!isShipProject) { + const modeFromUrl = searchParams?.get('mode') + if (modeFromUrl === "ENG" || modeFromUrl === "IM") { + setSelectedMode(modeFromUrl) + } + } + }, [searchParams, isShipProject]) + + // 프로젝트 타입이 변경될 때 모드 업데이트 + React.useEffect(() => { + if (isShipProject) { + setSelectedMode("ENG") + + // URL 모드 파라미터도 업데이트 + const url = new URL(window.location.href); + url.searchParams.set('mode', 'ENG'); + router.replace(url.pathname + url.search); + } + }, [isShipProject, router]) + + // (1) 새로고침 시 URL 파라미터(tagIdNumber) → selectedPackageId 세팅 + 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) 패키지 ID와 모드가 변경될 때마다 폼 로딩 + React.useEffect(() => { + const packageId = getTagIdFromPathname(pathname) + + if (packageId) { + setSelectedPackageId(packageId) + + // URL에서 패키지 ID를 얻었을 때 즉시 폼 로드 + loadFormsList(packageId, selectedMode); + } else if (currentContract?.packages?.length) { + const firstPackageId = currentContract.packages[0].itemId; + setSelectedPackageId(firstPackageId); + loadFormsList(firstPackageId, selectedMode); + } + }, [pathname, currentContract, selectedMode]) + + // 모드에 따른 폼 로드 함수 + const loadFormsList = async (packageId: number, mode: "IM" | "ENG") => { + if (!packageId) return; + + setIsLoadingForms(true); + try { + const result = await getFormsByContractItemId(packageId, mode); + setFormList(result.forms || []); + } catch (error) { + console.error(`폼 로딩 오류 (${mode} 모드):`, 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) + } + } + + // 모드 변경 핸들러 +// 모드 변경 핸들러 +const handleModeChange = async (mode: "IM" | "ENG") => { + // ship 프로젝트인 경우 모드 변경 금지 + if (isShipProject && mode !== "ENG") return; + + setSelectedMode(mode); + + // 모드가 변경될 때 자동 네비게이션 + if (currentContract?.packages?.length) { + const firstPackageId = currentContract.packages[0].itemId; + + if (mode === "IM") { + // IM 모드: 첫 번째 패키지로 이동 + const baseSegments = pathname?.split("/").filter(Boolean).slice(0, pathname.split("/").filter(Boolean).indexOf("vendor-data") + 1).join("/"); + router.push(`/${baseSegments}/tag/${firstPackageId}?mode=${mode}`); + } else { + // ENG 모드: 폼 목록을 먼저 로드 + setIsLoadingForms(true); + try { + const result = await getFormsByContractItemId(firstPackageId, mode); + setFormList(result.forms || []); + + // 폼이 있으면 첫 번째 폼으로 이동 + if (result.forms && result.forms.length > 0) { + const firstForm = result.forms[0]; + setSelectedFormCode(firstForm.formCode); + + const baseSegments = pathname?.split("/").filter(Boolean).slice(0, pathname.split("/").filter(Boolean).indexOf("vendor-data") + 1).join("/"); + router.push(`/${baseSegments}/form/0/${firstForm.formCode}/${selectedProjectId}/${selectedContractId}?mode=${mode}`); + } else { + // 폼이 없으면 모드만 변경 + const baseSegments = pathname?.split("/").filter(Boolean).slice(0, pathname.split("/").filter(Boolean).indexOf("vendor-data") + 1).join("/"); + router.push(`/${baseSegments}/form/0/0/${selectedProjectId}/${selectedContractId}?mode=${mode}`); + } + } catch (error) { + console.error(`폼 로딩 오류 (${mode} 모드):`, error); + // 오류 발생 시 모드만 변경 + const url = new URL(window.location.href); + url.searchParams.set('mode', mode); + router.replace(url.pathname + url.search); + } finally { + setIsLoadingForms(false); + } + } + } else { + // 패키지가 없는 경우, 모드만 변경 + const url = new URL(window.location.href); + url.searchParams.set('mode', mode); + router.replace(url.pathname + url.search); + } +}; + + 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 /> + + {!isCollapsed ? ( + isShipProject ? ( + // 프로젝트 타입이 ship인 경우: 탭 없이 ENG 모드 사이드바만 바로 표시 + <div className="mt-0"> + <Sidebar + isCollapsed={isCollapsed} + packages={currentContract?.packages || []} + selectedPackageId={selectedPackageId} + selectedProjectId={selectedProjectId} + selectedContractId={selectedContractId} + onSelectPackage={handleSelectPackage} + forms={formList} + selectedForm={ + selectedFormCode + ? formList.find((f) => f.formCode === selectedFormCode)?.formName || null + : null + } + onSelectForm={handleSelectForm} + isLoadingForms={isLoadingForms} + mode="ENG" + className="hidden lg:block" + /> + </div> + ) : ( + // 프로젝트 타입이 ship이 아닌 경우: 기존 탭 UI 표시 + <Tabs + defaultValue={initialMode} + value={selectedMode} + onValueChange={(value) => handleModeChange(value as "IM" | "ENG")} + className="w-full" + > + <TabsList className="w-full"> + <TabsTrigger value="IM" className="flex-1">H/O</TabsTrigger> + <TabsTrigger value="ENG" className="flex-1">ENG</TabsTrigger> + </TabsList> + + <TabsContent value="IM" className="mt-0"> + <Sidebar + isCollapsed={isCollapsed} + packages={currentContract?.packages || []} + selectedPackageId={selectedPackageId} + selectedContractId={selectedContractId} + selectedProjectId={selectedProjectId} + onSelectPackage={handleSelectPackage} + forms={formList} + selectedForm={ + selectedFormCode + ? formList.find((f) => f.formCode === selectedFormCode)?.formName || null + : null + } + onSelectForm={handleSelectForm} + isLoadingForms={isLoadingForms} + mode="IM" + className="hidden lg:block" + /> + </TabsContent> + + <TabsContent value="ENG" className="mt-0"> + <Sidebar + isCollapsed={isCollapsed} + packages={currentContract?.packages || []} + selectedPackageId={selectedPackageId} + selectedContractId={selectedContractId} + selectedProjectId={selectedProjectId} + onSelectPackage={handleSelectPackage} + forms={formList} + selectedForm={ + selectedFormCode + ? formList.find((f) => f.formCode === selectedFormCode)?.formName || null + : null + } + onSelectForm={handleSelectForm} + isLoadingForms={isLoadingForms} + mode="ENG" + className="hidden lg:block" + /> + </TabsContent> + </Tabs> + ) + ) : ( + // 접혀있을 때 UI + <> + {!isShipProject && ( + // ship 프로젝트가 아닐 때만 모드 선택 버튼 표시 + <div className="flex justify-center space-x-1 my-2"> + <Button + variant={selectedMode === "IM" ? "default" : "ghost"} + size="sm" + className="h-8 px-2" + onClick={() => handleModeChange("IM")} + > + H/O + </Button> + <Button + variant={selectedMode === "ENG" ? "default" : "ghost"} + size="sm" + className="h-8 px-2" + onClick={() => handleModeChange("ENG")} + > + ENG + </Button> + </div> + )} + + <Sidebar + isCollapsed={isCollapsed} + packages={currentContract?.packages || []} + selectedPackageId={selectedPackageId} + selectedProjectId={selectedProjectId} + selectedContractId={selectedContractId} + onSelectPackage={handleSelectPackage} + forms={formList} + selectedForm={ + selectedFormCode + ? formList.find((f) => f.formCode === selectedFormCode)?.formName || null + : null + } + onSelectForm={handleSelectForm} + isLoadingForms={isLoadingForms} + mode={isShipProject ? "ENG" : selectedMode} + 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"> + {isShipProject || selectedMode === "ENG" + ? "Engineering Mode" + : `Package: ${currentPackageName}`} + </h2> + </div> + {children} + </div> + </ResizablePanel> + </ResizablePanelGroup> + </TooltipProvider> + ) +}
\ No newline at end of file |
