160b78e70f
- Introduced new routes and controllers for managing shipping orders, including listing, saving, and previewing next order numbers. - Added design management routes and controller for handling design requests, projects, tasks, and work logs. - Implemented company code filtering for multi-tenancy support in both shipping order and design request functionalities. - Enhanced the shipping plan routes to include listing and updating plans, improving overall shipping management capabilities. These changes aim to provide comprehensive management features for shipping orders and design processes, facilitating better organization and tracking within the application.
341 lines
17 KiB
TypeScript
341 lines
17 KiB
TypeScript
"use client";
|
|
|
|
import React, { useMemo, useState, useEffect } from "react";
|
|
import dynamic from "next/dynamic";
|
|
import { Loader2 } from "lucide-react";
|
|
import { ScreenViewPageWrapper } from "@/app/(main)/screens/[screenId]/page";
|
|
import { apiClient } from "@/lib/api/client";
|
|
|
|
const LoadingFallback = () => (
|
|
<div className="flex h-full items-center justify-center">
|
|
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
</div>
|
|
);
|
|
|
|
function ScreenCodeResolver({ screenCode }: { screenCode: string }) {
|
|
const [screenId, setScreenId] = useState<number | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
const numericId = parseInt(screenCode);
|
|
if (!isNaN(numericId)) {
|
|
setScreenId(numericId);
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
const resolve = async () => {
|
|
try {
|
|
const res = await apiClient.get("/screen-management/screens", {
|
|
params: { searchTerm: screenCode, size: 50 },
|
|
});
|
|
const items = res.data?.data?.data || res.data?.data || [];
|
|
const arr = Array.isArray(items) ? items : [];
|
|
const exact = arr.find((s: any) => s.screenCode === screenCode);
|
|
const target = exact || arr[0];
|
|
if (target) setScreenId(target.screenId || target.screen_id);
|
|
} catch {
|
|
console.error("스크린 코드 변환 실패:", screenCode);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
resolve();
|
|
}, [screenCode]);
|
|
|
|
if (loading) return <LoadingFallback />;
|
|
if (!screenId) {
|
|
return (
|
|
<div className="flex h-full items-center justify-center">
|
|
<p className="text-sm text-muted-foreground">화면을 찾을 수 없습니다 (코드: {screenCode})</p>
|
|
</div>
|
|
);
|
|
}
|
|
return <ScreenViewPageWrapper screenIdProp={screenId} />;
|
|
}
|
|
|
|
const DashboardViewPage = dynamic(
|
|
() => import("@/app/(main)/dashboard/[dashboardId]/page"),
|
|
{ ssr: false, loading: LoadingFallback },
|
|
);
|
|
|
|
const ADMIN_PAGE_REGISTRY: Record<string, React.ComponentType<any>> = {
|
|
// 관리자 메인
|
|
"/admin": dynamic(() => import("@/app/(main)/admin/page"), { ssr: false, loading: LoadingFallback }),
|
|
|
|
// 메뉴 관리
|
|
"/admin/menu": dynamic(() => import("@/app/(main)/admin/menu/page"), { ssr: false, loading: LoadingFallback }),
|
|
|
|
// 사용자 관리
|
|
"/admin/userMng/userMngList": dynamic(() => import("@/app/(main)/admin/userMng/userMngList/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/userMng/rolesList": dynamic(() => import("@/app/(main)/admin/userMng/rolesList/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/userMng/userAuthList": dynamic(() => import("@/app/(main)/admin/userMng/userAuthList/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/userMng/companyList": dynamic(() => import("@/app/(main)/admin/userMng/companyList/page"), { ssr: false, loading: LoadingFallback }),
|
|
|
|
// 화면 관리
|
|
"/admin/screenMng/screenMngList": dynamic(() => import("@/app/(main)/admin/screenMng/screenMngList/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/screenMng/popScreenMngList": dynamic(() => import("@/app/(main)/admin/screenMng/popScreenMngList/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/screenMng/dashboardList": dynamic(() => import("@/app/(main)/admin/screenMng/dashboardList/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/screenMng/reportList": dynamic(() => import("@/app/(main)/admin/screenMng/reportList/page"), { ssr: false, loading: LoadingFallback }),
|
|
|
|
// 시스템 관리
|
|
"/admin/systemMng/commonCodeList": dynamic(() => import("@/app/(main)/admin/systemMng/commonCodeList/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/systemMng/tableMngList": dynamic(() => import("@/app/(main)/admin/systemMng/tableMngList/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/systemMng/i18nList": dynamic(() => import("@/app/(main)/admin/systemMng/i18nList/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/systemMng/collection-managementList": dynamic(() => import("@/app/(main)/admin/systemMng/collection-managementList/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/systemMng/cascading-managementList": dynamic(() => import("@/app/(main)/admin/cascading-management/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/systemMng/dataflow": dynamic(() => import("@/app/(main)/admin/systemMng/dataflow/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/systemMng/dataflow/node-editorList": dynamic(() => import("@/app/(main)/admin/systemMng/dataflow/page"), { ssr: false, loading: LoadingFallback }),
|
|
|
|
// 자동화 관리
|
|
"/admin/automaticMng/flowMgmtList": dynamic(() => import("@/app/(main)/admin/automaticMng/flowMgmtList/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/automaticMng/batchmngList": dynamic(() => import("@/app/(main)/admin/automaticMng/batchmngList/page"), { ssr: false, loading: LoadingFallback }),
|
|
|
|
// 설계 관리 (커스텀 페이지)
|
|
"/design/task-management": dynamic(() => import("@/app/(main)/design/task-management/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/design/my-work": dynamic(() => import("@/app/(main)/design/my-work/page"), { ssr: false, loading: LoadingFallback }),
|
|
|
|
// 영업 관리 (커스텀 페이지)
|
|
"/sales/shipping-plan": dynamic(() => import("@/app/(main)/sales/shipping-plan/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/sales/shipping-order": dynamic(() => import("@/app/(main)/sales/shipping-order/page"), { ssr: false, loading: LoadingFallback }),
|
|
|
|
// 물류 관리 (커스텀 페이지)
|
|
"/logistics/material-status": dynamic(() => import("@/app/(main)/logistics/material-status/page"), { ssr: false, loading: LoadingFallback }),
|
|
|
|
// 설계 관리 (커스텀 페이지)
|
|
"/design/change-management": dynamic(() => import("@/app/(main)/design/change-management/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/automaticMng/exconList": dynamic(() => import("@/app/(main)/admin/automaticMng/exconList/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/automaticMng/exCallConfList": dynamic(() => import("@/app/(main)/admin/automaticMng/exCallConfList/page"), { ssr: false, loading: LoadingFallback }),
|
|
|
|
// 메일
|
|
"/admin/automaticMng/mail/send": dynamic(() => import("@/app/(main)/admin/automaticMng/mail/send/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/automaticMng/mail/receive": dynamic(() => import("@/app/(main)/admin/automaticMng/mail/receive/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/automaticMng/mail/sent": dynamic(() => import("@/app/(main)/admin/automaticMng/mail/sent/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/automaticMng/mail/drafts": dynamic(() => import("@/app/(main)/admin/automaticMng/mail/drafts/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/automaticMng/mail/trash": dynamic(() => import("@/app/(main)/admin/automaticMng/mail/trash/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/automaticMng/mail/accounts": dynamic(() => import("@/app/(main)/admin/automaticMng/mail/accounts/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/automaticMng/mail/templates": dynamic(() => import("@/app/(main)/admin/automaticMng/mail/templates/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/automaticMng/mail/dashboardList": dynamic(() => import("@/app/(main)/admin/automaticMng/mail/dashboardList/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/automaticMng/mail/bulk-send": dynamic(() => import("@/app/(main)/admin/automaticMng/mail/bulk-send/page"), { ssr: false, loading: LoadingFallback }),
|
|
|
|
// 배치 관리
|
|
"/admin/batch-management": dynamic(() => import("@/app/(main)/admin/batch-management/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/batch-management-new": dynamic(() => import("@/app/(main)/admin/batch-management-new/page"), { ssr: false, loading: LoadingFallback }),
|
|
|
|
// 결재 관리
|
|
"/admin/approvalTemplate": dynamic(() => import("@/app/(main)/admin/approvalTemplate/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/approvalBox": dynamic(() => import("@/app/(main)/admin/approvalBox/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/approvalMng": dynamic(() => import("@/app/(main)/admin/approvalMng/page"), { ssr: false, loading: LoadingFallback }),
|
|
|
|
// 시스템
|
|
"/admin/audit-log": dynamic(() => import("@/app/(main)/admin/audit-log/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/system-notices": dynamic(() => import("@/app/(main)/admin/system-notices/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/aiAssistant": dynamic(() => import("@/app/(main)/admin/aiAssistant/page"), { ssr: false, loading: LoadingFallback }),
|
|
|
|
// 기타
|
|
"/admin/cascading-management": dynamic(() => import("@/app/(main)/admin/cascading-management/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/cascading-relations": dynamic(() => import("@/app/(main)/admin/cascading-management/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/layouts": dynamic(() => import("@/app/(main)/admin/layouts/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/templates": dynamic(() => import("@/app/(main)/admin/templates/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/monitoring": dynamic(() => import("@/app/(main)/admin/monitoring/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/standards": dynamic(() => import("@/app/(main)/admin/standards/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/flow-external-db": dynamic(() => import("@/app/(main)/admin/flow-external-db/page"), { ssr: false, loading: LoadingFallback }),
|
|
"/admin/auto-fill": dynamic(() => import("@/app/(main)/admin/cascading-management/page"), { ssr: false, loading: LoadingFallback }),
|
|
};
|
|
|
|
const DYNAMIC_ADMIN_IMPORTS: Record<string, () => Promise<any>> = {
|
|
"/admin/aiAssistant/dashboard": () => import("@/app/(main)/admin/aiAssistant/dashboard/page"),
|
|
"/admin/aiAssistant/history": () => import("@/app/(main)/admin/aiAssistant/history/page"),
|
|
"/admin/aiAssistant/api-keys": () => import("@/app/(main)/admin/aiAssistant/api-keys/page"),
|
|
"/admin/aiAssistant/api-test": () => import("@/app/(main)/admin/aiAssistant/api-test/page"),
|
|
"/admin/aiAssistant/usage": () => import("@/app/(main)/admin/aiAssistant/usage/page"),
|
|
"/admin/aiAssistant/chat": () => import("@/app/(main)/admin/aiAssistant/chat/page"),
|
|
"/admin/screenMng/barcodeList": () => import("@/app/(main)/admin/screenMng/barcodeList/page"),
|
|
"/admin/automaticMng/batchmngList/create": () => import("@/app/(main)/admin/automaticMng/batchmngList/create/page"),
|
|
"/admin/systemMng/dataflow/node-editorList": () => import("@/app/(main)/admin/systemMng/dataflow/page"),
|
|
"/admin/standards/new": () => import("@/app/(main)/admin/standards/new/page"),
|
|
};
|
|
|
|
const DYNAMIC_ADMIN_PATTERNS: Array<{
|
|
pattern: RegExp;
|
|
getImport: (match: RegExpMatchArray) => Promise<any>;
|
|
extractParams: (match: RegExpMatchArray) => Record<string, string>;
|
|
}> = [
|
|
{
|
|
pattern: /^\/admin\/userMng\/rolesList\/([^/]+)$/,
|
|
getImport: () => import("@/app/(main)/admin/userMng/rolesList/[id]/page"),
|
|
extractParams: (m) => ({ id: m[1] }),
|
|
},
|
|
{
|
|
pattern: /^\/admin\/screenMng\/dashboardList\/([^/]+)$/,
|
|
getImport: () => import("@/app/(main)/admin/screenMng/dashboardList/[id]/page"),
|
|
extractParams: (m) => ({ id: m[1] }),
|
|
},
|
|
{
|
|
pattern: /^\/admin\/automaticMng\/flowMgmtList\/([^/]+)$/,
|
|
getImport: () => import("@/app/(main)/admin/automaticMng/flowMgmtList/[id]/page"),
|
|
extractParams: (m) => ({ id: m[1] }),
|
|
},
|
|
{
|
|
pattern: /^\/admin\/automaticMng\/batchmngList\/edit\/([^/]+)$/,
|
|
getImport: () => import("@/app/(main)/admin/automaticMng/batchmngList/edit/[id]/page"),
|
|
extractParams: (m) => ({ id: m[1] }),
|
|
},
|
|
{
|
|
pattern: /^\/admin\/screenMng\/barcodeList\/designer\/([^/]+)$/,
|
|
getImport: () => import("@/app/(main)/admin/screenMng/barcodeList/designer/[labelId]/page"),
|
|
extractParams: (m) => ({ labelId: m[1] }),
|
|
},
|
|
{
|
|
pattern: /^\/admin\/screenMng\/reportList\/designer\/([^/]+)$/,
|
|
getImport: () => import("@/app/(main)/admin/screenMng/reportList/designer/[reportId]/page"),
|
|
extractParams: (m) => ({ reportId: m[1] }),
|
|
},
|
|
{
|
|
pattern: /^\/admin\/systemMng\/dataflow\/edit\/([^/]+)$/,
|
|
getImport: () => import("@/app/(main)/admin/systemMng/dataflow/edit/[diagramId]/page"),
|
|
extractParams: (m) => ({ diagramId: m[1] }),
|
|
},
|
|
{
|
|
pattern: /^\/admin\/userMng\/companyList\/([^/]+)\/departments$/,
|
|
getImport: () => import("@/app/(main)/admin/userMng/companyList/[companyCode]/departments/page"),
|
|
extractParams: (m) => ({ companyCode: m[1] }),
|
|
},
|
|
{
|
|
pattern: /^\/admin\/standards\/([^/]+)\/edit$/,
|
|
getImport: () => import("@/app/(main)/admin/standards/[webType]/edit/page"),
|
|
extractParams: (m) => ({ webType: m[1] }),
|
|
},
|
|
{
|
|
pattern: /^\/admin\/standards\/([^/]+)$/,
|
|
getImport: () => import("@/app/(main)/admin/standards/[webType]/page"),
|
|
extractParams: (m) => ({ webType: m[1] }),
|
|
},
|
|
];
|
|
|
|
function DynamicAdminLoader({ url, params }: { url: string; params?: Record<string, string> }) {
|
|
const [Component, setComponent] = useState<React.ComponentType<any> | null>(null);
|
|
const [failed, setFailed] = useState(false);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
|
|
const tryLoad = async () => {
|
|
// 1) 정적 import 목록
|
|
const staticImport = DYNAMIC_ADMIN_IMPORTS[url];
|
|
if (staticImport) {
|
|
try {
|
|
const mod = await staticImport();
|
|
if (!cancelled) setComponent(() => mod.default);
|
|
} catch {
|
|
if (!cancelled) setFailed(true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 2) 동적 라우트 패턴 매칭
|
|
for (const { pattern, getImport } of DYNAMIC_ADMIN_PATTERNS) {
|
|
const match = url.match(pattern);
|
|
if (match) {
|
|
try {
|
|
const mod = await getImport();
|
|
if (!cancelled) setComponent(() => mod.default);
|
|
} catch {
|
|
if (!cancelled) setFailed(true);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 3) URL 경로 기반 자동 import 시도
|
|
const pagePath = url.replace(/^\//, "");
|
|
try {
|
|
const mod = await import(
|
|
/* webpackMode: "lazy" */
|
|
/* webpackInclude: /\/page\.tsx$/ */
|
|
`@/app/(main)/${pagePath}/page`
|
|
);
|
|
if (!cancelled) setComponent(() => mod.default);
|
|
} catch {
|
|
console.warn("[DynamicAdminLoader] 자동 import 실패:", url);
|
|
if (!cancelled) setFailed(true);
|
|
}
|
|
};
|
|
|
|
tryLoad();
|
|
return () => { cancelled = true; };
|
|
}, [url]);
|
|
|
|
if (failed) return <AdminPageFallback url={url} />;
|
|
if (!Component) return <LoadingFallback />;
|
|
if (params) return <Component params={Promise.resolve(params)} />;
|
|
return <Component />;
|
|
}
|
|
|
|
function AdminPageFallback({ url }: { url: string }) {
|
|
return (
|
|
<div className="flex h-full items-center justify-center">
|
|
<div className="text-center">
|
|
<p className="text-lg font-semibold text-foreground">페이지 로딩 불가</p>
|
|
<p className="mt-1 text-sm text-muted-foreground">경로: {url}</p>
|
|
<p className="mt-2 text-xs text-muted-foreground">해당 페이지가 존재하지 않습니다.</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface AdminPageRendererProps {
|
|
url: string;
|
|
}
|
|
|
|
export function AdminPageRenderer({ url }: AdminPageRendererProps) {
|
|
const cleanUrl = url.split("?")[0].split("#")[0].replace(/\/$/, "");
|
|
|
|
console.log("[AdminPageRenderer] 렌더링:", { url, cleanUrl });
|
|
|
|
// 화면 할당: /screens/[id]
|
|
const screensIdMatch = cleanUrl.match(/^\/screens\/(\d+)$/);
|
|
if (screensIdMatch) {
|
|
console.log("[AdminPageRenderer] → /screens/[id] 매칭:", screensIdMatch[1]);
|
|
return <ScreenViewPageWrapper screenIdProp={parseInt(screensIdMatch[1])} />;
|
|
}
|
|
|
|
// 화면 할당: /screen/[code] (구 형식)
|
|
const screenCodeMatch = cleanUrl.match(/^\/screen\/([^/]+)$/);
|
|
if (screenCodeMatch) {
|
|
console.log("[AdminPageRenderer] → /screen/[code] 매칭:", screenCodeMatch[1]);
|
|
return <ScreenCodeResolver screenCode={screenCodeMatch[1]} />;
|
|
}
|
|
|
|
// 대시보드 할당: /dashboard/[id]
|
|
const dashboardMatch = cleanUrl.match(/^\/dashboard\/([^/]+)$/);
|
|
if (dashboardMatch) {
|
|
console.log("[AdminPageRenderer] → /dashboard/[id] 매칭:", dashboardMatch[1]);
|
|
return <DashboardViewPage params={Promise.resolve({ dashboardId: dashboardMatch[1] })} />;
|
|
}
|
|
|
|
// URL 직접 입력: 레지스트리 매칭
|
|
const PageComponent = useMemo(() => {
|
|
return ADMIN_PAGE_REGISTRY[cleanUrl] || null;
|
|
}, [cleanUrl]);
|
|
|
|
if (PageComponent) {
|
|
console.log("[AdminPageRenderer] → 레지스트리 매칭:", cleanUrl);
|
|
return <PageComponent />;
|
|
}
|
|
|
|
// 레지스트리에 없으면 동적 import 시도
|
|
// 동적 라우트 패턴 매칭 (params 추출)
|
|
for (const { pattern, extractParams } of DYNAMIC_ADMIN_PATTERNS) {
|
|
const match = cleanUrl.match(pattern);
|
|
if (match) {
|
|
const params = extractParams(match);
|
|
console.log("[AdminPageRenderer] → 동적 라우트 매칭:", cleanUrl, params);
|
|
return <DynamicAdminLoader url={cleanUrl} params={params} />;
|
|
}
|
|
}
|
|
|
|
// 레지스트리/패턴에 없으면 DynamicAdminLoader가 자동 import 시도
|
|
console.log("[AdminPageRenderer] → 자동 import 시도:", cleanUrl);
|
|
return <DynamicAdminLoader url={cleanUrl} />;
|
|
}
|