diff options
Diffstat (limited to 'components/vendor-data-plant')
| -rw-r--r-- | components/vendor-data-plant/project-swicher.tsx | 163 | ||||
| -rw-r--r-- | components/vendor-data-plant/sidebar.tsx | 479 | ||||
| -rw-r--r-- | components/vendor-data-plant/vendor-data-container.tsx | 523 |
3 files changed, 402 insertions, 763 deletions
diff --git a/components/vendor-data-plant/project-swicher.tsx b/components/vendor-data-plant/project-swicher.tsx index d3123709..9b8f9bea 100644 --- a/components/vendor-data-plant/project-swicher.tsx +++ b/components/vendor-data-plant/project-swicher.tsx @@ -1,6 +1,7 @@ "use client" import * as React from "react" +import { Check, ChevronsUpDown } from "lucide-react" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" import { @@ -16,149 +17,103 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover" -import { Check, ChevronsUpDown, Loader2 } from "lucide-react" -interface ContractInfo { - contractId: number - contractName: string +interface PackageData { + packageCode: string + packageName: string | null } -interface ProjectInfo { +interface ProjectData { projectId: number projectCode: string projectName: string - contracts: ContractInfo[] + projectType: string + packages: PackageData[] } interface ProjectSwitcherProps { isCollapsed: boolean - projects: ProjectInfo[] - - // 상위가 관리하는 "현재 선택된 contractId" - selectedContractId: number | null - - // 콜백: 사용자가 "어떤 contract"를 골랐는지 - // => 우리가 projectId도 찾아서 상위 state를 같이 갱신해야 함 - onSelectContract: (projectId: number, contractId: number) => void - - // 로딩 상태 (선택사항) - isLoading?: boolean + projects: ProjectData[] + selectedProjectId: number + selectedPackageCode: string | null + onSelectPackage: (projectId: number, packageCode: string) => void } export function ProjectSwitcher({ isCollapsed, projects, - selectedContractId, - onSelectContract, - isLoading = false, + selectedProjectId, + selectedPackageCode, + onSelectPackage, }: ProjectSwitcherProps) { - const [popoverOpen, setPopoverOpen] = React.useState(false) - const [searchTerm, setSearchTerm] = React.useState("") + const [open, setOpen] = React.useState(false) - // 현재 선택된 contract 객체 찾기 - const selectedContract = React.useMemo(() => { - if (!selectedContractId) return null - for (const proj of projects) { - const found = proj.contracts.find((c) => c.contractId === selectedContractId) - if (found) { - return { ...found, projectId: proj.projectId, projectName: proj.projectName } - } - } - return null - }, [projects, selectedContractId]) - - // Trigger label => 계약 이름 or placeholder - const triggerLabel = selectedContract?.contractName ?? "Select a contract" + // 현재 선택된 프로젝트와 패키지 정보 + const selectedProject = projects.find(p => p.projectId === selectedProjectId) + const selectedPackage = selectedProject?.packages.find( + pkg => pkg.packageCode === selectedPackageCode + ) - // 검색어에 따른 필터링된 프로젝트/계약 목록 - const filteredProjects = React.useMemo(() => { - if (!searchTerm) return projects - - return projects.map(project => ({ - ...project, - contracts: project.contracts.filter(contract => - contract.contractName.toLowerCase().includes(searchTerm.toLowerCase()) || - project.projectName.toLowerCase().includes(searchTerm.toLowerCase()) - ) - })).filter(project => project.contracts.length > 0) - }, [projects, searchTerm]) - // 계약 선택 핸들러 - function handleSelectContract(projectId: number, contractId: number) { - onSelectContract(projectId, contractId) - setPopoverOpen(false) - setSearchTerm("") // 검색어 초기화 - } + console.log(projects,"projects") - // 총 계약 수 계산 (빈 상태 표시용) - const totalContracts = filteredProjects.reduce((sum, project) => sum + project.contracts.length, 0) + const displayText = selectedPackage + ? `${selectedProject?.projectCode} - ${selectedPackage.packageCode}` + : selectedProject?.projectCode || "Select Package" return ( - <Popover open={popoverOpen} onOpenChange={setPopoverOpen}> + <Popover open={open} onOpenChange={setOpen}> <PopoverTrigger asChild> <Button - type="button" variant="outline" - className={cn( - "justify-between relative", - isCollapsed ? "h-9 w-9 shrink-0 items-center justify-center p-0" : "w-full h-9" - )} - disabled={isLoading} - aria-label="Select Contract" + role="combobox" + aria-expanded={open} + aria-label="Select a package" + className={cn("w-full justify-between", isCollapsed && "w-[50px]")} > - {isLoading ? ( - <> - <span className={cn(isCollapsed && "hidden")}>Loading...</span> - <Loader2 className={cn("h-4 w-4 animate-spin", !isCollapsed && "ml-2")} /> - </> + {isCollapsed ? ( + <ChevronsUpDown className="h-4 w-4" /> ) : ( <> - <span className={cn("truncate flex-grow text-left", isCollapsed && "hidden")}> - {triggerLabel} - </span> - <ChevronsUpDown className={cn("h-4 w-4 opacity-50 flex-shrink-0", isCollapsed && "hidden")} /> + <span className="truncate">{displayText}</span> + <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> </> )} </Button> </PopoverTrigger> - - <PopoverContent className="w-[320px] p-0" align="start"> + <PopoverContent className="w-[300px] p-0"> <Command> - <CommandInput - placeholder="Search contracts..." - value={searchTerm} - onValueChange={setSearchTerm} - /> - - <CommandList - className="max-h-[320px]" - onWheel={(e) => { - e.stopPropagation() // 이벤트 전파 차단 - const target = e.currentTarget - target.scrollTop += e.deltaY // 직접 스크롤 처리 - }} - > - <CommandEmpty> - {totalContracts === 0 ? "No contracts found." : "No search results."} - </CommandEmpty> - - {filteredProjects.map((project) => ( - <CommandGroup key={project.projectCode} heading={project.projectName}> - {project.contracts.map((contract) => ( + <CommandInput placeholder="Search package..." /> + <CommandList> + <CommandEmpty>No package found.</CommandEmpty> + {projects.map((project) => ( + <CommandGroup key={project.projectId} heading={project.projectName}> + {project.packages.map((pkg) => ( <CommandItem - key={contract.contractId} - onSelect={() => handleSelectContract(project.projectId, contract.contractId)} - value={`${project.projectName} ${contract.contractName}`} - className="truncate" - title={contract.contractName} + key={`${project.projectId}-${pkg.packageCode}`} + onSelect={() => { + onSelectPackage(project.projectId, pkg.packageCode) + setOpen(false) + }} + className="text-sm" > - <span className="truncate">{contract.contractName}</span> <Check className={cn( - "ml-auto h-4 w-4 flex-shrink-0", - selectedContractId === contract.contractId ? "opacity-100" : "opacity-0" + "mr-2 h-4 w-4", + selectedProjectId === project.projectId && + selectedPackageCode === pkg.packageCode + ? "opacity-100" + : "opacity-0" )} /> + <div className="flex flex-col"> + <span className="font-medium">{pkg.packageCode}</span> + {pkg.packageName && ( + <span className="text-xs text-muted-foreground"> + {pkg.packageName} + </span> + )} + </div> </CommandItem> ))} </CommandGroup> diff --git a/components/vendor-data-plant/sidebar.tsx b/components/vendor-data-plant/sidebar.tsx index 31ee6dc7..b746e69d 100644 --- a/components/vendor-data-plant/sidebar.tsx +++ b/components/vendor-data-plant/sidebar.tsx @@ -10,304 +10,265 @@ import { TooltipTrigger, TooltipContent, } from "@/components/ui/tooltip" -import { Package2, FormInput } from "lucide-react" -import { useRouter, usePathname } from "next/navigation" +import { List, FormInput, FileText } from "lucide-react" import { Skeleton } from "@/components/ui/skeleton" -import { type FormInfo } from "@/lib/forms/services" +import { getEngineeringForms, getIMForms } from "@/lib/tags-plant/service" -interface PackageData { - itemId: number - itemName: string +interface FormInfo { + formCode: string + formName: string } interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> { isCollapsed: boolean - packages: PackageData[] - selectedPackageId: number | null - selectedProjectId: number | null - selectedContractId: number | null - onSelectPackage: (itemId: number) => void - forms?: FormInfo[] - onSelectForm: (formName: string) => void - isLoadingForms?: boolean - mode: "IM" | "ENG" + selectedPackageCode: string | null + selectedFormCode: string | null + currentMode: "master" | "engineering" | "im" | null + projectCode: string // 추가 + onMasterTagListClick: () => void + onEngineeringFormClick: (formCode: string) => void + onIMFormClick: (formCode: string) => void } export function Sidebar({ className, isCollapsed, - packages, - selectedPackageId, - selectedProjectId, - selectedContractId, - onSelectPackage, - forms, - onSelectForm, - isLoadingForms = false, - mode = "IM", + selectedPackageCode, + selectedFormCode, + currentMode, + projectCode, // 추가 + onMasterTagListClick, + onEngineeringFormClick, + onIMFormClick, }: SidebarProps) { - const router = useRouter() - const rawPathname = usePathname() - const pathname = rawPathname ?? "" + const [engineeringForms, setEngineeringForms] = React.useState<FormInfo[]>([]) + const [imForms, setIMForms] = React.useState<FormInfo[]>([]) + const [isLoadingEngineering, setIsLoadingEngineering] = React.useState(false) + const [isLoadingIM, setIsLoadingIM] = React.useState(false) - /** - * --------------------------- - * 1) URL에서 현재 패키지 / 폼 코드 추출 - * --------------------------- - */ - const segments = pathname.split("/").filter(Boolean) - - let currentItemId: number | null = null - let currentFormCode: string | null = null + // Engineering 폼 로드 + React.useEffect(() => { + if (!selectedPackageCode || !projectCode) { + setEngineeringForms([]) + return + } - const tagIndex = segments.indexOf("tag") - if (tagIndex !== -1 && segments[tagIndex + 1]) { - currentItemId = parseInt(segments[tagIndex + 1], 10) - } + const loadEngineeringForms = async () => { + setIsLoadingEngineering(true) + try { + const result = await getEngineeringForms(projectCode, selectedPackageCode) + setEngineeringForms(result) + } catch (error) { + console.error("Engineering 폼 로딩 오류:", error) + setEngineeringForms([]) + } finally { + setIsLoadingEngineering(false) + } + } - const formIndex = segments.indexOf("form") - if (formIndex !== -1) { - const itemSegment = segments[formIndex + 1] - const codeSegment = segments[formIndex + 2] + loadEngineeringForms() + }, [selectedPackageCode, projectCode]) - if (itemSegment) { - currentItemId = parseInt(itemSegment, 10) - } - if (codeSegment) { - currentFormCode = codeSegment + // IM 폼 로드 + React.useEffect(() => { + if (!selectedPackageCode || !projectCode) { + setIMForms([]) + return } - } - /** - * --------------------------- - * 2) 패키지 클릭 핸들러 (IM 모드) - * --------------------------- - */ - const handlePackageClick = (itemId: number) => { - // 상위 컴포넌트 상태 업데이트 - onSelectPackage(itemId) - - // 해당 태그 페이지로 라우팅 - // 예: /vendor-data-plant/tag/123 - const baseSegments = segments.slice(0, segments.indexOf("vendor-data-plant") + 1).join("/") - router.push(`/${baseSegments}/tag/${itemId}`) - } + const loadIMForms = async () => { + setIsLoadingIM(true) + try { + const result = await getIMForms(projectCode, selectedPackageCode) + setIMForms(result) + } catch (error) { + console.error("IM 폼 로딩 오류:", error) + setIMForms([]) + } finally { + setIsLoadingIM(false) + } + } - /** - * --------------------------- - * 3) 폼 클릭 핸들러 (IM 모드만 사용) - * --------------------------- - */ - const handleFormClick = (form: FormInfo) => { - // IM 모드에서만 사용 - if (selectedPackageId === null) return; - - onSelectForm(form.formName) - - const baseSegments = segments.slice(0, segments.indexOf("vendor-data-plant") + 1).join("/") - router.push(`/${baseSegments}/form/${selectedPackageId}/${form.formCode}/${selectedProjectId}/${selectedContractId}?mode=${mode}`) - } + loadIMForms() + }, [selectedPackageCode, projectCode]) - /** - * --------------------------- - * 4) 패키지 클릭 핸들러 (ENG 모드) - * --------------------------- - */ - const handlePackageUnderFormClick = (form: FormInfo, pkg: PackageData) => { - onSelectForm(form.formName) - onSelectPackage(pkg.itemId) - - const baseSegments = segments.slice(0, segments.indexOf("vendor-data-plant") + 1).join("/") - router.push(`/${baseSegments}/form/${pkg.itemId}/${form.formCode}/${selectedProjectId}/${selectedContractId}?mode=${mode}`) - } + const isMasterActive = currentMode === "master" + const isPackageSelected = selectedPackageCode !== null return ( <div className={cn("pb-12", className)}> <div className="space-y-4 py-4"> - {/* ---------- 패키지(Items) 목록 - IM 모드에서만 표시 ---------- */} - {mode === "IM" && ( - <> - <div className="py-1"> - <h2 className="relative px-7 text-lg font-semibold tracking-tight"> - {isCollapsed ? "P" : "Package Lists"} - </h2> - <ScrollArea className="h-[150px] px-1"> - <div className="space-y-1 p-2"> - {packages.map((pkg) => { - const isActive = pkg.itemId === currentItemId + {/* Master Tag List */} + <div className="py-1"> + <h2 className="relative px-7 text-lg font-semibold tracking-tight"> + {isCollapsed ? "M" : "Master"} + </h2> + <div className="space-y-1 p-2"> + {isCollapsed ? ( + <Tooltip delayDuration={0}> + <TooltipTrigger asChild> + <Button + variant="ghost" + className={cn( + "w-full justify-start font-normal", + isMasterActive && "bg-accent text-accent-foreground" + )} + onClick={onMasterTagListClick} + disabled={!isPackageSelected} + > + <List className="h-4 w-4" /> + </Button> + </TooltipTrigger> + <TooltipContent side="right"> + Master Tag List + </TooltipContent> + </Tooltip> + ) : ( + <Button + variant="ghost" + className={cn( + "w-full justify-start font-normal", + isMasterActive && "bg-accent text-accent-foreground" + )} + onClick={onMasterTagListClick} + disabled={!isPackageSelected} + > + <List className="mr-2 h-4 w-4" /> + Master Tag List + </Button> + )} + </div> + </div> - return ( - <div key={pkg.itemId}> - {isCollapsed ? ( - <Tooltip delayDuration={0}> - <TooltipTrigger asChild> - <Button - variant="ghost" - className={cn( - "w-full justify-start font-normal", - isActive && "bg-accent text-accent-foreground" - )} - onClick={() => handlePackageClick(pkg.itemId)} - > - <Package2 className="mr-2 h-4 w-4" /> - </Button> - </TooltipTrigger> - <TooltipContent side="right"> - {pkg.itemName} - </TooltipContent> - </Tooltip> - ) : ( - <Button - variant="ghost" - className={cn( - "w-full justify-start font-normal", - isActive && "bg-accent text-accent-foreground" - )} - onClick={() => handlePackageClick(pkg.itemId)} - > - <Package2 className="mr-2 h-4 w-4" /> - {pkg.itemName} - </Button> - )} - </div> - ) - })} - </div> - </ScrollArea> - </div> - <Separator /> - </> - )} + <Separator /> - {/* ---------- 폼 목록 (IM 모드) / 패키지와 폼 목록 (ENG 모드) ---------- */} + {/* Engineering Forms */} <div className="py-1"> <h2 className="relative px-7 text-lg font-semibold tracking-tight"> - {isCollapsed - ? (mode === "IM" ? "F" : "P") - : (mode === "IM" ? "Form Lists" : "Package Lists") - } + {isCollapsed ? "E" : "Engineering"} </h2> - <ScrollArea className={cn( - "px-1", - mode === "IM" ? "h-[300px]" : "h-[450px]" - )}> + <ScrollArea className="h-[250px] px-1"> <div className="space-y-1 p-2"> - {isLoadingForms ? ( + {isLoadingEngineering ? ( Array.from({ length: 3 }).map((_, index) => ( - <div key={`form-skeleton-${index}`} className="px-2 py-1.5"> + <div key={`eng-skeleton-${index}`} className="px-2 py-1.5"> <Skeleton className="h-8 w-full" /> </div> )) - ) : mode === "IM" ? ( - // =========== IM 모드: 폼만 표시 =========== - !forms || forms.length === 0 ? ( - <p className="text-sm text-muted-foreground px-2"> - (No forms loaded) - </p> - ) : ( - forms.map((form) => { - const isFormActive = form.formCode === currentFormCode - const isDisabled = currentItemId === null + ) : !isPackageSelected ? ( + <p className="text-sm text-muted-foreground px-2"> + Select a package first + </p> + ) : engineeringForms.length === 0 ? ( + <p className="text-sm text-muted-foreground px-2"> + No forms available + </p> + ) : ( + engineeringForms.map((form) => { + const isActive = + currentMode === "engineering" && + form.formCode === selectedFormCode - return isCollapsed ? ( - <Tooltip key={form.formCode} delayDuration={0}> - <TooltipTrigger asChild> - <Button - variant="ghost" - className={cn( - "w-full justify-start font-normal", - isFormActive && "bg-accent text-accent-foreground" - )} - onClick={() => handleFormClick(form)} - disabled={isDisabled} - > - <FormInput className="mr-2 h-4 w-4" /> - </Button> - </TooltipTrigger> - <TooltipContent side="right"> - {form.formName} - </TooltipContent> - </Tooltip> - ) : ( - <Button - key={form.formCode} - variant="ghost" - className={cn( - "w-full justify-start font-normal", - isFormActive && "bg-accent text-accent-foreground" - )} - onClick={() => handleFormClick(form)} - disabled={isDisabled} - > - <FormInput className="mr-2 h-4 w-4" /> + return isCollapsed ? ( + <Tooltip key={form.formCode} delayDuration={0}> + <TooltipTrigger asChild> + <Button + variant="ghost" + className={cn( + "w-full justify-start font-normal", + isActive && "bg-accent text-accent-foreground" + )} + onClick={() => onEngineeringFormClick(form.formCode)} + > + <FormInput className="h-4 w-4" /> + </Button> + </TooltipTrigger> + <TooltipContent side="right"> {form.formName} - </Button> - ) - }) - ) + </TooltipContent> + </Tooltip> + ) : ( + <Button + key={form.formCode} + variant="ghost" + className={cn( + "w-full justify-start font-normal", + isActive && "bg-accent text-accent-foreground" + )} + onClick={() => onEngineeringFormClick(form.formCode)} + > + <FormInput className="mr-2 h-4 w-4" /> + {form.formName} + </Button> + ) + }) + )} + </div> + </ScrollArea> + </div> + + <Separator /> + + {/* IM Forms */} + <div className="py-1"> + <h2 className="relative px-7 text-lg font-semibold tracking-tight"> + {isCollapsed ? "I" : "IM"} + </h2> + <ScrollArea className="h-[250px] px-1"> + <div className="space-y-1 p-2"> + {isLoadingIM ? ( + Array.from({ length: 3 }).map((_, index) => ( + <div key={`im-skeleton-${index}`} className="px-2 py-1.5"> + <Skeleton className="h-8 w-full" /> + </div> + )) + ) : !isPackageSelected ? ( + <p className="text-sm text-muted-foreground px-2"> + Select a package first + </p> + ) : imForms.length === 0 ? ( + <p className="text-sm text-muted-foreground px-2"> + No forms available + </p> ) : ( - // =========== ENG 모드: 패키지 > 폼 계층 구조 =========== - packages.length === 0 ? ( - <p className="text-sm text-muted-foreground px-2"> - (No packages loaded) - </p> - ) : ( - packages.map((pkg) => ( - <div key={pkg.itemId} className="space-y-1"> - {isCollapsed ? ( - <Tooltip delayDuration={0}> - <TooltipTrigger asChild> - <div className="px-2 py-1"> - <Package2 className="h-4 w-4" /> - </div> - </TooltipTrigger> - <TooltipContent side="right"> - {pkg.itemName} - </TooltipContent> - </Tooltip> - ) : ( - <> - {/* 패키지 이름 (클릭 불가능한 라벨) */} - <div className="flex items-center px-2 py-1 text-sm font-medium"> - <Package2 className="mr-2 h-4 w-4" /> - {pkg.itemName} - </div> - - {/* 폼 목록 바로 표시 */} - <div className="ml-6 space-y-1"> - {!forms || forms.length === 0 ? ( - <p className="text-xs text-muted-foreground px-2 py-1"> - No forms available - </p> - ) : ( - forms.map((form) => { - const isFormPackageActive = - pkg.itemId === currentItemId && - form.formCode === currentFormCode + imForms.map((form) => { + const isActive = + currentMode === "im" && + form.formCode === selectedFormCode - return ( - <Button - key={`${pkg.itemId}-${form.formCode}`} - variant="ghost" - size="sm" - className={cn( - "w-full justify-start font-normal text-sm", - isFormPackageActive && "bg-accent text-accent-foreground" - )} - onClick={() => handlePackageUnderFormClick(form, pkg)} - > - <FormInput className="mr-2 h-3 w-3" /> - {form.formName} - </Button> - ) - }) - )} - </div> - </> + return isCollapsed ? ( + <Tooltip key={form.formCode} delayDuration={0}> + <TooltipTrigger asChild> + <Button + variant="ghost" + className={cn( + "w-full justify-start font-normal", + isActive && "bg-accent text-accent-foreground" + )} + onClick={() => onIMFormClick(form.formCode)} + > + <FileText className="h-4 w-4" /> + </Button> + </TooltipTrigger> + <TooltipContent side="right"> + {form.formName} + </TooltipContent> + </Tooltip> + ) : ( + <Button + key={form.formCode} + variant="ghost" + className={cn( + "w-full justify-start font-normal", + isActive && "bg-accent text-accent-foreground" )} - </div> - )) - ) + onClick={() => onIMFormClick(form.formCode)} + > + <FileText className="mr-2 h-4 w-4" /> + {form.formName} + </Button> + ) + }) )} </div> </ScrollArea> diff --git a/components/vendor-data-plant/vendor-data-container.tsx b/components/vendor-data-plant/vendor-data-container.tsx index 60ec2c94..7ce831df 100644 --- a/components/vendor-data-plant/vendor-data-container.tsx +++ b/components/vendor-data-plant/vendor-data-container.tsx @@ -4,28 +4,14 @@ 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 } from "next/navigation" -import { getFormsByContractItemId, type FormInfo } from "@/lib/forms/services" +import { usePathname, useRouter } from "next/navigation" 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' +import { ProjectSwitcher } from "./project-swicher" interface PackageData { - itemId: number - itemName: string -} - -interface ContractData { - contractId: number - contractName: string - packages: PackageData[] + packageCode: string + packageName: string | null } interface ProjectData { @@ -33,7 +19,7 @@ interface ProjectData { projectCode: string projectName: string projectType: string - contracts: ContractData[] + packages: PackageData[] } interface VendorDataContainerProps { @@ -44,18 +30,39 @@ interface VendorDataContainerProps { children: React.ReactNode } -function getTagIdFromPathname(path: string | null): number | null { - if (!path) return null; +function getInfoFromPathname(path: string | null): { + projectCode: string | null + packageCode: string | null + formCode: string | null + mode: "master" | "engineering" | "im" | null +} { + if (!path) return { projectCode: null, packageCode: null, formCode: null, mode: null } + + const segments = path.split("/").filter(Boolean) + const vendorDataIndex = segments.indexOf("vendor-data-plant") + + if (vendorDataIndex === -1) { + return { projectCode: null, packageCode: null, formCode: null, mode: null } + } - // 태그 패턴 검사 (/tag/123) - const tagMatch = path.match(/\/tag\/(\d+)/) - if (tagMatch) return parseInt(tagMatch[1], 10) + const projectCode = segments[vendorDataIndex + 1] || null + const packageCode = segments[vendorDataIndex + 2] || null - // 폼 패턴 검사 (/form/123/...) - const formMatch = path.match(/\/form\/(\d+)/) - if (formMatch) return parseInt(formMatch[1], 10) + // /eng/{formCode} 또는 /im/{formCode} 패턴 체크 + const modeSegment = segments[vendorDataIndex + 3] + const formCode = segments[vendorDataIndex + 4] || null - return null + let mode: "master" | "engineering" | "im" | null = null + + if (modeSegment === "eng") { + mode = "engineering" + } else if (modeSegment === "im") { + mode = "im" + } else if (projectCode && packageCode && !modeSegment) { + mode = "master" + } + + return { projectCode, packageCode, formCode, mode } } export function VendorDataContainer({ @@ -67,267 +74,106 @@ export function VendorDataContainer({ }: VendorDataContainerProps) { const pathname = usePathname() const router = useRouter() - const searchParams = useSearchParams() - 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 [selectedPackageCode, setSelectedPackageCode] = React.useState<string | null>(null) const [selectedFormCode, setSelectedFormCode] = React.useState<string | null>(null) - const [isLoadingForms, setIsLoadingForms] = React.useState(false) + const [currentMode, setCurrentMode] = React.useState<"master" | "engineering" | "im" | null>(null) - console.log(selectedPackageId,"selectedPackageId") - - - // 현재 선택된 프로젝트/계약/패키지 + // 현재 선택된 프로젝트 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 ="ENG" - - // 모드 초기화 (기존의 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]) - // URL에서 모드가 변경되면 상태도 업데이트 (ship 프로젝트가 아닐 때만) + // URL 변경 시 상태 동기화 React.useEffect(() => { - if (!isShipProject) { - const modeFromUrl = searchParams?.get('mode') - if (modeFromUrl === "ENG" || modeFromUrl === "IM") { - setSelectedMode(modeFromUrl) + const { projectCode, packageCode, formCode, mode } = getInfoFromPathname(pathname) + + if (projectCode && packageCode) { + // 프로젝트 찾기 + const project = projects.find(p => p.projectCode === projectCode) + if (project) { + setSelectedProjectId(project.projectId) + setSelectedPackageCode(packageCode) } } - }, [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); + + if (formCode) { + setSelectedFormCode(formCode) + } else { + setSelectedFormCode(null) } - }, [isShipProject, router]) - - // (1) 새로고침 시 URL 파라미터(tagIdNumber) → selectedPackageId 세팅 - React.useEffect(() => { - if (!currentContract) return - - if (tagIdNumber) { - setSelectedPackageId(tagIdNumber) + + if (mode) { + setCurrentMode(mode) } else { - // tagIdNumber가 없으면, 현재 계약의 첫 번째 패키지로 - if (currentContract.packages?.length) { - setSelectedPackageId(currentContract.packages[0].itemId) - } else { - setSelectedPackageId(null) - } + setCurrentMode(null) } - }, [tagIdNumber, currentContract]) - - // (2) 프로젝트 변경 시 계약 초기화 - // React.useEffect(() => { - // if (currentProject?.contracts.length) { - // setSelectedContractId(currentProject.contracts[0].contractId) - // } else { - // setSelectedContractId(0) - // } - // }, [currentProject]) + }, [pathname, projects]) + + // 베이스 URL 생성 헬퍼 + const getBaseUrl = () => { + const segments = pathname?.split("/").filter(Boolean) || [] + const vendorDataIndex = segments.indexOf("vendor-data-plant") + if (vendorDataIndex === -1) return "" + return "/" + segments.slice(0, vendorDataIndex + 1).join("/") + } - // (3) 패키지 ID와 모드가 변경될 때마다 폼 로딩 - React.useEffect(() => { - const packageId = getTagIdFromPathname(pathname) + // 프로젝트 및 패키지 선택 핸들러 + const handleSelectPackage = (projectId: number, packageCode: string) => { + const project = projects.find(p => p.projectId === projectId) + if (!project) return - 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; + setSelectedProjectId(projectId) + setSelectedPackageCode(packageCode) + setSelectedFormCode(null) + setCurrentMode("master") - setIsLoadingForms(true); - try { - const result = await getFormsByContractItemId(packageId, mode); - setFormList(result.forms || []); - } catch (error) { - console.error(`폼 로딩 오류 (${mode} 모드):`, error); - setFormList([]); - } finally { - setIsLoadingForms(false); - } - }; - - // 핸들러들 -// 수정된 handleSelectContract 함수 -async function handleSelectContract(projId: number, cId: number) { - setSelectedProjectId(projId) - setSelectedContractId(cId) - - // 선택된 계약의 첫 번째 패키지 찾기 - const selectedProject = projects.find(p => p.projectId === projId) - const selectedContract = selectedProject?.contracts.find(c => c.contractId === cId) + const baseUrl = getBaseUrl() + router.push(`${baseUrl}/${project.projectCode}/${packageCode}`) + } - if (selectedContract?.packages?.length) { - const firstPackageId = selectedContract.packages[0].itemId - setSelectedPackageId(firstPackageId) + // Master Tag List 클릭 핸들러 + const handleMasterTagListClick = () => { + if (!selectedPackageCode) return - // ENG 모드로 폼 목록 로드 - setIsLoadingForms(true) - try { - const result = await getFormsByContractItemId(firstPackageId, "ENG") - setFormList(result.forms || []) - - // 첫 번째 폼이 있으면 자동 선택 및 네비게이션 - if (result.forms && result.forms.length > 0) { - const firstForm = result.forms[0] - setSelectedFormCode(firstForm.formCode) - - // ENG 모드로 설정 - setSelectedMode("ENG") - - // 첫 번째 폼으로 네비게이션 - const baseSegments = pathname?.split("/").filter(Boolean).slice(0, pathname.split("/").filter(Boolean).indexOf("vendor-data-plant") + 1).join("/") - router.push(`/${baseSegments}/form/0/${firstForm.formCode}/${projId}/${cId}?mode=ENG`) - } else { - // 폼이 없는 경우에도 ENG 모드로 설정 - setSelectedMode("ENG") - setSelectedFormCode(null) - - const baseSegments = pathname?.split("/").filter(Boolean).slice(0, pathname.split("/").filter(Boolean).indexOf("vendor-data-plant") + 1).join("/") - router.push(`/${baseSegments}/form/0/0/${projId}/${cId}?mode=ENG`) - } - } catch (error) { - console.error("폼 로딩 오류:", error) - setFormList([]) - setSelectedFormCode(null) - - // 오류 발생 시에도 ENG 모드로 설정 - setSelectedMode("ENG") - } finally { - setIsLoadingForms(false) - } - } else { - // 패키지가 없는 경우 - setSelectedPackageId(null) - setFormList([]) + const project = projects.find(p => p.projectId === selectedProjectId) + if (!project) return + + setCurrentMode("master") setSelectedFormCode(null) - setSelectedMode("ENG") - } -} - - function handleSelectPackage(itemId: number) { - setSelectedPackageId(itemId) - } - - function handleSelectForm(formName: string) { - const form = formList.find((f) => f.formName === formName) - if (form) { - setSelectedFormCode(form.formCode) - } + + const baseUrl = getBaseUrl() + router.push(`${baseUrl}/${project.projectCode}/${selectedPackageCode}`) } - - // 모드 변경 핸들러 -// 모드 변경 핸들러 -const handleModeChange = async (mode: "IM" | "ENG") => { - // ship 프로젝트인 경우 모드 변경 금지 - if (isShipProject && mode !== "ENG") return; - setSelectedMode(mode); + // Engineering 폼 클릭 핸들러 + const handleEngineeringFormClick = (formCode: string) => { + if (!selectedPackageCode) return + + const project = projects.find(p => p.projectId === selectedProjectId) + if (!project) return + + setCurrentMode("engineering") + setSelectedFormCode(formCode) + + const baseUrl = getBaseUrl() + router.push(`${baseUrl}/${project.projectCode}/${selectedPackageCode}/eng/${formCode}`) + } - // 모드가 변경될 때 자동 네비게이션 - if (currentContract?.packages?.length) { - const firstPackageId = currentContract.packages[0].itemId; + // IM 폼 클릭 핸들러 + const handleIMFormClick = (formCode: string) => { + if (!selectedPackageCode) return - if (mode === "IM") { - // IM 모드: 첫 번째 패키지로 이동 - const baseSegments = pathname?.split("/").filter(Boolean).slice(0, pathname.split("/").filter(Boolean).indexOf("vendor-data-plant") + 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-plant") + 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-plant") + 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); + const project = projects.find(p => p.projectId === selectedProjectId) + if (!project) return + + setCurrentMode("im") + setSelectedFormCode(formCode) + + const baseUrl = getBaseUrl() + router.push(`${baseUrl}/${project.projectCode}/${selectedPackageCode}/im/${formCode}`) } -}; return ( <TooltipProvider delayDuration={0}> @@ -351,151 +197,28 @@ const handleModeChange = async (mode: "IM" | "ENG") => { <ProjectSwitcher isCollapsed={isCollapsed} projects={projects} - selectedContractId={selectedContractId} - onSelectContract={handleSelectContract} + selectedProjectId={selectedProjectId} + selectedPackageCode={selectedPackageCode} + onSelectPackage={handleSelectPackage} /> </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="ENG" className="flex-1">Engineering</TabsTrigger> - <TabsTrigger value="IM" className="flex-1">Handover</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 === "ENG" ? "default" : "ghost"} - size="sm" - className="h-8 px-2" - onClick={() => handleModeChange("ENG")} - > - Engineering - </Button> - <Button - variant={selectedMode === "IM" ? "default" : "ghost"} - size="sm" - className="h-8 px-2" - onClick={() => handleModeChange("IM")} - > - Handover - </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" - /> - </> - )} + <Sidebar + isCollapsed={isCollapsed} + selectedPackageCode={selectedPackageCode} + selectedFormCode={selectedFormCode} + currentMode={currentMode} + onMasterTagListClick={handleMasterTagListClick} + onEngineeringFormClick={handleEngineeringFormClick} + onIMFormClick={handleIMFormClick} + /> </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> |
