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"
+ >
+
+ 사용자 정의...
+
+
+ )}
+
+
+
+
사용자 정의 해상도
+
+
+
+
+
+ >
+ )}
+
+ {/* 격자 설정 */}
+ {gridSettings && onGridSettingsChange && (
+ <>
+
+
+
+
+
+
+
>
)}