feat: 검색 필터 위젯 화면별 독립 설정 및 고정 모드 추가
- 검색 필터 설정을 화면별로 독립적으로 저장하도록 개선 (screenId 포함) - FilterPanel, TableSearchWidget, TableListComponent에 화면 ID 기반 localStorage 키 적용 - 동적 모드(사용자 설정)와 고정 모드(디자이너 설정) 2가지 필터 방식 추가 - 고정 모드에서 컬럼 드롭다운 선택 기능 구현 - 컬럼 선택 시 라벨 및 필터 타입 자동 설정 - ConfigPanel 표시 문제 해결 (type='component' 지원) - UnifiedPropertiesPanel에서 독립 컴포넌트 ConfigPanel 조회 개선 주요 변경: - 같은 테이블을 사용하는 다른 화면에서 필터 설정이 독립적으로 관리됨 - 고정 모드에서는 설정 버튼이 숨겨지고 지정된 필터만 표시 - 테이블 정보가 있으면 컬럼을 드롭다운으로 선택 가능 - inputType에 따라 filterType 자동 추론 (number, date, select, text)
This commit is contained in:
parent
45ac397417
commit
e2cc09b2d6
|
|
@ -286,7 +286,8 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
|||
const componentId =
|
||||
selectedComponent.componentType || // ⭐ section-card 등
|
||||
selectedComponent.componentConfig?.type ||
|
||||
selectedComponent.componentConfig?.id;
|
||||
selectedComponent.componentConfig?.id ||
|
||||
(selectedComponent.type === "component" ? selectedComponent.id : null); // 🆕 독립 컴포넌트 (table-search-widget 등)
|
||||
|
||||
if (componentId) {
|
||||
const definition = ComponentRegistry.getComponent(componentId);
|
||||
|
|
@ -318,7 +319,11 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
|||
<Settings className="h-4 w-4 text-primary" />
|
||||
<h3 className="text-sm font-semibold">{definition.name} 설정</h3>
|
||||
</div>
|
||||
<ConfigPanelComponent config={config} onChange={handleConfigChange} />
|
||||
<ConfigPanelComponent
|
||||
config={config}
|
||||
onChange={handleConfigChange}
|
||||
tables={tables} // 테이블 정보 전달
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -994,6 +999,16 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
|||
);
|
||||
}
|
||||
|
||||
// 🆕 ComponentRegistry에서 전용 ConfigPanel이 있는지 먼저 확인
|
||||
const definition = ComponentRegistry.getComponent(componentId);
|
||||
if (definition?.configPanel) {
|
||||
// 전용 ConfigPanel이 있으면 renderComponentConfigPanel 호출
|
||||
const configPanelContent = renderComponentConfigPanel();
|
||||
if (configPanelContent) {
|
||||
return configPanelContent;
|
||||
}
|
||||
}
|
||||
|
||||
// 현재 웹타입의 기본 입력 타입 추출
|
||||
const currentBaseInputType = webType ? getBaseInputType(webType as any) : null;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ interface Props {
|
|||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onFiltersApplied?: (filters: TableFilter[]) => void; // 필터 적용 시 콜백
|
||||
screenId?: number; // 화면 ID 추가
|
||||
}
|
||||
|
||||
// 필터 타입별 연산자
|
||||
|
|
@ -69,7 +70,7 @@ interface ColumnFilterConfig {
|
|||
selectOptions?: Array<{ label: string; value: string }>;
|
||||
}
|
||||
|
||||
export const FilterPanel: React.FC<Props> = ({ isOpen, onClose, onFiltersApplied }) => {
|
||||
export const FilterPanel: React.FC<Props> = ({ isOpen, onClose, onFiltersApplied, screenId }) => {
|
||||
const { getTable, selectedTableId } = useTableOptions();
|
||||
const table = selectedTableId ? getTable(selectedTableId) : undefined;
|
||||
|
||||
|
|
@ -79,7 +80,10 @@ export const FilterPanel: React.FC<Props> = ({ isOpen, onClose, onFiltersApplied
|
|||
// localStorage에서 저장된 필터 설정 불러오기
|
||||
useEffect(() => {
|
||||
if (table?.columns && table?.tableName) {
|
||||
const storageKey = `table_filters_${table.tableName}`;
|
||||
// 화면별로 독립적인 필터 설정 저장
|
||||
const storageKey = screenId
|
||||
? `table_filters_${table.tableName}_screen_${screenId}`
|
||||
: `table_filters_${table.tableName}`;
|
||||
const savedFilters = localStorage.getItem(storageKey);
|
||||
|
||||
let filters: ColumnFilterConfig[];
|
||||
|
|
@ -192,9 +196,11 @@ export const FilterPanel: React.FC<Props> = ({ isOpen, onClose, onFiltersApplied
|
|||
width: cf.width || 200, // 너비 포함 (기본 200px)
|
||||
}));
|
||||
|
||||
// localStorage에 저장
|
||||
// localStorage에 저장 (화면별로 독립적)
|
||||
if (table?.tableName) {
|
||||
const storageKey = `table_filters_${table.tableName}`;
|
||||
const storageKey = screenId
|
||||
? `table_filters_${table.tableName}_screen_${screenId}`
|
||||
: `table_filters_${table.tableName}`;
|
||||
localStorage.setItem(storageKey, JSON.stringify(columnFilters));
|
||||
}
|
||||
|
||||
|
|
@ -216,9 +222,11 @@ export const FilterPanel: React.FC<Props> = ({ isOpen, onClose, onFiltersApplied
|
|||
setColumnFilters(clearedFilters);
|
||||
setSelectAll(false);
|
||||
|
||||
// localStorage에서 제거
|
||||
// localStorage에서 제거 (화면별로 독립적)
|
||||
if (table?.tableName) {
|
||||
const storageKey = `table_filters_${table.tableName}`;
|
||||
const storageKey = screenId
|
||||
? `table_filters_${table.tableName}_screen_${screenId}`
|
||||
: `table_filters_${table.tableName}`;
|
||||
localStorage.removeItem(storageKey);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ export interface TableListComponentProps {
|
|||
tableName?: string;
|
||||
onRefresh?: () => void;
|
||||
onClose?: () => void;
|
||||
screenId?: string;
|
||||
screenId?: number | string; // 화면 ID (필터 설정 저장용)
|
||||
userId?: string; // 사용자 ID (컬럼 순서 저장용)
|
||||
onSelectedRowsChange?: (
|
||||
selectedRows: any[],
|
||||
|
|
@ -183,6 +183,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
refreshKey,
|
||||
tableName,
|
||||
userId,
|
||||
screenId, // 화면 ID 추출
|
||||
}) => {
|
||||
// ========================================
|
||||
// 설정 및 스타일
|
||||
|
|
@ -1535,17 +1536,21 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
// useEffect 훅
|
||||
// ========================================
|
||||
|
||||
// 필터 설정 localStorage 키 생성
|
||||
// 필터 설정 localStorage 키 생성 (화면별로 독립적)
|
||||
const filterSettingKey = useMemo(() => {
|
||||
if (!tableConfig.selectedTable) return null;
|
||||
return `tableList_filterSettings_${tableConfig.selectedTable}`;
|
||||
}, [tableConfig.selectedTable]);
|
||||
return screenId
|
||||
? `tableList_filterSettings_${tableConfig.selectedTable}_screen_${screenId}`
|
||||
: `tableList_filterSettings_${tableConfig.selectedTable}`;
|
||||
}, [tableConfig.selectedTable, screenId]);
|
||||
|
||||
// 그룹 설정 localStorage 키 생성
|
||||
// 그룹 설정 localStorage 키 생성 (화면별로 독립적)
|
||||
const groupSettingKey = useMemo(() => {
|
||||
if (!tableConfig.selectedTable) return null;
|
||||
return `tableList_groupSettings_${tableConfig.selectedTable}`;
|
||||
}, [tableConfig.selectedTable]);
|
||||
return screenId
|
||||
? `tableList_groupSettings_${tableConfig.selectedTable}_screen_${screenId}`
|
||||
: `tableList_groupSettings_${tableConfig.selectedTable}`;
|
||||
}, [tableConfig.selectedTable, screenId]);
|
||||
|
||||
// 저장된 필터 설정 불러오기
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,14 @@ import { GroupingPanel } from "@/components/screen/table-options/GroupingPanel";
|
|||
import { TableFilter } from "@/types/table-options";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
|
||||
interface PresetFilter {
|
||||
id: string;
|
||||
columnName: string;
|
||||
columnLabel: string;
|
||||
filterType: "text" | "number" | "date" | "select";
|
||||
width?: number;
|
||||
}
|
||||
|
||||
interface TableSearchWidgetProps {
|
||||
component: {
|
||||
id: string;
|
||||
|
|
@ -25,6 +33,8 @@ interface TableSearchWidgetProps {
|
|||
componentConfig?: {
|
||||
autoSelectFirstTable?: boolean; // 첫 번째 테이블 자동 선택 여부
|
||||
showTableSelector?: boolean; // 테이블 선택 드롭다운 표시 여부
|
||||
filterMode?: "dynamic" | "preset"; // 필터 모드
|
||||
presetFilters?: PresetFilter[]; // 고정 필터 목록
|
||||
};
|
||||
};
|
||||
screenId?: number; // 화면 ID
|
||||
|
|
@ -63,6 +73,8 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
|
||||
const autoSelectFirstTable = component.componentConfig?.autoSelectFirstTable ?? true;
|
||||
const showTableSelector = component.componentConfig?.showTableSelector ?? true;
|
||||
const filterMode = component.componentConfig?.filterMode ?? "dynamic";
|
||||
const presetFilters = component.componentConfig?.presetFilters ?? [];
|
||||
|
||||
// Map을 배열로 변환
|
||||
const tableList = Array.from(registeredTables.values());
|
||||
|
|
@ -77,41 +89,58 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
}
|
||||
}, [registeredTables, selectedTableId, autoSelectFirstTable, setSelectedTableId]);
|
||||
|
||||
// 현재 테이블의 저장된 필터 불러오기
|
||||
// 현재 테이블의 저장된 필터 불러오기 (동적 모드) 또는 고정 필터 적용 (고정 모드)
|
||||
useEffect(() => {
|
||||
if (currentTable?.tableName) {
|
||||
const storageKey = `table_filters_${currentTable.tableName}`;
|
||||
const savedFilters = localStorage.getItem(storageKey);
|
||||
if (!currentTable?.tableName) return;
|
||||
|
||||
if (savedFilters) {
|
||||
try {
|
||||
const parsed = JSON.parse(savedFilters) as Array<{
|
||||
columnName: string;
|
||||
columnLabel: string;
|
||||
inputType: string;
|
||||
enabled: boolean;
|
||||
filterType: "text" | "number" | "date" | "select";
|
||||
width?: number;
|
||||
}>;
|
||||
// 고정 모드: presetFilters를 activeFilters로 설정
|
||||
if (filterMode === "preset") {
|
||||
const activeFiltersList: TableFilter[] = presetFilters.map((f) => ({
|
||||
columnName: f.columnName,
|
||||
operator: "contains",
|
||||
value: "",
|
||||
filterType: f.filterType,
|
||||
width: f.width || 200,
|
||||
}));
|
||||
setActiveFilters(activeFiltersList);
|
||||
return;
|
||||
}
|
||||
|
||||
// enabled된 필터들만 activeFilters로 설정
|
||||
const activeFiltersList: TableFilter[] = parsed
|
||||
.filter((f) => f.enabled)
|
||||
.map((f) => ({
|
||||
columnName: f.columnName,
|
||||
operator: "contains",
|
||||
value: "",
|
||||
filterType: f.filterType,
|
||||
width: f.width || 200, // 저장된 너비 포함
|
||||
}));
|
||||
// 동적 모드: 화면별로 독립적인 필터 설정 불러오기
|
||||
const storageKey = screenId
|
||||
? `table_filters_${currentTable.tableName}_screen_${screenId}`
|
||||
: `table_filters_${currentTable.tableName}`;
|
||||
const savedFilters = localStorage.getItem(storageKey);
|
||||
|
||||
setActiveFilters(activeFiltersList);
|
||||
} catch (error) {
|
||||
console.error("저장된 필터 불러오기 실패:", error);
|
||||
}
|
||||
if (savedFilters) {
|
||||
try {
|
||||
const parsed = JSON.parse(savedFilters) as Array<{
|
||||
columnName: string;
|
||||
columnLabel: string;
|
||||
inputType: string;
|
||||
enabled: boolean;
|
||||
filterType: "text" | "number" | "date" | "select";
|
||||
width?: number;
|
||||
}>;
|
||||
|
||||
// enabled된 필터들만 activeFilters로 설정
|
||||
const activeFiltersList: TableFilter[] = parsed
|
||||
.filter((f) => f.enabled)
|
||||
.map((f) => ({
|
||||
columnName: f.columnName,
|
||||
operator: "contains",
|
||||
value: "",
|
||||
filterType: f.filterType,
|
||||
width: f.width || 200, // 저장된 너비 포함
|
||||
}));
|
||||
|
||||
setActiveFilters(activeFiltersList);
|
||||
} catch (error) {
|
||||
console.error("저장된 필터 불러오기 실패:", error);
|
||||
}
|
||||
}
|
||||
}, [currentTable?.tableName]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentTable?.tableName, filterMode, screenId, JSON.stringify(presetFilters)]);
|
||||
|
||||
// select 옵션 초기 로드 (한 번만 실행, 이후 유지)
|
||||
useEffect(() => {
|
||||
|
|
@ -362,7 +391,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
{/* 필터가 없을 때는 빈 공간 */}
|
||||
{activeFilters.length === 0 && <div className="flex-1" />}
|
||||
|
||||
{/* 오른쪽: 데이터 건수 + 설정 버튼들 */}
|
||||
{/* 오른쪽: 데이터 건수 + 설정 버튼들 (고정 모드에서는 숨김) */}
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
{/* 데이터 건수 표시 */}
|
||||
{currentTable?.dataCount !== undefined && (
|
||||
|
|
@ -371,38 +400,43 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
</div>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setColumnVisibilityOpen(true)}
|
||||
disabled={!selectedTableId}
|
||||
className="h-8 text-xs sm:h-9 sm:text-sm"
|
||||
>
|
||||
<Settings className="mr-1 h-3 w-3 sm:h-4 sm:w-4" />
|
||||
테이블 옵션
|
||||
</Button>
|
||||
{/* 동적 모드일 때만 설정 버튼들 표시 */}
|
||||
{filterMode === "dynamic" && (
|
||||
<>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setColumnVisibilityOpen(true)}
|
||||
disabled={!selectedTableId}
|
||||
className="h-8 text-xs sm:h-9 sm:text-sm"
|
||||
>
|
||||
<Settings className="mr-1 h-3 w-3 sm:h-4 sm:w-4" />
|
||||
테이블 옵션
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setFilterOpen(true)}
|
||||
disabled={!selectedTableId}
|
||||
className="h-8 text-xs sm:h-9 sm:text-sm"
|
||||
>
|
||||
<Filter className="mr-1 h-3 w-3 sm:h-4 sm:w-4" />
|
||||
필터 설정
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setFilterOpen(true)}
|
||||
disabled={!selectedTableId}
|
||||
className="h-8 text-xs sm:h-9 sm:text-sm"
|
||||
>
|
||||
<Filter className="mr-1 h-3 w-3 sm:h-4 sm:w-4" />
|
||||
필터 설정
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setGroupingOpen(true)}
|
||||
disabled={!selectedTableId}
|
||||
className="h-8 text-xs sm:h-9 sm:text-sm"
|
||||
>
|
||||
<Layers className="mr-1 h-3 w-3 sm:h-4 sm:w-4" />
|
||||
그룹 설정
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setGroupingOpen(true)}
|
||||
disabled={!selectedTableId}
|
||||
className="h-8 text-xs sm:h-9 sm:text-sm"
|
||||
>
|
||||
<Layers className="mr-1 h-3 w-3 sm:h-4 sm:w-4" />
|
||||
그룹 설정
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 패널들 */}
|
||||
|
|
@ -411,6 +445,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
|||
isOpen={filterOpen}
|
||||
onClose={() => setFilterOpen(false)}
|
||||
onFiltersApplied={(filters) => setActiveFilters(filters)}
|
||||
screenId={screenId}
|
||||
/>
|
||||
<GroupingPanel isOpen={groupingOpen} onClose={() => setGroupingOpen(false)} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,27 +3,126 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Plus, X } from "lucide-react";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
|
||||
interface TableSearchWidgetConfigPanelProps {
|
||||
component: any;
|
||||
onUpdateProperty: (property: string, value: any) => void;
|
||||
component?: any; // 레거시 지원
|
||||
config?: any; // 새 인터페이스
|
||||
onUpdateProperty?: (property: string, value: any) => void; // 레거시 지원
|
||||
onChange?: (newConfig: any) => void; // 새 인터페이스
|
||||
tables?: any[]; // 화면의 테이블 정보
|
||||
}
|
||||
|
||||
interface PresetFilter {
|
||||
id: string;
|
||||
columnName: string;
|
||||
columnLabel: string;
|
||||
filterType: "text" | "number" | "date" | "select";
|
||||
width?: number;
|
||||
}
|
||||
|
||||
export function TableSearchWidgetConfigPanel({
|
||||
component,
|
||||
config,
|
||||
onUpdateProperty,
|
||||
onChange,
|
||||
tables = [],
|
||||
}: TableSearchWidgetConfigPanelProps) {
|
||||
// 레거시와 새 인터페이스 모두 지원
|
||||
const currentConfig = config || component?.componentConfig || {};
|
||||
const updateConfig = onChange || ((key: string, value: any) => {
|
||||
if (onUpdateProperty) {
|
||||
onUpdateProperty(`componentConfig.${key}`, value);
|
||||
}
|
||||
});
|
||||
|
||||
// 첫 번째 테이블의 컬럼 목록 가져오기
|
||||
const availableColumns = tables.length > 0 && tables[0].columns ? tables[0].columns : [];
|
||||
|
||||
// inputType에서 filterType 추출 헬퍼 함수
|
||||
const 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";
|
||||
};
|
||||
|
||||
const [localAutoSelect, setLocalAutoSelect] = useState(
|
||||
component.componentConfig?.autoSelectFirstTable ?? true
|
||||
currentConfig.autoSelectFirstTable ?? true
|
||||
);
|
||||
const [localShowSelector, setLocalShowSelector] = useState(
|
||||
component.componentConfig?.showTableSelector ?? true
|
||||
currentConfig.showTableSelector ?? true
|
||||
);
|
||||
const [localFilterMode, setLocalFilterMode] = useState<"dynamic" | "preset">(
|
||||
currentConfig.filterMode ?? "dynamic"
|
||||
);
|
||||
const [localPresetFilters, setLocalPresetFilters] = useState<PresetFilter[]>(
|
||||
currentConfig.presetFilters ?? []
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setLocalAutoSelect(component.componentConfig?.autoSelectFirstTable ?? true);
|
||||
setLocalShowSelector(component.componentConfig?.showTableSelector ?? true);
|
||||
}, [component.componentConfig]);
|
||||
setLocalAutoSelect(currentConfig.autoSelectFirstTable ?? true);
|
||||
setLocalShowSelector(currentConfig.showTableSelector ?? true);
|
||||
setLocalFilterMode(currentConfig.filterMode ?? "dynamic");
|
||||
setLocalPresetFilters(currentConfig.presetFilters ?? []);
|
||||
}, [currentConfig]);
|
||||
|
||||
// 설정 업데이트 헬퍼
|
||||
const handleUpdate = (key: string, value: any) => {
|
||||
if (onChange) {
|
||||
// 새 인터페이스: 전체 config 업데이트
|
||||
onChange({ ...currentConfig, [key]: value });
|
||||
} else if (onUpdateProperty) {
|
||||
// 레거시: 개별 속성 업데이트
|
||||
onUpdateProperty(`componentConfig.${key}`, value);
|
||||
}
|
||||
};
|
||||
|
||||
// 필터 추가
|
||||
const addFilter = () => {
|
||||
const newFilter: PresetFilter = {
|
||||
id: `filter_${Date.now()}`,
|
||||
columnName: "",
|
||||
columnLabel: "",
|
||||
filterType: "text",
|
||||
width: 200,
|
||||
};
|
||||
const updatedFilters = [...localPresetFilters, newFilter];
|
||||
setLocalPresetFilters(updatedFilters);
|
||||
handleUpdate("presetFilters", updatedFilters);
|
||||
};
|
||||
|
||||
// 필터 삭제
|
||||
const removeFilter = (id: string) => {
|
||||
const updatedFilters = localPresetFilters.filter((f) => f.id !== id);
|
||||
setLocalPresetFilters(updatedFilters);
|
||||
handleUpdate("presetFilters", updatedFilters);
|
||||
};
|
||||
|
||||
// 필터 업데이트
|
||||
const updateFilter = (id: string, field: keyof PresetFilter, value: any) => {
|
||||
const updatedFilters = localPresetFilters.map((f) =>
|
||||
f.id === id ? { ...f, [field]: value } : f
|
||||
);
|
||||
setLocalPresetFilters(updatedFilters);
|
||||
handleUpdate("presetFilters", updatedFilters);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
|
|
@ -41,7 +140,7 @@ export function TableSearchWidgetConfigPanel({
|
|||
checked={localAutoSelect}
|
||||
onCheckedChange={(checked) => {
|
||||
setLocalAutoSelect(checked as boolean);
|
||||
onUpdateProperty("componentConfig.autoSelectFirstTable", checked);
|
||||
handleUpdate("autoSelectFirstTable", checked);
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="autoSelectFirstTable" className="text-xs sm:text-sm cursor-pointer">
|
||||
|
|
@ -56,7 +155,7 @@ export function TableSearchWidgetConfigPanel({
|
|||
checked={localShowSelector}
|
||||
onCheckedChange={(checked) => {
|
||||
setLocalShowSelector(checked as boolean);
|
||||
onUpdateProperty("componentConfig.showTableSelector", checked);
|
||||
handleUpdate("showTableSelector", checked);
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="showTableSelector" className="text-xs sm:text-sm cursor-pointer">
|
||||
|
|
@ -64,12 +163,178 @@ export function TableSearchWidgetConfigPanel({
|
|||
</Label>
|
||||
</div>
|
||||
|
||||
{/* 필터 모드 선택 */}
|
||||
<div className="space-y-2 border-t pt-4">
|
||||
<Label className="text-xs sm:text-sm font-medium">필터 모드</Label>
|
||||
<RadioGroup
|
||||
value={localFilterMode}
|
||||
onValueChange={(value: "dynamic" | "preset") => {
|
||||
setLocalFilterMode(value);
|
||||
handleUpdate("filterMode", value);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="dynamic" id="mode-dynamic" />
|
||||
<Label htmlFor="mode-dynamic" className="text-xs sm:text-sm cursor-pointer font-normal">
|
||||
동적 모드 (사용자가 필터 설정 버튼으로 선택)
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="preset" id="mode-preset" />
|
||||
<Label htmlFor="mode-preset" className="text-xs sm:text-sm cursor-pointer font-normal">
|
||||
고정 모드 (디자이너가 미리 필터 지정)
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
{/* 고정 모드일 때만 필터 설정 UI 표시 */}
|
||||
{localFilterMode === "preset" && (
|
||||
<div className="space-y-3 border-t pt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs sm:text-sm font-medium">고정 필터 목록</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={addFilter}
|
||||
className="h-7 text-xs"
|
||||
>
|
||||
<Plus className="mr-1 h-3 w-3" />
|
||||
필터 추가
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{localPresetFilters.length === 0 ? (
|
||||
<div className="rounded-md bg-muted p-3 text-center text-xs text-muted-foreground">
|
||||
필터가 없습니다. 필터 추가 버튼을 클릭하세요.
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{localPresetFilters.map((filter) => (
|
||||
<div
|
||||
key={filter.id}
|
||||
className="rounded-md border bg-card p-3 space-y-2"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs font-medium">필터 설정</Label>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => removeFilter(filter.id)}
|
||||
className="h-6 w-6 p-0"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 컬럼 선택 */}
|
||||
<div>
|
||||
<Label className="text-[10px] sm:text-xs mb-1">컬럼 선택</Label>
|
||||
{availableColumns.length > 0 ? (
|
||||
<Select
|
||||
value={filter.columnName}
|
||||
onValueChange={(value) => {
|
||||
// 선택된 컬럼 정보 가져오기
|
||||
const selectedColumn = availableColumns.find(
|
||||
(col: any) => col.columnName === value
|
||||
);
|
||||
// 컬럼명과 라벨 동시 업데이트
|
||||
const updatedFilters = localPresetFilters.map((f) =>
|
||||
f.id === filter.id
|
||||
? {
|
||||
...f,
|
||||
columnName: value,
|
||||
columnLabel: selectedColumn?.columnLabel || value,
|
||||
filterType: getFilterTypeFromInputType(selectedColumn?.inputType || "text"),
|
||||
}
|
||||
: f
|
||||
);
|
||||
setLocalPresetFilters(updatedFilters);
|
||||
handleUpdate("presetFilters", updatedFilters);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-7 text-xs">
|
||||
<SelectValue placeholder="컬럼 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availableColumns.map((col: any) => (
|
||||
<SelectItem key={col.columnName} value={col.columnName}>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">{col.columnLabel}</span>
|
||||
<span className="text-muted-foreground text-[10px]">
|
||||
({col.columnName})
|
||||
</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
) : (
|
||||
<Input
|
||||
value={filter.columnName}
|
||||
onChange={(e) => updateFilter(filter.id, "columnName", e.target.value)}
|
||||
placeholder="예: customer_name"
|
||||
className="h-7 text-xs"
|
||||
/>
|
||||
)}
|
||||
{filter.columnLabel && (
|
||||
<p className="text-muted-foreground mt-1 text-[10px]">
|
||||
표시명: {filter.columnLabel}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 필터 타입 */}
|
||||
<div>
|
||||
<Label className="text-[10px] sm:text-xs mb-1">필터 타입</Label>
|
||||
<Select
|
||||
value={filter.filterType}
|
||||
onValueChange={(value: "text" | "number" | "date" | "select") =>
|
||||
updateFilter(filter.id, "filterType", value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="h-7 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="text">텍스트</SelectItem>
|
||||
<SelectItem value="number">숫자</SelectItem>
|
||||
<SelectItem value="date">날짜</SelectItem>
|
||||
<SelectItem value="select">선택</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 너비 */}
|
||||
<div>
|
||||
<Label className="text-[10px] sm:text-xs mb-1">너비 (px)</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={filter.width || 200}
|
||||
onChange={(e) => updateFilter(filter.id, "width", parseInt(e.target.value))}
|
||||
placeholder="200"
|
||||
className="h-7 text-xs"
|
||||
min={100}
|
||||
max={500}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="rounded-md bg-muted p-3 text-xs">
|
||||
<p className="font-medium mb-1">참고사항:</p>
|
||||
<ul className="list-disc list-inside space-y-1 text-muted-foreground">
|
||||
<li>테이블 리스트, 분할 패널, 플로우 위젯이 자동 감지됩니다</li>
|
||||
<li>여러 테이블이 있으면 드롭다운에서 선택할 수 있습니다</li>
|
||||
<li>선택한 테이블의 컬럼 정보가 자동으로 로드됩니다</li>
|
||||
{localFilterMode === "dynamic" ? (
|
||||
<li>사용자가 필터 설정 버튼을 클릭하여 원하는 필터를 선택합니다</li>
|
||||
) : (
|
||||
<li>고정 모드에서는 설정 버튼이 숨겨지고 지정된 필터만 표시됩니다</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import React from "react";
|
|||
import { TableSearchWidget } from "./TableSearchWidget";
|
||||
|
||||
export class TableSearchWidgetRenderer {
|
||||
static render(component: any) {
|
||||
return <TableSearchWidget component={component} />;
|
||||
static render(component: any, props?: any) {
|
||||
return <TableSearchWidget component={component} screenId={props?.screenId} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue