diff --git a/src/utils/CustomFilterStorage.js b/src/utils/CustomFilterStorage.js deleted file mode 100644 index d0952ed..0000000 --- a/src/utils/CustomFilterStorage.js +++ /dev/null @@ -1,220 +0,0 @@ -/** - * Custom Filter Storage - Manages saving/loading custom filters - * - * This module handles CRUD operations for custom filters. - * Currently uses localStorage, but designed to easily migrate to backend API. - */ - -const STORAGE_KEY = 'customFilters' -const MAX_FILTERS = 20 // Limit to prevent localStorage overflow - -const generateFilterId = () => { - return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}` -} - -export const getSavedFilters = () => { - try { - const stored = localStorage.getItem(STORAGE_KEY) - if (!stored) return [] - - const filters = JSON.parse(stored) - - return filters.filter(f => f.id && f.name && f.conditions) - } catch (error) { - console.error('Error loading saved filters:', error) - return [] - } -} - -export const saveFilter = filter => { - try { - const filters = getSavedFilters() - - // Check limit - if (filters.length >= MAX_FILTERS) { - throw new Error( - `Maximum of ${MAX_FILTERS} filters allowed. Please delete some filters first.`, - ) - } - - // Create new filter with metadata - const newFilter = { - id: generateFilterId(), - name: filter.name, - description: filter.description || '', - color: filter.color || null, - icon: filter.icon || null, - conditions: filter.conditions, - operator: filter.operator || 'AND', - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - isPinned: filter.isPinned || false, - usageCount: 0, - lastUsedAt: null, - } - - // Add to filters array - const updatedFilters = [...filters, newFilter] - - // Save to localStorage - localStorage.setItem(STORAGE_KEY, JSON.stringify(updatedFilters)) - - return newFilter - } catch (error) { - console.error('Error saving filter:', error) - throw error - } -} - -export const updateFilter = (filterId, updates) => { - try { - const filters = getSavedFilters() - const filterIndex = filters.findIndex(f => f.id === filterId) - - if (filterIndex === -1) { - throw new Error('Filter not found') - } - - // Update filter - const updatedFilter = { - ...filters[filterIndex], - ...updates, - updatedAt: new Date().toISOString(), - } - - filters[filterIndex] = updatedFilter - - // Save to localStorage - localStorage.setItem(STORAGE_KEY, JSON.stringify(filters)) - - return updatedFilter - } catch (error) { - console.error('Error updating filter:', error) - throw error - } -} - -export const deleteFilter = filterId => { - try { - const filters = getSavedFilters() - const updatedFilters = filters.filter(f => f.id !== filterId) - - localStorage.setItem(STORAGE_KEY, JSON.stringify(updatedFilters)) - - return true - } catch (error) { - console.error('Error deleting filter:', error) - throw error - } -} - -export const getFilterById = filterId => { - const filters = getSavedFilters() - return filters.find(f => f.id === filterId) || null -} - -export const trackFilterUsage = filterId => { - try { - const filters = getSavedFilters() - const filterIndex = filters.findIndex(f => f.id === filterId) - - if (filterIndex !== -1) { - filters[filterIndex].usageCount = - (filters[filterIndex].usageCount || 0) + 1 - filters[filterIndex].lastUsedAt = new Date().toISOString() - - localStorage.setItem(STORAGE_KEY, JSON.stringify(filters)) - } - } catch (error) { - console.error('Error tracking filter usage:', error) - } -} - -export const toggleFilterPin = filterId => { - try { - const filters = getSavedFilters() - const filterIndex = filters.findIndex(f => f.id === filterId) - - if (filterIndex === -1) { - throw new Error('Filter not found') - } - - filters[filterIndex].isPinned = !filters[filterIndex].isPinned - filters[filterIndex].updatedAt = new Date().toISOString() - - localStorage.setItem(STORAGE_KEY, JSON.stringify(filters)) - - return filters[filterIndex].isPinned - } catch (error) { - console.error('Error toggling filter pin:', error) - throw error - } -} - -export const getFiltersByUsage = () => { - const filters = getSavedFilters() - return filters.sort((a, b) => (b.usageCount || 0) - (a.usageCount || 0)) -} - -export const getPinnedFilters = () => { - const filters = getSavedFilters() - return filters.filter(f => f.isPinned) -} - -export const filterNameExists = (name, excludeId = null) => { - const filters = getSavedFilters() - return filters.some( - f => f.name.toLowerCase() === name.toLowerCase() && f.id !== excludeId, - ) -} - -export const exportFilters = () => { - const filters = getSavedFilters() - return JSON.stringify(filters, null, 2) -} - -export const importFilters = jsonString => { - try { - const importedFilters = JSON.parse(jsonString) - - if (!Array.isArray(importedFilters)) { - throw new Error('Invalid filter format') - } - - const existingFilters = getSavedFilters() - - // Generate new IDs for imported filters to avoid conflicts - const newFilters = importedFilters.map(filter => ({ - ...filter, - id: generateFilterId(), - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - usageCount: 0, - lastUsedAt: null, - })) - - const allFilters = [...existingFilters, ...newFilters] - - // Check limit - if (allFilters.length > MAX_FILTERS) { - throw new Error(`Import would exceed maximum of ${MAX_FILTERS} filters`) - } - - localStorage.setItem(STORAGE_KEY, JSON.stringify(allFilters)) - - return newFilters.length - } catch (error) { - console.error('Error importing filters:', error) - throw error - } -} - -export const clearAllFilters = () => { - try { - localStorage.removeItem(STORAGE_KEY) - return true - } catch (error) { - console.error('Error clearing filters:', error) - throw error - } -} diff --git a/src/utils/Fetcher.jsx b/src/utils/Fetcher.jsx index 03a391f..4dae85b 100644 --- a/src/utils/Fetcher.jsx +++ b/src/utils/Fetcher.jsx @@ -839,6 +839,72 @@ const DeleteProject = id => { }) } +// Filter-related API functions +const GetFilters = () => { + return Fetch(`/filters`, { + method: 'GET', + headers: HEADERS(), + }) +} + +const GetPinnedFilters = () => { + return Fetch(`/filters/pinned`, { + method: 'GET', + headers: HEADERS(), + }) +} + +const GetFiltersByUsage = () => { + return Fetch(`/filters/by-usage`, { + method: 'GET', + headers: HEADERS(), + }) +} + +const GetFilterById = id => { + return Fetch(`/filters/${id}`, { + method: 'GET', + headers: HEADERS(), + }) +} + +const CreateFilter = filter => { + return Fetch(`/filters`, { + method: 'POST', + headers: HEADERS(), + body: JSON.stringify(filter), + }) +} + +const UpdateFilter = (id, filter) => { + return Fetch(`/filters/${id}`, { + method: 'PUT', + headers: HEADERS(), + body: JSON.stringify(filter), + }) +} + +const DeleteFilter = id => { + return Fetch(`/filters/${id}`, { + method: 'DELETE', + headers: HEADERS(), + }) +} + +const ToggleFilterPin = id => { + return Fetch(`/filters/${id}/toggle-pin`, { + method: 'POST', + headers: HEADERS(), + }) +} + +const TrackFilterUsage = id => { + return Fetch(`/filters/${id}/track-usage`, { + method: 'POST', + headers: HEADERS(), + }) +} + export { AcceptCircleMemberRequest, ApproveChore, @@ -853,6 +919,7 @@ export { CreateChildUser, CreateChore, createChore, + CreateFilter, CreateLabel, CreateLongLiveToken, CreateProject, @@ -861,6 +928,7 @@ export { DeleteChore, DeleteChoreHistory, DeleteCircleMember, + DeleteFilter, DeleteLabel, DeleteLongLiveToken, DeleteProject, @@ -881,9 +949,13 @@ export { GetChoreTimer, GetCircleMemberRequests, GetDeviceTokens, + GetFilterById, + GetFilters, + GetFiltersByUsage, GetLabels, GetLongLiveTokens, GetMFAStatus, + GetPinnedFilters, GetProjectById, GetProjects, GetResource, @@ -916,6 +988,8 @@ export { signUp, SkipChore, StartChore, + ToggleFilterPin, + TrackFilterUsage, UnArchiveChore, UndoChoreAction, UnregisterDeviceToken, @@ -924,6 +998,7 @@ export { UpdateChoreHistory, UpdateChorePriority, UpdateDueDate, + UpdateFilter, UpdateLabel, UpdateMemberRole, UpdateNotificationTarget, diff --git a/src/views/Chores/components/CustomFilterChips.jsx b/src/views/Chores/components/CustomFilterChips.jsx index 75911ba..fc6f221 100644 --- a/src/views/Chores/components/CustomFilterChips.jsx +++ b/src/views/Chores/components/CustomFilterChips.jsx @@ -63,6 +63,7 @@ const CustomFilterChips = ({ if (!a.isPinned && b.isPinned) return 1 return (b.usageCount || 0) - (a.usageCount || 0) }) + if (sortedFilters.filter(f => f.isPinned).length === 0) return null return ( - {sortedFilters - .filter(f => f.isPinned) - .map(filter => { - const isActive = activeFilterId === filter.id - const hasWarning = !filter.isValid - const hasCustomColor = !!filter.color && !hasWarning - const textColor = hasCustomColor - ? getTextColorFromBackgroundColor(filter.color) - : undefined + {sortedFilters.map(filter => { + const isActive = activeFilterId === filter.id + const hasWarning = !filter.isValid + const hasCustomColor = !!filter.color && !hasWarning + const textColor = hasCustomColor + ? getTextColorFromBackgroundColor(filter.color) + : undefined - return ( - 0 ? ` (${filter.overdueCount} overdue)` : ''}` - } - placement='bottom' - > -
!hasWarning && onFilterClick(filter.id)}> - handleContextMenu(e, filter)} - sx={{ - cursor: hasWarning ? 'not-allowed' : 'pointer', - transition: 'all 0.2s ease', - px: 1.0, + return ( + 0 ? ` (${filter.overdueCount} overdue)` : ''}` + } + placement='bottom' + > +
!hasWarning && onFilterClick(filter.id)}> + handleContextMenu(e, filter)} + sx={{ + cursor: hasWarning ? 'not-allowed' : 'pointer', + transition: 'all 0.2s ease', + px: 1.0, + py: 0.5, - opacity: hasWarning ? 0.7 : isActive ? 1 : 0.85, - ...(hasCustomColor && { + opacity: hasWarning ? 0.7 : isActive ? 1 : 0.85, + ...(hasCustomColor && { + backgroundColor: `${filter.color} !important`, + color: `${textColor} !important`, + '&:hover': { backgroundColor: `${filter.color} !important`, - color: `${textColor} !important`, - '&:hover': { - backgroundColor: `${filter.color} !important`, - filter: 'brightness(0.95)', - opacity: 1, - }, + filter: 'brightness(0.95)', + opacity: 1, + }, + }), + }} + startDecorator={ + + {isActive ? ( + + ) : ( + + {filter.count} + + )} + + } + // endDecorator={ + // + // {hasWarning && } + // {!hasWarning && filter.overdueCount > 0 && ( + // + // {filter.overdueCount} + // + // )} + // + // } + > + - {isActive ? ( - - ) : ( - - {filter.count} - - )} - - } - // endDecorator={ - // - // {hasWarning && } - // {!hasWarning && filter.overdueCount > 0 && ( - // - // {filter.overdueCount} - // - // )} - // - // } > - - {filter.name} - - -
-
- ) - })} + {filter.name} + +
+
+
+ ) + })} { const { data: userProfile } = useUserProfile() - const [savedFilters, setSavedFilters] = useState([]) + + // React Query hooks + const { data: filtersData = [] } = useFilters() + const createFilterMutation = useCreateFilter() + const updateFilterMutation = useUpdateFilter() + const deleteFilterMutation = useDeleteFilter() + const togglePinMutation = useToggleFilterPin() + const trackUsageMutation = useTrackFilterUsage() + const [activeFilterId, setActiveFilterId] = useState(null) const [tempFilter, setTempFilter] = useState(null) - const loadFilters = useCallback(() => { - const filters = getSavedFilters() - setSavedFilters(filters) - }, []) - - useEffect(() => { - loadFilters() - }, [loadFilters]) - const context = useMemo( () => ({ userId: userProfile?.id, @@ -43,7 +42,7 @@ export const useCustomFilters = (chores, membersData, labels, projects) => { const filtersWithCounts = useMemo(() => { if (!chores || !Array.isArray(chores)) return [] - return savedFilters.map(filter => { + return filtersData.map(filter => { const validation = validateFilter(filter, context) const count = validation.isValid ? getFilterCount(chores, filter, context) @@ -62,7 +61,7 @@ export const useCustomFilters = (chores, membersData, labels, projects) => { return result }) - }, [savedFilters, chores, context]) + }, [filtersData, chores, context]) const activeFilter = useMemo(() => { if (!activeFilterId) return null @@ -94,10 +93,9 @@ export const useCustomFilters = (chores, membersData, labels, projects) => { const applyCustomFilter = useCallback( filterId => { setActiveFilterId(filterId) - trackFilterUsage(filterId) - loadFilters() + trackUsageMutation.mutate(filterId) }, - [loadFilters], + [trackUsageMutation], ) const clearActiveFilter = useCallback(() => { @@ -116,20 +114,37 @@ export const useCustomFilters = (chores, membersData, labels, projects) => { const saveFilter = useCallback( filter => { - const savedFilter = saveFilterStorage(filter) - loadFilters() - return savedFilter + return new Promise((resolve, reject) => { + createFilterMutation.mutate(filter, { + onSuccess: savedFilter => { + resolve(savedFilter) + }, + onError: error => { + reject(error) + }, + }) + }) }, - [loadFilters], + [createFilterMutation], ) const updateFilter = useCallback( (filterId, updates) => { - const updated = updateFilterStorage(filterId, updates) - loadFilters() - return updated + return new Promise((resolve, reject) => { + updateFilterMutation.mutate( + { filterId, filterData: updates }, + { + onSuccess: updated => { + resolve(updated) + }, + onError: error => { + reject(error) + }, + }, + ) + }) }, - [loadFilters], + [updateFilterMutation], ) const deleteFilter = useCallback( @@ -137,18 +152,16 @@ export const useCustomFilters = (chores, membersData, labels, projects) => { if (activeFilterId === filterId) { setActiveFilterId(null) } - deleteFilterStorage(filterId) - loadFilters() + deleteFilterMutation.mutate(filterId) }, - [activeFilterId, loadFilters], + [activeFilterId, deleteFilterMutation], ) const pinFilter = useCallback( filterId => { - toggleFilterPin(filterId) - loadFilters() + togglePinMutation.mutate(filterId) }, - [loadFilters], + [togglePinMutation], ) const createFilterFromCurrentState = useCallback( diff --git a/src/views/Filters/FilterQueries.js b/src/views/Filters/FilterQueries.js new file mode 100644 index 0000000..babd5a1 --- /dev/null +++ b/src/views/Filters/FilterQueries.js @@ -0,0 +1,294 @@ +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import { + GetFilters, + GetFilterById, + GetPinnedFilters, + GetFiltersByUsage, + CreateFilter, + UpdateFilter, + DeleteFilter, + ToggleFilterPin, + TrackFilterUsage, +} from '../../utils/Fetcher' + +// Query hook for fetching all filters +export const useFilters = () => { + return useQuery({ + queryKey: ['filters'], + queryFn: async () => { + try { + const response = await GetFilters() + if (response.ok) { + const data = await response.json() + return data.res || data + } + throw new Error('Failed to fetch filters') + } catch (error) { + console.error('Error fetching filters:', error) + return [] + } + }, + staleTime: 5 * 60 * 1000, // 5 minutes + cacheTime: 10 * 60 * 1000, // 10 minutes + refetchOnWindowFocus: false, + }) +} + +// Query hook for fetching pinned filters +export const usePinnedFilters = () => { + return useQuery({ + queryKey: ['filters', 'pinned'], + queryFn: async () => { + try { + const response = await GetPinnedFilters() + if (response.ok) { + const data = await response.json() + return data.res || data + } + throw new Error('Failed to fetch pinned filters') + } catch (error) { + console.error('Error fetching pinned filters:', error) + return [] + } + }, + staleTime: 5 * 60 * 1000, + cacheTime: 10 * 60 * 1000, + refetchOnWindowFocus: false, + }) +} + +// Query hook for fetching filters by usage +export const useFiltersByUsage = () => { + return useQuery({ + queryKey: ['filters', 'by-usage'], + queryFn: async () => { + try { + const response = await GetFiltersByUsage() + if (response.ok) { + const data = await response.json() + return data.res || data + } + throw new Error('Failed to fetch filters by usage') + } catch (error) { + console.error('Error fetching filters by usage:', error) + return [] + } + }, + staleTime: 5 * 60 * 1000, + cacheTime: 10 * 60 * 1000, + refetchOnWindowFocus: false, + }) +} + +// Hook to get a specific filter by ID +export const useFilter = filterId => { + return useQuery({ + queryKey: ['filters', filterId], + queryFn: async () => { + try { + const response = await GetFilterById(filterId) + if (response.ok) { + const data = await response.json() + return data.res || data + } + throw new Error('Failed to fetch filter') + } catch (error) { + console.error('Error fetching filter:', error) + return null + } + }, + enabled: !!filterId, + staleTime: 5 * 60 * 1000, + cacheTime: 10 * 60 * 1000, + }) +} + +// Mutation hook for creating a new filter +export const useCreateFilter = () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async filterData => { + try { + const response = await CreateFilter(filterData) + if (response.ok) { + const data = await response.json() + return data.res || data + } + const errorData = await response.json() + throw new Error(errorData.message || 'Failed to create filter') + } catch (error) { + console.error('Error creating filter:', error) + throw error + } + }, + onSuccess: newFilter => { + // Update the filters cache + queryClient.setQueryData(['filters'], oldFilters => { + if (!oldFilters) return [newFilter] + return [...oldFilters, newFilter] + }) + + // Invalidate and refetch + queryClient.invalidateQueries(['filters']) + }, + onError: error => { + console.error('Create filter mutation failed:', error) + }, + }) +} + +// Mutation hook for updating an existing filter +export const useUpdateFilter = () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async ({ filterId, filterData }) => { + try { + const response = await UpdateFilter(filterId, filterData) + if (response.ok) { + const data = await response.json() + return data.res || data + } + const errorData = await response.json() + throw new Error(errorData.message || 'Failed to update filter') + } catch (error) { + console.error('Error updating filter:', error) + throw error + } + }, + onSuccess: updatedFilter => { + // Update the filters cache + queryClient.setQueryData(['filters'], oldFilters => { + if (!oldFilters) return [updatedFilter] + return oldFilters.map(filter => + filter.id === updatedFilter.id ? updatedFilter : filter, + ) + }) + + // Update specific filter cache + queryClient.setQueryData(['filters', updatedFilter.id], updatedFilter) + + // Invalidate and refetch + queryClient.invalidateQueries(['filters']) + }, + onError: error => { + console.error('Update filter mutation failed:', error) + }, + }) +} + +// Mutation hook for deleting a filter +export const useDeleteFilter = () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async filterId => { + try { + const response = await DeleteFilter(filterId) + if (response.ok) { + return { id: filterId, deleted: true } + } + const errorData = await response.json() + throw new Error(errorData.message || 'Failed to delete filter') + } catch (error) { + console.error('Error deleting filter:', error) + throw error + } + }, + onSuccess: ({ id: deletedFilterId }) => { + // Remove the filter from cache + queryClient.setQueryData(['filters'], oldFilters => { + if (!oldFilters) return [] + return oldFilters.filter(filter => filter.id !== deletedFilterId) + }) + + // Invalidate and refetch + queryClient.invalidateQueries(['filters']) + }, + onError: error => { + console.error('Delete filter mutation failed:', error) + }, + }) +} + +// Mutation hook for toggling filter pin status +export const useToggleFilterPin = () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async filterId => { + try { + const response = await ToggleFilterPin(filterId) + if (response.ok) { + const data = await response.json() + return data.res || data + } + const errorData = await response.json() + throw new Error(errorData.message || 'Failed to toggle filter pin') + } catch (error) { + console.error('Error toggling filter pin:', error) + throw error + } + }, + onSuccess: updatedFilter => { + // Update the filters cache + queryClient.setQueryData(['filters'], oldFilters => { + if (!oldFilters) return [updatedFilter] + return oldFilters.map(filter => + filter.id === updatedFilter.id ? updatedFilter : filter, + ) + }) + + // Update specific filter cache + queryClient.setQueryData(['filters', updatedFilter.id], updatedFilter) + + // Invalidate related queries + queryClient.invalidateQueries(['filters']) + queryClient.invalidateQueries(['filters', 'pinned']) + }, + onError: error => { + console.error('Toggle filter pin mutation failed:', error) + }, + }) +} + +// Mutation hook for tracking filter usage +export const useTrackFilterUsage = () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async filterId => { + try { + const response = await TrackFilterUsage(filterId) + if (response.ok) { + const data = await response.json() + return data.res || data + } + // Silently fail for tracking - not critical + return null + } catch (error) { + console.error('Error tracking filter usage:', error) + // Silently fail for tracking + return null + } + }, + onSuccess: updatedFilter => { + if (!updatedFilter) return + + // Update the filters cache + queryClient.setQueryData(['filters'], oldFilters => { + if (!oldFilters) return [updatedFilter] + return oldFilters.map(filter => + filter.id === updatedFilter.id ? updatedFilter : filter, + ) + }) + + // Update specific filter cache + queryClient.setQueryData(['filters', updatedFilter.id], updatedFilter) + + // Invalidate usage-related queries + queryClient.invalidateQueries(['filters', 'by-usage']) + }, + }) +} diff --git a/src/views/Filters/FilterView.jsx b/src/views/Filters/FilterView.jsx index 7596685..19b1149 100644 --- a/src/views/Filters/FilterView.jsx +++ b/src/views/Filters/FilterView.jsx @@ -23,13 +23,6 @@ import { } from '@mui/icons-material' import { useChores } from '../../queries/ChoreQueries' import { useCircleMembers, useUserProfile } from '../../queries/UserQueries' -import { - deleteFilter, - getSavedFilters, - saveFilter, - toggleFilterPin, - updateFilter, -} from '../../utils/CustomFilterStorage' import { getFilterCount, getFilterOverdueCount } from '../../utils/FilterEngine' import { getSafeBottomStyles } from '../../utils/SafeAreaUtils' @@ -37,6 +30,13 @@ import { useLabels } from '../Labels/LabelQueries' import AdvancedFilterBuilder from '../Modals/Inputs/AdvancedFilterBuilder' import ConfirmationModal from '../Modals/Inputs/ConfirmationModal' import { useProjects } from '../Projects/ProjectQueries' +import { + useFilters, + useCreateFilter, + useUpdateFilter, + useDeleteFilter, + useToggleFilterPin, +} from './FilterQueries' const FilterCard = ({ filter, @@ -551,42 +551,32 @@ const FilterView = () => { const { data: projects = [] } = useProjects() const { data: membersData } = useCircleMembers() - const [savedFilters, setSavedFilters] = useState([]) + // React Query hooks + const { data: filtersData = [], isLoading } = useFilters() + const createFilterMutation = useCreateFilter() + const updateFilterMutation = useUpdateFilter() + const deleteFilterMutation = useDeleteFilter() + const togglePinMutation = useToggleFilterPin() + const [filterCounts, setFilterCounts] = useState({}) const [showAdvancedFilterBuilder, setShowAdvancedFilterBuilder] = useState(false) const [editingFilter, setEditingFilter] = useState(null) const [confirmationModel, setConfirmationModel] = useState({}) - const [isLoading, setIsLoading] = useState(true) - // Load filters - const loadFilters = () => { - try { - const filters = getSavedFilters() - // Sort: pinned first, then by usage count, then by last used - const sortedFilters = filters.sort((a, b) => { - if (a.isPinned !== b.isPinned) { - return a.isPinned ? -1 : 1 - } - if ((b.usageCount || 0) !== (a.usageCount || 0)) { - return (b.usageCount || 0) - (a.usageCount || 0) - } - if (a.lastUsedAt && b.lastUsedAt) { - return new Date(b.lastUsedAt) - new Date(a.lastUsedAt) - } - return new Date(b.createdAt) - new Date(a.createdAt) - }) - setSavedFilters(sortedFilters) - } catch (error) { - console.error('Error loading filters:', error) - } finally { - setIsLoading(false) + // Sort filters: pinned first, then by usage count, then by last used + const savedFilters = [...filtersData].sort((a, b) => { + if (a.isPinned !== b.isPinned) { + return a.isPinned ? -1 : 1 } - } - - useEffect(() => { - loadFilters() - }, []) + if ((b.usageCount || 0) !== (a.usageCount || 0)) { + return (b.usageCount || 0) - (a.usageCount || 0) + } + if (a.lastUsedAt && b.lastUsedAt) { + return new Date(b.lastUsedAt) - new Date(a.lastUsedAt) + } + return new Date(b.createdAt) - new Date(a.createdAt) + }) // Calculate task counts for each filter useEffect(() => { @@ -659,41 +649,40 @@ const FilterView = () => { } const handleDeleteFilter = id => { - try { - deleteFilter(id) - // if it's the selected filter, we might want to clear it - localStorage.getItem('selectedChoreFilter') === id && - localStorage.removeItem('selectedChoreFilter') - - loadFilters() - } catch (error) { - console.error('Error deleting filter:', error) - } + deleteFilterMutation.mutate(id, { + onSuccess: () => { + // if it's the selected filter, we might want to clear it + if (localStorage.getItem('selectedChoreFilter') === id) { + localStorage.removeItem('selectedChoreFilter') + } + }, + }) } const handlePinFilter = id => { - try { - toggleFilterPin(id) - loadFilters() - } catch (error) { - console.error('Error pinning filter:', error) - } + togglePinMutation.mutate(id) } const handleSaveFilter = filterData => { - try { - if (editingFilter) { - // Update existing filter - updateFilter(editingFilter.id, filterData) - } else { - // Save new filter - saveFilter(filterData) - } - setShowAdvancedFilterBuilder(false) - setEditingFilter(null) - loadFilters() - } catch (error) { - console.error('Error saving filter:', error) + if (editingFilter) { + // Update existing filter + updateFilterMutation.mutate( + { filterId: editingFilter.id, filterData }, + { + onSuccess: () => { + setShowAdvancedFilterBuilder(false) + setEditingFilter(null) + }, + }, + ) + } else { + // Save new filter + createFilterMutation.mutate(filterData, { + onSuccess: () => { + setShowAdvancedFilterBuilder(false) + setEditingFilter(null) + }, + }) } }