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

307 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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";
import { Grid3X3, RotateCcw, Eye, EyeOff, Zap, RefreshCw } from "lucide-react";
import { GridSettings, ScreenResolution } from "@/types/screen";
import { calculateGridInfo } from "@/lib/utils/gridUtils";
interface GridPanelProps {
gridSettings: GridSettings;
onGridSettingsChange: (settings: GridSettings) => void;
onResetGrid: () => void;
onForceGridUpdate?: () => void; // 강제 격자 재조정 추가
screenResolution?: ScreenResolution; // 해상도 정보 추가
}
export const GridPanel: React.FC<GridPanelProps> = ({
gridSettings,
onGridSettingsChange,
onResetGrid,
onForceGridUpdate,
screenResolution,
}) => {
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;
return (
<div className="flex h-full flex-col">
{/* 헤더 */}
<div className="border-b p-4">
<div className="mb-3 flex items-center justify-between">
<div className="flex items-center gap-2">
<Grid3X3 className="text-muted-foreground h-4 w-4" />
<h3 className="text-sm font-semibold"> </h3>
</div>
<div className="flex items-center gap-1.5">
{onForceGridUpdate && (
<Button
size="sm"
variant="outline"
onClick={onForceGridUpdate}
className="h-7 px-2 text-xs"
title="현재 해상도에 맞게 모든 컴포넌트를 격자에 재정렬합니다"
>
<RefreshCw className="mr-1 h-3 w-3" />
</Button>
)}
<Button size="sm" variant="outline" onClick={onResetGrid} className="h-7 px-2 text-xs">
<RotateCcw className="mr-1 h-3 w-3" />
</Button>
</div>
</div>
{/* 주요 토글들 */}
<div className="space-y-2.5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
{gridSettings.showGrid ? (
<Eye className="text-primary h-3.5 w-3.5" />
) : (
<EyeOff className="text-muted-foreground h-3.5 w-3.5" />
)}
<Label htmlFor="showGrid" className="text-xs 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 gap-2">
<Zap className="text-primary h-3.5 w-3.5" />
<Label htmlFor="snapToGrid" className="text-xs font-medium">
</Label>
</div>
<Checkbox
id="snapToGrid"
checked={gridSettings.snapToGrid}
onCheckedChange={(checked) => updateSetting("snapToGrid", checked)}
/>
</div>
</div>
</div>
{/* 설정 영역 */}
<div className="flex-1 space-y-4 overflow-y-auto p-4">
{/* 격자 구조 */}
<div className="space-y-3">
<h4 className="text-xs font-semibold"> </h4>
<div className="space-y-2">
<Label htmlFor="columns" className="text-xs font-medium">
: <span className="text-primary">{gridSettings.columns}</span>
</Label>
<Slider
id="columns"
min={1}
max={24}
step={1}
value={[gridSettings.columns]}
onValueChange={([value]) => updateSetting("columns", value)}
className="w-full"
/>
<div className="text-muted-foreground flex justify-between text-xs">
<span>1</span>
<span>24</span>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="gap" className="text-xs font-medium">
: <span className="text-primary">{gridSettings.gap}px</span>
</Label>
<Slider
id="gap"
min={0}
max={40}
step={2}
value={[gridSettings.gap]}
onValueChange={([value]) => updateSetting("gap", value)}
className="w-full"
/>
<div className="text-muted-foreground flex justify-between text-xs">
<span>0px</span>
<span>40px</span>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="padding" className="text-xs font-medium">
: <span className="text-primary">{gridSettings.padding}px</span>
</Label>
<Slider
id="padding"
min={0}
max={60}
step={4}
value={[gridSettings.padding]}
onValueChange={([value]) => updateSetting("padding", value)}
className="w-full"
/>
<div className="text-muted-foreground flex justify-between text-xs">
<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="bg-primary/20 flex h-16 items-center justify-center rounded border-2 border-dashed border-blue-300">
<span className="text-primary text-xs"> </span>
</div>
</div>
</div>
</div>
{/* 푸터 */}
<div className="border-t border-gray-200 bg-gray-50 p-3">
<div className="text-muted-foreground text-xs">💡 </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-muted-foreground">:</span>
<span className="font-mono">
{screenResolution.width} × {screenResolution.height}
</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground"> :</span>
<span className={`font-mono ${isColumnsTooSmall ? "text-destructive" : "text-gray-900"}`}>
{actualGridInfo.columnWidth.toFixed(1)}px
{isColumnsTooSmall && " (너무 작음)"}
</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground"> :</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>
);
};
export default GridPanel;