diff --git a/docker/dev/docker-compose.frontend.mac.yml b/docker/dev/docker-compose.frontend.mac.yml index 23bcb654..6428d481 100644 --- a/docker/dev/docker-compose.frontend.mac.yml +++ b/docker/dev/docker-compose.frontend.mac.yml @@ -9,7 +9,8 @@ services: - "9771:3000" environment: - NEXT_PUBLIC_API_URL=http://localhost:8080/api - - NODE_OPTIONS=--max-old-space-size=4096 + - NODE_OPTIONS=--max-old-space-size=8192 + - NEXT_TELEMETRY_DISABLED=1 volumes: - ../../frontend:/app - /app/node_modules diff --git a/frontend/components/common/ScreenModal.tsx b/frontend/components/common/ScreenModal.tsx index 62685175..38aebadc 100644 --- a/frontend/components/common/ScreenModal.tsx +++ b/frontend/components/common/ScreenModal.tsx @@ -603,7 +603,7 @@ export const ScreenModal: React.FC = ({ className }) => {
{loading ? (
@@ -620,6 +620,8 @@ export const ScreenModal: React.FC = ({ className }) => { style={{ width: `${screenDimensions?.width || 800}px`, height: `${screenDimensions?.height || 600}px`, + // ๐Ÿ†• ๋ผ๋ฒจ์ด ์œ„๋กœ ํŠ€์–ด๋‚˜๊ฐˆ ์ˆ˜ ์žˆ๋„๋ก overflow visible ์„ค์ • + overflow: "visible", }} > {(() => { diff --git a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx index 8dc5da89..d8e26377 100644 --- a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx +++ b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx @@ -1062,22 +1062,35 @@ export const InteractiveScreenViewerDynamic: React.FC 0 ? "visible" : undefined, }; return ( <>
- {/* ๋ผ๋ฒจ ์ˆจ๊น€ - ๋ชจ๋‹ฌ์—์„œ๋Š” ๋ผ๋ฒจ์„ ํ‘œ์‹œํ•˜์ง€ ์•Š์Œ */} - {/* ์œ„์ ฏ ๋ Œ๋”๋ง */} + {/* ์œ„์ ฏ ๋ Œ๋”๋ง (๋ผ๋ฒจ์€ V2Input ๋‚ด๋ถ€์—์„œ absolute๋กœ ํ‘œ์‹œ๋จ) */} {renderInteractiveWidget(component)}
diff --git a/frontend/components/screen/RealtimePreview.tsx b/frontend/components/screen/RealtimePreview.tsx index b58a6a1f..5a786616 100644 --- a/frontend/components/screen/RealtimePreview.tsx +++ b/frontend/components/screen/RealtimePreview.tsx @@ -119,6 +119,9 @@ const WidgetRenderer: React.FC<{ tableDisplayData?: any[]; [key: string]: any; }> = ({ component, isDesignMode = false, sortBy, sortOrder, tableDisplayData, ...restProps }) => { + // ๐Ÿ”ง ๋ฌด์กฐ๊ฑด ๋กœ๊ทธ (๋ Œ๋”๋ง ํ™•์ธ์šฉ) + console.log("๐Ÿ“ฆ WidgetRenderer ๋ Œ๋”๋ง:", component.id, "labelDisplay:", component.style?.labelDisplay); + // ์œ„์ ฏ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ ๋นˆ div ๋ฐ˜ํ™˜ if (!isWidgetComponent(component)) { return
์œ„์ ฏ์ด ์•„๋‹™๋‹ˆ๋‹ค
; @@ -127,9 +130,6 @@ const WidgetRenderer: React.FC<{ const widget = component; const { widgetType, label, placeholder, required, readonly, columnName, style } = widget; - // ๋””๋ฒ„๊น…: ์‹ค์ œ widgetType ๊ฐ’ ํ™•์ธ - // console.log("RealtimePreviewDynamic - widgetType:", widgetType, "columnName:", columnName); - // ์‚ฌ์šฉ์ž๊ฐ€ ํ…Œ๋‘๋ฆฌ๋ฅผ ์„ค์ •ํ–ˆ๋Š”์ง€ ํ™•์ธ const hasCustomBorder = style && (style.borderWidth || style.borderStyle || style.borderColor || style.border); @@ -246,8 +246,17 @@ export const RealtimePreviewDynamic: React.FC = ({ tableDisplayData, // ๐Ÿ†• ํ™”๋ฉด ํ‘œ์‹œ ๋ฐ์ดํ„ฐ ...restProps }) => { + // ๐Ÿ”ง ๋ฌด์กฐ๊ฑด ๋กœ๊ทธ - ํŒŒ์ผ ๋ฐ˜์˜ ํ…Œ์ŠคํŠธ์šฉ (2024-TEST) + console.log("๐Ÿ”ท๐Ÿ”ท๐Ÿ”ท RealtimePreview 2024:", component.id); + const { user } = useAuth(); const { type, id, position, size, style = {} } = component; + + // ๐Ÿ”ง v2 ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง ์ถ”์  + if (id?.includes("v2-")) { + console.log("๐Ÿ”ท RealtimePreview ๋ Œ๋”:", id, "type:", type, "labelDisplay:", style?.labelDisplay); + } + const [fileUpdateTrigger, setFileUpdateTrigger] = useState(0); const [actualHeight, setActualHeight] = useState(null); const contentRef = React.useRef(null); @@ -741,6 +750,7 @@ export const RealtimePreviewDynamic: React.FC = ({ {/* ์ปดํฌ๋„ŒํŠธ ํƒ€์ž… - ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๊ธฐ๋ฐ˜ ๋ Œ๋”๋ง (Section Paper, Section Card ๋“ฑ) */} {type === "component" && (() => { + console.log("๐Ÿ“ฆ DynamicComponentRenderer ๋ Œ๋”๋ง:", component.id, "labelDisplay:", component.style?.labelDisplay); const { DynamicComponentRenderer } = require("@/lib/registry/DynamicComponentRenderer"); return ( = ({ />
- {/* ์„ ํƒ๋œ ์ปดํฌ๋„ŒํŠธ ์ •๋ณด ํ‘œ์‹œ */} + {/* ์„ ํƒ๋œ ์ปดํฌ๋„ŒํŠธ ์ •๋ณด ํ‘œ์‹œ - ๐Ÿ”ง ์˜ค๋ฅธ์ชฝ์œผ๋กœ ์ด๋™ (๋ผ๋ฒจ๊ณผ ๊ฒน์น˜์ง€ ์•Š๋„๋ก) */} {isSelected && ( -
+
{type === "widget" && (
{getWidgetIcon((component as WidgetComponent).widgetType)} @@ -685,7 +685,8 @@ const RealtimePreviewDynamicComponent: React.FC = ({ ); }; -// React.memo๋กœ ๋ž˜ํ•‘ํ•˜์—ฌ ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง ๋ฐฉ์ง€ +// ๐Ÿ”ง arePropsEqual ์ œ๊ฑฐ - ๊ธฐ๋ณธ React.memo ์‚ฌ์šฉ (๋””๋ฒ„๊น…์šฉ) +// component ๊ฐ์ฒด๊ฐ€ ์ƒˆ๋กœ ์ƒ์„ฑ๋˜๋ฉด ์ž๋™์œผ๋กœ ๋ฆฌ๋ Œ๋”๋ง๋จ export const RealtimePreviewDynamic = React.memo(RealtimePreviewDynamicComponent); // displayName ์„ค์ • (๋””๋ฒ„๊น…์šฉ) diff --git a/frontend/components/screen/ScreenDesigner.tsx b/frontend/components/screen/ScreenDesigner.tsx index 192bd16c..389e8366 100644 --- a/frontend/components/screen/ScreenDesigner.tsx +++ b/frontend/components/screen/ScreenDesigner.tsx @@ -472,14 +472,20 @@ export default function ScreenDesigner({ selectedScreen, onBackToList, onScreenU // ์ด๋ฏธ ๋ฐฐ์น˜๋œ ์ปฌ๋Ÿผ ๋ชฉ๋ก ๊ณ„์‚ฐ const placedColumns = useMemo(() => { const placed = new Set(); + // ๐Ÿ”ง ํ™”๋ฉด์˜ ๋ฉ”์ธ ํ…Œ์ด๋ธ”๋ช…์„ fallback์œผ๋กœ ์‚ฌ์šฉ + const screenTableName = selectedScreen?.tableName; const collectColumns = (components: ComponentData[]) => { components.forEach((comp) => { const anyComp = comp as any; - // widget ํƒ€์ž… ๋˜๋Š” component ํƒ€์ž… (์ƒˆ๋กœ์šด ์‹œ์Šคํ…œ)์—์„œ tableName๊ณผ columnName ํ™•์ธ - if ((comp.type === "widget" || comp.type === "component") && anyComp.tableName && anyComp.columnName) { - const key = `${anyComp.tableName}.${anyComp.columnName}`; + // ๐Ÿ”ง tableName๊ณผ columnName์„ ์—ฌ๋Ÿฌ ์œ„์น˜์—์„œ ์ฐพ๊ธฐ (์ตœ์ƒ์œ„, componentConfig, ๋˜๋Š” ํ™”๋ฉด ํ…Œ์ด๋ธ”๋ช…) + const tableName = anyComp.tableName || anyComp.componentConfig?.tableName || screenTableName; + const columnName = anyComp.columnName || anyComp.componentConfig?.columnName; + + // widget ํƒ€์ž… ๋˜๋Š” component ํƒ€์ž…์—์„œ columnName ํ™•์ธ (tableName์€ ํ™”๋ฉด ํ…Œ์ด๋ธ”๋ช…์œผ๋กœ fallback) + if ((comp.type === "widget" || comp.type === "component") && tableName && columnName) { + const key = `${tableName}.${columnName}`; placed.add(key); } @@ -492,7 +498,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList, onScreenU collectColumns(layout.components); return placed; - }, [layout.components]); + }, [layout.components, selectedScreen?.tableName]); // ํžˆ์Šคํ† ๋ฆฌ์— ์ €์žฅ const saveToHistory = useCallback( @@ -770,6 +776,22 @@ export default function ScreenDesigner({ selectedScreen, onBackToList, onScreenU const finalKey = pathParts[pathParts.length - 1]; current[finalKey] = value; + // ๐Ÿ”ง style ๊ด€๋ จ ์—…๋ฐ์ดํŠธ ๋””๋ฒ„๊ทธ ๋กœ๊ทธ + if (path.includes("style") || path.includes("labelDisplay")) { + console.log("๐ŸŽจ style ์—…๋ฐ์ดํŠธ ์ œ๋Œ€๋กœ ๋ Œ๋”๋ง๋œ๊ฑฐ๋‹ค ๋‚ด๊ฐ€๋ฐ”๊ฟˆ:", { + componentId: comp.id, + path, + value, + updatedStyle: newComp.style, + pathIncludesLabelDisplay: path.includes("labelDisplay"), + }); + } + + // ๐Ÿ†• labelDisplay ๋ณ€๊ฒฝ ์‹œ ๊ฐ•์ œ ๋ฆฌ๋ Œ๋”๋ง ํŠธ๋ฆฌ๊ฑฐ (์กฐ๊ฑด๋ฌธ ๋ฐ–์œผ๋กœ ์ด๋™) + if (path === "style.labelDisplay") { + console.log("โฐโฐโฐ labelDisplay ๋ณ€๊ฒฝ ๊ฐ์ง€! forceRenderTrigger ์‹คํ–‰ ์˜ˆ์ •"); + } + // ๐Ÿ†• size ๋ณ€๊ฒฝ ์‹œ style๋„ ํ•จ๊ป˜ ์—…๋ฐ์ดํŠธ (ํŒŒ๋ž€ ํ…Œ๋‘๋ฆฌ์™€ ์‹ค์ œ ํฌ๊ธฐ ๋™๊ธฐํ™”) if (path === "size.width" || path === "size.height" || path === "size") { // ๐Ÿ”ง style ๊ฐ์ฒด๋ฅผ ์ƒˆ๋กœ ๋ณต์‚ฌํ•˜์—ฌ ๋ถˆ๋ณ€์„ฑ ์œ ์ง€ @@ -1787,97 +1809,21 @@ export default function ScreenDesigner({ selectedScreen, onBackToList, onScreenU const buttonComponents = layoutWithResolution.components.filter( (c: any) => c.componentType?.startsWith("button") || c.type === "button" || c.type === "button-primary", ); - console.log("๐Ÿ’พ ์ €์žฅ ์‹œ์ž‘:", { - screenId: selectedScreen.screenId, - componentsCount: layoutWithResolution.components.length, - gridSettings: layoutWithResolution.gridSettings, - screenResolution: layoutWithResolution.screenResolution, - buttonComponents: buttonComponents.map((c: any) => ({ - id: c.id, - type: c.type, - componentType: c.componentType, - text: c.componentConfig?.text, - actionType: c.componentConfig?.action?.type, - fullAction: c.componentConfig?.action, - })), - }); - - // ๐Ÿ” ๋””๋ฒ„๊ทธ: ๋ถ„ํ•  ํŒจ๋„ ๋‚ด๋ถ€์˜ ํƒญ ๋ฐ ์ปดํฌ๋„ŒํŠธ ์„ค์ • ํ™•์ธ - const splitPanels = layoutWithResolution.components.filter( - (c: any) => c.componentType === "v2-split-panel-layout" || c.componentType === "split-panel-layout" - ); - splitPanels.forEach((sp: any) => { - console.log("๐Ÿ” [์ €์žฅ] ๋ถ„ํ•  ํŒจ๋„ ์„ค์ •:", { - id: sp.id, - leftPanel: sp.componentConfig?.leftPanel, - rightPanel: sp.componentConfig?.rightPanel, - }); - // ๐Ÿ†• ๋ถ„ํ•  ํŒจ๋„ ๋‚ด ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์˜ componentConfig ๋กœ๊ทธ - const rightComponents = sp.componentConfig?.rightPanel?.components || []; - console.log("๐Ÿ” [์ €์žฅ] ์˜ค๋ฅธ์ชฝ ํŒจ๋„ ์ปดํฌ๋„ŒํŠธ๋“ค:", rightComponents.map((c: any) => ({ - id: c.id, - componentType: c.componentType, - hasComponentConfig: !!c.componentConfig, - componentConfig: JSON.parse(JSON.stringify(c.componentConfig || {})), - }))); - // ์™ผ์ชฝ ํŒจ๋„์˜ ํƒญ ์ปดํฌ๋„ŒํŠธ ํ™•์ธ - const leftTabs = sp.componentConfig?.leftPanel?.components?.filter( - (c: any) => c.componentType === "v2-tabs-widget" - ); - leftTabs?.forEach((tabWidget: any) => { - console.log("๐Ÿ” [์ €์žฅ] ์™ผ์ชฝ ํŒจ๋„ ํƒญ ์œ„์ ฏ ์ „์ฒด componentConfig:", { - tabWidgetId: tabWidget.id, - fullComponentConfig: JSON.parse(JSON.stringify(tabWidget.componentConfig || {})), - }); - console.log("๐Ÿ” [์ €์žฅ] ์™ผ์ชฝ ํŒจ๋„ ํƒญ ๋‚ด๋ถ€ ์ปดํฌ๋„ŒํŠธ:", { - tabId: tabWidget.id, - tabs: tabWidget.componentConfig?.tabs?.map((t: any) => ({ - id: t.id, - label: t.label, - componentsCount: t.components?.length || 0, - components: t.components, - })), - }); - }); - // ์˜ค๋ฅธ์ชฝ ํŒจ๋„์˜ ํƒญ ์ปดํฌ๋„ŒํŠธ ํ™•์ธ - const rightTabs = sp.componentConfig?.rightPanel?.components?.filter( - (c: any) => c.componentType === "v2-tabs-widget" - ); - rightTabs?.forEach((tabWidget: any) => { - console.log("๐Ÿ” [์ €์žฅ] ์˜ค๋ฅธ์ชฝ ํŒจ๋„ ํƒญ ์œ„์ ฏ ์ „์ฒด componentConfig:", { - tabWidgetId: tabWidget.id, - fullComponentConfig: JSON.parse(JSON.stringify(tabWidget.componentConfig || {})), - }); - console.log("๐Ÿ” [์ €์žฅ] ์˜ค๋ฅธ์ชฝ ํŒจ๋„ ํƒญ ๋‚ด๋ถ€ ์ปดํฌ๋„ŒํŠธ:", { - tabId: tabWidget.id, - tabs: tabWidget.componentConfig?.tabs?.map((t: any) => ({ - id: t.id, - label: t.label, - componentsCount: t.components?.length || 0, - components: t.components, - })), - }); - }); - }); + // ๐Ÿ’พ ์ €์žฅ ๋กœ๊ทธ (๋””๋ฒ„๊ทธ ์™„๋ฃŒ - ๊ฐ„์†Œํ™”) + // console.log("๐Ÿ’พ ์ €์žฅ ์‹œ์ž‘:", { screenId: selectedScreen.screenId, componentsCount: layoutWithResolution.components.length }); + // ๋ถ„ํ•  ํŒจ๋„ ๋””๋ฒ„๊ทธ ๋กœ๊ทธ (์ฃผ์„ ์ฒ˜๋ฆฌ) // V2 API ์‚ฌ์šฉ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ๋ถ„๊ธฐ if (USE_V2_API) { + // ๐Ÿ”ง V2 ๋ ˆ์ด์•„์›ƒ ์ €์žฅ (๋””๋ฒ„๊ทธ ๋กœ๊ทธ ์ฃผ์„ ์ฒ˜๋ฆฌ) const v2Layout = convertLegacyToV2(layoutWithResolution); - console.log("๐Ÿ“ฆ V2 ๋ณ€ํ™˜ ๊ฒฐ๊ณผ (๋ถ„ํ•  ํŒจ๋„ overrides):", v2Layout.components - .filter((c: any) => c.url?.includes("split-panel")) - .map((c: any) => ({ - id: c.id, - url: c.url, - overrides: c.overrides, - })) - ); await screenApi.saveLayoutV2(selectedScreen.screenId, v2Layout); - console.log("๐Ÿ“ฆ V2 ๋ ˆ์ด์•„์›ƒ ์ €์žฅ:", v2Layout.components.length, "๊ฐœ ์ปดํฌ๋„ŒํŠธ"); + // console.log("๐Ÿ“ฆ V2 ๋ ˆ์ด์•„์›ƒ ์ €์žฅ:", v2Layout.components.length, "๊ฐœ ์ปดํฌ๋„ŒํŠธ"); } else { await screenApi.saveLayout(selectedScreen.screenId, layoutWithResolution); } - console.log("โœ… ์ €์žฅ ์„ฑ๊ณต! ๋ฉ”๋‰ด ํ• ๋‹น ๋ชจ๋‹ฌ ์—ด๊ธฐ"); + // console.log("โœ… ์ €์žฅ ์„ฑ๊ณต!"); toast.success("ํ™”๋ฉด์ด ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); // ์ €์žฅ ์„ฑ๊ณต ํ›„ ๋ถ€๋ชจ์—๊ฒŒ ํ™”๋ฉด ์ •๋ณด ์—…๋ฐ์ดํŠธ ์•Œ๋ฆผ (ํ…Œ์ด๋ธ”๋ช… ์ฆ‰์‹œ ๋ฐ˜์˜) @@ -3084,7 +3030,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList, onScreenU }, webTypeConfig: getDefaultWebTypeConfig(component.webType), style: { - labelDisplay: false, // ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์˜ ๊ธฐ๋ณธ ๋ผ๋ฒจ ํ‘œ์‹œ๋ฅผ false๋กœ ์„ค์ • + labelDisplay: true, // ๐Ÿ†• ๋ผ๋ฒจ ๊ธฐ๋ณธ ํ‘œ์‹œ (์‚ฌ์šฉ์ž๊ฐ€ ๋„๊ณ  ์‹ถ์œผ๋ฉด ์ฒดํฌ ํ•ด์ œ) labelFontSize: "14px", labelColor: "#212121", labelFontWeight: "500", @@ -3750,7 +3696,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList, onScreenU entityJoinColumn: column.entityJoinColumn, }), style: { - labelDisplay: false, // ๋ผ๋ฒจ ์ˆจ๊น€ + labelDisplay: true, // ๐Ÿ†• ๋ผ๋ฒจ ๊ธฐ๋ณธ ํ‘œ์‹œ labelFontSize: "12px", labelColor: "#212121", labelFontWeight: "500", @@ -3816,7 +3762,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList, onScreenU entityJoinColumn: column.entityJoinColumn, }), style: { - labelDisplay: false, // ๋ผ๋ฒจ ์ˆจ๊น€ + labelDisplay: true, // ๐Ÿ†• ๋ผ๋ฒจ ๊ธฐ๋ณธ ํ‘œ์‹œ labelFontSize: "14px", labelColor: "#000000", // ์ˆœ์ˆ˜ํ•œ ๊ฒ€์ • labelFontWeight: "500", @@ -5452,6 +5398,9 @@ export default function ScreenDesigner({ selectedScreen, onBackToList, onScreenU ); } + // ๐Ÿ”ง ScreenDesigner ๋ Œ๋”๋ง ํ™•์ธ (๋””๋ฒ„๊ทธ ์™„๋ฃŒ - ์ฃผ์„ ์ฒ˜๋ฆฌ) + // console.log("๐Ÿ  ScreenDesigner ๋ Œ๋”!", Date.now()); + return ( @@ -6163,6 +6112,9 @@ export default function ScreenDesigner({ selectedScreen, onBackToList, onScreenU // ๊ทธ๋ฃน์— ์†ํ•˜์ง€ ์•Š์€ ์ผ๋ฐ˜ ์ปดํฌ๋„ŒํŠธ๋“ค const regularComponents = topLevelComponents.filter((c) => !processedButtonIds.has(c.id)); + // ๐Ÿ”ง ๋ Œ๋”๋ง ํ™•์ธ ๋กœ๊ทธ (๋””๋ฒ„๊ทธ ์™„๋ฃŒ - ์ฃผ์„ ์ฒ˜๋ฆฌ) + // console.log("๐Ÿ”„ ScreenDesigner ๋ Œ๋”๋ง:", { componentsCount: regularComponents.length, forceRenderTrigger, timestamp: Date.now() }); + return ( <> {/* ์ผ๋ฐ˜ ์ปดํฌ๋„ŒํŠธ๋“ค */} @@ -6228,11 +6180,23 @@ export default function ScreenDesigner({ selectedScreen, onBackToList, onScreenU const globalFiles = globalFileState[component.id] || []; const componentFiles = (component as any).uploadedFiles || []; const fileStateKey = `${globalFiles.length}-${JSON.stringify(globalFiles.map((f: any) => f.objid) || [])}-${componentFiles.length}`; + // ๐Ÿ†• style ๋ณ€๊ฒฝ ์‹œ ๋ฆฌ๋ Œ๋”๋ง์„ ์œ„ํ•œ key ์ถ”๊ฐ€ + const styleKey = component.style?.labelDisplay !== undefined ? `label-${component.style.labelDisplay}` : ""; + const fullKey = `${component.id}-${fileStateKey}-${styleKey}-${(component as any).lastFileUpdate || 0}-${forceRenderTrigger}`; + + // ๐Ÿ”ง v2-input ๊ณ„์—ด ์ปดํฌ๋„ŒํŠธ key ๋ณ€๊ฒฝ ๋กœ๊ทธ (๋””๋ฒ„๊ทธ ์™„๋ฃŒ - ์ฃผ์„ ์ฒ˜๋ฆฌ) + // if (component.id.includes("v2-") || component.widgetType?.includes("v2-")) { console.log("๐Ÿ”‘ RealtimePreview key:", { id: component.id, styleKey, labelDisplay: component.style?.labelDisplay, forceRenderTrigger, fullKey }); } + + // ๐Ÿ†• labelDisplay ๋ณ€๊ฒฝ ์‹œ ์ƒˆ ๊ฐ์ฒด๋กœ ๊ฐ•์ œ ๋ณ€๊ฒฝ ๊ฐ์ง€ + const componentWithLabel = { + ...displayComponent, + _labelDisplayKey: component.style?.labelDisplay, + }; return ( = ({
{ handleUpdate("style.labelText", e.target.value); handleUpdate("label", e.target.value); // label๋„ ํ•จ๊ป˜ ์—…๋ฐ์ดํŠธ @@ -861,8 +861,23 @@ export const V2PropertiesPanel: React.FC = ({
handleUpdate("style.labelDisplay", checked)} + checked={selectedComponent.style?.labelDisplay === true || selectedComponent.labelDisplay === true} + onCheckedChange={(checked) => { + const boolValue = checked === true; + // ๐Ÿ”ง "ํ•„์ˆ˜"์ฒ˜๋Ÿผ ์ง์ ‘ ๊ฒฝ๋กœ๋กœ ์—…๋ฐ์ดํŠธ! (style ๊ฐ์ฒด ์ „์ฒด ๋ฎ์–ด์“ฐ๊ธฐ ๋ฐฉ์ง€) + handleUpdate("style.labelDisplay", boolValue); + handleUpdate("labelDisplay", boolValue); + // labelText๋„ ์„ค์ • (์ฒ˜์Œ ์ผค ๋•Œ ๋ผ๋ฒจ ํ…์ŠคํŠธ๊ฐ€ ์—†์„ ์ˆ˜ ์žˆ์Œ) + if (boolValue && !selectedComponent.style?.labelText) { + const labelValue = + selectedComponent.label || + selectedComponent.componentConfig?.label || + ""; + if (labelValue) { + handleUpdate("style.labelText", labelValue); + } + } + }} className="h-4 w-4" /> diff --git a/frontend/components/v2/V2Input.tsx b/frontend/components/v2/V2Input.tsx index 85083174..ac4bc33c 100644 --- a/frontend/components/v2/V2Input.tsx +++ b/frontend/components/v2/V2Input.tsx @@ -802,7 +802,9 @@ export const V2Input = forwardRef((props, ref) => }; // ๋ผ๋ฒจ์ด ํ‘œ์‹œ๋  ๋•Œ ์ž…๋ ฅ ํ•„๋“œ๊ฐ€ ์ฐจ์ง€ํ•  ๋†’์ด ๊ณ„์‚ฐ - const showLabel = label && style?.labelDisplay !== false; + // ๐Ÿ”ง label prop์ด ์—†์–ด๋„ style.labelText์—์„œ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋„๋ก ์ˆ˜์ • + const actualLabel = label || style?.labelText; + const showLabel = actualLabel && style?.labelDisplay === true; // size์—์„œ ์šฐ์„  ๊ฐ€์ ธ์˜ค๊ณ , ์—†์œผ๋ฉด style์—์„œ ๊ฐ€์ ธ์˜ด const componentWidth = size?.width || style?.width; const componentHeight = size?.height || style?.height; @@ -836,7 +838,7 @@ export const V2Input = forwardRef((props, ref) => }} className="text-sm font-medium whitespace-nowrap" > - {label} + {actualLabel} {required && *} )} diff --git a/frontend/lib/registry/DynamicComponentRenderer.tsx b/frontend/lib/registry/DynamicComponentRenderer.tsx index 9571abef..4fe888bf 100644 --- a/frontend/lib/registry/DynamicComponentRenderer.tsx +++ b/frontend/lib/registry/DynamicComponentRenderer.tsx @@ -513,6 +513,18 @@ export const DynamicComponentRenderer: React.FC = componentType === "modal-repeater-table" || componentType === "v2-input"; + // ๐Ÿ†• v2-input ๋“ฑ์˜ ๋ผ๋ฒจ ํ‘œ์‹œ ๋กœ์ง (labelDisplay๊ฐ€ true์ผ ๋•Œ๋งŒ ๋ผ๋ฒจ ํ‘œ์‹œ) + const labelDisplay = component.style?.labelDisplay ?? (component as any).labelDisplay; + const effectiveLabel = labelDisplay === true + ? (component.style?.labelText || (component as any).label || component.componentConfig?.label) + : undefined; + + // ๐Ÿ”ง ์ˆœ์„œ ์ค‘์š”! finalStyle ๋จผ์ €, component.style ๋‚˜์ค‘์— (์ปค์Šคํ…€ ์†์„ฑ์ด CSS ์†์„ฑ์„ ๋ฎ์–ด์จ์•ผ ํ•จ) + const mergedStyle = { + ...finalStyle, // CSS ์†์„ฑ (width, height ๋“ฑ) - ๋จผ์ €! + ...component.style, // ์›๋ณธ style (labelDisplay, labelText ๋“ฑ) - ๋‚˜์ค‘์—! (๋ฎ์–ด์”€) + }; + const rendererProps = { component, isSelected, @@ -521,11 +533,14 @@ export const DynamicComponentRenderer: React.FC = onDragEnd, size: component.size || newComponent.defaultSize, position: component.position, - style: finalStyle, // size๋ฅผ ํฌํ•จํ•œ ์ตœ์ข… style config: component.componentConfig, componentConfig: component.componentConfig, // componentConfig์˜ ๋ชจ๋“  ์†์„ฑ์„ props๋กœ spread (tableName, displayField ๋“ฑ) ...(component.componentConfig || {}), + // ๐Ÿ”ง style์€ ๋งจ ๋งˆ์ง€๋ง‰์—! (componentConfig.style์ด ์žˆ์–ด๋„ mergedStyle์ด ์šฐ์„ ) + style: mergedStyle, + // ๐Ÿ†• ๋ผ๋ฒจ ํ‘œ์‹œ (labelDisplay๊ฐ€ true์ผ ๋•Œ๋งŒ) + label: effectiveLabel, // ๐Ÿ†• V2 ๋ ˆ์ด์•„์›ƒ์—์„œ overrides์—์„œ ๋ณต์›๋œ ์ƒ์œ„ ๋ ˆ๋ฒจ ์†์„ฑ๋“ค๋„ ์ „๋‹ฌ inputType: (component as any).inputType || component.componentConfig?.inputType, columnName: (component as any).columnName || component.componentConfig?.columnName, diff --git a/frontend/lib/registry/components/v2-button-primary/ButtonPrimaryComponent.tsx b/frontend/lib/registry/components/v2-button-primary/ButtonPrimaryComponent.tsx index 83a7771d..d6ed349f 100644 --- a/frontend/lib/registry/components/v2-button-primary/ButtonPrimaryComponent.tsx +++ b/frontend/lib/registry/components/v2-button-primary/ButtonPrimaryComponent.tsx @@ -555,13 +555,10 @@ export const ButtonPrimaryComponent: React.FC = ({ } // ์Šคํƒ€์ผ ๊ณ„์‚ฐ - // height: 100%๋กœ ๋ถ€๋ชจ(RealtimePreviewDynamic์˜ ๋‚ด๋ถ€ div)์˜ ๋†’์ด๋ฅผ ๋”ฐ๋ผ๊ฐ - // width๋Š” ํ•ญ์ƒ 100%๋กœ ๊ณ ์ • (๋ถ€๋ชจ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ gridColumns๋กœ ํฌ๊ธฐ ์ œ์–ด) + // ๐Ÿ”ง ์‚ฌ์šฉ์ž๊ฐ€ ์„ค์ •ํ•œ ํฌ๊ธฐ๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ const componentStyle: React.CSSProperties = { ...component.style, ...style, - width: "100%", - height: "100%", }; // ๋””์ž์ธ ๋ชจ๋“œ ์Šคํƒ€์ผ (border ์†์„ฑ ๋ถ„๋ฆฌํ•˜์—ฌ ์ถฉ๋Œ ๋ฐฉ์ง€) @@ -1289,19 +1286,23 @@ export const ButtonPrimaryComponent: React.FC = ({ componentConfig.disabled || isOperationButtonDisabled || isRowSelectionDisabled || statusLoading; // ๊ณตํ†ต ๋ฒ„ํŠผ ์Šคํƒ€์ผ - // ๐Ÿ”ง component.style์—์„œ background/backgroundColor ์ถฉ๋Œ ๋ฐฉ์ง€ + // ๐Ÿ”ง component.style์—์„œ background/backgroundColor ์ถฉ๋Œ ๋ฐฉ์ง€ (width/height๋Š” ํ—ˆ์šฉ) const userStyle = component.style ? Object.fromEntries( Object.entries(component.style).filter( - ([key]) => !["width", "height", "background", "backgroundColor"].includes(key), + ([key]) => !["background", "backgroundColor"].includes(key), ), ) : {}; + // ๐Ÿ”ง ์‚ฌ์šฉ์ž๊ฐ€ ์„ค์ •ํ•œ ํฌ๊ธฐ ์šฐ์„  ์‚ฌ์šฉ, ์—†์œผ๋ฉด 100% + const buttonWidth = component.size?.width ? `${component.size.width}px` : (style?.width || "100%"); + const buttonHeight = component.size?.height ? `${component.size.height}px` : (style?.height || "100%"); + const buttonElementStyle: React.CSSProperties = { - width: "100%", - height: "100%", - minHeight: "40px", + width: buttonWidth, + height: buttonHeight, + minHeight: "32px", // ๐Ÿ”ง ์ตœ์†Œ ๋†’์ด๋ฅผ 32px๋กœ ์ค„์ž„ border: "none", borderRadius: "0.5rem", backgroundColor: finalDisabled ? "#e5e7eb" : buttonColor, diff --git a/frontend/lib/registry/components/v2-input/V2InputRenderer.tsx b/frontend/lib/registry/components/v2-input/V2InputRenderer.tsx index 52a230fa..e67a8399 100644 --- a/frontend/lib/registry/components/v2-input/V2InputRenderer.tsx +++ b/frontend/lib/registry/components/v2-input/V2InputRenderer.tsx @@ -31,9 +31,11 @@ export class V2InputRenderer extends AutoRegisteringComponentRenderer { }; // ๋ผ๋ฒจ: style.labelText ์šฐ์„ , ์—†์œผ๋ฉด component.label ์‚ฌ์šฉ - // style.labelDisplay๊ฐ€ false๋ฉด ๋ผ๋ฒจ ์ˆจ๊น€ + // ๐Ÿ”ง style.labelDisplay๋ฅผ ๋จผ์ € ์ฒดํฌ (์†์„ฑ ํŒจ๋„์—์„œ style ๊ฐ์ฒด๋กœ ์—…๋ฐ์ดํŠธํ•˜๋ฏ€๋กœ) const style = component.style || {}; - const effectiveLabel = style.labelDisplay === false ? undefined : (style.labelText || component.label); + const labelDisplay = style.labelDisplay ?? (component as any).labelDisplay; + // labelDisplay: true โ†’ ๋ผ๋ฒจ ํ‘œ์‹œ, false โ†’ ์ˆจ๊น€, undefined โ†’ ๊ธฐ์กด ๋™์ž‘ ์œ ์ง€(์ˆจ๊น€) + const effectiveLabel = labelDisplay === true ? (style.labelText || component.label) : undefined; return ( ; }; + // ๐Ÿ†• ํ”Œ๋กœ์šฐ ๊ธฐ๋ฐ˜ ์ œ์–ด ์„ค์ • + flowConfig?: { + flowId: number; + flowName: string; + executionTiming: "before" | "after" | "replace"; + contextData?: Record; + }; } interface ExecutionPlan { @@ -163,15 +170,22 @@ export class ImprovedButtonActionExecutor { return plan; } - // enableDataflowControl ์ฒดํฌ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  dataflowConfig๋งŒ ์žˆ์œผ๋ฉด ์‹คํ–‰ + // ๐Ÿ”ง controlMode๊ฐ€ ์—†์œผ๋ฉด flowConfig/relationshipConfig ์กด์žฌ ์—ฌ๋ถ€๋กœ ์ž๋™ ํŒ๋‹จ + const effectiveControlMode = dataflowConfig.controlMode + || (dataflowConfig.flowConfig ? "flow" : null) + || (dataflowConfig.relationshipConfig ? "relationship" : null) + || "none"; + console.log("๐Ÿ“‹ ์‹คํ–‰ ๊ณ„ํš ์ƒ์„ฑ:", { controlMode: dataflowConfig.controlMode, + effectiveControlMode, + hasFlowConfig: !!dataflowConfig.flowConfig, hasRelationshipConfig: !!dataflowConfig.relationshipConfig, enableDataflowControl: buttonConfig.enableDataflowControl, }); - // ๊ด€๊ณ„ ๊ธฐ๋ฐ˜ ์ œ์–ด๋งŒ ์ง€์› - if (dataflowConfig.controlMode === "relationship" && dataflowConfig.relationshipConfig) { + // ๊ด€๊ณ„ ๊ธฐ๋ฐ˜ ์ œ์–ด + if (effectiveControlMode === "relationship" && dataflowConfig.relationshipConfig) { const control: ControlConfig = { type: "relationship", relationshipConfig: dataflowConfig.relationshipConfig, @@ -191,11 +205,34 @@ export class ImprovedButtonActionExecutor { } } + // ๐Ÿ†• ํ”Œ๋กœ์šฐ ๊ธฐ๋ฐ˜ ์ œ์–ด + if (effectiveControlMode === "flow" && dataflowConfig.flowConfig) { + const control: ControlConfig = { + type: "flow", + flowConfig: dataflowConfig.flowConfig, + }; + + console.log("๐Ÿ“‹ ํ”Œ๋กœ์šฐ ์ œ์–ด ์„ค์ •:", dataflowConfig.flowConfig); + + switch (dataflowConfig.flowConfig.executionTiming) { + case "before": + plan.beforeControls.push(control); + break; + case "after": + plan.afterControls.push(control); + break; + case "replace": + plan.afterControls.push(control); + plan.hasReplaceControl = true; + break; + } + } + return plan; } /** - * ๐Ÿ”ฅ ์ œ์–ด ์‹คํ–‰ (๊ด€๊ณ„ ๋˜๋Š” ์™ธ๋ถ€ํ˜ธ์ถœ) + * ๐Ÿ”ฅ ์ œ์–ด ์‹คํ–‰ (๊ด€๊ณ„ ๋˜๋Š” ํ”Œ๋กœ์šฐ) */ private static async executeControls( controls: ControlConfig[], @@ -206,8 +243,16 @@ export class ImprovedButtonActionExecutor { for (const control of controls) { try { - // ๊ด€๊ณ„ ์‹คํ–‰๋งŒ ์ง€์› - const result = await this.executeRelationship(control.relationshipConfig, formData, context); + let result: ExecutionResult; + + // ๐Ÿ†• ์ œ์–ด ํƒ€์ž…์— ๋”ฐ๋ผ ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ + if (control.type === "flow" && control.flowConfig) { + result = await this.executeFlow(control.flowConfig, formData, context); + } else if (control.type === "relationship" && control.relationshipConfig) { + result = await this.executeRelationship(control.relationshipConfig, formData, context); + } else { + throw new Error(`์ง€์›ํ•˜์ง€ ์•Š๋Š” ์ œ์–ด ํƒ€์ž…: ${control.type}`); + } results.push(result); @@ -215,7 +260,7 @@ export class ImprovedButtonActionExecutor { if (!result.success) { throw new Error(result.message); } - } catch (error) { + } catch (error: any) { console.error(`์ œ์–ด ์‹คํ–‰ ์‹คํŒจ (${control.type}):`, error); results.push({ success: false, @@ -230,6 +275,61 @@ export class ImprovedButtonActionExecutor { return results; } + /** + * ๐Ÿ†• ํ”Œ๋กœ์šฐ ์‹คํ–‰ + */ + private static async executeFlow( + config: { + flowId: number; + flowName: string; + executionTiming: "before" | "after" | "replace"; + contextData?: Record; + }, + formData: Record, + context: ButtonExecutionContext, + ): Promise { + const startTime = Date.now(); + + try { + console.log(`๐Ÿ”„ ํ”Œ๋กœ์šฐ ์‹คํ–‰ ์‹œ์ž‘: ${config.flowName} (ID: ${config.flowId})`); + + // ํ”Œ๋กœ์šฐ ์‹คํ–‰ API ํ˜ธ์ถœ + const response = await apiClient.post(`/api/dataflow/node-flows/${config.flowId}/execute`, { + formData, + contextData: config.contextData || {}, + selectedRows: context.selectedRows || [], + flowSelectedData: context.flowSelectedData || [], + screenId: context.screenId, + companyCode: context.companyCode, + userId: context.userId, + }); + + const executionTime = Date.now() - startTime; + + if (response.data?.success) { + console.log(`โœ… ํ”Œ๋กœ์šฐ ์‹คํ–‰ ์„ฑ๊ณต: ${config.flowName}`, response.data); + return { + success: true, + message: `ํ”Œ๋กœ์šฐ "${config.flowName}" ์‹คํ–‰ ์™„๋ฃŒ`, + executionTime, + data: response.data, + }; + } else { + throw new Error(response.data?.message || "ํ”Œ๋กœ์šฐ ์‹คํ–‰ ์‹คํŒจ"); + } + } catch (error: any) { + const executionTime = Date.now() - startTime; + console.error(`โŒ ํ”Œ๋กœ์šฐ ์‹คํ–‰ ์‹คํŒจ: ${config.flowName}`, error); + + return { + success: false, + message: `ํ”Œ๋กœ์šฐ "${config.flowName}" ์‹คํ–‰ ์‹คํŒจ: ${error.message}`, + executionTime, + error: error.message, + }; + } + } + /** * ๐Ÿ”ฅ ๊ด€๊ณ„ ์‹คํ–‰ */ diff --git a/frontend/lib/utils/layoutV2Converter.ts b/frontend/lib/utils/layoutV2Converter.ts index 32360a73..b8485487 100644 --- a/frontend/lib/utils/layoutV2Converter.ts +++ b/frontend/lib/utils/layoutV2Converter.ts @@ -191,6 +191,8 @@ export function convertV2ToLegacy(v2Layout: LayoutV2 | null): LegacyLayoutData | autoFill: overrides.autoFill, // ๐Ÿ†• style ์„ค์ • ๋ณต์› (๋ผ๋ฒจ ํ…์ŠคํŠธ, ๋ผ๋ฒจ ์Šคํƒ€์ผ ๋“ฑ) style: overrides.style || {}, + // ๐Ÿ”ง webTypeConfig ๋ณต์› (๋ฒ„ํŠผ ์ œ์–ด๊ธฐ๋Šฅ, ํ”Œ๋กœ์šฐ ๊ฐ€์‹œ์„ฑ ๋“ฑ) + webTypeConfig: overrides.webTypeConfig || {}, // ๊ธฐ์กด ๊ตฌ์กฐ ํ˜ธํ™˜์„ ์œ„ํ•œ ์ถ”๊ฐ€ ํ•„๋“œ parentId: null, gridColumns: 12, @@ -245,13 +247,47 @@ export function convertLegacyToV2(legacyLayout: LegacyLayoutData): LayoutV2 { if (comp.autoFill) topLevelProps.autoFill = comp.autoFill; // ๐Ÿ†• style ์„ค์ • ์ €์žฅ (๋ผ๋ฒจ ํ…์ŠคํŠธ, ๋ผ๋ฒจ ์Šคํƒ€์ผ ๋“ฑ) if (comp.style && Object.keys(comp.style).length > 0) topLevelProps.style = comp.style; + // ๐Ÿ”ง webTypeConfig ์ €์žฅ (๋ฒ„ํŠผ ์ œ์–ด๊ธฐ๋Šฅ, ํ”Œ๋กœ์šฐ ๊ฐ€์‹œ์„ฑ ๋“ฑ) + if (comp.webTypeConfig && Object.keys(comp.webTypeConfig).length > 0) { + topLevelProps.webTypeConfig = comp.webTypeConfig; + // ๐Ÿ” ๋””๋ฒ„๊ทธ: webTypeConfig ์ €์žฅ ํ™•์ธ + if (comp.webTypeConfig.dataflowConfig || comp.webTypeConfig.enableDataflowControl) { + console.log("๐Ÿ’พ webTypeConfig ์ €์žฅ:", { + componentId: comp.id, + enableDataflowControl: comp.webTypeConfig.enableDataflowControl, + dataflowConfig: comp.webTypeConfig.dataflowConfig, + }); + } + } // ํ˜„์žฌ ์„ค์ •์—์„œ ์ฐจ์ด๊ฐ’๋งŒ ์ถ”์ถœ const fullConfig = comp.componentConfig || {}; const configOverrides = extractCustomConfig(fullConfig, defaults); + // ๐Ÿ”ง ๋””๋ฒ„๊ทธ: style ์ €์žฅ ํ™•์ธ (์ฃผ์„ ์ฒ˜๋ฆฌ) + // if (comp.style?.labelDisplay !== undefined || configOverrides.style?.labelDisplay !== undefined) { console.log("๐Ÿ’พ ์ €์žฅ ์‹œ style ๋ณ€ํ™˜:", { componentId: comp.id, "comp.style": comp.style, "configOverrides.style": configOverrides.style, "topLevelProps.style": topLevelProps.style }); } + // ์ƒ์œ„ ๋ ˆ๋ฒจ ์†์„ฑ๊ณผ componentConfig ๋ณ‘ํ•ฉ - const overrides = { ...topLevelProps, ...configOverrides }; + // ๐Ÿ”ง style์€ ์–‘์ชฝ์„ ๋ณ‘ํ•ฉํ•˜๋˜ comp.style(topLevelProps.style)์„ ์šฐ์„ ์‹œ + const mergedStyle = { + ...(configOverrides.style || {}), + ...(topLevelProps.style || {}), + }; + + // ๐Ÿ”ง webTypeConfig๋„ ๋ณ‘ํ•ฉ (topLevelProps๊ฐ€ ์šฐ์„ , dataflowConfig ๋“ฑ ๋ณด์กด) + const mergedWebTypeConfig = { + ...(configOverrides.webTypeConfig || {}), + ...(topLevelProps.webTypeConfig || {}), + }; + + const overrides = { + ...topLevelProps, + ...configOverrides, + // ๐Ÿ†• ๋ณ‘ํ•ฉ๋œ style ์‚ฌ์šฉ (comp.style ๊ฐ’์ด ์ตœ์ข… ์šฐ์„ ) + ...(Object.keys(mergedStyle).length > 0 ? { style: mergedStyle } : {}), + // ๐Ÿ†• ๋ณ‘ํ•ฉ๋œ webTypeConfig ์‚ฌ์šฉ (comp.webTypeConfig๊ฐ€ ์ตœ์ข… ์šฐ์„ ) + ...(Object.keys(mergedWebTypeConfig).length > 0 ? { webTypeConfig: mergedWebTypeConfig } : {}), + }; return { id: comp.id, diff --git a/frontend/next.config.mjs b/frontend/next.config.mjs index ca804adc..2e23bc81 100644 --- a/frontend/next.config.mjs +++ b/frontend/next.config.mjs @@ -15,7 +15,8 @@ const nextConfig = { // ์‹คํ—˜์  ๊ธฐ๋Šฅ ํ™œ์„ฑํ™” experimental: { - outputFileTracingRoot: undefined, + // ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ตœ์ ํ™” (Next.js 15+) + webpackMemoryOptimizations: true, }, // API ํ”„๋ก์‹œ ์„ค์ • - ๋ฐฑ์—”๋“œ๋กœ ์š”์ฒญ ์ „๋‹ฌ