2026-03-12 01:17:51 +09:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* V2 카테고리 관리 설정 패널 (토스식 리디자인)
|
|
|
|
|
* 토스식 단계별 UX: 뷰 모드 -> 트리 설정 -> 레이아웃(접힘)
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import React, { useState } from "react";
|
|
|
|
|
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 { Settings, ChevronDown, FolderTree } from "lucide-react";
|
|
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
|
import type { V2CategoryManagerConfig, ViewMode } from "@/lib/registry/components/v2-category-manager/types";
|
|
|
|
|
import { defaultV2CategoryManagerConfig } from "@/lib/registry/components/v2-category-manager/types";
|
|
|
|
|
|
|
|
|
|
interface V2CategoryManagerConfigPanelProps {
|
|
|
|
|
config: Partial<V2CategoryManagerConfig>;
|
|
|
|
|
onChange: (config: Partial<V2CategoryManagerConfig>) => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const V2CategoryManagerConfigPanel: React.FC<V2CategoryManagerConfigPanelProps> = ({
|
|
|
|
|
config: externalConfig,
|
|
|
|
|
onChange,
|
|
|
|
|
}) => {
|
|
|
|
|
const [layoutOpen, setLayoutOpen] = useState(false);
|
|
|
|
|
|
|
|
|
|
const config: V2CategoryManagerConfig = {
|
|
|
|
|
...defaultV2CategoryManagerConfig,
|
|
|
|
|
...externalConfig,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleChange = <K extends keyof V2CategoryManagerConfig>(key: K, value: V2CategoryManagerConfig[K]) => {
|
2026-03-12 03:41:33 +09:00
|
|
|
const newConfig = { ...config, [key]: value };
|
|
|
|
|
onChange(newConfig);
|
|
|
|
|
if (typeof window !== "undefined") {
|
|
|
|
|
window.dispatchEvent(
|
|
|
|
|
new CustomEvent("componentConfigChanged", { detail: { config: newConfig } })
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-03-12 01:17:51 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
{/* ─── 1단계: 뷰 모드 설정 ─── */}
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<FolderTree className="h-4 w-4 text-muted-foreground" />
|
|
|
|
|
<p className="text-sm font-medium">뷰 모드 설정</p>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-[11px] text-muted-foreground">카테고리 표시 방식을 설정해요</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
|
|
|
|
|
<div className="flex items-center justify-between py-1">
|
|
|
|
|
<span className="text-xs text-muted-foreground">기본 뷰 모드</span>
|
|
|
|
|
<Select
|
|
|
|
|
value={config.viewMode}
|
|
|
|
|
onValueChange={(value: ViewMode) => handleChange("viewMode", value)}
|
|
|
|
|
>
|
|
|
|
|
<SelectTrigger className="h-7 w-[120px] text-xs">
|
|
|
|
|
<SelectValue />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
<SelectItem value="tree">트리 뷰</SelectItem>
|
|
|
|
|
<SelectItem value="list">목록 뷰</SelectItem>
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
</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
|
|
|
|
|
checked={config.showViewModeToggle}
|
|
|
|
|
onCheckedChange={(checked) => handleChange("showViewModeToggle", checked)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* ─── 2단계: 트리 설정 ─── */}
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<p className="text-sm font-medium">트리 설정</p>
|
|
|
|
|
<p className="text-[11px] text-muted-foreground">트리 뷰의 기본 동작을 설정해요</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
|
|
|
|
|
<div className="flex items-center justify-between py-1">
|
|
|
|
|
<div>
|
|
|
|
|
<span className="text-xs text-muted-foreground">기본 펼침 단계</span>
|
|
|
|
|
<p className="text-[10px] text-muted-foreground mt-0.5">처음 로드 시 펼쳐지는 깊이</p>
|
|
|
|
|
</div>
|
|
|
|
|
<Select
|
|
|
|
|
value={String(config.defaultExpandLevel)}
|
|
|
|
|
onValueChange={(value) => handleChange("defaultExpandLevel", Number(value))}
|
|
|
|
|
>
|
|
|
|
|
<SelectTrigger className="h-7 w-[120px] text-xs">
|
|
|
|
|
<SelectValue />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
<SelectItem value="1">1단계 (대분류만)</SelectItem>
|
|
|
|
|
<SelectItem value="2">2단계 (중분류까지)</SelectItem>
|
|
|
|
|
<SelectItem value="3">3단계 (전체 펼침)</SelectItem>
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
</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
|
|
|
|
|
checked={config.showInactiveItems}
|
|
|
|
|
onCheckedChange={(checked) => handleChange("showInactiveItems", checked)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* ─── 3단계: 레이아웃 (Collapsible) ─── */}
|
|
|
|
|
<Collapsible open={layoutOpen} onOpenChange={setLayoutOpen}>
|
|
|
|
|
<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",
|
|
|
|
|
layoutOpen && "rotate-180"
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
</button>
|
|
|
|
|
</CollapsibleTrigger>
|
|
|
|
|
<CollapsibleContent>
|
|
|
|
|
<div className="rounded-b-lg border border-t-0 p-4 space-y-3">
|
|
|
|
|
<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.showColumnList}
|
|
|
|
|
onCheckedChange={(checked) => handleChange("showColumnList", checked)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{config.showColumnList && (
|
|
|
|
|
<div className="ml-1 border-l-2 border-primary/20 pl-3">
|
|
|
|
|
<div className="flex items-center justify-between py-1">
|
|
|
|
|
<div>
|
|
|
|
|
<span className="text-xs text-muted-foreground">좌측 패널 너비 (%)</span>
|
|
|
|
|
<p className="text-[10px] text-muted-foreground mt-0.5">10~40% 범위</p>
|
|
|
|
|
</div>
|
|
|
|
|
<Input
|
|
|
|
|
type="number"
|
|
|
|
|
min={10}
|
|
|
|
|
max={40}
|
|
|
|
|
value={config.leftPanelWidth}
|
|
|
|
|
onChange={(e) => handleChange("leftPanelWidth", Number(e.target.value))}
|
|
|
|
|
className="h-7 w-[80px] text-xs"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center justify-between py-1">
|
|
|
|
|
<div>
|
|
|
|
|
<span className="text-xs text-muted-foreground">높이</span>
|
|
|
|
|
<p className="text-[10px] text-muted-foreground mt-0.5">px 또는 % (예: 100%, 600)</p>
|
|
|
|
|
</div>
|
|
|
|
|
<Input
|
|
|
|
|
value={String(config.height)}
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
const v = e.target.value;
|
|
|
|
|
handleChange("height", isNaN(Number(v)) ? v : Number(v));
|
|
|
|
|
}}
|
|
|
|
|
placeholder="100%"
|
|
|
|
|
className="h-7 w-[100px] text-xs"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</CollapsibleContent>
|
|
|
|
|
</Collapsible>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
V2CategoryManagerConfigPanel.displayName = "V2CategoryManagerConfigPanel";
|
|
|
|
|
|
|
|
|
|
export default V2CategoryManagerConfigPanel;
|