ERP-node/frontend/components/v2/config-panels/V2LayoutConfigPanel.tsx

437 lines
16 KiB
TypeScript
Raw Normal View History

2025-12-19 15:44:38 +09:00
"use client";
/**
* V2Layout
* UX: 레이아웃 -> -> ()
2025-12-19 15:44:38 +09:00
*/
import React, { useState } from "react";
2025-12-19 15:44:38 +09:00
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
LayoutGrid,
PanelLeftClose,
MoveHorizontal,
Minus,
MonitorPlay,
Settings,
ChevronDown,
} from "lucide-react";
import { cn } from "@/lib/utils";
// ─── 레이아웃 타입 카드 정의 ───
const LAYOUT_TYPE_CARDS = [
{
value: "grid",
icon: LayoutGrid,
title: "그리드",
description: "행과 열로 배치해요",
},
{
value: "split",
icon: PanelLeftClose,
title: "분할 패널",
description: "영역을 나눠서 배치해요",
},
{
value: "flex",
icon: MoveHorizontal,
title: "플렉스",
description: "유연하게 배치해요",
},
{
value: "divider",
icon: Minus,
title: "구분선",
description: "영역을 구분해요",
},
{
value: "screen-embed",
icon: MonitorPlay,
title: "화면 임베드",
description: "다른 화면을 불러와요",
},
] as const;
2025-12-19 15:44:38 +09:00
interface V2LayoutConfigPanelProps {
2025-12-19 15:44:38 +09:00
config: Record<string, any>;
onChange: (config: Record<string, any>) => void;
}
export const V2LayoutConfigPanel: React.FC<V2LayoutConfigPanelProps> = ({
2025-12-19 15:44:38 +09:00
config,
onChange,
}) => {
const [advancedOpen, setAdvancedOpen] = useState(false);
2025-12-19 15:44:38 +09:00
const updateConfig = (field: string, value: any) => {
onChange({ ...config, [field]: value });
};
const currentLayoutType = config.layoutType || config.type || "grid";
const isGridType = currentLayoutType === "grid";
const isSplitType = currentLayoutType === "split";
const isFlexType = currentLayoutType === "flex";
const isScreenEmbedType = currentLayoutType === "screen-embed";
2025-12-19 15:44:38 +09:00
return (
<div className="space-y-4">
{/* ─── 1단계: 레이아웃 타입 선택 (카드) ─── */}
<div className="space-y-2">
<p className="text-sm font-medium"> ?</p>
<div className="grid grid-cols-3 gap-2">
{LAYOUT_TYPE_CARDS.map((card) => {
const Icon = card.icon;
const isSelected = currentLayoutType === card.value;
return (
<button
key={card.value}
type="button"
onClick={() => updateConfig("layoutType", card.value)}
className={cn(
"flex flex-col items-center justify-center rounded-lg border p-3 text-center transition-all min-h-[80px]",
isSelected
? "border-primary bg-primary/5 ring-1 ring-primary/20"
: "border-border hover:border-primary/50 hover:bg-muted/50",
)}
>
<Icon className="h-5 w-5 mb-1.5 text-primary" />
<span className="text-xs font-medium leading-tight">
{card.title}
</span>
<span className="text-[10px] text-muted-foreground leading-tight mt-0.5">
{card.description}
</span>
</button>
);
})}
</div>
2025-12-19 15:44:38 +09:00
</div>
{/* ─── 2단계: 타입별 설정 ─── */}
{/* 그리드 타입 설정 */}
{isGridType && (
<div className="space-y-3">
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
<div className="flex items-center gap-2">
<LayoutGrid className="h-4 w-4 text-primary" />
<span className="text-sm font-medium"> </span>
</div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
2025-12-19 15:44:38 +09:00
<Select
value={String(config.columns || 12)}
onValueChange={(value) => updateConfig("columns", Number(value))}
>
<SelectTrigger className="h-8 w-[180px] text-sm">
2025-12-19 15:44:38 +09:00
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">1 </SelectItem>
<SelectItem value="2">2 </SelectItem>
<SelectItem value="3">3 </SelectItem>
<SelectItem value="4">4 </SelectItem>
<SelectItem value="6">6 </SelectItem>
<SelectItem value="12">12 </SelectItem>
2025-12-19 15:44:38 +09:00
</SelectContent>
</Select>
</div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> (px)</span>
2025-12-19 15:44:38 +09:00
<Input
value={config.gap || "16"}
onChange={(e) => updateConfig("gap", e.target.value)}
placeholder="16"
className="h-8 w-[180px] text-sm"
2025-12-19 15:44:38 +09:00
/>
</div>
</div>
<div className="flex items-center justify-between py-1">
<div>
<p className="text-sm">12 </p>
<p className="text-[11px] text-muted-foreground">
12
</p>
</div>
<Switch
checked={config.use12Column !== false}
onCheckedChange={(checked) => updateConfig("use12Column", checked)}
/>
</div>
2025-12-19 15:44:38 +09:00
</div>
)}
{/* 분할 패널 타입 설정 */}
{isSplitType && (
<div className="space-y-3">
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
<div className="flex items-center gap-2">
<PanelLeftClose className="h-4 w-4 text-primary" />
<span className="text-sm font-medium"> </span>
</div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
<Select
value={config.direction || "horizontal"}
onValueChange={(value) => updateConfig("direction", value)}
>
<SelectTrigger className="h-8 w-[180px] text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="horizontal"></SelectItem>
<SelectItem value="vertical"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex gap-2">
<div className="flex-1">
<span className="text-xs text-muted-foreground"> (%)</span>
<Input
type="number"
value={config.splitRatio?.[0] || 50}
onChange={(e) => updateConfig("splitRatio", [Number(e.target.value), 100 - Number(e.target.value)])}
placeholder="50"
min="10"
max="90"
className="mt-1 h-8 text-sm"
/>
</div>
<div className="flex-1">
<span className="text-xs text-muted-foreground"></span>
<Input
type="number"
value={config.splitRatio?.[1] || 50}
disabled
className="mt-1 h-8 text-sm bg-muted"
/>
</div>
2025-12-19 15:44:38 +09:00
</div>
</div>
<div className="flex items-center justify-between py-1">
<div>
<p className="text-sm"> </p>
<p className="text-[11px] text-muted-foreground">
</p>
</div>
<Switch
2025-12-19 15:44:38 +09:00
checked={config.resizable !== false}
onCheckedChange={(checked) => updateConfig("resizable", checked)}
/>
</div>
</div>
)}
{/* 플렉스 타입 설정 */}
{isFlexType && (
<div className="space-y-3">
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
<div className="flex items-center gap-2">
<MoveHorizontal className="h-4 w-4 text-primary" />
<span className="text-sm font-medium"> </span>
</div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"></span>
<Select
value={config.direction || "row"}
onValueChange={(value) => updateConfig("direction", value)}
>
<SelectTrigger className="h-8 w-[180px] text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="row"></SelectItem>
<SelectItem value="column"></SelectItem>
<SelectItem value="row-reverse"> ()</SelectItem>
<SelectItem value="column-reverse"> ()</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"></span>
2025-12-19 15:44:38 +09:00
<Select
value={config.justifyContent || "flex-start"}
onValueChange={(value) => updateConfig("justifyContent", value)}
>
<SelectTrigger className="h-8 w-[180px] text-sm">
2025-12-19 15:44:38 +09:00
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="flex-start"></SelectItem>
<SelectItem value="center"></SelectItem>
<SelectItem value="flex-end"></SelectItem>
<SelectItem value="space-between"> </SelectItem>
<SelectItem value="space-around"> </SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
2025-12-19 15:44:38 +09:00
<Select
value={config.alignItems || "stretch"}
onValueChange={(value) => updateConfig("alignItems", value)}
>
<SelectTrigger className="h-8 w-[180px] text-sm">
2025-12-19 15:44:38 +09:00
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="flex-start"></SelectItem>
<SelectItem value="center"></SelectItem>
<SelectItem value="flex-end"></SelectItem>
<SelectItem value="stretch"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> (px)</span>
<Input
value={config.gap || "16"}
onChange={(e) => updateConfig("gap", e.target.value)}
placeholder="16"
className="h-8 w-[180px] text-sm"
/>
</div>
2025-12-19 15:44:38 +09:00
</div>
<div className="flex items-center justify-between py-1">
<div>
<p className="text-sm"> </p>
<p className="text-[11px] text-muted-foreground">
</p>
</div>
<Switch
2025-12-19 15:44:38 +09:00
checked={config.wrap || false}
onCheckedChange={(checked) => updateConfig("wrap", checked)}
/>
</div>
</div>
)}
{/* 화면 임베드 타입 설정 */}
{isScreenEmbedType && (
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
<div className="flex items-center gap-2">
<MonitorPlay className="h-4 w-4 text-primary" />
<span className="text-sm font-medium"> </span>
</div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> ID</span>
<Input
type="number"
value={config.screenId || ""}
onChange={(e) => updateConfig("screenId", e.target.value ? Number(e.target.value) : undefined)}
placeholder="화면 ID 입력"
className="h-8 w-[180px] text-sm"
/>
</div>
2025-12-19 15:44:38 +09:00
</div>
)}
{/* 구분선 타입: 별도 설정 없음 - 빈 상태 표시 */}
{currentLayoutType === "divider" && (
<div className="text-center py-6 text-muted-foreground">
<Minus className="mx-auto mb-2 h-8 w-8 opacity-30" />
<p className="text-sm"> </p>
<p className="text-xs mt-0.5"> </p>
</div>
)}
{/* ─── 3단계: 고급 설정 (그리드/플렉스 타입에서만) ─── */}
{(isGridType || isFlexType || isSplitType) && (
<Collapsible open={advancedOpen} onOpenChange={setAdvancedOpen}>
<CollapsibleTrigger asChild>
<button
type="button"
className="flex w-full items-center justify-between rounded-lg border bg-muted/30 px-4 py-2.5 text-left transition-colors hover:bg-muted/50"
>
<div className="flex items-center gap-2">
<Settings className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium"> </span>
</div>
<ChevronDown
className={cn(
"h-4 w-4 text-muted-foreground transition-transform duration-200",
advancedOpen && "rotate-180",
)}
/>
</button>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="rounded-b-lg border border-t-0 p-4 space-y-3">
{isGridType && (
<div className="flex items-center justify-between py-1">
<div>
<p className="text-sm"> </p>
<p className="text-[11px] text-muted-foreground">
</p>
</div>
<Switch
checked={config.responsive !== false}
onCheckedChange={(checked) => updateConfig("responsive", checked)}
/>
</div>
)}
{isFlexType && (
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
<Input
value={config.minItemWidth || ""}
onChange={(e) => updateConfig("minItemWidth", e.target.value)}
placeholder="자동"
className="h-8 w-[180px] text-sm"
/>
</div>
)}
{isSplitType && (
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> (px)</span>
<Input
type="number"
value={config.minPanelSize || ""}
onChange={(e) => updateConfig("minPanelSize", e.target.value ? Number(e.target.value) : undefined)}
placeholder="자동"
className="h-8 w-[180px] text-sm"
/>
</div>
)}
</div>
</CollapsibleContent>
</Collapsible>
)}
2025-12-19 15:44:38 +09:00
</div>
);
};
V2LayoutConfigPanel.displayName = "V2LayoutConfigPanel";
2025-12-19 15:44:38 +09:00
export default V2LayoutConfigPanel;