diff --git a/frontend/components/screen/ScreenDesigner.tsx b/frontend/components/screen/ScreenDesigner.tsx index 891b8a58..602ea890 100644 --- a/frontend/components/screen/ScreenDesigner.tsx +++ b/frontend/components/screen/ScreenDesigner.tsx @@ -4252,6 +4252,9 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD onBack={onBackToList} onSave={handleSave} isSaving={isSaving} + onResolutionChange={setScreenResolution} + gridSettings={layout.gridSettings} + onGridSettingsChange={updateGridSettings} /> {/* 메인 컨테이너 (좌측 툴바 + 패널들 + 캔버스) */}
@@ -4303,9 +4306,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD 0 ? tables[0] : undefined} @@ -4317,8 +4318,6 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD updateComponentProperty(selectedComponent.id, "style", style); } }} - currentResolution={screenResolution} - onResolutionChange={handleResolutionChange} allComponents={layout.components} // 🆕 플로우 위젯 감지용 menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달 /> diff --git a/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx b/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx index e2e9d837..eb3f5853 100644 --- a/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx +++ b/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx @@ -10,7 +10,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { Checkbox } from "@/components/ui/checkbox"; import { Textarea } from "@/components/ui/textarea"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; -import { ChevronDown, Settings, Info, Database, Trash2, Copy, Palette, Monitor } from "lucide-react"; +import { ChevronDown, Settings, Info, Database, Trash2, Copy, Palette } from "lucide-react"; import { ComponentData, WebType, @@ -59,26 +59,15 @@ import { AlertConfigPanel } from "../config-panels/AlertConfigPanel"; import { BadgeConfigPanel } from "../config-panels/BadgeConfigPanel"; import { DynamicComponentConfigPanel } from "@/lib/utils/getComponentConfigPanel"; import StyleEditor from "../StyleEditor"; -import ResolutionPanel from "./ResolutionPanel"; import { Slider } from "@/components/ui/slider"; -import { Grid3X3, Eye, EyeOff, Zap } from "lucide-react"; +import { Zap } from "lucide-react"; import { ConditionalConfigPanel } from "@/components/unified/ConditionalConfigPanel"; import { ConditionalConfig } from "@/types/unified-components"; interface UnifiedPropertiesPanelProps { selectedComponent?: ComponentData; tables: TableInfo[]; - gridSettings?: { - columns: number; - gap: number; - padding: number; - snapToGrid: boolean; - showGrid: boolean; - gridColor?: string; - gridOpacity?: number; - }; onUpdateProperty: (componentId: string, path: string, value: any) => void; - onGridSettingsChange?: (settings: any) => void; onDeleteComponent?: (componentId: string) => void; onCopyComponent?: (componentId: string) => void; currentTable?: TableInfo; @@ -86,9 +75,6 @@ interface UnifiedPropertiesPanelProps { dragState?: any; // 스타일 관련 onStyleChange?: (style: any) => void; - // 해상도 관련 - currentResolution?: { name: string; width: number; height: number }; - onResolutionChange?: (resolution: { name: string; width: number; height: number }) => void; // 🆕 플로우 위젯 감지용 allComponents?: ComponentData[]; // 🆕 메뉴 OBJID (코드/카테고리 스코프용) @@ -100,9 +86,7 @@ interface UnifiedPropertiesPanelProps { export const UnifiedPropertiesPanel: React.FC = ({ selectedComponent, tables, - gridSettings, onUpdateProperty, - onGridSettingsChange, onDeleteComponent, onCopyComponent, currentTable, @@ -111,8 +95,6 @@ export const UnifiedPropertiesPanel: React.FC = ({ dragState, onStyleChange, menuObjid, - currentResolution, - onResolutionChange, allComponents = [], // 🆕 기본값 빈 배열 }) => { const { webTypes } = useWebTypes({ active: "Y" }); @@ -165,106 +147,13 @@ export const UnifiedPropertiesPanel: React.FC = ({ } }, [selectedComponent?.size?.width, selectedComponent?.id]); - // 격자 설정 업데이트 함수 (early return 이전에 정의) - const updateGridSetting = (key: string, value: any) => { - if (onGridSettingsChange && gridSettings) { - onGridSettingsChange({ - ...gridSettings, - [key]: value, - }); - } - }; - - // 격자 설정 렌더링 (early return 이전에 정의) - const renderGridSettings = () => { - if (!gridSettings || !onGridSettingsChange) return null; - - // 최대 컬럼 수 계산 - const MIN_COLUMN_WIDTH = 30; - const maxColumns = currentResolution - ? Math.floor( - (currentResolution.width - gridSettings.padding * 2 + gridSettings.gap) / - (MIN_COLUMN_WIDTH + gridSettings.gap), - ) - : 24; - const safeMaxColumns = Math.max(1, Math.min(maxColumns, 100)); // 최대 100개로 제한 - - return ( -
-
- -

