diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/[lng]/evcp/equip-class/page.tsx | 2 | ||||
| -rw-r--r-- | app/[lng]/evcp/pq-criteria/[id]/page.tsx | 81 | ||||
| -rw-r--r-- | app/[lng]/evcp/pq-criteria/page.tsx | 37 | ||||
| -rw-r--r-- | app/[lng]/evcp/pq/[vendorId]/page.tsx | 109 | ||||
| -rw-r--r-- | app/[lng]/evcp/vendor-candidates/page.tsx | 66 | ||||
| -rw-r--r-- | app/[lng]/evcp/vendor-investigation/page.tsx | 65 | ||||
| -rw-r--r-- | app/[lng]/partners/(partners)/info/page.tsx | 21 | ||||
| -rw-r--r-- | app/[lng]/partners/pq/page.tsx | 62 |
8 files changed, 396 insertions, 47 deletions
diff --git a/app/[lng]/evcp/equip-class/page.tsx b/app/[lng]/evcp/equip-class/page.tsx index fcda1c1d..375eb69e 100644 --- a/app/[lng]/evcp/equip-class/page.tsx +++ b/app/[lng]/evcp/equip-class/page.tsx @@ -38,7 +38,7 @@ export default async function IndexPage(props: IndexPageProps) { Object Class List from S-EDP </h2> <p className="text-muted-foreground"> - 벤더 데이터 입력을 위한 Form 리스트입니다.{" "} + Object Class List를 확인할 수 있습니다.{" "} {/* <span className="inline-flex items-center whitespace-nowrap"> <Ellipsis className="size-3" /> <span className="ml-1">버튼</span> diff --git a/app/[lng]/evcp/pq-criteria/[id]/page.tsx b/app/[lng]/evcp/pq-criteria/[id]/page.tsx new file mode 100644 index 00000000..f040a0ca --- /dev/null +++ b/app/[lng]/evcp/pq-criteria/[id]/page.tsx @@ -0,0 +1,81 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" +import { searchParamsCache } from "@/lib/pq/validations" +import { getPQs } from "@/lib/pq/service" +import { PqsTable } from "@/lib/pq/table/pq-table" +import { ProjectSelectorWrapper } from "@/components/pq/project-select-wrapper" +import { notFound } from "next/navigation" + +interface ProjectPageProps { + params: { id: string } + searchParams: Promise<SearchParams> +} + +export default async function ProjectPage(props: ProjectPageProps) { + const resolvedParams = await props.params + const id = resolvedParams.id + + const projectId = parseInt(id, 10) + + // 유효하지 않은 projectId 확인 + if (isNaN(projectId)) { + notFound() + } + + const searchParams = await props.searchParams + const search = searchParamsCache.parse(searchParams) + + // filters가 없는 경우를 처리 + const validFilters = getValidFilters(search.filters) + + // 프로젝트별 PQ 데이터 가져오기 + const promises = Promise.all([ + getPQs({ + ...search, + filters: validFilters, + }, projectId, false) + ]) + + return ( + <Shell className="gap-2"> + <div className="flex items-center justify-between"> + <div> + <h2 className="text-2xl font-bold tracking-tight"> + Pre-Qualification Check Sheet + </h2> + <p className="text-muted-foreground"> + 벤더 등록을 위한, 벤더가 제출할 PQ 항목을: 프로젝트별로 관리할 수 있습니다. + </p> + </div> + <ProjectSelectorWrapper selectedProjectId={projectId} /> + </div> + + <React.Suspense fallback={<Skeleton className="h-7 w-52" />}> + {/* <DateRangePicker + triggerSize="sm" + triggerClassName="ml-auto w-56 sm:w-60" + align="end" + shallow={false} + /> */} + </React.Suspense> + + <React.Suspense + fallback={ + <DataTableSkeleton + columnCount={6} + searchableColumnCount={1} + filterableColumnCount={2} + cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]} + shrinkZero + /> + } + > + <PqsTable promises={promises} currentProjectId={projectId}/> + </React.Suspense> + </Shell> + ) +}
\ No newline at end of file diff --git a/app/[lng]/evcp/pq-criteria/page.tsx b/app/[lng]/evcp/pq-criteria/page.tsx index d924890d..778baa93 100644 --- a/app/[lng]/evcp/pq-criteria/page.tsx +++ b/app/[lng]/evcp/pq-criteria/page.tsx @@ -1,14 +1,13 @@ import * as React from "react" import { type SearchParams } from "@/types/table" - import { getValidFilters } from "@/lib/data-table" import { Skeleton } from "@/components/ui/skeleton" import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" import { Shell } from "@/components/shell" - import { searchParamsCache } from "@/lib/pq/validations" import { getPQs } from "@/lib/pq/service" import { PqsTable } from "@/lib/pq/table/pq-table" +import { ProjectSelectorWrapper } from "@/components/pq/project-select-wrapper" interface IndexPageProps { searchParams: Promise<SearchParams> @@ -17,34 +16,33 @@ interface IndexPageProps { export default async function IndexPage(props: IndexPageProps) { const searchParams = await props.searchParams const search = searchParamsCache.parse(searchParams) - + + // filters가 없는 경우를 처리 + const validFilters = getValidFilters(search.filters) + // onlyGeneral: true로 설정하여 일반 PQ 항목만 가져옴 const promises = Promise.all([ getPQs({ ...search, filters: validFilters, - }), + }, null, true) ]) return ( <Shell className="gap-2"> - - <div className="flex items-center justify-between space-y-2"> - <div className="flex items-center justify-between space-y-2"> - <div> - <h2 className="text-2xl font-bold tracking-tight"> - Pre-Qualification Check Sheet - </h2> - <p className="text-muted-foreground"> - 벤더 등록을 위한, 벤더가 제출할 PQ 항목을 관리할 수 있습니다. - - </p> - </div> + <div className="flex items-center justify-between"> + <div> + <h2 className="text-2xl font-bold tracking-tight"> + Pre-Qualification Check Sheet + </h2> + <p className="text-muted-foreground"> + 벤더 등록을 위한, 벤더가 제출할 PQ 항목을 관리할 수 있습니다. + </p> </div> + <ProjectSelectorWrapper /> </div> - <React.Suspense fallback={<Skeleton className="h-7 w-52" />}> {/* <DateRangePicker triggerSize="sm" @@ -53,6 +51,7 @@ export default async function IndexPage(props: IndexPageProps) { shallow={false} /> */} </React.Suspense> + <React.Suspense fallback={ <DataTableSkeleton @@ -64,8 +63,8 @@ export default async function IndexPage(props: IndexPageProps) { /> } > - <PqsTable promises={promises} /> + <PqsTable promises={promises}/> </React.Suspense> </Shell> ) -} +}
\ No newline at end of file diff --git a/app/[lng]/evcp/pq/[vendorId]/page.tsx b/app/[lng]/evcp/pq/[vendorId]/page.tsx index cb4277f1..97c9a29a 100644 --- a/app/[lng]/evcp/pq/[vendorId]/page.tsx +++ b/app/[lng]/evcp/pq/[vendorId]/page.tsx @@ -1,38 +1,109 @@ import * as React from "react" import { Shell } from "@/components/shell" import { Skeleton } from "@/components/ui/skeleton" - import { type SearchParams } from "@/types/table" -import { getPQDataByVendorId } from "@/lib/pq/service" -import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { getPQDataByVendorId, getVendorPQsList } from "@/lib/pq/service" import { Vendor } from "@/db/schema/vendors" import { findVendorById } from "@/lib/vendors/service" -import VendorPQReviewPage from "@/components/pq/pq-review-detail" import VendorPQAdminReview from "@/components/pq/pq-review-detail" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { Badge } from "@/components/ui/badge" interface IndexPageProps { params: { - vendorId: string // Updated from 'id' to 'contractId' to match route parameter + vendorId: string } searchParams: Promise<SearchParams> } -export default async function DocumentListPage(props: IndexPageProps) { +export default async function PQReviewPage(props: IndexPageProps) { const resolvedParams = await props.params - const vendorId = resolvedParams.vendorId // Updated from 'id' to 'contractId' - - const idAsNumber = Number(vendorId) - - const data = await getPQDataByVendorId(idAsNumber) - - const vendor: Vendor | null = await findVendorById(idAsNumber) - - // 4) 렌더링 + const vendorId = Number(resolvedParams.vendorId) + + // Fetch the vendor data + const vendor: Vendor | null = await findVendorById(vendorId) + if (!vendor) return <div>Vendor not found</div> + + // Get list of all PQs (general + project-specific) for this vendor + const pqsList = await getVendorPQsList(vendorId) + + // Determine default active PQ to display + // If query param projectId exists, use that, otherwise use general PQ if available + const searchParams = await props.searchParams + const activeProjectId = searchParams.projectId ? Number(searchParams.projectId) : undefined + + // If no projectId query param, default to general PQ or first project PQ + const defaultTabId = activeProjectId ? + `project-${activeProjectId}` : + (pqsList.hasGeneralPq ? 'general' : `project-${pqsList.projectPQs[0]?.projectId}`) + + // Fetch PQ data for the active tab + let pqData; + if (activeProjectId) { + // Get project-specific PQ data + pqData = await getPQDataByVendorId(vendorId, activeProjectId) + } else { + // Get general PQ data + pqData = await getPQDataByVendorId(vendorId) + } + return ( <Shell className="gap-2"> - {vendor && - <VendorPQAdminReview data={data} vendor={vendor} /> - } + {pqsList.hasGeneralPq || pqsList.projectPQs.length > 0 ? ( + <Tabs defaultValue={defaultTabId} className="space-y-4"> + <div className="flex justify-between items-center"> + <h1 className="text-2xl font-bold"> + {vendor.vendorName} PQ Review + </h1> + + <TabsList> + {pqsList.hasGeneralPq && ( + <TabsTrigger value="general"> + General PQ <Badge variant="outline" className="ml-2">Standard</Badge> + </TabsTrigger> + )} + + {pqsList.projectPQs.map((project) => ( + <TabsTrigger key={project.projectId} value={`project-${project.projectId}`}> + {project.projectName} <Badge variant="outline" className="ml-2">{project.status}</Badge> + </TabsTrigger> + ))} + </TabsList> + </div> + + {/* Tab content for General PQ */} + {pqsList.hasGeneralPq && ( + <TabsContent value="general" className="mt-0"> + <VendorPQAdminReview + data={activeProjectId ? [] : pqData} + vendor={vendor} + projectId={undefined} + loadData={() => getPQDataByVendorId(vendorId)} + pqType="general" + /> + </TabsContent> + )} + + {/* Tab content for each Project PQ */} + {pqsList.projectPQs.map((project) => ( + <TabsContent key={project.projectId} value={`project-${project.projectId}`} className="mt-0"> + <VendorPQAdminReview + data={activeProjectId === project.projectId ? pqData : []} + vendor={vendor} + projectId={project.projectId} + projectName={project.projectName} + projectStatus={project.status} + loadData={() => getPQDataByVendorId(vendorId, project.projectId)} + pqType="project" + /> + </TabsContent> + ))} + </Tabs> + ) : ( + <div className="text-center py-10"> + <h2 className="text-xl font-medium">No PQ submissions found for this vendor</h2> + </div> + )} </Shell> ) -} +}
\ No newline at end of file diff --git a/app/[lng]/evcp/vendor-candidates/page.tsx b/app/[lng]/evcp/vendor-candidates/page.tsx new file mode 100644 index 00000000..668c0dc6 --- /dev/null +++ b/app/[lng]/evcp/vendor-candidates/page.tsx @@ -0,0 +1,66 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" + +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" + +import { getVendorCandidateCounts, getVendorCandidates } from "@/lib/vendor-candidates/service" +import { searchParamsCandidateCache } from "@/lib/vendor-candidates/validations" +import { VendorCandidateTable } from "@/lib/vendor-candidates/table/candidates-table" + +interface IndexPageProps { + searchParams: Promise<SearchParams> +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = searchParamsCandidateCache.parse(searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getVendorCandidates({ + ...search, + filters: validFilters, + }), + getVendorCandidateCounts() + ]) + + return ( + <Shell className="gap-2"> + + <div className="flex items-center justify-between space-y-2"> + <div className="flex items-center justify-between space-y-2"> + <div> + <h2 className="text-2xl font-bold tracking-tight"> + Vendor Candidates Management + </h2> + <p className="text-muted-foreground"> + 수집한 벤더 후보를 등록하고 초대 메일을 송부할 수 있습니다. + + </p> + </div> + </div> + </div> + + + <React.Suspense fallback={<Skeleton className="h-7 w-52" />}> + </React.Suspense> + <React.Suspense + fallback={ + <DataTableSkeleton + columnCount={6} + searchableColumnCount={1} + filterableColumnCount={2} + cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]} + shrinkZero + /> + } + > + <VendorCandidateTable promises={promises}/> + </React.Suspense> + </Shell> + ) +} diff --git a/app/[lng]/evcp/vendor-investigation/page.tsx b/app/[lng]/evcp/vendor-investigation/page.tsx new file mode 100644 index 00000000..c59de869 --- /dev/null +++ b/app/[lng]/evcp/vendor-investigation/page.tsx @@ -0,0 +1,65 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" + +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" + +import { VendorsInvestigationTable } from "@/lib/vendor-investigation/table/investigation-table" +import { getVendorsInvestigation } from "@/lib/vendor-investigation/service" +import { searchParamsInvestigationCache } from "@/lib/vendor-investigation/validations" + +interface IndexPageProps { + searchParams: Promise<SearchParams> +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = searchParamsInvestigationCache.parse(searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getVendorsInvestigation({ + ...search, + filters: validFilters, + }), + ]) + + return ( + <Shell className="gap-2"> + + <div className="flex items-center justify-between space-y-2"> + <div className="flex items-center justify-between space-y-2"> + <div> + <h2 className="text-2xl font-bold tracking-tight"> + Vendor Investigation Management + </h2> + <p className="text-muted-foreground"> + 요청된 Vendor 실사에 대한 스케줄 정보를 관리하고 결과를 입력할 수 있습니다. + + </p> + </div> + </div> + </div> + + + <React.Suspense fallback={<Skeleton className="h-7 w-52" />}> + </React.Suspense> + <React.Suspense + fallback={ + <DataTableSkeleton + columnCount={6} + searchableColumnCount={1} + filterableColumnCount={2} + cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]} + shrinkZero + /> + } + > + <VendorsInvestigationTable promises={promises}/> + </React.Suspense> + </Shell> + ) +} diff --git a/app/[lng]/partners/(partners)/info/page.tsx b/app/[lng]/partners/(partners)/info/page.tsx new file mode 100644 index 00000000..8215a451 --- /dev/null +++ b/app/[lng]/partners/(partners)/info/page.tsx @@ -0,0 +1,21 @@ +import { Suspense } from "react" +import { Metadata } from "next" +import { JoinFormSkeleton } from "@/components/signup/join-form-skeleton" +import { InfoForm } from "@/components/additional-info/join-form" + +// (Optional) If Next.js attempts to statically optimize this page and you need full runtime +// behavior for query params, you may also need: +// export const dynamic = "force-dynamic" + +export const metadata: Metadata = { + title: "Partner Portal", + description: "Authentication forms built using the components.", +} + +export default function IndexPage() { + return ( + <Suspense fallback={<JoinFormSkeleton/>}> + <InfoForm /> + </Suspense> + ) +}
\ No newline at end of file diff --git a/app/[lng]/partners/pq/page.tsx b/app/[lng]/partners/pq/page.tsx index 8ad23f6e..42c88b21 100644 --- a/app/[lng]/partners/pq/page.tsx +++ b/app/[lng]/partners/pq/page.tsx @@ -3,11 +3,16 @@ import { authOptions } from "@/app/api/auth/[...nextauth]/route" import * as React from "react" import { Shell } from "@/components/shell" import { Skeleton } from "@/components/ui/skeleton" -import { getPQDataByVendorId } from "@/lib/pq/service" +import { getPQDataByVendorId, getPQProjectsByVendorId } from "@/lib/pq/service" import { PQInputTabs } from "@/components/pq/pq-input-tabs" +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs" -export default async function PQInputPage() { +export default async function PQInputPage({ + searchParams +}: { + searchParams: { projectId?: string } +}) { // 세션 const session = await getServerSession(authOptions) // 예: 세션에서 vendorId 가져오기 @@ -15,25 +20,66 @@ export default async function PQInputPage() { const vendorId = 17 // 임시 const idAsNumber = Number(vendorId) - // 1) 서버에서 PQ 데이터 조회 (groupName별로 묶인 구조) - const pqData = await getPQDataByVendorId(idAsNumber) + const projectId = searchParams.projectId ? parseInt(searchParams.projectId, 10) : undefined + + // 벤더에게 요청된 프로젝트 PQ 목록 가져오기 (탭 표시용) + const projectPQs = await getPQProjectsByVendorId(idAsNumber) + + // PQ 데이터 조회 + const pqData = await getPQDataByVendorId(idAsNumber, projectId) + + // 현재 프로젝트 정보 (있다면) + const currentProject = projectId + ? projectPQs.find(p => p.projectId === projectId) + : null return ( <Shell className="gap-2"> + {/* 헤더 - 프로젝트 정보 포함 */} <div className="space-y-2"> <h2 className="text-2xl font-bold tracking-tight"> Pre-Qualification Check Sheet + {currentProject && ( + <span className="ml-2 text-muted-foreground"> + - {currentProject.projectCode} + </span> + )} </h2> <p className="text-muted-foreground"> - PQ에 적절한 응답을 제출하시기 바랍니다. 진행 중 문의가 있으면 담당자에게 연락바랍니다. + PQ에 적절한 응답을 제출하시기 바랍니다. </p> </div> - {/* 클라이언트 탭 UI 로드 (Suspense는 여기서는 크게 필요치 않을 수도 있음) */} + {/* 일반/프로젝트 PQ 선택 탭 */} + {projectPQs.length > 0 && ( + <div className="border-b"> + <Tabs defaultValue={projectId ? `project-${projectId}` : "general"}> + <TabsList> + <TabsTrigger value="general" asChild> + <a href="/partners/pq">일반 PQ</a> + </TabsTrigger> + + {projectPQs.map(project => ( + <TabsTrigger key={project.projectId} value={`project-${project.projectId}`} asChild> + <a href={`/partners/pq?projectId=${project.projectId}`}> + {project.projectCode} + </a> + </TabsTrigger> + ))} + </TabsList> + </Tabs> + </div> + )} + + {/* PQ 입력 탭 */} <React.Suspense fallback={<Skeleton className="h-7 w-52" />}> - <PQInputTabs data={pqData} vendorId={idAsNumber} /> + <PQInputTabs + data={pqData} + vendorId={idAsNumber} + projectId={projectId} + projectData={currentProject} + /> </React.Suspense> - </Shell> ) }
\ No newline at end of file |
