From 2acf5f8966a40c1c9a97680c8dc263ee3f1ad3d1 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Wed, 2 Jul 2025 00:45:49 +0000 Subject: (대표님/최겸) 20250702 변경사항 업데이트 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../engineering/(engineering)/report/page.tsx | 154 +++++++++++++++----- app/[lng]/evcp/(evcp)/report/page.tsx | 4 +- app/[lng]/page.tsx | 158 +++++++++++++++++++++ .../procurement/(procurement)/report/page.tsx | 154 +++++++++++++++----- app/[lng]/sales/(sales)/report/page.tsx | 154 +++++++++++++++----- app/api/richtext/route.ts | 54 +++++++ app/api/vendor-responses/update-comment/route.ts | 2 + app/api/vendor-responses/update/route.ts | 1 + 8 files changed, 571 insertions(+), 110 deletions(-) create mode 100644 app/[lng]/page.tsx create mode 100644 app/api/richtext/route.ts (limited to 'app') diff --git a/app/[lng]/engineering/(engineering)/report/page.tsx b/app/[lng]/engineering/(engineering)/report/page.tsx index 3efaa7c3..eb932e0f 100644 --- a/app/[lng]/engineering/(engineering)/report/page.tsx +++ b/app/[lng]/engineering/(engineering)/report/page.tsx @@ -1,47 +1,129 @@ -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" +// app/procurement/dashboard/page.tsx +import * as React from "react"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Shell } from "@/components/shell"; +import { ErrorBoundary } from "@/components/error-boundary"; +import { getDashboardData } from "@/lib/dashboard/service"; +import { DashboardClient } from "@/lib/dashboard/dashboard-client"; +// 대시보드 데이터 로딩 컴포넌트 +async function DashboardContent() { + try { + const data = await getDashboardData("engineering"); + + const handleRefresh = async () => { + "use server"; + return await getDashboardData("engineering"); + }; -export default async function IndexPage() { - + return ( + + ); + } catch (error) { + console.error("Dashboard data loading error:", error); + throw error; + } +} +// 대시보드 로딩 스켈레톤 +function DashboardSkeleton() { return ( - +
+ {/* 헤더 스켈레톤 */}
-
-

- Dashboard -

-

- 각종 지표 등을 대시보드로 표현하거나 리포트를 출력할 수 있습니다. -

+
+ + +
+ +
+ + {/* 요약 카드 스켈레톤 */} +
+ {[...Array(4)].map((_, i) => ( +
+
+ + +
+ + +
+ ))} +
+ + {/* 차트 스켈레톤 */} +
+ {[...Array(2)].map((_, i) => ( +
+
+ + +
+ +
+ ))} +
+ + {/* 탭 스켈레톤 */} +
+ +
+ {[...Array(6)].map((_, i) => ( +
+ +
+
+ + +
+
+ + + +
+ +
+
+ ))}
+
+ ); +} - }> - {/* */} - - - - } +// 에러 표시 컴포넌트 +function DashboardError({ error, reset }: { error: Error; reset: () => void }) { + return ( +
+
+

대시보드를 불러올 수 없습니다

+

+ {error.message || "알 수 없는 오류가 발생했습니다."} +

+
+ +
+ ); +} + +export default async function DashboardPage() { + return ( + + + }> + + + - ) -} \ No newline at end of file + ); +} diff --git a/app/[lng]/evcp/(evcp)/report/page.tsx b/app/[lng]/evcp/(evcp)/report/page.tsx index eeb6a91d..95566b05 100644 --- a/app/[lng]/evcp/(evcp)/report/page.tsx +++ b/app/[lng]/evcp/(evcp)/report/page.tsx @@ -10,11 +10,11 @@ import { DashboardClient } from "@/lib/dashboard/dashboard-client"; // 대시보드 데이터 로딩 컴포넌트 async function DashboardContent() { try { - const data = await getDashboardData("procurement"); + const data = await getDashboardData("evcp"); const handleRefresh = async () => { "use server"; - return await getDashboardData("procurement"); + return await getDashboardData("evcp"); }; return ( diff --git a/app/[lng]/page.tsx b/app/[lng]/page.tsx new file mode 100644 index 00000000..2ee83857 --- /dev/null +++ b/app/[lng]/page.tsx @@ -0,0 +1,158 @@ +import React from 'react'; +import Link from 'next/link'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { ShoppingCart, Users, Settings, ArrowRight, Building2 } from 'lucide-react'; + +export default function LandingPage() { + const portals = [ + { + id: 'sales', + title: '기술영업포탈', + description: '기술 영업 단계에서의 RFQ를 관리할 수 있는 통합 플랫폼', + icon: Users, + color: 'from-emerald-500 to-teal-500', + href: '/sales', + features: ['벤더 관리', '기술 영업 RFQ'] + }, + { + id: 'purchase', + title: '구매포탈', + description: '협력업체에서부터 마지막 발주까지 원스톱 구매 솔루션', + icon: ShoppingCart, + color: 'from-blue-500 to-cyan-500', + href: '/procurement', + features: ['협력업체 관리', '구매관리'] + }, + + { + id: 'design', + title: '설계포탈', + description: '벤더가 플랫폼을 통해 데이터와 문서를 제출할 수 있게 하고 TBE를 처리할 수 있는 플랫폼', + icon: Settings, + color: 'from-purple-500 to-pink-500', + href: '/engineering', + features: ['설계 기준정보관리', 'TBE'] + } + ]; + + + + return ( +
+ {/* Header */} +
+
+
+
+ +

