"use client";
/**
* V2TableSearchWidget 설정 패널
* 토스식 단계별 UX: 대상 패널 카드 선택 -> 필터 모드 카드 선택 -> 고정 필터 목록 -> 고급 설정(접힘)
*/
import React, { useState, useEffect, useCallback } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
PanelLeft,
PanelRight,
Layers,
Zap,
Lock,
Plus,
Trash2,
Settings,
ChevronDown,
Search,
Filter,
} from "lucide-react";
import { cn } from "@/lib/utils";
// ─── 대상 패널 위치 카드 정의 ───
const PANEL_POSITION_CARDS = [
{
value: "left",
icon: PanelLeft,
title: "좌측 패널",
description: "카드 디스플레이 등",
},
{
value: "right",
icon: PanelRight,
title: "우측 패널",
description: "테이블 리스트 등",
},
{
value: "auto",
icon: Layers,
title: "자동",
description: "모든 테이블 대상",
},
] as const;
// ─── 필터 모드 카드 정의 ───
const FILTER_MODE_CARDS = [
{
value: "dynamic",
icon: Zap,
title: "동적 모드",
description: "사용자가 직접 필터를 선택해요",
},
{
value: "preset",
icon: Lock,
title: "고정 모드",
description: "디자이너가 미리 필터를 지정해요",
},
] as const;
// ─── 필터 타입 옵션 ───
const FILTER_TYPE_OPTIONS = [
{ value: "text", label: "텍스트" },
{ value: "number", label: "숫자" },
{ value: "date", label: "날짜" },
{ value: "select", label: "선택" },
] as const;
interface PresetFilter {
id: string;
columnName: string;
columnLabel: string;
filterType: "text" | "number" | "date" | "select";
width?: number;
multiSelect?: boolean;
}
// ─── 수평 Switch Row (토스 패턴) ───
function SwitchRow({
label,
description,
checked,
onCheckedChange,
}: {
label: string;
description?: string;
checked: boolean;
onCheckedChange: (checked: boolean) => void;
}) {
return (
{label}
{description && (
{description}
)}
);
}
// ─── 섹션 헤더 컴포넌트 ───
function SectionHeader({
icon: Icon,
title,
description,
}: {
icon: React.ComponentType<{ className?: string }>;
title: string;
description?: string;
}) {
return (
{title}
{description && (
{description}
)}
);
}
// ─── inputType에서 filterType 추출 헬퍼 ───
function getFilterTypeFromInputType(
inputType: string
): "text" | "number" | "date" | "select" {
if (
inputType.includes("number") ||
inputType.includes("decimal") ||
inputType.includes("integer")
) {
return "number";
}
if (inputType.includes("date") || inputType.includes("time")) {
return "date";
}
if (
inputType.includes("select") ||
inputType.includes("dropdown") ||
inputType.includes("code") ||
inputType.includes("category")
) {
return "select";
}
return "text";
}
interface V2TableSearchWidgetConfigPanelProps {
config: Record;
onChange: (config: Record) => void;
tables?: any[];
}
export const V2TableSearchWidgetConfigPanel: React.FC<
V2TableSearchWidgetConfigPanelProps
> = ({ config: configProp, onChange, tables = [] }) => {
const config = configProp || {};
// componentConfigChanged 이벤트 발행 래퍼
const handleChange = useCallback(
(newConfig: Record) => {
onChange(newConfig);
if (typeof window !== "undefined") {
window.dispatchEvent(
new CustomEvent("componentConfigChanged", {
detail: { config: newConfig },
})
);
}
},
[onChange]
);
// key-value 형태 업데이트 헬퍼
const updateField = useCallback(
(key: string, value: any) => {
handleChange({ ...config, [key]: value });
},
[handleChange, config]
);
// 첫 번째 테이블의 컬럼 목록
const availableColumns =
tables.length > 0 && tables[0].columns ? tables[0].columns : [];
// ─── 로컬 상태 ───
const [advancedOpen, setAdvancedOpen] = useState(false);
const [localPresetFilters, setLocalPresetFilters] = useState(
config.presetFilters ?? []
);
// config 외부 변경 시 로컬 상태 동기화
useEffect(() => {
setLocalPresetFilters(config.presetFilters ?? []);
}, [config.presetFilters]);
// 현재 config 값들
const targetPanelPosition = config.targetPanelPosition ?? "left";
const filterMode = config.filterMode ?? "dynamic";
const autoSelectFirstTable = config.autoSelectFirstTable ?? true;
const showTableSelector = config.showTableSelector ?? true;
// ─── 고정 필터 CRUD ───
const addFilter = useCallback(() => {
const newFilter: PresetFilter = {
id: `filter_${Date.now()}`,
columnName: "",
columnLabel: "",
filterType: "text",
width: 200,
};
const updated = [...localPresetFilters, newFilter];
setLocalPresetFilters(updated);
handleChange({ ...config, presetFilters: updated });
}, [localPresetFilters, handleChange, config]);
const removeFilter = useCallback(
(id: string) => {
const updated = localPresetFilters.filter((f) => f.id !== id);
setLocalPresetFilters(updated);
handleChange({ ...config, presetFilters: updated });
},
[localPresetFilters, handleChange, config]
);
const updateFilter = useCallback(
(id: string, field: keyof PresetFilter, value: any) => {
const updated = localPresetFilters.map((f) =>
f.id === id ? { ...f, [field]: value } : f
);
setLocalPresetFilters(updated);
handleChange({ ...config, presetFilters: updated });
},
[localPresetFilters, handleChange, config]
);
// 컬럼 선택 시 라벨+타입 자동 설정
const handleColumnSelect = useCallback(
(filterId: string, columnName: string) => {
const selectedColumn = availableColumns.find(
(col: any) => col.columnName === columnName
);
const updated = localPresetFilters.map((f) =>
f.id === filterId
? {
...f,
columnName,
columnLabel: selectedColumn?.columnLabel || columnName,
filterType: getFilterTypeFromInputType(
selectedColumn?.inputType || "text"
),
}
: f
);
setLocalPresetFilters(updated);
handleChange({ ...config, presetFilters: updated });
},
[availableColumns, localPresetFilters, handleChange, config]
);
return (
{/* ─── 1단계: 대상 패널 위치 선택 ─── */}
어떤 패널의 테이블을 대상으로 하나요?
{PANEL_POSITION_CARDS.map((card) => {
const Icon = card.icon;
const isSelected = targetPanelPosition === card.value;
return (
);
})}
{/* ─── 2단계: 필터 모드 선택 ─── */}
필터를 어떻게 구성할까요?
{FILTER_MODE_CARDS.map((card) => {
const Icon = card.icon;
const isSelected = filterMode === card.value;
return (
);
})}
{/* ─── 3단계: 고정 모드 필터 목록 ─── */}
{filterMode === "preset" && (
{localPresetFilters.length === 0 ? (
아직 필터가 없어요
위의 추가 버튼으로 필터를 만들어보세요
) : (
{localPresetFilters.map((filter) => (
{/* 상단: 컬럼 선택 + 삭제 */}
{availableColumns.length > 0 ? (
) : (
updateFilter(
filter.id,
"columnName",
e.target.value
)
}
placeholder="예: customer_name"
className="h-7 text-xs"
/>
)}
{/* 하단: 필터 타입 + 너비 */}
{/* 표시명 (컬럼 선택 시 자동 설정, 수동 변경 가능) */}
{filter.columnLabel && (
표시명: {filter.columnLabel}
)}
))}
)}
고정 모드에서는 설정 버튼이 숨겨지고 지정된 필터만 표시돼요
)}
{/* 동적 모드 안내 */}
{filterMode === "dynamic" && (
동적 모드 안내
사용자가 테이블 설정 버튼을 클릭하여 원하는 필터를 직접 선택할 수
있어요. 필터 설정은 브라우저에 저장되어 다음 접속 시에도 유지돼요.
)}
{/* ─── 4단계: 고급 설정 (기본 접혀있음) ─── */}
updateField("autoSelectFirstTable", checked)
}
/>
updateField("showTableSelector", checked)
}
/>
);
};
V2TableSearchWidgetConfigPanel.displayName = "V2TableSearchWidgetConfigPanel";
export default V2TableSearchWidgetConfigPanel;