From 18954df6565108a469fb1608ea3715dd9bb1b02d Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 1 Sep 2025 09:12:09 +0000 Subject: (대표님) 구매 기본계약, gtc 개발 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(evcp)/basic-contract/vendor-gtc/[id]/page.tsx | 308 +++++++++++++++++++++ app/[lng]/evcp/(evcp)/gtc/[id]/page.tsx | 160 ++++++++--- 2 files changed, 430 insertions(+), 38 deletions(-) create mode 100644 app/[lng]/evcp/(evcp)/basic-contract/vendor-gtc/[id]/page.tsx (limited to 'app') diff --git a/app/[lng]/evcp/(evcp)/basic-contract/vendor-gtc/[id]/page.tsx b/app/[lng]/evcp/(evcp)/basic-contract/vendor-gtc/[id]/page.tsx new file mode 100644 index 00000000..830d7146 --- /dev/null +++ b/app/[lng]/evcp/(evcp)/basic-contract/vendor-gtc/[id]/page.tsx @@ -0,0 +1,308 @@ +// page.tsx +import * as React from "react" +import { notFound } from "next/navigation" +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 { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { FileText, Calendar, AlertTriangle, ArrowLeft, Building2, FileSignature } from "lucide-react" +import { Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbSeparator, BreadcrumbPage } from "@/components/ui/breadcrumb" +import { formatDateTime } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import Link from "next/link" + +import { + getGtcClauses, + getUsersForFilter, + getVendorClausesForDocument +} from "@/lib/gtc-contract/gtc-clauses/service" +import { getGtcDocumentById } from "@/lib/gtc-contract/service" +import { searchParamsCache } from "@/lib/gtc-contract/gtc-clauses/validations" +import { GtcClausesVendorTable } from "@/lib/basic-contract/gtc-vendor/clause-table" +import { UpdateVendorDocumentStatusButton } from "@/lib/basic-contract/vendor-table/update-vendor-document-status-button" + +interface GtcClausesPageProps { + params: Promise<{ id: string }> + searchParams: Promise +} +export const dynamicParams = true; // 동적 파라미터 허용 (SSG용 경로 강제 생성 방지) +export const revalidate = 0; // (선택) 완전 SSR + +// page.tsx 수정 부분 +export default async function GtcClausesPage(props: GtcClausesPageProps) { + const params = await props.params + const searchParams = await props.searchParams + const documentId = parseInt(params.id) + + if (isNaN(documentId)) { + notFound() + } + + // URL 파라미터에서 벤더 정보 가져오기 + const vendorId = searchParams.vendorId ? parseInt(searchParams.vendorId as string) : undefined + const vendorName = searchParams.vendorName ? decodeURIComponent(searchParams.vendorName as string) : undefined + const contractId = searchParams.contractId ? parseInt(searchParams.contractId as string) : undefined + const templateId = searchParams.templateId ? parseInt(searchParams.templateId as string) : undefined + + // 문서 정보 조회 + const document = await getGtcDocumentById(documentId) + if (!document) { + notFound() + } + + console.log(document, "document") + + const search = searchParamsCache.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + // 병렬로 데이터 조회 + const promises = Promise.all([ + getGtcClauses({ + ...search, + filters: validFilters, + documentId, + }), + getUsersForFilter(), + getVendorClausesForDocument({ documentId, vendorId }) // vendorId 전달 + ]) + + const [_, __, vendorData] = await promises.then(values => values) + const vendorDocument = vendorData?.vendorDocument || null + + return ( + +
+ + + + EVCP + + + + GTC 목록 관리 + + + + + {vendorName ? `${vendorName} GTC 협의` : 'GTC 조항 관리'} + + + + + + +
+ + {/* 카드 섹션 - vendorName이 있을 때는 나란히, 없을 때는 DocumentInfo만 */} +
+ {/* 벤더 정보 섹션 (vendorName이 있을 때만 표시) */} + {vendorName && ( + + +
+
+ +
+ 벤더 협의 정보 + + {vendorName}과(와)의 GTC 조항 협의를 진행합니다 + +
+
+ {contractId && vendorDocument && ( + + + {vendorDocument.reviewStatus === 'draft' ? '초안' : + vendorDocument.reviewStatus === 'reviewing' ? '협의 중' : + vendorDocument.reviewStatus === 'complete' ? '협의 완료' : + vendorDocument.reviewStatus} + + )} +
+
+ + {vendorData && contractId && ( + +
+
+ 총 {vendorData.totalModified || 0}개 조항 협의됨, {vendorData.totalExcluded || 0}개 조항 제외됨 +
+ + {/* 협의 완료 업데이트 버튼 */} + {vendorDocument && ( + + )} +
+
+ )} +
+ )} + + {/* 템플릿 정보 섹션 */} + }> + + +
+ + {/* 조항 테이블 */} + + } + > + + +
+ ) +} + +// 메타데이터 생성 함수도 업데이트 +export async function generateMetadata(props: GtcClausesPageProps) { + const params = await props.params + const searchParams = await props.searchParams + const documentId = parseInt(params.id) + const vendorName = searchParams.vendorName ? decodeURIComponent(searchParams.vendorName as string) : undefined + + if (isNaN(documentId)) { + return { + title: "GTC 조항 관리", + } + } + + try { + const document = await getGtcDocumentById(documentId) + + if (!document) { + return { + title: "GTC 조항 관리", + } + } + + const baseTitle = `GTC 조항 관리 - ${document.type === "standard" ? "표준" : "프로젝트"} v${document.revision}` + const title = vendorName ? `${vendorName} ${baseTitle}` : baseTitle + + const description = vendorName + ? `${vendorName}과(와)의 GTC 조항 협의를 관리합니다.` + : document.project + ? `${document.project.name} (${document.project.code}) 프로젝트의 GTC 조항을 관리합니다.` + : "표준 GTC 조항을 관리합니다." + + return { + title, + description, + } + } catch (error) { + return { + title: "GTC 조항 관리", + } + } +} + +// DocumentInfo 컴포넌트는 기존 유지 +async function DocumentInfo({ + document, +}: { + document: Promise>> +}) { + const documentInfo = await document + + if (!documentInfo) { + return ( + + +
+ +

