diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/[lng]/evcp/(evcp)/bid/page.tsx | 47 | ||||
| -rw-r--r-- | app/[lng]/evcp/(evcp)/legal-review/page.tsx | 4 | ||||
| -rw-r--r-- | app/[lng]/evcp/(evcp)/pq/[vendorId]/page.tsx | 108 | ||||
| -rw-r--r-- | app/[lng]/evcp/(evcp)/pq/page.tsx | 74 | ||||
| -rw-r--r-- | app/[lng]/evcp/(evcp)/vendor-regular-registrations/page.tsx | 56 | ||||
| -rw-r--r-- | app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/basic-info-client.tsx | 2 | ||||
| -rw-r--r-- | app/[lng]/partners/(partners)/vendor-data/form/[packageId]/[formId]/[projectId]/[contractId]/page.tsx | 13 | ||||
| -rw-r--r-- | app/[lng]/partners/(partners)/vendor-data/layout.tsx | 36 | ||||
| -rw-r--r-- | app/[lng]/partners/(partners)/vendor-data/page.tsx | 28 | ||||
| -rw-r--r-- | app/[lng]/partners/pq/page.tsx | 49 | ||||
| -rw-r--r-- | app/[lng]/procurement/(procurement)/pq/[vendorId]/page.tsx | 108 | ||||
| -rw-r--r-- | app/[lng]/procurement/(procurement)/pq/page.tsx | 71 | ||||
| -rw-r--r-- | app/api/files/[...path]/route.ts | 2 |
13 files changed, 138 insertions, 460 deletions
diff --git a/app/[lng]/evcp/(evcp)/bid/page.tsx b/app/[lng]/evcp/(evcp)/bid/page.tsx index 7480ce88..0e129e8a 100644 --- a/app/[lng]/evcp/(evcp)/bid/page.tsx +++ b/app/[lng]/evcp/(evcp)/bid/page.tsx @@ -1,36 +1,45 @@ import { Suspense } from "react" import { Shell } from "@/components/shell" import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" -import { - getBiddings, - getBiddingStatusCounts, +import { + getBiddings, + getBiddingStatusCounts, getBiddingTypeCounts, getBiddingManagerCounts, - getBiddingMonthlyStats + getBiddingMonthlyStats } from "@/lib/bidding/service" import { searchParamsCache } from "@/lib/bidding/validation" import { BiddingsPageHeader } from "@/lib/bidding/list/biddings-page-header" import { BiddingsStatsCards } from "@/lib/bidding/list/biddings-stats-cards" import { BiddingsTable } from "@/lib/bidding/list/biddings-table" +import { getValidFilters } from "@/lib/data-table" +import { type SearchParams } from "@/types/table" export const metadata = { title: "입찰 목록", description: "입찰 공고를 생성하고 진행 상황을 관리할 수 있습니다.", } -interface BiddingsPageProps { - searchParams: Record<string, string | string[] | undefined> +interface IndexPageProps { + searchParams: Promise<SearchParams> } -export default async function BiddingsPage({ searchParams }: BiddingsPageProps) { +export default async function BiddingsPage(props: IndexPageProps) { // ✅ nuqs searchParamsCache로 파싱 (타입 안전성 보장) + const searchParams = await props.searchParams const search = searchParamsCache.parse(searchParams) + + const validFilters = getValidFilters(search.filters) + // ✅ 모든 데이터를 병렬로 로드 const promises = Promise.all([ - getBiddings(search), + getBiddings({ + ...search, + filters: validFilters, + }), getBiddingStatusCounts(), - getBiddingTypeCounts(), + getBiddingTypeCounts(), getBiddingManagerCounts(), getBiddingMonthlyStats(), ]) @@ -52,14 +61,14 @@ export default async function BiddingsPage({ searchParams }: BiddingsPageProps) {/* ═══════════════════════════════════════════════════════════════ */} {/* 메인 테이블 */} {/* ═══════════════════════════════════════════════════════════════ */} - <Suspense + <Suspense fallback={ - <DataTableSkeleton - columnCount={20} - searchableColumnCount={3} - filterableColumnCount={4} + <DataTableSkeleton + columnCount={20} + searchableColumnCount={3} + filterableColumnCount={4} cellWidths={["10rem", "8rem", "12rem", "15rem", "10rem", "8rem"]} - shrinkZero + shrinkZero /> } > @@ -72,16 +81,16 @@ export default async function BiddingsPage({ searchParams }: BiddingsPageProps) // ═══════════════════════════════════════════════════════════════ // 통계 카드 래퍼 컴포넌트 // ═══════════════════════════════════════════════════════════════ -async function BiddingsStatsCardsWrapper({ - promises -}: { +async function BiddingsStatsCardsWrapper({ + promises +}: { promises: Promise<[ Awaited<ReturnType<typeof getBiddings>>, Awaited<ReturnType<typeof getBiddingStatusCounts>>, Awaited<ReturnType<typeof getBiddingTypeCounts>>, Awaited<ReturnType<typeof getBiddingManagerCounts>>, Awaited<ReturnType<typeof getBiddingMonthlyStats>> - ]> + ]> }) { const [biddingsResult, statusCounts, typeCounts, managerCounts, monthlyStats] = await promises diff --git a/app/[lng]/evcp/(evcp)/legal-review/page.tsx b/app/[lng]/evcp/(evcp)/legal-review/page.tsx index 63560db3..44150492 100644 --- a/app/[lng]/evcp/(evcp)/legal-review/page.tsx +++ b/app/[lng]/evcp/(evcp)/legal-review/page.tsx @@ -15,7 +15,7 @@ export const dynamic = "force-dynamic"; export const revalidate = 0; export const metadata: Metadata = { - title: "법무업무 관리", + title: "법무검토 관리", description: "법무 검토 요청 및 답변을 관리합니다.", }; @@ -39,7 +39,7 @@ export default async function LegalWorksPage({ searchParams }: LegalWorksPagePro {/* Header - EvaluationTargetsPage와 동일한 패턴 */} <div className="flex items-center justify-between space-y-2"> <div className="flex items-center gap-2"> - <h2 className="text-2xl font-bold tracking-tight">법무업무 관리</h2> + <h2 className="text-2xl font-bold tracking-tight">법무검토 관리</h2> <InformationButton pagePath="evcp/legal-review" /> {/* ✅ EvaluationTargetsPage와 동일하게 Badge 추가 */} <Badge variant="outline" className="text-sm"> diff --git a/app/[lng]/evcp/(evcp)/pq/[vendorId]/page.tsx b/app/[lng]/evcp/(evcp)/pq/[vendorId]/page.tsx deleted file mode 100644 index 76bcfe59..00000000 --- a/app/[lng]/evcp/(evcp)/pq/[vendorId]/page.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import * as React from "react" -import { Shell } from "@/components/shell" -import { type SearchParams } from "@/types/table" -import { getPQDataByVendorId, getVendorPQsList, loadGeneralPQData, loadProjectPQAction, loadProjectPQData } from "@/lib/pq/service" -import { Vendor } from "@/db/schema/vendors" -import { findVendorById } from "@/lib/vendors/service" -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 - } - searchParams: Promise<SearchParams> -} - -export default async function PQReviewPage(props: IndexPageProps) { - const resolvedParams = await props.params - 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"> - {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={loadGeneralPQData} - 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={loadProjectPQAction} - 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/(evcp)/pq/page.tsx b/app/[lng]/evcp/(evcp)/pq/page.tsx deleted file mode 100644 index 1607db19..00000000 --- a/app/[lng]/evcp/(evcp)/pq/page.tsx +++ /dev/null @@ -1,74 +0,0 @@ -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 { getVendorsInPQ } from "@/lib/pq/service" -import { searchParamsCache } from "@/lib/vendors/validations" -import { VendorsPQReviewTable } from "@/lib/pq/pq-review-table/vendors-table" -import { InformationButton } from "@/components/information/information-button" -interface IndexPageProps { - searchParams: Promise<SearchParams> -} - -export default async function IndexPage(props: IndexPageProps) { - const searchParams = await props.searchParams - const search = searchParamsCache.parse(searchParams) - - const validFilters = getValidFilters(search.filters) - - const promises = Promise.all([ - getVendorsInPQ({ - ...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> - <div className="flex items-center gap-2"> - <h2 className="text-2xl font-bold tracking-tight"> - PQ 관리 - </h2> - <InformationButton pagePath="evcp/pq" /> - </div> - {/* <p className="text-muted-foreground"> - 벤더가 제출한 PQ를 확인하고 수정 요청 등을 할 수 있으며 PQ 종료 후에는 통과 여부를 결정할 수 있습니다. - - </p> */} - </div> - </div> - </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 - /> - } - > - <VendorsPQReviewTable promises={promises} /> - </React.Suspense> - </Shell> - ) -} diff --git a/app/[lng]/evcp/(evcp)/vendor-regular-registrations/page.tsx b/app/[lng]/evcp/(evcp)/vendor-regular-registrations/page.tsx new file mode 100644 index 00000000..2260396c --- /dev/null +++ b/app/[lng]/evcp/(evcp)/vendor-regular-registrations/page.tsx @@ -0,0 +1,56 @@ +import * as React from "react"
+
+
+import { Skeleton } from "@/components/ui/skeleton"
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
+import { Shell } from "@/components/shell"
+
+import { fetchVendorRegularRegistrations } from "@/lib/vendor-regular-registrations/service"
+import { VendorRegularRegistrationsTable } from "@/lib/vendor-regular-registrations/table/vendor-regular-registrations-table"
+import { InformationButton } from "@/components/information/information-button"
+
+
+
+export default async function VendorRegularRegistrationsPage() {
+ const promises = Promise.all([
+ fetchVendorRegularRegistrations(),
+ ])
+
+ 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>
+ <div className="flex items-center gap-2">
+ <h2 className="text-2xl font-bold tracking-tight">
+ 정규업체 등록관리
+ </h2>
+ <InformationButton pagePath="evcp/vendor-regular-registrations" />
+ </div>
+ <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={13}
+ searchableColumnCount={1}
+ filterableColumnCount={3}
+ cellWidths={["10rem", "8rem", "12rem", "20rem", "15rem", "10rem", "10rem", "15rem", "12rem", "12rem", "12rem", "20rem", "8rem"]}
+ shrinkZero
+ />
+ }
+ >
+ <VendorRegularRegistrationsTable promises={promises} />
+ </React.Suspense>
+ </Shell>
+ )
+}
diff --git a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/basic-info-client.tsx b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/basic-info-client.tsx index d537c5fe..d00bfaa8 100644 --- a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/basic-info-client.tsx +++ b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/basic-info-client.tsx @@ -923,7 +923,7 @@ export default function BasicInfoClient({ column2={ <div className="space-y-2"> <InfoItem - title="공장 PIC" + title="공장 담당자" value={ initialData.factoryInfo?.factoryPIC ? `${initialData.factoryInfo.factoryPIC} [${ diff --git a/app/[lng]/partners/(partners)/vendor-data/form/[packageId]/[formId]/[projectId]/[contractId]/page.tsx b/app/[lng]/partners/(partners)/vendor-data/form/[packageId]/[formId]/[projectId]/[contractId]/page.tsx index f69aa525..2f73e096 100644 --- a/app/[lng]/partners/(partners)/vendor-data/form/[packageId]/[formId]/[projectId]/[contractId]/page.tsx +++ b/app/[lng]/partners/(partners)/vendor-data/form/[packageId]/[formId]/[projectId]/[contractId]/page.tsx @@ -1,5 +1,6 @@ import DynamicTable from "@/components/form-data/form-data-table"; import { findContractItemId, getFormData, getFormId } from "@/lib/forms/services"; +import { useTranslation } from "@/i18n"; interface IndexPageProps { params: { @@ -8,8 +9,6 @@ interface IndexPageProps { formId: string; projectId: string; contractId: string; - - }; searchParams?: { mode?: string; @@ -24,7 +23,10 @@ export default async function FormPage({ params, searchParams }: IndexPageProps) const resolvedSearchParams = await searchParams; // 3) 구조 분해 할당 - const { lng, packageId, formId: formCode, projectId,contractId } = resolvedParams; + const { lng, packageId, formId: formCode, projectId, contractId } = resolvedParams; + + // i18n 설정 + const { t } = await useTranslation(lng, 'engineering'); // URL 쿼리 파라미터에서 mode 가져오기 (await 해서 사용) const mode = resolvedSearchParams?.mode === "ENG" ? "ENG" : "IM"; // 기본값은 IM @@ -49,7 +51,6 @@ export default async function FormPage({ params, searchParams }: IndexPageProps) // 5) DB 조회 const { columns, data, editableFieldsMap } = await getFormData(formCode, packageIdAsNumber); - // 6) formId 및 report temp file 조회 const { formId } = await getFormId(String(packageIdAsNumber), formCode); @@ -57,7 +58,9 @@ export default async function FormPage({ params, searchParams }: IndexPageProps) // 7) 예외 처리 if (!columns) { return ( - <p className="text-red-500">해당 폼의 메타 정보를 불러올 수 없습니다. ENG 모드의 경우에는 SHI 관리자에게 폼 생성 요청을 하시기 바랍니다.</p> + <p className="text-red-500"> + {t('errors.form_meta_not_found')} + </p> ); } diff --git a/app/[lng]/partners/(partners)/vendor-data/layout.tsx b/app/[lng]/partners/(partners)/vendor-data/layout.tsx index cae8d10b..9621d23f 100644 --- a/app/[lng]/partners/(partners)/vendor-data/layout.tsx +++ b/app/[lng]/partners/(partners)/vendor-data/layout.tsx @@ -4,33 +4,43 @@ import { cookies } from "next/headers" import { Shell } from "@/components/shell" import { getVendorProjectsAndContracts } from "@/lib/vendor-data/services" import { VendorDataContainer } from "@/components/vendor-data/vendor-data-container" -import { authOptions } from "@/app/api/auth/[...nextauth]/route"; -import { getServerSession } from "next-auth"; +import { authOptions } from "@/app/api/auth/[...nextauth]/route" +import { getServerSession } from "next-auth" import { InformationButton } from "@/components/information/information-button" +import { useTranslation } from "@/i18n" + +interface VendorDataLayoutProps { + children: React.ReactNode + params?: { locale?: string } +} + // Layout 컴포넌트는 서버 컴포넌트입니다 export default async function VendorDataLayout({ children, -}: { - children: React.ReactNode -}) { + params, +}: VendorDataLayoutProps) { + // 기본 언어는 'ko'로 설정, params.locale이 있으면 사용 + const lng = params?.locale || 'ko' + const { t } = await useTranslation(lng, 'engineering') + const session = await getServerSession(authOptions) const vendorId = session?.user.companyId // const vendorId = "17" const idAsNumber = Number(vendorId) - + // 프로젝트 데이터 가져오기 - const projects = await getVendorProjectsAndContracts(idAsNumber) + const projects = await getVendorProjectsAndContracts(idAsNumber) // 레이아웃 설정 쿠키 가져오기 // Next.js 15에서는 cookies()가 Promise를 반환하므로 await 사용 const cookieStore = await cookies() - + // 이제 cookieStore.get() 메서드 사용 가능 const layout = cookieStore.get("react-resizable-panels:layout:mail") const collapsed = cookieStore.get("react-resizable-panels:collapsed") - + const defaultLayout = layout ? JSON.parse(layout.value) : undefined - const defaultCollapsed = collapsed ? JSON.parse(collapsed.value) : undefined + const defaultCollapsed = collapsed ? JSON.parse(collapsed.value) : undefined return ( <Shell className="gap-2"> @@ -39,7 +49,7 @@ export default async function VendorDataLayout({ <div> <div className="flex items-center gap-2"> <h2 className="text-2xl font-bold tracking-tight"> - 협력업체 데이터 입력 + {t('layout.page_title')} </h2> <InformationButton pagePath="partners/vendor-data" /> </div> @@ -49,12 +59,12 @@ export default async function VendorDataLayout({ </div> </div> </div> - + <section className="overflow-hidden rounded-[0.5rem] border bg-background shadow"> <div className="hidden flex-col md:flex"> {projects.length === 0 ? ( <div className="p-4 text-center text-sm text-muted-foreground"> - No projects found for this vendor. + {t('layout.no_projects')} </div> ) : ( <VendorDataContainer diff --git a/app/[lng]/partners/(partners)/vendor-data/page.tsx b/app/[lng]/partners/(partners)/vendor-data/page.tsx index afc3932c..db032377 100644 --- a/app/[lng]/partners/(partners)/vendor-data/page.tsx +++ b/app/[lng]/partners/(partners)/vendor-data/page.tsx @@ -1,26 +1,34 @@ -// app/vendor-data/page.tsx import * as React from "react" import { Separator } from "@/components/ui/separator" +import { useTranslation } from "@/i18n" + +interface Props { + params: { locale?: string } +} + +export default async function VendorDataPage({ params }: Props) { + // 기본 언어는 'ko'로 설정, params.locale이 있으면 사용 + const lng = params?.locale || 'ko' + const { t } = await useTranslation(lng, 'engineering') -export default async function IndexPage() { return ( <div className="space-y-6"> <div> - <h3 className="text-lg font-medium">협력업체 데이터 대시보드</h3> + <h3 className="text-lg font-medium">{t('layout.title')}</h3> <p className="text-sm text-muted-foreground"> - 왼쪽 사이드바에서 패키지를 선택하여 태그를 관리하세요. + {t('layout.description')} </p> </div> <Separator /> <div className="grid gap-4"> <div className="rounded-lg border p-4"> - <h4 className="text-sm font-medium">시작하는 방법</h4> + <h4 className="text-sm font-medium">{t('layout.getting_started.title')}</h4> <p className="text-sm text-muted-foreground mt-1"> - 1. 왼쪽 상단에서 프로젝트/계약을 선택하세요.<br /> - 2. 사이드바에서 패키지 항목을 클릭하세요.<br /> - 3. 선택한 패키지의 태그 정보를 확인하고 관리할 수 있습니다.<br /> - 4. 사이드바에서 폼 항목을 클릭하세요.<br /> - 5. 선택함 폼의 칼럼 정보를 확인하고 관리할 수 있습니다. + 1. {t('layout.getting_started.step1')}<br /> + 2. {t('layout.getting_started.step2')}<br /> + 3. {t('layout.getting_started.step3')}<br /> + 4. {t('layout.getting_started.step4')}<br /> + 5. {t('layout.getting_started.step5')} </p> </div> </div> diff --git a/app/[lng]/partners/pq/page.tsx b/app/[lng]/partners/pq/page.tsx deleted file mode 100644 index 87bcd409..00000000 --- a/app/[lng]/partners/pq/page.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { getServerSession } from "next-auth" -import { authOptions } from "@/app/api/auth/[...nextauth]/route" -import { getPQDataByVendorId, getPQProjectsByVendorId } from "@/lib/pq/service" -import { ClientPQWrapper } from "@/components/pq/client-pq-input-wrapper" -import { unstable_noStore as noStore } from 'next/cache' - -// 페이지가 기본적으로 동적임을 나타냄 -export const dynamic = "force-dynamic" - -export default async function PQInputPage({ - searchParams, -}: { - searchParams: Promise<{ projectId?: string }> -}) { - // Opt out of caching for this route - noStore() - - // searchParams를 await - const resolvedSearchParams = await searchParams - - // 세션 - const session = await getServerSession(authOptions) - // 세션에서 vendorId 가져오기 - const vendorId = session?.user.companyId - // const vendorId = 17 // 임시 - const idAsNumber = Number(vendorId) - - // 프로젝트 목록 가져오기 - const projectPQs = await getPQProjectsByVendorId(idAsNumber) - - // searchParams에서 projectId 파싱 - const projectIdParam = resolvedSearchParams.projectId - const projectId = projectIdParam ? parseInt(projectIdParam, 10) : undefined - - // 현재 선택된 프로젝트를 위한 PQ 데이터 가져오기 - const selectedProjectPQData = projectId - ? await getPQDataByVendorId(idAsNumber, projectId) - : await getPQDataByVendorId(idAsNumber, undefined) - - // 클라이언트 컴포넌트로 데이터 전달 - return ( - <ClientPQWrapper - pqData={selectedProjectPQData} - projectPQs={projectPQs} - vendorId={idAsNumber} - rawSearchParams={resolvedSearchParams} - /> - ) -}
\ No newline at end of file diff --git a/app/[lng]/procurement/(procurement)/pq/[vendorId]/page.tsx b/app/[lng]/procurement/(procurement)/pq/[vendorId]/page.tsx deleted file mode 100644 index 76bcfe59..00000000 --- a/app/[lng]/procurement/(procurement)/pq/[vendorId]/page.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import * as React from "react" -import { Shell } from "@/components/shell" -import { type SearchParams } from "@/types/table" -import { getPQDataByVendorId, getVendorPQsList, loadGeneralPQData, loadProjectPQAction, loadProjectPQData } from "@/lib/pq/service" -import { Vendor } from "@/db/schema/vendors" -import { findVendorById } from "@/lib/vendors/service" -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 - } - searchParams: Promise<SearchParams> -} - -export default async function PQReviewPage(props: IndexPageProps) { - const resolvedParams = await props.params - 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"> - {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={loadGeneralPQData} - 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={loadProjectPQAction} - 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]/procurement/(procurement)/pq/page.tsx b/app/[lng]/procurement/(procurement)/pq/page.tsx deleted file mode 100644 index 0274dc9f..00000000 --- a/app/[lng]/procurement/(procurement)/pq/page.tsx +++ /dev/null @@ -1,71 +0,0 @@ -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 { getVendorsInPQ } from "@/lib/pq/service" -import { searchParamsCache } from "@/lib/vendors/validations" -import { VendorsPQReviewTable } from "@/lib/pq/pq-review-table/vendors-table" - -interface IndexPageProps { - searchParams: Promise<SearchParams> -} - -export default async function IndexPage(props: IndexPageProps) { - const searchParams = await props.searchParams - const search = searchParamsCache.parse(searchParams) - - const validFilters = getValidFilters(search.filters) - - const promises = Promise.all([ - getVendorsInPQ({ - ...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"> - PQ 관리 - </h2> - {/* <p className="text-muted-foreground"> - 벤더가 제출한 PQ를 확인하고 수정 요청 등을 할 수 있으며 PQ 종료 후에는 통과 여부를 결정할 수 있습니다. - - </p> */} - </div> - </div> - </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 - /> - } - > - <VendorsPQReviewTable promises={promises} /> - </React.Suspense> - </Shell> - ) -} diff --git a/app/api/files/[...path]/route.ts b/app/api/files/[...path]/route.ts index 81f4b95d..5eab8210 100644 --- a/app/api/files/[...path]/route.ts +++ b/app/api/files/[...path]/route.ts @@ -44,6 +44,8 @@ const isAllowedPath = (requestedPath: string): boolean => { 'vendor-evaluation', 'evaluation-attachments', 'vendor-attachments', + 'pq', + 'pq/vendor' ]; return allowedPaths.some(allowed => |
