Refactor chore filtering and search performance (#195)

* Refactor assignment strategy handling in ChoreEdit component

* performance improvement : Refactor chore filtering logic and improve search functionality in MyChores and useChoreFilters hooks. reduce rerender etc...

* making sure the filter clear when it supposed to

* formating
This commit is contained in:
Mohamad Tarbin
2026-08-06 23:56:15 -04:00
committed by GitHub
parent 555c9d0774
commit 0b4b21bbb0
6 changed files with 200 additions and 237 deletions

View File

@@ -2,21 +2,21 @@ import { Add, Close } from '@mui/icons-material'
import { Box, Button, Chip, ChipDelete, Typography } from '@mui/joy'
const ActiveFilterChips = ({
chips = [],
onOpen,
onClearAll,
onAdd,
showAddChip = false,
resultCount,
totalCount,
maxVisible = 2,
chipSize = 'md',
chipSx,
chips = [],
clearButtonSize = 'sm',
clearButtonSx,
containerSx,
chipSx,
maxVisible = 2,
onAdd,
onClearAll,
onOpen,
overflowChipSx,
resultCount,
resultSx,
showAddChip = false,
totalCount,
}) => {
if (!chips.length) {
return null
@@ -39,7 +39,7 @@ const ActiveFilterChips = ({
...containerSx,
}}
>
{visible.map(({ key, label, onClear, color = 'primary' }) => (
{visible.map(({ color = 'primary', key, label, onClear }) => (
<Chip
key={key}
size={chipSize}
@@ -52,7 +52,10 @@ const ActiveFilterChips = ({
<ChipDelete
variant='plain'
color={color}
onDelete={() => onClear?.()}
onDelete={event => {
event.stopPropagation()
onClear?.()
}}
aria-label={`Remove ${label} filter`}
sx={{
'--Chip-deleteSize': chipSize === 'sm' ? '1.1rem' : '1.4rem',

View File

@@ -89,6 +89,8 @@ const buildActualDateGroups = chores => {
export const ChoresGrouper = (groupBy, chores, filter) => {
if (filter) {
chores = chores.filter(chore => filter(chore))
} else {
chores = [...chores]
}
// sort by priority then due date:

View File

@@ -37,6 +37,7 @@ import {
import moment from 'moment'
import { useEffect, useState } from 'react'
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
import DurationInput from '../../components/common/DurationInput'
import KeyboardShortcutHint from '../../components/common/KeyboardShortcutHint'
import NotificationTemplate from '../../components/NotificationTemplate.jsx'
@@ -60,10 +61,10 @@ import {
} from '../../utils/Fetcher'
import { isPlusAccount, resolvePhotoURL } from '../../utils/Helpers'
import { getImageSrc, removeCachedImage } from '../../utils/ImageCache'
import { generateUUID } from '../../utils/UUID'
import Priorities from '../../utils/Priorities.jsx'
import { getIconComponent } from '../../utils/ProjectIcons'
import { getSafeBottomPadding } from '../../utils/SafeAreaUtils.js'
import { generateUUID } from '../../utils/UUID'
import { useProjectFilter } from '../Chores/hooks/useProjectFilter.js'
import LoadingComponent from '../components/Loading.jsx'
import RichTextEditor from '../components/RichTextEditor.jsx'
@@ -84,6 +85,7 @@ const ASSIGN_STRATEGIES = [
'round_robin',
'no_assignee',
]
const DEFAULT_ASSIGN_STRATEGY = ASSIGN_STRATEGIES[3] // keep_last_assigned
const REPEAT_ON_TYPE = ['interval', 'days_of_the_week', 'day_of_the_month']
const NO_DUE_DATE_REQUIRED_TYPE = ['no_repeat', 'once']
@@ -103,7 +105,7 @@ const ChoreEdit = () => {
const [anyone, setAnyone] = useState(false)
const [assignableTo, setAssignableTo] = useState([])
const [performers, setPerformers] = useState([])
const [assignStrategy, setAssignStrategy] = useState(ASSIGN_STRATEGIES[2])
const [assignStrategy, setAssignStrategy] = useState(DEFAULT_ASSIGN_STRATEGY)
const [dueDate, setDueDate] = useState(null)
const [dueDateOnly, setDueDateOnly] = useState(null)
const [dueTime, setDueTime] = useState(null)
@@ -151,7 +153,7 @@ const ChoreEdit = () => {
const { data: userLabelsRaw, isLoading: isUserLabelsLoading } = useLabels()
const { data: projects = [], isLoading: isProjectsLoading } = useProjects()
const { selectedProject, projectsWithDefault, setSelectedProjectWithCache } =
const { projectsWithDefault, selectedProject, setSelectedProjectWithCache } =
useProjectFilter(projects)
const [projectId, setProjectId] = useState(
@@ -170,7 +172,7 @@ const ChoreEdit = () => {
} = useChore(choreId)
const { data: membersData, isLoading: isMemberDataLoading } =
useCircleMembers()
const { showSuccess, showError } = useNotification()
const { showError, showSuccess } = useNotification()
const [userLabels, setUserLabels] = useState([])
@@ -183,20 +185,26 @@ const ChoreEdit = () => {
const Navigate = useNavigate()
const assignees = anyone ? performers : assignableTo
const hasSpecificAssignees = !anyone && assignableTo.length > 0
const canPickStrategy = hasSpecificAssignees && assignableTo.length > 1
const assignStrategyValue = !hasSpecificAssignees
? 'no_assignee'
: canPickStrategy
? assignStrategy
: DEFAULT_ASSIGN_STRATEGY
const assignedToValue =
!hasSpecificAssignees || assignStrategyValue === 'no_assignee'
? null
: assignableTo.some(a => a.userId === assignedTo)
? assignedTo
: assignableTo[0].userId
const HandleValidateChore = () => {
const errors = {}
if (name.trim() === '') {
errors.name = 'Name is required'
}
if (assignStrategy !== 'no_assignee') {
if (assignees.length === 0) {
errors.assignees = 'At least 1 assignees is required'
}
if (assignedTo === null || assignedTo < 0) {
errors.assignedTo = 'Assigned to is required'
}
}
if (frequencyType === 'interval' && !frequency > 0) {
errors.frequency = `Invalid frequency, the ${frequencyMetadata.unit} should be > 0`
}
@@ -366,8 +374,8 @@ const ChoreEdit = () => {
frequencyType: frequencyType,
frequency: Number(frequency),
frequencyMetadata: frequencyMetadata,
assignedTo: assignStrategy === 'no_assignee' ? null : assignedTo,
assignStrategy: assignStrategy,
assignedTo: assignedToValue,
assignStrategy: assignStrategyValue,
isRolling: isRolling,
isActive: isActive,
notification: isNotificable,
@@ -463,6 +471,20 @@ const ChoreEdit = () => {
}
}
}, [])
useEffect(() => {
if (choreId || !userProfile?.id) return
const defaultAnyoneSetting = localStorage.getItem('defaultAnyoneSetting')
const defaultAssigneeSetting = localStorage.getItem(
'defaultAssigneeSetting',
)
if (defaultAnyoneSetting === null && defaultAssigneeSetting === null) {
setAnyone(false)
setAssignableTo([{ userId: userProfile.id }])
setAssignedTo(userProfile.id)
}
}, [choreId, userProfile?.id])
useEffect(() => {
const anyoneSetting = localStorage.getItem('defaultAnyoneSetting')
const anyoneDirty = anyoneSetting !== JSON.stringify(anyone)
@@ -549,7 +571,7 @@ const ChoreEdit = () => {
setAssignStrategy(
data.res.assignStrategy
? data.res.assignStrategy
: ASSIGN_STRATEGIES[2],
: DEFAULT_ASSIGN_STRATEGY,
)
setIsRolling(data.res.isRolling)
setIsActive(data.res.isActive)
@@ -632,21 +654,6 @@ const ChoreEdit = () => {
}
}, [frequencyType])
useEffect(() => {
if (anyone || assignableTo.length === 0) {
setAssignStrategy('no_assignee')
setAssignedTo(null)
} else if (assignStrategy === 'no_assignee') {
// user explicitly picked no_assignee while having assignees, keep it
// but there is nobody currently assigned
if (assignedTo !== null) {
setAssignedTo(null)
}
} else if (!assignableTo.some(a => a.userId === assignedTo)) {
setAssignedTo(assignableTo[0].userId)
}
}, [assignStrategy, assignedTo, assignableTo, anyone])
// useEffect(() => {
// if (performers.length > 0 && assignees.length === 0 && userProfile) {
// setAssignees([
@@ -1260,12 +1267,13 @@ const ChoreEdit = () => {
)}
</Box>
{!anyone && assignableTo.length > 1 && (
{canPickStrategy && (
<>
<Box
mb={3}
sx={{
display: assignStrategy === 'no_assignee' ? 'none' : 'block',
display:
assignStrategyValue === 'no_assignee' ? 'none' : 'block',
}}
>
<Typography level='h4'>Currently Assigned To</Typography>
@@ -1279,7 +1287,7 @@ const ChoreEdit = () => {
: 'Select an assignee for this task'
}
disabled={assignees.length === 0}
value={assignedTo > -1 ? assignedTo : null}
value={assignedToValue}
onChange={(_, selectedUserId) => setAssignedTo(selectedUserId)}
>
{performers
@@ -1309,7 +1317,7 @@ const ChoreEdit = () => {
{ASSIGN_STRATEGIES.map((item, idx) => (
<ListItem key={item}>
<Checkbox
checked={assignStrategy === item}
checked={assignStrategyValue === item}
onClick={() => setAssignStrategy(item)}
overlay
disableIcon

View File

@@ -4,9 +4,9 @@ import {
CalendarMonth,
CloudOff,
EditCalendar,
SearchOff,
ExpandCircleDown,
PriorityHigh,
SearchOff,
Style,
} from '@mui/icons-material'
import {
@@ -20,44 +20,42 @@ import {
IconButton,
Typography,
} from '@mui/joy'
import Fuse from 'fuse.js'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { useChores } from '../../queries/ChoreQueries'
import { useNotification } from '../../service/NotificationProvider'
import Priorities from '../../utils/Priorities'
import LoadingComponent from '../components/Loading'
import { useLabels } from '../Labels/LabelQueries'
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
import IconButtonWithMenu from './IconButtonWithMenu'
import { useMediaQuery } from '@mui/material'
import { useQueryClient } from '@tanstack/react-query'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import EmptyState from '../../components/common/EmptyState'
import KeyboardShortcutHint from '../../components/common/KeyboardShortcutHint'
import { useFilter } from '../../hooks/useFilter'
import { useImpersonateUser } from '../../contexts/ImpersonateUserContext.jsx'
import { useFilter } from '../../hooks/useFilter'
import { useChores } from '../../queries/ChoreQueries'
import { useCircleMembers, useUserProfile } from '../../queries/UserQueries'
import { useNotification } from '../../service/NotificationProvider'
import {
ChoreFilters,
ChoresGrouper,
ChoreSorter,
filterByProject,
} from '../../utils/Chores'
import Priorities from '../../utils/Priorities'
import { getSafeBottom } from '../../utils/SafeAreaUtils.js'
import TaskInput from '../components/AddTaskModal'
import CalendarDual from '../components/CalendarDual'
import CalendarMonthly from '../components/CalendarMonthly.jsx'
import FeedbackPrompt from '../components/FeedbackPrompt.jsx'
import LoadingComponent from '../components/Loading'
import { useLabels } from '../Labels/LabelQueries'
import AdvancedFilterBuilder from '../Modals/Inputs/AdvancedFilterBuilder'
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
import { useProjects } from '../Projects/ProjectQueries.js'
import ChoreListView from './ChoreListView.jsx'
import ChoreModals from './components/ChoreModals'
import ChoreToolbar from './components/ChoreToolbarPrototype'
import {
conditionsToSelections,
selectionsToConditions,
} from './components/FilterBuilderContent'
import ChoreModals from './components/ChoreModals'
import MultiSelectToolbar from './components/MultiSelectToolbar'
import MyChoreHeader from './components/MyChoreHeader'
import { useChoreActions } from './hooks/useChoreActions'
@@ -79,7 +77,7 @@ const MyChores = () => {
const { data: userProfile, isLoading: isUserProfileLoading } =
useUserProfile()
const isLargeScreen = useMediaQuery(theme => theme.breakpoints.up('md'))
const { showSuccess, showError, showWarning, showUndo } = useNotification()
const { showError, showSuccess, showUndo, showWarning } = useNotification()
const queryClient = useQueryClient()
const { impersonatedUser } = useImpersonateUser()
const Navigate = useNavigate()
@@ -88,20 +86,19 @@ const MyChores = () => {
const { data: projects = [], isLoading: projectsLoading } = useProjects()
const {
data: choresData,
isLoading: choresLoading,
isError: choresError,
error: choresErrorDetails,
isError: choresError,
isLoading: choresLoading,
refetch: refetchChores,
} = useChores(false)
const {
data: membersData,
isLoading: membersLoading,
isError: membersError,
isLoading: membersLoading,
} = useCircleMembers()
const [chores, setChores] = useState([])
const [filteredChores, setFilteredChores] = useState([])
const [choreSections, setChoreSections] = useState([])
const [addTaskModalOpen, setAddTaskModalOpen] = useState(false)
// 'voice' | 'scan' | null — set by the quick-capture widget deep links
const [addTaskInitialMode, setAddTaskInitialMode] = useState(null)
@@ -118,6 +115,9 @@ const MyChores = () => {
return {}
}
})
const openSectionsInitializedRef = useRef(
localStorage.getItem('openChoreSections') !== null,
)
const [anchorEl, setAnchorEl] = useState(null)
const [viewMode, setViewMode] = useState(
localStorage.getItem('choreCardViewMode') || 'default',
@@ -126,15 +126,15 @@ const MyChores = () => {
const menuRef = useRef(null)
const [confirmModelConfig, setConfirmModelConfig] = useState({})
const { selectedProject, projectsWithDefault, setSelectedProjectWithCache } =
const { projectsWithDefault, selectedProject, setSelectedProjectWithCache } =
useProjectFilter(projects, !projectsLoading)
const {
searchTerm,
selectedChoreFilter,
nonProjectFilteredChores,
projectFilteredChores,
searchFilteredChores,
nonProjectFilteredChores,
searchTerm,
selectedChoreFilter,
setSearchTerm,
setSelectedChoreFilterWithCache,
} = useChoreFilters({
@@ -145,37 +145,37 @@ const MyChores = () => {
})
const {
isMultiSelectMode,
selectedChores,
toggleMultiSelectMode,
toggleChoreSelection,
enterMultiSelectWithChore,
selectAllVisibleChores,
clearSelection,
enterMultiSelectWithChore,
getSelectedChoresData,
isMultiSelectMode,
selectAllVisibleChores,
selectedChores,
toggleChoreSelection,
toggleMultiSelectMode,
} = useMultiSelect()
const { activeModal, modalChore, modalData, openModal, closeModal } =
const { activeModal, closeModal, modalChore, modalData, openModal } =
useChoreModals()
const {
savedFilters,
activeFilter,
activeFilterId,
applyCustomFilter,
applyTempFilter,
clearActiveFilter,
clearTempFilter,
createFilterFromCurrentState,
deleteFilter,
filteredChores: customFilteredChores,
hasFilterApplied,
hasProjectConditions,
pinFilter,
saveFilter,
savedFilters,
tempFilter,
tempFilterMeta,
filteredChores: customFilteredChores,
applyCustomFilter,
clearActiveFilter,
applyTempFilter,
clearTempFilter,
saveFilter,
updateFilter,
deleteFilter,
pinFilter,
createFilterFromCurrentState,
hasProjectConditions,
hasFilterApplied,
} = useCustomFilters(
nonProjectFilteredChores,
membersData?.res,
@@ -276,10 +276,10 @@ const MyChores = () => {
)
const {
filteredData: quickFilteredChores,
setFilter: setQuickFilter,
clearAll: clearQuickFilters,
filteredData: quickFilteredChores,
hasActiveFilters: hasQuickFilters,
setFilter: setQuickFilter,
} = useFilter(projectFilteredChores, quickFilterDefs)
const processedChores = useMemo(() => {
@@ -301,7 +301,7 @@ const MyChores = () => {
return sortedChores
}, [choresData?.res, impersonatedUser])
const processedSections = useMemo(() => {
const choreSections = useMemo(() => {
if (!chores.length || !userProfile?.id) {
return []
}
@@ -385,26 +385,16 @@ const MyChores = () => {
impersonatedUser?.userId,
])
// Auto-update sections when processedSections changes
useEffect(() => {
// Always update choreSections to match processedSections, even if empty
setChoreSections(processedSections)
if (openSectionsInitializedRef.current || choreSections.length === 0) return
// Auto-open sections if needed - only check localStorage once
if (processedSections.length > 0) {
const storedSections = localStorage.getItem('openChoreSections')
if (storedSections === null) {
const openSections = processedSections.reduce(
(acc, _section, index) => {
acc[index] = true
return acc
},
{},
)
setOpenChoreSections(openSections)
}
}
}, [processedSections])
openSectionsInitializedRef.current = true
const openSections = choreSections.reduce((acc, _section, index) => {
acc[index] = true
return acc
}, {})
setOpenChoreSections(openSections)
}, [choreSections])
useEffect(() => {
document.addEventListener('mousedown', handleMenuOutsideClick)
@@ -567,17 +557,17 @@ const MyChores = () => {
}, [tempFilterMeta?.id, searchParams])
const {
handleChoreAction,
handleChangeDueDate,
handleCompleteWithPastDate,
handleAssigneeChange,
handleCompleteWithNote,
handleNudge,
handleBulkComplete,
handleBulkArchive,
handleBulkComplete,
handleBulkDelete,
handleBulkSkip,
handleBulkMoveToProject,
handleBulkSkip,
handleChangeDueDate,
handleChoreAction,
handleCompleteWithNote,
handleCompleteWithPastDate,
handleNudge,
} = useChoreActions({
chores,
filteredChores,
@@ -603,24 +593,14 @@ const MyChores = () => {
return customFilteredChores
}
if (searchTerm?.length > 0) {
return searchFilteredChores
}
const baseChores = hasQuickFilters
? quickFilteredChores
: projectFilteredChores
if (searchTerm?.length > 0) {
const searchableChores = baseChores.map(c => ({
...c,
raw_label: c.labelsV2?.map(l => l.name).join(' '),
}))
const fuse = new Fuse(searchableChores, {
keys: ['name', 'raw_label'],
includeScore: true,
isCaseSensitive: false,
findAllMatches: true,
})
return fuse.search(searchTerm).map(result => result.item)
}
return baseChores
}, [
activeFilterId,
@@ -629,6 +609,7 @@ const MyChores = () => {
hasQuickFilters,
quickFilteredChores,
projectFilteredChores,
searchFilteredChores,
searchTerm,
])
@@ -751,29 +732,10 @@ const MyChores = () => {
)
}
const searchOptions = useMemo(
() => ({
keys: ['name', 'raw_label'],
includeScore: true,
isCaseSensitive: false,
findAllMatches: true,
}),
[],
)
const processedChoresForSearch = useMemo(
() =>
chores.map(c => ({
...c,
raw_label: c.labelsV2?.map(l => l.name).join(' '),
})),
[chores],
)
const fuse = useMemo(
() => new Fuse(processedChoresForSearch, searchOptions),
[processedChoresForSearch, searchOptions],
)
const clearTempFilterAndUrl = () => {
clearTempFilter()
updateFilterUrl(null, null)
}
const handleSearchChange = e => {
clearActiveFilter()
@@ -790,22 +752,6 @@ const MyChores = () => {
const term = search.toLowerCase()
setSearchTerm(term)
// Use project-filtered chores as base for search
const baseChores = selectedProject ? projectFilteredChores : chores
const searchableChores = baseChores.map(c => ({
...c,
raw_label: c.labelsV2?.map(l => l.name).join(' '),
}))
const fuse = new Fuse(searchableChores, {
keys: ['name', 'raw_label'],
includeScore: true,
isCaseSensitive: false,
findAllMatches: true,
})
setFilteredChores(fuse.search(term).map(result => result.item))
// Clear selected calendar date when search changes
setSelectedCalendarDate(null)
}
@@ -895,19 +841,15 @@ const MyChores = () => {
// )
// }
const getChoresForDate = useCallback(
date => {
const filteredChoresData = getFilteredChores
return filteredChoresData.filter(chore => {
if (!chore.nextDueDate) return false
const choreDate = new Date(chore.nextDueDate).toLocaleDateString()
const selectedDate = date.toLocaleDateString()
return choreDate === selectedDate
})
},
[getFilteredChores],
)
const selectedDateChores = useMemo(() => {
if (!selectedCalendarDate) return []
const selectedDate = selectedCalendarDate.toLocaleDateString()
return getFilteredChores.filter(chore => {
if (!chore.nextDueDate) return false
return new Date(chore.nextDueDate).toLocaleDateString() === selectedDate
})
}, [getFilteredChores, selectedCalendarDate])
// "Narrowed" means the user actively cut the list down (search, quick
// filters, a saved filter). Picking a project is not narrowing: an empty
@@ -929,7 +871,6 @@ const MyChores = () => {
const appendChore = (prev, newChore) => {
let newChores = [...prev, newChore]
if (impersonatedUser) {
newChores = newChores.filter(
chore => chore.assignedTo === impersonatedUser.userId,
@@ -1010,7 +951,7 @@ const MyChores = () => {
tempFilter={tempFilter}
tempFilterMeta={tempFilterMeta}
applyTempFilter={applyTempFilter}
clearTempFilter={clearTempFilter}
clearTempFilter={clearTempFilterAndUrl}
saveFilter={saveFilter}
updateFilter={updateFilter}
onFilterSaved={name =>
@@ -1363,7 +1304,7 @@ const MyChores = () => {
overflowY: 'auto',
}}
>
{getChoresForDate(selectedCalendarDate).length === 0 ? (
{selectedDateChores.length === 0 ? (
<EmptyState
size='sm'
icon={<EditCalendar />}
@@ -1377,7 +1318,7 @@ const MyChores = () => {
/>
) : (
<ChoreListView
chores={getChoresForDate(selectedCalendarDate)}
chores={selectedDateChores}
viewMode={'compact'}
membersData={membersData}
userLabels={userLabels}
@@ -1457,18 +1398,20 @@ const MyChores = () => {
},
}}
>
<ChoreListView
chores={section.content}
viewMode={viewMode}
membersData={membersData}
userLabels={userLabels}
handleLabelFiltering={handleLabelFiltering}
handleChoreAction={handleChoreAction}
isMultiSelectMode={isMultiSelectMode}
selectedChores={selectedChores}
toggleChoreSelection={toggleChoreSelection}
onLongPressChore={enterMultiSelectWithChore}
/>
{openChoreSections[index] && (
<ChoreListView
chores={section.content}
viewMode={viewMode}
membersData={membersData}
userLabels={userLabels}
handleLabelFiltering={handleLabelFiltering}
handleChoreAction={handleChoreAction}
isMultiSelectMode={isMultiSelectMode}
selectedChores={selectedChores}
toggleChoreSelection={toggleChoreSelection}
onLongPressChore={enterMultiSelectWithChore}
/>
)}
</AccordionDetails>
</Accordion>
)
@@ -1574,7 +1517,7 @@ const MyChores = () => {
allChores={chores}
performers={membersData?.res || []}
applyTempFilter={applyTempFilter}
clearTempFilter={clearTempFilter}
clearTempFilter={clearTempFilterAndUrl}
tempFilter={tempFilter}
/>

View File

@@ -22,23 +22,36 @@ export const useChoreFilters = ({
return filterByProject(chores, selectedProject.id)
}, [chores, selectedProject])
const hasSearchTerm = searchTerm.length > 0
const searchIndex = useMemo(() => {
if (!hasSearchTerm) return null
const searchableChores = chores.map(chore => ({
...chore,
raw_label: chore.labelsV2?.map(label => label.name).join(' '),
}))
return new Fuse(searchableChores, {
keys: ['name', 'raw_label'],
includeScore: true,
isCaseSensitive: false,
findAllMatches: true,
})
}, [chores, hasSearchTerm])
const projectChoreIds = useMemo(
() => new Set(projectFilteredChores.map(chore => chore.id)),
[projectFilteredChores],
)
const searchFilteredChores = useMemo(() => {
let baseChores = projectFilteredChores
if (searchTerm?.length > 0) {
const searchableChores = baseChores.map(c => ({
...c,
raw_label: c.labelsV2?.map(l => l.name).join(' '),
}))
const fuse = new Fuse(searchableChores, {
keys: ['name', 'raw_label'],
includeScore: true,
isCaseSensitive: false,
findAllMatches: true,
})
return fuse.search(searchTerm.toLowerCase()).map(result => result.item)
if (searchIndex) {
return searchIndex
.search(searchTerm.toLowerCase())
.map(result => result.item)
.filter(chore => projectChoreIds.has(chore.id))
}
if (impersonatedUser) {
@@ -55,6 +68,8 @@ export const useChoreFilters = ({
}, [
searchTerm,
projectFilteredChores,
searchIndex,
projectChoreIds,
impersonatedUser,
userProfile?.id,
selectedChoreFilter,
@@ -64,20 +79,10 @@ export const useChoreFilters = ({
const nonProjectFilteredChores = useMemo(() => {
let baseChores = chores
if (searchTerm?.length > 0) {
const searchableChores = baseChores.map(c => ({
...c,
raw_label: c.labelsV2?.map(l => l.name).join(' '),
}))
const fuse = new Fuse(searchableChores, {
keys: ['name', 'raw_label'],
includeScore: true,
isCaseSensitive: false,
findAllMatches: true,
})
return fuse.search(searchTerm.toLowerCase()).map(result => result.item)
if (searchIndex) {
return searchIndex
.search(searchTerm.toLowerCase())
.map(result => result.item)
}
if (impersonatedUser) {
@@ -94,6 +99,7 @@ export const useChoreFilters = ({
}, [
searchTerm,
chores,
searchIndex,
impersonatedUser,
userProfile?.id,
selectedChoreFilter,

View File

@@ -1,11 +1,7 @@
import { useCallback, useMemo, useState } from 'react'
import { useUserProfile } from '../../../queries/UserQueries'
import {
applyFilter,
getFilterCount,
getFilterOverdueCount,
validateFilter,
} from '../../../utils/FilterEngine'
import { applyFilter, validateFilter } from '../../../utils/FilterEngine'
import {
useCreateFilter,
useDeleteFilter,
@@ -43,12 +39,17 @@ export const useCustomFilters = (chores, membersData, labels, projects) => {
return filtersData.map(filter => {
const validation = validateFilter(filter, context)
const count = validation.isValid
? getFilterCount(chores, filter, context)
: 0
const overdueCount = validation.isValid
? getFilterOverdueCount(chores, filter, context)
: 0
const matchingChores = validation.isValid
? applyFilter(chores, filter, context)
: []
const count = matchingChores.length
const now = new Date()
const overdueCount = matchingChores.reduce((total, chore) => {
if (!chore.nextDueDate || new Date(chore.nextDueDate) >= now) {
return total
}
return total + 1
}, 0)
const result = {
...filter,