격자 설정

-
- -
- {/* 토글들 */} -
-
- {gridSettings.showGrid ? ( - - ) : ( - - )} - -
- updateGridSetting("showGrid", checked)} - /> -
- -
-
- - -
- updateGridSetting("snapToGrid", checked)} - /> -
- - {/* 10px 단위 스냅 안내 */} -
-

모든 컴포넌트는 10px 단위로 자동 배치됩니다.

-
-
-
- ); - }; - - // 컴포넌트가 선택되지 않았을 때도 해상도 설정과 격자 설정은 표시 + // 컴포넌트가 선택되지 않았을 때는 안내 메시지만 표시 if (!selectedComponent) { return (
- {/* 해상도 설정과 격자 설정 표시 */}
- {/* 해상도 설정 */} - {currentResolution && onResolutionChange && ( - <> -
-
- -

해상도 설정

-
- -
- - - )} - - {/* 격자 설정 */} - {renderGridSettings()} - {/* 안내 메시지 */} -

컴포넌트를 선택하여

@@ -1518,24 +1407,6 @@ export const UnifiedPropertiesPanel: React.FC = ({ {/* 통합 컨텐츠 (탭 제거) */}
- {/* 해상도 설정 - 항상 맨 위에 표시 */} - {currentResolution && onResolutionChange && ( - <> -
-
- -

해상도 설정

-
- -
- - - )} - - {/* 격자 설정 - 해상도 설정 아래 표시 */} - {renderGridSettings()} - {gridSettings && onGridSettingsChange && } - {/* 기본 설정 */} {renderBasicTab()} @@ -1578,10 +1449,10 @@ export const UnifiedPropertiesPanel: React.FC = ({ const widgetType = (c as any).widgetType || (c as any).componentType || "text"; const config = (c as any).componentConfig || (c as any).webTypeConfig || {}; const detailSettings = (c as any).detailSettings || {}; - + // 정적 옵션 추출 (select, dropdown, radio, entity 등) let options: Array<{ value: string; label: string }> | undefined; - + // Unified 컴포넌트의 경우 if (config.options && Array.isArray(config.options)) { options = config.options; @@ -1590,27 +1461,27 @@ export const UnifiedPropertiesPanel: React.FC = ({ else if ((c as any).options && Array.isArray((c as any).options)) { options = (c as any).options; } - + // 엔티티 정보 추출 (config > detailSettings > 직접 속성 순으로 우선순위) - const entityTable = - config.entityTable || - detailSettings.referenceTable || + const entityTable = + config.entityTable || + detailSettings.referenceTable || (c as any).entityTable || (c as any).referenceTable; - const entityValueColumn = - config.entityValueColumn || + const entityValueColumn = + config.entityValueColumn || detailSettings.referenceColumn || (c as any).entityValueColumn || (c as any).referenceColumn; - const entityLabelColumn = - config.entityLabelColumn || + const entityLabelColumn = + config.entityLabelColumn || detailSettings.displayColumn || (c as any).entityLabelColumn || (c as any).displayColumn; - + // 공통코드 정보 추출 const codeGroup = config.codeGroup || detailSettings.codeGroup || (c as any).codeGroup; - + return { id: (c as any).columnName || c.id, label: (c as any).label || config.label || c.id, diff --git a/frontend/components/screen/toolbar/SlimToolbar.tsx b/frontend/components/screen/toolbar/SlimToolbar.tsx index 2c6c7222..1a573847 100644 --- a/frontend/components/screen/toolbar/SlimToolbar.tsx +++ b/frontend/components/screen/toolbar/SlimToolbar.tsx @@ -1,9 +1,48 @@ "use client"; -import React from "react"; +import React, { useState } from "react"; import { Button } from "@/components/ui/button"; -import { Database, ArrowLeft, Save, Monitor, Smartphone } from "lucide-react"; -import { ScreenResolution } from "@/types/screen"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Checkbox } from "@/components/ui/checkbox"; +import { + Database, + ArrowLeft, + Save, + Monitor, + Smartphone, + Tablet, + ChevronDown, + Settings, + Grid3X3, + Eye, + EyeOff, + Zap, +} from "lucide-react"; +import { ScreenResolution, SCREEN_RESOLUTIONS } from "@/types/screen"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; + +interface GridSettings { + columns: number; + gap: number; + padding: number; + snapToGrid: boolean; + showGrid: boolean; + gridColor?: string; + gridOpacity?: number; +} interface SlimToolbarProps { screenName?: string; @@ -13,6 +52,9 @@ interface SlimToolbarProps { onSave: () => void; isSaving?: boolean; onPreview?: () => void; + onResolutionChange?: (resolution: ScreenResolution) => void; + gridSettings?: GridSettings; + onGridSettingsChange?: (settings: GridSettings) => void; } export const SlimToolbar: React.FC = ({ @@ -23,7 +65,52 @@ export const SlimToolbar: React.FC = ({ onSave, isSaving = false, onPreview, + onResolutionChange, + gridSettings, + onGridSettingsChange, }) => { + // 사용자 정의 해상도 상태 + const [customWidth, setCustomWidth] = useState(""); + const [customHeight, setCustomHeight] = useState(""); + const [showCustomInput, setShowCustomInput] = useState(false); + + const getCategoryIcon = (category: string) => { + switch (category) { + case "desktop": + return ; + case "tablet": + return ; + case "mobile": + return ; + default: + return ; + } + }; + + const handleCustomResolution = () => { + const width = parseInt(customWidth); + const height = parseInt(customHeight); + if (width > 0 && height > 0 && onResolutionChange) { + const customResolution: ScreenResolution = { + width, + height, + name: `사용자 정의 (${width}×${height})`, + category: "custom", + }; + onResolutionChange(customResolution); + setShowCustomInput(false); + } + }; + + const updateGridSetting = (key: keyof GridSettings, value: boolean) => { + if (onGridSettingsChange && gridSettings) { + onGridSettingsChange({ + ...gridSettings, + [key]: value, + }); + } + }; + return (
{/* 좌측: 네비게이션 및 화면 정보 */} @@ -47,16 +134,149 @@ export const SlimToolbar: React.FC = ({
- {/* 해상도 정보 표시 */} + {/* 해상도 선택 드롭다운 */} {screenResolution && ( <>
-
- - {screenResolution.name} - - ({screenResolution.width} × {screenResolution.height}) - + + + + + + {onResolutionChange && ( + + 데스크톱 + {SCREEN_RESOLUTIONS.filter((r) => r.category === "desktop").map((resolution) => ( + onResolutionChange(resolution)} + className="flex items-center space-x-2" + > + + {resolution.name} + + {resolution.width}×{resolution.height} + + + ))} + + 태블릿 + {SCREEN_RESOLUTIONS.filter((r) => r.category === "tablet").map((resolution) => ( + onResolutionChange(resolution)} + className="flex items-center space-x-2" + > + + {resolution.name} + + {resolution.width}×{resolution.height} + + + ))} + + 모바일 + {SCREEN_RESOLUTIONS.filter((r) => r.category === "mobile").map((resolution) => ( + onResolutionChange(resolution)} + className="flex items-center space-x-2" + > + + {resolution.name} + + {resolution.width}×{resolution.height} + + + ))} + + 사용자 정의 + { + e.preventDefault(); + setCustomWidth(screenResolution.width.toString()); + setCustomHeight(screenResolution.height.toString()); + setShowCustomInput(true); + }} + className="flex items-center space-x-2" + > + + 사용자 정의... + + + )} + + +
+
사용자 정의 해상도
+
+
+ + setCustomWidth(e.target.value)} + placeholder="1920" + className="h-8 text-xs" + /> +
+
+ + setCustomHeight(e.target.value)} + placeholder="1080" + className="h-8 text-xs" + /> +
+
+ +
+
+
+ + )} + + {/* 격자 설정 */} + {gridSettings && onGridSettingsChange && ( + <> +
+
+ +
+ + +
)}