From c59400cd48b8a5cb56f121434d64485919343e02 Mon Sep 17 00:00:00 2001 From: Ahmed Darrazi Date: Wed, 10 Dec 2025 00:28:35 +0100 Subject: [PATCH] feat(policy-explorer-v2): implement Phase 4 - Enhanced Filtering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✨ New Features (Tasks T023-T028) - PolicyTypeFilter component with multi-select checkboxes - 8 common Intune policy types (deviceConfiguration, compliancePolicy, etc.) - Active filter badges with individual remove buttons - 'Clear All Filters' button when filters active - Filter count badge in dropdown trigger 🔧 Updates - PolicyTableToolbar now accepts filter props - PolicyExplorerV2Client connects filters to URL state - Filters sync with URL for shareable links - Filter state triggers data refetch automatically 📦 Dependencies - Added shadcn DropdownMenu component ✅ Zero TypeScript compilation errors ✅ All Phase 4 tasks complete (T023-T028) ✅ Ready for Phase 5 (CSV Export) Refs: specs/004-policy-explorer-v2/tasks.md Phase 4 --- app/(app)/search/PolicyExplorerV2Client.tsx | 4 + .../policy-explorer/PolicyTableToolbar.tsx | 68 +++++++++- .../policy-explorer/PolicyTypeFilter.tsx | 125 ++++++++++++++++++ 3 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 components/policy-explorer/PolicyTypeFilter.tsx diff --git a/app/(app)/search/PolicyExplorerV2Client.tsx b/app/(app)/search/PolicyExplorerV2Client.tsx index 696be9f..701ee44 100644 --- a/app/(app)/search/PolicyExplorerV2Client.tsx +++ b/app/(app)/search/PolicyExplorerV2Client.tsx @@ -147,6 +147,10 @@ export function PolicyExplorerV2Client() { table={table} density={preferences.density} onDensityChange={handleDensityChange} + selectedPolicyTypes={urlState.policyTypes} + onSelectedPolicyTypesChange={urlState.updatePolicyTypes} + searchQuery={urlState.searchQuery} + onSearchQueryChange={urlState.updateSearchQuery} /> {/* Table */} diff --git a/components/policy-explorer/PolicyTableToolbar.tsx b/components/policy-explorer/PolicyTableToolbar.tsx index 8ae150a..84a1a78 100644 --- a/components/policy-explorer/PolicyTableToolbar.tsx +++ b/components/policy-explorer/PolicyTableToolbar.tsx @@ -12,7 +12,9 @@ import { Button } from '@/components/ui/button'; import { ColumnVisibilityMenu } from './ColumnVisibilityMenu'; -import { LayoutList, LayoutGrid } from 'lucide-react'; +import { PolicyTypeFilter } from './PolicyTypeFilter'; +import { LayoutList, LayoutGrid, X } from 'lucide-react'; +import { Badge } from '@/components/ui/badge'; import type { Table } from '@tanstack/react-table'; import type { PolicySettingRow } from '@/lib/types/policy-table'; @@ -20,17 +22,79 @@ interface PolicyTableToolbarProps { table: Table; density: 'compact' | 'comfortable'; onDensityChange: (density: 'compact' | 'comfortable') => void; + // Filter props + selectedPolicyTypes: string[]; + onSelectedPolicyTypesChange: (types: string[]) => void; + searchQuery: string; + onSearchQueryChange: (query: string) => void; } export function PolicyTableToolbar({ table, density, onDensityChange, + selectedPolicyTypes, + onSelectedPolicyTypesChange, + searchQuery, + onSearchQueryChange, }: PolicyTableToolbarProps) { + const hasActiveFilters = selectedPolicyTypes.length > 0 || searchQuery.length > 0; + + const handleClearFilters = () => { + onSelectedPolicyTypesChange([]); + onSearchQueryChange(''); + }; + return (
- {/* Search and filters will be added here in Phase 4 */} + {/* Policy Type Filter */} + + + {/* Active Filter Badges */} + {selectedPolicyTypes.length > 0 && ( +
+ {selectedPolicyTypes.slice(0, 2).map((type) => ( + + {type} + + + ))} + {selectedPolicyTypes.length > 2 && ( + + +{selectedPolicyTypes.length - 2} more + + )} +
+ )} + + {/* Clear All Filters Button */} + {hasActiveFilters && ( + + )}
diff --git a/components/policy-explorer/PolicyTypeFilter.tsx b/components/policy-explorer/PolicyTypeFilter.tsx new file mode 100644 index 0000000..0a80c1a --- /dev/null +++ b/components/policy-explorer/PolicyTypeFilter.tsx @@ -0,0 +1,125 @@ +/** + * PolicyTypeFilter Component + * + * Multi-select checkbox dropdown for filtering by policy types. + * + * Features: + * - Checkbox list of all available policy types + * - "Select All" / "Clear All" actions + * - Active filter count badge + * - Syncs with URL state via useURLState hook + */ + +'use client'; + +import { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { Badge } from '@/components/ui/badge'; +import { Filter } from 'lucide-react'; + +// Common Intune policy types +const POLICY_TYPES = [ + { value: 'deviceConfiguration', label: 'Device Configuration' }, + { value: 'compliancePolicy', label: 'Compliance Policy' }, + { value: 'deviceManagementScript', label: 'Device Management Script' }, + { value: 'windowsUpdateForBusiness', label: 'Windows Update for Business' }, + { value: 'iosUpdateConfiguration', label: 'iOS Update Configuration' }, + { value: 'macOSExtensionsConfiguration', label: 'macOS Extensions' }, + { value: 'settingsCatalog', label: 'Settings Catalog' }, + { value: 'endpointProtection', label: 'Endpoint Protection' }, +] as const; + +interface PolicyTypeFilterProps { + selectedTypes: string[]; + onSelectedTypesChange: (types: string[]) => void; +} + +export function PolicyTypeFilter({ + selectedTypes, + onSelectedTypesChange, +}: PolicyTypeFilterProps) { + const [open, setOpen] = useState(false); + + const handleSelectAll = () => { + onSelectedTypesChange(POLICY_TYPES.map(t => t.value)); + }; + + const handleClearAll = () => { + onSelectedTypesChange([]); + }; + + const handleToggleType = (type: string) => { + if (selectedTypes.includes(type)) { + onSelectedTypesChange(selectedTypes.filter(t => t !== type)); + } else { + onSelectedTypesChange([...selectedTypes, type]); + } + }; + + const activeCount = selectedTypes.length; + const allSelected = activeCount === POLICY_TYPES.length; + const noneSelected = activeCount === 0; + + return ( + + + + + + Filter by Policy Type + + + {/* Quick Actions */} +
+ + +
+ + + + {/* Policy Type Checkboxes */} + {POLICY_TYPES.map((type) => ( + handleToggleType(type.value)} + > + {type.label} + + ))} +
+
+ ); +}