summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/[lng]/evcp/(evcp)/bid/page.tsx47
-rw-r--r--app/[lng]/evcp/(evcp)/legal-review/page.tsx4
-rw-r--r--app/[lng]/evcp/(evcp)/pq/[vendorId]/page.tsx108
-rw-r--r--app/[lng]/evcp/(evcp)/pq/page.tsx74
-rw-r--r--app/[lng]/evcp/(evcp)/vendor-regular-registrations/page.tsx56
-rw-r--r--app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/basic-info-client.tsx2
-rw-r--r--app/[lng]/partners/(partners)/vendor-data/form/[packageId]/[formId]/[projectId]/[contractId]/page.tsx13
-rw-r--r--app/[lng]/partners/(partners)/vendor-data/layout.tsx36
-rw-r--r--app/[lng]/partners/(partners)/vendor-data/page.tsx28
-rw-r--r--app/[lng]/partners/pq/page.tsx49
-rw-r--r--app/[lng]/procurement/(procurement)/pq/[vendorId]/page.tsx108
-rw-r--r--app/[lng]/procurement/(procurement)/pq/page.tsx71
-rw-r--r--app/api/files/[...path]/route.ts2
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 =>