템플릿 정보를 찾을 수 없습니다.

+
+
+
+ ) + } + + return ( + + +
+
+ + {documentInfo.title} + + v{documentInfo.revision} +
+ +
+ + {documentInfo.isActive ? "활성" : "비활성"} + +
+
+ +
+ + + 생성일: {formatDateTime(documentInfo.createdAt, "KR")} + + {documentInfo.fileName && ( + <> + + + + 템플릿 파일: {documentInfo.fileName} + + + )} +
+
+
+ ) +} + +function TemplateInfoSkeleton() { + return ( + + +
+
+
+
+ + + ) +} \ No newline at end of file diff --git a/app/[lng]/evcp/(evcp)/gtc/[id]/page.tsx b/app/[lng]/evcp/(evcp)/gtc/[id]/page.tsx index 0f783375..20f0ea51 100644 --- a/app/[lng]/evcp/(evcp)/gtc/[id]/page.tsx +++ b/app/[lng]/evcp/(evcp)/gtc/[id]/page.tsx @@ -6,14 +6,20 @@ import { Skeleton } from "@/components/ui/skeleton" import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" import { Shell } from "@/components/shell" import { InformationButton } from "@/components/information/information-button" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { FileText, Calendar, AlertTriangle, ArrowLeft } from "lucide-react" +import { Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbSeparator, BreadcrumbPage } from "@/components/ui/breadcrumb" +import { formatDateTime } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import Link from "next/link" -import { - getGtcClauses, +import { + getGtcClauses, getUsersForFilter, } from "@/lib/gtc-contract/gtc-clauses/service" import { getGtcDocumentById } from "@/lib/gtc-contract/service" import { searchParamsCache } from "@/lib/gtc-contract/gtc-clauses/validations" -import { GtcClausesPageHeader } from "@/lib/gtc-contract/gtc-clauses/gtc-clauses-page-header" import { GtcClausesTable } from "@/lib/gtc-contract/gtc-clauses/table/clause-table" interface GtcClausesPageProps { @@ -51,38 +57,38 @@ export default async function GtcClausesPage(props: GtcClausesPageProps) { return ( - {/* 헤더 컴포넌트 */} - - - {/* 문서 정보 카드 */} -
-
-
-
최초등록일
-
{document.createdAt ? new Date(document.createdAt).toLocaleDateString('ko-KR') : '-'}
-
-
-
최초등록자
-
{document.createdByName || '-'}
-
-
-
최종수정일
-
{document.updatedAt ? new Date(document.updatedAt).toLocaleDateString('ko-KR') : '-'}
-
-
-
최종수정자
-
{document.updatedByName || '-'}
-
-
- - {document.editReason && ( -
-
최종 편집사유
-
{document.editReason}
-
- )} + +
+ + + + EVCP + + + + GTC 목록 관리 + + + + GTC 조항 관리 + + + + +
+ {/* 템플릿 정보 섹션 (콤팩트) */} + }> + + + + {/* 조항 테이블 */} @@ -109,7 +115,7 @@ export default async function GtcClausesPage(props: GtcClausesPageProps) { export async function generateMetadata(props: GtcClausesPageProps) { const params = await props.params const documentId = parseInt(params.id) - + if (isNaN(documentId)) { return { title: "GTC 조항 관리", @@ -118,7 +124,7 @@ export async function generateMetadata(props: GtcClausesPageProps) { try { const document = await getGtcDocumentById(documentId) - + if (!document) { return { title: "GTC 조항 관리", @@ -126,7 +132,7 @@ export async function generateMetadata(props: GtcClausesPageProps) { } const title = `GTC 조항 관리 - ${document.type === "standard" ? "표준" : "프로젝트"} v${document.revision}` - const description = document.project + const description = document.project ? `${document.project.name} (${document.project.code}) 프로젝트의 GTC 조항을 관리합니다.` : "표준 GTC 조항을 관리합니다." @@ -139,4 +145,82 @@ export async function generateMetadata(props: GtcClausesPageProps) { title: "GTC 조항 관리", } } -} \ No newline at end of file +} + + +async function DocumentInfo({ + document, +}: { + document: Promise>> +}) { + const documentInfo = await document + + if (!documentInfo) { + return ( + + +
+ +

템플릿 정보를 찾을 수 없습니다.

+
+
+
+ ) + } + + return ( + + +
+ {/* 좌측: 제목 + 리비전 */} +
+ + {documentInfo.title} + + v{documentInfo.revision} +
+ +
+ + {documentInfo.isActive ? "활성" : "비활성"} + +
+
+ + {/* 메타 정보 한 줄 정리 */} +
+ + + 생성일: {formatDateTime(documentInfo.createdAt, "KR")} + + {documentInfo.fileName && ( + <> + + + + 템플릿 파일: {documentInfo.fileName} + + + )} +
+
+
+ ) +} + +// 로딩 스켈레톤 (콤팩트) +function TemplateInfoSkeleton() { + return ( + + +
+
+
+
+ + + ) +} -- cgit v1.2.3