From c62ec046327fd388ebce04571b55910747e69a3b Mon Sep 17 00:00:00 2001 From: dujinkim Date: Tue, 9 Sep 2025 10:32:34 +0000 Subject: (정희성, 최겸, 대표님) formatDate 변경 등 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/[lng]/evcp/(evcp)/consent/page.tsx | 238 +++++++++++++++++++++ app/[lng]/evcp/(evcp)/polices/page.tsx | 238 --------------------- .../pq_new/[vendorId]/[submissionId]/page.tsx | 19 +- .../pq_new/[vendorId]/[submissionId]/page.tsx | 19 +- app/api/basic-contract/get-template/route.ts | 31 ++- 5 files changed, 274 insertions(+), 271 deletions(-) create mode 100644 app/[lng]/evcp/(evcp)/consent/page.tsx delete mode 100644 app/[lng]/evcp/(evcp)/polices/page.tsx (limited to 'app') diff --git a/app/[lng]/evcp/(evcp)/consent/page.tsx b/app/[lng]/evcp/(evcp)/consent/page.tsx new file mode 100644 index 00000000..5e760b5e --- /dev/null +++ b/app/[lng]/evcp/(evcp)/consent/page.tsx @@ -0,0 +1,238 @@ +// app/admin/policies/page.tsx (서버 컴포넌트) +import { Suspense } from 'react' +import { Metadata } from 'next' +import { eq, desc } from 'drizzle-orm' +import db from '@/db/db' +import { policyVersions } from '@/db/schema' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' +import { Separator } from '@/components/ui/separator' +import { FileText, Shield, Calendar, User, Clock } from 'lucide-react' +import { PolicyManagementClient } from '@/components/polices/policy-management-client' + +export const metadata: Metadata = { + title: '정책 관리 | eVCP Admin', + description: '개인정보 처리방침 및 이용약관 관리' +} + +// 정책 데이터 조회 함수 +async function getPoliciesData() { + try { + // 현재 활성 정책들 + const currentPolicies = await db + .select() + .from(policyVersions) + .where(eq(policyVersions.isCurrent, true)) + .orderBy(policyVersions.policyType) + + // 전체 정책 히스토리 + const allPolicies = await db + .select() + .from(policyVersions) + .orderBy(desc(policyVersions.createdAt)) + + // 정책 타입별로 그룹화 + const policiesByType = { + privacy_policy: allPolicies.filter(p => p.policyType === 'privacy_policy'), + terms_of_service: allPolicies.filter(p => p.policyType === 'terms_of_service') + } + + // 현재 정책 맵 + const currentPolicyMap = {} + currentPolicies.forEach(policy => { + currentPolicyMap[policy.policyType] = policy + }) + + return { + currentPolicies: currentPolicyMap, + allPolicies: policiesByType, + stats: { + totalVersions: allPolicies.length, + privacyVersions: policiesByType.privacy_policy.length, + termsVersions: policiesByType.terms_of_service.length, + lastUpdate: allPolicies[0]?.createdAt || null + } + } + } catch (error) { + console.error('Failed to fetch policies:', error) + return { + currentPolicies: {}, + allPolicies: { privacy_policy: [], terms_of_service: [] }, + stats: { totalVersions: 0, privacyVersions: 0, termsVersions: 0, lastUpdate: null } + } + } +} + +export default async function PoliciesPage() { + const data = await getPoliciesData() + + return ( +
+ {/* 헤더 */} +
+
+

정책 관리

+

+ 개인정보 처리방침과 이용약관을 버전별로 관리합니다 +

+
+
+ + {/* 통계 카드들 */} +
+ + + 총 버전 수 + + + +
{data.stats.totalVersions}
+

+ 전체 정책 버전 +

+
+
+ + + + 개인정보 정책 + + + +
{data.stats.privacyVersions}
+

+ 버전 수 +

+
+
+ + + + 이용약관 + + + +
{data.stats.termsVersions}
+

+ 버전 수 +

+
+
+ + + + 최근 업데이트 + + + +
+ {data.stats.lastUpdate + ? new Date(data.stats.lastUpdate).toLocaleDateString('ko-KR') + : 'N/A' + } +
+

+ 마지막 정책 변경 +

