diff --git a/src/components/animations/PageTransition.jsx b/src/components/animations/PageTransition.jsx index e46b500..865d46c 100644 --- a/src/components/animations/PageTransition.jsx +++ b/src/components/animations/PageTransition.jsx @@ -1,4 +1,4 @@ -import { useEffect, useLayoutEffect, useRef, useState } from 'react' +import { useLayoutEffect, useRef, useState } from 'react' import { flushSync } from 'react-dom' import { useLocation } from 'react-router-dom' import './PageTransition.css' diff --git a/src/contexts/RouterContext.jsx b/src/contexts/RouterContext.jsx index 2c36cb7..51c2208 100644 --- a/src/contexts/RouterContext.jsx +++ b/src/contexts/RouterContext.jsx @@ -23,6 +23,7 @@ import ArchivedTasks from '../views/Chores/ArchivedTasks' import MyChores from '../views/Chores/MyChores' import JoinCircleView from '../views/Circles/JoinCircle' import NotFound from '../views/components/NotFound' +import FilterView from '../views/Filters/FilterView' import ChoreHistory from '../views/History/ChoreHistory' import LabelView from '../views/Labels/LabelView' import Landing from '../views/Landing/Landing' @@ -30,7 +31,6 @@ import PaymentCancelledView from '../views/Payments/PaymentFailView' import PaymentSuccessView from '../views/Payments/PaymentSuccessView' import PrivacyPolicyView from '../views/PrivacyPolicy/PrivacyPolicyView' import ProjectView from '../views/Projects/ProjectView' -import FilterView from '../views/Filters/FilterView' import APITokenSettings from '../views/Settings/APITokenSettings' import MFASettings from '../views/Settings/MFASettings' import NotificationSetting from '../views/Settings/NotificationSetting' diff --git a/src/utils/CustomFilterStorage.js b/src/utils/CustomFilterStorage.js index 640f687..0dc82a1 100644 --- a/src/utils/CustomFilterStorage.js +++ b/src/utils/CustomFilterStorage.js @@ -8,17 +8,10 @@ const STORAGE_KEY = 'customFilters' const MAX_FILTERS = 20 // Limit to prevent localStorage overflow -/** - * Generate a unique filter ID - */ const generateFilterId = () => { - return `filter_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` + return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}` } -/** - * Get all saved filters - * @returns {Array} - Array of filter objects - */ export const getSavedFilters = () => { try { const stored = localStorage.getItem(STORAGE_KEY) @@ -26,7 +19,6 @@ export const getSavedFilters = () => { const filters = JSON.parse(stored) - // Ensure filters have required fields return filters.filter(f => f.id && f.name && f.conditions) } catch (error) { console.error('Error loading saved filters:', error) @@ -34,18 +26,15 @@ export const getSavedFilters = () => { } } -/** - * Save a new filter - * @param {Object} filter - The filter to save - * @returns {Object} - The saved filter with generated ID and metadata - */ -export const saveFilter = (filter) => { +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.`) + throw new Error( + `Maximum of ${MAX_FILTERS} filters allowed. Please delete some filters first.`, + ) } // Create new filter with metadata @@ -61,7 +50,7 @@ export const saveFilter = (filter) => { updatedAt: new Date().toISOString(), isPinned: filter.isPinned || false, usageCount: 0, - lastUsedAt: null + lastUsedAt: null, } // Add to filters array @@ -77,12 +66,7 @@ export const saveFilter = (filter) => { } } -/** - * Update an existing filter - * @param {string} filterId - The ID of the filter to update - * @param {Object} updates - The fields to update - * @returns {Object} - The updated filter - */ + export const updateFilter = (filterId, updates) => { try { const filters = getSavedFilters() @@ -96,7 +80,7 @@ export const updateFilter = (filterId, updates) => { const updatedFilter = { ...filters[filterIndex], ...updates, - updatedAt: new Date().toISOString() + updatedAt: new Date().toISOString(), } filters[filterIndex] = updatedFilter @@ -111,12 +95,7 @@ export const updateFilter = (filterId, updates) => { } } -/** - * Delete a filter - * @param {string} filterId - The ID of the filter to delete - * @returns {boolean} - Success status - */ -export const deleteFilter = (filterId) => { +export const deleteFilter = filterId => { try { const filters = getSavedFilters() const updatedFilters = filters.filter(f => f.id !== filterId) @@ -130,27 +109,20 @@ export const deleteFilter = (filterId) => { } } -/** - * Get a single filter by ID - * @param {string} filterId - The ID of the filter - * @returns {Object|null} - The filter object or null if not found - */ -export const getFilterById = (filterId) => { + +export const getFilterById = filterId => { const filters = getSavedFilters() return filters.find(f => f.id === filterId) || null } -/** - * Increment usage count for a filter - * @param {string} filterId - The ID of the filter - */ -export const trackFilterUsage = (filterId) => { +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].usageCount = + (filters[filterIndex].usageCount || 0) + 1 filters[filterIndex].lastUsedAt = new Date().toISOString() localStorage.setItem(STORAGE_KEY, JSON.stringify(filters)) @@ -160,12 +132,8 @@ export const trackFilterUsage = (filterId) => { } } -/** - * Toggle pin status of a filter - * @param {string} filterId - The ID of the filter - * @returns {boolean} - New pin status - */ -export const toggleFilterPin = (filterId) => { + +export const toggleFilterPin = filterId => { try { const filters = getSavedFilters() const filterIndex = filters.findIndex(f => f.id === filterId) @@ -186,53 +154,31 @@ export const toggleFilterPin = (filterId) => { } } -/** - * Get filters sorted by usage (most used first) - * @returns {Array} - Sorted filters - */ + export const getFiltersByUsage = () => { const filters = getSavedFilters() return filters.sort((a, b) => (b.usageCount || 0) - (a.usageCount || 0)) } -/** - * Get pinned filters - * @returns {Array} - Pinned filters - */ + export const getPinnedFilters = () => { const filters = getSavedFilters() return filters.filter(f => f.isPinned) } -/** - * Check if filter name already exists - * @param {string} name - The filter name to check - * @param {string} excludeId - Optional ID to exclude from check (for updates) - * @returns {boolean} - Whether the name exists - */ export const filterNameExists = (name, excludeId = null) => { const filters = getSavedFilters() - return filters.some(f => - f.name.toLowerCase() === name.toLowerCase() && - f.id !== excludeId + return filters.some( + f => f.name.toLowerCase() === name.toLowerCase() && f.id !== excludeId, ) } -/** - * Export filters as JSON (for backup/sharing) - * @returns {string} - JSON string of all filters - */ export const exportFilters = () => { const filters = getSavedFilters() return JSON.stringify(filters, null, 2) } -/** - * Import filters from JSON - * @param {string} jsonString - JSON string of filters - * @returns {number} - Number of filters imported - */ -export const importFilters = (jsonString) => { +export const importFilters = jsonString => { try { const importedFilters = JSON.parse(jsonString) @@ -249,7 +195,7 @@ export const importFilters = (jsonString) => { createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), usageCount: 0, - lastUsedAt: null + lastUsedAt: null,, })) const allFilters = [...existingFilters, ...newFilters] @@ -268,10 +214,7 @@ export const importFilters = (jsonString) => { } } -/** - * Clear all filters (use with caution!) - * @returns {boolean} - Success status - */ + export const clearAllFilters = () => { try { localStorage.removeItem(STORAGE_KEY) diff --git a/src/views/Chores/ArchivedTasks.jsx b/src/views/Chores/ArchivedTasks.jsx index 06db537..6d4eee6 100644 --- a/src/views/Chores/ArchivedTasks.jsx +++ b/src/views/Chores/ArchivedTasks.jsx @@ -28,7 +28,6 @@ import { useImpersonateUser } from '../../contexts/ImpersonateUserContext.jsx' import { useUnArchiveChore } from '../../queries/ChoreQueries' import { useCircleMembers, useUserProfile } from '../../queries/UserQueries' import { useNotification } from '../../service/NotificationProvider' -import { ChoreSorter } from '../../utils/Chores' import { DeleteChore, GetArchivedChores } from '../../utils/Fetcher' import LoadingComponent from '../components/Loading' import ConfirmationModal from '../Modals/Inputs/ConfirmationModal' @@ -68,7 +67,12 @@ const ArchivedTasks = () => { try { const response = await GetArchivedChores() const data = await response.json() - const sortedChores = data.res.sort(ChoreSorter) + // Sort by updatedAt (most recent first) + const sortedChores = data.res.sort((a, b) => { + const dateA = new Date(a.updatedAt || 0) + const dateB = new Date(b.updatedAt || 0) + return dateB - dateA + }) setArchivedChores(sortedChores) setFilteredChores(sortedChores) } catch (error) { diff --git a/src/views/Chores/MyChores.jsx b/src/views/Chores/MyChores.jsx index 2a69675..9b70336 100644 --- a/src/views/Chores/MyChores.jsx +++ b/src/views/Chores/MyChores.jsx @@ -161,15 +161,19 @@ const MyChores = () => { savedFilters, activeFilter, activeFilterId, + tempFilter, filteredChores: customFilteredChores, applyCustomFilter, clearActiveFilter, + applyTempFilter, + clearTempFilter, saveFilter, updateFilter, deleteFilter, pinFilter, createFilterFromCurrentState, hasProjectConditions, + hasFilterApplied, } = useCustomFilters( nonProjectFilteredChores, membersData?.res, @@ -205,9 +209,12 @@ const MyChores = () => { return [] } - // Use project-filtered chores for section grouping + // If a custom filter (temp or saved) is active, use customFilteredChores let choresToGroup = chores - if (selectedProject) { + if (tempFilter || activeFilterId) { + choresToGroup = customFilteredChores + } else if (selectedProject) { + // Otherwise, use project-filtered chores for section grouping if (selectedProject.id === 'default') { // Default project: only show tasks without a projectId choresToGroup = chores.filter(chore => !chore.projectId) @@ -228,6 +235,9 @@ const MyChores = () => { return sections }, [ chores, + customFilteredChores, + tempFilter, + activeFilterId, selectedChoreSection, selectedChoreFilter, selectedProject, @@ -325,6 +335,32 @@ const MyChores = () => { } }, [searchInputFocus]) + // Read and apply project from URL parameters + useEffect(() => { + if (!projects.length) return + + const projectIdFromUrl = searchParams.get('project') + + if (projectIdFromUrl && projectIdFromUrl !== selectedProject?.id) { + const project = projectsWithDefault.find(p => p.id === projectIdFromUrl) + if (project) { + setSelectedProjectWithCache(project) + } + } + }, [ + searchParams, + projects, + projectsWithDefault, + selectedProject, + setSelectedProjectWithCache, + + searchParams, + projects, + projectsWithDefault, + selectedProject, + setSelectedProjectWithCache, + ]) + // Read and apply filters from URL parameters useEffect(() => { if (!chores.length || !savedFilters.length) return @@ -332,6 +368,7 @@ const MyChores = () => { // Check for filterId (camelCase) or filter_id (snake_case) for advanced filters const filterId = searchParams.get('filterId') || searchParams.get('filter_id') + const oldFilter = searchParams.get('filter') // Handle advanced filter parameter @@ -505,10 +542,7 @@ const MyChores = () => { } // Always navigate with params (preserves project param) - const paramString = params.toString() - Navigate(paramString ? `/chores?${paramString}` : '/chores', { - replace: true, - }) + Navigate({ pathname: '/chores', search: params.toString() }) } const searchOptions = useMemo( @@ -617,7 +651,7 @@ const MyChores = () => { } const getFilteredChores = useMemo(() => { - if (activeFilterId) { + if (activeFilterId || tempFilter) { return customFilteredChores } @@ -644,6 +678,7 @@ const MyChores = () => { return baseChores }, [ activeFilterId, + tempFilter, customFilteredChores, projectFilteredChores, searchTerm, @@ -741,16 +776,18 @@ const MyChores = () => { /> {/* Project Selector - Hidden when active filter has project conditions */} - {projectsWithDefault.length > 1 && !hasProjectConditions && ( - { - setSelectedProjectWithCache(project) - clearActiveFilter() - }} - showKeyboardShortcuts={showKeyboardShortcuts} - /> - )} + {projectsWithDefault.length > 1 && + !hasProjectConditions && + !hasFilterApplied && ( + { + setSelectedProjectWithCache(project) + clearActiveFilter() + }} + showKeyboardShortcuts={showKeyboardShortcuts} + /> + )} {/* View Mode Toggle Button */} { )} - + {/* Multi-select Help - only show when in multi-select mode */} {/* */} diff --git a/src/views/Chores/SortAndGrouping.jsx b/src/views/Chores/SortAndGrouping.jsx index 413b2ca..1a08edf 100644 --- a/src/views/Chores/SortAndGrouping.jsx +++ b/src/views/Chores/SortAndGrouping.jsx @@ -1,7 +1,10 @@ +import { Add, Check } from '@mui/icons-material' import { + Box, Button, - Chip, Divider, + ListItemContent, + ListItemDecorator, Menu, MenuItem, Radio, @@ -9,7 +12,7 @@ import { } from '@mui/joy' import IconButton from '@mui/joy/IconButton' import { useEffect, useRef, useState } from 'react' -import { getTextColorFromBackgroundColor } from '../../utils/Colors.jsx' +import KeyboardShortcutHint from '../../components/common/KeyboardShortcutHint' const SortAndGrouping = ({ label, @@ -26,10 +29,15 @@ const SortAndGrouping = ({ onCreateNewFilter, }) => { const [anchorEl, setAnchorEl] = useState(null) + const [selectedIndex, setSelectedIndex] = useState(0) + const [isKeyboardNavigating, setIsKeyboardNavigating] = useState(false) + const [showKeyboardShortcuts, setShowKeyboardShortcuts] = useState(false) const menuRef = useRef(null) + const buttonRef = useRef(null) const handleMenuOpen = event => { setAnchorEl(event.currentTarget) + setIsKeyboardNavigating(false) } const handleMenuClose = () => { @@ -49,37 +57,177 @@ const SortAndGrouping = ({ } }, []) + // Keyboard shortcut handler + useEffect(() => { + const handleKeyDown = event => { + const isHoldingCmdOrCtrl = event.ctrlKey || event.metaKey + + // Cmd/Ctrl + G to open sort menu + if (isHoldingCmdOrCtrl && event.key === 'g') { + event.preventDefault() + if (!anchorEl) { + setAnchorEl(buttonRef.current) + setSelectedIndex(0) + setIsKeyboardNavigating(true) + } else { + handleMenuClose() + } + return + } + + // Only handle navigation keys when menu is open + if (!anchorEl) return + + const groupByItems = [ + { name: 'Smart', value: 'default' }, + { name: 'Due Date', value: 'due_date' }, + { name: 'Priority', value: 'priority' }, + { name: 'Labels', value: 'labels' }, + ] + + const filterItems = ['anyone', 'assigned_to_me', 'assigned_to_others'] + + // Total selectable items: 4 (group by) + 3 (filters) + 1 (create custom filter) = 8 + const totalItems = groupByItems.length + filterItems.length + 1 + + switch (event.key) { + case 'ArrowDown': + event.preventDefault() + setIsKeyboardNavigating(true) + setSelectedIndex(prev => (prev < totalItems - 1 ? prev + 1 : prev)) + break + case 'ArrowUp': + event.preventDefault() + setIsKeyboardNavigating(true) + setSelectedIndex(prev => (prev > 0 ? prev - 1 : prev)) + break + case 'Enter': + event.preventDefault() + if (selectedIndex < groupByItems.length) { + // Group by items (0-3) + const item = groupByItems[selectedIndex] + onItemSelect(item) + setSelectedItem?.(item.name) + handleMenuClose() + } else if (selectedIndex < groupByItems.length + filterItems.length) { + // Filter items (4-6) + const filterIndex = selectedIndex - groupByItems.length + setFilter(filterItems[filterIndex]) + handleMenuClose() + } else { + // Create custom filter (7) + onCreateNewFilter() + handleMenuClose() + } + break + case 'Escape': + event.preventDefault() + handleMenuClose() + break + } + } + + document.addEventListener('keydown', handleKeyDown) + return () => { + document.removeEventListener('keydown', handleKeyDown) + } + }, [ + anchorEl, + selectedIndex, + onItemSelect, + setSelectedItem, + setFilter, + onCreateNewFilter, + ]) + + // Reset selected index when menu opens + useEffect(() => { + if (anchorEl) { + setSelectedIndex(0) + } + }, [anchorEl]) + + // Keyboard shortcut hint handler + useEffect(() => { + const handleKeyDown = event => { + if (event.ctrlKey || event.metaKey) { + setShowKeyboardShortcuts(true) + } + } + + const handleKeyUp = event => { + if (!event.ctrlKey && !event.metaKey) { + setShowKeyboardShortcuts(false) + } + } + + document.addEventListener('keydown', handleKeyDown) + document.addEventListener('keyup', handleKeyUp) + return () => { + document.removeEventListener('keydown', handleKeyDown) + document.removeEventListener('keyup', handleKeyUp) + } + }, []) + return ( <> {!label && ( - - {icon} - {label ? label : null} - + + + {icon} + {label ? label : null} + + + )} {label && ( - + + + + )} - - - Group By - + + + + {title || 'Group By'} + + + + {[ { name: 'Smart', value: 'default' }, { name: 'Due Date', value: 'due_date' }, { name: 'Priority', value: 'priority' }, { name: 'Labels', value: 'labels' }, - ].map(item => ( + ].map((item, index) => ( { @@ -113,41 +276,87 @@ const SortAndGrouping = ({ setSelectedItem?.(item.name) handleMenuClose() }} + onMouseEnter={() => setIsKeyboardNavigating(false)} + sx={{ + borderRadius: 'var(--joy-radius-sm)', + backgroundColor: + selectedItem === item.name + ? 'var(--joy-palette-primary-softBg)' + : selectedIndex === index && anchorEl && isKeyboardNavigating + ? 'var(--joy-palette-neutral-softHoverBg)' + : 'transparent', + '&:hover': { + backgroundColor: + selectedItem === item.name + ? 'var(--joy-palette-primary-softBg)' + : 'var(--joy-palette-neutral-softHoverBg)', + }, + }} > - {useChips ? ( - + - {item.name} - - ) : ( - <> - {item?.icon} - + {item.name} - - )} + {selectedItem === item.name && ( + + )} + + ))} - + - - - Quick Filters - + + + + Quick Filters + + - - - Assigned to : - + + + + Assigned to: + + setIsKeyboardNavigating(false)} + sx={{ + borderRadius: 'var(--joy-radius-sm)', + backgroundColor: + selectedFilter === 'anyone' + ? 'var(--joy-palette-primary-softBg)' + : selectedIndex === 4 && anchorEl && isKeyboardNavigating + ? 'var(--joy-palette-neutral-softHoverBg)' + : 'transparent', + '&:hover': { + backgroundColor: + selectedFilter === 'anyone' + ? 'var(--joy-palette-primary-softBg)' + : 'var(--joy-palette-neutral-softHoverBg)', + }, + }} > - - Anyone + + + + + + + Anyone + + + - - Assigned to me - - - {/* { - setFilter('assignable_to_me') - handleMenuClose() + onMouseEnter={() => setIsKeyboardNavigating(false)} + sx={{ + borderRadius: 'var(--joy-radius-sm)', + backgroundColor: + selectedFilter === 'assigned_to_me' + ? 'var(--joy-palette-primary-softBg)' + : selectedIndex === 5 && anchorEl && isKeyboardNavigating + ? 'var(--joy-palette-neutral-softHoverBg)' + : 'transparent', + '&:hover': { + backgroundColor: + selectedFilter === 'assigned_to_me' + ? 'var(--joy-palette-primary-softBg)' + : 'var(--joy-palette-neutral-softHoverBg)', + }, }} > - - Available for me - */} + + + + + + + Assigned to me + + + + setIsKeyboardNavigating(false)} + sx={{ + borderRadius: 'var(--joy-radius-sm)', + backgroundColor: + selectedFilter === 'assigned_to_others' + ? 'var(--joy-palette-primary-softBg)' + : selectedIndex === 6 && anchorEl && isKeyboardNavigating + ? 'var(--joy-palette-neutral-softHoverBg)' + : 'transparent', + '&:hover': { + backgroundColor: + selectedFilter === 'assigned_to_others' + ? 'var(--joy-palette-primary-softBg)' + : 'var(--joy-palette-neutral-softHoverBg)', + }, + }} > - - Assigned to others + + + + + + + Assigned to others + + + - + { onCreateNewFilter() handleMenuClose() - // TODO: Open advanced filter builder + }} + onMouseEnter={() => setIsKeyboardNavigating(false)} + sx={{ + borderRadius: 'var(--joy-radius-sm)', + backgroundColor: + selectedIndex === 7 && anchorEl && isKeyboardNavigating + ? 'var(--joy-palette-success-softHoverBg)' + : 'transparent', + '&:hover': { + backgroundColor: 'var(--joy-palette-success-softHoverBg)', + }, }} > - - Create Custom Filter... - + + + + + + Create Custom Filter + + + Build advanced filter rules + + - - {/* -// i need this but i think it have a bad UX and confusing so commenting it for now - { - setFilter('created_by_me') - handleMenuClose() - }} - > - - Created by me - */} ) diff --git a/src/views/Chores/components/CustomFilterChips.jsx b/src/views/Chores/components/CustomFilterChips.jsx index 68c0c98..5e5fbd9 100644 --- a/src/views/Chores/components/CustomFilterChips.jsx +++ b/src/views/Chores/components/CustomFilterChips.jsx @@ -1,4 +1,5 @@ import { + Check, Delete, Edit, Settings, @@ -129,33 +130,40 @@ const CustomFilterChips = ({ }} /> )} - + ) : ( + - {filter.count} - + } + > + {filter.count} + + )} } endDecorator={ @@ -206,15 +214,14 @@ const CustomFilterChips = ({ } - sx={{ cursor: 'pointer' }} + sx={{ cursor: 'pointer', minWidth: 'auto', px: 0.8 }} onClick={e => { e.preventDefault() e.stopPropagation() navigate('/filters') }} > - Manage Filters + { const { data: userProfile } = useUserProfile() const [savedFilters, setSavedFilters] = useState([]) const [activeFilterId, setActiveFilterId] = useState(null) + const [tempFilter, setTempFilter] = useState(null) const loadFilters = useCallback(() => { const filters = getSavedFilters() @@ -74,12 +75,21 @@ export const useCustomFilters = (chores, membersData, labels, projects) => { return activeFilter.conditions.some(c => c.type === 'project') }, [activeFilter]) + // check if has any filter applied: + const hasFilterApplied = useMemo(() => { + return activeFilterId !== null || tempFilter !== null + }, [activeFilterId, tempFilter]) + const filteredChores = useMemo(() => { + // Temporary filter takes precedence over saved filters + if (tempFilter) { + return applyFilter(chores, tempFilter, context) + } if (!activeFilter || !activeFilter.isValid) { return chores } return applyFilter(chores, activeFilter, context) - }, [chores, activeFilter, context]) + }, [chores, activeFilter, tempFilter, context]) const applyCustomFilter = useCallback( filterId => { @@ -92,6 +102,16 @@ export const useCustomFilters = (chores, membersData, labels, projects) => { const clearActiveFilter = useCallback(() => { setActiveFilterId(null) + setTempFilter(null) + }, []) + + const applyTempFilter = useCallback(filter => { + setTempFilter(filter) + setActiveFilterId(null) + }, []) + + const clearTempFilter = useCallback(() => { + setTempFilter(null) }, []) const saveFilter = useCallback( @@ -228,14 +248,18 @@ export const useCustomFilters = (chores, membersData, labels, projects) => { savedFilters: filtersWithCounts, activeFilter, activeFilterId, + tempFilter, filteredChores, applyCustomFilter, clearActiveFilter, + applyTempFilter, + clearTempFilter, saveFilter, updateFilter, deleteFilter, pinFilter, createFilterFromCurrentState, hasProjectConditions, + hasFilterApplied, } } diff --git a/src/views/Filters/FilterView.jsx b/src/views/Filters/FilterView.jsx index 3af21b2..7b3b3df 100644 --- a/src/views/Filters/FilterView.jsx +++ b/src/views/Filters/FilterView.jsx @@ -13,7 +13,14 @@ import { import { useEffect, useRef, useState } from 'react' import { useNavigate } from 'react-router-dom' -import { Add, FilterAlt, Star, StarBorder, Task } from '@mui/icons-material' +import { + Add, + FilterAlt, + MoreVert, + Star, + StarBorder, + Task, +} from '@mui/icons-material' import { useChores } from '../../queries/ChoreQueries' import { useCircleMembers, useUserProfile } from '../../queries/UserQueries' import { @@ -328,7 +335,7 @@ const FilterCard = ({ return } // Navigate to MyChores with filter applied via URL param - navigate(`/chores?filterId=${encodeURIComponent(filter.id)}`) + navigate(`/filters/${encodeURIComponent(filter.id)}`) }} onTouchStart={handleTouchStart} onTouchMove={handleTouchMove} @@ -362,6 +369,11 @@ const FilterCard = ({ }} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} + onClick={e => { + e.stopPropagation() + const clampedDelta = isSwipeRevealed ? 0 : -maxSwipeDistance + setSwipeTranslateX(clampedDelta) + }} > {/* Drag indicator dots */} - {[...Array(3)].map((_, i) => ( - - ))} + diff --git a/src/views/Modals/Inputs/AdvancedFilterBuilder.jsx b/src/views/Modals/Inputs/AdvancedFilterBuilder.jsx index af5ee54..c4aafd4 100644 --- a/src/views/Modals/Inputs/AdvancedFilterBuilder.jsx +++ b/src/views/Modals/Inputs/AdvancedFilterBuilder.jsx @@ -173,6 +173,12 @@ const AdvancedFilterBuilder = ({ } placeholder='Select assignees' sx={{ width: '100%' }} + slotProps={{ + listbox: { + placement: 'bottom-start', + disablePortal: false, + }, + }} renderValue={selected => ( {selected.map((selectedElement, idx) => { @@ -210,6 +216,12 @@ const AdvancedFilterBuilder = ({ } placeholder='Select creators' sx={{ width: '100%' }} + slotProps={{ + listbox: { + placement: 'bottom-start', + disablePortal: false, + }, + }} renderValue={selected => ( {selected.map((selectedElement, idx) => { @@ -255,13 +267,23 @@ const AdvancedFilterBuilder = ({ } placeholder='Select priorities' sx={{ width: '100%' }} + slotProps={{ + listbox: { + placement: 'bottom-start', + disablePortal: false, + }, + }} renderValue={selected => ( - {selected.map((value, idx) => ( - - Priority {value} - - ))} + {selected.map((selectedElement, idx) => { + const value = selectedElement.value + const priority = Priorities.find(p => p.value === value) + return ( + + {priority?.name || `Priority ${value}`} + + ) + })} )} > @@ -286,6 +308,12 @@ const AdvancedFilterBuilder = ({ } placeholder='Select labels' sx={{ width: '100%' }} + slotProps={{ + listbox: { + placement: 'bottom-start', + disablePortal: false, + }, + }} renderValue={selected => ( {selected.map((selectedElement, idx) => { @@ -319,6 +347,12 @@ const AdvancedFilterBuilder = ({ } placeholder='Select projects' sx={{ width: '100%' }} + slotProps={{ + listbox: { + placement: 'bottom-start', + disablePortal: false, + }, + }} renderValue={selected => ( {selected.map((event, idx) => { @@ -363,6 +397,12 @@ const AdvancedFilterBuilder = ({ updateCondition(index, 'value', [newValue]) } sx={{ width: '100%' }} + slotProps={{ + listbox: { + placement: 'bottom-start', + disablePortal: false, + }, + }} > @@ -379,6 +419,12 @@ const AdvancedFilterBuilder = ({ updateCondition(index, 'operator', newValue) } sx={{ width: '100%' }} + slotProps={{ + listbox: { + placement: 'bottom-start', + disablePortal: false, + }, + }} > @@ -467,6 +513,12 @@ const AdvancedFilterBuilder = ({