ERP-node/frontend/components/screen/panels/GridPanel.tsx

307 lines
11 KiB
TypeScript
Raw Normal View History

2025-09-02 16:18:38 +09:00
"use client";
import React from "react";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { Slider } from "@/components/ui/slider";
2025-09-04 17:01:07 +09:00
import { Grid3X3, RotateCcw, Eye, EyeOff, Zap, RefreshCw } from "lucide-react";
import { GridSettings, ScreenResolution } from "@/types/screen";
import { calculateGridInfo } from "@/lib/utils/gridUtils";
2025-09-02 16:18:38 +09:00
interface GridPanelProps {
gridSettings: GridSettings;
onGridSettingsChange: (settings: GridSettings) => void;
onResetGrid: () => void;
2025-09-04 17:01:07 +09:00
onForceGridUpdate?: () => void; // 강제 격자 재조정 추가
screenResolution?: ScreenResolution; // 해상도 정보 추가
2025-09-02 16:18:38 +09:00
}
export const GridPanel: React.FC<GridPanelProps> = ({
gridSettings,
onGridSettingsChange,
onResetGrid,
2025-09-04 17:01:07 +09:00
onForceGridUpdate,
screenResolution,
}) => {
2025-09-02 16:18:38 +09:00
const updateSetting = (key: keyof GridSettings, value: any) => {
onGridSettingsChange({
...gridSettings,
[key]: value,
});
};
// 실제 격자 정보 계산
const actualGridInfo = screenResolution
? calculateGridInfo(screenResolution.width, screenResolution.height, {
columns: gridSettings.columns,
gap: gridSettings.gap,
padding: gridSettings.padding,
snapToGrid: gridSettings.snapToGrid || false,
})
: null;
// 실제 표시되는 컬럼 수 계산 (항상 설정된 개수를 표시하되, 너비가 너무 작으면 경고)
const actualColumns = gridSettings.columns;
// 컬럼이 너무 작은지 확인
const isColumnsTooSmall =
screenResolution && actualGridInfo
? actualGridInfo.columnWidth < 30 // 30px 미만이면 너무 작다고 판단
: false;
2025-09-02 16:18:38 +09:00
return (
<div className="flex h-full flex-col">
{/* 헤더 */}
<div className="border-b border-gray-200 p-4">
<div className="mb-3 flex items-center justify-between">
<div className="flex items-center space-x-2">
<Grid3X3 className="h-4 w-4 text-gray-600" />
<h3 className="font-medium text-gray-900"> </h3>
</div>
2025-09-04 17:01:07 +09:00
<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>
2025-09-02 16:18:38 +09:00
</div>
{/* 주요 토글들 */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
{gridSettings.showGrid ? (
<Eye className="h-4 w-4 text-blue-600" />
) : (
<EyeOff className="h-4 w-4 text-gray-400" />
)}
<Label htmlFor="showGrid" className="text-sm font-medium">
</Label>
</div>
<Checkbox
id="showGrid"
checked={gridSettings.showGrid}
onCheckedChange={(checked) => updateSetting("showGrid", checked)}
/>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<Zap className="h-4 w-4 text-green-600" />
<Label htmlFor="snapToGrid" className="text-sm font-medium">
</Label>
</div>
<Checkbox
id="snapToGrid"
checked={gridSettings.snapToGrid}
onCheckedChange={(checked) => updateSetting("snapToGrid", checked)}
/>
</div>
</div>
</div>
{/* 설정 영역 */}
<div className="flex-1 space-y-6 overflow-y-auto p-4">
{/* 격자 구조 */}
<div className="space-y-4">
<h4 className="font-medium text-gray-900"> </h4>
<div>
<Label htmlFor="columns" className="mb-2 block text-sm font-medium">
: {gridSettings.columns}
</Label>
<Slider
id="columns"
min={1}
max={24}
step={1}
value={[gridSettings.columns]}
onValueChange={([value]) => updateSetting("columns", value)}
className="w-full"
/>
<div className="mt-1 flex justify-between text-xs text-gray-500">
<span>1</span>
<span>24</span>
</div>
</div>
<div>
<Label htmlFor="gap" className="mb-2 block text-sm font-medium">
: {gridSettings.gap}px
</Label>
<Slider
id="gap"
min={0}
max={40}
step={2}
value={[gridSettings.gap]}
onValueChange={([value]) => updateSetting("gap", value)}
className="w-full"
/>
<div className="mt-1 flex justify-between text-xs text-gray-500">
<span>0px</span>
<span>40px</span>
</div>
</div>
<div>
<Label htmlFor="padding" className="mb-2 block text-sm font-medium">
: {gridSettings.padding}px
</Label>
<Slider
id="padding"
min={0}
max={60}
step={4}
value={[gridSettings.padding]}
onValueChange={([value]) => updateSetting("padding", value)}
className="w-full"
/>
<div className="mt-1 flex justify-between text-xs text-gray-500">
<span>0px</span>
<span>60px</span>
</div>
</div>
</div>
<Separator />
{/* 격자 스타일 */}
<div className="space-y-4">
<h4 className="font-medium text-gray-900"> </h4>
<div>
<Label htmlFor="gridColor" className="text-sm font-medium">
</Label>
<div className="mt-1 flex items-center space-x-2">
<Input
id="gridColor"
type="color"
value={gridSettings.gridColor || "#d1d5db"}
onChange={(e) => updateSetting("gridColor", e.target.value)}
className="h-8 w-12 rounded border p-1"
/>
<Input
type="text"
value={gridSettings.gridColor || "#d1d5db"}
onChange={(e) => updateSetting("gridColor", e.target.value)}
placeholder="#d1d5db"
className="flex-1"
/>
</div>
</div>
<div>
<Label htmlFor="gridOpacity" className="mb-2 block text-sm font-medium">
: {Math.round((gridSettings.gridOpacity || 0.5) * 100)}%
</Label>
<Slider
id="gridOpacity"
min={0.1}
max={1}
step={0.1}
value={[gridSettings.gridOpacity || 0.5]}
onValueChange={([value]) => updateSetting("gridOpacity", value)}
className="w-full"
/>
<div className="mt-1 flex justify-between text-xs text-gray-500">
<span>10%</span>
<span>100%</span>
</div>
</div>
</div>
<Separator />
{/* 미리보기 */}
<div className="space-y-3">
<h4 className="font-medium text-gray-900"></h4>
<div
className="rounded-md border border-gray-200 bg-white p-4"
style={{
backgroundImage: gridSettings.showGrid
? `linear-gradient(to right, ${gridSettings.gridColor || "#d1d5db"} 1px, transparent 1px),
linear-gradient(to bottom, ${gridSettings.gridColor || "#d1d5db"} 1px, transparent 1px)`
: "none",
backgroundSize: gridSettings.showGrid ? `${100 / gridSettings.columns}% 20px` : "none",
opacity: gridSettings.gridOpacity || 0.5,
}}
>
<div className="flex h-16 items-center justify-center rounded border-2 border-dashed border-blue-300 bg-blue-100">
<span className="text-xs text-blue-600"> </span>
</div>
</div>
</div>
</div>
{/* 푸터 */}
<div className="border-t border-gray-200 bg-gray-50 p-3">
2025-09-04 17:01:07 +09:00
<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>
</>
)}
2025-09-02 16:18:38 +09:00
</div>
</div>
);
};
export default GridPanel;