- Created useSearch hook for search state and debouncing - Created useFilters hook for filter state management - Created useOptimisticUpdate hook for optimistic UI updates - Created useScrollPosition hook for scroll preservation - Updated SearchBar, GlobalSearch, and ListDetailView to use new hooks - Added comprehensive documentation for all hooks Validates: Requirements 6.4, 7.3, 7.4, 8.3 Task: 9. Implement client-side state management
98 lines
2.5 KiB
TypeScript
98 lines
2.5 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useCallback, useMemo } from 'react'
|
|
|
|
export interface FilterState {
|
|
priority: 'low' | 'medium' | 'high' | null
|
|
completed: boolean | null
|
|
tags: string[]
|
|
dueDateRange: { start: Date | null; end: Date | null }
|
|
sortBy: 'createdAt' | 'dueDate' | 'priority' | 'title'
|
|
sortOrder: 'asc' | 'desc'
|
|
}
|
|
|
|
interface UseFiltersOptions {
|
|
initialFilters?: Partial<FilterState>
|
|
onFiltersChange?: (filters: FilterState) => void | Promise<void>
|
|
}
|
|
|
|
interface UseFiltersReturn {
|
|
filters: FilterState
|
|
setFilters: (filters: FilterState) => void
|
|
updateFilter: <K extends keyof FilterState>(key: K, value: FilterState[K]) => void
|
|
clearFilters: () => void
|
|
hasActiveFilters: boolean
|
|
}
|
|
|
|
const DEFAULT_FILTERS: FilterState = {
|
|
priority: null,
|
|
completed: null,
|
|
tags: [],
|
|
dueDateRange: { start: null, end: null },
|
|
sortBy: 'createdAt',
|
|
sortOrder: 'desc'
|
|
}
|
|
|
|
/**
|
|
* Custom hook for managing filter state
|
|
* Validates: Requirements 6.4
|
|
*/
|
|
export function useFilters(options: UseFiltersOptions = {}): UseFiltersReturn {
|
|
const { initialFilters = {}, onFiltersChange } = options
|
|
|
|
const [filters, setFiltersState] = useState<FilterState>({
|
|
...DEFAULT_FILTERS,
|
|
...initialFilters
|
|
})
|
|
|
|
// Check if any filters are active (excluding sort options)
|
|
const hasActiveFilters = useMemo(() => {
|
|
return (
|
|
filters.priority !== null ||
|
|
filters.completed !== null ||
|
|
filters.tags.length > 0 ||
|
|
filters.dueDateRange.start !== null ||
|
|
filters.dueDateRange.end !== null
|
|
)
|
|
}, [filters])
|
|
|
|
// Set filters with callback
|
|
const setFilters = useCallback((newFilters: FilterState) => {
|
|
setFiltersState(newFilters)
|
|
if (onFiltersChange) {
|
|
onFiltersChange(newFilters)
|
|
}
|
|
}, [onFiltersChange])
|
|
|
|
// Update a single filter property
|
|
const updateFilter = useCallback(<K extends keyof FilterState>(
|
|
key: K,
|
|
value: FilterState[K]
|
|
) => {
|
|
setFiltersState(prev => {
|
|
const newFilters = { ...prev, [key]: value }
|
|
if (onFiltersChange) {
|
|
onFiltersChange(newFilters)
|
|
}
|
|
return newFilters
|
|
})
|
|
}, [onFiltersChange])
|
|
|
|
// Clear all filters (reset to defaults)
|
|
const clearFilters = useCallback(() => {
|
|
const clearedFilters = { ...DEFAULT_FILTERS }
|
|
setFiltersState(clearedFilters)
|
|
if (onFiltersChange) {
|
|
onFiltersChange(clearedFilters)
|
|
}
|
|
}, [onFiltersChange])
|
|
|
|
return {
|
|
filters,
|
|
setFilters,
|
|
updateFilter,
|
|
clearFilters,
|
|
hasActiveFilters
|
|
}
|
|
}
|