Skip to content

Commit 110e3ae

Browse files
committed
feat: advanced filter redesign with enhanced operators and nested groups
Major UX redesign and feature enhancements for the Advanced Filter modal: Filter Enhancements: - Add $in/$notIn operator with tag-based multi-value input - Add $between operator with dual-input (field-type-aware) - Add $not negation checkbox per filter row - Add case-insensitive operators ($eqi, $containsi, $startsWithi, etc.) - Align operator sets with native Strapi Content Manager filters UI/UX Improvements: - Redesign modal layout: card-style rows, improved spacing - Increase modal size (720px width, 90vh height) for better usability - Add "Enter value" labels with context-aware text - Fix DatePicker z-index overlap issue - Fix CustomSelect dark mode (text colors, backgrounds) - Standardize input heights to 32px for visual consistency - Split filter row into two lines: field+operator on top, value below - Add value type badges and better visual hierarchy Nested Groups (Premium Feature): - Add FilterGroup interface for hierarchical filter structure - Implement color-coded group containers (blue=AND, orange=OR) - Add group management: add/remove groups, toggle AND/OR logic - Support unlimited nesting depth - Auto-populate relations from nested structures - Enable via license check (Premium/Advanced tiers only) Technical: - Extend FilterRow with valueTo and negate fields - Add generateFromGroup() for nested query generation - Update buildFilterFromPath() to handle new operators - Add recursive group rendering and state management - Improve error handling for validation Breaking Changes: None - backward compatible with flat mode
1 parent 634590e commit 110e3ae

5 files changed

Lines changed: 1203 additions & 169 deletions

File tree

admin/src/components/AdvancedFilterButton.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useNavigate, useLocation } from 'react-router-dom';
55
import { useFetchClient } from '@strapi/strapi/admin';
66
import styled from 'styled-components';
77
import StrapiStyleFilterModal from './StrapiStyleFilterModal';
8+
import { useLicenseInfo } from '../hooks/useFeatureGate';
89

910
// ================ STYLED COMPONENTS ================
1011
const FilterButtonGroup = styled.div`
@@ -90,6 +91,7 @@ const AdvancedFilterButton: React.FC = () => {
9091
const navigate = useNavigate();
9192
const location = useLocation();
9293
const { get } = useFetchClient();
94+
const { isPremium, isAdvanced } = useLicenseInfo();
9395
const [availableFields, setAvailableFields] = useState<Array<{ name: string; type: string }>>([]);
9496
const [availableRelations, setAvailableRelations] = useState<Array<{ name: string; target: string }>>([]);
9597
const [hasActiveFilters, setHasActiveFilters] = useState(false);
@@ -290,7 +292,7 @@ const AdvancedFilterButton: React.FC = () => {
290292
title="Open advanced filter builder"
291293
>
292294
<Filter />
293-
Filters
295+
Advanced Filter
294296
{hasActiveFilters && <ActiveDot />}
295297
</FilterButton>
296298

@@ -310,6 +312,7 @@ const AdvancedFilterButton: React.FC = () => {
310312
availableFields={availableFields}
311313
availableRelations={availableRelations}
312314
currentQuery={getCurrentFilters()}
315+
enableNestedGroups={isPremium || isAdvanced}
313316
/>
314317
)}
315318
</FilterButtonGroup>

admin/src/components/CustomSelect.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ const SelectContainer = styled.div`
1111

1212
const SelectButton = styled.button<{ $isOpen: boolean }>`
1313
width: 100%;
14-
padding: 10px 36px 10px 12px;
14+
height: 32px;
15+
padding: 0 36px 0 12px;
1516
border: 1px solid ${props => props.$isOpen ? '#4945ff' : 'rgba(128, 128, 128, 0.25)'};
1617
border-radius: 4px;
1718
background: ${(p) => p.theme.colors.neutral0};
@@ -34,13 +35,14 @@ const SelectButton = styled.button<{ $isOpen: boolean }>`
3435
}
3536
3637
@media (max-width: 768px) {
37-
padding: 8px 32px 8px 10px;
38+
height: 32px;
39+
padding: 0 32px 0 10px;
3840
font-size: 13px;
3941
}
4042
`;
4143

4244
const SelectText = styled.span<{ $isPlaceholder: boolean }>`
43-
color: ${props => props.$isPlaceholder ? '#8e8ea9' : '#212134'};
45+
color: ${props => props.$isPlaceholder ? props.theme.colors.neutral500 : props.theme.colors.neutral800};
4446
overflow: hidden;
4547
text-overflow: ellipsis;
4648
white-space: nowrap;
@@ -63,7 +65,7 @@ const DropdownList = styled.div<{ $isOpen: boolean; top: number; left: number; w
6365
top: ${props => props.top}px;
6466
left: ${props => props.left}px;
6567
width: ${props => props.width}px;
66-
max-height: 300px;
68+
max-height: 400px;
6769
overflow-y: auto;
6870
background: ${(p) => p.theme.colors.neutral0};
6971
border: 1px solid rgba(128, 128, 128, 0.25);
@@ -73,15 +75,15 @@ const DropdownList = styled.div<{ $isOpen: boolean; top: number; left: number; w
7375
display: ${props => props.$isOpen ? 'block' : 'none'};
7476
7577
@media (max-width: 768px) {
76-
max-height: 250px;
78+
max-height: 320px;
7779
}
7880
`;
7981

8082
const DropdownItem = styled.button<{ $isSelected: boolean }>`
8183
width: 100%;
8284
padding: 10px 12px;
8385
border: none;
84-
background: ${props => props.$isSelected ? '#f0f0ff' : props.theme.colors.neutral0};
86+
background: ${props => props.$isSelected ? 'rgba(73, 69, 255, 0.06)' : props.theme.colors.neutral0};
8587
text-align: left;
8688
cursor: pointer;
8789
font-size: 14px;
@@ -109,6 +111,8 @@ const SearchInput = styled.input`
109111
border: none;
110112
border-bottom: 1px solid rgba(128, 128, 128, 0.25);
111113
font-size: 14px;
114+
background: ${(p) => p.theme.colors.neutral0};
115+
color: ${(p) => p.theme.colors.neutral800};
112116
113117
&:focus {
114118
outline: none;

0 commit comments

Comments
 (0)