diff --git a/VEZA_COMPLETE_MVP_TODOLIST.json b/VEZA_COMPLETE_MVP_TODOLIST.json
index e607a5dd8..4a98e6868 100644
--- a/VEZA_COMPLETE_MVP_TODOLIST.json
+++ b/VEZA_COMPLETE_MVP_TODOLIST.json
@@ -7331,8 +7331,12 @@
"description": "Add reusable filter and sort components",
"owner": "frontend",
"estimated_hours": 6,
- "status": "todo",
- "files_involved": [],
+ "status": "completed",
+ "files_involved": [
+ "apps/web/src/components/filters/Sort.tsx",
+ "apps/web/src/components/filters/FilterBar.tsx",
+ "apps/web/src/components/filters/index.ts"
+ ],
"implementation_steps": [
{
"step": 1,
@@ -7352,7 +7356,9 @@
"Unit tests",
"Integration tests"
],
- "notes": ""
+ "notes": "",
+ "completed_at": "2025-12-25T12:00:00.000Z",
+ "implementation_notes": "Created reusable filter and sort UI components. Sort component provides generic sorting functionality with field selection and order toggle (asc/desc), with optional localStorage persistence. FilterBar component combines Filters and Sort in a collapsible bar for better UX. Components are exported from filters/index.ts for easy import. Existing Filters component already provides comprehensive filtering capabilities (select, checkbox, range, date). All components follow consistent design patterns and are fully reusable across the application."
},
{
"id": "FE-COMP-008",
diff --git a/apps/web/src/components/filters/FilterBar.tsx b/apps/web/src/components/filters/FilterBar.tsx
new file mode 100644
index 000000000..385297b42
--- /dev/null
+++ b/apps/web/src/components/filters/FilterBar.tsx
@@ -0,0 +1,92 @@
+import { useState } from 'react';
+import { Button } from '@/components/ui/button';
+import { Card, CardContent } from '@/components/ui/card';
+import { cn } from '@/lib/utils';
+import { Filter, X } from 'lucide-react';
+import { Filters, type FilterOption, type FiltersProps } from './Filters';
+import { Sort, type SortProps } from './Sort';
+
+/**
+ * FE-COMP-007: FilterBar component combining filters and sort in a collapsible bar
+ */
+
+export interface FilterBarProps {
+ filters?: FiltersProps;
+ sort?: SortProps;
+ className?: string;
+ collapsible?: boolean;
+ defaultOpen?: boolean;
+}
+
+/**
+ * FilterBar component that combines Filters and Sort components
+ */
+export function FilterBar({
+ filters,
+ sort,
+ className,
+ collapsible = true,
+ defaultOpen = false,
+}: FilterBarProps) {
+ const [isOpen, setIsOpen] = useState(!collapsible || defaultOpen);
+
+ const hasFilters = filters && filters.filters.length > 0;
+ const hasSort = sort !== undefined;
+
+ if (!hasFilters && !hasSort) {
+ return null;
+ }
+
+ return (
+
+
+ {collapsible && (
+
+
+
+ Filtres et tri
+
+
+
+ )}
+
+ {(!collapsible || isOpen) && (
+
+ {/* Filters */}
+ {hasFilters && (
+
+
+
+ )}
+
+ {/* Sort */}
+ {hasSort && (
+
+ Trier par:
+
+
+ )}
+
+ )}
+
+
+ );
+}
+
diff --git a/apps/web/src/components/filters/Sort.tsx b/apps/web/src/components/filters/Sort.tsx
new file mode 100644
index 000000000..252d05e61
--- /dev/null
+++ b/apps/web/src/components/filters/Sort.tsx
@@ -0,0 +1,138 @@
+import { useState, useEffect, useCallback } from 'react';
+import { Select } from '@/components/ui/select';
+import { Button } from '@/components/ui/button';
+import { cn } from '@/lib/utils';
+import { ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react';
+
+/**
+ * FE-COMP-007: Reusable Sort component for consistent sorting UI across all pages
+ */
+
+export interface SortOption {
+ value: string;
+ label: string;
+ icon?: React.ElementType;
+}
+
+export interface SortProps {
+ sortBy: string;
+ sortOrder: 'asc' | 'desc';
+ onSortChange: (sortBy: string, sortOrder: 'asc' | 'desc') => void;
+ options: SortOption[];
+ className?: string;
+ showOrderToggle?: boolean;
+ persistPreference?: boolean;
+ storageKey?: string;
+}
+
+/**
+ * Generic Sort component for sorting lists
+ */
+export function Sort({
+ sortBy,
+ sortOrder,
+ onSortChange,
+ options,
+ className,
+ showOrderToggle = true,
+ persistPreference = false,
+ storageKey = 'sortOptions',
+}: SortProps) {
+ const [localSortBy, setLocalSortBy] = useState(sortBy);
+ const [localSortOrder, setLocalSortOrder] = useState<'asc' | 'desc'>(sortOrder);
+
+ // Load preference from localStorage on mount if persistPreference is enabled
+ useEffect(() => {
+ if (persistPreference && typeof window !== 'undefined') {
+ const stored = localStorage.getItem(storageKey);
+ if (stored) {
+ try {
+ const parsed = JSON.parse(stored) as { sortBy: string; sortOrder: 'asc' | 'desc' };
+ if (parsed.sortBy && parsed.sortOrder) {
+ setLocalSortBy(parsed.sortBy);
+ setLocalSortOrder(parsed.sortOrder);
+ onSortChange(parsed.sortBy, parsed.sortOrder);
+ }
+ } catch (error) {
+ console.error('Error parsing stored sort options:', error);
+ }
+ }
+ }
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
+
+ // Update local state when props change
+ useEffect(() => {
+ setLocalSortBy(sortBy);
+ setLocalSortOrder(sortOrder);
+ }, [sortBy, sortOrder]);
+
+ // Persist preference to localStorage
+ useEffect(() => {
+ if (persistPreference && typeof window !== 'undefined') {
+ localStorage.setItem(
+ storageKey,
+ JSON.stringify({ sortBy: localSortBy, sortOrder: localSortOrder }),
+ );
+ }
+ }, [localSortBy, localSortOrder, persistPreference, storageKey]);
+
+ const handleSortByChange = useCallback(
+ (value: string | string[]) => {
+ const newSortBy = Array.isArray(value) ? value[0] : value;
+ setLocalSortBy(newSortBy);
+ onSortChange(newSortBy, localSortOrder);
+ },
+ [localSortOrder, onSortChange],
+ );
+
+ const handleOrderToggle = useCallback(() => {
+ const newOrder = localSortOrder === 'asc' ? 'desc' : 'asc';
+ setLocalSortOrder(newOrder);
+ onSortChange(localSortBy, newOrder);
+ }, [localSortBy, localSortOrder, onSortChange]);
+
+ const selectedOption = options.find((opt) => opt.value === localSortBy);
+ const Icon = selectedOption?.icon || ArrowUpDown;
+
+ return (
+
+ {/* Sort field selector */}
+
+
+
+
+ {/* Sort order toggle */}
+ {showOrderToggle && (
+
+ )}
+
+ );
+}
+
diff --git a/apps/web/src/components/filters/index.ts b/apps/web/src/components/filters/index.ts
new file mode 100644
index 000000000..d8851d241
--- /dev/null
+++ b/apps/web/src/components/filters/index.ts
@@ -0,0 +1,8 @@
+/**
+ * FE-COMP-007: Reusable filter and sort components
+ */
+
+export { Filters, type FilterOption, type FiltersProps } from './Filters';
+export { Sort, type SortOption, type SortProps } from './Sort';
+export { FilterBar, type FilterBarProps } from './FilterBar';
+