diff --git a/backend-node/src/services/riskAlertCacheService.ts b/backend-node/src/services/riskAlertCacheService.ts index cc4de181..ce8b6089 100644 --- a/backend-node/src/services/riskAlertCacheService.ts +++ b/backend-node/src/services/riskAlertCacheService.ts @@ -34,16 +34,35 @@ export class RiskAlertCacheService { */ public startAutoRefresh(): void { console.log('๐Ÿ”„ ๋ฆฌ์Šคํฌ/์•Œ๋ฆผ ์ž๋™ ๊ฐฑ์‹  ์‹œ์ž‘ (10๋ถ„ ๊ฐ„๊ฒฉ)'); + console.log(' - ๊ธฐ์ƒํŠน๋ณด: ์ฆ‰์‹œ ํ˜ธ์ถœ'); + console.log(' - ๊ตํ†ต์‚ฌ๊ณ /๋„๋กœ๊ณต์‚ฌ: 10๋ถ„ ํ›„ ์ฒซ ํ˜ธ์ถœ'); - // ์ฆ‰์‹œ ์ฒซ ๊ฐฑ์‹  - this.refreshCache(); + // ๊ธฐ์ƒํŠน๋ณด๋งŒ ์ฆ‰์‹œ ํ˜ธ์ถœ (ITS API๋Š” 10๋ถ„ ํ›„๋ถ€ํ„ฐ) + this.refreshWeatherOnly(); - // 10๋ถ„๋งˆ๋‹ค ๊ฐฑ์‹  (600,000ms) + // 10๋ถ„๋งˆ๋‹ค ์ „์ฒด ๊ฐฑ์‹  (600,000ms) this.updateInterval = setInterval(() => { this.refreshCache(); }, 10 * 60 * 1000); } + /** + * ๊ธฐ์ƒํŠน๋ณด๋งŒ ๊ฐฑ์‹  (์žฌ์‹œ์ž‘ ์‹œ ์‚ฌ์šฉ) + */ + private async refreshWeatherOnly(): Promise { + try { + console.log('๐ŸŒค๏ธ ๊ธฐ์ƒํŠน๋ณด๋งŒ ์ฆ‰์‹œ ๊ฐฑ์‹  ์ค‘...'); + const weatherAlerts = await this.riskAlertService.getWeatherAlerts(); + + this.cachedAlerts = weatherAlerts; + this.lastUpdated = new Date(); + + console.log(`โœ… ๊ธฐ์ƒํŠน๋ณด ๊ฐฑ์‹  ์™„๋ฃŒ! (${weatherAlerts.length}๊ฑด)`); + } catch (error: any) { + console.error('โŒ ๊ธฐ์ƒํŠน๋ณด ๊ฐฑ์‹  ์‹คํŒจ:', error.message); + } + } + /** * ์ž๋™ ๊ฐฑ์‹  ์ค‘์ง€ */ diff --git a/frontend/components/dashboard/widgets/RiskAlertWidget.tsx b/frontend/components/dashboard/widgets/RiskAlertWidget.tsx index 98278fdd..de6b2af8 100644 --- a/frontend/components/dashboard/widgets/RiskAlertWidget.tsx +++ b/frontend/components/dashboard/widgets/RiskAlertWidget.tsx @@ -33,11 +33,11 @@ export default function RiskAlertWidget({ element }: RiskAlertWidgetProps) { const [lastUpdated, setLastUpdated] = useState(null); const [newAlertIds, setNewAlertIds] = useState>(new Set()); - // ๋ฐ์ดํ„ฐ ๋กœ๋“œ (๋ฐฑ์—”๋“œ ํ†ตํ•ฉ ํ˜ธ์ถœ) + // ๋ฐ์ดํ„ฐ ๋กœ๋“œ (๋ฐฑ์—”๋“œ ์บ์‹œ ์กฐํšŒ) const loadData = async () => { setIsRefreshing(true); try { - // ๋ฐฑ์—”๋“œ API ํ˜ธ์ถœ (๊ตํ†ต์‚ฌ๊ณ , ๊ธฐ์ƒํŠน๋ณด, ๋„๋กœ๊ณต์‚ฌ ํ†ตํ•ฉ) + // ๋ฐฑ์—”๋“œ API ํ˜ธ์ถœ (์บ์‹œ๋œ ๋ฐ์ดํ„ฐ) const response = await apiClient.get<{ success: boolean; data: Alert[]; @@ -79,6 +79,48 @@ export default function RiskAlertWidget({ element }: RiskAlertWidgetProps) { } }; + // ๊ฐ•์ œ ์ƒˆ๋กœ๊ณ ์นจ (์‹ค์‹œ๊ฐ„ API ํ˜ธ์ถœ) + const forceRefresh = async () => { + setIsRefreshing(true); + try { + // ๊ฐ•์ œ ๊ฐฑ์‹  API ํ˜ธ์ถœ (์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ) + const response = await apiClient.post<{ + success: boolean; + data: Alert[]; + count: number; + message?: string; + }>("/risk-alerts/refresh", {}); + + if (response.data.success && response.data.data) { + const newData = response.data.data; + + // ์ƒˆ๋กœ์šด ์•Œ๋ฆผ ๊ฐ์ง€ + const oldIds = new Set(alerts.map(a => a.id)); + const newIds = new Set(); + newData.forEach(alert => { + if (!oldIds.has(alert.id)) { + newIds.add(alert.id); + } + }); + + setAlerts(newData); + setNewAlertIds(newIds); + setLastUpdated(new Date()); + + // 3์ดˆ ํ›„ ์ƒˆ ์•Œ๋ฆผ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ œ๊ฑฐ + if (newIds.size > 0) { + setTimeout(() => setNewAlertIds(new Set()), 3000); + } + } else { + console.error("โŒ ๋ฆฌ์Šคํฌ ์•Œ๋ฆผ ๊ฐ•์ œ ๊ฐฑ์‹  ์‹คํŒจ"); + } + } catch (error: any) { + console.error("โŒ ๋ฆฌ์Šคํฌ ์•Œ๋ฆผ ๊ฐ•์ œ ๊ฐฑ์‹  ์˜ค๋ฅ˜:", error.message); + } finally { + setIsRefreshing(false); + } + }; + useEffect(() => { loadData(); // 1๋ถ„๋งˆ๋‹ค ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ (60000ms) @@ -156,7 +198,7 @@ export default function RiskAlertWidget({ element }: RiskAlertWidgetProps) { {lastUpdated.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit' })} )} - diff --git a/frontend/components/screen/ScreenDesigner.tsx b/frontend/components/screen/ScreenDesigner.tsx index 022b53c1..1b9ec180 100644 --- a/frontend/components/screen/ScreenDesigner.tsx +++ b/frontend/components/screen/ScreenDesigner.tsx @@ -420,37 +420,39 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD timestamp: new Date().toISOString(), }); - const targetComponent = layout.components.find((comp) => comp.id === componentId); - const isLayoutComponent = targetComponent?.type === "layout"; + // ๐Ÿ”ฅ ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ์ตœ์‹  layout ์‚ฌ์šฉ + setLayout((prevLayout) => { + const targetComponent = prevLayout.components.find((comp) => comp.id === componentId); + const isLayoutComponent = targetComponent?.type === "layout"; - // ๋ ˆ์ด์•„์›ƒ ์ปดํฌ๋„ŒํŠธ์˜ ์œ„์น˜๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒฝ์šฐ ์กด์— ์†ํ•œ ์ปดํฌ๋„ŒํŠธ๋“ค๋„ ํ•จ๊ป˜ ์ด๋™ - const positionDelta = { x: 0, y: 0 }; - if (isLayoutComponent && (path === "position.x" || path === "position.y" || path === "position")) { - const oldPosition = targetComponent.position; - let newPosition = { ...oldPosition }; + // ๋ ˆ์ด์•„์›ƒ ์ปดํฌ๋„ŒํŠธ์˜ ์œ„์น˜๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒฝ์šฐ ์กด์— ์†ํ•œ ์ปดํฌ๋„ŒํŠธ๋“ค๋„ ํ•จ๊ป˜ ์ด๋™ + const positionDelta = { x: 0, y: 0 }; + if (isLayoutComponent && (path === "position.x" || path === "position.y" || path === "position")) { + const oldPosition = targetComponent.position; + let newPosition = { ...oldPosition }; - if (path === "position.x") { - newPosition.x = value; - positionDelta.x = value - oldPosition.x; - } else if (path === "position.y") { - newPosition.y = value; - positionDelta.y = value - oldPosition.y; - } else if (path === "position") { - newPosition = value; - positionDelta.x = value.x - oldPosition.x; - positionDelta.y = value.y - oldPosition.y; + if (path === "position.x") { + newPosition.x = value; + positionDelta.x = value - oldPosition.x; + } else if (path === "position.y") { + newPosition.y = value; + positionDelta.y = value - oldPosition.y; + } else if (path === "position") { + newPosition = value; + positionDelta.x = value.x - oldPosition.x; + positionDelta.y = value.y - oldPosition.y; + } + + console.log("๐Ÿ“ ๋ ˆ์ด์•„์›ƒ ์ด๋™ ๊ฐ์ง€:", { + layoutId: componentId, + oldPosition, + newPosition, + positionDelta, + }); } - console.log("๐Ÿ“ ๋ ˆ์ด์•„์›ƒ ์ด๋™ ๊ฐ์ง€:", { - layoutId: componentId, - oldPosition, - newPosition, - positionDelta, - }); - } - - const pathParts = path.split("."); - const updatedComponents = layout.components.map((comp) => { + const pathParts = path.split("."); + const updatedComponents = prevLayout.components.map((comp) => { if (comp.id !== componentId) { // ๋ ˆ์ด์•„์›ƒ ์ด๋™ ์‹œ ์กด์— ์†ํ•œ ์ปดํฌ๋„ŒํŠธ๋“ค๋„ ํ•จ๊ป˜ ์ด๋™ if (isLayoutComponent && (positionDelta.x !== 0 || positionDelta.y !== 0)) { @@ -480,22 +482,35 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD // ์ค‘์ฒฉ ๊ฒฝ๋กœ๋ฅผ ๊ณ ๋ คํ•œ ์•ˆ์ „ํ•œ ๋ณต์‚ฌ const newComp = { ...comp }; + console.log("๐Ÿ” ์—…๋ฐ์ดํŠธ ์ „ ์ƒํƒœ:", { + path, + value, + "๊ธฐ์กด componentConfig": newComp.componentConfig, + "๊ธฐ์กด action": (newComp as any).componentConfig?.action, + }); + // ๊ฒฝ๋กœ๋ฅผ ๋”ฐ๋ผ ๋‚ด๋ ค๊ฐ€๋ฉด์„œ ๊ฐ ๋ ˆ๋ฒจ์„ ์ƒˆ ๊ฐ์ฒด๋กœ ๋ณต์‚ฌ let current: any = newComp; for (let i = 0; i < pathParts.length - 1; i++) { const key = pathParts[i]; + console.log(`๐Ÿ” ๊ฒฝ๋กœ ํƒ์ƒ‰ [${i}]: key="${key}", current[key]=`, current[key]); + // ๋‹ค์Œ ๋ ˆ๋ฒจ์ด ์—†๊ฑฐ๋‚˜ ๊ฐ์ฒด๊ฐ€ ์•„๋‹ˆ๋ฉด ์ƒˆ ๊ฐ์ฒด ์ƒ์„ฑ if (!current[key] || typeof current[key] !== "object" || Array.isArray(current[key])) { + console.log(`๐Ÿ†• ์ƒˆ ๊ฐ์ฒด ์ƒ์„ฑ: ${key}`); current[key] = {}; } else { // ๊ธฐ์กด ๊ฐ์ฒด๋ฅผ ๋ณต์‚ฌํ•˜์—ฌ ๋ถˆ๋ณ€์„ฑ ์œ ์ง€ + console.log(`๐Ÿ“‹ ๊ธฐ์กด ๊ฐ์ฒด ๋ณต์‚ฌ: ${key}`, { ...current[key] }); current[key] = { ...current[key] }; } current = current[key]; } // ์ตœ์ข… ๊ฐ’ ์„ค์ • - current[pathParts[pathParts.length - 1]] = value; + const finalKey = pathParts[pathParts.length - 1]; + console.log(`โœ๏ธ ์ตœ์ข… ๊ฐ’ ์„ค์ •: ${finalKey} = ${value}`); + current[finalKey] = value; console.log("โœ… ์ปดํฌ๋„ŒํŠธ ์—…๋ฐ์ดํŠธ ์™„๋ฃŒ:", { componentId, @@ -551,25 +566,25 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD // ํฌ๊ธฐ ๋ณ€๊ฒฝ ์‹œ ๊ฒฉ์ž ์Šค๋ƒ… ์ ์šฉ (๊ทธ๋ฃน ์ปดํฌ๋„ŒํŠธ ์ œ์™ธ) if ( (path === "size.width" || path === "size.height") && - layout.gridSettings?.snapToGrid && + prevLayout.gridSettings?.snapToGrid && gridInfo && newComp.type !== "group" ) { // ํ˜„์žฌ ํ•ด์ƒ๋„์— ๋งž๋Š” ๊ฒฉ์ž ์ •๋ณด๋กœ ์Šค๋ƒ… ์ ์šฉ const currentGridInfo = calculateGridInfo(screenResolution.width, screenResolution.height, { - columns: layout.gridSettings.columns, - gap: layout.gridSettings.gap, - padding: layout.gridSettings.padding, - snapToGrid: layout.gridSettings.snapToGrid || false, + columns: prevLayout.gridSettings.columns, + gap: prevLayout.gridSettings.gap, + padding: prevLayout.gridSettings.padding, + snapToGrid: prevLayout.gridSettings.snapToGrid || false, }); - const snappedSize = snapSizeToGrid(newComp.size, currentGridInfo, layout.gridSettings as GridUtilSettings); + const snappedSize = snapSizeToGrid(newComp.size, currentGridInfo, prevLayout.gridSettings as GridUtilSettings); newComp.size = snappedSize; // ํฌ๊ธฐ ๋ณ€๊ฒฝ ์‹œ gridColumns๋„ ์ž๋™ ์กฐ์ • const adjustedColumns = adjustGridColumnsFromSize( newComp, currentGridInfo, - layout.gridSettings as GridUtilSettings, + prevLayout.gridSettings as GridUtilSettings, ); if (newComp.gridColumns !== adjustedColumns) { newComp.gridColumns = adjustedColumns; @@ -582,19 +597,19 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD } // gridColumns ๋ณ€๊ฒฝ ์‹œ ํฌ๊ธฐ๋ฅผ ๊ฒฉ์ž์— ๋งž๊ฒŒ ์ž๋™ ์กฐ์ • - if (path === "gridColumns" && layout.gridSettings?.snapToGrid && newComp.type !== "group") { + if (path === "gridColumns" && prevLayout.gridSettings?.snapToGrid && newComp.type !== "group") { const currentGridInfo = calculateGridInfo(screenResolution.width, screenResolution.height, { - columns: layout.gridSettings.columns, - gap: layout.gridSettings.gap, - padding: layout.gridSettings.padding, - snapToGrid: layout.gridSettings.snapToGrid || false, + columns: prevLayout.gridSettings.columns, + gap: prevLayout.gridSettings.gap, + padding: prevLayout.gridSettings.padding, + snapToGrid: prevLayout.gridSettings.snapToGrid || false, }); // gridColumns์— ๋งž๋Š” ์ •ํ™•ํ•œ ๋„ˆ๋น„ ๊ณ„์‚ฐ const newWidth = calculateWidthFromColumns( newComp.gridColumns, currentGridInfo, - layout.gridSettings as GridUtilSettings, + prevLayout.gridSettings as GridUtilSettings, ); newComp.size = { ...newComp.size, @@ -699,52 +714,71 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD } } - return newComp; - }); + return newComp; + }); - const newLayout = { ...layout, components: updatedComponents }; - setLayout(newLayout); - saveToHistory(newLayout); + // ๐Ÿ”ฅ ์ƒˆ๋กœ์šด layout ์ƒ์„ฑ + const newLayout = { ...prevLayout, components: updatedComponents }; + + console.log("๐Ÿ”„ setLayout ์‹คํ–‰:", { + componentId, + path, + value, + ์—…๋ฐ์ดํŠธ๋œ์ปดํฌ๋„ŒํŠธ: updatedComponents.find((c) => c.id === componentId), + }); + + saveToHistory(newLayout); + + // selectedComponent๊ฐ€ ์—…๋ฐ์ดํŠธ๋œ ์ปดํฌ๋„ŒํŠธ์™€ ๊ฐ™๋‹ค๋ฉด selectedComponent๋„ ์—…๋ฐ์ดํŠธ + setSelectedComponent((prevSelected) => { + if (prevSelected && prevSelected.id === componentId) { + const updatedSelectedComponent = updatedComponents.find((c) => c.id === componentId); + if (updatedSelectedComponent) { + // ๐Ÿ”ง ์™„์ „ํžˆ ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ React๊ฐ€ ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•˜๋„๋ก ํ•จ + const newSelectedComponent = JSON.parse(JSON.stringify(updatedSelectedComponent)); + + console.log("๐Ÿ”„ selectedComponent ๋™๊ธฐํ™”:", { + componentId, + path, + oldAction: (prevSelected as any).componentConfig?.action, + newAction: (newSelectedComponent as any).componentConfig?.action, + oldColumnsCount: + prevSelected.type === "datatable" ? (prevSelected as any).columns?.length : "N/A", + newColumnsCount: + newSelectedComponent.type === "datatable" ? (newSelectedComponent as any).columns?.length : "N/A", + oldFiltersCount: + prevSelected.type === "datatable" ? (prevSelected as any).filters?.length : "N/A", + newFiltersCount: + newSelectedComponent.type === "datatable" ? (newSelectedComponent as any).filters?.length : "N/A", + timestamp: new Date().toISOString(), + }); + return newSelectedComponent; + } + } + return prevSelected; + }); - // selectedComponent๊ฐ€ ์—…๋ฐ์ดํŠธ๋œ ์ปดํฌ๋„ŒํŠธ์™€ ๊ฐ™๋‹ค๋ฉด selectedComponent๋„ ์—…๋ฐ์ดํŠธ - if (selectedComponent && selectedComponent.id === componentId) { - const updatedSelectedComponent = updatedComponents.find((c) => c.id === componentId); - if (updatedSelectedComponent) { - console.log("๐Ÿ”„ selectedComponent ๋™๊ธฐํ™”:", { + // webTypeConfig ์—…๋ฐ์ดํŠธ ํ›„ ๋ ˆ์ด์•„์›ƒ ์ƒํƒœ ํ™•์ธ + if (path === "webTypeConfig") { + const updatedComponent = newLayout.components.find((c) => c.id === componentId); + console.log("๐Ÿ”„ ๋ ˆ์ด์•„์›ƒ ์—…๋ฐ์ดํŠธ ํ›„ ์ปดํฌ๋„ŒํŠธ ์ƒํƒœ:", { componentId, - path, - oldColumnsCount: - selectedComponent.type === "datatable" ? (selectedComponent as any).columns?.length : "N/A", - newColumnsCount: - updatedSelectedComponent.type === "datatable" ? (updatedSelectedComponent as any).columns?.length : "N/A", - oldFiltersCount: - selectedComponent.type === "datatable" ? (selectedComponent as any).filters?.length : "N/A", - newFiltersCount: - updatedSelectedComponent.type === "datatable" ? (updatedSelectedComponent as any).filters?.length : "N/A", + updatedComponent: updatedComponent + ? { + id: updatedComponent.id, + type: updatedComponent.type, + webTypeConfig: updatedComponent.type === "widget" ? (updatedComponent as any).webTypeConfig : null, + } + : null, + layoutComponentsCount: newLayout.components.length, timestamp: new Date().toISOString(), }); - setSelectedComponent(updatedSelectedComponent); } - } - // webTypeConfig ์—…๋ฐ์ดํŠธ ํ›„ ๋ ˆ์ด์•„์›ƒ ์ƒํƒœ ํ™•์ธ - if (path === "webTypeConfig") { - const updatedComponent = newLayout.components.find((c) => c.id === componentId); - console.log("๐Ÿ”„ ๋ ˆ์ด์•„์›ƒ ์—…๋ฐ์ดํŠธ ํ›„ ์ปดํฌ๋„ŒํŠธ ์ƒํƒœ:", { - componentId, - updatedComponent: updatedComponent - ? { - id: updatedComponent.id, - type: updatedComponent.type, - webTypeConfig: updatedComponent.type === "widget" ? (updatedComponent as any).webTypeConfig : null, - } - : null, - layoutComponentsCount: newLayout.components.length, - timestamp: new Date().toISOString(), - }); - } + return newLayout; + }); }, - [layout, gridInfo, saveToHistory], + [gridInfo, saveToHistory], // ๐Ÿ”ง layout, selectedComponent ์ œ๊ฑฐ! ); // ์ปดํฌ๋„ŒํŠธ ์‹œ์Šคํ…œ ์ดˆ๊ธฐํ™” @@ -1294,11 +1328,22 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD components: updatedComponents, screenResolution: screenResolution, }; + // ๐Ÿ” ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ๋“ค์˜ action.type ํ™•์ธ + const buttonComponents = layoutWithResolution.components.filter( + (c: any) => c.type === "button" || c.type === "button-primary" || c.type === "button-secondary" + ); 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, + text: c.componentConfig?.text, + actionType: c.componentConfig?.action?.type, + fullAction: c.componentConfig?.action, + })), }); await screenApi.saveLayout(selectedScreen.screenId, layoutWithResolution); @@ -2127,7 +2172,8 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD // ์ƒˆ ์ปดํฌ๋„ŒํŠธ ์„ ํƒ setSelectedComponent(newComponent); - openPanel("properties"); + // ๐Ÿ”ง ํ…Œ์ด๋ธ” ํŒจ๋„ ์œ ์ง€๋ฅผ ์œ„ํ•ด ์ž๋™ ์†์„ฑ ํŒจ๋„ ์—ด๊ธฐ ๋น„ํ™œ์„ฑํ™” + // openPanel("properties"); toast.success(`${component.name} ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`); }, @@ -2610,8 +2656,8 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD saveToHistory(newLayout); setSelectedComponent(newComponent); - // ์†์„ฑ ํŒจ๋„ ์ž๋™ ์—ด๊ธฐ - openPanel("properties"); + // ๐Ÿ”ง ํ…Œ์ด๋ธ” ํŒจ๋„ ์œ ์ง€๋ฅผ ์œ„ํ•ด ์ž๋™ ์†์„ฑ ํŒจ๋„ ์—ด๊ธฐ ๋น„ํ™œ์„ฑํ™” + // openPanel("properties"); } catch (error) { // console.error("๋“œ๋กญ ์ฒ˜๋ฆฌ ์‹คํŒจ:", error); } @@ -2674,47 +2720,66 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD return; } + // ๐Ÿ”ง layout.components์—์„œ ์ตœ์‹  ๋ฒ„์ „์˜ ์ปดํฌ๋„ŒํŠธ ์ฐพ๊ธฐ + const latestComponent = layout.components.find((c) => c.id === component.id); + if (!latestComponent) { + console.warn("โš ๏ธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค:", component.id); + return; + } + + console.log("๐Ÿ” ์ปดํฌ๋„ŒํŠธ ํด๋ฆญ ์‹œ ์ตœ์‹  ๋ฒ„์ „ ํ™•์ธ:", { + componentId: component.id, + ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ๋ฐ›์€๋ฒ„์ „: { + actionType: (component as any).componentConfig?.action?.type, + fullAction: (component as any).componentConfig?.action, + }, + layout์—์„œ์ฐพ์€์ตœ์‹ ๋ฒ„์ „: { + actionType: (latestComponent as any).componentConfig?.action?.type, + fullAction: (latestComponent as any).componentConfig?.action, + }, + }); + const isShiftPressed = event?.shiftKey || false; const isCtrlPressed = event?.ctrlKey || event?.metaKey || false; - const isGroupContainer = component.type === "group"; + const isGroupContainer = latestComponent.type === "group"; if (isShiftPressed || isCtrlPressed || groupState.isGrouping) { // ๋‹ค์ค‘ ์„ ํƒ ๋ชจ๋“œ if (isGroupContainer) { // ๊ทธ๋ฃน ์ปจํ…Œ์ด๋„ˆ๋Š” ๋‹จ์ผ ์„ ํƒ์œผ๋กœ ์ฒ˜๋ฆฌ - handleComponentSelect(component); + handleComponentSelect(latestComponent); // ๐Ÿ”ง ์ตœ์‹  ๋ฒ„์ „ ์‚ฌ์šฉ setGroupState((prev) => ({ ...prev, - selectedComponents: [component.id], + selectedComponents: [latestComponent.id], isGrouping: false, })); return; } - const isSelected = groupState.selectedComponents.includes(component.id); + const isSelected = groupState.selectedComponents.includes(latestComponent.id); setGroupState((prev) => ({ ...prev, selectedComponents: isSelected - ? prev.selectedComponents.filter((id) => id !== component.id) - : [...prev.selectedComponents, component.id], + ? prev.selectedComponents.filter((id) => id !== latestComponent.id) + : [...prev.selectedComponents, latestComponent.id], })); // ๋งˆ์ง€๋ง‰ ์„ ํƒ๋œ ์ปดํฌ๋„ŒํŠธ๋ฅผ selectedComponent๋กœ ์„ค์ • if (!isSelected) { - // console.log("๐ŸŽฏ ์ปดํฌ๋„ŒํŠธ ์„ ํƒ (๋‹ค์ค‘ ๋ชจ๋“œ):", component.id); - handleComponentSelect(component); + // console.log("๐ŸŽฏ ์ปดํฌ๋„ŒํŠธ ์„ ํƒ (๋‹ค์ค‘ ๋ชจ๋“œ):", latestComponent.id); + handleComponentSelect(latestComponent); // ๐Ÿ”ง ์ตœ์‹  ๋ฒ„์ „ ์‚ฌ์šฉ } } else { // ๋‹จ์ผ ์„ ํƒ ๋ชจ๋“œ - // console.log("๐ŸŽฏ ์ปดํฌ๋„ŒํŠธ ์„ ํƒ (๋‹จ์ผ ๋ชจ๋“œ):", component.id); - handleComponentSelect(component); + // console.log("๐ŸŽฏ ์ปดํฌ๋„ŒํŠธ ์„ ํƒ (๋‹จ์ผ ๋ชจ๋“œ):", latestComponent.id); + handleComponentSelect(latestComponent); // ๐Ÿ”ง ์ตœ์‹  ๋ฒ„์ „ ์‚ฌ์šฉ setGroupState((prev) => ({ ...prev, - selectedComponents: [component.id], + selectedComponents: [latestComponent.id], })); } }, - [handleComponentSelect, groupState.isGrouping, groupState.selectedComponents, dragState.justFinishedDrag], + [handleComponentSelect, groupState.isGrouping, groupState.selectedComponents, dragState.justFinishedDrag, layout.components], ); // ์ปดํฌ๋„ŒํŠธ ๋“œ๋ž˜๊ทธ ์‹œ์ž‘ diff --git a/frontend/components/screen/config-panels/ButtonConfigPanel-fixed.tsx b/frontend/components/screen/config-panels/ButtonConfigPanel-fixed.tsx new file mode 100644 index 00000000..a70a0633 --- /dev/null +++ b/frontend/components/screen/config-panels/ButtonConfigPanel-fixed.tsx @@ -0,0 +1,625 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { Button } from "@/components/ui/button"; +import { Check, ChevronsUpDown, Search } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { ComponentData } from "@/types/screen"; +import { apiClient } from "@/lib/api/client"; +import { ButtonDataflowConfigPanel } from "./ButtonDataflowConfigPanel"; +import { ImprovedButtonControlConfigPanel } from "./ImprovedButtonControlConfigPanel"; + +interface ButtonConfigPanelProps { + component: ComponentData; + onUpdateProperty: (path: string, value: any) => void; +} + +interface ScreenOption { + id: number; + name: string; + description?: string; +} + +export const ButtonConfigPanel: React.FC = ({ component, onUpdateProperty }) => { + // ๐Ÿ”ง ํ•ญ์ƒ ์ตœ์‹  component์—์„œ ์ง์ ‘ ์ฐธ์กฐ + const config = component.componentConfig || {}; + const currentAction = component.componentConfig?.action || {}; // ๐Ÿ”ง ์ตœ์‹  action ์ฐธ์กฐ + + // ๋กœ์ปฌ ์ƒํƒœ ๊ด€๋ฆฌ (์‹ค์‹œ๊ฐ„ ์ž…๋ ฅ ๋ฐ˜์˜) + const [localInputs, setLocalInputs] = useState({ + text: config.text !== undefined ? config.text : "๋ฒ„ํŠผ", // ๐Ÿ”ง ๋นˆ ๋ฌธ์ž์—ด ํ—ˆ์šฉ + modalTitle: config.action?.modalTitle || "", + editModalTitle: config.action?.editModalTitle || "", + editModalDescription: config.action?.editModalDescription || "", + targetUrl: config.action?.targetUrl || "", + }); + + const [localSelects, setLocalSelects] = useState({ + variant: config.variant || "default", + size: config.size || "md", // ๐Ÿ”ง ๊ธฐ๋ณธ๊ฐ’์„ "md"๋กœ ๋ณ€๊ฒฝ + actionType: config.action?.type, // ๐Ÿ”ง ๊ธฐ๋ณธ๊ฐ’ ์™„์ „ ์ œ๊ฑฐ (undefined) + modalSize: config.action?.modalSize || "md", + editMode: config.action?.editMode || "modal", + }); + + const [screens, setScreens] = useState([]); + const [screensLoading, setScreensLoading] = useState(false); + const [modalScreenOpen, setModalScreenOpen] = useState(false); + const [navScreenOpen, setNavScreenOpen] = useState(false); + const [modalSearchTerm, setModalSearchTerm] = useState(""); + const [navSearchTerm, setNavSearchTerm] = useState(""); + + // ์ปดํฌ๋„ŒํŠธ ๋ณ€๊ฒฝ ์‹œ ๋กœ์ปฌ ์ƒํƒœ ๋™๊ธฐํ™” + useEffect(() => { + console.log("๐Ÿ”„ ButtonConfigPanel useEffect ์‹คํ–‰:", { + componentId: component.id, + "config.action?.type": config.action?.type, + "localSelects.actionType (before)": localSelects.actionType, + fullAction: config.action, + "component.componentConfig.action": component.componentConfig?.action, + }); + + setLocalInputs({ + text: config.text !== undefined ? config.text : "๋ฒ„ํŠผ", // ๐Ÿ”ง ๋นˆ ๋ฌธ์ž์—ด ํ—ˆ์šฉ + modalTitle: config.action?.modalTitle || "", + editModalTitle: config.action?.editModalTitle || "", + editModalDescription: config.action?.editModalDescription || "", + targetUrl: config.action?.targetUrl || "", + }); + + setLocalSelects((prev) => { + const newSelects = { + variant: config.variant || "default", + size: config.size || "md", // ๐Ÿ”ง ๊ธฐ๋ณธ๊ฐ’์„ "md"๋กœ ๋ณ€๊ฒฝ + actionType: config.action?.type, // ๐Ÿ”ง ๊ธฐ๋ณธ๊ฐ’ ์™„์ „ ์ œ๊ฑฐ (undefined) + modalSize: config.action?.modalSize || "md", + editMode: config.action?.editMode || "modal", + }; + + console.log("๐Ÿ“ setLocalSelects ํ˜ธ์ถœ:", { + "prev.actionType": prev.actionType, + "new.actionType": newSelects.actionType, + "config.action?.type": config.action?.type, + }); + + return newSelects; + }); + }, [ + component.id, // ๐Ÿ”ง ์ปดํฌ๋„ŒํŠธ ID (๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋กœ ์ „ํ™˜ ์‹œ) + component.componentConfig?.action?.type, // ๐Ÿ”ง ์•ก์…˜ ํƒ€์ž… (์•ก์…˜ ๋ณ€๊ฒฝ ์‹œ ์ฆ‰์‹œ ๋ฐ˜์˜) + component.componentConfig?.text, // ๐Ÿ”ง ๋ฒ„ํŠผ ํ…์ŠคํŠธ + component.componentConfig?.variant, // ๐Ÿ”ง ๋ฒ„ํŠผ ์Šคํƒ€์ผ + component.componentConfig?.size, // ๐Ÿ”ง ๋ฒ„ํŠผ ํฌ๊ธฐ + ]); + + // ํ™”๋ฉด ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ + useEffect(() => { + const fetchScreens = async () => { + try { + setScreensLoading(true); + const response = await apiClient.get("/screen-management/screens"); + + if (response.data.success && Array.isArray(response.data.data)) { + const screenList = response.data.data.map((screen: any) => ({ + id: screen.screenId, + name: screen.screenName, + description: screen.description, + })); + setScreens(screenList); + } + } catch (error) { + // console.error("โŒ ํ™”๋ฉด ๋ชฉ๋ก ๋กœ๋”ฉ ์‹คํŒจ:", error); + } finally { + setScreensLoading(false); + } + }; + + fetchScreens(); + }, []); + + // ๊ฒ€์ƒ‰ ํ•„ํ„ฐ๋ง ํ•จ์ˆ˜ + const filterScreens = (searchTerm: string) => { + if (!searchTerm.trim()) return screens; + return screens.filter( + (screen) => + screen.name.toLowerCase().includes(searchTerm.toLowerCase()) || + (screen.description && screen.description.toLowerCase().includes(searchTerm.toLowerCase())), + ); + }; + + console.log("๐Ÿ”ง config-panels/ButtonConfigPanel ๋ Œ๋”๋ง:", { + component, + config, + action: config.action, + actionType: config.action?.type, + screensCount: screens.length, + }); + + return ( +
+
+ + { + const newValue = e.target.value; + setLocalInputs((prev) => ({ ...prev, text: newValue })); + onUpdateProperty("componentConfig.text", newValue); + }} + placeholder="๋ฒ„ํŠผ ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”" + /> +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + {/* ๋ชจ๋‹ฌ ์—ด๊ธฐ ์•ก์…˜ ์„ค์ • */} + {localSelects.actionType === "modal" && ( +
+

๋ชจ๋‹ฌ ์„ค์ •

+ +
+ + { + const newValue = e.target.value; + setLocalInputs((prev) => ({ ...prev, modalTitle: newValue })); + onUpdateProperty("componentConfig.action.modalTitle", newValue); + }} + /> +
+ +
+ + +
+ +
+ + + + + + +
+
+ + setModalSearchTerm(e.target.value)} + className="border-0 p-0 focus-visible:ring-0" + /> +
+
+ {(() => { + const filteredScreens = filterScreens(modalSearchTerm); + if (screensLoading) { + return
ํ™”๋ฉด ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...
; + } + if (filteredScreens.length === 0) { + return
๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
; + } + return filteredScreens.map((screen, index) => ( +
{ + onUpdateProperty("componentConfig.action.targetScreenId", screen.id); + setModalScreenOpen(false); + setModalSearchTerm(""); + }} + > + +
+ {screen.name} + {screen.description && {screen.description}} +
+
+ )); + })()} +
+
+
+
+
+
+ )} + + {/* ์ˆ˜์ • ์•ก์…˜ ์„ค์ • */} + {localSelects.actionType === "edit" && ( +
+

์ˆ˜์ • ์„ค์ •

+ +
+ + + + + + +
+
+ + setModalSearchTerm(e.target.value)} + className="border-0 p-0 focus-visible:ring-0" + /> +
+
+ {(() => { + const filteredScreens = filterScreens(modalSearchTerm); + if (screensLoading) { + return
ํ™”๋ฉด ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...
; + } + if (filteredScreens.length === 0) { + return
๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
; + } + return filteredScreens.map((screen, index) => ( +
{ + onUpdateProperty("componentConfig.action.targetScreenId", screen.id); + setModalScreenOpen(false); + setModalSearchTerm(""); + }} + > + +
+ {screen.name} + {screen.description && {screen.description}} +
+
+ )); + })()} +
+
+
+
+

+ ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ด ํผ ํ™”๋ฉด์— ์ž๋™์œผ๋กœ ๋กœ๋“œ๋˜์–ด ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค +

+
+ +
+ + +
+ + {localSelects.editMode === "modal" && ( + <> +
+ + { + const newValue = e.target.value; + setLocalInputs((prev) => ({ ...prev, editModalTitle: newValue })); + onUpdateProperty("componentConfig.action.editModalTitle", newValue); + onUpdateProperty("webTypeConfig.editModalTitle", newValue); + }} + /> +

๋น„์›Œ๋‘๋ฉด ๊ธฐ๋ณธ ์ œ๋ชฉ์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค

+
+ +
+ + { + const newValue = e.target.value; + setLocalInputs((prev) => ({ ...prev, editModalDescription: newValue })); + onUpdateProperty("componentConfig.action.editModalDescription", newValue); + onUpdateProperty("webTypeConfig.editModalDescription", newValue); + }} + /> +

๋น„์›Œ๋‘๋ฉด ์„ค๋ช…์ด ํ‘œ์‹œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค

+
+ +
+ + +
+ + )} +
+ )} + + {/* ํŽ˜์ด์ง€ ์ด๋™ ์•ก์…˜ ์„ค์ • */} + {localSelects.actionType === "navigate" && ( +
+

ํŽ˜์ด์ง€ ์ด๋™ ์„ค์ •

+ +
+ + + + + + +
+
+ + setNavSearchTerm(e.target.value)} + className="border-0 p-0 focus-visible:ring-0" + /> +
+
+ {(() => { + const filteredScreens = filterScreens(navSearchTerm); + if (screensLoading) { + return
ํ™”๋ฉด ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...
; + } + if (filteredScreens.length === 0) { + return
๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
; + } + return filteredScreens.map((screen, index) => ( +
{ + onUpdateProperty("componentConfig.action.targetScreenId", screen.id); + setNavScreenOpen(false); + setNavSearchTerm(""); + }} + > + +
+ {screen.name} + {screen.description && {screen.description}} +
+
+ )); + })()} +
+
+
+
+

