Compare commits
No commits in common. "966191786a17119406c022d2e245806c139f2b52" and "014979bebf92eb7453560302c4eb3879a5799977" have entirely different histories.
966191786a
...
014979bebf
|
|
@ -1,6 +1,6 @@
|
||||||
import { Response } from "express";
|
import { Response } from "express";
|
||||||
import { AuthenticatedRequest } from "../middleware/authMiddleware";
|
import { AuthenticatedRequest } from "../middleware/authMiddleware";
|
||||||
import { auditLogService, getClientIp, AuditAction, AuditResourceType } from "../services/auditLogService";
|
import { auditLogService } from "../services/auditLogService";
|
||||||
import { query } from "../database/db";
|
import { query } from "../database/db";
|
||||||
import logger from "../utils/logger";
|
import logger from "../utils/logger";
|
||||||
|
|
||||||
|
|
@ -137,40 +137,3 @@ export const getAuditLogUsers = async (
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 프론트엔드에서 직접 감사 로그 기록 (그룹 복제 등 프론트 오케스트레이션 작업용)
|
|
||||||
*/
|
|
||||||
export const createAuditLog = async (
|
|
||||||
req: AuthenticatedRequest,
|
|
||||||
res: Response
|
|
||||||
): Promise<void> => {
|
|
||||||
try {
|
|
||||||
const { action, resourceType, resourceId, resourceName, tableName, summary, changes } = req.body;
|
|
||||||
|
|
||||||
if (!action || !resourceType) {
|
|
||||||
res.status(400).json({ success: false, message: "action, resourceType은 필수입니다." });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await auditLogService.log({
|
|
||||||
companyCode: req.user?.companyCode || "",
|
|
||||||
userId: req.user?.userId || "",
|
|
||||||
userName: req.user?.userName || "",
|
|
||||||
action: action as AuditAction,
|
|
||||||
resourceType: resourceType as AuditResourceType,
|
|
||||||
resourceId: resourceId || undefined,
|
|
||||||
resourceName: resourceName || undefined,
|
|
||||||
tableName: tableName || undefined,
|
|
||||||
summary: summary || undefined,
|
|
||||||
changes: changes || undefined,
|
|
||||||
ipAddress: getClientIp(req),
|
|
||||||
requestPath: req.originalUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
res.json({ success: true });
|
|
||||||
} catch (error: any) {
|
|
||||||
logger.error("감사 로그 기록 실패", { error: error.message });
|
|
||||||
res.status(500).json({ success: false, message: "감사 로그 기록 실패" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -614,6 +614,20 @@ export const copyScreenWithModals = async (
|
||||||
modalScreens: modalScreens || [],
|
modalScreens: modalScreens || [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
auditLogService.log({
|
||||||
|
companyCode: targetCompanyCode || companyCode,
|
||||||
|
userId: userId || "",
|
||||||
|
userName: (req.user as any)?.userName || "",
|
||||||
|
action: "COPY",
|
||||||
|
resourceType: "SCREEN",
|
||||||
|
resourceId: id,
|
||||||
|
resourceName: mainScreen?.screenName,
|
||||||
|
summary: `화면 일괄 복사 (메인 1개 + 모달 ${result.modalScreens.length}개, 원본 ID:${id})`,
|
||||||
|
changes: { after: { sourceScreenId: id, targetCompanyCode, mainScreenName: mainScreen?.screenName } },
|
||||||
|
ipAddress: getClientIp(req),
|
||||||
|
requestPath: req.originalUrl,
|
||||||
|
});
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: result,
|
data: result,
|
||||||
|
|
@ -649,6 +663,20 @@ export const copyScreen = async (
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
auditLogService.log({
|
||||||
|
companyCode,
|
||||||
|
userId: userId || "",
|
||||||
|
userName: (req.user as any)?.userName || "",
|
||||||
|
action: "COPY",
|
||||||
|
resourceType: "SCREEN",
|
||||||
|
resourceId: String(copiedScreen?.screenId || ""),
|
||||||
|
resourceName: screenName,
|
||||||
|
summary: `화면 "${screenName}" 복사 (원본 ID:${id})`,
|
||||||
|
changes: { after: { sourceScreenId: id, screenName, screenCode } },
|
||||||
|
ipAddress: getClientIp(req),
|
||||||
|
requestPath: req.originalUrl,
|
||||||
|
});
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: copiedScreen,
|
data: copiedScreen,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import { authenticateToken } from "../middleware/authMiddleware";
|
import { authenticateToken } from "../middleware/authMiddleware";
|
||||||
import { getAuditLogs, getAuditLogStats, getAuditLogUsers, createAuditLog } from "../controllers/auditLogController";
|
import { getAuditLogs, getAuditLogStats, getAuditLogUsers } from "../controllers/auditLogController";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", authenticateToken, getAuditLogs);
|
router.get("/", authenticateToken, getAuditLogs);
|
||||||
router.get("/stats", authenticateToken, getAuditLogStats);
|
router.get("/stats", authenticateToken, getAuditLogStats);
|
||||||
router.get("/users", authenticateToken, getAuditLogUsers);
|
router.get("/users", authenticateToken, getAuditLogUsers);
|
||||||
router.post("/", authenticateToken, createAuditLog);
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
||||||
|
|
@ -1165,28 +1165,6 @@ export default function CopyScreenModal({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 그룹 복제 요약 감사 로그 1건 기록
|
|
||||||
try {
|
|
||||||
await apiClient.post("/audit-log", {
|
|
||||||
action: "COPY",
|
|
||||||
resourceType: "SCREEN",
|
|
||||||
resourceId: String(sourceGroup.id),
|
|
||||||
resourceName: sourceGroup.group_name,
|
|
||||||
summary: `그룹 "${sourceGroup.group_name}" → "${rootGroupName}" 복제 (그룹 ${stats.groups}개, 화면 ${stats.screens}개)${finalCompanyCode !== sourceGroup.company_code ? ` [${sourceGroup.company_code} → ${finalCompanyCode}]` : ""}`,
|
|
||||||
changes: {
|
|
||||||
after: {
|
|
||||||
원본그룹: sourceGroup.group_name,
|
|
||||||
대상그룹: rootGroupName,
|
|
||||||
복제그룹수: stats.groups,
|
|
||||||
복제화면수: stats.screens,
|
|
||||||
대상회사: finalCompanyCode,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (auditError) {
|
|
||||||
console.warn("그룹 복제 감사 로그 기록 실패 (무시):", auditError);
|
|
||||||
}
|
|
||||||
|
|
||||||
toast.success(
|
toast.success(
|
||||||
`그룹 복제가 완료되었습니다! (그룹 ${stats.groups}개 + 화면 ${stats.screens}개)`
|
`그룹 복제가 완료되었습니다! (그룹 ${stats.groups}개 + 화면 ${stats.screens}개)`
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,6 @@ interface RealtimePreviewProps {
|
||||||
selectedTabComponentId?: string; // 🆕 선택된 탭 컴포넌트 ID
|
selectedTabComponentId?: string; // 🆕 선택된 탭 컴포넌트 ID
|
||||||
onSelectPanelComponent?: (panelSide: "left" | "right", compId: string, comp: any) => void; // 🆕 분할 패널 내부 컴포넌트 선택 콜백
|
onSelectPanelComponent?: (panelSide: "left" | "right", compId: string, comp: any) => void; // 🆕 분할 패널 내부 컴포넌트 선택 콜백
|
||||||
selectedPanelComponentId?: string; // 🆕 선택된 분할 패널 컴포넌트 ID
|
selectedPanelComponentId?: string; // 🆕 선택된 분할 패널 컴포넌트 ID
|
||||||
onNestedPanelSelect?: (splitPanelId: string, panelSide: "left" | "right", compId: string, comp: any) => void;
|
|
||||||
onResize?: (componentId: string, newSize: { width: number; height: number }) => void; // 🆕 리사이즈 콜백
|
onResize?: (componentId: string, newSize: { width: number; height: number }) => void; // 🆕 리사이즈 콜백
|
||||||
|
|
||||||
// 버튼 액션을 위한 props
|
// 버튼 액션을 위한 props
|
||||||
|
|
@ -151,7 +150,6 @@ const RealtimePreviewDynamicComponent: React.FC<RealtimePreviewProps> = ({
|
||||||
selectedTabComponentId, // 🆕 선택된 탭 컴포넌트 ID
|
selectedTabComponentId, // 🆕 선택된 탭 컴포넌트 ID
|
||||||
onSelectPanelComponent, // 🆕 분할 패널 내부 컴포넌트 선택 콜백
|
onSelectPanelComponent, // 🆕 분할 패널 내부 컴포넌트 선택 콜백
|
||||||
selectedPanelComponentId, // 🆕 선택된 분할 패널 컴포넌트 ID
|
selectedPanelComponentId, // 🆕 선택된 분할 패널 컴포넌트 ID
|
||||||
onNestedPanelSelect,
|
|
||||||
onResize, // 🆕 리사이즈 콜백
|
onResize, // 🆕 리사이즈 콜백
|
||||||
}) => {
|
}) => {
|
||||||
// 🆕 화면 다국어 컨텍스트
|
// 🆕 화면 다국어 컨텍스트
|
||||||
|
|
@ -770,7 +768,6 @@ const RealtimePreviewDynamicComponent: React.FC<RealtimePreviewProps> = ({
|
||||||
selectedTabComponentId={selectedTabComponentId}
|
selectedTabComponentId={selectedTabComponentId}
|
||||||
onSelectPanelComponent={onSelectPanelComponent}
|
onSelectPanelComponent={onSelectPanelComponent}
|
||||||
selectedPanelComponentId={selectedPanelComponentId}
|
selectedPanelComponentId={selectedPanelComponentId}
|
||||||
onNestedPanelSelect={onNestedPanelSelect}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6744,6 +6744,15 @@ export default function ScreenDesigner({
|
||||||
const { splitPanelId, panelSide } = selectedPanelComponentInfo;
|
const { splitPanelId, panelSide } = selectedPanelComponentInfo;
|
||||||
const panelKey = panelSide === "left" ? "leftPanel" : "rightPanel";
|
const panelKey = panelSide === "left" ? "leftPanel" : "rightPanel";
|
||||||
|
|
||||||
|
console.log("🔧 updatePanelComponentProperty 호출:", {
|
||||||
|
componentId,
|
||||||
|
path,
|
||||||
|
value,
|
||||||
|
splitPanelId,
|
||||||
|
panelSide,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 🆕 안전한 깊은 경로 업데이트 헬퍼 함수
|
||||||
const setNestedValue = (obj: any, pathStr: string, val: any): any => {
|
const setNestedValue = (obj: any, pathStr: string, val: any): any => {
|
||||||
const result = JSON.parse(JSON.stringify(obj));
|
const result = JSON.parse(JSON.stringify(obj));
|
||||||
const parts = pathStr.split(".");
|
const parts = pathStr.split(".");
|
||||||
|
|
@ -6760,27 +6769,9 @@ export default function ScreenDesigner({
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 중첩 구조 포함 분할패널 찾기 헬퍼
|
|
||||||
const findSplitPanelInLayout = (components: any[]): { found: any; path: "top" | "nested"; parentTabId?: string; parentTabTabId?: string } | null => {
|
|
||||||
const direct = components.find((c) => c.id === splitPanelId);
|
|
||||||
if (direct) return { found: direct, path: "top" };
|
|
||||||
for (const comp of components) {
|
|
||||||
const ct = (comp as any)?.componentType || (comp as any)?.overrides?.type;
|
|
||||||
const cfg = (comp as any)?.componentConfig || (comp as any)?.overrides || {};
|
|
||||||
if (ct === "tabs-widget" || ct === "v2-tabs-widget") {
|
|
||||||
for (const tab of (cfg.tabs || [])) {
|
|
||||||
const nested = (tab.components || []).find((c: any) => c.id === splitPanelId);
|
|
||||||
if (nested) return { found: nested, path: "nested", parentTabId: comp.id, parentTabTabId: tab.id };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
setLayout((prevLayout) => {
|
setLayout((prevLayout) => {
|
||||||
const result = findSplitPanelInLayout(prevLayout.components);
|
const splitPanelComponent = prevLayout.components.find((c) => c.id === splitPanelId);
|
||||||
if (!result) return prevLayout;
|
if (!splitPanelComponent) return prevLayout;
|
||||||
const splitPanelComponent = result.found;
|
|
||||||
|
|
||||||
const currentConfig = (splitPanelComponent as any).componentConfig || {};
|
const currentConfig = (splitPanelComponent as any).componentConfig || {};
|
||||||
const panelConfig = currentConfig[panelKey] || {};
|
const panelConfig = currentConfig[panelKey] || {};
|
||||||
|
|
@ -6816,37 +6807,17 @@ export default function ScreenDesigner({
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// selectedPanelComponentInfo 업데이트
|
||||||
setSelectedPanelComponentInfo((prev) =>
|
setSelectedPanelComponentInfo((prev) =>
|
||||||
prev ? { ...prev, component: updatedComp } : null,
|
prev ? { ...prev, component: updatedComp } : null,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 중첩 구조 반영
|
return {
|
||||||
const applyUpdatedSplitPanel = (layout: any, updated: any, info: any) => {
|
...prevLayout,
|
||||||
if (info.path === "top") {
|
components: prevLayout.components.map((c) =>
|
||||||
return { ...layout, components: layout.components.map((c: any) => c.id === splitPanelId ? updated : c) };
|
c.id === splitPanelId ? updatedComponent : c,
|
||||||
}
|
),
|
||||||
return {
|
|
||||||
...layout,
|
|
||||||
components: layout.components.map((c: any) => {
|
|
||||||
if (c.id !== info.parentTabId) return c;
|
|
||||||
const cfgKey = c.componentConfig?.tabs ? "componentConfig" : "overrides";
|
|
||||||
const cfg = c[cfgKey] || {};
|
|
||||||
return {
|
|
||||||
...c,
|
|
||||||
[cfgKey]: {
|
|
||||||
...cfg,
|
|
||||||
tabs: (cfg.tabs || []).map((t: any) =>
|
|
||||||
t.id === info.parentTabTabId
|
|
||||||
? { ...t, components: (t.components || []).map((tc: any) => tc.id === splitPanelId ? updated : tc) }
|
|
||||||
: t,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return applyUpdatedSplitPanel(prevLayout, updatedComponent, result);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -6856,23 +6827,8 @@ export default function ScreenDesigner({
|
||||||
const panelKey = panelSide === "left" ? "leftPanel" : "rightPanel";
|
const panelKey = panelSide === "left" ? "leftPanel" : "rightPanel";
|
||||||
|
|
||||||
setLayout((prevLayout) => {
|
setLayout((prevLayout) => {
|
||||||
const findResult = (() => {
|
const splitPanelComponent = prevLayout.components.find((c) => c.id === splitPanelId);
|
||||||
const direct = prevLayout.components.find((c: any) => c.id === splitPanelId);
|
if (!splitPanelComponent) return prevLayout;
|
||||||
if (direct) return { found: direct, path: "top" as const };
|
|
||||||
for (const comp of prevLayout.components) {
|
|
||||||
const ct = (comp as any)?.componentType || (comp as any)?.overrides?.type;
|
|
||||||
const cfg = (comp as any)?.componentConfig || (comp as any)?.overrides || {};
|
|
||||||
if (ct === "tabs-widget" || ct === "v2-tabs-widget") {
|
|
||||||
for (const tab of (cfg.tabs || [])) {
|
|
||||||
const nested = (tab.components || []).find((c: any) => c.id === splitPanelId);
|
|
||||||
if (nested) return { found: nested, path: "nested" as const, parentTabId: comp.id, parentTabTabId: tab.id };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})();
|
|
||||||
if (!findResult) return prevLayout;
|
|
||||||
const splitPanelComponent = findResult.found;
|
|
||||||
|
|
||||||
const currentConfig = (splitPanelComponent as any).componentConfig || {};
|
const currentConfig = (splitPanelComponent as any).componentConfig || {};
|
||||||
const panelConfig = currentConfig[panelKey] || {};
|
const panelConfig = currentConfig[panelKey] || {};
|
||||||
|
|
@ -6893,27 +6849,11 @@ export default function ScreenDesigner({
|
||||||
|
|
||||||
setSelectedPanelComponentInfo(null);
|
setSelectedPanelComponentInfo(null);
|
||||||
|
|
||||||
if (findResult.path === "top") {
|
|
||||||
return { ...prevLayout, components: prevLayout.components.map((c: any) => c.id === splitPanelId ? updatedComponent : c) };
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
...prevLayout,
|
...prevLayout,
|
||||||
components: prevLayout.components.map((c: any) => {
|
components: prevLayout.components.map((c) =>
|
||||||
if (c.id !== findResult.parentTabId) return c;
|
c.id === splitPanelId ? updatedComponent : c,
|
||||||
const cfgKey = c.componentConfig?.tabs ? "componentConfig" : "overrides";
|
),
|
||||||
const cfg = c[cfgKey] || {};
|
|
||||||
return {
|
|
||||||
...c,
|
|
||||||
[cfgKey]: {
|
|
||||||
...cfg,
|
|
||||||
tabs: (cfg.tabs || []).map((t: any) =>
|
|
||||||
t.id === findResult.parentTabTabId
|
|
||||||
? { ...t, components: (t.components || []).map((tc: any) => tc.id === splitPanelId ? updatedComponent : tc) }
|
|
||||||
: t,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -7517,7 +7457,6 @@ export default function ScreenDesigner({
|
||||||
onSelectPanelComponent={(panelSide, compId, comp) =>
|
onSelectPanelComponent={(panelSide, compId, comp) =>
|
||||||
handleSelectPanelComponent(component.id, panelSide, compId, comp)
|
handleSelectPanelComponent(component.id, panelSide, compId, comp)
|
||||||
}
|
}
|
||||||
onNestedPanelSelect={handleSelectPanelComponent}
|
|
||||||
selectedPanelComponentId={
|
selectedPanelComponentId={
|
||||||
selectedPanelComponentInfo?.splitPanelId === component.id
|
selectedPanelComponentInfo?.splitPanelId === component.id
|
||||||
? selectedPanelComponentInfo.componentId
|
? selectedPanelComponentInfo.componentId
|
||||||
|
|
|
||||||
|
|
@ -429,31 +429,29 @@ export function TabsWidget({
|
||||||
})) as any;
|
})) as any;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-0 flex-1 flex-col">
|
<ResponsiveGridRenderer
|
||||||
<ResponsiveGridRenderer
|
components={componentDataList}
|
||||||
components={componentDataList}
|
canvasWidth={canvasWidth}
|
||||||
canvasWidth={canvasWidth}
|
canvasHeight={canvasHeight}
|
||||||
canvasHeight={canvasHeight}
|
renderComponent={(comp) => (
|
||||||
renderComponent={(comp) => (
|
<DynamicComponentRenderer
|
||||||
<DynamicComponentRenderer
|
{...restProps}
|
||||||
{...restProps}
|
component={comp}
|
||||||
component={comp}
|
formData={formData}
|
||||||
formData={formData}
|
onFormDataChange={onFormDataChange}
|
||||||
onFormDataChange={onFormDataChange}
|
menuObjid={menuObjid}
|
||||||
menuObjid={menuObjid}
|
isDesignMode={false}
|
||||||
isDesignMode={false}
|
isInteractive={true}
|
||||||
isInteractive={true}
|
selectedRowsData={localSelectedRowsData}
|
||||||
selectedRowsData={localSelectedRowsData}
|
onSelectedRowsChange={handleSelectedRowsChange}
|
||||||
onSelectedRowsChange={handleSelectedRowsChange}
|
parentTabId={tab.id}
|
||||||
parentTabId={tab.id}
|
parentTabsComponentId={component.id}
|
||||||
parentTabsComponentId={component.id}
|
{...(screenInfoMap[tab.id]
|
||||||
{...(screenInfoMap[tab.id]
|
? { tableName: screenInfoMap[tab.id].tableName, screenId: screenInfoMap[tab.id].id }
|
||||||
? { tableName: screenInfoMap[tab.id].tableName, screenId: screenInfoMap[tab.id].id }
|
: {})}
|
||||||
: {})}
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -498,7 +496,7 @@ export function TabsWidget({
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative flex flex-1 flex-col overflow-auto">
|
<div className="relative flex-1 overflow-auto">
|
||||||
{visibleTabs.map((tab) => {
|
{visibleTabs.map((tab) => {
|
||||||
const shouldRender = mountedTabs.has(tab.id);
|
const shouldRender = mountedTabs.has(tab.id);
|
||||||
const isActive = selectedTab === tab.id;
|
const isActive = selectedTab === tab.id;
|
||||||
|
|
@ -508,7 +506,7 @@ export function TabsWidget({
|
||||||
key={tab.id}
|
key={tab.id}
|
||||||
value={tab.id}
|
value={tab.id}
|
||||||
forceMount
|
forceMount
|
||||||
className={cn("flex min-h-0 flex-1 flex-col overflow-auto", !isActive && "hidden")}
|
className={cn("h-full overflow-auto", !isActive && "hidden")}
|
||||||
>
|
>
|
||||||
{shouldRender && renderTabContent(tab)}
|
{shouldRender && renderTabContent(tab)}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
|
||||||
|
|
@ -235,8 +235,6 @@ export interface DynamicComponentRendererProps {
|
||||||
// 🆕 분할 패널 내부 컴포넌트 선택 콜백
|
// 🆕 분할 패널 내부 컴포넌트 선택 콜백
|
||||||
onSelectPanelComponent?: (panelSide: "left" | "right", compId: string, comp: any) => void;
|
onSelectPanelComponent?: (panelSide: "left" | "right", compId: string, comp: any) => void;
|
||||||
selectedPanelComponentId?: string;
|
selectedPanelComponentId?: string;
|
||||||
// 중첩된 분할패널 내부 컴포넌트 선택 콜백 (탭 안의 분할패널)
|
|
||||||
onNestedPanelSelect?: (splitPanelId: string, panelSide: "left" | "right", compId: string, comp: any) => void;
|
|
||||||
flowSelectedStepId?: number | null;
|
flowSelectedStepId?: number | null;
|
||||||
onFlowSelectedDataChange?: (selectedData: any[], stepId: number | null) => void;
|
onFlowSelectedDataChange?: (selectedData: any[], stepId: number | null) => void;
|
||||||
// 테이블 새로고침 키
|
// 테이블 새로고침 키
|
||||||
|
|
@ -870,7 +868,6 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
||||||
// 🆕 분할 패널 내부 컴포넌트 선택 콜백
|
// 🆕 분할 패널 내부 컴포넌트 선택 콜백
|
||||||
onSelectPanelComponent: props.onSelectPanelComponent,
|
onSelectPanelComponent: props.onSelectPanelComponent,
|
||||||
selectedPanelComponentId: props.selectedPanelComponentId,
|
selectedPanelComponentId: props.selectedPanelComponentId,
|
||||||
onNestedPanelSelect: props.onNestedPanelSelect,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 렌더러가 클래스인지 함수인지 확인
|
// 렌더러가 클래스인지 함수인지 확인
|
||||||
|
|
|
||||||
|
|
@ -720,29 +720,29 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
}, [leftData, leftGroupSumConfig]);
|
}, [leftData, leftGroupSumConfig]);
|
||||||
|
|
||||||
// 컴포넌트 스타일
|
// 컴포넌트 스타일
|
||||||
// height: component.size?.height 우선, 없으면 component.style?.height, 기본 600px
|
// height 처리: 이미 px 단위면 그대로, 숫자면 px 추가
|
||||||
const getHeightValue = () => {
|
const getHeightValue = () => {
|
||||||
const sizeH = component.size?.height;
|
|
||||||
if (sizeH && typeof sizeH === "number" && sizeH > 0) return `${sizeH}px`;
|
|
||||||
const height = component.style?.height;
|
const height = component.style?.height;
|
||||||
if (!height) return "600px";
|
if (!height) return "600px";
|
||||||
if (typeof height === "string") return height;
|
if (typeof height === "string") return height; // 이미 '540px' 형태
|
||||||
return `${height}px`;
|
return `${height}px`; // 숫자면 px 추가
|
||||||
};
|
};
|
||||||
|
|
||||||
const componentStyle: React.CSSProperties = isDesignMode
|
const componentStyle: React.CSSProperties = isDesignMode
|
||||||
? {
|
? {
|
||||||
|
position: "absolute",
|
||||||
|
left: `${component.style?.positionX || 0}px`,
|
||||||
|
top: `${component.style?.positionY || 0}px`,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: getHeightValue(),
|
||||||
minHeight: getHeightValue(),
|
zIndex: component.style?.positionZ || 1,
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
border: isSelected ? "2px solid #3b82f6" : "1px solid #e5e7eb",
|
border: isSelected ? "2px solid #3b82f6" : "1px solid #e5e7eb",
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
position: "relative",
|
position: "relative",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: getHeightValue(),
|
||||||
minHeight: getHeightValue(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 계층 구조 빌드 함수 (트리 구조 유지)
|
// 계층 구조 빌드 함수 (트리 구조 유지)
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,7 @@ const TabsDesignEditor: React.FC<{
|
||||||
onUpdateComponent?: (updatedComponent: any) => void;
|
onUpdateComponent?: (updatedComponent: any) => void;
|
||||||
onSelectTabComponent?: (tabId: string, compId: string, comp: TabInlineComponent) => void;
|
onSelectTabComponent?: (tabId: string, compId: string, comp: TabInlineComponent) => void;
|
||||||
selectedTabComponentId?: string;
|
selectedTabComponentId?: string;
|
||||||
onNestedPanelSelect?: (splitPanelId: string, panelSide: "left" | "right", compId: string, comp: any) => void;
|
}> = ({ component, tabs, onUpdateComponent, onSelectTabComponent, selectedTabComponentId }) => {
|
||||||
}> = ({ component, tabs, onUpdateComponent, onSelectTabComponent, selectedTabComponentId, onNestedPanelSelect }) => {
|
|
||||||
const [activeTabId, setActiveTabId] = useState<string>(tabs[0]?.id || "");
|
const [activeTabId, setActiveTabId] = useState<string>(tabs[0]?.id || "");
|
||||||
const [draggingCompId, setDraggingCompId] = useState<string | null>(null);
|
const [draggingCompId, setDraggingCompId] = useState<string | null>(null);
|
||||||
const [dragPosition, setDragPosition] = useState<{ x: number; y: number } | null>(null);
|
const [dragPosition, setDragPosition] = useState<{ x: number; y: number } | null>(null);
|
||||||
|
|
@ -325,12 +324,15 @@ const TabsDesignEditor: React.FC<{
|
||||||
const isDragging = draggingCompId === comp.id;
|
const isDragging = draggingCompId === comp.id;
|
||||||
const isResizing = resizingCompId === comp.id;
|
const isResizing = resizingCompId === comp.id;
|
||||||
|
|
||||||
|
// 드래그/리사이즈 중 표시할 크기
|
||||||
|
// resizeSize가 있고 해당 컴포넌트이면 resizeSize 우선 사용 (레이아웃 업데이트 반영 전까지)
|
||||||
const compWidth = comp.size?.width || 200;
|
const compWidth = comp.size?.width || 200;
|
||||||
const compHeight = comp.size?.height || 100;
|
const compHeight = comp.size?.height || 100;
|
||||||
const isResizingThis = (resizingCompId === comp.id || lastResizedCompId === comp.id) && resizeSize;
|
const isResizingThis = (resizingCompId === comp.id || lastResizedCompId === comp.id) && resizeSize;
|
||||||
const displayWidth = isResizingThis ? resizeSize!.width : compWidth;
|
const displayWidth = isResizingThis ? resizeSize!.width : compWidth;
|
||||||
const displayHeight = isResizingThis ? resizeSize!.height : compHeight;
|
const displayHeight = isResizingThis ? resizeSize!.height : compHeight;
|
||||||
|
|
||||||
|
// 컴포넌트 데이터를 DynamicComponentRenderer 형식으로 변환
|
||||||
const componentData = {
|
const componentData = {
|
||||||
id: comp.id,
|
id: comp.id,
|
||||||
type: "component" as const,
|
type: "component" as const,
|
||||||
|
|
@ -342,6 +344,7 @@ const TabsDesignEditor: React.FC<{
|
||||||
style: comp.style || {},
|
style: comp.style || {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 드래그 중인 컴포넌트는 dragPosition 사용, 아니면 저장된 position 사용
|
||||||
const displayX = isDragging && dragPosition ? dragPosition.x : (comp.position?.x || 0);
|
const displayX = isDragging && dragPosition ? dragPosition.x : (comp.position?.x || 0);
|
||||||
const displayY = isDragging && dragPosition ? dragPosition.y : (comp.position?.y || 0);
|
const displayY = isDragging && dragPosition ? dragPosition.y : (comp.position?.y || 0);
|
||||||
|
|
||||||
|
|
@ -444,11 +447,7 @@ const TabsDesignEditor: React.FC<{
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSelectPanelComponent: (panelSide: string, compId: string, panelComp: any) => {
|
onSelectPanelComponent: (panelSide: string, compId: string, panelComp: any) => {
|
||||||
if (onNestedPanelSelect) {
|
onSelectTabComponent?.(activeTabId, comp.id, { ...comp, _selectedPanelSide: panelSide, _selectedPanelCompId: compId, _selectedPanelComp: panelComp } as any);
|
||||||
onNestedPanelSelect(comp.id, panelSide as "left" | "right", compId, panelComp);
|
|
||||||
} else {
|
|
||||||
onSelectTabComponent?.(activeTabId, comp.id, comp);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
} : {})}
|
} : {})}
|
||||||
/>
|
/>
|
||||||
|
|
@ -511,7 +510,6 @@ const TabsWidgetWrapper: React.FC<any> = (props) => {
|
||||||
onUpdateComponent,
|
onUpdateComponent,
|
||||||
onSelectTabComponent,
|
onSelectTabComponent,
|
||||||
selectedTabComponentId,
|
selectedTabComponentId,
|
||||||
onNestedPanelSelect,
|
|
||||||
...restProps
|
...restProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
|
@ -528,7 +526,6 @@ const TabsWidgetWrapper: React.FC<any> = (props) => {
|
||||||
onUpdateComponent={onUpdateComponent}
|
onUpdateComponent={onUpdateComponent}
|
||||||
onSelectTabComponent={onSelectTabComponent}
|
onSelectTabComponent={onSelectTabComponent}
|
||||||
selectedTabComponentId={selectedTabComponentId}
|
selectedTabComponentId={selectedTabComponentId}
|
||||||
onNestedPanelSelect={onNestedPanelSelect}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue