검색필터 업그레이드

This commit is contained in:
leeheejin 2026-01-19 17:25:12 +09:00
parent faf4100056
commit d09a6977f7
1 changed files with 55 additions and 7 deletions

View File

@ -41,7 +41,7 @@ import {
Lock,
} from "lucide-react";
import * as XLSX from "xlsx";
import { FileText, ChevronRightIcon } from "lucide-react";
import { FileText, ChevronRightIcon, Search } from "lucide-react";
import { Checkbox } from "@/components/ui/checkbox";
import { cn } from "@/lib/utils";
import { toast } from "sonner";
@ -455,6 +455,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
// 🆕 컬럼 헤더 필터 상태 (상단에서 선언)
const [headerFilters, setHeaderFilters] = useState<Record<string, Set<string>>>({});
const [headerLikeFilters, setHeaderLikeFilters] = useState<Record<string, string>>({}); // LIKE 검색용
const [openFilterColumn, setOpenFilterColumn] = useState<string | null>(null);
// 🆕 Filter Builder (고급 필터) 관련 상태 - filteredData보다 먼저 정의해야 함
@ -488,6 +489,22 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
});
}
// 2-1. 🆕 LIKE 검색 필터 적용
if (Object.keys(headerLikeFilters).length > 0) {
result = result.filter((row) => {
return Object.entries(headerLikeFilters).every(([columnName, searchText]) => {
if (!searchText || searchText.trim() === "") return true;
// 여러 가능한 컬럼명 시도
const cellValue = row[columnName] ?? row[columnName.toLowerCase()] ?? row[columnName.toUpperCase()];
const cellStr = cellValue !== null && cellValue !== undefined ? String(cellValue).toLowerCase() : "";
// LIKE 검색 (대소문자 무시)
return cellStr.includes(searchText.toLowerCase());
});
});
}
// 3. 🆕 Filter Builder 적용
if (filterGroups.length > 0) {
result = result.filter((row) => {
@ -541,7 +558,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
}
return result;
}, [data, splitPanelPosition, splitPanelContext?.addedItemIds, headerFilters, filterGroups]);
}, [data, splitPanelPosition, splitPanelContext?.addedItemIds, headerFilters, headerLikeFilters, filterGroups]);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(0);
@ -2935,6 +2952,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
headerFilters: Object.fromEntries(
Object.entries(headerFilters).map(([key, set]) => [key, Array.from(set as Set<string>)]),
),
headerLikeFilters, // LIKE 검색 필터 저장
pageSize: localPageSize,
timestamp: Date.now(),
};
@ -2955,6 +2973,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
frozenColumnCount,
showGridLines,
headerFilters,
headerLikeFilters,
localPageSize,
]);
@ -2991,6 +3010,9 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
});
setHeaderFilters(filters);
}
if (state.headerLikeFilters) {
setHeaderLikeFilters(state.headerLikeFilters);
}
} catch (error) {
console.error("❌ 테이블 상태 복원 실패:", error);
}
@ -5737,7 +5759,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
}}
className={cn(
"hover:bg-primary/20 ml-1 rounded p-0.5 transition-colors",
headerFilters[column.columnName]?.size > 0 && "text-primary bg-primary/10",
(headerFilters[column.columnName]?.size > 0 || headerLikeFilters[column.columnName]) && "text-primary bg-primary/10",
)}
title="필터"
>
@ -5745,7 +5767,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
</button>
</PopoverTrigger>
<PopoverContent
className="w-48 p-2"
className="w-56 p-2"
align="start"
onClick={(e) => e.stopPropagation()}
>
@ -5754,16 +5776,42 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
<span className="text-xs font-medium">
: {columnLabels[column.columnName] || column.displayName}
</span>
{headerFilters[column.columnName]?.size > 0 && (
{(headerFilters[column.columnName]?.size > 0 || headerLikeFilters[column.columnName]) && (
<button
onClick={() => clearHeaderFilter(column.columnName)}
onClick={() => {
clearHeaderFilter(column.columnName);
setHeaderLikeFilters((prev) => {
const newFilters = { ...prev };
delete newFilters[column.columnName];
return newFilters;
});
}}
className="text-destructive text-xs hover:underline"
>
</button>
)}
</div>
<div className="max-h-48 space-y-1 overflow-y-auto">
{/* LIKE 검색 입력 필드 */}
<div className="relative">
<Search className="text-muted-foreground absolute left-2 top-1/2 h-3 w-3 -translate-y-1/2" />
<input
type="text"
placeholder="검색어 입력 (포함)"
value={headerLikeFilters[column.columnName] || ""}
onChange={(e) => {
setHeaderLikeFilters((prev) => ({
...prev,
[column.columnName]: e.target.value,
}));
}}
className="border-input bg-background placeholder:text-muted-foreground h-7 w-full rounded-md border pl-7 pr-2 text-xs focus:outline-none focus:ring-1 focus:ring-primary"
onClick={(e) => e.stopPropagation()}
/>
</div>
{/* 구분선 */}
<div className="text-muted-foreground border-t pt-2 text-[10px]"> :</div>
<div className="max-h-40 space-y-1 overflow-y-auto">
{columnUniqueValues[column.columnName]?.slice(0, 50).map((val) => {
const isSelected = headerFilters[column.columnName]?.has(val);
return (