diff options
Diffstat (limited to 'components/vendor-data')
| -rw-r--r-- | components/vendor-data/sidebar.tsx | 121 | ||||
| -rw-r--r-- | components/vendor-data/vendor-data-container.tsx | 265 |
2 files changed, 287 insertions, 99 deletions
diff --git a/components/vendor-data/sidebar.tsx b/components/vendor-data/sidebar.tsx index b9e14b65..2dff6bc1 100644 --- a/components/vendor-data/sidebar.tsx +++ b/components/vendor-data/sidebar.tsx @@ -29,6 +29,7 @@ interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> { selectedForm: string | null onSelectForm: (formName: string) => void isLoadingForms?: boolean + mode: "IM" | "ENG" // 새로 추가: 현재 선택된 모드 } export function Sidebar({ @@ -41,9 +42,11 @@ export function Sidebar({ selectedForm, onSelectForm, isLoadingForms = false, + mode = "IM", // 기본값 설정 }: SidebarProps) { const router = useRouter() - const pathname = usePathname() + const rawPathname = usePathname() + const pathname = rawPathname ?? "" /** * --------------------------- @@ -93,43 +96,69 @@ export function Sidebar({ /** * --------------------------- - * 3) 폼 클릭 핸들러 + * 3) 폼 클릭 핸들러 (mode 추가) * --------------------------- */ const handleFormClick = (form: FormInfo) => { - // 패키지가 선택되어 있을 때만 동작 - if (selectedPackageId === null) return + // 패키지 ID 선택 전략 + let packageId: number; + + if (mode === "ENG") { + // ENG 모드에서는 첫 번째 패키지 ID 또는 현재 URL에서 추출한 ID 사용 + packageId = currentItemId || (packages[0]?.itemId || 0); + } else { + // IM 모드에서는 반드시 선택된 패키지 ID 필요 + if (selectedPackageId === null) return; + packageId = selectedPackageId; + } // 상위 컴포넌트 상태 업데이트 onSelectForm(form.formName) // 해당 폼 페이지로 라우팅 // 예: /vendor-data/form/[packageId]/[formCode] - const baseSegments = segments.slice(0, segments.indexOf("vendor-data") + 1).join("/") - - router.push(`/${baseSegments}/form/${selectedPackageId}/${form.formCode}`) + // 모드 정보를 쿼리 파라미터로 추가 + router.push(`/${baseSegments}/form/${packageId}/${form.formCode}?mode=${mode}`) } return ( <div className={cn("pb-12", className)}> <div className="space-y-4 py-4"> - {/* ---------- 패키지(Items) 목록 ---------- */} - <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) => { - // URL 기준으로 active 여부 판단 - const isActive = pkg.itemId === currentItemId - - return ( - <div key={pkg.itemId}> - {isCollapsed ? ( - <Tooltip delayDuration={0}> - <TooltipTrigger asChild> + {/* ---------- 패키지(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) => { + // URL 기준으로 active 여부 판단 + const isActive = pkg.itemId === currentItemId + + 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( @@ -139,40 +168,29 @@ export function Sidebar({ onClick={() => handlePackageClick(pkg.itemId)} > <Package2 className="mr-2 h-4 w-4" /> + {pkg.itemName} </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> + ) + })} + </div> + </ScrollArea> </div> - </ScrollArea> - </div> - - <Separator /> + <Separator /> + </> + )} {/* ---------- 폼 목록 ---------- */} <div className="py-1"> <h2 className="relative px-7 text-lg font-semibold tracking-tight"> {isCollapsed ? "F" : "Form Lists"} </h2> - <ScrollArea className="h-[300px] px-1"> + <ScrollArea className={cn( + "px-1", + // IM 모드는 더 작은 높이, ENG 모드는 더 큰 높이 + mode === "IM" ? "h-[300px]" : "h-[450px]" + )}> <div className="space-y-1 p-2"> {isLoadingForms ? ( // 로딩 중 스켈레톤 UI 표시 @@ -190,6 +208,9 @@ export function Sidebar({ // URL 기준으로 active 여부 판단 const isActive = form.formCode === currentFormCode + // IM 모드에서만 패키지 선택 여부에 따라 비활성화 + const isDisabled = mode === "IM" && currentItemId === null; + return isCollapsed ? ( <Tooltip key={form.formCode} delayDuration={0}> <TooltipTrigger asChild> @@ -200,7 +221,7 @@ export function Sidebar({ isActive && "bg-accent text-accent-foreground" )} onClick={() => handleFormClick(form)} - disabled={currentItemId === null} + disabled={isDisabled} > <FormInput className="mr-2 h-4 w-4" /> </Button> @@ -218,7 +239,7 @@ export function Sidebar({ isActive && "bg-accent text-accent-foreground" )} onClick={() => handleFormClick(form)} - disabled={currentItemId === null} + disabled={isDisabled} > <FormInput className="mr-2 h-4 w-4" /> {form.formName} diff --git a/components/vendor-data/vendor-data-container.tsx b/components/vendor-data/vendor-data-container.tsx index 11aa6f9d..77b36abf 100644 --- a/components/vendor-data/vendor-data-container.tsx +++ b/components/vendor-data/vendor-data-container.tsx @@ -6,9 +6,14 @@ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/componen import { cn } from "@/lib/utils" import { ProjectSwitcher } from "./project-swicher" import { Sidebar } from "./sidebar" -import { usePathname, useRouter } from "next/navigation" +import { usePathname, useRouter, useSearchParams } 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" interface PackageData { itemId: number @@ -25,6 +30,7 @@ interface ProjectData { projectId: number projectCode: string projectName: string + projectType: string contracts: ContractData[] } @@ -58,45 +64,86 @@ export function VendorDataContainer({ children }: VendorDataContainerProps) { const pathname = usePathname() + const router = useRouter() + const searchParams = useSearchParams() 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 [isCollapsed, setIsCollapsed] = React.useState(defaultCollapsed) 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 ? (pathname.includes("/tag/") || pathname.includes("/form/")) : false - const currentPackageName = isTagOrFormRoute - ? currentContract?.packages.find((pkg) => pkg.itemId === selectedPackageId)?.itemName || "None" - : "None" + // 프로젝트 타입 확인 - ship인 경우 항상 ENG 모드 + const isShipProject = currentProject?.projectType === "ship" + + // URL에서 모드 추출 (ship 프로젝트면 무조건 ENG로, 아니면 URL 또는 기본값) + const modeFromUrl = searchParams?.get('mode') + const initialMode = isShipProject + ? "ENG" + : (modeFromUrl === "ENG" || modeFromUrl === "IM") ? modeFromUrl : "IM" + + // 모드 선택 상태 - 프로젝트 타입에 따라 초기값 결정 + const [selectedMode, setSelectedMode] = React.useState<"IM" | "ENG">(initialMode) + + 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 프로젝트가 아닐 때만) + 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 세팅 - // URL에 tagIdNumber가 있으면 그걸 우선으로, 아니면 기존 로직대로 '첫 번째 패키지' 사용 React.useEffect(() => { if (!currentContract) return @@ -121,17 +168,7 @@ export function VendorDataContainer({ } }, [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가 정해질 때마다 폼 로딩 + // (3) 패키지 ID와 모드가 변경될 때마다 폼 로딩 React.useEffect(() => { const packageId = getTagIdFromPathname(pathname) @@ -139,27 +176,30 @@ export function VendorDataContainer({ setSelectedPackageId(packageId) // URL에서 패키지 ID를 얻었을 때 즉시 폼 로드 - loadFormsList(packageId); + loadFormsList(packageId, selectedMode); } else if (currentContract?.packages?.length) { - setSelectedPackageId(currentContract.packages[0].itemId) + const firstPackageId = currentContract.packages[0].itemId; + setSelectedPackageId(firstPackageId); + loadFormsList(firstPackageId, selectedMode); } - }, [pathname, currentContract]) + }, [pathname, currentContract, selectedMode]) - // 폼 로드 함수를 컴포넌트 내부에 정의하고 재사용 - const loadFormsList = async (packageId: number) => { + // 모드에 따른 폼 로드 함수 + const loadFormsList = async (packageId: number, mode: "IM" | "ENG") => { if (!packageId) return; setIsLoadingForms(true); try { - const result = await getFormsByContractItemId(packageId); + const result = await getFormsByContractItemId(packageId, mode); setFormList(result.forms || []); } catch (error) { - console.error("폼 로딩 오류:", error); + console.error(`폼 로딩 오류 (${mode} 모드):`, error); setFormList([]); } finally { setIsLoadingForms(false); } }; + // 핸들러들 function handleSelectContract(projId: number, cId: number) { setSelectedProjectId(projId) @@ -177,6 +217,27 @@ export function VendorDataContainer({ } } + // 모드 변경 핸들러 + const handleModeChange = (mode: "IM" | "ENG") => { + // ship 프로젝트인 경우 모드 변경 금지 + if (isShipProject && mode !== "ENG") return; + + setSelectedMode(mode); + + // 현재 URL에서 mode 파라미터 업데이트 (현재 경로 유지) + const url = new URL(window.location.href); + url.searchParams.set('mode', mode); + router.replace(url.pathname + url.search); + + // 모드가 변경되면 현재 패키지의 폼 다시 로드 + if (selectedPackageId) { + loadFormsList(selectedPackageId, mode); + } else if (currentContract?.packages?.length) { + const firstPackageId = currentContract.packages[0].itemId; + loadFormsList(firstPackageId, mode); + } + }; + return ( <TooltipProvider delayDuration={0}> <ResizablePanelGroup direction="horizontal" className="h-full"> @@ -204,21 +265,123 @@ export function VendorDataContainer({ /> </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" - /> + + {!isCollapsed ? ( + isShipProject ? ( + // 프로젝트 타입이 ship인 경우: 탭 없이 ENG 모드 사이드바만 바로 표시 + <div className="mt-0"> + <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} + 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">IM</TabsTrigger> + <TabsTrigger value="ENG" className="flex-1">ENG</TabsTrigger> + </TabsList> + + <TabsContent value="IM" className="mt-0"> + <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} + mode="IM" + className="hidden lg:block" + /> + </TabsContent> + + <TabsContent value="ENG" className="mt-0"> + <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} + 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")} + > + IM + </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} + 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 /> @@ -226,7 +389,11 @@ export function VendorDataContainer({ <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> + <h2 className="text-lg font-bold"> + {isShipProject || selectedMode === "ENG" + ? "Engineering Mode" + : `Package: ${currentPackageName}`} + </h2> </div> {children} </div> |
