검색필터 업그레이드
This commit is contained in:
parent
faf4100056
commit
d09a6977f7
|
|
@ -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 (
|
||||
|
|
|
|||
Loading…
Reference in New Issue