Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into feature/v2-unified-renewal

This commit is contained in:
kjs
2026-01-15 09:22:31 +09:00
194 changed files with 52224 additions and 4678 deletions

View File

@@ -78,6 +78,7 @@ import StyleEditor from "./StyleEditor";
import { RealtimePreview } from "./RealtimePreviewDynamic";
import FloatingPanel from "./FloatingPanel";
import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRenderer";
import { MultilangSettingsModal } from "./modals/MultilangSettingsModal";
import DesignerToolbar from "./DesignerToolbar";
import TablesPanel from "./panels/TablesPanel";
import { TemplatesPanel, TemplateComponent } from "./panels/TemplatesPanel";
@@ -144,6 +145,8 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
},
});
const [isSaving, setIsSaving] = useState(false);
const [isGeneratingMultilang, setIsGeneratingMultilang] = useState(false);
const [showMultilangSettingsModal, setShowMultilangSettingsModal] = useState(false);
// 🆕 화면에 할당된 메뉴 OBJID
const [menuObjid, setMenuObjid] = useState<number | undefined>(undefined);
@@ -1474,6 +1477,101 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
}
}, [selectedScreen, layout, screenResolution]);
// 다국어 자동 생성 핸들러
const handleGenerateMultilang = useCallback(async () => {
if (!selectedScreen?.screenId) {
toast.error("화면 정보가 없습니다.");
return;
}
setIsGeneratingMultilang(true);
try {
// 공통 유틸 사용하여 라벨 추출
const { extractMultilangLabels, extractTableNames, applyMultilangMappings } = await import(
"@/lib/utils/multilangLabelExtractor"
);
const { apiClient } = await import("@/lib/api/client");
// 테이블별 컬럼 라벨 로드
const tableNames = extractTableNames(layout.components);
const columnLabelMap: Record<string, Record<string, string>> = {};
for (const tableName of tableNames) {
try {
const response = await apiClient.get(`/table-management/tables/${tableName}/columns`);
if (response.data?.success && response.data?.data) {
const columns = response.data.data.columns || response.data.data;
if (Array.isArray(columns)) {
columnLabelMap[tableName] = {};
columns.forEach((col: any) => {
const colName = col.columnName || col.column_name || col.name;
const colLabel = col.displayName || col.columnLabel || col.column_label || colName;
if (colName) {
columnLabelMap[tableName][colName] = colLabel;
}
});
}
}
} catch (error) {
console.error(`컬럼 라벨 조회 실패 (${tableName}):`, error);
}
}
// 라벨 추출 (다국어 설정과 동일한 로직)
const extractedLabels = extractMultilangLabels(layout.components, columnLabelMap);
const labels = extractedLabels.map((l) => ({
componentId: l.componentId,
label: l.label,
type: l.type,
}));
if (labels.length === 0) {
toast.info("다국어로 변환할 라벨이 없습니다.");
setIsGeneratingMultilang(false);
return;
}
// API 호출
const { generateScreenLabelKeys } = await import("@/lib/api/multilang");
const response = await generateScreenLabelKeys({
screenId: selectedScreen.screenId,
menuObjId: menuObjid?.toString(),
labels,
});
if (response.success && response.data) {
// 자동 매핑 적용
const updatedComponents = applyMultilangMappings(layout.components, response.data);
// 레이아웃 업데이트
const updatedLayout = {
...layout,
components: updatedComponents,
screenResolution: screenResolution,
};
setLayout(updatedLayout);
// 자동 저장 (매핑 정보가 손실되지 않도록)
try {
await screenApi.saveLayout(selectedScreen.screenId, updatedLayout);
toast.success(`${response.data.length}개의 다국어 키가 생성되고 자동 저장되었습니다.`);
} catch (saveError) {
console.error("다국어 매핑 저장 실패:", saveError);
toast.warning(`${response.data.length}개의 다국어 키가 생성되었습니다. 저장 버튼을 눌러 매핑을 저장하세요.`);
}
} else {
toast.error(response.error?.details || "다국어 키 생성에 실패했습니다.");
}
} catch (error) {
console.error("다국어 생성 실패:", error);
toast.error("다국어 키 생성 중 오류가 발생했습니다.");
} finally {
setIsGeneratingMultilang(false);
}
}, [selectedScreen, layout, screenResolution, menuObjid]);
// 템플릿 드래그 처리
const handleTemplateDrop = useCallback(
(e: React.DragEvent, template: TemplateComponent) => {
@@ -4255,6 +4353,9 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
onResolutionChange={setScreenResolution}
gridSettings={layout.gridSettings}
onGridSettingsChange={updateGridSettings}
onGenerateMultilang={handleGenerateMultilang}
isGeneratingMultilang={isGeneratingMultilang}
onOpenMultilangSettings={() => setShowMultilangSettingsModal(true)}
/>
{/* 메인 컨테이너 (좌측 툴바 + 패널들 + 캔버스) */}
<div className="flex flex-1 overflow-hidden">
@@ -5033,6 +5134,42 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
screenId={selectedScreen.screenId}
/>
)}
{/* 다국어 설정 모달 */}
<MultilangSettingsModal
isOpen={showMultilangSettingsModal}
onClose={() => setShowMultilangSettingsModal(false)}
components={layout.components}
onSave={async (updates) => {
if (updates.length === 0) {
toast.info("저장할 변경사항이 없습니다.");
return;
}
try {
// 공통 유틸 사용하여 매핑 적용
const { applyMultilangMappings } = await import("@/lib/utils/multilangLabelExtractor");
// 매핑 형식 변환
const mappings = updates.map((u) => ({
componentId: u.componentId,
keyId: u.langKeyId,
langKey: u.langKey,
}));
// 레이아웃 업데이트
const updatedComponents = applyMultilangMappings(layout.components, mappings);
setLayout((prev) => ({
...prev,
components: updatedComponents,
}));
toast.success(`${updates.length}개 항목의 다국어 설정이 저장되었습니다.`);
} catch (error) {
console.error("다국어 설정 저장 실패:", error);
toast.error("다국어 설정 저장 중 오류가 발생했습니다.");
}
}}
/>
</div>
</TableOptionsProvider>
</ScreenPreviewProvider>