+ enterprise Vendor Co-work Platform +

+
+

+ 통합된 비즈니스 솔루션으로 구매부터 설계까지, +
모든 업무 프로세스를 하나의 플랫폼에서 관리하세요 +

+ + Enterprise Ready + +
+
+ + {/* Main Portal Selection */} +
+
+

+ 포털을 선택하세요 +

+

+ 각 포털은 특화된 기능과 도구를 제공하여 업무 효율성을 극대화합니다 +

+
+ +
+ {portals.map((portal) => { + const Icon = portal.icon; + return ( + + +
+ + +
+ +
+ + {portal.title} + + + {portal.description} + +
+ + +
+

주요 기능

+
+ {portal.features.map((feature, idx) => ( +
+
+ {feature} +
+ ))} +
+
+ + +
+
+ + ); + })} +
+ + {/* Additional Info Section */} +
+
+

+ 모든 포털이 연동됩니다 +

+

+ 구매, 영업, 설계 포털 간의 데이터가 실시간으로 동기화되어 + 효율적인 업무 협업이 가능합니다 +

+
+ 실시간 동기화 + 통합 대시보드 + 권한 관리 + 보안 인증 +
+
+
+
+ + {/* Footer */} +
+
+
+ + enterprise Vendor Co-work Platform +
+

+ © 2025 삼성중공업. All rights reserved. +

+ {/*
+ 이용약관 + 개인정보처리방침 + 고객지원 +
*/} +
+
+
+ ); +} \ No newline at end of file diff --git a/app/[lng]/procurement/(procurement)/report/page.tsx b/app/[lng]/procurement/(procurement)/report/page.tsx index 3efaa7c3..800fbd8b 100644 --- a/app/[lng]/procurement/(procurement)/report/page.tsx +++ b/app/[lng]/procurement/(procurement)/report/page.tsx @@ -1,47 +1,129 @@ -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" +// app/procurement/dashboard/page.tsx +import * as React from "react"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Shell } from "@/components/shell"; +import { ErrorBoundary } from "@/components/error-boundary"; +import { getDashboardData } from "@/lib/dashboard/service"; +import { DashboardClient } from "@/lib/dashboard/dashboard-client"; +// 대시보드 데이터 로딩 컴포넌트 +async function DashboardContent() { + try { + const data = await getDashboardData("procurement"); + + const handleRefresh = async () => { + "use server"; + return await getDashboardData("procurement"); + }; -export default async function IndexPage() { - + return ( + + ); + } catch (error) { + console.error("Dashboard data loading error:", error); + throw error; + } +} +// 대시보드 로딩 스켈레톤 +function DashboardSkeleton() { return ( - +
+ {/* 헤더 스켈레톤 */}
-
-

- Dashboard -

-

- 각종 지표 등을 대시보드로 표현하거나 리포트를 출력할 수 있습니다. -

+
+ + +
+ +
+ + {/* 요약 카드 스켈레톤 */} +
+ {[...Array(4)].map((_, i) => ( +
+
+ + +
+ + +
+ ))} +
+ + {/* 차트 스켈레톤 */} +
+ {[...Array(2)].map((_, i) => ( +
+
+ + +
+ +
+ ))} +
+ + {/* 탭 스켈레톤 */} +
+ +
+ {[...Array(6)].map((_, i) => ( +
+ +
+
+ + +
+
+ + + +
+ +
+
+ ))}
+
+ ); +} - }> - {/* */} - - - - } +// 에러 표시 컴포넌트 +function DashboardError({ error, reset }: { error: Error; reset: () => void }) { + return ( +
+
+

대시보드를 불러올 수 없습니다

+

+ {error.message || "알 수 없는 오류가 발생했습니다."} +

+
+ +
+ ); +} + +export default async function DashboardPage() { + return ( + + + }> + + + - ) -} \ No newline at end of file + ); +} diff --git a/app/[lng]/sales/(sales)/report/page.tsx b/app/[lng]/sales/(sales)/report/page.tsx index 3efaa7c3..33225e33 100644 --- a/app/[lng]/sales/(sales)/report/page.tsx +++ b/app/[lng]/sales/(sales)/report/page.tsx @@ -1,47 +1,129 @@ -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" +// app/procurement/dashboard/page.tsx +import * as React from "react"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Shell } from "@/components/shell"; +import { ErrorBoundary } from "@/components/error-boundary"; +import { getDashboardData } from "@/lib/dashboard/service"; +import { DashboardClient } from "@/lib/dashboard/dashboard-client"; +// 대시보드 데이터 로딩 컴포넌트 +async function DashboardContent() { + try { + const data = await getDashboardData("sales"); + + const handleRefresh = async () => { + "use server"; + return await getDashboardData("sales"); + }; -export default async function IndexPage() { - + return ( + + ); + } catch (error) { + console.error("Dashboard data loading error:", error); + throw error; + } +} +// 대시보드 로딩 스켈레톤 +function DashboardSkeleton() { return ( - +
+ {/* 헤더 스켈레톤 */}
-
-