+
+
+
+ + {/* 현재 활성 정책들 */} +
+ } + policy={data.currentPolicies.privacy_policy} + type="privacy_policy" + /> + } + policy={data.currentPolicies.terms_of_service} + type="terms_of_service" + /> +
+ + + + {/* 클라이언트 컴포넌트로 편집 기능 제공 */} + }> + + +
+ ) +} + +// 현재 정책 카드 컴포넌트 +function CurrentPolicyCard({ title, icon, policy, type }) { + if (!policy) { + return ( + + + + {icon} + {title} + + + +
+

아직 등록된 정책이 없습니다

+

새 버전을 생성해주세요

+
+
+
+ ) + } + + return ( + + + + {icon} + {title} + v{policy.version} + + + 현재 활성 정책 • 시행일: {new Date(policy.effectiveDate).toLocaleDateString('ko-KR')} + + + +
+ {/* 정책 내용 미리보기 */} +
+
+ {policy.content?.replace(/#{1,6}\s+/g, '').replace(/\*\*(.*?)\*\*/g, '$1').substring(0, 200)}... +
+
+ + {/* 메타 정보 */} +
+
+ + 생성: {new Date(policy.createdAt).toLocaleDateString('ko-KR')} +
+
+ + 관리자 +
+
+
+
+
+ ) +} + +// 로딩 스켈레톤 +function PolicyManagementSkeleton() { + return ( +
+
+
+
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/app/[lng]/evcp/(evcp)/polices/page.tsx b/app/[lng]/evcp/(evcp)/polices/page.tsx deleted file mode 100644 index 46a9e87a..00000000 --- a/app/[lng]/evcp/(evcp)/polices/page.tsx +++ /dev/null @@ -1,238 +0,0 @@ -// app/admin/policies/page.tsx (서버 컴포넌트) -import { Suspense } from 'react' -import { Metadata } from 'next' -import { eq, desc } from 'drizzle-orm' -import db from '@/db/db' -import { policyVersions } from '@/db/schema' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Badge } from '@/components/ui/badge' -import { Separator } from '@/components/ui/separator' -import { FileText, Shield, Calendar, User, Clock } from 'lucide-react' -import { PolicyManagementClient } from '@/components/polices/policy-management-client' - -export const metadata: Metadata = { - title: '정책 관리 | eVCP Admin', - description: '개인정보 처리방침 및 이용약관 관리' -} - -// 정책 데이터 조회 함수 -async function getPoliciesData() { - try { - // 현재 활성 정책들 - const currentPolicies = await db - .select() - .from(policyVersions) - .where(eq(policyVersions.isCurrent, true)) - .orderBy(policyVersions.policyType) - - // 전체 정책 히스토리 - const allPolicies = await db - .select() - .from(policyVersions) - .orderBy(desc(policyVersions.createdAt)) - - // 정책 타입별로 그룹화 - const policiesByType = { - privacy_policy: allPolicies.filter(p => p.policyType === 'privacy_policy'), - terms_of_service: allPolicies.filter(p => p.policyType === 'terms_of_service') - } - - // 현재 정책 맵 - const currentPolicyMap = {} - currentPolicies.forEach(policy => { - currentPolicyMap[policy.policyType] = policy - }) - - return { - currentPolicies: currentPolicyMap, - allPolicies: policiesByType, - stats: { - totalVersions: allPolicies.length, - privacyVersions: policiesByType.privacy_policy.length, - termsVersions: policiesByType.terms_of_service.length, - lastUpdate: allPolicies[0]?.createdAt || null - } - } - } catch (error) { - console.error('Failed to fetch policies:', error) - return { - currentPolicies: {}, - allPolicies: { privacy_policy: [], terms_of_service: [] }, - stats: { totalVersions: 0, privacyVersions: 0, termsVersions: 0, lastUpdate: null } - } - } -} - -export default async function PoliciesPage() { - const data = await getPoliciesData() - - return ( -
- {/* 헤더 */} -
-
-

정책 관리

-

- 개인정보 처리방침과 이용약관을 버전별로 관리합니다 -

-
-
- - {/* 통계 카드들 */} -
- - - 총 버전 수 - - - -
{data.stats.totalVersions}
-

- 전체 정책 버전 -

-
-
- - - - 개인정보 정책 - - - -
{data.stats.privacyVersions}
-

- 버전 수 -

-
-
- - - - 이용약관 - - - -
{data.stats.termsVersions}
-

- 버전 수 -

-
-
- - - - 최근 업데이트 - - - -
- {data.stats.lastUpdate - ? new Date(data.stats.lastUpdate).toLocaleDateString('ko-KR') - : 'N/A' - } -
-

- 마지막 정책 변경 -

-
-
-
- - {/* 현재 활성 정책들 */} -
- } - policy={data.currentPolicies.privacy_policy} - type="privacy_policy" - /> - } - policy={data.currentPolicies.terms_of_service} - type="terms_of_service" - /> -
- - - - {/* 클라이언트 컴포넌트로 편집 기능 제공 */} - }> - - -
- ) -} - -// 현재 정책 카드 컴포넌트 -function CurrentPolicyCard({ title, icon, policy, type }) { - if (!policy) { - return ( - - - - {icon} - {title} - - - -
-