+ ์„ ํƒํ•œ ํ™”๋ฉด์œผ๋กœ /screens/{"{"}ํ™”๋ฉดID{"}"} ํ˜•ํƒœ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค +

+
+ +
+ + { + const newValue = e.target.value; + setLocalInputs((prev) => ({ ...prev, targetUrl: newValue })); + onUpdateProperty("componentConfig.action.targetUrl", newValue); + }} + /> +

URL์„ ์ž…๋ ฅํ•˜๋ฉด ํ™”๋ฉด ์„ ํƒ๋ณด๋‹ค ์šฐ์„  ์ ์šฉ๋ฉ๋‹ˆ๋‹ค

+
+
+ )} + + {/* ๐Ÿ”ฅ NEW: ์ œ์–ด๊ด€๋ฆฌ ๊ธฐ๋Šฅ ์„น์…˜ */} +
+
+

๐Ÿ”ง ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ

+

๋ฒ„ํŠผ ์•ก์…˜๊ณผ ํ•จ๊ป˜ ์‹คํ–‰๋  ์ถ”๊ฐ€ ๊ธฐ๋Šฅ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค

+
+ + +
+
+ ); +}; + diff --git a/frontend/components/screen/config-panels/ButtonConfigPanel.tsx b/frontend/components/screen/config-panels/ButtonConfigPanel.tsx index 2cb1e3ce..dac32163 100644 --- a/frontend/components/screen/config-panels/ButtonConfigPanel.tsx +++ b/frontend/components/screen/config-panels/ButtonConfigPanel.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useMemo } from "react"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; @@ -26,25 +26,24 @@ interface ScreenOption { } export const ButtonConfigPanel: React.FC = ({ component, onUpdateProperty }) => { + console.log("๐ŸŽจ ButtonConfigPanel ๋ Œ๋”๋ง:", { + componentId: component.id, + "component.componentConfig?.action?.type": component.componentConfig?.action?.type, + }); + + // ๐Ÿ”ง component์—์„œ ์ง์ ‘ ์ฝ๊ธฐ (useMemo ์ œ๊ฑฐ) const config = component.componentConfig || {}; + const currentAction = component.componentConfig?.action || {}; // ๋กœ์ปฌ ์ƒํƒœ ๊ด€๋ฆฌ (์‹ค์‹œ๊ฐ„ ์ž…๋ ฅ ๋ฐ˜์˜) const [localInputs, setLocalInputs] = useState({ - text: config.text || "๋ฒ„ํŠผ", + text: config.text !== undefined ? config.text : "๋ฒ„ํŠผ", modalTitle: config.action?.modalTitle || "", editModalTitle: config.action?.editModalTitle || "", editModalDescription: config.action?.editModalDescription || "", targetUrl: config.action?.targetUrl || "", }); - const [localSelects, setLocalSelects] = useState({ - variant: config.variant || "default", - size: config.size || "default", - actionType: config.action?.type || "save", - modalSize: config.action?.modalSize || "md", - editMode: config.action?.editMode || "modal", - }); - const [screens, setScreens] = useState([]); const [screensLoading, setScreensLoading] = useState(false); const [modalScreenOpen, setModalScreenOpen] = useState(false); @@ -52,44 +51,27 @@ export const ButtonConfigPanel: React.FC = ({ component, const [modalSearchTerm, setModalSearchTerm] = useState(""); const [navSearchTerm, setNavSearchTerm] = useState(""); - // ์ปดํฌ๋„ŒํŠธ ๋ณ€๊ฒฝ ์‹œ ๋กœ์ปฌ ์ƒํƒœ ๋™๊ธฐํ™” + // ์ปดํฌ๋„ŒํŠธ prop ๋ณ€๊ฒฝ ์‹œ ๋กœ์ปฌ ์ƒํƒœ ๋™๊ธฐํ™” (Input๋งŒ) useEffect(() => { - setLocalInputs({ - text: config.text || "๋ฒ„ํŠผ", - modalTitle: config.action?.modalTitle || "", - editModalTitle: config.action?.editModalTitle || "", - editModalDescription: config.action?.editModalDescription || "", - targetUrl: config.action?.targetUrl || "", - }); + const latestConfig = component.componentConfig || {}; + const latestAction = latestConfig.action || {}; - setLocalSelects({ - variant: config.variant || "default", - size: config.size || "default", - actionType: config.action?.type || "save", - modalSize: config.action?.modalSize || "md", - editMode: config.action?.editMode || "modal", + setLocalInputs({ + text: latestConfig.text !== undefined ? latestConfig.text : "๋ฒ„ํŠผ", + modalTitle: latestAction.modalTitle || "", + editModalTitle: latestAction.editModalTitle || "", + editModalDescription: latestAction.editModalDescription || "", + targetUrl: latestAction.targetUrl || "", }); - }, [ - config.text, - config.variant, - config.size, - config.action?.type, - config.action?.modalTitle, - config.action?.modalSize, - config.action?.editMode, - config.action?.editModalTitle, - config.action?.editModalDescription, - config.action?.targetUrl, - ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [component.id]); // ํ™”๋ฉด ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ useEffect(() => { const fetchScreens = async () => { try { setScreensLoading(true); - // console.log("๐Ÿ” ํ™”๋ฉด ๋ชฉ๋ก API ํ˜ธ์ถœ ์‹œ์ž‘"); const response = await apiClient.get("/screen-management/screens"); - // console.log("โœ… ํ™”๋ฉด ๋ชฉ๋ก API ์‘๋‹ต:", response.data); if (response.data.success && Array.isArray(response.data.data)) { const screenList = response.data.data.map((screen: any) => ({ @@ -98,7 +80,6 @@ export const ButtonConfigPanel: React.FC = ({ component, description: screen.description, })); setScreens(screenList); - // console.log("โœ… ํ™”๋ฉด ๋ชฉ๋ก ์„ค์ • ์™„๋ฃŒ:", screenList.length, "๊ฐœ"); } } catch (error) { // console.error("โŒ ํ™”๋ฉด ๋ชฉ๋ก ๋กœ๋”ฉ ์‹คํŒจ:", error); @@ -120,13 +101,13 @@ export const ButtonConfigPanel: React.FC = ({ component, ); }; - console.log("๐Ÿ”ง config-panels/ButtonConfigPanel ๋ Œ๋”๋ง:", { - component, - config, - action: config.action, - actionType: config.action?.type, - screensCount: screens.length, - }); + // console.log("๐Ÿ”ง config-panels/ButtonConfigPanel ๋ Œ๋”๋ง:", { + // component, + // config, + // action: config.action, + // actionType: config.action?.type, + // screensCount: screens.length, + // }); return (
@@ -147,9 +128,8 @@ export const ButtonConfigPanel: React.FC = ({ component,
{ - setLocalSelects((prev) => ({ ...prev, size: value })); onUpdateProperty("componentConfig.size", value); }} > - + - ์ž‘์Œ (Small) - ๊ธฐ๋ณธ (Default) - ํผ (Large) + ์ž‘์Œ (Small) + ๊ธฐ๋ณธ (Default) + ํผ (Large)
@@ -191,28 +170,23 @@ export const ButtonConfigPanel: React.FC = ({ component,
{ - setLocalSelects((prev) => ({ ...prev, modalSize: value })); - onUpdateProperty("componentConfig.action", { - ...config.action, - modalSize: value, - }); + onUpdateProperty("componentConfig.action.modalSize", value); }} > @@ -301,7 +268,6 @@ export const ButtonConfigPanel: React.FC = ({ component,
- {/* ๊ฒ€์ƒ‰ ์ž…๋ ฅ */}
= ({ component, className="border-0 p-0 focus-visible:ring-0" />
- {/* ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ */}
{(() => { const filteredScreens = filterScreens(modalSearchTerm); @@ -326,10 +291,7 @@ export const ButtonConfigPanel: React.FC = ({ component, key={`modal-screen-${screen.id}-${index}`} className="flex cursor-pointer items-center px-3 py-2 hover:bg-gray-100" onClick={() => { - onUpdateProperty("componentConfig.action", { - ...config.action, - targetScreenId: screen.id, - }); + onUpdateProperty("componentConfig.action.targetScreenId", screen.id); setModalScreenOpen(false); setModalSearchTerm(""); }} @@ -356,7 +318,7 @@ export const ButtonConfigPanel: React.FC = ({ component, )} {/* ์ˆ˜์ • ์•ก์…˜ ์„ค์ • */} - {localSelects.actionType === "edit" && ( + {(component.componentConfig?.action?.type || "save") === "edit" && (

์ˆ˜์ • ์„ค์ •

@@ -380,7 +342,6 @@ export const ButtonConfigPanel: React.FC = ({ component,
- {/* ๊ฒ€์ƒ‰ ์ž…๋ ฅ */}
= ({ component, className="border-0 p-0 focus-visible:ring-0" />
- {/* ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ */}
{(() => { const filteredScreens = filterScreens(modalSearchTerm); @@ -405,10 +365,7 @@ export const ButtonConfigPanel: React.FC = ({ component, key={`edit-screen-${screen.id}-${index}`} className="flex cursor-pointer items-center px-3 py-2 hover:bg-gray-100" onClick={() => { - onUpdateProperty("componentConfig.action", { - ...config.action, - targetScreenId: screen.id, - }); + onUpdateProperty("componentConfig.action.targetScreenId", screen.id); setModalScreenOpen(false); setModalSearchTerm(""); }} @@ -438,13 +395,9 @@ export const ButtonConfigPanel: React.FC = ({ component,
- {localSelects.editMode === "modal" && ( + {(component.componentConfig?.action?.editMode || "modal") === "modal" && ( <>
@@ -469,11 +422,7 @@ export const ButtonConfigPanel: React.FC = ({ component, onChange={(e) => { const newValue = e.target.value; setLocalInputs((prev) => ({ ...prev, editModalTitle: newValue })); - onUpdateProperty("componentConfig.action", { - ...config.action, - editModalTitle: newValue, - }); - // webTypeConfig์—๋„ ์ €์žฅ + onUpdateProperty("componentConfig.action.editModalTitle", newValue); onUpdateProperty("webTypeConfig.editModalTitle", newValue); }} /> @@ -489,11 +438,7 @@ export const ButtonConfigPanel: React.FC = ({ component, onChange={(e) => { const newValue = e.target.value; setLocalInputs((prev) => ({ ...prev, editModalDescription: newValue })); - onUpdateProperty("componentConfig.action", { - ...config.action, - editModalDescription: newValue, - }); - // webTypeConfig์—๋„ ์ €์žฅ + onUpdateProperty("componentConfig.action.editModalDescription", newValue); onUpdateProperty("webTypeConfig.editModalDescription", newValue); }} /> @@ -503,13 +448,9 @@ export const ButtonConfigPanel: React.FC = ({ component,
= ({ component, className="border-0 p-0 focus-visible:ring-0" />
- {/* ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ */}
{(() => { const filteredScreens = filterScreens(navSearchTerm); @@ -579,10 +518,7 @@ export const ButtonConfigPanel: React.FC = ({ component, key={`navigate-screen-${screen.id}-${index}`} className="flex cursor-pointer items-center px-3 py-2 hover:bg-gray-100" onClick={() => { - onUpdateProperty("componentConfig.action", { - ...config.action, - targetScreenId: screen.id, - }); + onUpdateProperty("componentConfig.action.targetScreenId", screen.id); setNavScreenOpen(false); setNavSearchTerm(""); }} @@ -618,10 +554,7 @@ export const ButtonConfigPanel: React.FC = ({ component, onChange={(e) => { const newValue = e.target.value; setLocalInputs((prev) => ({ ...prev, targetUrl: newValue })); - onUpdateProperty("componentConfig.action", { - ...config.action, - targetUrl: newValue, - }); + onUpdateProperty("componentConfig.action.targetUrl", newValue); }} />

URL์„ ์ž…๋ ฅํ•˜๋ฉด ํ™”๋ฉด ์„ ํƒ๋ณด๋‹ค ์šฐ์„  ์ ์šฉ๋ฉ๋‹ˆ๋‹ค

@@ -641,3 +574,4 @@ export const ButtonConfigPanel: React.FC = ({ component,
); }; + diff --git a/frontend/components/screen/panels/DetailSettingsPanel.tsx b/frontend/components/screen/panels/DetailSettingsPanel.tsx index 84fdf251..9540c21b 100644 --- a/frontend/components/screen/panels/DetailSettingsPanel.tsx +++ b/frontend/components/screen/panels/DetailSettingsPanel.tsx @@ -822,7 +822,8 @@ export const DetailSettingsPanel: React.FC = ({ case "button": case "button-primary": case "button-secondary": - return ; + // ๐Ÿ”ง component.id๋งŒ key๋กœ ์‚ฌ์šฉ (unmount ๋ฐฉ์ง€) + return ; case "card": return ; diff --git a/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx b/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx index 6c325ac8..71172d4d 100644 --- a/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx +++ b/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx @@ -123,7 +123,8 @@ export const UnifiedPropertiesPanel: React.FC = ({ case "button": case "button-primary": case "button-secondary": - return ; + // ๐Ÿ”ง component.id๋งŒ key๋กœ ์‚ฌ์šฉ (unmount ๋ฐฉ์ง€) + return ; case "card": return ; diff --git a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx index 80d3a001..0b0255dc 100644 --- a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx +++ b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx @@ -491,7 +491,8 @@ export const ButtonPrimaryComponent: React.FC = ({ ? "linear-gradient(135deg, #e5e7eb 0%, #d1d5db 100%)" : `linear-gradient(135deg, ${buttonColor} 0%, ${buttonDarkColor} 100%)`, color: componentConfig.disabled ? "#9ca3af" : "white", - fontSize: "0.875rem", + // ๐Ÿ”ง ํฌ๊ธฐ ์„ค์ • ์ ์šฉ (sm/md/lg) + fontSize: componentConfig.size === "sm" ? "0.75rem" : componentConfig.size === "lg" ? "1rem" : "0.875rem", fontWeight: "600", cursor: componentConfig.disabled ? "not-allowed" : "pointer", outline: "none", @@ -499,10 +500,10 @@ export const ButtonPrimaryComponent: React.FC = ({ display: "flex", alignItems: "center", justifyContent: "center", - padding: "0 1rem", + // ๐Ÿ”ง ํฌ๊ธฐ์— ๋”ฐ๋ฅธ ํŒจ๋”ฉ ์กฐ์ • + padding: componentConfig.size === "sm" ? "0 0.75rem" : componentConfig.size === "lg" ? "0 1.25rem" : "0 1rem", margin: "0", lineHeight: "1.25", - minHeight: "2.25rem", boxShadow: componentConfig.disabled ? "0 1px 2px 0 rgba(0, 0, 0, 0.05)" : `0 1px 3px 0 ${buttonColor}40`, // isInteractive ๋ชจ๋“œ์—์„œ๋Š” ์‚ฌ์šฉ์ž ์Šคํƒ€์ผ ์šฐ์„  ์ ์šฉ ...(isInteractive && component.style ? component.style : {}), @@ -511,7 +512,8 @@ export const ButtonPrimaryComponent: React.FC = ({ onDragStart={onDragStart} onDragEnd={onDragEnd} > - {processedConfig.text || component.label || "๋ฒ„ํŠผ"} + {/* ๐Ÿ”ง ๋นˆ ๋ฌธ์ž์—ด๋„ ํ—ˆ์šฉ (undefined์ผ ๋•Œ๋งŒ ๊ธฐ๋ณธ๊ฐ’ ์ ์šฉ) */} + {processedConfig.text !== undefined ? processedConfig.text : component.label || "๋ฒ„ํŠผ"}