diff options
Diffstat (limited to 'app')
8 files changed, 613 insertions, 14 deletions
diff --git a/app/[lng]/partners/(partners)/vendor-data-plant/[projectCode]/[packageCode]/eng/[formCode]/page.tsx b/app/[lng]/partners/(partners)/vendor-data-plant/[projectCode]/[packageCode]/eng/[formCode]/page.tsx new file mode 100644 index 00000000..351fbca3 --- /dev/null +++ b/app/[lng]/partners/(partners)/vendor-data-plant/[projectCode]/[packageCode]/eng/[formCode]/page.tsx @@ -0,0 +1,95 @@ +// app/[lng]/partners/vendor-data-plant/[projectCode]/[packageCode]/eng/[formCode]/page.tsx +import DynamicTable from "@/components/form-data-plant/form-data-table"; +import { getFormData, getFormId,getProjectIdByCode } from "@/lib/forms-plant/services"; +import { useTranslation } from "@/i18n"; +import { Skeleton } from "@/components/ui/skeleton"; + +interface EngineeringFormPageProps { + params: { + lng: string; + projectCode: string; + packageCode: string; + formCode: string; + }; + searchParams?: { + mode?: string; + }; +} + +export default async function EngineeringFormPage({ + params, + searchParams, +}: EngineeringFormPageProps) { + // 1) 구조 분해 할당 + const resolvedParams = await params; + + // 2) searchParams도 await 필요 + const resolvedSearchParams = await searchParams; + + // 3) 구조 분해 할당 + const { lng, projectCode, packageCode, formCode } = resolvedParams; + + // i18n 설정 + const { t } = await useTranslation(lng, 'engineering'); + + // URL 쿼리 파라미터에서 mode 가져오기 (await 해서 사용) + const mode = "ENG"; // 기본값은 IM + + // 4) DB 조회 - projectCode와 packageCode를 전달 + const { columns, data, editableFieldsMap } = await getFormData( + formCode, + projectCode, + packageCode + ); + + // 5) formId 조회 - projectCode와 packageCode를 전달 + const { formId } = await getFormId(projectCode, packageCode, formCode, mode); + + const projectId = await getProjectIdByCode(projectCode) + + // 6) 예외 처리 + if (!columns) { + return ( + <p className="text-red-500"> + {t('errors.form_meta_not_found')} + </p> + ); + } + + // 7) 렌더링 + return ( + <div className="space-y-4"> + <div className="flex items-center justify-between"> + <div> + <h3 className="text-lg font-semibold">Engineering Form</h3> + <p className="text-sm text-muted-foreground"> + Project: {projectCode} / Package: {packageCode} / Form: {formCode} + </p> + </div> + </div> + + <div className="space-y-6"> + <DynamicTable + projectId={projectId} + projectCode={projectCode} + packageCode={packageCode} + formCode={formCode} + formId={formId} + columnsJSON={columns} + dataJSON={data} + editableFieldsMap={editableFieldsMap} + mode={"ENG"} + /> + </div> + </div> + ); +} + +function TableSkeleton() { + return ( + <div className="space-y-4"> + <Skeleton className="h-10 w-full" /> + <Skeleton className="h-[400px] w-full" /> + </div> + ); +}
\ No newline at end of file diff --git a/app/[lng]/partners/(partners)/vendor-data-plant/[projectCode]/[packageCode]/im/[formCode]/page.tsx b/app/[lng]/partners/(partners)/vendor-data-plant/[projectCode]/[packageCode]/im/[formCode]/page.tsx new file mode 100644 index 00000000..29188061 --- /dev/null +++ b/app/[lng]/partners/(partners)/vendor-data-plant/[projectCode]/[packageCode]/im/[formCode]/page.tsx @@ -0,0 +1,95 @@ +// app/[lng]/partners/vendor-data-plant/[projectCode]/[packageCode]/eng/[formCode]/page.tsx +import DynamicTable from "@/components/form-data-plant/form-data-table"; +import { getFormData, getFormId, getProjectIdByCode } from "@/lib/forms-plant/services"; +import { useTranslation } from "@/i18n"; +import { Skeleton } from "@/components/ui/skeleton"; + +interface EngineeringFormPageProps { + params: { + lng: string; + projectCode: string; + packageCode: string; + formCode: string; + }; + searchParams?: { + mode?: string; + }; +} + +export default async function IMFormPage({ + params, + searchParams, +}: EngineeringFormPageProps) { + // 1) 구조 분해 할당 + const resolvedParams = await params; + + // 2) searchParams도 await 필요 + const resolvedSearchParams = await searchParams; + + // 3) 구조 분해 할당 + const { lng, projectCode, packageCode, formCode } = resolvedParams; + + // i18n 설정 + const { t } = await useTranslation(lng, 'engineering'); + + // URL 쿼리 파라미터에서 mode 가져오기 (await 해서 사용) + const mode = "IM"; // 기본값은 IM + + // 4) DB 조회 - projectCode와 packageCode를 전달 + const { columns, data, editableFieldsMap } = await getFormData( + formCode, + projectCode, + packageCode + ); + + // 5) formId 조회 - projectCode와 packageCode를 전달 + const { formId } = await getFormId(projectCode, packageCode, formCode, mode); + + const projectId = await getProjectIdByCode(projectCode) + + // 6) 예외 처리 + if (!columns) { + return ( + <p className="text-red-500"> + {t('errors.form_meta_not_found')} + </p> + ); + } + + // 7) 렌더링 + return ( + <div className="space-y-4"> + <div className="flex items-center justify-between"> + <div> + <h3 className="text-lg font-semibold">Engineering Form</h3> + <p className="text-sm text-muted-foreground"> + Project: {projectCode} / Package: {packageCode} / Form: {formCode} + </p> + </div> + </div> + + <div className="space-y-6"> + <DynamicTable + projectId={projectId} + projectCode={projectCode} + packageCode={packageCode} + formCode={formCode} + formId={formId} + columnsJSON={columns} + dataJSON={data} + editableFieldsMap={editableFieldsMap} + mode={"IM"} + /> + </div> + </div> + ); +} + +function TableSkeleton() { + return ( + <div className="space-y-4"> + <Skeleton className="h-10 w-full" /> + <Skeleton className="h-[400px] w-full" /> + </div> + ); +}
\ No newline at end of file diff --git a/app/[lng]/partners/(partners)/vendor-data-plant/[projectCode]/[packageCode]/page.tsx b/app/[lng]/partners/(partners)/vendor-data-plant/[projectCode]/[packageCode]/page.tsx new file mode 100644 index 00000000..4904a8ff --- /dev/null +++ b/app/[lng]/partners/(partners)/vendor-data-plant/[projectCode]/[packageCode]/page.tsx @@ -0,0 +1,37 @@ +// app/[lng]/partners/vendor-data-plant/[projectCode]/[packageCode]/page.tsx + +import { TagsTable } from "@/lib/tags-plant/table/tag-table" + +interface MasterTagListPageProps { + params: Promise<{ + lng: string + projectCode: string + packageCode: string + }> +} + +export default async function MasterTagListPage({ + params, +}: MasterTagListPageProps) { + const { projectCode, packageCode } = await params + + return ( + <div className="space-y-4"> + <div className="flex items-center justify-between"> + <div> + <h3 className="text-lg font-semibold">Master Tag List</h3> + <p className="text-sm text-muted-foreground"> + Project: {projectCode} / Package: {packageCode} + </p> + </div> + </div> + + {/* 완전 클라이언트 컴포넌트 */} + <TagsTable + projectCode={projectCode} + packageCode={packageCode} + formCode="MASTER" + /> + </div> + ) +}
\ No newline at end of file diff --git a/app/[lng]/partners/(partners)/vendor-data-plant/layout.tsx b/app/[lng]/partners/(partners)/vendor-data-plant/layout.tsx index 8a9c43e9..792a3a6a 100644 --- a/app/[lng]/partners/(partners)/vendor-data-plant/layout.tsx +++ b/app/[lng]/partners/(partners)/vendor-data-plant/layout.tsx @@ -2,41 +2,35 @@ import * as React from "react" import { cookies } from "next/headers" import { Shell } from "@/components/shell" -import { getVendorProjectsAndContracts } from "@/lib/vendor-data-plant/services" import { VendorDataContainer } from "@/components/vendor-data-plant/vendor-data-container" import { authOptions } from "@/app/api/auth/[...nextauth]/route" import { getServerSession } from "next-auth" import { InformationButton } from "@/components/information/information-button" import { useTranslation } from "@/i18n" +import { getVendorProjectsWithPackages } from "@/lib/vendor-data/services" interface VendorDataLayoutProps { children: React.ReactNode params: { lng?: string } } -// Layout 컴포넌트는 서버 컴포넌트입니다 export default async function VendorDataLayout({ children, params, }: VendorDataLayoutProps) { - // 기본 언어는 'ko'로 설정, params.locale이 있으면 사용 - const { lng } = await params; + const { lng } = await params const language = lng || 'en' const { t } = await useTranslation(language, 'engineering') const session = await getServerSession(authOptions) const vendorId = session?.user.companyId - // const vendorId = "17" const idAsNumber = Number(vendorId) - // 프로젝트 데이터 가져오기 (type=plant만) - const projects = await getVendorProjectsAndContracts(idAsNumber, "plant") + // 프로젝트 및 패키지 데이터 가져오기 + const projects = await getVendorProjectsWithPackages(idAsNumber, "plant") // 레이아웃 설정 쿠키 가져오기 - // 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") @@ -54,9 +48,6 @@ export default async function VendorDataLayout({ </h2> <InformationButton pagePath="partners/vendor-data-plant" /> </div> - {/* <p className="text-muted-foreground"> - 각종 Data 입력할 수 있습니다 - </p> */} </div> </div> </div> @@ -74,7 +65,6 @@ export default async function VendorDataLayout({ defaultCollapsed={defaultCollapsed} navCollapsedSize={4} > - {/* 페이지별 콘텐츠가 여기에 들어갑니다 */} {children} </VendorDataContainer> )} diff --git a/app/api/cron/form-tags-plant/start/route.ts b/app/api/cron/form-tags-plant/start/route.ts new file mode 100644 index 00000000..17eb8979 --- /dev/null +++ b/app/api/cron/form-tags-plant/start/route.ts @@ -0,0 +1,141 @@ +// app/api/cron/form-tags/start/route.ts +import { NextRequest } from 'next/server'; +import { v4 as uuidv4 } from 'uuid'; +import { revalidateTag } from 'next/cache'; + +// 동기화 작업의 상태를 저장할 Map +// 실제 프로덕션에서는 Redis 또는 DB에 저장하는 것이 좋습니다 +const syncJobs = new Map<string, { + status: 'queued' | 'processing' | 'completed' | 'failed'; + startTime: Date; + endTime?: Date; + result?: any; + error?: string; + progress?: number; + projectCode?: string; + formCode?: string; + packageCode?: string; + mode?: string +}>(); + +export async function POST(request: NextRequest) { + try { + // 요청 데이터 가져오기 + let projectCode: string | undefined; + let formCode: string | undefined; + let packageCode: string | undefined; + let mode: string | undefined; + + + const body = await request.json(); + projectCode = body.projectCode; + formCode = body.formCode; + packageCode = body.packageCode; + mode = body.mode; // 모드 정보 추출 + + + // 고유 ID 생성 + const syncId = uuidv4(); + + // 작업 상태 초기화 + syncJobs.set(syncId, { + status: 'queued', + startTime: new Date(), + formCode, + projectCode, + packageCode, + mode + + }); + + // 비동기 작업 시작 (백그라운드에서 실행) + processTagImport(syncId).catch(error => { + console.error('Background tag import job failed:', error); + syncJobs.set(syncId, { + ...syncJobs.get(syncId)!, + status: 'failed', + endTime: new Date(), + error: error.message || 'Unknown error occurred' + }); + }); + + // 즉시 응답 반환 (작업 ID 포함) + return Response.json({ + success: true, + message: 'Tag import job started', + syncId + }, { status: 200 }); + + } catch (error: any) { + console.error('Failed to start tag import job:', error); + return Response.json({ + success: false, + error: error.message || 'Failed to start tag import job' + }, { status: 500 }); + } +} + +// 백그라운드에서 실행되는 태그 가져오기 작업 +async function processTagImport(syncId: string) { + try { + const jobInfo = syncJobs.get(syncId)!; + const formCode = jobInfo.formCode; + const projectCode = jobInfo.projectCode; + const packageCode = jobInfo.packageCode || 0; + const mode = jobInfo.mode || 0; + + // 상태 업데이트: 처리 중 + syncJobs.set(syncId, { + ...jobInfo, + status: 'processing', + progress: 0, + }); + + if (!formCode || !projectCode ) { + throw new Error('formCode,projectCode is required'); + } + + // 여기서 실제 태그 가져오기 로직 import + const { importTagsFromSEDP } = await import('@/lib/sedp/get-form-tags-plant'); + + // 진행 상황 업데이트를 위한 콜백 함수 + const updateProgress = (progress: number) => { + syncJobs.set(syncId, { + ...syncJobs.get(syncId)!, + progress + }); + }; + + // 실제 태그 가져오기 실행 + const result = await importTagsFromSEDP(formCode, projectCode, packageCode, updateProgress); + + // 명시적으로 캐시 무효화 + revalidateTag(`forms-${packageCode}-${mode}`); + + // 상태 업데이트: 완료 + syncJobs.set(syncId, { + ...syncJobs.get(syncId)!, + status: 'completed', + endTime: new Date(), + result, + progress: 100, + }); + + return result; + } catch (error: any) { + // 에러 발생 시 상태 업데이트 + syncJobs.set(syncId, { + ...syncJobs.get(syncId)!, + status: 'failed', + endTime: new Date(), + error: error.message || 'Unknown error occurred', + }); + + throw error; // 에러 다시 던지기 + } +} + +// 서버 메모리에 저장된 작업 상태 접근 함수 (다른 API에서 사용) +export function getSyncJobStatus(id: string) { + return syncJobs.get(id); +}
\ No newline at end of file diff --git a/app/api/cron/form-tags-plant/status/route.ts b/app/api/cron/form-tags-plant/status/route.ts new file mode 100644 index 00000000..9d288f52 --- /dev/null +++ b/app/api/cron/form-tags-plant/status/route.ts @@ -0,0 +1,46 @@ +// app/api/cron/tags/status/route.ts +import { NextRequest } from 'next/server'; +import { getSyncJobStatus } from '../start/route'; + +export async function GET(request: NextRequest) { + try { + // URL에서 작업 ID 가져오기 + const searchParams = request.nextUrl.searchParams; + const syncId = searchParams.get('id'); + + if (!syncId) { + return Response.json({ + success: false, + error: 'Missing sync ID parameter' + }, { status: 400 }); + } + + // 작업 상태 조회 + const jobStatus = getSyncJobStatus(syncId); + + if (!jobStatus) { + return Response.json({ + success: false, + error: 'Sync job not found' + }, { status: 404 }); + } + + // 작업 상태 반환 + return Response.json({ + success: true, + status: jobStatus.status, + startTime: jobStatus.startTime, + endTime: jobStatus.endTime, + progress: jobStatus.progress, + result: jobStatus.result, + error: jobStatus.error + }, { status: 200 }); + + } catch (error: any) { + console.error('Error retrieving tag import status:', error); + return Response.json({ + success: false, + error: error.message || 'Failed to retrieve tag import status' + }, { status: 500 }); + } +}
\ No newline at end of file diff --git a/app/api/cron/tags-plant/start/route.ts b/app/api/cron/tags-plant/start/route.ts new file mode 100644 index 00000000..83e06935 --- /dev/null +++ b/app/api/cron/tags-plant/start/route.ts @@ -0,0 +1,149 @@ +// app/api/cron/tags/start/route.ts +import { NextRequest } from 'next/server'; +import { v4 as uuidv4 } from 'uuid'; +import { revalidateTag } from 'next/cache'; + +// 동기화 작업의 상태를 저장할 Map +// 실제 프로덕션에서는 Redis 또는 DB에 저장하는 것이 좋습니다 +const syncJobs = new Map<string, { + status: 'queued' | 'processing' | 'completed' | 'failed'; + startTime: Date; + endTime?: Date; + result?: any; + error?: string; + progress?: number; + projectCode?: string; + packageCode?: string; + mode?: string +}>(); + +export async function POST(request: NextRequest) { + try { + // 요청 데이터 가져오기 + let projectCode: string | undefined; + let packageCode: string | undefined; + let mode: string | undefined; + + try { + const body = await request.json(); + projectCode = body.projectCode; + packageCode = body.packageCode; + mode = body.mode; // 모드 정보 추출 + + } catch (error) { + // 요청 본문이 없거나 JSON이 아닌 경우, URL 파라미터 확인 + const searchParams = request.nextUrl.searchParams; + const projectCodeParam = searchParams.get('projectCode'); + const packageCodeParam = searchParams.get('packageCode'); + + if (projectCodeParam&&packageCodeParam) { + projectCode = projectCodeParam; + packageCode = packageCodeParam; + } + mode = searchParams.get('mode') || undefined; + } + + // 고유 ID 생성 + const syncId = uuidv4(); + + // 작업 상태 초기화 + syncJobs.set(syncId, { + status: 'queued', + startTime: new Date(), + projectCode, + packageCode, + mode + }); + + // 비동기 작업 시작 (백그라운드에서 실행) + processTagImport(syncId).catch(error => { + console.error('Background tag import job failed:', error); + syncJobs.set(syncId, { + ...syncJobs.get(syncId)!, + status: 'failed', + endTime: new Date(), + error: error.message || 'Unknown error occurred' + }); + }); + + // 즉시 응답 반환 (작업 ID 포함) + return Response.json({ + success: true, + message: 'Tag import job started', + syncId + }, { status: 200 }); + + } catch (error: any) { + console.error('Failed to start tag import job:', error); + return Response.json({ + success: false, + error: error.message || 'Failed to start tag import job' + }, { status: 500 }); + } +} + +// 백그라운드에서 실행되는 태그 가져오기 작업 +async function processTagImport(syncId: string) { + try { + const jobInfo = syncJobs.get(syncId)!; + const projectCode = jobInfo.projectCode; + const packageCode = jobInfo.packageCode; + const mode = jobInfo.mode; // 모드 정보 추출 + + + // 상태 업데이트: 처리 중 + syncJobs.set(syncId, { + ...jobInfo, + status: 'processing', + progress: 0, + }); + + if (!packageCode) { + throw new Error('Package is required'); + } + + // 여기서 실제 태그 가져오기 로직 import + const { importTagsFromSEDP } = await import('@/lib/sedp/get-tags-plant'); + + // 진행 상황 업데이트를 위한 콜백 함수 + const updateProgress = (progress: number) => { + syncJobs.set(syncId, { + ...syncJobs.get(syncId)!, + progress + }); + }; + + // 실제 태그 가져오기 실행 + const result = await importTagsFromSEDP(projectCode, packageCode,updateProgress, mode); + + // 명시적으로 캐시 무효화 + revalidateTag(`tags-${packageCode}`); + revalidateTag(`forms-${packageCode}-${mode}`); + + // 상태 업데이트: 완료 + syncJobs.set(syncId, { + ...syncJobs.get(syncId)!, + status: 'completed', + endTime: new Date(), + result, + progress: 100, + }); + + return result; + } catch (error: any) { + // 에러 발생 시 상태 업데이트 + syncJobs.set(syncId, { + ...syncJobs.get(syncId)!, + status: 'failed', + endTime: new Date(), + error: error.message || 'Unknown error occurred', + }); + + throw error; // 에러 다시 던지기 + } +} + +// 서버 메모리에 저장된 작업 상태 접근 함수 (다른 API에서 사용) +export function getSyncJobStatus(id: string) { + return syncJobs.get(id); +}
\ No newline at end of file diff --git a/app/api/cron/tags-plant/status/route.ts b/app/api/cron/tags-plant/status/route.ts new file mode 100644 index 00000000..9d288f52 --- /dev/null +++ b/app/api/cron/tags-plant/status/route.ts @@ -0,0 +1,46 @@ +// app/api/cron/tags/status/route.ts +import { NextRequest } from 'next/server'; +import { getSyncJobStatus } from '../start/route'; + +export async function GET(request: NextRequest) { + try { + // URL에서 작업 ID 가져오기 + const searchParams = request.nextUrl.searchParams; + const syncId = searchParams.get('id'); + + if (!syncId) { + return Response.json({ + success: false, + error: 'Missing sync ID parameter' + }, { status: 400 }); + } + + // 작업 상태 조회 + const jobStatus = getSyncJobStatus(syncId); + + if (!jobStatus) { + return Response.json({ + success: false, + error: 'Sync job not found' + }, { status: 404 }); + } + + // 작업 상태 반환 + return Response.json({ + success: true, + status: jobStatus.status, + startTime: jobStatus.startTime, + endTime: jobStatus.endTime, + progress: jobStatus.progress, + result: jobStatus.result, + error: jobStatus.error + }, { status: 200 }); + + } catch (error: any) { + console.error('Error retrieving tag import status:', error); + return Response.json({ + success: false, + error: error.message || 'Failed to retrieve tag import status' + }, { status: 500 }); + } +}
\ No newline at end of file |
