ERP-node/frontend/components/screen/toolbar/SlimToolbar.tsx

361 lines
14 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, { useState } from "react";
import { Button } from "@/components/ui/button";
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,
Languages,
Settings2,
PanelLeft,
PanelLeftClose,
} 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;
tableName?: string;
screenResolution?: ScreenResolution;
onBack: () => void;
onSave: () => void;
isSaving?: boolean;
onPreview?: () => void;
onResolutionChange?: (resolution: ScreenResolution) => void;
gridSettings?: GridSettings;
onGridSettingsChange?: (settings: GridSettings) => void;
onGenerateMultilang?: () => void;
isGeneratingMultilang?: boolean;
onOpenMultilangSettings?: () => void;
// 패널 토글 기능
isPanelOpen?: boolean;
onTogglePanel?: () => void;
}
export const SlimToolbar: React.FC<SlimToolbarProps> = ({
screenName,
tableName,
screenResolution,
onBack,
onSave,
isSaving = false,
onPreview,
onResolutionChange,
gridSettings,
onGridSettingsChange,
onGenerateMultilang,
isGeneratingMultilang = false,
onOpenMultilangSettings,
isPanelOpen = false,
onTogglePanel,
}) => {
// 사용자 정의 해상도 상태
const [customWidth, setCustomWidth] = useState("");
const [customHeight, setCustomHeight] = useState("");
const [showCustomInput, setShowCustomInput] = useState(false);
const getCategoryIcon = (category: string) => {
switch (category) {
case "desktop":
return <Monitor className="h-4 w-4 text-blue-600" />;
case "tablet":
return <Tablet className="h-4 w-4 text-green-600" />;
case "mobile":
return <Smartphone className="h-4 w-4 text-purple-600" />;
default:
return <Monitor className="h-4 w-4 text-blue-600" />;
}
};
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 (
<div className="flex h-14 items-center justify-between border-b border-gray-200 bg-gradient-to-r from-gray-50 to-white px-4 shadow-sm">
{/* 좌측: 네비게이션 + 패널 토글 + 화면 정보 */}
<div className="flex items-center space-x-4">
<Button variant="ghost" size="sm" onClick={onBack} className="flex items-center space-x-2">
<ArrowLeft className="h-4 w-4" />
<span></span>
</Button>
{onTogglePanel && <div className="h-6 w-px bg-gray-300" />}
{/* 패널 토글 버튼 */}
{onTogglePanel && (
<Button
variant={isPanelOpen ? "default" : "outline"}
size="sm"
onClick={onTogglePanel}
className="flex items-center space-x-2"
title="패널 열기/닫기 (P)"
>
{isPanelOpen ? (
<PanelLeftClose className="h-4 w-4" />
) : (
<PanelLeft className="h-4 w-4" />
)}
<span></span>
</Button>
)}
<div className="h-6 w-px bg-gray-300" />
<div className="flex items-center space-x-3">
<div>
<h1 className="text-lg font-semibold text-gray-900">{screenName || "화면 설계"}</h1>
{tableName && (
<div className="mt-0.5 flex items-center space-x-1">
<Database className="h-3 w-3 text-gray-500" />
<span className="font-mono text-xs text-gray-500">{tableName}</span>
</div>
)}
</div>
</div>
{/* 해상도 선택 드롭다운 */}
{screenResolution && (
<>
<div className="h-6 w-px bg-gray-300" />
<Popover open={showCustomInput} onOpenChange={setShowCustomInput}>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="flex items-center space-x-2 rounded-md bg-blue-50 px-3 py-1.5 transition-colors hover:bg-blue-100">
{getCategoryIcon(screenResolution.category || "desktop")}
<span className="text-sm font-medium text-blue-900">{screenResolution.name}</span>
<span className="text-xs text-blue-600">
({screenResolution.width} × {screenResolution.height})
</span>
{onResolutionChange && <ChevronDown className="h-3 w-3 text-blue-600" />}
</button>
</DropdownMenuTrigger>
{onResolutionChange && (
<DropdownMenuContent align="start" className="w-64">
<DropdownMenuLabel className="text-xs text-gray-500"></DropdownMenuLabel>
{SCREEN_RESOLUTIONS.filter((r) => r.category === "desktop").map((resolution) => (
<DropdownMenuItem
key={resolution.name}
onClick={() => onResolutionChange(resolution)}
className="flex items-center space-x-2"
>
<Monitor className="h-4 w-4 text-blue-600" />
<span className="flex-1">{resolution.name}</span>
<span className="text-xs text-gray-400">
{resolution.width}×{resolution.height}
</span>
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
<DropdownMenuLabel className="text-xs text-gray-500">릿</DropdownMenuLabel>
{SCREEN_RESOLUTIONS.filter((r) => r.category === "tablet").map((resolution) => (
<DropdownMenuItem
key={resolution.name}
onClick={() => onResolutionChange(resolution)}
className="flex items-center space-x-2"
>
<Tablet className="h-4 w-4 text-green-600" />
<span className="flex-1">{resolution.name}</span>
<span className="text-xs text-gray-400">
{resolution.width}×{resolution.height}
</span>
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
<DropdownMenuLabel className="text-xs text-gray-500"></DropdownMenuLabel>
{SCREEN_RESOLUTIONS.filter((r) => r.category === "mobile").map((resolution) => (
<DropdownMenuItem
key={resolution.name}
onClick={() => onResolutionChange(resolution)}
className="flex items-center space-x-2"
>
<Smartphone className="h-4 w-4 text-purple-600" />
<span className="flex-1">{resolution.name}</span>
<span className="text-xs text-gray-400">
{resolution.width}×{resolution.height}
</span>
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
<DropdownMenuLabel className="text-xs text-gray-500"> </DropdownMenuLabel>
<DropdownMenuItem
onClick={(e) => {
e.preventDefault();
setCustomWidth(screenResolution.width.toString());
setCustomHeight(screenResolution.height.toString());
setShowCustomInput(true);
}}
className="flex items-center space-x-2"
>
<Settings className="h-4 w-4 text-gray-600" />
<span className="flex-1"> ...</span>
</DropdownMenuItem>
</DropdownMenuContent>
)}
</DropdownMenu>
<PopoverContent align="start" className="w-64 p-3">
<div className="space-y-3">
<div className="text-sm font-medium"> </div>
<div className="grid grid-cols-2 gap-2">
<div className="space-y-1">
<Label className="text-xs text-gray-500"> (px)</Label>
<Input
type="number"
value={customWidth}
onChange={(e) => setCustomWidth(e.target.value)}
placeholder="1920"
className="h-8 text-xs"
/>
</div>
<div className="space-y-1">
<Label className="text-xs text-gray-500"> (px)</Label>
<Input
type="number"
value={customHeight}
onChange={(e) => setCustomHeight(e.target.value)}
placeholder="1080"
className="h-8 text-xs"
/>
</div>
</div>
<Button onClick={handleCustomResolution} size="sm" className="w-full">
</Button>
</div>
</PopoverContent>
</Popover>
</>
)}
{/* 격자 설정 */}
{gridSettings && onGridSettingsChange && (
<>
<div className="h-6 w-px bg-gray-300" />
<div className="flex items-center space-x-2 rounded-md bg-gray-50 px-3 py-1.5">
<Grid3X3 className="h-4 w-4 text-gray-600" />
<div className="flex items-center space-x-3">
<label className="flex cursor-pointer items-center space-x-1.5">
{gridSettings.showGrid ? (
<Eye className="h-3.5 w-3.5 text-primary" />
) : (
<EyeOff className="h-3.5 w-3.5 text-gray-400" />
)}
<Checkbox
checked={gridSettings.showGrid}
onCheckedChange={(checked) => updateGridSetting("showGrid", checked as boolean)}
className="h-3.5 w-3.5"
/>
<span className="text-xs text-gray-600"> </span>
</label>
<label className="flex cursor-pointer items-center space-x-1.5">
<Zap className={`h-3.5 w-3.5 ${gridSettings.snapToGrid ? "text-primary" : "text-gray-400"}`} />
<Checkbox
checked={gridSettings.snapToGrid}
onCheckedChange={(checked) => updateGridSetting("snapToGrid", checked as boolean)}
className="h-3.5 w-3.5"
/>
<span className="text-xs text-gray-600"> </span>
</label>
</div>
</div>
</>
)}
</div>
{/* 우측: 버튼들 */}
<div className="flex items-center space-x-2">
{onPreview && (
<Button variant="outline" onClick={onPreview} className="flex items-center space-x-2">
<Smartphone className="h-4 w-4" />
<span> </span>
</Button>
)}
{onGenerateMultilang && (
<Button
variant="outline"
onClick={onGenerateMultilang}
disabled={isGeneratingMultilang}
className="flex items-center space-x-2"
title="화면 라벨에 대한 다국어 키를 자동으로 생성합니다"
>
<Languages className="h-4 w-4" />
<span>{isGeneratingMultilang ? "생성 중..." : "다국어 생성"}</span>
</Button>
)}
{onOpenMultilangSettings && (
<Button
variant="outline"
onClick={onOpenMultilangSettings}
className="flex items-center space-x-2"
title="다국어 키 연결 및 설정을 관리합니다"
>
<Settings2 className="h-4 w-4" />
<span> </span>
</Button>
)}
<Button onClick={onSave} disabled={isSaving} className="flex items-center space-x-2">
<Save className="h-4 w-4" />
<span>{isSaving ? "저장 중..." : "저장"}</span>
</Button>
</div>
</div>
);
};
export default SlimToolbar;