nest-filter 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
|
|
2
|
+
# 🌳 Nest Filter
|
|
3
|
+
|
|
4
|
+
[](https://www.npmjs.com/package/nest-filter)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://reactjs.org/)
|
|
7
|
+
[](https://tailwindcss.com/)
|
|
8
|
+
|
|
9
|
+
**Nest Filter** is an enterprise-grade, highly optimized advanced filtering component for React. It allows users to build complex, nested logical queries (AND/OR) through a beautiful, accessible modal interface.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## ✨ Features
|
|
14
|
+
|
|
15
|
+
- 🚀 **Zero Configuration Logic**: Automatically generates operators based on data types (String, Number, Date, Boolean, Select).
|
|
16
|
+
- 🌲 **Infinite Nesting**: Build complex query trees with recursive AND/OR logical blocks.
|
|
17
|
+
- ⚡ **Optimized Performance**: Memoized components and stable state management to handle large datasets.
|
|
18
|
+
- 🎨 **Shadcn/UI Aesthetic**: Built with Tailwind CSS for a modern, professional look that fits into any dashboard.
|
|
19
|
+
- 🛠 **Type Safe**: Fully written in TypeScript with Generic support for your data models.
|
|
20
|
+
- 🔍 **Robust Matching**: Built-in normalization (trimming and case-insensitivity) to prevent common filtering bugs.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
<!-- ## 📺 Visual Preview
|
|
25
|
+
|
|
26
|
+
### 🖼️ Modal UI
|
|
27
|
+

|
|
28
|
+
*(Imagine a sleek modal here with nested logic blocks, dropdowns, and clear action buttons)* -->
|
|
29
|
+
|
|
30
|
+
<!-- ### 🎥 Demo in Action
|
|
31
|
+
> [!TIP]
|
|
32
|
+
> **View the Interactive Demo Video**
|
|
33
|
+
> [](https://your-video-link-here.com)
|
|
34
|
+
|
|
35
|
+
--- -->
|
|
36
|
+
|
|
37
|
+
## 📦 Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install nest-filter
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
> **Note**: This package requires `lucide-react` for icons and `tailwindcss` for styling.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 🚀 Quick Start
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { useState } from 'react';
|
|
51
|
+
import { AdvancedFilter } from 'nest-filter';
|
|
52
|
+
|
|
53
|
+
// 1. Define your columns
|
|
54
|
+
const COLUMNS = [
|
|
55
|
+
{ id: 'name', label: 'Full Name', type: 'string' },
|
|
56
|
+
{ id: 'age', label: 'Age', type: 'number' },
|
|
57
|
+
{ id: 'status', label: 'Status', type: 'select', options: ['Active', 'Paused', 'Banned'] },
|
|
58
|
+
{ id: 'is_verified', label: 'Verified', type: 'boolean' },
|
|
59
|
+
{ id: 'created_at', label: 'Join Date', type: 'date' },
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
function App() {
|
|
63
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
64
|
+
const [data, setData] = useState(MY_RAW_DATA);
|
|
65
|
+
const [filteredData, setFilteredData] = useState(MY_RAW_DATA);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div>
|
|
69
|
+
<button onClick={() => setIsOpen(true)}>Open Filters</button>
|
|
70
|
+
|
|
71
|
+
<AdvancedFilter
|
|
72
|
+
isOpen={isOpen}
|
|
73
|
+
onClose={() => setIsOpen(false)}
|
|
74
|
+
data={data}
|
|
75
|
+
columns={COLUMNS}
|
|
76
|
+
setFilteredData={setFilteredData}
|
|
77
|
+
/>
|
|
78
|
+
|
|
79
|
+
{/* Render your table using filteredData */}
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 📖 API Documentation
|
|
88
|
+
|
|
89
|
+
### Props
|
|
90
|
+
|
|
91
|
+
| Prop | Type | Required | Description |
|
|
92
|
+
| :--- | :--- | :--- | :--- |
|
|
93
|
+
| `isOpen` | `boolean` | Yes | Controls the visibility of the modal. |
|
|
94
|
+
| `onClose` | `() => void` | Yes | Callback fired when the modal requests to close. |
|
|
95
|
+
| `data` | `T[]` | Yes | Your raw array of objects to be filtered. |
|
|
96
|
+
| `columns` | `ColumnDefinition<T>[]` | Yes | Configuration for which fields can be filtered. |
|
|
97
|
+
| `setFilteredData` | `(data: T[]) => void` | Yes | Callback that receives the newly filtered array after "Apply". |
|
|
98
|
+
| `initialFilters` | `FilterGroup<T>` | No | Pass an existing filter tree to restore state. |
|
|
99
|
+
|
|
100
|
+
### Column Definition Object
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
interface ColumnDefinition<T> {
|
|
104
|
+
id: keyof T; // The key in your data object
|
|
105
|
+
label: string; // Human-readable name shown in UI
|
|
106
|
+
type: ColumnType; // 'string' | 'number' | 'date' | 'boolean' | 'select'
|
|
107
|
+
options?: string[]; // Required ONLY if type is 'select'
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 🔍 Filtering Logic Details
|
|
114
|
+
|
|
115
|
+
**Nest Filter** uses a deep-recursive evaluation engine. Here is how it handles different types:
|
|
116
|
+
|
|
117
|
+
- **String**: Normalizes both data and input (trimmed, lowercase) to ensure "Exact Match" works even with copy-pasted whitespace.
|
|
118
|
+
- **Select**: Provides a dropdown of your predefined `options`. It performs an exact, case-insensitive match against the record.
|
|
119
|
+
- **Number**: Converts inputs to floats and supports standard mathematical operators (`>`, `<`, `<=`, etc.).
|
|
120
|
+
- **Date**: Handles timestamp strings or Date objects, offering "Before", "After", and "On Date" comparisons.
|
|
121
|
+
- **Boolean**: Simply toggles the rule to filter for `true` or `false` states without needing an input value.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 🎨 Customization
|
|
126
|
+
|
|
127
|
+
The component uses **Tailwind CSS**. To ensure the modal looks perfect, make sure your `tailwind.config.js` includes the `nest-filter` node module path (if you are shipping styles separately) or that your project has the standard Shadcn-like colors defined (`slate`, `blue`, etc.).
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 🤝 Contributing
|
|
132
|
+
|
|
133
|
+
Contributions, issues, and feature requests are welcome! Feel free to check the [issues page](https://github.com/your-username/nest-filter/issues).
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 📜 License
|
|
138
|
+
|
|
139
|
+
Distributed under the MIT License. See `LICENSE` for more information.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
<p align="center">
|
|
144
|
+
Made with ❤️ for the Developer Community
|
|
145
|
+
</p>
|
|
@@ -156,7 +156,7 @@ const AdvancedFilter = ({ isOpen, onClose, data, columns, setFilteredData, initi
|
|
|
156
156
|
setFilteredData(data);
|
|
157
157
|
onClose();
|
|
158
158
|
};
|
|
159
|
-
return (jsxs(Dialog, { open: isOpen, onOpenChange: onClose, title: "Advanced Filters", description: "Refine your dataset using structured logic and multi-type comparisons.", children: [jsx("div", { className: "max-h-[60vh] overflow-y-auto px-1 custom-scrollbar pr-3 pb-32", children: jsx(FilterGroupUI, { group: rootGroup, depth: 0, columns: columns, onAddRule: handleAddRule, onAddGroup: handleAddGroup, onUpdateRule: handleUpdateRule, onUpdateGroupLogic: handleUpdateGroupLogic, onRemoveItem: handleRemoveItem }) }), jsxs("div", { className: "flex w-full justify-between items-center gap-4", children: [jsxs(Button, { variant: "ghost", onClick: handleClear, className: "text-slate-400 hover:text-red-500 font-black text-[10px] uppercase tracking-widest gap-2", children: [jsx(FilterX, { className: "h-4 w-4" }), " Reset Filters"] }), jsxs("div", { className: "flex gap-2", children: [jsx(Button, { variant: "outline", onClick: onClose, className: "font-bold h-11 px-6", children: "Cancel" }), jsx(Button, { onClick: handleApply, className: "bg-slate-900 font-bold h-11 px-10 rounded-lg shadow-lg hover:shadow-xl transition-shadow", children: "Apply View" })] })] })] }));
|
|
159
|
+
return (jsxs(Dialog, { open: isOpen, onOpenChange: onClose, title: "Advanced Filters by arijit", description: "Refine your dataset using structured logic and multi-type comparisons.", children: [jsx("div", { className: "max-h-[60vh] overflow-y-auto px-1 custom-scrollbar pr-3 pb-32", children: jsx(FilterGroupUI, { group: rootGroup, depth: 0, columns: columns, onAddRule: handleAddRule, onAddGroup: handleAddGroup, onUpdateRule: handleUpdateRule, onUpdateGroupLogic: handleUpdateGroupLogic, onRemoveItem: handleRemoveItem }) }), jsxs("div", { className: "flex w-full justify-between items-center gap-4", children: [jsxs(Button, { variant: "ghost", onClick: handleClear, className: "text-slate-400 hover:text-red-500 font-black text-[10px] uppercase tracking-widest gap-2", children: [jsx(FilterX, { className: "h-4 w-4" }), " Reset Filters"] }), jsxs("div", { className: "flex gap-2", children: [jsx(Button, { variant: "outline", onClick: onClose, className: "font-bold h-11 px-6", children: "Cancel" }), jsx(Button, { onClick: handleApply, className: "bg-slate-900 font-bold h-11 px-10 rounded-lg shadow-lg hover:shadow-xl transition-shadow", children: "Apply View" })] })] })] }));
|
|
160
160
|
};
|
|
161
161
|
|
|
162
162
|
export { AdvancedFilter };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdvancedFilter.js","sources":["../../src/components/AdvancedFilter.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { useState, useEffect, useCallback } from \"react\";\nimport {\n FilterRule,\n FilterGroup,\n ColumnDefinition,\n LogicalOperator,\n FilterOperator,\n FilterItem,\n} from \"../types\";\nimport { Button } from \"./ui/Button\";\nimport { Input } from \"./ui/Input\";\nimport { Select } from \"./ui/Select\";\nimport { Dialog } from \"./ui/Dialog\";\nimport {\n Plus,\n Trash2,\n Layers,\n Binary,\n FilterX,\n ListFilter,\n} from \"lucide-react\";\nimport { applyFilters } from \"../utils/filterLogic\";\n\ninterface AdvancedFilterProps<T> {\n isOpen: boolean;\n onClose: () => void;\n data: T[];\n columns: ColumnDefinition<T>[];\n setFilteredData: (data: T[]) => void;\n initialFilters?: FilterGroup<T>;\n}\n\nconst getOperatorsForType = (\n type: string\n): { value: FilterOperator; label: string }[] => {\n switch (type) {\n case \"string\":\n return [\n { value: \"contains\", label: \"Contains\" },\n { value: \"not_contains\", label: \"Does not contain\" },\n { value: \"equals\", label: \"Exact match\" },\n ];\n case \"select\":\n return [\n { value: \"equals\", label: \"Is\" },\n { value: \"not_equals\", label: \"Is not\" },\n ];\n case \"number\":\n return [\n { value: \"equals\", label: \"=\" },\n { value: \"not_equals\", label: \"!=\" },\n { value: \"gt\", label: \">\" },\n { value: \"lt\", label: \"<\" },\n { value: \"gte\", label: \">=\" },\n { value: \"lte\", label: \"<=\" },\n ];\n case \"date\":\n return [\n { value: \"is\", label: \"On date\" },\n { value: \"before\", label: \"Before\" },\n { value: \"after\", label: \"After\" },\n ];\n case \"boolean\":\n return [\n { value: \"true\", label: \"True\" },\n { value: \"false\", label: \"False\" },\n ];\n default:\n return [];\n }\n};\n\nconst FilterGroupUI = <T,>({\n group,\n depth,\n columns,\n onAddRule,\n onAddGroup,\n onUpdateRule,\n onUpdateGroupLogic,\n onRemoveItem,\n}: {\n group: FilterGroup<T>;\n depth: number;\n columns: ColumnDefinition<T>[];\n onAddRule: (parentId: string) => void;\n onAddGroup: (parentId: string) => void;\n onUpdateRule: (ruleId: string, updates: Partial<FilterRule<T>>) => void;\n onUpdateGroupLogic: (groupId: string, logic: LogicalOperator) => void;\n onRemoveItem: (itemId: string) => void;\n}) => {\n return (\n <div\n className={`rounded-lg border border-slate-200 bg-slate-50/50 p-4 mb-4 ${\n depth > 0 ? \"ml-6 md:ml-10 relative border-l-2 border-l-slate-300\" : \"\"\n }`}\n >\n <div className=\"flex flex-wrap items-center justify-between gap-4 mb-4\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-[10px] font-bold uppercase tracking-widest text-slate-500\">\n Group Logic\n </span>\n <div className=\"flex rounded-md border border-slate-200 bg-white p-1 shadow-sm\">\n <button\n onClick={() => onUpdateGroupLogic(group.id, \"AND\")}\n className={`px-3 py-1 text-[10px] font-black rounded-sm transition-all ${\n group.logic === \"AND\"\n ? \"bg-slate-900 text-slate-50\"\n : \"text-slate-500 hover:text-slate-900\"\n }`}\n >\n AND\n </button>\n <button\n onClick={() => onUpdateGroupLogic(group.id, \"OR\")}\n className={`px-3 py-1 text-[10px] font-black rounded-sm transition-all ${\n group.logic === \"OR\"\n ? \"bg-slate-900 text-slate-50\"\n : \"text-slate-500 hover:text-slate-900\"\n }`}\n >\n OR\n </button>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => onAddRule(group.id)}\n className=\"h-7 text-[10px] font-bold uppercase\"\n >\n <Plus className=\"mr-1 h-3 w-3\" /> Rule\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => onAddGroup(group.id)}\n className=\"h-7 text-[10px] font-bold uppercase\"\n >\n <Layers className=\"mr-1 h-3 w-3\" /> Group\n </Button>\n {depth > 0 && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onRemoveItem(group.id)}\n className=\"text-slate-400\"\n >\n <Trash2 className=\"h-5 w-5 text-red-500\" />\n </Button>\n )}\n </div>\n </div>\n\n <div className=\"space-y-3\">\n {group.items.map((item) => {\n const column = columns.find(\n (c) => c.id === (item.type === \"rule\" ? item.columnId : null)\n );\n return item.type === \"rule\" ? (\n <div\n key={item.id}\n className=\"flex flex-col md:flex-row md:items-center gap-3 p-3 bg-white border border-slate-200 rounded-md shadow-sm\"\n >\n <div className=\"flex-1 min-w-[140px]\">\n <Select\n value={item.columnId as string}\n options={columns.map((c) => ({\n value: c.id as string,\n label: c.label,\n }))}\n onValueChange={(val) => {\n const colId = val as keyof T;\n const col = columns.find((c) => c.id === colId);\n onUpdateRule(item.id, {\n columnId: colId,\n operator:\n col?.type === \"date\"\n ? \"is\"\n : col?.type === \"number\"\n ? \"equals\"\n : col?.type === \"select\"\n ? \"equals\"\n : \"contains\",\n value: \"\",\n });\n }}\n />\n </div>\n <div className=\"w-full md:w-44\">\n <Select\n value={item.operator}\n options={getOperatorsForType(column?.type || \"string\")}\n onValueChange={(val) =>\n onUpdateRule(item.id, { operator: val as FilterOperator })\n }\n />\n </div>\n <div className=\"flex-[1.5] min-w-[180px]\">\n {column?.type === \"date\" ? (\n <Input\n type=\"date\"\n value={item.value || \"\"}\n onChange={(e) =>\n onUpdateRule(item.id, { value: e.target.value })\n }\n />\n ) : column?.type === \"boolean\" ? (\n <div className=\"h-10 flex items-center px-3 border border-slate-200 rounded-md bg-slate-50 text-[10px] font-black uppercase tracking-tighter text-slate-400 italic\">\n <Binary className=\"h-3 w-3 mr-2\" />\n Binary state\n </div>\n ) : column?.type === \"select\" ? (\n <Select\n value={item.value || \"\"}\n placeholder=\"Choose option...\"\n options={(column.options || []).map((opt) => ({\n value: opt,\n label: opt,\n }))}\n onValueChange={(val) =>\n onUpdateRule(item.id, { value: val })\n }\n />\n ) : (\n <Input\n placeholder=\"Search query...\"\n value={item.value || \"\"}\n onChange={(e) =>\n onUpdateRule(item.id, { value: e.target.value })\n }\n />\n )}\n </div>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => onRemoveItem(item.id)}\n className=\"h-9 w-9 text-slate-300 hover:text-red-500 hover:bg-red-50\"\n >\n <Trash2 className=\"h-5 w-5 text-red-500\" />\n </Button>\n </div>\n ) : (\n <FilterGroupUI\n group={item}\n depth={depth + 1}\n columns={columns}\n onAddRule={onAddRule}\n onAddGroup={onAddGroup}\n onUpdateRule={onUpdateRule}\n onUpdateGroupLogic={onUpdateGroupLogic}\n onRemoveItem={onRemoveItem}\n />\n );\n })}\n {group.items.length === 0 && (\n <div className=\"flex flex-col items-center justify-center py-6 rounded-md border border-dashed border-slate-200 bg-white/50\">\n <ListFilter className=\"h-5 w-5 text-slate-300 mb-1\" />\n <p className=\"text-[10px] text-slate-400 font-bold uppercase tracking-widest\">\n Add a rule to filter\n </p>\n </div>\n )}\n </div>\n </div>\n );\n};\n\nexport const AdvancedFilter = <T,>({\n isOpen,\n onClose,\n data,\n columns,\n setFilteredData,\n initialFilters,\n}: AdvancedFilterProps<T>) => {\n const [rootGroup, setRootGroup] = useState<FilterGroup<T>>({\n type: \"group\",\n id: \"root\",\n logic: \"AND\",\n items: [],\n });\n\n useEffect(() => {\n if (initialFilters) setRootGroup(initialFilters);\n }, [initialFilters, isOpen]);\n\n const updateItemRecursively = useCallback(\n (\n group: FilterGroup<T>,\n targetId: string,\n updater: (item: FilterItem<T>) => FilterItem<T> | null\n ): FilterGroup<T> => {\n if (group.id === targetId) return updater(group) as FilterGroup<T>;\n return {\n ...group,\n items: group.items\n .map((item) => {\n if (item.id === targetId) return updater(item);\n if (item.type === \"group\")\n return updateItemRecursively(item, targetId, updater);\n return item;\n })\n .filter(Boolean) as FilterItem<T>[],\n };\n },\n []\n );\n\n const handleAddRule = useCallback(\n (parentId: string) => {\n const firstCol = columns[0];\n const newRule: FilterRule<T> = {\n type: \"rule\",\n id: Math.random().toString(36).substr(2, 9),\n columnId: firstCol.id,\n operator:\n firstCol.type === \"string\"\n ? \"contains\"\n : firstCol.type === \"select\"\n ? \"equals\"\n : \"equals\",\n value: \"\",\n };\n setRootGroup((prev) =>\n updateItemRecursively(prev, parentId, (group) => ({\n ...(group as FilterGroup<T>),\n items: [...(group as FilterGroup<T>).items, newRule],\n }))\n );\n },\n [columns, updateItemRecursively]\n );\n\n const handleAddGroup = useCallback(\n (parentId: string) => {\n const newGroup: FilterGroup<T> = {\n type: \"group\",\n id: Math.random().toString(36).substr(2, 9),\n logic: \"AND\",\n items: [],\n };\n setRootGroup((prev) =>\n updateItemRecursively(prev, parentId, (group) => ({\n ...(group as FilterGroup<T>),\n items: [...(group as FilterGroup<T>).items, newGroup],\n }))\n );\n },\n [updateItemRecursively]\n );\n\n const handleUpdateRule = useCallback(\n (ruleId: string, updates: Partial<FilterRule<T>>) => {\n setRootGroup((prev) =>\n updateItemRecursively(\n prev,\n ruleId,\n (rule) => ({ ...rule, ...updates } as FilterRule<T>)\n )\n );\n },\n [updateItemRecursively]\n );\n\n const handleUpdateGroupLogic = useCallback(\n (groupId: string, logic: LogicalOperator) => {\n setRootGroup((prev) =>\n updateItemRecursively(\n prev,\n groupId,\n (group) => ({ ...group, logic } as FilterGroup<T>)\n )\n );\n },\n [updateItemRecursively]\n );\n\n const handleRemoveItem = useCallback(\n (itemId: string) => {\n setRootGroup((prev) => updateItemRecursively(prev, itemId, () => null));\n },\n [updateItemRecursively]\n );\n\n const handleApply = () => {\n const filtered = applyFilters(data, rootGroup, columns);\n setFilteredData(filtered);\n onClose();\n };\n\n const handleClear = () => {\n const emptyGroup: FilterGroup<T> = {\n type: \"group\",\n id: \"root\",\n logic: \"AND\",\n items: [],\n };\n setRootGroup(emptyGroup);\n setFilteredData(data);\n onClose();\n };\n\n return (\n <Dialog\n open={isOpen}\n onOpenChange={onClose}\n title=\"Advanced Filters\"\n description=\"Refine your dataset using structured logic and multi-type comparisons.\"\n >\n <div className=\"max-h-[60vh] overflow-y-auto px-1 custom-scrollbar pr-3 pb-32\">\n <FilterGroupUI\n group={rootGroup}\n depth={0}\n columns={columns}\n onAddRule={handleAddRule}\n onAddGroup={handleAddGroup}\n onUpdateRule={handleUpdateRule}\n onUpdateGroupLogic={handleUpdateGroupLogic}\n onRemoveItem={handleRemoveItem}\n />\n </div>\n <div className=\"flex w-full justify-between items-center gap-4\">\n <Button\n variant=\"ghost\"\n onClick={handleClear}\n className=\"text-slate-400 hover:text-red-500 font-black text-[10px] uppercase tracking-widest gap-2\"\n >\n <FilterX className=\"h-4 w-4\" /> Reset Filters\n </Button>\n <div className=\"flex gap-2\">\n <Button\n variant=\"outline\"\n onClick={onClose}\n className=\"font-bold h-11 px-6\"\n >\n Cancel\n </Button>\n <Button\n onClick={handleApply}\n className=\"bg-slate-900 font-bold h-11 px-10 rounded-lg shadow-lg hover:shadow-xl transition-shadow\"\n >\n Apply View\n </Button>\n </div>\n </div>\n </Dialog>\n );\n};\n"],"names":["_jsxs","_jsx"],"mappings":";;;;;;;;;AAiCA,MAAM,mBAAmB,GAAG,CAC1B,IAAY,KACkC;IAC9C,QAAQ,IAAI;AACV,QAAA,KAAK,QAAQ;YACX,OAAO;AACL,gBAAA,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE;AACxC,gBAAA,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,kBAAkB,EAAE;AACpD,gBAAA,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE;aAC1C;AACH,QAAA,KAAK,QAAQ;YACX,OAAO;AACL,gBAAA,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE;AAChC,gBAAA,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE;aACzC;AACH,QAAA,KAAK,QAAQ;YACX,OAAO;AACL,gBAAA,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE;AAC/B,gBAAA,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE;AACpC,gBAAA,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE;AAC3B,gBAAA,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE;AAC3B,gBAAA,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE;AAC7B,gBAAA,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE;aAC9B;AACH,QAAA,KAAK,MAAM;YACT,OAAO;AACL,gBAAA,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE;AACjC,gBAAA,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE;AACpC,gBAAA,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE;aACnC;AACH,QAAA,KAAK,SAAS;YACZ,OAAO;AACL,gBAAA,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE;AAChC,gBAAA,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE;aACnC;AACH,QAAA;AACE,YAAA,OAAO,EAAE;;AAEf,CAAC;AAED,MAAM,aAAa,GAAG,CAAK,EACzB,KAAK,EACL,KAAK,EACL,OAAO,EACP,SAAS,EACT,UAAU,EACV,YAAY,EACZ,kBAAkB,EAClB,YAAY,GAUb,KAAI;AACH,IAAA,QACEA,IAAA,CAAA,KAAA,EAAA,EACE,SAAS,EAAE,CAAA,2DAAA,EACT,KAAK,GAAG,CAAC,GAAG,sDAAsD,GAAG,EACvE,CAAA,CAAE,EAAA,QAAA,EAAA,CAEFA,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,wDAAwD,EAAA,QAAA,EAAA,CACrEA,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,yBAAyB,EAAA,QAAA,EAAA,CACtCC,GAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAC,gEAAgE,EAAA,QAAA,EAAA,aAAA,EAAA,CAEzE,EACPD,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,gEAAgE,EAAA,QAAA,EAAA,CAC7EC,GAAA,CAAA,QAAA,EAAA,EACE,OAAO,EAAE,MAAM,kBAAkB,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,EAClD,SAAS,EAAE,CAAA,2DAAA,EACT,KAAK,CAAC,KAAK,KAAK;AACd,8CAAE;8CACA,qCACN,CAAA,CAAE,EAAA,QAAA,EAAA,KAAA,EAAA,CAGK,EACTA,GAAA,CAAA,QAAA,EAAA,EACE,OAAO,EAAE,MAAM,kBAAkB,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,EACjD,SAAS,EAAE,CAAA,2DAAA,EACT,KAAK,CAAC,KAAK,KAAK;AACd,8CAAE;AACF,8CAAE,qCACN,CAAA,CAAE,EAAA,QAAA,EAAA,IAAA,EAAA,CAGK,IACL,CAAA,EAAA,CACF,EAEND,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,yBAAyB,EAAA,QAAA,EAAA,CACtCA,IAAA,CAAC,MAAM,EAAA,EACL,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,MAAM,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,EAClC,SAAS,EAAC,qCAAqC,EAAA,QAAA,EAAA,CAE/CC,GAAA,CAAC,IAAI,EAAA,EAAC,SAAS,EAAC,cAAc,EAAA,CAAG,EAAA,OAAA,CAAA,EAAA,CAC1B,EACTD,IAAA,CAAC,MAAM,EAAA,EACL,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,MAAM,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC,EACnC,SAAS,EAAC,qCAAqC,EAAA,QAAA,EAAA,CAE/CC,GAAA,CAAC,MAAM,IAAC,SAAS,EAAC,cAAc,EAAA,CAAG,EAAA,QAAA,CAAA,EAAA,CAC5B,EACR,KAAK,GAAG,CAAC,KACRA,GAAA,CAAC,MAAM,EAAA,EACL,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,MAAM,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,EACrC,SAAS,EAAC,gBAAgB,EAAA,QAAA,EAE1BA,GAAA,CAAC,MAAM,EAAA,EAAC,SAAS,EAAC,sBAAsB,GAAG,EAAA,CACpC,CACV,CAAA,EAAA,CACG,CAAA,EAAA,CACF,EAEND,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,WAAW,EAAA,QAAA,EAAA,CACvB,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,KAAI;AACxB,wBAAA,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CACzB,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,IAAI,KAAK,MAAM,GAAG,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,CAC9D;AACD,wBAAA,OAAO,IAAI,CAAC,IAAI,KAAK,MAAM,IACzBA,IAAA,CAAA,KAAA,EAAA,EAEE,SAAS,EAAC,2GAA2G,aAErHC,GAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,sBAAsB,EAAA,QAAA,EACnCA,GAAA,CAAC,MAAM,IACL,KAAK,EAAE,IAAI,CAAC,QAAkB,EAC9B,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM;4CAC3B,KAAK,EAAE,CAAC,CAAC,EAAY;4CACrB,KAAK,EAAE,CAAC,CAAC,KAAK;AACf,yCAAA,CAAC,CAAC,EACH,aAAa,EAAE,CAAC,GAAG,KAAI;4CACrB,MAAM,KAAK,GAAG,GAAc;AAC5B,4CAAA,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC;AAC/C,4CAAA,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE;AACpB,gDAAA,QAAQ,EAAE,KAAK;AACf,gDAAA,QAAQ,EACN,GAAG,EAAE,IAAI,KAAK;AACZ,sDAAE;AACF,sDAAE,GAAG,EAAE,IAAI,KAAK;AAChB,0DAAE;AACF,0DAAE,GAAG,EAAE,IAAI,KAAK;AAChB,8DAAE;AACF,8DAAE,UAAU;AAChB,gDAAA,KAAK,EAAE,EAAE;AACV,6CAAA,CAAC;AACJ,wCAAA,CAAC,GACD,EAAA,CACE,EACNA,aAAK,SAAS,EAAC,gBAAgB,EAAA,QAAA,EAC7BA,GAAA,CAAC,MAAM,EAAA,EACL,KAAK,EAAE,IAAI,CAAC,QAAQ,EACpB,OAAO,EAAE,mBAAmB,CAAC,MAAM,EAAE,IAAI,IAAI,QAAQ,CAAC,EACtD,aAAa,EAAE,CAAC,GAAG,KACjB,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAqB,EAAE,CAAC,GAE5D,EAAA,CACE,EACNA,aAAK,SAAS,EAAC,0BAA0B,EAAA,QAAA,EACtC,MAAM,EAAE,IAAI,KAAK,MAAM,IACtBA,GAAA,CAAC,KAAK,IACJ,IAAI,EAAC,MAAM,EACX,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,EACvB,QAAQ,EAAE,CAAC,CAAC,KACV,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,EAAA,CAElD,IACA,MAAM,EAAE,IAAI,KAAK,SAAS,IAC5BD,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,oJAAoJ,EAAA,QAAA,EAAA,CACjKC,GAAA,CAAC,MAAM,EAAA,EAAC,SAAS,EAAC,cAAc,EAAA,CAAG,oBAE/B,IACJ,MAAM,EAAE,IAAI,KAAK,QAAQ,IAC3BA,GAAA,CAAC,MAAM,IACL,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,EACvB,WAAW,EAAC,kBAAkB,EAC9B,OAAO,EAAE,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC,GAAG,MAAM;AAC5C,4CAAA,KAAK,EAAE,GAAG;AACV,4CAAA,KAAK,EAAE,GAAG;AACX,yCAAA,CAAC,CAAC,EACH,aAAa,EAAE,CAAC,GAAG,KACjB,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,EAAA,CAEvC,KAEFA,GAAA,CAAC,KAAK,IACJ,WAAW,EAAC,iBAAiB,EAC7B,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,EACvB,QAAQ,EAAE,CAAC,CAAC,KACV,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,EAAA,CAElD,CACH,EAAA,CACG,EACNA,GAAA,CAAC,MAAM,EAAA,EACL,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,MAAM,EACX,OAAO,EAAE,MAAM,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,EACpC,SAAS,EAAC,2DAA2D,EAAA,QAAA,EAErEA,GAAA,CAAC,MAAM,EAAA,EAAC,SAAS,EAAC,sBAAsB,GAAG,EAAA,CACpC,CAAA,EAAA,EAhFJ,IAAI,CAAC,EAAE,CAiFR,KAENA,GAAA,CAAC,aAAa,EAAA,EACZ,KAAK,EAAE,IAAI,EACX,KAAK,EAAE,KAAK,GAAG,CAAC,EAChB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,YAAY,EAC1B,kBAAkB,EAAE,kBAAkB,EACtC,YAAY,EAAE,YAAY,EAAA,CAC1B,CACH;AACH,oBAAA,CAAC,CAAC,EACD,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,KACvBD,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,6GAA6G,EAAA,QAAA,EAAA,CAC1HC,GAAA,CAAC,UAAU,EAAA,EAAC,SAAS,EAAC,6BAA6B,EAAA,CAAG,EACtDA,GAAA,CAAA,GAAA,EAAA,EAAG,SAAS,EAAC,gEAAgE,qCAEzE,CAAA,EAAA,CACA,CACP,CAAA,EAAA,CACG,CAAA,EAAA,CACF;AAEV,CAAC;AAEM,MAAM,cAAc,GAAG,CAAK,EACjC,MAAM,EACN,OAAO,EACP,IAAI,EACJ,OAAO,EACP,eAAe,EACf,cAAc,GACS,KAAI;AAC3B,IAAA,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAiB;AACzD,QAAA,IAAI,EAAE,OAAO;AACb,QAAA,EAAE,EAAE,MAAM;AACV,QAAA,KAAK,EAAE,KAAK;AACZ,QAAA,KAAK,EAAE,EAAE;AACV,KAAA,CAAC;IAEF,SAAS,CAAC,MAAK;AACb,QAAA,IAAI,cAAc;YAAE,YAAY,CAAC,cAAc,CAAC;AAClD,IAAA,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IAE5B,MAAM,qBAAqB,GAAG,WAAW,CACvC,CACE,KAAqB,EACrB,QAAgB,EAChB,OAAsD,KACpC;AAClB,QAAA,IAAI,KAAK,CAAC,EAAE,KAAK,QAAQ;AAAE,YAAA,OAAO,OAAO,CAAC,KAAK,CAAmB;QAClE,OAAO;AACL,YAAA,GAAG,KAAK;YACR,KAAK,EAAE,KAAK,CAAC;AACV,iBAAA,GAAG,CAAC,CAAC,IAAI,KAAI;AACZ,gBAAA,IAAI,IAAI,CAAC,EAAE,KAAK,QAAQ;AAAE,oBAAA,OAAO,OAAO,CAAC,IAAI,CAAC;AAC9C,gBAAA,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;oBACvB,OAAO,qBAAqB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC;AACvD,gBAAA,OAAO,IAAI;AACb,YAAA,CAAC;iBACA,MAAM,CAAC,OAAO,CAAoB;SACtC;IACH,CAAC,EACD,EAAE,CACH;AAED,IAAA,MAAM,aAAa,GAAG,WAAW,CAC/B,CAAC,QAAgB,KAAI;AACnB,QAAA,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC;AAC3B,QAAA,MAAM,OAAO,GAAkB;AAC7B,YAAA,IAAI,EAAE,MAAM;AACZ,YAAA,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC;YAC3C,QAAQ,EAAE,QAAQ,CAAC,EAAE;AACrB,YAAA,QAAQ,EACN,QAAQ,CAAC,IAAI,KAAK;AAChB,kBAAE;AACF,kBAAE,QAAQ,CAAC,IAAI,KAAK;AACpB,sBAAE;AACF,sBAAE,QAAQ;AACd,YAAA,KAAK,EAAE,EAAE;SACV;AACD,QAAA,YAAY,CAAC,CAAC,IAAI,KAChB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,MAAM;AAChD,YAAA,GAAI,KAAwB;YAC5B,KAAK,EAAE,CAAC,GAAI,KAAwB,CAAC,KAAK,EAAE,OAAO,CAAC;SACrD,CAAC,CAAC,CACJ;AACH,IAAA,CAAC,EACD,CAAC,OAAO,EAAE,qBAAqB,CAAC,CACjC;AAED,IAAA,MAAM,cAAc,GAAG,WAAW,CAChC,CAAC,QAAgB,KAAI;AACnB,QAAA,MAAM,QAAQ,GAAmB;AAC/B,YAAA,IAAI,EAAE,OAAO;AACb,YAAA,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC;AAC3C,YAAA,KAAK,EAAE,KAAK;AACZ,YAAA,KAAK,EAAE,EAAE;SACV;AACD,QAAA,YAAY,CAAC,CAAC,IAAI,KAChB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,MAAM;AAChD,YAAA,GAAI,KAAwB;YAC5B,KAAK,EAAE,CAAC,GAAI,KAAwB,CAAC,KAAK,EAAE,QAAQ,CAAC;SACtD,CAAC,CAAC,CACJ;AACH,IAAA,CAAC,EACD,CAAC,qBAAqB,CAAC,CACxB;IAED,MAAM,gBAAgB,GAAG,WAAW,CAClC,CAAC,MAAc,EAAE,OAA+B,KAAI;AAClD,QAAA,YAAY,CAAC,CAAC,IAAI,KAChB,qBAAqB,CACnB,IAAI,EACJ,MAAM,EACN,CAAC,IAAI,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,EAAoB,CAAA,CACrD,CACF;AACH,IAAA,CAAC,EACD,CAAC,qBAAqB,CAAC,CACxB;IAED,MAAM,sBAAsB,GAAG,WAAW,CACxC,CAAC,OAAe,EAAE,KAAsB,KAAI;QAC1C,YAAY,CAAC,CAAC,IAAI,KAChB,qBAAqB,CACnB,IAAI,EACJ,OAAO,EACP,CAAC,KAAK,MAAM,EAAE,GAAG,KAAK,EAAE,KAAK,EAAqB,CAAA,CACnD,CACF;AACH,IAAA,CAAC,EACD,CAAC,qBAAqB,CAAC,CACxB;AAED,IAAA,MAAM,gBAAgB,GAAG,WAAW,CAClC,CAAC,MAAc,KAAI;AACjB,QAAA,YAAY,CAAC,CAAC,IAAI,KAAK,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC,CAAC;AACzE,IAAA,CAAC,EACD,CAAC,qBAAqB,CAAC,CACxB;IAED,MAAM,WAAW,GAAG,MAAK;QACvB,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC;QACvD,eAAe,CAAC,QAAQ,CAAC;AACzB,QAAA,OAAO,EAAE;AACX,IAAA,CAAC;IAED,MAAM,WAAW,GAAG,MAAK;AACvB,QAAA,MAAM,UAAU,GAAmB;AACjC,YAAA,IAAI,EAAE,OAAO;AACb,YAAA,EAAE,EAAE,MAAM;AACV,YAAA,KAAK,EAAE,KAAK;AACZ,YAAA,KAAK,EAAE,EAAE;SACV;QACD,YAAY,CAAC,UAAU,CAAC;QACxB,eAAe,CAAC,IAAI,CAAC;AACrB,QAAA,OAAO,EAAE;AACX,IAAA,CAAC;AAED,IAAA,QACED,IAAA,CAAC,MAAM,EAAA,EACL,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,OAAO,EACrB,KAAK,EAAC,kBAAkB,EACxB,WAAW,EAAC,wEAAwE,EAAA,QAAA,EAAA,CAEpFC,GAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,+DAA+D,EAAA,QAAA,EAC5EA,GAAA,CAAC,aAAa,EAAA,EACZ,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,aAAa,EACxB,UAAU,EAAE,cAAc,EAC1B,YAAY,EAAE,gBAAgB,EAC9B,kBAAkB,EAAE,sBAAsB,EAC1C,YAAY,EAAE,gBAAgB,EAAA,CAC9B,GACE,EACND,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,gDAAgD,EAAA,QAAA,EAAA,CAC7DA,IAAA,CAAC,MAAM,EAAA,EACL,OAAO,EAAC,OAAO,EACf,OAAO,EAAE,WAAW,EACpB,SAAS,EAAC,0FAA0F,EAAA,QAAA,EAAA,CAEpGC,GAAA,CAAC,OAAO,EAAA,EAAC,SAAS,EAAC,SAAS,EAAA,CAAG,EAAA,gBAAA,CAAA,EAAA,CACxB,EACTD,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,YAAY,EAAA,QAAA,EAAA,CACzBC,GAAA,CAAC,MAAM,EAAA,EACL,OAAO,EAAC,SAAS,EACjB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAC,qBAAqB,EAAA,QAAA,EAAA,QAAA,EAAA,CAGxB,EACTA,GAAA,CAAC,MAAM,EAAA,EACL,OAAO,EAAE,WAAW,EACpB,SAAS,EAAC,0FAA0F,EAAA,QAAA,EAAA,YAAA,EAAA,CAG7F,CAAA,EAAA,CACL,CAAA,EAAA,CACF,CAAA,EAAA,CACC;AAEb;;;;"}
|
|
1
|
+
{"version":3,"file":"AdvancedFilter.js","sources":["../../src/components/AdvancedFilter.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { useState, useEffect, useCallback } from \"react\";\nimport {\n FilterRule,\n FilterGroup,\n ColumnDefinition,\n LogicalOperator,\n FilterOperator,\n FilterItem,\n} from \"../types\";\nimport { Button } from \"./ui/Button\";\nimport { Input } from \"./ui/Input\";\nimport { Select } from \"./ui/Select\";\nimport { Dialog } from \"./ui/Dialog\";\nimport {\n Plus,\n Trash2,\n Layers,\n Binary,\n FilterX,\n ListFilter,\n} from \"lucide-react\";\nimport { applyFilters } from \"../utils/filterLogic\";\n\ninterface AdvancedFilterProps<T> {\n isOpen: boolean;\n onClose: () => void;\n data: T[];\n columns: ColumnDefinition<T>[];\n setFilteredData: (data: T[]) => void;\n initialFilters?: FilterGroup<T>;\n}\n\nconst getOperatorsForType = (\n type: string\n): { value: FilterOperator; label: string }[] => {\n switch (type) {\n case \"string\":\n return [\n { value: \"contains\", label: \"Contains\" },\n { value: \"not_contains\", label: \"Does not contain\" },\n { value: \"equals\", label: \"Exact match\" },\n ];\n case \"select\":\n return [\n { value: \"equals\", label: \"Is\" },\n { value: \"not_equals\", label: \"Is not\" },\n ];\n case \"number\":\n return [\n { value: \"equals\", label: \"=\" },\n { value: \"not_equals\", label: \"!=\" },\n { value: \"gt\", label: \">\" },\n { value: \"lt\", label: \"<\" },\n { value: \"gte\", label: \">=\" },\n { value: \"lte\", label: \"<=\" },\n ];\n case \"date\":\n return [\n { value: \"is\", label: \"On date\" },\n { value: \"before\", label: \"Before\" },\n { value: \"after\", label: \"After\" },\n ];\n case \"boolean\":\n return [\n { value: \"true\", label: \"True\" },\n { value: \"false\", label: \"False\" },\n ];\n default:\n return [];\n }\n};\n\nconst FilterGroupUI = <T,>({\n group,\n depth,\n columns,\n onAddRule,\n onAddGroup,\n onUpdateRule,\n onUpdateGroupLogic,\n onRemoveItem,\n}: {\n group: FilterGroup<T>;\n depth: number;\n columns: ColumnDefinition<T>[];\n onAddRule: (parentId: string) => void;\n onAddGroup: (parentId: string) => void;\n onUpdateRule: (ruleId: string, updates: Partial<FilterRule<T>>) => void;\n onUpdateGroupLogic: (groupId: string, logic: LogicalOperator) => void;\n onRemoveItem: (itemId: string) => void;\n}) => {\n return (\n <div\n className={`rounded-lg border border-slate-200 bg-slate-50/50 p-4 mb-4 ${\n depth > 0 ? \"ml-6 md:ml-10 relative border-l-2 border-l-slate-300\" : \"\"\n }`}\n >\n <div className=\"flex flex-wrap items-center justify-between gap-4 mb-4\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-[10px] font-bold uppercase tracking-widest text-slate-500\">\n Group Logic\n </span>\n <div className=\"flex rounded-md border border-slate-200 bg-white p-1 shadow-sm\">\n <button\n onClick={() => onUpdateGroupLogic(group.id, \"AND\")}\n className={`px-3 py-1 text-[10px] font-black rounded-sm transition-all ${\n group.logic === \"AND\"\n ? \"bg-slate-900 text-slate-50\"\n : \"text-slate-500 hover:text-slate-900\"\n }`}\n >\n AND\n </button>\n <button\n onClick={() => onUpdateGroupLogic(group.id, \"OR\")}\n className={`px-3 py-1 text-[10px] font-black rounded-sm transition-all ${\n group.logic === \"OR\"\n ? \"bg-slate-900 text-slate-50\"\n : \"text-slate-500 hover:text-slate-900\"\n }`}\n >\n OR\n </button>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => onAddRule(group.id)}\n className=\"h-7 text-[10px] font-bold uppercase\"\n >\n <Plus className=\"mr-1 h-3 w-3\" /> Rule\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => onAddGroup(group.id)}\n className=\"h-7 text-[10px] font-bold uppercase\"\n >\n <Layers className=\"mr-1 h-3 w-3\" /> Group\n </Button>\n {depth > 0 && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onRemoveItem(group.id)}\n className=\"text-slate-400\"\n >\n <Trash2 className=\"h-5 w-5 text-red-500\" />\n </Button>\n )}\n </div>\n </div>\n\n <div className=\"space-y-3\">\n {group.items.map((item) => {\n const column = columns.find(\n (c) => c.id === (item.type === \"rule\" ? item.columnId : null)\n );\n return item.type === \"rule\" ? (\n <div\n key={item.id}\n className=\"flex flex-col md:flex-row md:items-center gap-3 p-3 bg-white border border-slate-200 rounded-md shadow-sm\"\n >\n <div className=\"flex-1 min-w-[140px]\">\n <Select\n value={item.columnId as string}\n options={columns.map((c) => ({\n value: c.id as string,\n label: c.label,\n }))}\n onValueChange={(val) => {\n const colId = val as keyof T;\n const col = columns.find((c) => c.id === colId);\n onUpdateRule(item.id, {\n columnId: colId,\n operator:\n col?.type === \"date\"\n ? \"is\"\n : col?.type === \"number\"\n ? \"equals\"\n : col?.type === \"select\"\n ? \"equals\"\n : \"contains\",\n value: \"\",\n });\n }}\n />\n </div>\n <div className=\"w-full md:w-44\">\n <Select\n value={item.operator}\n options={getOperatorsForType(column?.type || \"string\")}\n onValueChange={(val) =>\n onUpdateRule(item.id, { operator: val as FilterOperator })\n }\n />\n </div>\n <div className=\"flex-[1.5] min-w-[180px]\">\n {column?.type === \"date\" ? (\n <Input\n type=\"date\"\n value={item.value || \"\"}\n onChange={(e) =>\n onUpdateRule(item.id, { value: e.target.value })\n }\n />\n ) : column?.type === \"boolean\" ? (\n <div className=\"h-10 flex items-center px-3 border border-slate-200 rounded-md bg-slate-50 text-[10px] font-black uppercase tracking-tighter text-slate-400 italic\">\n <Binary className=\"h-3 w-3 mr-2\" />\n Binary state\n </div>\n ) : column?.type === \"select\" ? (\n <Select\n value={item.value || \"\"}\n placeholder=\"Choose option...\"\n options={(column.options || []).map((opt) => ({\n value: opt,\n label: opt,\n }))}\n onValueChange={(val) =>\n onUpdateRule(item.id, { value: val })\n }\n />\n ) : (\n <Input\n placeholder=\"Search query...\"\n value={item.value || \"\"}\n onChange={(e) =>\n onUpdateRule(item.id, { value: e.target.value })\n }\n />\n )}\n </div>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => onRemoveItem(item.id)}\n className=\"h-9 w-9 text-slate-300 hover:text-red-500 hover:bg-red-50\"\n >\n <Trash2 className=\"h-5 w-5 text-red-500\" />\n </Button>\n </div>\n ) : (\n <FilterGroupUI\n group={item}\n depth={depth + 1}\n columns={columns}\n onAddRule={onAddRule}\n onAddGroup={onAddGroup}\n onUpdateRule={onUpdateRule}\n onUpdateGroupLogic={onUpdateGroupLogic}\n onRemoveItem={onRemoveItem}\n />\n );\n })}\n {group.items.length === 0 && (\n <div className=\"flex flex-col items-center justify-center py-6 rounded-md border border-dashed border-slate-200 bg-white/50\">\n <ListFilter className=\"h-5 w-5 text-slate-300 mb-1\" />\n <p className=\"text-[10px] text-slate-400 font-bold uppercase tracking-widest\">\n Add a rule to filter\n </p>\n </div>\n )}\n </div>\n </div>\n );\n};\n\nexport const AdvancedFilter = <T,>({\n isOpen,\n onClose,\n data,\n columns,\n setFilteredData,\n initialFilters,\n}: AdvancedFilterProps<T>) => {\n const [rootGroup, setRootGroup] = useState<FilterGroup<T>>({\n type: \"group\",\n id: \"root\",\n logic: \"AND\",\n items: [],\n });\n\n useEffect(() => {\n if (initialFilters) setRootGroup(initialFilters);\n }, [initialFilters, isOpen]);\n\n const updateItemRecursively = useCallback(\n (\n group: FilterGroup<T>,\n targetId: string,\n updater: (item: FilterItem<T>) => FilterItem<T> | null\n ): FilterGroup<T> => {\n if (group.id === targetId) return updater(group) as FilterGroup<T>;\n return {\n ...group,\n items: group.items\n .map((item) => {\n if (item.id === targetId) return updater(item);\n if (item.type === \"group\")\n return updateItemRecursively(item, targetId, updater);\n return item;\n })\n .filter(Boolean) as FilterItem<T>[],\n };\n },\n []\n );\n\n const handleAddRule = useCallback(\n (parentId: string) => {\n const firstCol = columns[0];\n const newRule: FilterRule<T> = {\n type: \"rule\",\n id: Math.random().toString(36).substr(2, 9),\n columnId: firstCol.id,\n operator:\n firstCol.type === \"string\"\n ? \"contains\"\n : firstCol.type === \"select\"\n ? \"equals\"\n : \"equals\",\n value: \"\",\n };\n setRootGroup((prev) =>\n updateItemRecursively(prev, parentId, (group) => ({\n ...(group as FilterGroup<T>),\n items: [...(group as FilterGroup<T>).items, newRule],\n }))\n );\n },\n [columns, updateItemRecursively]\n );\n\n const handleAddGroup = useCallback(\n (parentId: string) => {\n const newGroup: FilterGroup<T> = {\n type: \"group\",\n id: Math.random().toString(36).substr(2, 9),\n logic: \"AND\",\n items: [],\n };\n setRootGroup((prev) =>\n updateItemRecursively(prev, parentId, (group) => ({\n ...(group as FilterGroup<T>),\n items: [...(group as FilterGroup<T>).items, newGroup],\n }))\n );\n },\n [updateItemRecursively]\n );\n\n const handleUpdateRule = useCallback(\n (ruleId: string, updates: Partial<FilterRule<T>>) => {\n setRootGroup((prev) =>\n updateItemRecursively(\n prev,\n ruleId,\n (rule) => ({ ...rule, ...updates } as FilterRule<T>)\n )\n );\n },\n [updateItemRecursively]\n );\n\n const handleUpdateGroupLogic = useCallback(\n (groupId: string, logic: LogicalOperator) => {\n setRootGroup((prev) =>\n updateItemRecursively(\n prev,\n groupId,\n (group) => ({ ...group, logic } as FilterGroup<T>)\n )\n );\n },\n [updateItemRecursively]\n );\n\n const handleRemoveItem = useCallback(\n (itemId: string) => {\n setRootGroup((prev) => updateItemRecursively(prev, itemId, () => null));\n },\n [updateItemRecursively]\n );\n\n const handleApply = () => {\n const filtered = applyFilters(data, rootGroup, columns);\n setFilteredData(filtered);\n onClose();\n };\n\n const handleClear = () => {\n const emptyGroup: FilterGroup<T> = {\n type: \"group\",\n id: \"root\",\n logic: \"AND\",\n items: [],\n };\n setRootGroup(emptyGroup);\n setFilteredData(data);\n onClose();\n };\n\n return (\n <Dialog\n open={isOpen}\n onOpenChange={onClose}\n title=\"Advanced Filters by arijit\"\n description=\"Refine your dataset using structured logic and multi-type comparisons.\"\n >\n <div className=\"max-h-[60vh] overflow-y-auto px-1 custom-scrollbar pr-3 pb-32\">\n <FilterGroupUI\n group={rootGroup}\n depth={0}\n columns={columns}\n onAddRule={handleAddRule}\n onAddGroup={handleAddGroup}\n onUpdateRule={handleUpdateRule}\n onUpdateGroupLogic={handleUpdateGroupLogic}\n onRemoveItem={handleRemoveItem}\n />\n </div>\n <div className=\"flex w-full justify-between items-center gap-4\">\n <Button\n variant=\"ghost\"\n onClick={handleClear}\n className=\"text-slate-400 hover:text-red-500 font-black text-[10px] uppercase tracking-widest gap-2\"\n >\n <FilterX className=\"h-4 w-4\" /> Reset Filters\n </Button>\n <div className=\"flex gap-2\">\n <Button\n variant=\"outline\"\n onClick={onClose}\n className=\"font-bold h-11 px-6\"\n >\n Cancel\n </Button>\n <Button\n onClick={handleApply}\n className=\"bg-slate-900 font-bold h-11 px-10 rounded-lg shadow-lg hover:shadow-xl transition-shadow\"\n >\n Apply View\n </Button>\n </div>\n </div>\n </Dialog>\n );\n};\n"],"names":["_jsxs","_jsx"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,453 +1,453 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { useState, useEffect, useCallback } from "react";
|
|
3
|
-
import {
|
|
4
|
-
FilterRule,
|
|
5
|
-
FilterGroup,
|
|
6
|
-
ColumnDefinition,
|
|
7
|
-
LogicalOperator,
|
|
8
|
-
FilterOperator,
|
|
9
|
-
FilterItem,
|
|
10
|
-
} from "../types";
|
|
11
|
-
import { Button } from "./ui/Button";
|
|
12
|
-
import { Input } from "./ui/Input";
|
|
13
|
-
import { Select } from "./ui/Select";
|
|
14
|
-
import { Dialog } from "./ui/Dialog";
|
|
15
|
-
import {
|
|
16
|
-
Plus,
|
|
17
|
-
Trash2,
|
|
18
|
-
Layers,
|
|
19
|
-
Binary,
|
|
20
|
-
FilterX,
|
|
21
|
-
ListFilter,
|
|
22
|
-
} from "lucide-react";
|
|
23
|
-
import { applyFilters } from "../utils/filterLogic";
|
|
24
|
-
|
|
25
|
-
interface AdvancedFilterProps<T> {
|
|
26
|
-
isOpen: boolean;
|
|
27
|
-
onClose: () => void;
|
|
28
|
-
data: T[];
|
|
29
|
-
columns: ColumnDefinition<T>[];
|
|
30
|
-
setFilteredData: (data: T[]) => void;
|
|
31
|
-
initialFilters?: FilterGroup<T>;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const getOperatorsForType = (
|
|
35
|
-
type: string
|
|
36
|
-
): { value: FilterOperator; label: string }[] => {
|
|
37
|
-
switch (type) {
|
|
38
|
-
case "string":
|
|
39
|
-
return [
|
|
40
|
-
{ value: "contains", label: "Contains" },
|
|
41
|
-
{ value: "not_contains", label: "Does not contain" },
|
|
42
|
-
{ value: "equals", label: "Exact match" },
|
|
43
|
-
];
|
|
44
|
-
case "select":
|
|
45
|
-
return [
|
|
46
|
-
{ value: "equals", label: "Is" },
|
|
47
|
-
{ value: "not_equals", label: "Is not" },
|
|
48
|
-
];
|
|
49
|
-
case "number":
|
|
50
|
-
return [
|
|
51
|
-
{ value: "equals", label: "=" },
|
|
52
|
-
{ value: "not_equals", label: "!=" },
|
|
53
|
-
{ value: "gt", label: ">" },
|
|
54
|
-
{ value: "lt", label: "<" },
|
|
55
|
-
{ value: "gte", label: ">=" },
|
|
56
|
-
{ value: "lte", label: "<=" },
|
|
57
|
-
];
|
|
58
|
-
case "date":
|
|
59
|
-
return [
|
|
60
|
-
{ value: "is", label: "On date" },
|
|
61
|
-
{ value: "before", label: "Before" },
|
|
62
|
-
{ value: "after", label: "After" },
|
|
63
|
-
];
|
|
64
|
-
case "boolean":
|
|
65
|
-
return [
|
|
66
|
-
{ value: "true", label: "True" },
|
|
67
|
-
{ value: "false", label: "False" },
|
|
68
|
-
];
|
|
69
|
-
default:
|
|
70
|
-
return [];
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const FilterGroupUI = <T,>({
|
|
75
|
-
group,
|
|
76
|
-
depth,
|
|
77
|
-
columns,
|
|
78
|
-
onAddRule,
|
|
79
|
-
onAddGroup,
|
|
80
|
-
onUpdateRule,
|
|
81
|
-
onUpdateGroupLogic,
|
|
82
|
-
onRemoveItem,
|
|
83
|
-
}: {
|
|
84
|
-
group: FilterGroup<T>;
|
|
85
|
-
depth: number;
|
|
86
|
-
columns: ColumnDefinition<T>[];
|
|
87
|
-
onAddRule: (parentId: string) => void;
|
|
88
|
-
onAddGroup: (parentId: string) => void;
|
|
89
|
-
onUpdateRule: (ruleId: string, updates: Partial<FilterRule<T>>) => void;
|
|
90
|
-
onUpdateGroupLogic: (groupId: string, logic: LogicalOperator) => void;
|
|
91
|
-
onRemoveItem: (itemId: string) => void;
|
|
92
|
-
}) => {
|
|
93
|
-
return (
|
|
94
|
-
<div
|
|
95
|
-
className={`rounded-lg border border-slate-200 bg-slate-50/50 p-4 mb-4 ${
|
|
96
|
-
depth > 0 ? "ml-6 md:ml-10 relative border-l-2 border-l-slate-300" : ""
|
|
97
|
-
}`}
|
|
98
|
-
>
|
|
99
|
-
<div className="flex flex-wrap items-center justify-between gap-4 mb-4">
|
|
100
|
-
<div className="flex items-center gap-2">
|
|
101
|
-
<span className="text-[10px] font-bold uppercase tracking-widest text-slate-500">
|
|
102
|
-
Group Logic
|
|
103
|
-
</span>
|
|
104
|
-
<div className="flex rounded-md border border-slate-200 bg-white p-1 shadow-sm">
|
|
105
|
-
<button
|
|
106
|
-
onClick={() => onUpdateGroupLogic(group.id, "AND")}
|
|
107
|
-
className={`px-3 py-1 text-[10px] font-black rounded-sm transition-all ${
|
|
108
|
-
group.logic === "AND"
|
|
109
|
-
? "bg-slate-900 text-slate-50"
|
|
110
|
-
: "text-slate-500 hover:text-slate-900"
|
|
111
|
-
}`}
|
|
112
|
-
>
|
|
113
|
-
AND
|
|
114
|
-
</button>
|
|
115
|
-
<button
|
|
116
|
-
onClick={() => onUpdateGroupLogic(group.id, "OR")}
|
|
117
|
-
className={`px-3 py-1 text-[10px] font-black rounded-sm transition-all ${
|
|
118
|
-
group.logic === "OR"
|
|
119
|
-
? "bg-slate-900 text-slate-50"
|
|
120
|
-
: "text-slate-500 hover:text-slate-900"
|
|
121
|
-
}`}
|
|
122
|
-
>
|
|
123
|
-
OR
|
|
124
|
-
</button>
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
|
|
128
|
-
<div className="flex items-center gap-2">
|
|
129
|
-
<Button
|
|
130
|
-
variant="outline"
|
|
131
|
-
size="sm"
|
|
132
|
-
onClick={() => onAddRule(group.id)}
|
|
133
|
-
className="h-7 text-[10px] font-bold uppercase"
|
|
134
|
-
>
|
|
135
|
-
<Plus className="mr-1 h-3 w-3" /> Rule
|
|
136
|
-
</Button>
|
|
137
|
-
<Button
|
|
138
|
-
variant="outline"
|
|
139
|
-
size="sm"
|
|
140
|
-
onClick={() => onAddGroup(group.id)}
|
|
141
|
-
className="h-7 text-[10px] font-bold uppercase"
|
|
142
|
-
>
|
|
143
|
-
<Layers className="mr-1 h-3 w-3" /> Group
|
|
144
|
-
</Button>
|
|
145
|
-
{depth > 0 && (
|
|
146
|
-
<Button
|
|
147
|
-
variant="ghost"
|
|
148
|
-
size="sm"
|
|
149
|
-
onClick={() => onRemoveItem(group.id)}
|
|
150
|
-
className="text-slate-400"
|
|
151
|
-
>
|
|
152
|
-
<Trash2 className="h-5 w-5 text-red-500" />
|
|
153
|
-
</Button>
|
|
154
|
-
)}
|
|
155
|
-
</div>
|
|
156
|
-
</div>
|
|
157
|
-
|
|
158
|
-
<div className="space-y-3">
|
|
159
|
-
{group.items.map((item) => {
|
|
160
|
-
const column = columns.find(
|
|
161
|
-
(c) => c.id === (item.type === "rule" ? item.columnId : null)
|
|
162
|
-
);
|
|
163
|
-
return item.type === "rule" ? (
|
|
164
|
-
<div
|
|
165
|
-
key={item.id}
|
|
166
|
-
className="flex flex-col md:flex-row md:items-center gap-3 p-3 bg-white border border-slate-200 rounded-md shadow-sm"
|
|
167
|
-
>
|
|
168
|
-
<div className="flex-1 min-w-[140px]">
|
|
169
|
-
<Select
|
|
170
|
-
value={item.columnId as string}
|
|
171
|
-
options={columns.map((c) => ({
|
|
172
|
-
value: c.id as string,
|
|
173
|
-
label: c.label,
|
|
174
|
-
}))}
|
|
175
|
-
onValueChange={(val) => {
|
|
176
|
-
const colId = val as keyof T;
|
|
177
|
-
const col = columns.find((c) => c.id === colId);
|
|
178
|
-
onUpdateRule(item.id, {
|
|
179
|
-
columnId: colId,
|
|
180
|
-
operator:
|
|
181
|
-
col?.type === "date"
|
|
182
|
-
? "is"
|
|
183
|
-
: col?.type === "number"
|
|
184
|
-
? "equals"
|
|
185
|
-
: col?.type === "select"
|
|
186
|
-
? "equals"
|
|
187
|
-
: "contains",
|
|
188
|
-
value: "",
|
|
189
|
-
});
|
|
190
|
-
}}
|
|
191
|
-
/>
|
|
192
|
-
</div>
|
|
193
|
-
<div className="w-full md:w-44">
|
|
194
|
-
<Select
|
|
195
|
-
value={item.operator}
|
|
196
|
-
options={getOperatorsForType(column?.type || "string")}
|
|
197
|
-
onValueChange={(val) =>
|
|
198
|
-
onUpdateRule(item.id, { operator: val as FilterOperator })
|
|
199
|
-
}
|
|
200
|
-
/>
|
|
201
|
-
</div>
|
|
202
|
-
<div className="flex-[1.5] min-w-[180px]">
|
|
203
|
-
{column?.type === "date" ? (
|
|
204
|
-
<Input
|
|
205
|
-
type="date"
|
|
206
|
-
value={item.value || ""}
|
|
207
|
-
onChange={(e) =>
|
|
208
|
-
onUpdateRule(item.id, { value: e.target.value })
|
|
209
|
-
}
|
|
210
|
-
/>
|
|
211
|
-
) : column?.type === "boolean" ? (
|
|
212
|
-
<div className="h-10 flex items-center px-3 border border-slate-200 rounded-md bg-slate-50 text-[10px] font-black uppercase tracking-tighter text-slate-400 italic">
|
|
213
|
-
<Binary className="h-3 w-3 mr-2" />
|
|
214
|
-
Binary state
|
|
215
|
-
</div>
|
|
216
|
-
) : column?.type === "select" ? (
|
|
217
|
-
<Select
|
|
218
|
-
value={item.value || ""}
|
|
219
|
-
placeholder="Choose option..."
|
|
220
|
-
options={(column.options || []).map((opt) => ({
|
|
221
|
-
value: opt,
|
|
222
|
-
label: opt,
|
|
223
|
-
}))}
|
|
224
|
-
onValueChange={(val) =>
|
|
225
|
-
onUpdateRule(item.id, { value: val })
|
|
226
|
-
}
|
|
227
|
-
/>
|
|
228
|
-
) : (
|
|
229
|
-
<Input
|
|
230
|
-
placeholder="Search query..."
|
|
231
|
-
value={item.value || ""}
|
|
232
|
-
onChange={(e) =>
|
|
233
|
-
onUpdateRule(item.id, { value: e.target.value })
|
|
234
|
-
}
|
|
235
|
-
/>
|
|
236
|
-
)}
|
|
237
|
-
</div>
|
|
238
|
-
<Button
|
|
239
|
-
variant="ghost"
|
|
240
|
-
size="icon"
|
|
241
|
-
onClick={() => onRemoveItem(item.id)}
|
|
242
|
-
className="h-9 w-9 text-slate-300 hover:text-red-500 hover:bg-red-50"
|
|
243
|
-
>
|
|
244
|
-
<Trash2 className="h-5 w-5 text-red-500" />
|
|
245
|
-
</Button>
|
|
246
|
-
</div>
|
|
247
|
-
) : (
|
|
248
|
-
<FilterGroupUI
|
|
249
|
-
group={item}
|
|
250
|
-
depth={depth + 1}
|
|
251
|
-
columns={columns}
|
|
252
|
-
onAddRule={onAddRule}
|
|
253
|
-
onAddGroup={onAddGroup}
|
|
254
|
-
onUpdateRule={onUpdateRule}
|
|
255
|
-
onUpdateGroupLogic={onUpdateGroupLogic}
|
|
256
|
-
onRemoveItem={onRemoveItem}
|
|
257
|
-
/>
|
|
258
|
-
);
|
|
259
|
-
})}
|
|
260
|
-
{group.items.length === 0 && (
|
|
261
|
-
<div className="flex flex-col items-center justify-center py-6 rounded-md border border-dashed border-slate-200 bg-white/50">
|
|
262
|
-
<ListFilter className="h-5 w-5 text-slate-300 mb-1" />
|
|
263
|
-
<p className="text-[10px] text-slate-400 font-bold uppercase tracking-widest">
|
|
264
|
-
Add a rule to filter
|
|
265
|
-
</p>
|
|
266
|
-
</div>
|
|
267
|
-
)}
|
|
268
|
-
</div>
|
|
269
|
-
</div>
|
|
270
|
-
);
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
export const AdvancedFilter = <T,>({
|
|
274
|
-
isOpen,
|
|
275
|
-
onClose,
|
|
276
|
-
data,
|
|
277
|
-
columns,
|
|
278
|
-
setFilteredData,
|
|
279
|
-
initialFilters,
|
|
280
|
-
}: AdvancedFilterProps<T>) => {
|
|
281
|
-
const [rootGroup, setRootGroup] = useState<FilterGroup<T>>({
|
|
282
|
-
type: "group",
|
|
283
|
-
id: "root",
|
|
284
|
-
logic: "AND",
|
|
285
|
-
items: [],
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
useEffect(() => {
|
|
289
|
-
if (initialFilters) setRootGroup(initialFilters);
|
|
290
|
-
}, [initialFilters, isOpen]);
|
|
291
|
-
|
|
292
|
-
const updateItemRecursively = useCallback(
|
|
293
|
-
(
|
|
294
|
-
group: FilterGroup<T>,
|
|
295
|
-
targetId: string,
|
|
296
|
-
updater: (item: FilterItem<T>) => FilterItem<T> | null
|
|
297
|
-
): FilterGroup<T> => {
|
|
298
|
-
if (group.id === targetId) return updater(group) as FilterGroup<T>;
|
|
299
|
-
return {
|
|
300
|
-
...group,
|
|
301
|
-
items: group.items
|
|
302
|
-
.map((item) => {
|
|
303
|
-
if (item.id === targetId) return updater(item);
|
|
304
|
-
if (item.type === "group")
|
|
305
|
-
return updateItemRecursively(item, targetId, updater);
|
|
306
|
-
return item;
|
|
307
|
-
})
|
|
308
|
-
.filter(Boolean) as FilterItem<T>[],
|
|
309
|
-
};
|
|
310
|
-
},
|
|
311
|
-
[]
|
|
312
|
-
);
|
|
313
|
-
|
|
314
|
-
const handleAddRule = useCallback(
|
|
315
|
-
(parentId: string) => {
|
|
316
|
-
const firstCol = columns[0];
|
|
317
|
-
const newRule: FilterRule<T> = {
|
|
318
|
-
type: "rule",
|
|
319
|
-
id: Math.random().toString(36).substr(2, 9),
|
|
320
|
-
columnId: firstCol.id,
|
|
321
|
-
operator:
|
|
322
|
-
firstCol.type === "string"
|
|
323
|
-
? "contains"
|
|
324
|
-
: firstCol.type === "select"
|
|
325
|
-
? "equals"
|
|
326
|
-
: "equals",
|
|
327
|
-
value: "",
|
|
328
|
-
};
|
|
329
|
-
setRootGroup((prev) =>
|
|
330
|
-
updateItemRecursively(prev, parentId, (group) => ({
|
|
331
|
-
...(group as FilterGroup<T>),
|
|
332
|
-
items: [...(group as FilterGroup<T>).items, newRule],
|
|
333
|
-
}))
|
|
334
|
-
);
|
|
335
|
-
},
|
|
336
|
-
[columns, updateItemRecursively]
|
|
337
|
-
);
|
|
338
|
-
|
|
339
|
-
const handleAddGroup = useCallback(
|
|
340
|
-
(parentId: string) => {
|
|
341
|
-
const newGroup: FilterGroup<T> = {
|
|
342
|
-
type: "group",
|
|
343
|
-
id: Math.random().toString(36).substr(2, 9),
|
|
344
|
-
logic: "AND",
|
|
345
|
-
items: [],
|
|
346
|
-
};
|
|
347
|
-
setRootGroup((prev) =>
|
|
348
|
-
updateItemRecursively(prev, parentId, (group) => ({
|
|
349
|
-
...(group as FilterGroup<T>),
|
|
350
|
-
items: [...(group as FilterGroup<T>).items, newGroup],
|
|
351
|
-
}))
|
|
352
|
-
);
|
|
353
|
-
},
|
|
354
|
-
[updateItemRecursively]
|
|
355
|
-
);
|
|
356
|
-
|
|
357
|
-
const handleUpdateRule = useCallback(
|
|
358
|
-
(ruleId: string, updates: Partial<FilterRule<T>>) => {
|
|
359
|
-
setRootGroup((prev) =>
|
|
360
|
-
updateItemRecursively(
|
|
361
|
-
prev,
|
|
362
|
-
ruleId,
|
|
363
|
-
(rule) => ({ ...rule, ...updates } as FilterRule<T>)
|
|
364
|
-
)
|
|
365
|
-
);
|
|
366
|
-
},
|
|
367
|
-
[updateItemRecursively]
|
|
368
|
-
);
|
|
369
|
-
|
|
370
|
-
const handleUpdateGroupLogic = useCallback(
|
|
371
|
-
(groupId: string, logic: LogicalOperator) => {
|
|
372
|
-
setRootGroup((prev) =>
|
|
373
|
-
updateItemRecursively(
|
|
374
|
-
prev,
|
|
375
|
-
groupId,
|
|
376
|
-
(group) => ({ ...group, logic } as FilterGroup<T>)
|
|
377
|
-
)
|
|
378
|
-
);
|
|
379
|
-
},
|
|
380
|
-
[updateItemRecursively]
|
|
381
|
-
);
|
|
382
|
-
|
|
383
|
-
const handleRemoveItem = useCallback(
|
|
384
|
-
(itemId: string) => {
|
|
385
|
-
setRootGroup((prev) => updateItemRecursively(prev, itemId, () => null));
|
|
386
|
-
},
|
|
387
|
-
[updateItemRecursively]
|
|
388
|
-
);
|
|
389
|
-
|
|
390
|
-
const handleApply = () => {
|
|
391
|
-
const filtered = applyFilters(data, rootGroup, columns);
|
|
392
|
-
setFilteredData(filtered);
|
|
393
|
-
onClose();
|
|
394
|
-
};
|
|
395
|
-
|
|
396
|
-
const handleClear = () => {
|
|
397
|
-
const emptyGroup: FilterGroup<T> = {
|
|
398
|
-
type: "group",
|
|
399
|
-
id: "root",
|
|
400
|
-
logic: "AND",
|
|
401
|
-
items: [],
|
|
402
|
-
};
|
|
403
|
-
setRootGroup(emptyGroup);
|
|
404
|
-
setFilteredData(data);
|
|
405
|
-
onClose();
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
return (
|
|
409
|
-
<Dialog
|
|
410
|
-
open={isOpen}
|
|
411
|
-
onOpenChange={onClose}
|
|
412
|
-
title="Advanced Filters"
|
|
413
|
-
description="Refine your dataset using structured logic and multi-type comparisons."
|
|
414
|
-
>
|
|
415
|
-
<div className="max-h-[60vh] overflow-y-auto px-1 custom-scrollbar pr-3 pb-32">
|
|
416
|
-
<FilterGroupUI
|
|
417
|
-
group={rootGroup}
|
|
418
|
-
depth={0}
|
|
419
|
-
columns={columns}
|
|
420
|
-
onAddRule={handleAddRule}
|
|
421
|
-
onAddGroup={handleAddGroup}
|
|
422
|
-
onUpdateRule={handleUpdateRule}
|
|
423
|
-
onUpdateGroupLogic={handleUpdateGroupLogic}
|
|
424
|
-
onRemoveItem={handleRemoveItem}
|
|
425
|
-
/>
|
|
426
|
-
</div>
|
|
427
|
-
<div className="flex w-full justify-between items-center gap-4">
|
|
428
|
-
<Button
|
|
429
|
-
variant="ghost"
|
|
430
|
-
onClick={handleClear}
|
|
431
|
-
className="text-slate-400 hover:text-red-500 font-black text-[10px] uppercase tracking-widest gap-2"
|
|
432
|
-
>
|
|
433
|
-
<FilterX className="h-4 w-4" /> Reset Filters
|
|
434
|
-
</Button>
|
|
435
|
-
<div className="flex gap-2">
|
|
436
|
-
<Button
|
|
437
|
-
variant="outline"
|
|
438
|
-
onClick={onClose}
|
|
439
|
-
className="font-bold h-11 px-6"
|
|
440
|
-
>
|
|
441
|
-
Cancel
|
|
442
|
-
</Button>
|
|
443
|
-
<Button
|
|
444
|
-
onClick={handleApply}
|
|
445
|
-
className="bg-slate-900 font-bold h-11 px-10 rounded-lg shadow-lg hover:shadow-xl transition-shadow"
|
|
446
|
-
>
|
|
447
|
-
Apply View
|
|
448
|
-
</Button>
|
|
449
|
-
</div>
|
|
450
|
-
</div>
|
|
451
|
-
</Dialog>
|
|
452
|
-
);
|
|
453
|
-
};
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useState, useEffect, useCallback } from "react";
|
|
3
|
+
import {
|
|
4
|
+
FilterRule,
|
|
5
|
+
FilterGroup,
|
|
6
|
+
ColumnDefinition,
|
|
7
|
+
LogicalOperator,
|
|
8
|
+
FilterOperator,
|
|
9
|
+
FilterItem,
|
|
10
|
+
} from "../types";
|
|
11
|
+
import { Button } from "./ui/Button";
|
|
12
|
+
import { Input } from "./ui/Input";
|
|
13
|
+
import { Select } from "./ui/Select";
|
|
14
|
+
import { Dialog } from "./ui/Dialog";
|
|
15
|
+
import {
|
|
16
|
+
Plus,
|
|
17
|
+
Trash2,
|
|
18
|
+
Layers,
|
|
19
|
+
Binary,
|
|
20
|
+
FilterX,
|
|
21
|
+
ListFilter,
|
|
22
|
+
} from "lucide-react";
|
|
23
|
+
import { applyFilters } from "../utils/filterLogic";
|
|
24
|
+
|
|
25
|
+
interface AdvancedFilterProps<T> {
|
|
26
|
+
isOpen: boolean;
|
|
27
|
+
onClose: () => void;
|
|
28
|
+
data: T[];
|
|
29
|
+
columns: ColumnDefinition<T>[];
|
|
30
|
+
setFilteredData: (data: T[]) => void;
|
|
31
|
+
initialFilters?: FilterGroup<T>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const getOperatorsForType = (
|
|
35
|
+
type: string
|
|
36
|
+
): { value: FilterOperator; label: string }[] => {
|
|
37
|
+
switch (type) {
|
|
38
|
+
case "string":
|
|
39
|
+
return [
|
|
40
|
+
{ value: "contains", label: "Contains" },
|
|
41
|
+
{ value: "not_contains", label: "Does not contain" },
|
|
42
|
+
{ value: "equals", label: "Exact match" },
|
|
43
|
+
];
|
|
44
|
+
case "select":
|
|
45
|
+
return [
|
|
46
|
+
{ value: "equals", label: "Is" },
|
|
47
|
+
{ value: "not_equals", label: "Is not" },
|
|
48
|
+
];
|
|
49
|
+
case "number":
|
|
50
|
+
return [
|
|
51
|
+
{ value: "equals", label: "=" },
|
|
52
|
+
{ value: "not_equals", label: "!=" },
|
|
53
|
+
{ value: "gt", label: ">" },
|
|
54
|
+
{ value: "lt", label: "<" },
|
|
55
|
+
{ value: "gte", label: ">=" },
|
|
56
|
+
{ value: "lte", label: "<=" },
|
|
57
|
+
];
|
|
58
|
+
case "date":
|
|
59
|
+
return [
|
|
60
|
+
{ value: "is", label: "On date" },
|
|
61
|
+
{ value: "before", label: "Before" },
|
|
62
|
+
{ value: "after", label: "After" },
|
|
63
|
+
];
|
|
64
|
+
case "boolean":
|
|
65
|
+
return [
|
|
66
|
+
{ value: "true", label: "True" },
|
|
67
|
+
{ value: "false", label: "False" },
|
|
68
|
+
];
|
|
69
|
+
default:
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const FilterGroupUI = <T,>({
|
|
75
|
+
group,
|
|
76
|
+
depth,
|
|
77
|
+
columns,
|
|
78
|
+
onAddRule,
|
|
79
|
+
onAddGroup,
|
|
80
|
+
onUpdateRule,
|
|
81
|
+
onUpdateGroupLogic,
|
|
82
|
+
onRemoveItem,
|
|
83
|
+
}: {
|
|
84
|
+
group: FilterGroup<T>;
|
|
85
|
+
depth: number;
|
|
86
|
+
columns: ColumnDefinition<T>[];
|
|
87
|
+
onAddRule: (parentId: string) => void;
|
|
88
|
+
onAddGroup: (parentId: string) => void;
|
|
89
|
+
onUpdateRule: (ruleId: string, updates: Partial<FilterRule<T>>) => void;
|
|
90
|
+
onUpdateGroupLogic: (groupId: string, logic: LogicalOperator) => void;
|
|
91
|
+
onRemoveItem: (itemId: string) => void;
|
|
92
|
+
}) => {
|
|
93
|
+
return (
|
|
94
|
+
<div
|
|
95
|
+
className={`rounded-lg border border-slate-200 bg-slate-50/50 p-4 mb-4 ${
|
|
96
|
+
depth > 0 ? "ml-6 md:ml-10 relative border-l-2 border-l-slate-300" : ""
|
|
97
|
+
}`}
|
|
98
|
+
>
|
|
99
|
+
<div className="flex flex-wrap items-center justify-between gap-4 mb-4">
|
|
100
|
+
<div className="flex items-center gap-2">
|
|
101
|
+
<span className="text-[10px] font-bold uppercase tracking-widest text-slate-500">
|
|
102
|
+
Group Logic
|
|
103
|
+
</span>
|
|
104
|
+
<div className="flex rounded-md border border-slate-200 bg-white p-1 shadow-sm">
|
|
105
|
+
<button
|
|
106
|
+
onClick={() => onUpdateGroupLogic(group.id, "AND")}
|
|
107
|
+
className={`px-3 py-1 text-[10px] font-black rounded-sm transition-all ${
|
|
108
|
+
group.logic === "AND"
|
|
109
|
+
? "bg-slate-900 text-slate-50"
|
|
110
|
+
: "text-slate-500 hover:text-slate-900"
|
|
111
|
+
}`}
|
|
112
|
+
>
|
|
113
|
+
AND
|
|
114
|
+
</button>
|
|
115
|
+
<button
|
|
116
|
+
onClick={() => onUpdateGroupLogic(group.id, "OR")}
|
|
117
|
+
className={`px-3 py-1 text-[10px] font-black rounded-sm transition-all ${
|
|
118
|
+
group.logic === "OR"
|
|
119
|
+
? "bg-slate-900 text-slate-50"
|
|
120
|
+
: "text-slate-500 hover:text-slate-900"
|
|
121
|
+
}`}
|
|
122
|
+
>
|
|
123
|
+
OR
|
|
124
|
+
</button>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<div className="flex items-center gap-2">
|
|
129
|
+
<Button
|
|
130
|
+
variant="outline"
|
|
131
|
+
size="sm"
|
|
132
|
+
onClick={() => onAddRule(group.id)}
|
|
133
|
+
className="h-7 text-[10px] font-bold uppercase"
|
|
134
|
+
>
|
|
135
|
+
<Plus className="mr-1 h-3 w-3" /> Rule
|
|
136
|
+
</Button>
|
|
137
|
+
<Button
|
|
138
|
+
variant="outline"
|
|
139
|
+
size="sm"
|
|
140
|
+
onClick={() => onAddGroup(group.id)}
|
|
141
|
+
className="h-7 text-[10px] font-bold uppercase"
|
|
142
|
+
>
|
|
143
|
+
<Layers className="mr-1 h-3 w-3" /> Group
|
|
144
|
+
</Button>
|
|
145
|
+
{depth > 0 && (
|
|
146
|
+
<Button
|
|
147
|
+
variant="ghost"
|
|
148
|
+
size="sm"
|
|
149
|
+
onClick={() => onRemoveItem(group.id)}
|
|
150
|
+
className="text-slate-400"
|
|
151
|
+
>
|
|
152
|
+
<Trash2 className="h-5 w-5 text-red-500" />
|
|
153
|
+
</Button>
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<div className="space-y-3">
|
|
159
|
+
{group.items.map((item) => {
|
|
160
|
+
const column = columns.find(
|
|
161
|
+
(c) => c.id === (item.type === "rule" ? item.columnId : null)
|
|
162
|
+
);
|
|
163
|
+
return item.type === "rule" ? (
|
|
164
|
+
<div
|
|
165
|
+
key={item.id}
|
|
166
|
+
className="flex flex-col md:flex-row md:items-center gap-3 p-3 bg-white border border-slate-200 rounded-md shadow-sm"
|
|
167
|
+
>
|
|
168
|
+
<div className="flex-1 min-w-[140px]">
|
|
169
|
+
<Select
|
|
170
|
+
value={item.columnId as string}
|
|
171
|
+
options={columns.map((c) => ({
|
|
172
|
+
value: c.id as string,
|
|
173
|
+
label: c.label,
|
|
174
|
+
}))}
|
|
175
|
+
onValueChange={(val) => {
|
|
176
|
+
const colId = val as keyof T;
|
|
177
|
+
const col = columns.find((c) => c.id === colId);
|
|
178
|
+
onUpdateRule(item.id, {
|
|
179
|
+
columnId: colId,
|
|
180
|
+
operator:
|
|
181
|
+
col?.type === "date"
|
|
182
|
+
? "is"
|
|
183
|
+
: col?.type === "number"
|
|
184
|
+
? "equals"
|
|
185
|
+
: col?.type === "select"
|
|
186
|
+
? "equals"
|
|
187
|
+
: "contains",
|
|
188
|
+
value: "",
|
|
189
|
+
});
|
|
190
|
+
}}
|
|
191
|
+
/>
|
|
192
|
+
</div>
|
|
193
|
+
<div className="w-full md:w-44">
|
|
194
|
+
<Select
|
|
195
|
+
value={item.operator}
|
|
196
|
+
options={getOperatorsForType(column?.type || "string")}
|
|
197
|
+
onValueChange={(val) =>
|
|
198
|
+
onUpdateRule(item.id, { operator: val as FilterOperator })
|
|
199
|
+
}
|
|
200
|
+
/>
|
|
201
|
+
</div>
|
|
202
|
+
<div className="flex-[1.5] min-w-[180px]">
|
|
203
|
+
{column?.type === "date" ? (
|
|
204
|
+
<Input
|
|
205
|
+
type="date"
|
|
206
|
+
value={item.value || ""}
|
|
207
|
+
onChange={(e) =>
|
|
208
|
+
onUpdateRule(item.id, { value: e.target.value })
|
|
209
|
+
}
|
|
210
|
+
/>
|
|
211
|
+
) : column?.type === "boolean" ? (
|
|
212
|
+
<div className="h-10 flex items-center px-3 border border-slate-200 rounded-md bg-slate-50 text-[10px] font-black uppercase tracking-tighter text-slate-400 italic">
|
|
213
|
+
<Binary className="h-3 w-3 mr-2" />
|
|
214
|
+
Binary state
|
|
215
|
+
</div>
|
|
216
|
+
) : column?.type === "select" ? (
|
|
217
|
+
<Select
|
|
218
|
+
value={item.value || ""}
|
|
219
|
+
placeholder="Choose option..."
|
|
220
|
+
options={(column.options || []).map((opt) => ({
|
|
221
|
+
value: opt,
|
|
222
|
+
label: opt,
|
|
223
|
+
}))}
|
|
224
|
+
onValueChange={(val) =>
|
|
225
|
+
onUpdateRule(item.id, { value: val })
|
|
226
|
+
}
|
|
227
|
+
/>
|
|
228
|
+
) : (
|
|
229
|
+
<Input
|
|
230
|
+
placeholder="Search query..."
|
|
231
|
+
value={item.value || ""}
|
|
232
|
+
onChange={(e) =>
|
|
233
|
+
onUpdateRule(item.id, { value: e.target.value })
|
|
234
|
+
}
|
|
235
|
+
/>
|
|
236
|
+
)}
|
|
237
|
+
</div>
|
|
238
|
+
<Button
|
|
239
|
+
variant="ghost"
|
|
240
|
+
size="icon"
|
|
241
|
+
onClick={() => onRemoveItem(item.id)}
|
|
242
|
+
className="h-9 w-9 text-slate-300 hover:text-red-500 hover:bg-red-50"
|
|
243
|
+
>
|
|
244
|
+
<Trash2 className="h-5 w-5 text-red-500" />
|
|
245
|
+
</Button>
|
|
246
|
+
</div>
|
|
247
|
+
) : (
|
|
248
|
+
<FilterGroupUI
|
|
249
|
+
group={item}
|
|
250
|
+
depth={depth + 1}
|
|
251
|
+
columns={columns}
|
|
252
|
+
onAddRule={onAddRule}
|
|
253
|
+
onAddGroup={onAddGroup}
|
|
254
|
+
onUpdateRule={onUpdateRule}
|
|
255
|
+
onUpdateGroupLogic={onUpdateGroupLogic}
|
|
256
|
+
onRemoveItem={onRemoveItem}
|
|
257
|
+
/>
|
|
258
|
+
);
|
|
259
|
+
})}
|
|
260
|
+
{group.items.length === 0 && (
|
|
261
|
+
<div className="flex flex-col items-center justify-center py-6 rounded-md border border-dashed border-slate-200 bg-white/50">
|
|
262
|
+
<ListFilter className="h-5 w-5 text-slate-300 mb-1" />
|
|
263
|
+
<p className="text-[10px] text-slate-400 font-bold uppercase tracking-widest">
|
|
264
|
+
Add a rule to filter
|
|
265
|
+
</p>
|
|
266
|
+
</div>
|
|
267
|
+
)}
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
);
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
export const AdvancedFilter = <T,>({
|
|
274
|
+
isOpen,
|
|
275
|
+
onClose,
|
|
276
|
+
data,
|
|
277
|
+
columns,
|
|
278
|
+
setFilteredData,
|
|
279
|
+
initialFilters,
|
|
280
|
+
}: AdvancedFilterProps<T>) => {
|
|
281
|
+
const [rootGroup, setRootGroup] = useState<FilterGroup<T>>({
|
|
282
|
+
type: "group",
|
|
283
|
+
id: "root",
|
|
284
|
+
logic: "AND",
|
|
285
|
+
items: [],
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
useEffect(() => {
|
|
289
|
+
if (initialFilters) setRootGroup(initialFilters);
|
|
290
|
+
}, [initialFilters, isOpen]);
|
|
291
|
+
|
|
292
|
+
const updateItemRecursively = useCallback(
|
|
293
|
+
(
|
|
294
|
+
group: FilterGroup<T>,
|
|
295
|
+
targetId: string,
|
|
296
|
+
updater: (item: FilterItem<T>) => FilterItem<T> | null
|
|
297
|
+
): FilterGroup<T> => {
|
|
298
|
+
if (group.id === targetId) return updater(group) as FilterGroup<T>;
|
|
299
|
+
return {
|
|
300
|
+
...group,
|
|
301
|
+
items: group.items
|
|
302
|
+
.map((item) => {
|
|
303
|
+
if (item.id === targetId) return updater(item);
|
|
304
|
+
if (item.type === "group")
|
|
305
|
+
return updateItemRecursively(item, targetId, updater);
|
|
306
|
+
return item;
|
|
307
|
+
})
|
|
308
|
+
.filter(Boolean) as FilterItem<T>[],
|
|
309
|
+
};
|
|
310
|
+
},
|
|
311
|
+
[]
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const handleAddRule = useCallback(
|
|
315
|
+
(parentId: string) => {
|
|
316
|
+
const firstCol = columns[0];
|
|
317
|
+
const newRule: FilterRule<T> = {
|
|
318
|
+
type: "rule",
|
|
319
|
+
id: Math.random().toString(36).substr(2, 9),
|
|
320
|
+
columnId: firstCol.id,
|
|
321
|
+
operator:
|
|
322
|
+
firstCol.type === "string"
|
|
323
|
+
? "contains"
|
|
324
|
+
: firstCol.type === "select"
|
|
325
|
+
? "equals"
|
|
326
|
+
: "equals",
|
|
327
|
+
value: "",
|
|
328
|
+
};
|
|
329
|
+
setRootGroup((prev) =>
|
|
330
|
+
updateItemRecursively(prev, parentId, (group) => ({
|
|
331
|
+
...(group as FilterGroup<T>),
|
|
332
|
+
items: [...(group as FilterGroup<T>).items, newRule],
|
|
333
|
+
}))
|
|
334
|
+
);
|
|
335
|
+
},
|
|
336
|
+
[columns, updateItemRecursively]
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
const handleAddGroup = useCallback(
|
|
340
|
+
(parentId: string) => {
|
|
341
|
+
const newGroup: FilterGroup<T> = {
|
|
342
|
+
type: "group",
|
|
343
|
+
id: Math.random().toString(36).substr(2, 9),
|
|
344
|
+
logic: "AND",
|
|
345
|
+
items: [],
|
|
346
|
+
};
|
|
347
|
+
setRootGroup((prev) =>
|
|
348
|
+
updateItemRecursively(prev, parentId, (group) => ({
|
|
349
|
+
...(group as FilterGroup<T>),
|
|
350
|
+
items: [...(group as FilterGroup<T>).items, newGroup],
|
|
351
|
+
}))
|
|
352
|
+
);
|
|
353
|
+
},
|
|
354
|
+
[updateItemRecursively]
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const handleUpdateRule = useCallback(
|
|
358
|
+
(ruleId: string, updates: Partial<FilterRule<T>>) => {
|
|
359
|
+
setRootGroup((prev) =>
|
|
360
|
+
updateItemRecursively(
|
|
361
|
+
prev,
|
|
362
|
+
ruleId,
|
|
363
|
+
(rule) => ({ ...rule, ...updates } as FilterRule<T>)
|
|
364
|
+
)
|
|
365
|
+
);
|
|
366
|
+
},
|
|
367
|
+
[updateItemRecursively]
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
const handleUpdateGroupLogic = useCallback(
|
|
371
|
+
(groupId: string, logic: LogicalOperator) => {
|
|
372
|
+
setRootGroup((prev) =>
|
|
373
|
+
updateItemRecursively(
|
|
374
|
+
prev,
|
|
375
|
+
groupId,
|
|
376
|
+
(group) => ({ ...group, logic } as FilterGroup<T>)
|
|
377
|
+
)
|
|
378
|
+
);
|
|
379
|
+
},
|
|
380
|
+
[updateItemRecursively]
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
const handleRemoveItem = useCallback(
|
|
384
|
+
(itemId: string) => {
|
|
385
|
+
setRootGroup((prev) => updateItemRecursively(prev, itemId, () => null));
|
|
386
|
+
},
|
|
387
|
+
[updateItemRecursively]
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
const handleApply = () => {
|
|
391
|
+
const filtered = applyFilters(data, rootGroup, columns);
|
|
392
|
+
setFilteredData(filtered);
|
|
393
|
+
onClose();
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const handleClear = () => {
|
|
397
|
+
const emptyGroup: FilterGroup<T> = {
|
|
398
|
+
type: "group",
|
|
399
|
+
id: "root",
|
|
400
|
+
logic: "AND",
|
|
401
|
+
items: [],
|
|
402
|
+
};
|
|
403
|
+
setRootGroup(emptyGroup);
|
|
404
|
+
setFilteredData(data);
|
|
405
|
+
onClose();
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
return (
|
|
409
|
+
<Dialog
|
|
410
|
+
open={isOpen}
|
|
411
|
+
onOpenChange={onClose}
|
|
412
|
+
title="Advanced Filters"
|
|
413
|
+
description="Refine your dataset using structured logic and multi-type comparisons."
|
|
414
|
+
>
|
|
415
|
+
<div className="max-h-[60vh] overflow-y-auto px-1 custom-scrollbar pr-3 pb-32">
|
|
416
|
+
<FilterGroupUI
|
|
417
|
+
group={rootGroup}
|
|
418
|
+
depth={0}
|
|
419
|
+
columns={columns}
|
|
420
|
+
onAddRule={handleAddRule}
|
|
421
|
+
onAddGroup={handleAddGroup}
|
|
422
|
+
onUpdateRule={handleUpdateRule}
|
|
423
|
+
onUpdateGroupLogic={handleUpdateGroupLogic}
|
|
424
|
+
onRemoveItem={handleRemoveItem}
|
|
425
|
+
/>
|
|
426
|
+
</div>
|
|
427
|
+
<div className="flex w-full justify-between items-center gap-4">
|
|
428
|
+
<Button
|
|
429
|
+
variant="ghost"
|
|
430
|
+
onClick={handleClear}
|
|
431
|
+
className="text-slate-400 hover:text-red-500 font-black text-[10px] uppercase tracking-widest gap-2"
|
|
432
|
+
>
|
|
433
|
+
<FilterX className="h-4 w-4" /> Reset Filters
|
|
434
|
+
</Button>
|
|
435
|
+
<div className="flex gap-2">
|
|
436
|
+
<Button
|
|
437
|
+
variant="outline"
|
|
438
|
+
onClick={onClose}
|
|
439
|
+
className="font-bold h-11 px-6"
|
|
440
|
+
>
|
|
441
|
+
Cancel
|
|
442
|
+
</Button>
|
|
443
|
+
<Button
|
|
444
|
+
onClick={handleApply}
|
|
445
|
+
className="bg-slate-900 font-bold h-11 px-10 rounded-lg shadow-lg hover:shadow-xl transition-shadow"
|
|
446
|
+
>
|
|
447
|
+
Apply View
|
|
448
|
+
</Button>
|
|
449
|
+
</div>
|
|
450
|
+
</div>
|
|
451
|
+
</Dialog>
|
|
452
|
+
);
|
|
453
|
+
};
|