From 92eda21e45d902663052575aaa4c4f80bfa2faea Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 4 Aug 2025 09:36:14 +0000 Subject: (대표님) 벤더 문서 변경사항, data-table 변경, sync 변경 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/[lng]/partners/(partners)/settings/layout.tsx | 4 +- app/[lng]/partners/(partners)/settings/page.tsx | 2 +- app/api/revision-attachment/route.ts | 16 +- app/api/revision-upload/route.ts | 18 +- app/api/sync/batches/route.ts | 6 +- app/api/sync/config/route.ts | 12 +- app/api/sync/import/route.ts | 6 +- app/api/sync/import/status/route.ts | 6 +- app/api/sync/status/route.ts | 18 +- app/api/sync/trigger/route.ts | 6 +- app/api/sync/workflow/action/route.ts | 6 +- app/api/sync/workflow/status/route.ts | 6 +- app/globals.css | 33 +- components/data-table/data-table-filter-list.tsx | 4 +- components/data-table/data-table-group-list.tsx | 7 +- components/data-table/data-table-pin-left.tsx | 7 +- components/data-table/data-table-pin-right.tsx | 9 +- components/data-table/data-table-sort-list.tsx | 8 +- components/data-table/data-table-view-options.tsx | 8 +- components/form-data/form-data-table-columns.tsx | 95 +- components/form-data/update-form-sheet.tsx | 310 +- components/layout/GroupedMenuRender.tsx | 30 +- components/layout/Header.tsx | 54 +- components/layout/MobileMenu.tsx | 30 +- .../ship-vendor-document/add-attachment-dialog.tsx | 5 +- .../ship-vendor-document/new-revision-dialog.tsx | 5 +- config/menuConfig.ts | 1054 +- db/migrations/0244_nebulous_mandarin.sql | 5 + db/migrations/0245_wild_living_mummy.sql | 1 + db/migrations/0246_gorgeous_justin_hammer.sql | 191 + db/migrations/0247_bright_bullseye.sql | 43 + db/migrations/0248_modern_earthquake.sql | 1 + db/migrations/0249_eminent_wild_child.sql | 209 + db/migrations/0250_clever_dreaming_celestial.sql | 229 + db/migrations/meta/0244_snapshot.json | 43595 ++++++++++++++++++ db/migrations/meta/0245_snapshot.json | 43595 ++++++++++++++++++ db/migrations/meta/0246_snapshot.json | 43661 ++++++++++++++++++ db/migrations/meta/0247_snapshot.json | 43587 ++++++++++++++++++ db/migrations/meta/0248_snapshot.json | 43587 ++++++++++++++++++ db/migrations/meta/0249_snapshot.json | 43587 ++++++++++++++++++ db/migrations/meta/0250_snapshot.json | 43671 +++++++++++++++++++ db/migrations/meta/_journal.json | 49 + db/schema/vendorDocu.ts | 303 +- hooks/use-sync-status.ts | 102 +- i18n/locales/en/menu.json | 226 + i18n/locales/en/translation.json | 9 + i18n/locales/ko/menu.json | 226 + i18n/locales/ko/translation.json | 9 + lib/forms/services.ts | 1 + lib/sedp/get-form-tags.ts | 18 +- lib/vendor-document-list/dolce-upload-service.ts | 17 +- .../enhanced-document-service.ts | 8 +- lib/vendor-document-list/import-service.ts | 56 +- .../plant/document-stage-dialogs.tsx | 446 +- .../plant/document-stage-toolbar.tsx | 103 + .../plant/document-stages-columns.tsx | 10 +- .../plant/document-stages-expanded-content.tsx | 4 +- .../plant/document-stages-service.ts | 400 +- .../plant/document-stages-table.tsx | 107 +- lib/vendor-document-list/service.ts | 15 + .../ship/enhanced-doc-table-toolbar-actions.tsx | 80 +- .../ship/enhanced-documents-table.tsx | 111 +- .../ship/import-from-dolce-button.tsx | 363 +- .../ship/send-to-shi-button.tsx | 32 +- lib/vendor-document-list/sync-service.ts | 270 +- types/table.d.ts | 2 +- 66 files changed, 308796 insertions(+), 1898 deletions(-) create mode 100644 db/migrations/0244_nebulous_mandarin.sql create mode 100644 db/migrations/0245_wild_living_mummy.sql create mode 100644 db/migrations/0246_gorgeous_justin_hammer.sql create mode 100644 db/migrations/0247_bright_bullseye.sql create mode 100644 db/migrations/0248_modern_earthquake.sql create mode 100644 db/migrations/0249_eminent_wild_child.sql create mode 100644 db/migrations/0250_clever_dreaming_celestial.sql create mode 100644 db/migrations/meta/0244_snapshot.json create mode 100644 db/migrations/meta/0245_snapshot.json create mode 100644 db/migrations/meta/0246_snapshot.json create mode 100644 db/migrations/meta/0247_snapshot.json create mode 100644 db/migrations/meta/0248_snapshot.json create mode 100644 db/migrations/meta/0249_snapshot.json create mode 100644 db/migrations/meta/0250_snapshot.json create mode 100644 i18n/locales/en/menu.json create mode 100644 i18n/locales/ko/menu.json create mode 100644 lib/vendor-document-list/plant/document-stage-toolbar.tsx diff --git a/app/[lng]/partners/(partners)/settings/layout.tsx b/app/[lng]/partners/(partners)/settings/layout.tsx index f3cea7d2..3b8f48a3 100644 --- a/app/[lng]/partners/(partners)/settings/layout.tsx +++ b/app/[lng]/partners/(partners)/settings/layout.tsx @@ -4,7 +4,7 @@ import { Separator } from "@/components/ui/separator" import { SidebarNav } from "@/components/layout/sidebar-nav" export const metadata: Metadata = { - title: "설정", + title: "Setting", // description: "Advanced form example using react-hook-form and Zod.", } @@ -46,7 +46,7 @@ export default async function SettingsLayout({
-

설정

+

Setting

{/*

Manage your account settings and preferences.

*/} diff --git a/app/[lng]/partners/(partners)/settings/page.tsx b/app/[lng]/partners/(partners)/settings/page.tsx index 412597a5..727be46b 100644 --- a/app/[lng]/partners/(partners)/settings/page.tsx +++ b/app/[lng]/partners/(partners)/settings/page.tsx @@ -149,7 +149,7 @@ export default function SettingsAccountPage() {
-

계정

+

Account

{/*

Update your account settings and manage your profile information.

*/} diff --git a/app/api/revision-attachment/route.ts b/app/api/revision-attachment/route.ts index 12834085..092eed8d 100644 --- a/app/api/revision-attachment/route.ts +++ b/app/api/revision-attachment/route.ts @@ -10,6 +10,7 @@ import { documents, revisions, documentAttachments, + issueStages, } from "@/db/schema/vendorDocu" import { eq } from "drizzle-orm" @@ -50,10 +51,11 @@ export async function POST(request: NextRequest) { usage: revisions.usage, usageType: revisions.usageType, issueStageId: revisions.issueStageId, - contractId: documents.contractId, + projectId: documents.projectId, }) .from(revisions) - .leftJoin(documents, eq(documents.id, revisions.issueStageId)) + .innerJoin(issueStages, eq(revisions.issueStageId, issueStages.id)) + .innerJoin(documents, eq(issueStages.documentId, documents.id)) .where(eq(revisions.id, revisionId)) .limit(1) @@ -95,7 +97,7 @@ export async function POST(request: NextRequest) { // change_logs: attachment CREATE await logAttachmentChange( - revisionInfo.contractId, + revisionInfo.projectId, att.id, "CREATE", att, @@ -117,16 +119,16 @@ export async function POST(request: NextRequest) { usage: revisionInfo.usage, usageType: revisionInfo.usageType, uploadedFiles, - contractId: revisionInfo.contractId + projectId: revisionInfo.projectId } }) // 캐시 무효화 try { - // revalidateTag(`enhanced-documents-${result.contractId}`) - revalidateTag(`sync-status-${result.contractId}`) + // revalidateTag(`enhanced-documents-${result.projectId}`) + revalidateTag(`sync-status-${result.projectId}`) - console.log(`✅ Cache invalidated for contract ${result.contractId}`) + console.log(`✅ Cache invalidated for contract ${result.projectId}`) } catch (cacheError) { console.warn('⚠️ Cache invalidation failed:', cacheError) } diff --git a/app/api/revision-upload/route.ts b/app/api/revision-upload/route.ts index b171b89a..bd75e0b5 100644 --- a/app/api/revision-upload/route.ts +++ b/app/api/revision-upload/route.ts @@ -56,7 +56,7 @@ export async function POST(request: NextRequest) { /* ------- 계약 ID 확보 ------- */ const [docInfo] = await db - .select({ contractId: documents.contractId }) + .select({ projectId: documents.projectId }) .from(documents) .where(eq(documents.id, docId)) .limit(1) @@ -124,7 +124,7 @@ export async function POST(request: NextRequest) { // change_logs: CREATE await logRevisionChange( - docInfo.contractId, + docInfo.projectId, revisionId, "CREATE", newRev, @@ -157,7 +157,7 @@ export async function POST(request: NextRequest) { revisionId = revRow.id await logRevisionChange( - docInfo.contractId, + docInfo.projectId, revisionId, "UPDATE", updated, @@ -215,7 +215,7 @@ export async function POST(request: NextRequest) { // change_logs: attachment CREATE await logAttachmentChange( - docInfo.contractId, + docInfo.projectId, att.id, "CREATE", att, @@ -247,7 +247,7 @@ export async function POST(request: NextRequest) { revision, uploadedFiles, mode, - contractId: docInfo.contractId, + projectId: docInfo.projectId, usage, usageType, securityFailures // 보안 실패 정보 포함 @@ -257,12 +257,12 @@ export async function POST(request: NextRequest) { // 캐시 무효화 - 트랜잭션 완료 후에 실행 try { // enhanced documents 캐시 무효화 - revalidateTag(`enhanced-documents-${result.contractId}`) + revalidateTag(`enhanced-documents-${result.projectId}`) // sync status 관련 캐시도 무효화 (필요시) - revalidateTag(`sync-status-${result.contractId}`) + revalidateTag(`sync-status-${result.projectId}`) - console.log(`✅ Cache invalidated for contract ${result.contractId}`) + console.log(`✅ Cache invalidated for contract ${result.projectId}`) } catch (cacheError) { console.warn('⚠️ Cache invalidation failed:', cacheError) // 캐시 무효화 실패해도 업로드는 성공으로 처리 @@ -288,7 +288,7 @@ export async function POST(request: NextRequest) { uploadedFiles: result.uploadedFiles, filesCount: result.uploadedFiles.length, securityFailures: result.securityFailures, // 클라이언트에 보안 실패 정보 전달 - contractId: result.contractId, + projectId: result.projectId, }, }) } catch (e) { diff --git a/app/api/sync/batches/route.ts b/app/api/sync/batches/route.ts index a1ef8d26..7a72530d 100644 --- a/app/api/sync/batches/route.ts +++ b/app/api/sync/batches/route.ts @@ -4,11 +4,11 @@ import { NextRequest, NextResponse } from "next/server" export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url) - const contractId = searchParams.get('contractId') + const projectId = searchParams.get('projectId') const targetSystem = searchParams.get('targetSystem') || 'SHI' const limit = parseInt(searchParams.get('limit') || '10') - if (!contractId) { + if (!projectId) { return NextResponse.json( { error: 'Contract ID is required' }, { status: 400 } @@ -16,7 +16,7 @@ export async function GET(request: NextRequest) { } const batches = await syncService.getRecentSyncBatches( - parseInt(contractId), + parseInt(projectId), targetSystem, limit ) diff --git a/app/api/sync/config/route.ts b/app/api/sync/config/route.ts index e54762fc..db5d17ca 100644 --- a/app/api/sync/config/route.ts +++ b/app/api/sync/config/route.ts @@ -6,10 +6,10 @@ import { authOptions } from "@/app/api/auth/[...nextauth]/route" export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url) - const contractId = searchParams.get('contractId') + const projectId = searchParams.get('projectId') const targetSystem = searchParams.get('targetSystem') || 'SHI' - if (!contractId) { + if (!projectId) { return NextResponse.json( { error: 'Contract ID is required' }, { status: 400 } @@ -17,7 +17,7 @@ export async function GET(request: NextRequest) { } const config = await syncService.getSyncConfig( - parseInt(contractId), + parseInt(projectId), targetSystem ) @@ -48,7 +48,7 @@ export async function POST(request: NextRequest) { } const body = await request.json() const { - contractId, + projectId, targetSystem, endpointUrl, authToken, @@ -57,7 +57,7 @@ export async function POST(request: NextRequest) { maxBatchSize } = body - if (!contractId || !targetSystem || !endpointUrl) { + if (!projectId || !targetSystem || !endpointUrl) { return NextResponse.json( { error: 'Contract ID, target system, and endpoint URL are required' }, { status: 400 } @@ -65,7 +65,7 @@ export async function POST(request: NextRequest) { } await syncService.upsertSyncConfig({ - contractId, + projectId, targetSystem, endpointUrl, authToken, diff --git a/app/api/sync/import/route.ts b/app/api/sync/import/route.ts index a6496578..f32a99e2 100644 --- a/app/api/sync/import/route.ts +++ b/app/api/sync/import/route.ts @@ -11,9 +11,9 @@ export async function POST(request: NextRequest) { } const body = await request.json() - const { contractId, sourceSystem = 'DOLCE' } = body + const { projectId, sourceSystem = 'DOLCE' } = body - if (!contractId) { + if (!projectId) { return NextResponse.json( { error: 'Contract ID is required' }, { status: 400 } @@ -21,7 +21,7 @@ export async function POST(request: NextRequest) { } const result = await importService.importFromExternalSystem( - contractId, + projectId, sourceSystem ) diff --git a/app/api/sync/import/status/route.ts b/app/api/sync/import/status/route.ts index c5b4b0bd..ddb202e4 100644 --- a/app/api/sync/import/status/route.ts +++ b/app/api/sync/import/status/route.ts @@ -12,10 +12,10 @@ export async function GET(request: NextRequest) { } const { searchParams } = new URL(request.url) - const contractId = searchParams.get('contractId') + const projectId = searchParams.get('projectId') const sourceSystem = searchParams.get('sourceSystem') || 'DOLCE' - if (!contractId) { + if (!projectId) { return NextResponse.json( { error: 'Contract ID is required' }, { status: 400 } @@ -23,7 +23,7 @@ export async function GET(request: NextRequest) { } const status = await importService.getImportStatus( - Number(contractId), + Number(projectId), sourceSystem ) diff --git a/app/api/sync/status/route.ts b/app/api/sync/status/route.ts index 886c14df..b4b18577 100644 --- a/app/api/sync/status/route.ts +++ b/app/api/sync/status/route.ts @@ -33,22 +33,22 @@ function serializeForJSON(obj: any): any { export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url) - const contractId = searchParams.get('contractId') + const projectId = searchParams.get('projectId') const targetSystem = searchParams.get('targetSystem') || 'SHI' - - if (!contractId) { + + if (!projectId) { return NextResponse.json( - { error: 'Contract ID is required' }, + { error: 'Project ID is required' }, { status: 400 } ) } - + let status try { // 실제 데이터베이스에서 조회 시도 status = await syncService.getSyncStatus( - parseInt(contractId), + parseInt(projectId), targetSystem ) } catch (error) { @@ -56,7 +56,7 @@ export async function GET(request: NextRequest) { // ✅ 데이터베이스 조회 실패시 임시 목업 데이터 반환 status = { - contractId: parseInt(contractId), + projectId: parseInt(projectId), targetSystem, totalChanges: 15, pendingChanges: 3, // 3건 대기 중 (빨간 뱃지 표시용) @@ -67,10 +67,10 @@ export async function GET(request: NextRequest) { syncEnabled: true } } - + // JSON 직렬화 가능한 형태로 변환 const serializedStatus = serializeForJSON(status) - + return NextResponse.json(serializedStatus) } catch (error) { console.error('Failed to get sync status:', error) diff --git a/app/api/sync/trigger/route.ts b/app/api/sync/trigger/route.ts index 3393365d..3fe58849 100644 --- a/app/api/sync/trigger/route.ts +++ b/app/api/sync/trigger/route.ts @@ -13,9 +13,9 @@ export async function POST(request: NextRequest) { } const body = await request.json() - const { contractId, targetSystem = 'SHI' } = body + const { projectId, targetSystem = 'SHI' } = body - if (!contractId) { + if (!projectId) { return NextResponse.json( { error: 'Contract ID is required' }, { status: 400 } @@ -23,7 +23,7 @@ export async function POST(request: NextRequest) { } const result = await syncService.syncToExternalSystem( - contractId, + projectId, targetSystem, true // manual trigger ) diff --git a/app/api/sync/workflow/action/route.ts b/app/api/sync/workflow/action/route.ts index b6b1a94f..8e311c86 100644 --- a/app/api/sync/workflow/action/route.ts +++ b/app/api/sync/workflow/action/route.ts @@ -12,9 +12,9 @@ export async function POST(request: NextRequest) { } const body = await request.json() - const { contractId, targetSystem = 'SWP', action, documents } = body + const { projectId, targetSystem = 'SWP', action, documents } = body - if (!contractId || !action) { + if (!projectId || !action) { return NextResponse.json( { error: 'Contract ID and action are required' }, { status: 400 } @@ -22,7 +22,7 @@ export async function POST(request: NextRequest) { } const result = await workflowService.executeWorkflowAction( - contractId, + projectId, targetSystem, action, documents || [], diff --git a/app/api/sync/workflow/status/route.ts b/app/api/sync/workflow/status/route.ts index a4c5d1d0..1274f049 100644 --- a/app/api/sync/workflow/status/route.ts +++ b/app/api/sync/workflow/status/route.ts @@ -12,10 +12,10 @@ export async function GET(request: NextRequest) { } const { searchParams } = new URL(request.url) - const contractId = searchParams.get('contractId') + const projectId = searchParams.get('projectId') const targetSystem = searchParams.get('targetSystem') || 'SWP' - if (!contractId) { + if (!projectId) { return NextResponse.json( { error: 'Contract ID is required' }, { status: 400 } @@ -23,7 +23,7 @@ export async function GET(request: NextRequest) { } const status = await workflowService.getWorkflowStatus( - Number(contractId), + Number(projectId), targetSystem ) diff --git a/app/globals.css b/app/globals.css index fa510ec4..a4ee4734 100644 --- a/app/globals.css +++ b/app/globals.css @@ -218,4 +218,35 @@ th[data-read-only="true"] { .tbl-compact tbody tr { @apply hover:bg-muted/40; } /* 선택 */ /* 필요시 행 높이 제한 */ .tbl-compact tbody tr > * { @apply h-8; } /* 32 px 정도 */ -} \ No newline at end of file +} + +/* 그룹 컬럼 구분선 스타일 */ +.group-column-border { + position: relative; +} + +.group-column-border::before, +.group-column-border::after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + width: 2px; + background-color: #e2e8f0; +} + +.group-column-border::before { + left: -1px; +} + +.group-column-border::after { + right: -1px; +} + +/* 그룹 헤더 스타일 */ +.group-header { + background-color: #f8fafc; + font-weight: 600; + border-left: 2px solid #cbd5e1; + border-right: 2px solid #cbd5e1; +} diff --git a/components/data-table/data-table-filter-list.tsx b/components/data-table/data-table-filter-list.tsx index 3efa02ed..6088e912 100644 --- a/components/data-table/data-table-filter-list.tsx +++ b/components/data-table/data-table-filter-list.tsx @@ -580,7 +580,7 @@ export function DataTableFilterList({ {/* 텍스트는 모바일에서 숨기고, sm 이상에서만 보임 */} - {t("Filters")} + {t("tableToolBar.filters")} {filters.length > 0 && ( @@ -603,7 +603,7 @@ export function DataTableFilterList({ )} > {filters.length > 0 ? ( -

{t("Filters")}

+

{t("tableToolBar.filters")}

) : (

{t("nofilters")}

diff --git a/components/data-table/data-table-group-list.tsx b/components/data-table/data-table-group-list.tsx index fcae9a79..213b429f 100644 --- a/components/data-table/data-table-group-list.tsx +++ b/components/data-table/data-table-group-list.tsx @@ -26,6 +26,8 @@ import { SortableItem, SortableDragHandle, } from "@/components/ui/sortable" +import { useTranslation } from '@/i18n/client' +import { useParams, usePathname } from "next/navigation"; interface DataTableGroupListProps { /** TanStack Table 인스턴스 (grouping을 이미 사용할 수 있어야 함) */ @@ -42,6 +44,9 @@ export function DataTableGroupList({ shallow, }: DataTableGroupListProps) { const id = React.useId() + const params = useParams(); + const lng = params?.lng as string; + const { t } = useTranslation(lng); // ------------------------------------------------------ // 1) 초기 그룹핑 상태 + URL Query State 동기화 @@ -156,7 +161,7 @@ export function DataTableGroupList({ aria-controls={`${id}-group-dialog`} >