diff options
| author | 0-Zz-ang <s1998319@gmail.com> | 2025-08-17 23:05:19 +0900 |
|---|---|---|
| committer | 0-Zz-ang <s1998319@gmail.com> | 2025-08-17 23:05:19 +0900 |
| commit | 5adc1df95f80fbec7a0b5bbee15448b10d5aec3a (patch) | |
| tree | c748910aacb05f13e335f9afa39eb9d763f88d7c /components | |
| parent | 6013fe51293ea067400e6b3b26691705608eba22 (diff) | |
(박서영)evcp/document-list-only, evcp/vendor-data 생성
Diffstat (limited to 'components')
| -rw-r--r-- | components/document-lists/vendor-doc-list-client-evcp.tsx | 35 | ||||
| -rw-r--r-- | components/vendor-data/sidebar.tsx | 15 | ||||
| -rw-r--r-- | components/vendor-data/vendor-data-container.tsx | 270 |
3 files changed, 114 insertions, 206 deletions
diff --git a/components/document-lists/vendor-doc-list-client-evcp.tsx b/components/document-lists/vendor-doc-list-client-evcp.tsx new file mode 100644 index 00000000..cc9b6804 --- /dev/null +++ b/components/document-lists/vendor-doc-list-client-evcp.tsx @@ -0,0 +1,35 @@ +"use client" +import * as React from "react" + +import { InformationButton } from "@/components/information/information-button" + +interface VendorDocumentsClientEvcpProps { + children: React.ReactNode +} + +export default function VendorDocumentListClientEvcp({ + children, +}: VendorDocumentsClientEvcpProps) { + return ( + <> + {/* 상단 영역: 타이틀만 표시 */} + <div className="flex items-center justify-between"> + {/* 왼쪽: 타이틀 & 설명 */} + <div> + <div className="flex items-center gap-2"> + <h2 className="text-2xl font-bold tracking-tight">전체 문서 리스트 관리</h2> + <InformationButton pagePath="evcp/document-list" /> + </div> + <p className="text-muted-foreground"> + 전체 계약 대상 문서를 관리하고 진행 상황을 추적할 수 있습니다. + </p> + </div> + </div> + + {/* 문서 목록/테이블 영역 */} + <section className="overflow-hidden rounded-[0.5rem] border bg-background shadow p-5"> + {children} + </section> + </> + ) +} diff --git a/components/vendor-data/sidebar.tsx b/components/vendor-data/sidebar.tsx index 3805d216..a6b35a9d 100644 --- a/components/vendor-data/sidebar.tsx +++ b/components/vendor-data/sidebar.tsx @@ -27,8 +27,7 @@ interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> { selectedProjectId: number | null selectedContractId: number | null onSelectPackage: (itemId: number) => void - forms: FormInfo[] - selectedForm: string | null + forms?: FormInfo[] // 선택적 속성으로 변경 onSelectForm: (formName: string) => void isLoadingForms?: boolean mode: "IM" | "ENG" // 새로 추가: 현재 선택된 모드 @@ -43,7 +42,7 @@ export function Sidebar({ selectedContractId, onSelectPackage, forms, - selectedForm, + // selectedForm, // 사용되지 않음 onSelectForm, isLoadingForms = false, mode = "IM", // 기본값 설정 @@ -90,13 +89,9 @@ export function Sidebar({ * --------------------------- */ const handlePackageClick = (itemId: number) => { - // 상위 컴포넌트 상태 업데이트 + // 상위 컴포넌트 상태 업데이트만 수행 + // 라우팅은 하지 않음 (프로젝트 선택 상태 유지) onSelectPackage(itemId) - - // 해당 태그 페이지로 라우팅 - // 예: /vendor-data/tag/123 - const baseSegments = segments.slice(0, segments.indexOf("vendor-data") + 1).join("/") - router.push(`/${baseSegments}/tag/${itemId}`) } /** @@ -204,7 +199,7 @@ export function Sidebar({ <Skeleton className="h-8 w-full" /> </div> )) - ) : forms.length === 0 ? ( + ) : !forms || forms.length === 0 ? ( <p className="text-sm text-muted-foreground px-2"> (No forms loaded) </p> diff --git a/components/vendor-data/vendor-data-container.tsx b/components/vendor-data/vendor-data-container.tsx index a549594c..3974b791 100644 --- a/components/vendor-data/vendor-data-container.tsx +++ b/components/vendor-data/vendor-data-container.tsx @@ -1,63 +1,42 @@ "use client" import * as React from "react" -import { TooltipProvider } from "@/components/ui/tooltip" +import { usePathname, useRouter, useSearchParams, useParams } from "next/navigation" +import { useAtom } from "jotai" +import { selectedModeAtom } from "@/atoms" +import { Sidebar } from "./sidebar" +import { ProjectSwitcher } from "./project-swicher" 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' +import { TooltipProvider } from "@/components/ui/tooltip" 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[] + projects: { + projectId: number + projectCode: string + projectName: string + projectType: string + contracts: { + contractId: number + contractNo: string + contractName: string + packages: PackageData[] + }[] + }[] defaultLayout?: number[] defaultCollapsed?: boolean - navCollapsedSize: number + 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], @@ -71,8 +50,6 @@ export function VendorDataContainer({ 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) @@ -80,15 +57,12 @@ export function VendorDataContainer({ 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" @@ -104,31 +78,6 @@ export function VendorDataContainer({ 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 프로젝트가 아닐 때만) React.useEffect(() => { @@ -152,23 +101,7 @@ export function VendorDataContainer({ } }, [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) 프로젝트 변경 시 계약 초기화 + // (1) 프로젝트 변경 시 계약 초기화 React.useEffect(() => { if (currentProject?.contracts.length) { setSelectedContractId(currentProject.contracts[0].contractId) @@ -177,38 +110,6 @@ export function VendorDataContainer({ } }, [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) @@ -217,67 +118,72 @@ export function VendorDataContainer({ function handleSelectPackage(itemId: number) { setSelectedPackageId(itemId) + + // partners와 동일하게: 패키지 선택 시 해당 페이지로 이동 + if (itemId && pathname) { + // 더 안전한 URL 생성 로직 + let baseSegments: string; + const vendorDataIndex = pathname.split("/").filter(Boolean).indexOf("vendor-data"); + + if (vendorDataIndex !== -1) { + baseSegments = pathname.split("/").filter(Boolean).slice(0, vendorDataIndex + 1).join("/"); + } else { + // vendor-data가 없으면 기본 경로 사용 + baseSegments = `${currentLng}/evcp/vendor-data`; + } + + const targetUrl = `/${baseSegments}/tag/${itemId}?mode=${selectedMode}`; + router.push(targetUrl); + } } function handleSelectForm(formName: string) { - const form = formList.find((f) => f.formName === formName) - if (form) { - setSelectedFormCode(form.formCode) + // partners와 동일하게: 폼 선택 시 해당 페이지로 이동 + if (selectedPackageId && pathname) { + // 더 안전한 URL 생성 로직 + let baseSegments: string; + const vendorDataIndex = pathname.split("/").filter(Boolean).indexOf("vendor-data"); + + if (vendorDataIndex !== -1) { + baseSegments = pathname.split("/").filter(Boolean).slice(0, vendorDataIndex + 1).join("/"); + } else { + // vendor-data가 없으면 기본 경로 사용 + baseSegments = `${currentLng}/evcp/vendor-data`; + } + + const targetUrl = `/${baseSegments}/form/${selectedPackageId}/${formName}/${selectedProjectId}/${selectedContractId}?mode=${selectedMode}`; + router.push(targetUrl); } } // 모드 변경 핸들러 -// 모드 변경 핸들러 -const handleModeChange = async (mode: "IM" | "ENG") => { - // ship 프로젝트인 경우 모드 변경 금지 - if (isShipProject && mode !== "ENG") return; - - setSelectedMode(mode); - - // 모드가 변경될 때 자동 네비게이션 - if (currentContract?.packages?.length) { - const firstPackageId = currentContract.packages[0].itemId; + const handleModeChange = async (mode: "IM" | "ENG") => { + // ship 프로젝트인 경우 모드 변경 금지 + if (isShipProject && mode !== "ENG") return; - if (mode === "IM") { - // IM 모드: 첫 번째 패키지로 이동 - const baseSegments = pathname?.split("/").filter(Boolean).slice(0, pathname.split("/").filter(Boolean).indexOf("vendor-data") + 1).join("/"); - router.push(`/${currentLng}/${baseSegments}/tag/${firstPackageId}?mode=${mode}`); - } else { - // ENG 모드: 폼 목록을 먼저 로드 - setIsLoadingForms(true); - try { - const result = await getFormsByContractItemId(firstPackageId, mode); - setFormList(result.forms || []); + setSelectedMode(mode); + + // 모드가 변경될 때 자동 네비게이션 + if (currentContract?.packages?.length) { + const firstPackageId = currentContract.packages[0].itemId; + + if (pathname) { + // 더 안전한 URL 생성 로직 + let baseSegments: string; + const vendorDataIndex = pathname.split("/").filter(Boolean).indexOf("vendor-data"); - // 폼이 있으면 첫 번째 폼으로 이동 - 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(`/${currentLng}/${baseSegments}/form/0/${firstForm.formCode}/${selectedProjectId}/${selectedContractId}?mode=${mode}`); + if (vendorDataIndex !== -1) { + baseSegments = pathname.split("/").filter(Boolean).slice(0, vendorDataIndex + 1).join("/"); } else { - // 폼이 없으면 모드만 변경 - const baseSegments = pathname?.split("/").filter(Boolean).slice(0, pathname.split("/").filter(Boolean).indexOf("vendor-data") + 1).join("/"); - router.push(`/${currentLng}/${baseSegments}/form/0/0/${selectedProjectId}/${selectedContractId}?mode=${mode}`); + // vendor-data가 없으면 기본 경로 사용 + baseSegments = `${currentLng}/evcp/vendor-data`; } - } 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); + + const targetUrl = `/${baseSegments}/tag/${firstPackageId}?mode=${mode}`; + router.push(targetUrl); } } - } else { - // 패키지가 없는 경우, 모드만 변경 - const url = new URL(window.location.href); - url.searchParams.set('mode', mode); - router.replace(url.pathname + url.search); } -}; return ( <TooltipProvider delayDuration={0}> @@ -318,14 +224,7 @@ const handleModeChange = async (mode: "IM" | "ENG") => { 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" /> @@ -351,14 +250,7 @@ const handleModeChange = async (mode: "IM" | "ENG") => { 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" /> @@ -372,14 +264,7 @@ const handleModeChange = async (mode: "IM" | "ENG") => { 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" /> @@ -418,14 +303,7 @@ const handleModeChange = async (mode: "IM" | "ENG") => { 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" /> @@ -441,7 +319,7 @@ const handleModeChange = async (mode: "IM" | "ENG") => { <h2 className="text-lg font-bold"> {isShipProject || selectedMode === "ENG" ? "Engineering Mode" - : `Package: ${currentPackageName}`} + : `Package: ${currentContract?.packages.find((pkg) => pkg.itemId === selectedPackageId)?.itemName || "None"}`} </h2> </div> {children} |