- Dashboard -

-

- 각종 지표 등을 대시보드로 표현하거나 리포트를 출력할 수 있습니다. -

+
+ + +
+ +
+ + {/* 요약 카드 스켈레톤 */} +
+ {[...Array(4)].map((_, i) => ( +
+
+ + +
+ + +
+ ))} +
+ + {/* 차트 스켈레톤 */} +
+ {[...Array(2)].map((_, i) => ( +
+
+ + +
+ +
+ ))} +
+ + {/* 탭 스켈레톤 */} +
+ +
+ {[...Array(6)].map((_, i) => ( +
+ +
+
+ + +
+
+ + + +
+ +
+
+ ))}
+
+ ); +} - }> - {/* */} - - - - } +// 에러 표시 컴포넌트 +function DashboardError({ error, reset }: { error: Error; reset: () => void }) { + return ( +
+
+

대시보드를 불러올 수 없습니다

+

+ {error.message || "알 수 없는 오류가 발생했습니다."} +

+
+ +
+ ); +} + +export default async function DashboardPage() { + return ( + + + }> + + + - ) -} \ No newline at end of file + ); +} diff --git a/app/api/richtext/route.ts b/app/api/richtext/route.ts new file mode 100644 index 00000000..3d050dc5 --- /dev/null +++ b/app/api/richtext/route.ts @@ -0,0 +1,54 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { writeFile, mkdir } from 'fs/promises'; +import { join } from 'path'; +import { randomUUID } from 'crypto'; + +export async function POST(req: NextRequest) { + try { + const formData = await req.formData(); + const file = formData.get('file') as File; + + console.log(file) + + if (!file) { + return NextResponse.json({ error: '파일이 없습니다.' }, { status: 400 }); + } + + // 파일 확장자 검증 + const allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif']; + if (!allowedTypes.includes(file.type)) { + return NextResponse.json({ error: '지원하지 않는 파일 형식입니다.' }, { status: 400 }); + } + + // 파일 크기 제한 (5MB) + if (file.size > 5 * 1024 * 1024) { + return NextResponse.json({ error: '파일 크기는 5MB 이하여야 합니다.' }, { status: 400 }); + } + + // 업로드 디렉토리 생성 + const uploadDir = join(process.cwd(), 'public', 'uploads', 'richtext'); + await mkdir(uploadDir, { recursive: true }); + + // 고유한 파일명 생성 + const fileExtension = file.name.split('.').pop(); + const fileName = `${randomUUID()}.${fileExtension}`; + const filePath = join(uploadDir, fileName); + + // 파일 저장 + const arrayBuffer = await file.arrayBuffer(); + await writeFile(filePath, new Uint8Array(arrayBuffer)); + + // 공개 URL 반환 + const fileUrl = `/uploads/richtext/${fileName}`; + + return NextResponse.json({ + success: true, + url: fileUrl, + message: '이미지가 성공적으로 업로드되었습니다.' + }); + + } catch (error) { + console.error('파일 업로드 실패:', error); + return NextResponse.json({ error: '파일 업로드에 실패했습니다.' }, { status: 500 }); + } +} \ No newline at end of file diff --git a/app/api/vendor-responses/update-comment/route.ts b/app/api/vendor-responses/update-comment/route.ts index 212173d7..f1e4c487 100644 --- a/app/api/vendor-responses/update-comment/route.ts +++ b/app/api/vendor-responses/update-comment/route.ts @@ -5,6 +5,7 @@ import { vendorAttachmentResponses } from "@/db/schema"; import { getServerSession } from "next-auth/next" import { authOptions } from "@/app/api/auth/[...nextauth]/route" +import { eq } from "drizzle-orm"; export async function POST(request: NextRequest) { try { @@ -34,6 +35,7 @@ export async function POST(request: NextRequest) { responseComment, vendorComment, updatedAt: new Date(), + updatedBy:Number(session?.user.id) }) .where(eq(vendorAttachmentResponses.id, parseInt(responseId))) .returning(); diff --git a/app/api/vendor-responses/update/route.ts b/app/api/vendor-responses/update/route.ts index 8771b062..5ee31d4d 100644 --- a/app/api/vendor-responses/update/route.ts +++ b/app/api/vendor-responses/update/route.ts @@ -94,6 +94,7 @@ export async function POST(request: NextRequest) { vendorComment, respondedAt: respondedAt ? new Date(respondedAt) : null, updatedAt: new Date(), + updatedBy:Number(session?.user.id) }) .where(eq(vendorAttachmentResponses.id, parseInt(responseId))) .returning(); -- cgit v1.2.3