아직 등록된 정책이 없습니다

-

새 버전을 생성해주세요

-
-
-
- ) - } - - return ( - - - - {icon} - {title} - v{policy.version} - - - 현재 활성 정책 • 시행일: {new Date(policy.effectiveDate).toLocaleDateString('ko-KR')} - - - -
- {/* 정책 내용 미리보기 */} -
-
- {policy.content?.replace(/#{1,6}\s+/g, '').replace(/\*\*(.*?)\*\*/g, '$1').substring(0, 200)}... -
-
- - {/* 메타 정보 */} -
-
- - 생성: {new Date(policy.createdAt).toLocaleDateString('ko-KR')} -
-
- - 관리자 -
-
-
-
-
- ) -} - -// 로딩 스켈레톤 -function PolicyManagementSkeleton() { - return ( -
-
-
-
-
-
-
-
- ) -} \ No newline at end of file diff --git a/app/[lng]/evcp/(evcp)/pq_new/[vendorId]/[submissionId]/page.tsx b/app/[lng]/evcp/(evcp)/pq_new/[vendorId]/[submissionId]/page.tsx index bd8d2347..c8b0e9b8 100644 --- a/app/[lng]/evcp/(evcp)/pq_new/[vendorId]/[submissionId]/page.tsx +++ b/app/[lng]/evcp/(evcp)/pq_new/[vendorId]/[submissionId]/page.tsx @@ -12,6 +12,7 @@ import { Separator } from "@/components/ui/separator" import { getPQById, getPQDataByVendorId } from "@/lib/pq/service" import { unstable_noStore as noStore } from 'next/cache' import { PQReviewWrapper } from "@/components/pq-input/pq-review-wrapper" +import { formatDate } from "@/lib/utils" export const metadata: Metadata = { title: "PQ 검토", @@ -93,7 +94,7 @@ export default async function PQReviewPage(props: PQReviewPageProps) { 제출 완료 - 협력업체가 {formatDate(pqSubmission.submittedAt)}에 PQ를 제출했습니다. 검토 후 승인 또는 거부할 수 있습니다. + 협력업체가 {formatDate(pqSubmission.submittedAt, "kr")}에 PQ를 제출했습니다. 검토 후 승인 또는 거부할 수 있습니다. )} @@ -102,7 +103,7 @@ export default async function PQReviewPage(props: PQReviewPageProps) { 승인됨 - {formatDate(pqSubmission.approvedAt)}에 승인되었습니다. + {formatDate(pqSubmission.approvedAt, "kr")}에 승인되었습니다. )} @@ -111,7 +112,7 @@ export default async function PQReviewPage(props: PQReviewPageProps) { 거부됨 - {formatDate(pqSubmission.rejectedAt)}에 거부되었습니다. + {formatDate(pqSubmission.rejectedAt, "kr")}에 거부되었습니다. {pqSubmission.rejectReason && (
사유: {pqSubmission.rejectReason} @@ -202,16 +203,4 @@ function getStatusVariant(status: string): "default" | "outline" | "secondary" | default: return "outline"; } -} - -// 날짜 형식화 함수 -function formatDate(date: Date | null) { - if (!date) return "날짜 없음"; - return new Date(date).toLocaleDateString("ko-KR", { - year: "numeric", - month: "long", - day: "numeric", - hour: "2-digit", - minute: "2-digit" - }); } \ No newline at end of file diff --git a/app/[lng]/procurement/(procurement)/pq_new/[vendorId]/[submissionId]/page.tsx b/app/[lng]/procurement/(procurement)/pq_new/[vendorId]/[submissionId]/page.tsx index b82075e9..b4b51363 100644 --- a/app/[lng]/procurement/(procurement)/pq_new/[vendorId]/[submissionId]/page.tsx +++ b/app/[lng]/procurement/(procurement)/pq_new/[vendorId]/[submissionId]/page.tsx @@ -12,6 +12,7 @@ import { Separator } from "@/components/ui/separator" import { getPQById, getPQDataByVendorId } from "@/lib/pq/service" import { unstable_noStore as noStore } from 'next/cache' import { PQReviewWrapper } from "@/components/pq-input/pq-review-wrapper" +import { formatDate } from "@/lib/utils" export const metadata: Metadata = { title: "PQ 검토", @@ -93,7 +94,7 @@ export default async function PQReviewPage(props: PQReviewPageProps) { 제출 완료 - 협력업체가 {formatDate(pqSubmission.submittedAt)}에 PQ를 제출했습니다. 검토 후 승인 또는 거부할 수 있습니다. + 협력업체가 {formatDate(pqSubmission.submittedAt, "kr")}에 PQ를 제출했습니다. 검토 후 승인 또는 거부할 수 있습니다. )} @@ -102,7 +103,7 @@ export default async function PQReviewPage(props: PQReviewPageProps) { 승인됨 - {formatDate(pqSubmission.approvedAt)}에 승인되었습니다. + {formatDate(pqSubmission.approvedAt, "kr")}에 승인되었습니다. )} @@ -111,7 +112,7 @@ export default async function PQReviewPage(props: PQReviewPageProps) { 거부됨 - {formatDate(pqSubmission.rejectedAt)}에 거부되었습니다. + {formatDate(pqSubmission.rejectedAt, "kr")}에 거부되었습니다. {pqSubmission.rejectReason && (
사유: {pqSubmission.rejectReason} @@ -202,16 +203,4 @@ function getStatusVariant(status: string): "default" | "outline" | "secondary" | default: return "outline"; } -} - -// 날짜 형식화 함수 -function formatDate(date: Date | null) { - if (!date) return "날짜 없음"; - return new Date(date).toLocaleDateString("ko-KR", { - year: "numeric", - month: "long", - day: "numeric", - hour: "2-digit", - minute: "2-digit" - }); } \ No newline at end of file diff --git a/app/api/basic-contract/get-template/route.ts b/app/api/basic-contract/get-template/route.ts index 111532f0..ad30ae89 100644 --- a/app/api/basic-contract/get-template/route.ts +++ b/app/api/basic-contract/get-template/route.ts @@ -26,8 +26,27 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: '템플릿 파일 경로가 없습니다.' }, { status: 404 }); } - // 파일 시스템에서 파일 읽기 - const fullPath = path.join(process.cwd(), "public", template.filePath); + // 환경에 따른 파일 경로 설정 + let fullPath: string; + + if (process.env.NODE_ENV === 'production') { + // production: NAS 경로 사용 + const nasPath = process.env.NAS_PATH || "/evcp_nas"; + // template.filePath가 /api/files/로 시작하는 경우 제거하고 basicContract로 시작하도록 정리 + const cleanPath = template.filePath.startsWith('/api/files/') + ? template.filePath.replace('/api/files/', '') + : template.filePath.startsWith('/') + ? template.filePath.slice(1) + : template.filePath; + fullPath = path.join(nasPath, cleanPath); + } else { + // development: public 폴더 사용 + // template.filePath에서 앞의 슬래시를 제거 + const cleanPath = template.filePath.startsWith('/') ? template.filePath.slice(1) : template.filePath; + fullPath = path.join(process.cwd(), "public", cleanPath); + } + + console.log(`📁 템플릿 파일 경로: ${fullPath} (환경: ${process.env.NODE_ENV})`); try { const fileBuffer = await fs.readFile(fullPath); @@ -42,7 +61,13 @@ export async function POST(request: NextRequest) { }); } catch (fileError) { console.error('파일 읽기 오류:', fileError); - return NextResponse.json({ error: '템플릿 파일을 읽을 수 없습니다.' }, { status: 500 }); + console.error(`파일 경로: ${fullPath}`); + console.error(`template.filePath: ${template.filePath}`); + return NextResponse.json({ + error: '템플릿 파일을 읽을 수 없습니다.', + details: `파일 경로: ${fullPath}`, + originalPath: template.filePath + }, { status: 500 }); } } catch (error) { -- cgit v1.2.3