화면 해상도 설정 기능 구현
This commit is contained in:
parent
9bf879e29d
commit
1c2249ee42
|
|
@ -45,12 +45,22 @@ export class ScreenManagementService {
|
|||
screenData: CreateScreenRequest,
|
||||
userCompanyCode: string
|
||||
): Promise<ScreenDefinition> {
|
||||
console.log(`=== 화면 생성 요청 ===`);
|
||||
console.log(`요청 데이터:`, screenData);
|
||||
console.log(`사용자 회사 코드:`, userCompanyCode);
|
||||
|
||||
// 화면 코드 중복 확인
|
||||
const existingScreen = await prisma.screen_definitions.findUnique({
|
||||
where: { screen_code: screenData.screenCode },
|
||||
});
|
||||
|
||||
console.log(
|
||||
`화면 코드 '${screenData.screenCode}' 중복 검사 결과:`,
|
||||
existingScreen ? "중복됨" : "사용 가능"
|
||||
);
|
||||
|
||||
if (existingScreen) {
|
||||
console.log(`기존 화면 정보:`, existingScreen);
|
||||
throw new Error("이미 존재하는 화면 코드입니다.");
|
||||
}
|
||||
|
||||
|
|
@ -437,6 +447,8 @@ export class ScreenManagementService {
|
|||
console.log(`=== 레이아웃 저장 시작 ===`);
|
||||
console.log(`화면 ID: ${screenId}`);
|
||||
console.log(`컴포넌트 수: ${layoutData.components.length}`);
|
||||
console.log(`격자 설정:`, layoutData.gridSettings);
|
||||
console.log(`해상도 설정:`, layoutData.screenResolution);
|
||||
|
||||
// 권한 확인
|
||||
const existingScreen = await prisma.screen_definitions.findUnique({
|
||||
|
|
@ -451,12 +463,37 @@ export class ScreenManagementService {
|
|||
throw new Error("이 화면의 레이아웃을 저장할 권한이 없습니다.");
|
||||
}
|
||||
|
||||
// 기존 레이아웃 삭제
|
||||
// 기존 레이아웃 삭제 (컴포넌트와 메타데이터 모두)
|
||||
await prisma.screen_layouts.deleteMany({
|
||||
where: { screen_id: screenId },
|
||||
});
|
||||
|
||||
// 새 레이아웃 저장
|
||||
// 1. 메타데이터 저장 (격자 설정과 해상도 정보)
|
||||
if (layoutData.gridSettings || layoutData.screenResolution) {
|
||||
const metadata: any = {
|
||||
gridSettings: layoutData.gridSettings,
|
||||
screenResolution: layoutData.screenResolution,
|
||||
};
|
||||
|
||||
await prisma.screen_layouts.create({
|
||||
data: {
|
||||
screen_id: screenId,
|
||||
component_type: "_metadata", // 특별한 타입으로 메타데이터 식별
|
||||
component_id: `_metadata_${screenId}`,
|
||||
parent_id: null,
|
||||
position_x: 0,
|
||||
position_y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
properties: metadata,
|
||||
display_order: -1, // 메타데이터는 맨 앞에 배치
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`메타데이터 저장 완료:`, metadata);
|
||||
}
|
||||
|
||||
// 2. 컴포넌트 저장
|
||||
for (const component of layoutData.components) {
|
||||
const { id, ...componentData } = component;
|
||||
|
||||
|
|
@ -531,14 +568,45 @@ export class ScreenManagementService {
|
|||
|
||||
console.log(`DB에서 조회된 레이아웃 수: ${layouts.length}`);
|
||||
|
||||
if (layouts.length === 0) {
|
||||
// 메타데이터와 컴포넌트 분리
|
||||
const metadataLayout = layouts.find(
|
||||
(layout) => layout.component_type === "_metadata"
|
||||
);
|
||||
const componentLayouts = layouts.filter(
|
||||
(layout) => layout.component_type !== "_metadata"
|
||||
);
|
||||
|
||||
// 기본 메타데이터 설정
|
||||
let gridSettings = {
|
||||
columns: 12,
|
||||
gap: 16,
|
||||
padding: 16,
|
||||
snapToGrid: true,
|
||||
showGrid: true,
|
||||
};
|
||||
let screenResolution = null;
|
||||
|
||||
// 저장된 메타데이터가 있으면 적용
|
||||
if (metadataLayout && metadataLayout.properties) {
|
||||
const metadata = metadataLayout.properties as any;
|
||||
if (metadata.gridSettings) {
|
||||
gridSettings = { ...gridSettings, ...metadata.gridSettings };
|
||||
}
|
||||
if (metadata.screenResolution) {
|
||||
screenResolution = metadata.screenResolution;
|
||||
}
|
||||
console.log(`메타데이터 로드:`, { gridSettings, screenResolution });
|
||||
}
|
||||
|
||||
if (componentLayouts.length === 0) {
|
||||
return {
|
||||
components: [],
|
||||
gridSettings: { columns: 12, gap: 16, padding: 16 },
|
||||
gridSettings,
|
||||
screenResolution,
|
||||
};
|
||||
}
|
||||
|
||||
const components: ComponentData[] = layouts.map((layout) => {
|
||||
const components: ComponentData[] = componentLayouts.map((layout) => {
|
||||
const properties = layout.properties as any;
|
||||
const component = {
|
||||
id: layout.component_id,
|
||||
|
|
@ -567,10 +635,13 @@ export class ScreenManagementService {
|
|||
|
||||
console.log(`=== 레이아웃 로드 완료 ===`);
|
||||
console.log(`반환할 컴포넌트 수: ${components.length}`);
|
||||
console.log(`최종 격자 설정:`, gridSettings);
|
||||
console.log(`최종 해상도 설정:`, screenResolution);
|
||||
|
||||
return {
|
||||
components,
|
||||
gridSettings: { columns: 12, gap: 16, padding: 16 },
|
||||
gridSettings,
|
||||
screenResolution,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ export type ComponentData =
|
|||
export interface LayoutData {
|
||||
components: ComponentData[];
|
||||
gridSettings?: GridSettings;
|
||||
screenResolution?: ScreenResolution;
|
||||
}
|
||||
|
||||
// 그리드 설정
|
||||
|
|
@ -115,6 +116,18 @@ export interface GridSettings {
|
|||
columns: number; // 기본값: 12
|
||||
gap: number; // 기본값: 16px
|
||||
padding: number; // 기본값: 16px
|
||||
snapToGrid?: boolean; // 격자에 맞춤 여부 (기본값: true)
|
||||
showGrid?: boolean; // 격자 표시 여부 (기본값: true)
|
||||
gridColor?: string; // 격자 색상 (기본값: #d1d5db)
|
||||
gridOpacity?: number; // 격자 투명도 (기본값: 0.5)
|
||||
}
|
||||
|
||||
// 화면 해상도 설정
|
||||
export interface ScreenResolution {
|
||||
width: number;
|
||||
height: number;
|
||||
name: string;
|
||||
category: "desktop" | "tablet" | "mobile" | "custom";
|
||||
}
|
||||
|
||||
// 유효성 검증 규칙
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export default function ScreenViewPage() {
|
|||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex h-screen w-screen items-center justify-center bg-white">
|
||||
<div className="flex h-full min-h-[400px] w-full items-center justify-center bg-white">
|
||||
<div className="text-center">
|
||||
<Loader2 className="mx-auto h-8 w-8 animate-spin text-blue-600" />
|
||||
<p className="mt-2 text-gray-600">화면을 불러오는 중...</p>
|
||||
|
|
@ -69,7 +69,7 @@ export default function ScreenViewPage() {
|
|||
|
||||
if (error || !screen) {
|
||||
return (
|
||||
<div className="flex h-screen w-screen items-center justify-center bg-white">
|
||||
<div className="flex h-full min-h-[400px] w-full items-center justify-center bg-white">
|
||||
<div className="text-center">
|
||||
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-red-100">
|
||||
<span className="text-2xl">⚠️</span>
|
||||
|
|
@ -84,11 +84,23 @@ export default function ScreenViewPage() {
|
|||
);
|
||||
}
|
||||
|
||||
// 화면 해상도 정보가 있으면 해당 크기로, 없으면 기본 크기 사용
|
||||
const screenWidth = layout?.screenResolution?.width || 1200;
|
||||
const screenHeight = layout?.screenResolution?.height || 800;
|
||||
|
||||
return (
|
||||
<div className="h-screen w-screen bg-white">
|
||||
<div className="h-full w-full overflow-auto bg-white">
|
||||
{layout && layout.components.length > 0 ? (
|
||||
// 캔버스 컴포넌트들만 표시 - 전체 화면 사용
|
||||
<div className="relative h-full w-full">
|
||||
// 캔버스 컴포넌트들을 정확한 해상도로 표시
|
||||
<div
|
||||
className="relative mx-auto bg-white"
|
||||
style={{
|
||||
width: `${screenWidth}px`,
|
||||
height: `${screenHeight}px`,
|
||||
minWidth: `${screenWidth}px`,
|
||||
minHeight: `${screenHeight}px`,
|
||||
}}
|
||||
>
|
||||
{layout.components
|
||||
.filter((comp) => !comp.parentId) // 최상위 컴포넌트만 렌더링 (그룹 포함)
|
||||
.map((component) => {
|
||||
|
|
@ -218,7 +230,15 @@ export default function ScreenViewPage() {
|
|||
</div>
|
||||
) : (
|
||||
// 빈 화면일 때도 깔끔하게 표시
|
||||
<div className="flex h-full items-center justify-center bg-gray-50">
|
||||
<div
|
||||
className="mx-auto flex items-center justify-center bg-gray-50"
|
||||
style={{
|
||||
width: `${screenWidth}px`,
|
||||
height: `${screenHeight}px`,
|
||||
minWidth: `${screenWidth}px`,
|
||||
minHeight: `${screenHeight}px`,
|
||||
}}
|
||||
>
|
||||
<div className="text-center">
|
||||
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-white shadow-sm">
|
||||
<span className="text-2xl">📄</span>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import { useRouter, usePathname, useSearchParams } from "next/navigation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Shield,
|
||||
|
|
@ -194,6 +194,7 @@ const convertSingleMenu = (menu: MenuItem, allMenus: MenuItem[], userInfo: Exten
|
|||
export function AppLayout({ children }: AppLayoutProps) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const { user, logout, refreshUserData } = useAuth();
|
||||
const { userMenus, adminMenus, loading, refreshMenus } = useMenu();
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
|
|
@ -216,8 +217,8 @@ export function AppLayout({ children }: AppLayoutProps) {
|
|||
saveProfile,
|
||||
} = useProfile(user, refreshUserData, refreshMenus);
|
||||
|
||||
// 현재 경로에 따라 어드민 모드인지 판단
|
||||
const isAdminMode = pathname.startsWith("/admin");
|
||||
// 현재 경로에 따라 어드민 모드인지 판단 (쿼리 파라미터도 고려)
|
||||
const isAdminMode = pathname.startsWith("/admin") || searchParams.get("mode") === "admin";
|
||||
|
||||
// 현재 모드에 따라 표시할 메뉴 결정
|
||||
const currentMenus = isAdminMode ? adminMenus : userMenus;
|
||||
|
|
@ -246,7 +247,20 @@ export function AppLayout({ children }: AppLayoutProps) {
|
|||
if (assignedScreens.length > 0) {
|
||||
// 할당된 화면이 있으면 첫 번째 화면으로 이동
|
||||
const firstScreen = assignedScreens[0];
|
||||
router.push(`/screens/${firstScreen.screenId}`);
|
||||
|
||||
// 관리자 모드 상태를 쿼리 파라미터로 전달
|
||||
const screenPath = isAdminMode
|
||||
? `/screens/${firstScreen.screenId}?mode=admin`
|
||||
: `/screens/${firstScreen.screenId}`;
|
||||
|
||||
console.log("🎯 메뉴에서 화면으로 이동:", {
|
||||
menuName: menu.name,
|
||||
screenId: firstScreen.screenId,
|
||||
isAdminMode,
|
||||
targetPath: screenPath,
|
||||
});
|
||||
|
||||
router.push(screenPath);
|
||||
setSidebarOpen(false);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
|||
// 팝업 화면 레이아웃 상태
|
||||
const [popupLayout, setPopupLayout] = useState<ComponentData[]>([]);
|
||||
const [popupLoading, setPopupLoading] = useState(false);
|
||||
const [popupScreenResolution, setPopupScreenResolution] = useState<{ width: number; height: number } | null>(null);
|
||||
|
||||
// 팝업 화면 레이아웃 로드
|
||||
React.useEffect(() => {
|
||||
|
|
@ -73,10 +74,28 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
|||
const loadPopupLayout = async () => {
|
||||
try {
|
||||
setPopupLoading(true);
|
||||
console.log("🔍 팝업 화면 로드 시작:", {
|
||||
screenId: popupScreen.screenId,
|
||||
title: popupScreen.title,
|
||||
size: popupScreen.size
|
||||
});
|
||||
|
||||
const layout = await screenApi.getLayout(popupScreen.screenId);
|
||||
console.log("📊 팝업 화면 레이아웃 로드 완료:", {
|
||||
componentsCount: layout.components?.length || 0,
|
||||
gridSettings: layout.gridSettings,
|
||||
screenResolution: layout.screenResolution,
|
||||
components: layout.components?.map(c => ({
|
||||
id: c.id,
|
||||
type: c.type,
|
||||
title: (c as any).title
|
||||
}))
|
||||
});
|
||||
|
||||
setPopupLayout(layout.components || []);
|
||||
setPopupScreenResolution(layout.screenResolution || null);
|
||||
} catch (error) {
|
||||
console.error("팝업 화면 레이아웃 로드 실패:", error);
|
||||
console.error("❌ 팝업 화면 레이아웃 로드 실패:", error);
|
||||
setPopupLayout([]);
|
||||
} finally {
|
||||
setPopupLoading(false);
|
||||
|
|
@ -1162,14 +1181,32 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
|||
<div className="text-gray-500">화면을 불러오는 중...</div>
|
||||
</div>
|
||||
) : popupLayout.length > 0 ? (
|
||||
<div className="space-y-4">
|
||||
<div className="relative bg-white border rounded" style={{
|
||||
width: popupScreenResolution ? `${popupScreenResolution.width}px` : "100%",
|
||||
height: popupScreenResolution ? `${popupScreenResolution.height}px` : "400px",
|
||||
minHeight: "400px",
|
||||
position: "relative",
|
||||
overflow: "hidden"
|
||||
}}>
|
||||
{/* 팝업에서도 실제 위치와 크기로 렌더링 */}
|
||||
{popupLayout.map((popupComponent) => (
|
||||
<InteractiveScreenViewer
|
||||
<div
|
||||
key={popupComponent.id}
|
||||
component={popupComponent}
|
||||
allComponents={popupLayout}
|
||||
hideLabel={false}
|
||||
/>
|
||||
className="absolute"
|
||||
style={{
|
||||
left: `${popupComponent.position.x}px`,
|
||||
top: `${popupComponent.position.y}px`,
|
||||
width: `${popupComponent.size.width}px`,
|
||||
height: `${popupComponent.size.height}px`,
|
||||
zIndex: popupComponent.position.z || 1,
|
||||
}}
|
||||
>
|
||||
<InteractiveScreenViewer
|
||||
component={popupComponent}
|
||||
allComponents={popupLayout}
|
||||
hideLabel={false}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -197,19 +197,24 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
height = canvasSize.height || window.innerHeight - 200;
|
||||
}
|
||||
|
||||
if (canvasRef.current) {
|
||||
const rect = canvasRef.current.getBoundingClientRect();
|
||||
width = rect.width || width;
|
||||
height = rect.height || height;
|
||||
}
|
||||
|
||||
return calculateGridInfo(width, height, {
|
||||
const newGridInfo = calculateGridInfo(width, height, {
|
||||
columns: layout.gridSettings.columns,
|
||||
gap: layout.gridSettings.gap,
|
||||
padding: layout.gridSettings.padding,
|
||||
snapToGrid: layout.gridSettings.snapToGrid || false,
|
||||
});
|
||||
}, [layout.gridSettings, canvasSize, screenResolution]);
|
||||
|
||||
console.log("🧮 격자 정보 재계산:", {
|
||||
resolution: `${width}x${height}`,
|
||||
columns: layout.gridSettings.columns,
|
||||
gap: layout.gridSettings.gap,
|
||||
padding: layout.gridSettings.padding,
|
||||
columnWidth: newGridInfo.columnWidth.toFixed(2),
|
||||
snapToGrid: layout.gridSettings.snapToGrid,
|
||||
});
|
||||
|
||||
return newGridInfo;
|
||||
}, [layout.gridSettings, screenResolution]);
|
||||
|
||||
// 격자 라인 생성
|
||||
const gridLines = useMemo(() => {
|
||||
|
|
@ -357,11 +362,22 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
gridInfo &&
|
||||
newComp.type !== "group"
|
||||
) {
|
||||
const snappedSize = snapSizeToGrid(newComp.size, gridInfo, layout.gridSettings as GridUtilSettings);
|
||||
// 현재 해상도에 맞는 격자 정보로 스냅 적용
|
||||
const currentGridInfo = calculateGridInfo(screenResolution.width, screenResolution.height, {
|
||||
columns: layout.gridSettings.columns,
|
||||
gap: layout.gridSettings.gap,
|
||||
padding: layout.gridSettings.padding,
|
||||
snapToGrid: layout.gridSettings.snapToGrid || false,
|
||||
});
|
||||
const snappedSize = snapSizeToGrid(newComp.size, currentGridInfo, layout.gridSettings as GridUtilSettings);
|
||||
newComp.size = snappedSize;
|
||||
|
||||
// 크기 변경 시 gridColumns도 자동 조정
|
||||
const adjustedColumns = adjustGridColumnsFromSize(newComp, gridInfo, layout.gridSettings as GridUtilSettings);
|
||||
const adjustedColumns = adjustGridColumnsFromSize(
|
||||
newComp,
|
||||
currentGridInfo,
|
||||
layout.gridSettings as GridUtilSettings,
|
||||
);
|
||||
if (newComp.gridColumns !== adjustedColumns) {
|
||||
newComp.gridColumns = adjustedColumns;
|
||||
console.log("📏 크기 변경으로 gridColumns 자동 조정:", {
|
||||
|
|
@ -372,15 +388,52 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
}
|
||||
}
|
||||
|
||||
// gridColumns 변경 시 크기를 격자에 맞게 자동 조정
|
||||
if (path === "gridColumns" && layout.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,
|
||||
});
|
||||
|
||||
// gridColumns에 맞는 정확한 너비 계산
|
||||
const newWidth = calculateWidthFromColumns(
|
||||
newComp.gridColumns,
|
||||
currentGridInfo,
|
||||
layout.gridSettings as GridUtilSettings,
|
||||
);
|
||||
newComp.size = {
|
||||
...newComp.size,
|
||||
width: newWidth,
|
||||
};
|
||||
|
||||
console.log("📐 gridColumns 변경으로 크기 자동 조정:", {
|
||||
componentId,
|
||||
gridColumns: newComp.gridColumns,
|
||||
oldWidth: comp.size.width,
|
||||
newWidth: newWidth,
|
||||
columnWidth: currentGridInfo.columnWidth,
|
||||
gap: layout.gridSettings.gap,
|
||||
});
|
||||
}
|
||||
|
||||
// 위치 변경 시 격자 스냅 적용 (그룹 내부 컴포넌트 포함)
|
||||
if (
|
||||
(path === "position.x" || path === "position.y" || path === "position") &&
|
||||
layout.gridSettings?.snapToGrid &&
|
||||
gridInfo
|
||||
layout.gridSettings?.snapToGrid
|
||||
) {
|
||||
// 현재 해상도에 맞는 격자 정보 계산
|
||||
const currentGridInfo = calculateGridInfo(screenResolution.width, screenResolution.height, {
|
||||
columns: layout.gridSettings.columns,
|
||||
gap: layout.gridSettings.gap,
|
||||
padding: layout.gridSettings.padding,
|
||||
snapToGrid: layout.gridSettings.snapToGrid || false,
|
||||
});
|
||||
|
||||
// 그룹 내부 컴포넌트인 경우 패딩을 고려한 격자 스냅 적용
|
||||
if (newComp.parentId && gridInfo) {
|
||||
const { columnWidth } = gridInfo;
|
||||
if (newComp.parentId && currentGridInfo) {
|
||||
const { columnWidth } = currentGridInfo;
|
||||
const { gap } = layout.gridSettings;
|
||||
|
||||
// 그룹 내부 패딩 고려한 격자 정렬
|
||||
|
|
@ -432,7 +485,11 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
});
|
||||
} else if (newComp.type !== "group") {
|
||||
// 그룹이 아닌 일반 컴포넌트만 격자 스냅 적용
|
||||
const snappedPosition = snapToGrid(newComp.position, gridInfo, layout.gridSettings as GridUtilSettings);
|
||||
const snappedPosition = snapToGrid(
|
||||
newComp.position,
|
||||
currentGridInfo,
|
||||
layout.gridSettings as GridUtilSettings,
|
||||
);
|
||||
newComp.position = snappedPosition;
|
||||
|
||||
console.log("🧲 일반 컴포넌트 격자 스냅:", {
|
||||
|
|
@ -637,61 +694,96 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
// 해상도 변경 핸들러
|
||||
const handleResolutionChange = useCallback(
|
||||
(newResolution: ScreenResolution) => {
|
||||
console.log("📱 해상도 변경 시작:", {
|
||||
from: `${screenResolution.width}x${screenResolution.height}`,
|
||||
to: `${newResolution.width}x${newResolution.height}`,
|
||||
hasComponents: layout.components.length > 0,
|
||||
snapToGrid: layout.gridSettings?.snapToGrid || false,
|
||||
});
|
||||
|
||||
setScreenResolution(newResolution);
|
||||
console.log("📱 해상도 변경:", newResolution);
|
||||
|
||||
// 레이아웃에 해상도 정보 즉시 반영
|
||||
const updatedLayout = { ...layout, screenResolution: newResolution };
|
||||
// 해상도 변경 시에는 격자 스냅을 적용하지 않고 해상도 정보만 업데이트
|
||||
// 이는 기존 컴포넌트들의 위치를 보존하기 위함
|
||||
const updatedLayout = {
|
||||
...layout,
|
||||
screenResolution: newResolution,
|
||||
};
|
||||
|
||||
// 격자 스냅이 활성화된 경우, 기존 컴포넌트들을 새로운 해상도의 격자에 맞게 조정
|
||||
if (layout.gridSettings?.snapToGrid && layout.components.length > 0) {
|
||||
// 새로운 해상도로 격자 정보 재계산
|
||||
const newGridInfo = calculateGridInfo(newResolution.width, newResolution.height, {
|
||||
columns: layout.gridSettings.columns,
|
||||
gap: layout.gridSettings.gap,
|
||||
padding: layout.gridSettings.padding,
|
||||
snapToGrid: layout.gridSettings.snapToGrid || false,
|
||||
});
|
||||
setLayout(updatedLayout);
|
||||
saveToHistory(updatedLayout);
|
||||
|
||||
const gridUtilSettings = {
|
||||
columns: layout.gridSettings.columns,
|
||||
gap: layout.gridSettings.gap,
|
||||
padding: layout.gridSettings.padding,
|
||||
snapToGrid: layout.gridSettings.snapToGrid,
|
||||
};
|
||||
|
||||
const adjustedComponents = layout.components.map((comp) => {
|
||||
const snappedPosition = snapToGrid(comp.position, newGridInfo, gridUtilSettings);
|
||||
const snappedSize = snapSizeToGrid(comp.size, newGridInfo, gridUtilSettings);
|
||||
|
||||
// gridColumns가 없거나 범위를 벗어나면 자동 조정
|
||||
let adjustedGridColumns = comp.gridColumns;
|
||||
if (!adjustedGridColumns || adjustedGridColumns < 1 || adjustedGridColumns > layout.gridSettings!.columns) {
|
||||
adjustedGridColumns = adjustGridColumnsFromSize({ size: snappedSize }, newGridInfo, gridUtilSettings);
|
||||
}
|
||||
|
||||
return {
|
||||
...comp,
|
||||
position: snappedPosition,
|
||||
size: snappedSize,
|
||||
gridColumns: adjustedGridColumns,
|
||||
};
|
||||
});
|
||||
|
||||
const newLayout = { ...updatedLayout, components: adjustedComponents };
|
||||
setLayout(newLayout);
|
||||
saveToHistory(newLayout);
|
||||
console.log("해상도 변경으로 컴포넌트 위치 및 크기 자동 조정:", adjustedComponents.length, "개");
|
||||
console.log("새로운 격자 정보:", newGridInfo);
|
||||
} else {
|
||||
// 격자 조정이 없는 경우에도 해상도 정보가 포함된 레이아웃 저장
|
||||
setLayout(updatedLayout);
|
||||
saveToHistory(updatedLayout);
|
||||
}
|
||||
console.log("✅ 해상도 변경 완료:", {
|
||||
newResolution: `${newResolution.width}x${newResolution.height}`,
|
||||
preservedComponents: layout.components.length,
|
||||
note: "컴포넌트 위치는 보존됨 (격자 스냅 생략)",
|
||||
});
|
||||
},
|
||||
[layout, saveToHistory],
|
||||
[layout, saveToHistory, screenResolution],
|
||||
);
|
||||
|
||||
// 강제 격자 재조정 핸들러 (해상도 변경 후 수동 격자 맞춤용)
|
||||
const handleForceGridUpdate = useCallback(() => {
|
||||
if (!layout.gridSettings?.snapToGrid || layout.components.length === 0) {
|
||||
console.log("격자 재조정 생략: 스냅 비활성화 또는 컴포넌트 없음");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("🔄 격자 강제 재조정 시작:", {
|
||||
componentsCount: layout.components.length,
|
||||
resolution: `${screenResolution.width}x${screenResolution.height}`,
|
||||
gridSettings: layout.gridSettings,
|
||||
});
|
||||
|
||||
// 현재 해상도로 격자 정보 계산
|
||||
const currentGridInfo = calculateGridInfo(screenResolution.width, screenResolution.height, {
|
||||
columns: layout.gridSettings.columns,
|
||||
gap: layout.gridSettings.gap,
|
||||
padding: layout.gridSettings.padding,
|
||||
snapToGrid: layout.gridSettings.snapToGrid || false,
|
||||
});
|
||||
|
||||
const gridUtilSettings = {
|
||||
columns: layout.gridSettings.columns,
|
||||
gap: layout.gridSettings.gap,
|
||||
padding: layout.gridSettings.padding,
|
||||
snapToGrid: layout.gridSettings.snapToGrid,
|
||||
};
|
||||
|
||||
const adjustedComponents = layout.components.map((comp) => {
|
||||
const snappedPosition = snapToGrid(comp.position, currentGridInfo, gridUtilSettings);
|
||||
const snappedSize = snapSizeToGrid(comp.size, currentGridInfo, gridUtilSettings);
|
||||
|
||||
// gridColumns가 없거나 범위를 벗어나면 자동 조정
|
||||
let adjustedGridColumns = comp.gridColumns;
|
||||
if (!adjustedGridColumns || adjustedGridColumns < 1 || adjustedGridColumns > layout.gridSettings!.columns) {
|
||||
adjustedGridColumns = adjustGridColumnsFromSize({ size: snappedSize }, currentGridInfo, gridUtilSettings);
|
||||
}
|
||||
|
||||
return {
|
||||
...comp,
|
||||
position: snappedPosition,
|
||||
size: snappedSize,
|
||||
gridColumns: adjustedGridColumns,
|
||||
};
|
||||
});
|
||||
|
||||
const newLayout = { ...layout, components: adjustedComponents };
|
||||
setLayout(newLayout);
|
||||
saveToHistory(newLayout);
|
||||
|
||||
console.log("✅ 격자 강제 재조정 완료:", {
|
||||
adjustedComponents: adjustedComponents.length,
|
||||
gridInfo: {
|
||||
columnWidth: currentGridInfo.columnWidth.toFixed(2),
|
||||
totalWidth: currentGridInfo.totalWidth,
|
||||
columns: layout.gridSettings.columns,
|
||||
},
|
||||
});
|
||||
|
||||
toast.success(`${adjustedComponents.length}개 컴포넌트가 격자에 맞게 재정렬되었습니다.`);
|
||||
}, [layout, screenResolution, saveToHistory]);
|
||||
|
||||
// 저장
|
||||
const handleSave = useCallback(async () => {
|
||||
if (!selectedScreen?.screenId) return;
|
||||
|
|
@ -703,6 +795,11 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
...layout,
|
||||
screenResolution: screenResolution,
|
||||
};
|
||||
console.log("💾 저장할 레이아웃 데이터:", {
|
||||
componentsCount: layoutWithResolution.components.length,
|
||||
gridSettings: layoutWithResolution.gridSettings,
|
||||
screenResolution: layoutWithResolution.screenResolution,
|
||||
});
|
||||
await screenApi.saveLayout(selectedScreen.screenId, layoutWithResolution);
|
||||
toast.success("화면이 저장되었습니다.");
|
||||
} catch (error) {
|
||||
|
|
@ -722,10 +819,20 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
const dropX = e.clientX - rect.left;
|
||||
const dropY = e.clientY - rect.top;
|
||||
|
||||
// 현재 해상도에 맞는 격자 정보 계산
|
||||
const currentGridInfo = layout.gridSettings
|
||||
? calculateGridInfo(screenResolution.width, screenResolution.height, {
|
||||
columns: layout.gridSettings.columns,
|
||||
gap: layout.gridSettings.gap,
|
||||
padding: layout.gridSettings.padding,
|
||||
snapToGrid: layout.gridSettings.snapToGrid || false,
|
||||
})
|
||||
: null;
|
||||
|
||||
// 격자 스냅 적용
|
||||
const snappedPosition =
|
||||
layout.gridSettings?.snapToGrid && gridInfo
|
||||
? snapToGrid({ x: dropX, y: dropY, z: 1 }, gridInfo, layout.gridSettings as GridUtilSettings)
|
||||
layout.gridSettings?.snapToGrid && currentGridInfo
|
||||
? snapToGrid({ x: dropX, y: dropY, z: 1 }, currentGridInfo, layout.gridSettings as GridUtilSettings)
|
||||
: { x: dropX, y: dropY, z: 1 };
|
||||
|
||||
console.log("🎨 템플릿 드롭:", {
|
||||
|
|
@ -756,8 +863,8 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
|
||||
// 격자 스냅 적용
|
||||
const finalPosition =
|
||||
layout.gridSettings?.snapToGrid && gridInfo
|
||||
? snapToGrid({ x: absoluteX, y: absoluteY, z: 1 }, gridInfo, layout.gridSettings as GridUtilSettings)
|
||||
layout.gridSettings?.snapToGrid && currentGridInfo
|
||||
? snapToGrid({ x: absoluteX, y: absoluteY, z: 1 }, currentGridInfo, layout.gridSettings as GridUtilSettings)
|
||||
: { x: absoluteX, y: absoluteY, z: 1 };
|
||||
|
||||
if (templateComp.type === "container") {
|
||||
|
|
@ -766,11 +873,11 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
typeof templateComp.size.width === "number" && templateComp.size.width <= 12 ? templateComp.size.width : 4; // 기본 4컬럼
|
||||
|
||||
const calculatedSize =
|
||||
gridInfo && layout.gridSettings?.snapToGrid
|
||||
currentGridInfo && layout.gridSettings?.snapToGrid
|
||||
? (() => {
|
||||
const newWidth = calculateWidthFromColumns(
|
||||
gridColumns,
|
||||
gridInfo,
|
||||
currentGridInfo,
|
||||
layout.gridSettings as GridUtilSettings,
|
||||
);
|
||||
return {
|
||||
|
|
@ -804,11 +911,11 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
|
||||
// gridColumns에 맞는 크기 계산
|
||||
const calculatedSize =
|
||||
gridInfo && layout.gridSettings?.snapToGrid
|
||||
currentGridInfo && layout.gridSettings?.snapToGrid
|
||||
? (() => {
|
||||
const newWidth = calculateWidthFromColumns(
|
||||
gridColumns,
|
||||
gridInfo,
|
||||
currentGridInfo,
|
||||
layout.gridSettings as GridUtilSettings,
|
||||
);
|
||||
return {
|
||||
|
|
@ -822,7 +929,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
gridColumns,
|
||||
templateSize: templateComp.size,
|
||||
calculatedSize,
|
||||
hasGridInfo: !!gridInfo,
|
||||
hasGridInfo: !!currentGridInfo,
|
||||
hasGridSettings: !!layout.gridSettings?.snapToGrid,
|
||||
});
|
||||
|
||||
|
|
@ -934,6 +1041,15 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
}
|
||||
};
|
||||
|
||||
// 위젯 크기도 격자에 맞게 조정
|
||||
const widgetSize =
|
||||
currentGridInfo && layout.gridSettings?.snapToGrid
|
||||
? {
|
||||
width: calculateWidthFromColumns(1, currentGridInfo, layout.gridSettings as GridUtilSettings),
|
||||
height: templateComp.size.height,
|
||||
}
|
||||
: templateComp.size;
|
||||
|
||||
return {
|
||||
id: componentId,
|
||||
type: "widget",
|
||||
|
|
@ -943,7 +1059,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
columnName: `field_${index + 1}`,
|
||||
parentId: templateComp.parentId ? idMapping[templateComp.parentId] : undefined,
|
||||
position: finalPosition,
|
||||
size: templateComp.size,
|
||||
size: widgetSize,
|
||||
required: templateComp.required || false,
|
||||
readonly: templateComp.readonly || false,
|
||||
gridColumns: 1,
|
||||
|
|
@ -1034,8 +1150,34 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
},
|
||||
};
|
||||
} else if (type === "column") {
|
||||
// 격자 기반 컬럼 너비 계산
|
||||
const columnWidth = gridInfo ? gridInfo.columnWidth : 200;
|
||||
// 현재 해상도에 맞는 격자 정보로 기본 크기 계산
|
||||
const currentGridInfo = layout.gridSettings
|
||||
? calculateGridInfo(screenResolution.width, screenResolution.height, {
|
||||
columns: layout.gridSettings.columns,
|
||||
gap: layout.gridSettings.gap,
|
||||
padding: layout.gridSettings.padding,
|
||||
snapToGrid: layout.gridSettings.snapToGrid || false,
|
||||
})
|
||||
: null;
|
||||
|
||||
// 격자 스냅이 활성화된 경우 정확한 격자 크기로 생성, 아니면 기본값
|
||||
const defaultWidth =
|
||||
currentGridInfo && layout.gridSettings?.snapToGrid
|
||||
? calculateWidthFromColumns(1, currentGridInfo, layout.gridSettings as GridUtilSettings)
|
||||
: 200;
|
||||
|
||||
console.log("🎯 컴포넌트 생성 시 크기 계산:", {
|
||||
screenResolution: `${screenResolution.width}x${screenResolution.height}`,
|
||||
gridSettings: layout.gridSettings,
|
||||
currentGridInfo: currentGridInfo
|
||||
? {
|
||||
columnWidth: currentGridInfo.columnWidth.toFixed(2),
|
||||
totalWidth: currentGridInfo.totalWidth,
|
||||
}
|
||||
: null,
|
||||
defaultWidth: defaultWidth.toFixed(2),
|
||||
snapToGrid: layout.gridSettings?.snapToGrid,
|
||||
});
|
||||
|
||||
// 웹타입별 기본 설정 생성
|
||||
const getDefaultWebTypeConfig = (widgetType: string) => {
|
||||
|
|
@ -1183,7 +1325,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
readonly: false,
|
||||
parentId: formContainerId, // 폼 컨테이너의 자식으로 설정
|
||||
position: { x: relativeX, y: relativeY, z: 1 } as Position,
|
||||
size: { width: 200, height: 40 },
|
||||
size: { width: defaultWidth, height: 40 },
|
||||
style: {
|
||||
labelDisplay: true,
|
||||
labelFontSize: "12px",
|
||||
|
|
@ -1208,7 +1350,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
required: column.required,
|
||||
readonly: false,
|
||||
position: { x, y, z: 1 } as Position,
|
||||
size: { width: columnWidth, height: 40 },
|
||||
size: { width: defaultWidth, height: 40 },
|
||||
gridColumns: 1,
|
||||
style: {
|
||||
labelDisplay: true,
|
||||
|
|
@ -1225,20 +1367,30 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
}
|
||||
|
||||
// 격자 스냅 적용 (그룹 컴포넌트 제외)
|
||||
if (layout.gridSettings?.snapToGrid && gridInfo && newComponent.type !== "group") {
|
||||
if (layout.gridSettings?.snapToGrid && newComponent.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,
|
||||
});
|
||||
|
||||
const gridUtilSettings = {
|
||||
columns: layout.gridSettings.columns,
|
||||
gap: layout.gridSettings.gap,
|
||||
padding: layout.gridSettings.padding,
|
||||
snapToGrid: layout.gridSettings.snapToGrid || false,
|
||||
};
|
||||
newComponent.position = snapToGrid(newComponent.position, gridInfo, gridUtilSettings);
|
||||
newComponent.size = snapSizeToGrid(newComponent.size, gridInfo, gridUtilSettings);
|
||||
newComponent.position = snapToGrid(newComponent.position, currentGridInfo, gridUtilSettings);
|
||||
newComponent.size = snapSizeToGrid(newComponent.size, currentGridInfo, gridUtilSettings);
|
||||
|
||||
console.log("🧲 새 컴포넌트 격자 스냅 적용:", {
|
||||
type: newComponent.type,
|
||||
resolution: `${screenResolution.width}x${screenResolution.height}`,
|
||||
snappedPosition: newComponent.position,
|
||||
snappedSize: newComponent.size,
|
||||
columnWidth: currentGridInfo.columnWidth,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1412,15 +1564,25 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
const draggedComponent = layout.components.find((c) => c.id === dragState.draggedComponent);
|
||||
let finalPosition = dragState.currentPosition;
|
||||
|
||||
// 현재 해상도에 맞는 격자 정보 계산
|
||||
const currentGridInfo = layout.gridSettings
|
||||
? calculateGridInfo(screenResolution.width, screenResolution.height, {
|
||||
columns: layout.gridSettings.columns,
|
||||
gap: layout.gridSettings.gap,
|
||||
padding: layout.gridSettings.padding,
|
||||
snapToGrid: layout.gridSettings.snapToGrid || false,
|
||||
})
|
||||
: null;
|
||||
|
||||
// 일반 컴포넌트만 격자 스냅 적용 (그룹 제외)
|
||||
if (draggedComponent?.type !== "group" && layout.gridSettings?.snapToGrid && gridInfo) {
|
||||
if (draggedComponent?.type !== "group" && layout.gridSettings?.snapToGrid && currentGridInfo) {
|
||||
finalPosition = snapToGrid(
|
||||
{
|
||||
x: dragState.currentPosition.x,
|
||||
y: dragState.currentPosition.y,
|
||||
z: dragState.currentPosition.z ?? 1,
|
||||
},
|
||||
gridInfo,
|
||||
currentGridInfo,
|
||||
{
|
||||
columns: layout.gridSettings.columns,
|
||||
gap: layout.gridSettings.gap,
|
||||
|
|
@ -1428,6 +1590,13 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
snapToGrid: layout.gridSettings.snapToGrid || false,
|
||||
},
|
||||
);
|
||||
|
||||
console.log("🎯 격자 스냅 적용됨:", {
|
||||
resolution: `${screenResolution.width}x${screenResolution.height}`,
|
||||
originalPosition: dragState.currentPosition,
|
||||
snappedPosition: finalPosition,
|
||||
columnWidth: currentGridInfo.columnWidth,
|
||||
});
|
||||
}
|
||||
|
||||
// 스냅으로 인한 추가 이동 거리 계산
|
||||
|
|
@ -2291,6 +2460,11 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
...layout,
|
||||
screenResolution: screenResolution,
|
||||
};
|
||||
console.log("⚡ 자동 저장할 레이아웃 데이터:", {
|
||||
componentsCount: layoutWithResolution.components.length,
|
||||
gridSettings: layoutWithResolution.gridSettings,
|
||||
screenResolution: layoutWithResolution.screenResolution,
|
||||
});
|
||||
await screenApi.saveLayout(selectedScreen.screenId, layoutWithResolution);
|
||||
toast.success("레이아웃이 저장되었습니다.");
|
||||
} catch (error) {
|
||||
|
|
@ -2758,6 +2932,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
const defaultSettings = { columns: 12, gap: 16, padding: 16, snapToGrid: true, showGrid: true };
|
||||
updateGridSettings(defaultSettings);
|
||||
}}
|
||||
onForceGridUpdate={handleForceGridUpdate}
|
||||
screenResolution={screenResolution}
|
||||
/>
|
||||
</FloatingPanel>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { Checkbox } from "@/components/ui/checkbox";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import { Grid3X3, RotateCcw, Eye, EyeOff, Zap } from "lucide-react";
|
||||
import { Grid3X3, RotateCcw, Eye, EyeOff, Zap, RefreshCw } from "lucide-react";
|
||||
import { GridSettings, ScreenResolution } from "@/types/screen";
|
||||
import { calculateGridInfo } from "@/lib/utils/gridUtils";
|
||||
|
||||
|
|
@ -15,6 +15,7 @@ interface GridPanelProps {
|
|||
gridSettings: GridSettings;
|
||||
onGridSettingsChange: (settings: GridSettings) => void;
|
||||
onResetGrid: () => void;
|
||||
onForceGridUpdate?: () => void; // 강제 격자 재조정 추가
|
||||
screenResolution?: ScreenResolution; // 해상도 정보 추가
|
||||
}
|
||||
|
||||
|
|
@ -22,6 +23,7 @@ export const GridPanel: React.FC<GridPanelProps> = ({
|
|||
gridSettings,
|
||||
onGridSettingsChange,
|
||||
onResetGrid,
|
||||
onForceGridUpdate,
|
||||
screenResolution,
|
||||
}) => {
|
||||
const updateSetting = (key: keyof GridSettings, value: any) => {
|
||||
|
|
@ -60,10 +62,25 @@ export const GridPanel: React.FC<GridPanelProps> = ({
|
|||
<h3 className="font-medium text-gray-900">격자 설정</h3>
|
||||
</div>
|
||||
|
||||
<Button size="sm" variant="outline" onClick={onResetGrid} className="flex items-center space-x-1">
|
||||
<RotateCcw className="h-3 w-3" />
|
||||
<span>초기화</span>
|
||||
</Button>
|
||||
<div className="flex items-center space-x-2">
|
||||
{onForceGridUpdate && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={onForceGridUpdate}
|
||||
className="flex items-center space-x-1"
|
||||
title="현재 해상도에 맞게 모든 컴포넌트를 격자에 재정렬합니다"
|
||||
>
|
||||
<RefreshCw className="h-3 w-3" />
|
||||
<span>재정렬</span>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button size="sm" variant="outline" onClick={onResetGrid} className="flex items-center space-x-1">
|
||||
<RotateCcw className="h-3 w-3" />
|
||||
<span>초기화</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 주요 토글들 */}
|
||||
|
|
@ -100,19 +117,6 @@ export const GridPanel: React.FC<GridPanelProps> = ({
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 해상도 정보 */}
|
||||
{screenResolution && (
|
||||
<div className="mt-3 rounded bg-blue-50 p-3">
|
||||
<h4 className="text-xs font-medium text-blue-900">현재 해상도</h4>
|
||||
<p className="mt-1 text-xs text-blue-700">
|
||||
{screenResolution.width} × {screenResolution.height}
|
||||
</p>
|
||||
{actualGridInfo && (
|
||||
<p className="mt-1 text-xs text-blue-600">컬럼 너비: {Math.round(actualGridInfo.columnWidth)}px</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 설정 영역 */}
|
||||
|
|
@ -124,11 +128,6 @@ export const GridPanel: React.FC<GridPanelProps> = ({
|
|||
<div>
|
||||
<Label htmlFor="columns" className="mb-2 block text-sm font-medium">
|
||||
컬럼 수: {gridSettings.columns}
|
||||
{isColumnsTooSmall && (
|
||||
<span className="ml-2 text-xs text-orange-600">
|
||||
(컬럼이 너무 작음: {Math.round(actualGridInfo!.columnWidth)}px)
|
||||
</span>
|
||||
)}
|
||||
</Label>
|
||||
<Slider
|
||||
id="columns"
|
||||
|
|
@ -258,7 +257,47 @@ export const GridPanel: React.FC<GridPanelProps> = ({
|
|||
|
||||
{/* 푸터 */}
|
||||
<div className="border-t border-gray-200 bg-gray-50 p-3">
|
||||
<div className="text-xs text-gray-600">💡 격자 설정은 실시간으로 캔버스에 반영됩니다</div>
|
||||
<div className="text-xs text-gray-600">💡 격자 설정은 실시간으로 캔버스에 반영됩니다 </div>
|
||||
|
||||
{/* 해상도 및 격자 정보 */}
|
||||
{screenResolution && actualGridInfo && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="space-y-3">
|
||||
<h4 className="font-medium text-gray-900">격자 정보</h4>
|
||||
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">해상도:</span>
|
||||
<span className="font-mono">
|
||||
{screenResolution.width} × {screenResolution.height}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">컬럼 너비:</span>
|
||||
<span className={`font-mono ${isColumnsTooSmall ? "text-red-600" : "text-gray-900"}`}>
|
||||
{actualGridInfo.columnWidth.toFixed(1)}px
|
||||
{isColumnsTooSmall && " (너무 작음)"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">사용 가능 너비:</span>
|
||||
<span className="font-mono">
|
||||
{(screenResolution.width - gridSettings.padding * 2).toLocaleString()}px
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{isColumnsTooSmall && (
|
||||
<div className="rounded-md bg-yellow-50 p-2 text-xs text-yellow-800">
|
||||
💡 컬럼이 너무 작습니다. 컬럼 수를 줄이거나 간격을 줄여보세요.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue