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:
@@ -2,21 +2,21 @@ import { Add, Close } from '@mui/icons-material'
|
|||||||
import { Box, Button, Chip, ChipDelete, Typography } from '@mui/joy'
|
import { Box, Button, Chip, ChipDelete, Typography } from '@mui/joy'
|
||||||
|
|
||||||
const ActiveFilterChips = ({
|
const ActiveFilterChips = ({
|
||||||
chips = [],
|
|
||||||
onOpen,
|
|
||||||
onClearAll,
|
|
||||||
onAdd,
|
|
||||||
showAddChip = false,
|
|
||||||
resultCount,
|
|
||||||
totalCount,
|
|
||||||
maxVisible = 2,
|
|
||||||
chipSize = 'md',
|
chipSize = 'md',
|
||||||
|
chipSx,
|
||||||
|
chips = [],
|
||||||
clearButtonSize = 'sm',
|
clearButtonSize = 'sm',
|
||||||
clearButtonSx,
|
clearButtonSx,
|
||||||
containerSx,
|
containerSx,
|
||||||
chipSx,
|
maxVisible = 2,
|
||||||
|
onAdd,
|
||||||
|
onClearAll,
|
||||||
|
onOpen,
|
||||||
overflowChipSx,
|
overflowChipSx,
|
||||||
|
resultCount,
|
||||||
resultSx,
|
resultSx,
|
||||||
|
showAddChip = false,
|
||||||
|
totalCount,
|
||||||
}) => {
|
}) => {
|
||||||
if (!chips.length) {
|
if (!chips.length) {
|
||||||
return null
|
return null
|
||||||
@@ -39,7 +39,7 @@ const ActiveFilterChips = ({
|
|||||||
...containerSx,
|
...containerSx,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{visible.map(({ key, label, onClear, color = 'primary' }) => (
|
{visible.map(({ color = 'primary', key, label, onClear }) => (
|
||||||
<Chip
|
<Chip
|
||||||
key={key}
|
key={key}
|
||||||
size={chipSize}
|
size={chipSize}
|
||||||
@@ -52,7 +52,10 @@ const ActiveFilterChips = ({
|
|||||||
<ChipDelete
|
<ChipDelete
|
||||||
variant='plain'
|
variant='plain'
|
||||||
color={color}
|
color={color}
|
||||||
onDelete={() => onClear?.()}
|
onDelete={event => {
|
||||||
|
event.stopPropagation()
|
||||||
|
onClear?.()
|
||||||
|
}}
|
||||||
aria-label={`Remove ${label} filter`}
|
aria-label={`Remove ${label} filter`}
|
||||||
sx={{
|
sx={{
|
||||||
'--Chip-deleteSize': chipSize === 'sm' ? '1.1rem' : '1.4rem',
|
'--Chip-deleteSize': chipSize === 'sm' ? '1.1rem' : '1.4rem',
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ const buildActualDateGroups = chores => {
|
|||||||
export const ChoresGrouper = (groupBy, chores, filter) => {
|
export const ChoresGrouper = (groupBy, chores, filter) => {
|
||||||
if (filter) {
|
if (filter) {
|
||||||
chores = chores.filter(chore => filter(chore))
|
chores = chores.filter(chore => filter(chore))
|
||||||
|
} else {
|
||||||
|
chores = [...chores]
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort by priority then due date:
|
// sort by priority then due date:
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
|
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
|
||||||
|
|
||||||
import DurationInput from '../../components/common/DurationInput'
|
import DurationInput from '../../components/common/DurationInput'
|
||||||
import KeyboardShortcutHint from '../../components/common/KeyboardShortcutHint'
|
import KeyboardShortcutHint from '../../components/common/KeyboardShortcutHint'
|
||||||
import NotificationTemplate from '../../components/NotificationTemplate.jsx'
|
import NotificationTemplate from '../../components/NotificationTemplate.jsx'
|
||||||
@@ -60,10 +61,10 @@ import {
|
|||||||
} from '../../utils/Fetcher'
|
} from '../../utils/Fetcher'
|
||||||
import { isPlusAccount, resolvePhotoURL } from '../../utils/Helpers'
|
import { isPlusAccount, resolvePhotoURL } from '../../utils/Helpers'
|
||||||
import { getImageSrc, removeCachedImage } from '../../utils/ImageCache'
|
import { getImageSrc, removeCachedImage } from '../../utils/ImageCache'
|
||||||
import { generateUUID } from '../../utils/UUID'
|
|
||||||
import Priorities from '../../utils/Priorities.jsx'
|
import Priorities from '../../utils/Priorities.jsx'
|
||||||
import { getIconComponent } from '../../utils/ProjectIcons'
|
import { getIconComponent } from '../../utils/ProjectIcons'
|
||||||
import { getSafeBottomPadding } from '../../utils/SafeAreaUtils.js'
|
import { getSafeBottomPadding } from '../../utils/SafeAreaUtils.js'
|
||||||
|
import { generateUUID } from '../../utils/UUID'
|
||||||
import { useProjectFilter } from '../Chores/hooks/useProjectFilter.js'
|
import { useProjectFilter } from '../Chores/hooks/useProjectFilter.js'
|
||||||
import LoadingComponent from '../components/Loading.jsx'
|
import LoadingComponent from '../components/Loading.jsx'
|
||||||
import RichTextEditor from '../components/RichTextEditor.jsx'
|
import RichTextEditor from '../components/RichTextEditor.jsx'
|
||||||
@@ -84,6 +85,7 @@ const ASSIGN_STRATEGIES = [
|
|||||||
'round_robin',
|
'round_robin',
|
||||||
'no_assignee',
|
'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 REPEAT_ON_TYPE = ['interval', 'days_of_the_week', 'day_of_the_month']
|
||||||
|
|
||||||
const NO_DUE_DATE_REQUIRED_TYPE = ['no_repeat', 'once']
|
const NO_DUE_DATE_REQUIRED_TYPE = ['no_repeat', 'once']
|
||||||
@@ -103,7 +105,7 @@ const ChoreEdit = () => {
|
|||||||
const [anyone, setAnyone] = useState(false)
|
const [anyone, setAnyone] = useState(false)
|
||||||
const [assignableTo, setAssignableTo] = useState([])
|
const [assignableTo, setAssignableTo] = useState([])
|
||||||
const [performers, setPerformers] = 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 [dueDate, setDueDate] = useState(null)
|
||||||
const [dueDateOnly, setDueDateOnly] = useState(null)
|
const [dueDateOnly, setDueDateOnly] = useState(null)
|
||||||
const [dueTime, setDueTime] = useState(null)
|
const [dueTime, setDueTime] = useState(null)
|
||||||
@@ -151,7 +153,7 @@ const ChoreEdit = () => {
|
|||||||
const { data: userLabelsRaw, isLoading: isUserLabelsLoading } = useLabels()
|
const { data: userLabelsRaw, isLoading: isUserLabelsLoading } = useLabels()
|
||||||
const { data: projects = [], isLoading: isProjectsLoading } = useProjects()
|
const { data: projects = [], isLoading: isProjectsLoading } = useProjects()
|
||||||
|
|
||||||
const { selectedProject, projectsWithDefault, setSelectedProjectWithCache } =
|
const { projectsWithDefault, selectedProject, setSelectedProjectWithCache } =
|
||||||
useProjectFilter(projects)
|
useProjectFilter(projects)
|
||||||
|
|
||||||
const [projectId, setProjectId] = useState(
|
const [projectId, setProjectId] = useState(
|
||||||
@@ -170,7 +172,7 @@ const ChoreEdit = () => {
|
|||||||
} = useChore(choreId)
|
} = useChore(choreId)
|
||||||
const { data: membersData, isLoading: isMemberDataLoading } =
|
const { data: membersData, isLoading: isMemberDataLoading } =
|
||||||
useCircleMembers()
|
useCircleMembers()
|
||||||
const { showSuccess, showError } = useNotification()
|
const { showError, showSuccess } = useNotification()
|
||||||
|
|
||||||
const [userLabels, setUserLabels] = useState([])
|
const [userLabels, setUserLabels] = useState([])
|
||||||
|
|
||||||
@@ -183,20 +185,26 @@ const ChoreEdit = () => {
|
|||||||
const Navigate = useNavigate()
|
const Navigate = useNavigate()
|
||||||
|
|
||||||
const assignees = anyone ? performers : assignableTo
|
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 HandleValidateChore = () => {
|
||||||
const errors = {}
|
const errors = {}
|
||||||
|
|
||||||
if (name.trim() === '') {
|
if (name.trim() === '') {
|
||||||
errors.name = 'Name is required'
|
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) {
|
if (frequencyType === 'interval' && !frequency > 0) {
|
||||||
errors.frequency = `Invalid frequency, the ${frequencyMetadata.unit} should be > 0`
|
errors.frequency = `Invalid frequency, the ${frequencyMetadata.unit} should be > 0`
|
||||||
}
|
}
|
||||||
@@ -366,8 +374,8 @@ const ChoreEdit = () => {
|
|||||||
frequencyType: frequencyType,
|
frequencyType: frequencyType,
|
||||||
frequency: Number(frequency),
|
frequency: Number(frequency),
|
||||||
frequencyMetadata: frequencyMetadata,
|
frequencyMetadata: frequencyMetadata,
|
||||||
assignedTo: assignStrategy === 'no_assignee' ? null : assignedTo,
|
assignedTo: assignedToValue,
|
||||||
assignStrategy: assignStrategy,
|
assignStrategy: assignStrategyValue,
|
||||||
isRolling: isRolling,
|
isRolling: isRolling,
|
||||||
isActive: isActive,
|
isActive: isActive,
|
||||||
notification: isNotificable,
|
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(() => {
|
useEffect(() => {
|
||||||
const anyoneSetting = localStorage.getItem('defaultAnyoneSetting')
|
const anyoneSetting = localStorage.getItem('defaultAnyoneSetting')
|
||||||
const anyoneDirty = anyoneSetting !== JSON.stringify(anyone)
|
const anyoneDirty = anyoneSetting !== JSON.stringify(anyone)
|
||||||
@@ -549,7 +571,7 @@ const ChoreEdit = () => {
|
|||||||
setAssignStrategy(
|
setAssignStrategy(
|
||||||
data.res.assignStrategy
|
data.res.assignStrategy
|
||||||
? data.res.assignStrategy
|
? data.res.assignStrategy
|
||||||
: ASSIGN_STRATEGIES[2],
|
: DEFAULT_ASSIGN_STRATEGY,
|
||||||
)
|
)
|
||||||
setIsRolling(data.res.isRolling)
|
setIsRolling(data.res.isRolling)
|
||||||
setIsActive(data.res.isActive)
|
setIsActive(data.res.isActive)
|
||||||
@@ -632,21 +654,6 @@ const ChoreEdit = () => {
|
|||||||
}
|
}
|
||||||
}, [frequencyType])
|
}, [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(() => {
|
// useEffect(() => {
|
||||||
// if (performers.length > 0 && assignees.length === 0 && userProfile) {
|
// if (performers.length > 0 && assignees.length === 0 && userProfile) {
|
||||||
// setAssignees([
|
// setAssignees([
|
||||||
@@ -1260,12 +1267,13 @@ const ChoreEdit = () => {
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{!anyone && assignableTo.length > 1 && (
|
{canPickStrategy && (
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
mb={3}
|
mb={3}
|
||||||
sx={{
|
sx={{
|
||||||
display: assignStrategy === 'no_assignee' ? 'none' : 'block',
|
display:
|
||||||
|
assignStrategyValue === 'no_assignee' ? 'none' : 'block',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography level='h4'>Currently Assigned To</Typography>
|
<Typography level='h4'>Currently Assigned To</Typography>
|
||||||
@@ -1279,7 +1287,7 @@ const ChoreEdit = () => {
|
|||||||
: 'Select an assignee for this task'
|
: 'Select an assignee for this task'
|
||||||
}
|
}
|
||||||
disabled={assignees.length === 0}
|
disabled={assignees.length === 0}
|
||||||
value={assignedTo > -1 ? assignedTo : null}
|
value={assignedToValue}
|
||||||
onChange={(_, selectedUserId) => setAssignedTo(selectedUserId)}
|
onChange={(_, selectedUserId) => setAssignedTo(selectedUserId)}
|
||||||
>
|
>
|
||||||
{performers
|
{performers
|
||||||
@@ -1309,7 +1317,7 @@ const ChoreEdit = () => {
|
|||||||
{ASSIGN_STRATEGIES.map((item, idx) => (
|
{ASSIGN_STRATEGIES.map((item, idx) => (
|
||||||
<ListItem key={item}>
|
<ListItem key={item}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={assignStrategy === item}
|
checked={assignStrategyValue === item}
|
||||||
onClick={() => setAssignStrategy(item)}
|
onClick={() => setAssignStrategy(item)}
|
||||||
overlay
|
overlay
|
||||||
disableIcon
|
disableIcon
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import {
|
|||||||
CalendarMonth,
|
CalendarMonth,
|
||||||
CloudOff,
|
CloudOff,
|
||||||
EditCalendar,
|
EditCalendar,
|
||||||
SearchOff,
|
|
||||||
ExpandCircleDown,
|
ExpandCircleDown,
|
||||||
PriorityHigh,
|
PriorityHigh,
|
||||||
|
SearchOff,
|
||||||
Style,
|
Style,
|
||||||
} from '@mui/icons-material'
|
} from '@mui/icons-material'
|
||||||
import {
|
import {
|
||||||
@@ -20,44 +20,42 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@mui/joy'
|
} 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 { useMediaQuery } from '@mui/material'
|
||||||
import { useQueryClient } from '@tanstack/react-query'
|
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 EmptyState from '../../components/common/EmptyState'
|
||||||
import KeyboardShortcutHint from '../../components/common/KeyboardShortcutHint'
|
import KeyboardShortcutHint from '../../components/common/KeyboardShortcutHint'
|
||||||
import { useFilter } from '../../hooks/useFilter'
|
|
||||||
import { useImpersonateUser } from '../../contexts/ImpersonateUserContext.jsx'
|
import { useImpersonateUser } from '../../contexts/ImpersonateUserContext.jsx'
|
||||||
|
import { useFilter } from '../../hooks/useFilter'
|
||||||
|
import { useChores } from '../../queries/ChoreQueries'
|
||||||
import { useCircleMembers, useUserProfile } from '../../queries/UserQueries'
|
import { useCircleMembers, useUserProfile } from '../../queries/UserQueries'
|
||||||
|
import { useNotification } from '../../service/NotificationProvider'
|
||||||
import {
|
import {
|
||||||
ChoreFilters,
|
ChoreFilters,
|
||||||
ChoresGrouper,
|
ChoresGrouper,
|
||||||
ChoreSorter,
|
ChoreSorter,
|
||||||
filterByProject,
|
filterByProject,
|
||||||
} from '../../utils/Chores'
|
} from '../../utils/Chores'
|
||||||
|
import Priorities from '../../utils/Priorities'
|
||||||
import { getSafeBottom } from '../../utils/SafeAreaUtils.js'
|
import { getSafeBottom } from '../../utils/SafeAreaUtils.js'
|
||||||
import TaskInput from '../components/AddTaskModal'
|
import TaskInput from '../components/AddTaskModal'
|
||||||
import CalendarDual from '../components/CalendarDual'
|
import CalendarDual from '../components/CalendarDual'
|
||||||
import CalendarMonthly from '../components/CalendarMonthly.jsx'
|
import CalendarMonthly from '../components/CalendarMonthly.jsx'
|
||||||
import FeedbackPrompt from '../components/FeedbackPrompt.jsx'
|
import FeedbackPrompt from '../components/FeedbackPrompt.jsx'
|
||||||
|
import LoadingComponent from '../components/Loading'
|
||||||
|
import { useLabels } from '../Labels/LabelQueries'
|
||||||
import AdvancedFilterBuilder from '../Modals/Inputs/AdvancedFilterBuilder'
|
import AdvancedFilterBuilder from '../Modals/Inputs/AdvancedFilterBuilder'
|
||||||
|
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
|
||||||
import { useProjects } from '../Projects/ProjectQueries.js'
|
import { useProjects } from '../Projects/ProjectQueries.js'
|
||||||
import ChoreListView from './ChoreListView.jsx'
|
import ChoreListView from './ChoreListView.jsx'
|
||||||
|
import ChoreModals from './components/ChoreModals'
|
||||||
import ChoreToolbar from './components/ChoreToolbarPrototype'
|
import ChoreToolbar from './components/ChoreToolbarPrototype'
|
||||||
import {
|
import {
|
||||||
conditionsToSelections,
|
conditionsToSelections,
|
||||||
selectionsToConditions,
|
selectionsToConditions,
|
||||||
} from './components/FilterBuilderContent'
|
} from './components/FilterBuilderContent'
|
||||||
import ChoreModals from './components/ChoreModals'
|
|
||||||
import MultiSelectToolbar from './components/MultiSelectToolbar'
|
import MultiSelectToolbar from './components/MultiSelectToolbar'
|
||||||
import MyChoreHeader from './components/MyChoreHeader'
|
import MyChoreHeader from './components/MyChoreHeader'
|
||||||
import { useChoreActions } from './hooks/useChoreActions'
|
import { useChoreActions } from './hooks/useChoreActions'
|
||||||
@@ -79,7 +77,7 @@ const MyChores = () => {
|
|||||||
const { data: userProfile, isLoading: isUserProfileLoading } =
|
const { data: userProfile, isLoading: isUserProfileLoading } =
|
||||||
useUserProfile()
|
useUserProfile()
|
||||||
const isLargeScreen = useMediaQuery(theme => theme.breakpoints.up('md'))
|
const isLargeScreen = useMediaQuery(theme => theme.breakpoints.up('md'))
|
||||||
const { showSuccess, showError, showWarning, showUndo } = useNotification()
|
const { showError, showSuccess, showUndo, showWarning } = useNotification()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const { impersonatedUser } = useImpersonateUser()
|
const { impersonatedUser } = useImpersonateUser()
|
||||||
const Navigate = useNavigate()
|
const Navigate = useNavigate()
|
||||||
@@ -88,20 +86,19 @@ const MyChores = () => {
|
|||||||
const { data: projects = [], isLoading: projectsLoading } = useProjects()
|
const { data: projects = [], isLoading: projectsLoading } = useProjects()
|
||||||
const {
|
const {
|
||||||
data: choresData,
|
data: choresData,
|
||||||
isLoading: choresLoading,
|
|
||||||
isError: choresError,
|
|
||||||
error: choresErrorDetails,
|
error: choresErrorDetails,
|
||||||
|
isError: choresError,
|
||||||
|
isLoading: choresLoading,
|
||||||
refetch: refetchChores,
|
refetch: refetchChores,
|
||||||
} = useChores(false)
|
} = useChores(false)
|
||||||
const {
|
const {
|
||||||
data: membersData,
|
data: membersData,
|
||||||
isLoading: membersLoading,
|
|
||||||
isError: membersError,
|
isError: membersError,
|
||||||
|
isLoading: membersLoading,
|
||||||
} = useCircleMembers()
|
} = useCircleMembers()
|
||||||
|
|
||||||
const [chores, setChores] = useState([])
|
const [chores, setChores] = useState([])
|
||||||
const [filteredChores, setFilteredChores] = useState([])
|
const [filteredChores, setFilteredChores] = useState([])
|
||||||
const [choreSections, setChoreSections] = useState([])
|
|
||||||
const [addTaskModalOpen, setAddTaskModalOpen] = useState(false)
|
const [addTaskModalOpen, setAddTaskModalOpen] = useState(false)
|
||||||
// 'voice' | 'scan' | null — set by the quick-capture widget deep links
|
// 'voice' | 'scan' | null — set by the quick-capture widget deep links
|
||||||
const [addTaskInitialMode, setAddTaskInitialMode] = useState(null)
|
const [addTaskInitialMode, setAddTaskInitialMode] = useState(null)
|
||||||
@@ -118,6 +115,9 @@ const MyChores = () => {
|
|||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const openSectionsInitializedRef = useRef(
|
||||||
|
localStorage.getItem('openChoreSections') !== null,
|
||||||
|
)
|
||||||
const [anchorEl, setAnchorEl] = useState(null)
|
const [anchorEl, setAnchorEl] = useState(null)
|
||||||
const [viewMode, setViewMode] = useState(
|
const [viewMode, setViewMode] = useState(
|
||||||
localStorage.getItem('choreCardViewMode') || 'default',
|
localStorage.getItem('choreCardViewMode') || 'default',
|
||||||
@@ -126,15 +126,15 @@ const MyChores = () => {
|
|||||||
const menuRef = useRef(null)
|
const menuRef = useRef(null)
|
||||||
const [confirmModelConfig, setConfirmModelConfig] = useState({})
|
const [confirmModelConfig, setConfirmModelConfig] = useState({})
|
||||||
|
|
||||||
const { selectedProject, projectsWithDefault, setSelectedProjectWithCache } =
|
const { projectsWithDefault, selectedProject, setSelectedProjectWithCache } =
|
||||||
useProjectFilter(projects, !projectsLoading)
|
useProjectFilter(projects, !projectsLoading)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
searchTerm,
|
nonProjectFilteredChores,
|
||||||
selectedChoreFilter,
|
|
||||||
projectFilteredChores,
|
projectFilteredChores,
|
||||||
searchFilteredChores,
|
searchFilteredChores,
|
||||||
nonProjectFilteredChores,
|
searchTerm,
|
||||||
|
selectedChoreFilter,
|
||||||
setSearchTerm,
|
setSearchTerm,
|
||||||
setSelectedChoreFilterWithCache,
|
setSelectedChoreFilterWithCache,
|
||||||
} = useChoreFilters({
|
} = useChoreFilters({
|
||||||
@@ -145,37 +145,37 @@ const MyChores = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isMultiSelectMode,
|
|
||||||
selectedChores,
|
|
||||||
toggleMultiSelectMode,
|
|
||||||
toggleChoreSelection,
|
|
||||||
enterMultiSelectWithChore,
|
|
||||||
selectAllVisibleChores,
|
|
||||||
clearSelection,
|
clearSelection,
|
||||||
|
enterMultiSelectWithChore,
|
||||||
getSelectedChoresData,
|
getSelectedChoresData,
|
||||||
|
isMultiSelectMode,
|
||||||
|
selectAllVisibleChores,
|
||||||
|
selectedChores,
|
||||||
|
toggleChoreSelection,
|
||||||
|
toggleMultiSelectMode,
|
||||||
} = useMultiSelect()
|
} = useMultiSelect()
|
||||||
|
|
||||||
const { activeModal, modalChore, modalData, openModal, closeModal } =
|
const { activeModal, closeModal, modalChore, modalData, openModal } =
|
||||||
useChoreModals()
|
useChoreModals()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
savedFilters,
|
|
||||||
activeFilter,
|
activeFilter,
|
||||||
activeFilterId,
|
activeFilterId,
|
||||||
|
applyCustomFilter,
|
||||||
|
applyTempFilter,
|
||||||
|
clearActiveFilter,
|
||||||
|
clearTempFilter,
|
||||||
|
createFilterFromCurrentState,
|
||||||
|
deleteFilter,
|
||||||
|
filteredChores: customFilteredChores,
|
||||||
|
hasFilterApplied,
|
||||||
|
hasProjectConditions,
|
||||||
|
pinFilter,
|
||||||
|
saveFilter,
|
||||||
|
savedFilters,
|
||||||
tempFilter,
|
tempFilter,
|
||||||
tempFilterMeta,
|
tempFilterMeta,
|
||||||
filteredChores: customFilteredChores,
|
|
||||||
applyCustomFilter,
|
|
||||||
clearActiveFilter,
|
|
||||||
applyTempFilter,
|
|
||||||
clearTempFilter,
|
|
||||||
saveFilter,
|
|
||||||
updateFilter,
|
updateFilter,
|
||||||
deleteFilter,
|
|
||||||
pinFilter,
|
|
||||||
createFilterFromCurrentState,
|
|
||||||
hasProjectConditions,
|
|
||||||
hasFilterApplied,
|
|
||||||
} = useCustomFilters(
|
} = useCustomFilters(
|
||||||
nonProjectFilteredChores,
|
nonProjectFilteredChores,
|
||||||
membersData?.res,
|
membersData?.res,
|
||||||
@@ -276,10 +276,10 @@ const MyChores = () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
filteredData: quickFilteredChores,
|
|
||||||
setFilter: setQuickFilter,
|
|
||||||
clearAll: clearQuickFilters,
|
clearAll: clearQuickFilters,
|
||||||
|
filteredData: quickFilteredChores,
|
||||||
hasActiveFilters: hasQuickFilters,
|
hasActiveFilters: hasQuickFilters,
|
||||||
|
setFilter: setQuickFilter,
|
||||||
} = useFilter(projectFilteredChores, quickFilterDefs)
|
} = useFilter(projectFilteredChores, quickFilterDefs)
|
||||||
|
|
||||||
const processedChores = useMemo(() => {
|
const processedChores = useMemo(() => {
|
||||||
@@ -301,7 +301,7 @@ const MyChores = () => {
|
|||||||
return sortedChores
|
return sortedChores
|
||||||
}, [choresData?.res, impersonatedUser])
|
}, [choresData?.res, impersonatedUser])
|
||||||
|
|
||||||
const processedSections = useMemo(() => {
|
const choreSections = useMemo(() => {
|
||||||
if (!chores.length || !userProfile?.id) {
|
if (!chores.length || !userProfile?.id) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@@ -385,26 +385,16 @@ const MyChores = () => {
|
|||||||
impersonatedUser?.userId,
|
impersonatedUser?.userId,
|
||||||
])
|
])
|
||||||
|
|
||||||
// Auto-update sections when processedSections changes
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Always update choreSections to match processedSections, even if empty
|
if (openSectionsInitializedRef.current || choreSections.length === 0) return
|
||||||
setChoreSections(processedSections)
|
|
||||||
|
|
||||||
// Auto-open sections if needed - only check localStorage once
|
openSectionsInitializedRef.current = true
|
||||||
if (processedSections.length > 0) {
|
const openSections = choreSections.reduce((acc, _section, index) => {
|
||||||
const storedSections = localStorage.getItem('openChoreSections')
|
acc[index] = true
|
||||||
if (storedSections === null) {
|
return acc
|
||||||
const openSections = processedSections.reduce(
|
}, {})
|
||||||
(acc, _section, index) => {
|
setOpenChoreSections(openSections)
|
||||||
acc[index] = true
|
}, [choreSections])
|
||||||
return acc
|
|
||||||
},
|
|
||||||
{},
|
|
||||||
)
|
|
||||||
setOpenChoreSections(openSections)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [processedSections])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener('mousedown', handleMenuOutsideClick)
|
document.addEventListener('mousedown', handleMenuOutsideClick)
|
||||||
@@ -567,17 +557,17 @@ const MyChores = () => {
|
|||||||
}, [tempFilterMeta?.id, searchParams])
|
}, [tempFilterMeta?.id, searchParams])
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleChoreAction,
|
|
||||||
handleChangeDueDate,
|
|
||||||
handleCompleteWithPastDate,
|
|
||||||
handleAssigneeChange,
|
handleAssigneeChange,
|
||||||
handleCompleteWithNote,
|
|
||||||
handleNudge,
|
|
||||||
handleBulkComplete,
|
|
||||||
handleBulkArchive,
|
handleBulkArchive,
|
||||||
|
handleBulkComplete,
|
||||||
handleBulkDelete,
|
handleBulkDelete,
|
||||||
handleBulkSkip,
|
|
||||||
handleBulkMoveToProject,
|
handleBulkMoveToProject,
|
||||||
|
handleBulkSkip,
|
||||||
|
handleChangeDueDate,
|
||||||
|
handleChoreAction,
|
||||||
|
handleCompleteWithNote,
|
||||||
|
handleCompleteWithPastDate,
|
||||||
|
handleNudge,
|
||||||
} = useChoreActions({
|
} = useChoreActions({
|
||||||
chores,
|
chores,
|
||||||
filteredChores,
|
filteredChores,
|
||||||
@@ -603,24 +593,14 @@ const MyChores = () => {
|
|||||||
return customFilteredChores
|
return customFilteredChores
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (searchTerm?.length > 0) {
|
||||||
|
return searchFilteredChores
|
||||||
|
}
|
||||||
|
|
||||||
const baseChores = hasQuickFilters
|
const baseChores = hasQuickFilters
|
||||||
? quickFilteredChores
|
? quickFilteredChores
|
||||||
: projectFilteredChores
|
: 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
|
return baseChores
|
||||||
}, [
|
}, [
|
||||||
activeFilterId,
|
activeFilterId,
|
||||||
@@ -629,6 +609,7 @@ const MyChores = () => {
|
|||||||
hasQuickFilters,
|
hasQuickFilters,
|
||||||
quickFilteredChores,
|
quickFilteredChores,
|
||||||
projectFilteredChores,
|
projectFilteredChores,
|
||||||
|
searchFilteredChores,
|
||||||
searchTerm,
|
searchTerm,
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -751,29 +732,10 @@ const MyChores = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchOptions = useMemo(
|
const clearTempFilterAndUrl = () => {
|
||||||
() => ({
|
clearTempFilter()
|
||||||
keys: ['name', 'raw_label'],
|
updateFilterUrl(null, null)
|
||||||
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 handleSearchChange = e => {
|
const handleSearchChange = e => {
|
||||||
clearActiveFilter()
|
clearActiveFilter()
|
||||||
@@ -790,22 +752,6 @@ const MyChores = () => {
|
|||||||
|
|
||||||
const term = search.toLowerCase()
|
const term = search.toLowerCase()
|
||||||
setSearchTerm(term)
|
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
|
// Clear selected calendar date when search changes
|
||||||
setSelectedCalendarDate(null)
|
setSelectedCalendarDate(null)
|
||||||
}
|
}
|
||||||
@@ -895,19 +841,15 @@ const MyChores = () => {
|
|||||||
// )
|
// )
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const getChoresForDate = useCallback(
|
const selectedDateChores = useMemo(() => {
|
||||||
date => {
|
if (!selectedCalendarDate) return []
|
||||||
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 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
|
// "Narrowed" means the user actively cut the list down (search, quick
|
||||||
// filters, a saved filter). Picking a project is not narrowing: an empty
|
// filters, a saved filter). Picking a project is not narrowing: an empty
|
||||||
@@ -929,7 +871,6 @@ const MyChores = () => {
|
|||||||
const appendChore = (prev, newChore) => {
|
const appendChore = (prev, newChore) => {
|
||||||
let newChores = [...prev, newChore]
|
let newChores = [...prev, newChore]
|
||||||
|
|
||||||
|
|
||||||
if (impersonatedUser) {
|
if (impersonatedUser) {
|
||||||
newChores = newChores.filter(
|
newChores = newChores.filter(
|
||||||
chore => chore.assignedTo === impersonatedUser.userId,
|
chore => chore.assignedTo === impersonatedUser.userId,
|
||||||
@@ -1010,7 +951,7 @@ const MyChores = () => {
|
|||||||
tempFilter={tempFilter}
|
tempFilter={tempFilter}
|
||||||
tempFilterMeta={tempFilterMeta}
|
tempFilterMeta={tempFilterMeta}
|
||||||
applyTempFilter={applyTempFilter}
|
applyTempFilter={applyTempFilter}
|
||||||
clearTempFilter={clearTempFilter}
|
clearTempFilter={clearTempFilterAndUrl}
|
||||||
saveFilter={saveFilter}
|
saveFilter={saveFilter}
|
||||||
updateFilter={updateFilter}
|
updateFilter={updateFilter}
|
||||||
onFilterSaved={name =>
|
onFilterSaved={name =>
|
||||||
@@ -1363,7 +1304,7 @@ const MyChores = () => {
|
|||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{getChoresForDate(selectedCalendarDate).length === 0 ? (
|
{selectedDateChores.length === 0 ? (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
size='sm'
|
size='sm'
|
||||||
icon={<EditCalendar />}
|
icon={<EditCalendar />}
|
||||||
@@ -1377,7 +1318,7 @@ const MyChores = () => {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ChoreListView
|
<ChoreListView
|
||||||
chores={getChoresForDate(selectedCalendarDate)}
|
chores={selectedDateChores}
|
||||||
viewMode={'compact'}
|
viewMode={'compact'}
|
||||||
membersData={membersData}
|
membersData={membersData}
|
||||||
userLabels={userLabels}
|
userLabels={userLabels}
|
||||||
@@ -1457,18 +1398,20 @@ const MyChores = () => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ChoreListView
|
{openChoreSections[index] && (
|
||||||
chores={section.content}
|
<ChoreListView
|
||||||
viewMode={viewMode}
|
chores={section.content}
|
||||||
membersData={membersData}
|
viewMode={viewMode}
|
||||||
userLabels={userLabels}
|
membersData={membersData}
|
||||||
handleLabelFiltering={handleLabelFiltering}
|
userLabels={userLabels}
|
||||||
handleChoreAction={handleChoreAction}
|
handleLabelFiltering={handleLabelFiltering}
|
||||||
isMultiSelectMode={isMultiSelectMode}
|
handleChoreAction={handleChoreAction}
|
||||||
selectedChores={selectedChores}
|
isMultiSelectMode={isMultiSelectMode}
|
||||||
toggleChoreSelection={toggleChoreSelection}
|
selectedChores={selectedChores}
|
||||||
onLongPressChore={enterMultiSelectWithChore}
|
toggleChoreSelection={toggleChoreSelection}
|
||||||
/>
|
onLongPressChore={enterMultiSelectWithChore}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
)
|
)
|
||||||
@@ -1574,7 +1517,7 @@ const MyChores = () => {
|
|||||||
allChores={chores}
|
allChores={chores}
|
||||||
performers={membersData?.res || []}
|
performers={membersData?.res || []}
|
||||||
applyTempFilter={applyTempFilter}
|
applyTempFilter={applyTempFilter}
|
||||||
clearTempFilter={clearTempFilter}
|
clearTempFilter={clearTempFilterAndUrl}
|
||||||
tempFilter={tempFilter}
|
tempFilter={tempFilter}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -22,23 +22,36 @@ export const useChoreFilters = ({
|
|||||||
return filterByProject(chores, selectedProject.id)
|
return filterByProject(chores, selectedProject.id)
|
||||||
}, [chores, selectedProject])
|
}, [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(() => {
|
const searchFilteredChores = useMemo(() => {
|
||||||
let baseChores = projectFilteredChores
|
let baseChores = projectFilteredChores
|
||||||
|
|
||||||
if (searchTerm?.length > 0) {
|
if (searchIndex) {
|
||||||
const searchableChores = baseChores.map(c => ({
|
return searchIndex
|
||||||
...c,
|
.search(searchTerm.toLowerCase())
|
||||||
raw_label: c.labelsV2?.map(l => l.name).join(' '),
|
.map(result => result.item)
|
||||||
}))
|
.filter(chore => projectChoreIds.has(chore.id))
|
||||||
|
|
||||||
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 (impersonatedUser) {
|
if (impersonatedUser) {
|
||||||
@@ -55,6 +68,8 @@ export const useChoreFilters = ({
|
|||||||
}, [
|
}, [
|
||||||
searchTerm,
|
searchTerm,
|
||||||
projectFilteredChores,
|
projectFilteredChores,
|
||||||
|
searchIndex,
|
||||||
|
projectChoreIds,
|
||||||
impersonatedUser,
|
impersonatedUser,
|
||||||
userProfile?.id,
|
userProfile?.id,
|
||||||
selectedChoreFilter,
|
selectedChoreFilter,
|
||||||
@@ -64,20 +79,10 @@ export const useChoreFilters = ({
|
|||||||
const nonProjectFilteredChores = useMemo(() => {
|
const nonProjectFilteredChores = useMemo(() => {
|
||||||
let baseChores = chores
|
let baseChores = chores
|
||||||
|
|
||||||
if (searchTerm?.length > 0) {
|
if (searchIndex) {
|
||||||
const searchableChores = baseChores.map(c => ({
|
return searchIndex
|
||||||
...c,
|
.search(searchTerm.toLowerCase())
|
||||||
raw_label: c.labelsV2?.map(l => l.name).join(' '),
|
.map(result => result.item)
|
||||||
}))
|
|
||||||
|
|
||||||
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 (impersonatedUser) {
|
if (impersonatedUser) {
|
||||||
@@ -94,6 +99,7 @@ export const useChoreFilters = ({
|
|||||||
}, [
|
}, [
|
||||||
searchTerm,
|
searchTerm,
|
||||||
chores,
|
chores,
|
||||||
|
searchIndex,
|
||||||
impersonatedUser,
|
impersonatedUser,
|
||||||
userProfile?.id,
|
userProfile?.id,
|
||||||
selectedChoreFilter,
|
selectedChoreFilter,
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
import { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
|
|
||||||
import { useUserProfile } from '../../../queries/UserQueries'
|
import { useUserProfile } from '../../../queries/UserQueries'
|
||||||
import {
|
import { applyFilter, validateFilter } from '../../../utils/FilterEngine'
|
||||||
applyFilter,
|
|
||||||
getFilterCount,
|
|
||||||
getFilterOverdueCount,
|
|
||||||
validateFilter,
|
|
||||||
} from '../../../utils/FilterEngine'
|
|
||||||
import {
|
import {
|
||||||
useCreateFilter,
|
useCreateFilter,
|
||||||
useDeleteFilter,
|
useDeleteFilter,
|
||||||
@@ -43,12 +39,17 @@ export const useCustomFilters = (chores, membersData, labels, projects) => {
|
|||||||
|
|
||||||
return filtersData.map(filter => {
|
return filtersData.map(filter => {
|
||||||
const validation = validateFilter(filter, context)
|
const validation = validateFilter(filter, context)
|
||||||
const count = validation.isValid
|
const matchingChores = validation.isValid
|
||||||
? getFilterCount(chores, filter, context)
|
? applyFilter(chores, filter, context)
|
||||||
: 0
|
: []
|
||||||
const overdueCount = validation.isValid
|
const count = matchingChores.length
|
||||||
? getFilterOverdueCount(chores, filter, context)
|
const now = new Date()
|
||||||
: 0
|
const overdueCount = matchingChores.reduce((total, chore) => {
|
||||||
|
if (!chore.nextDueDate || new Date(chore.nextDueDate) >= now) {
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
return total + 1
|
||||||
|
}, 0)
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
...filter,
|
...filter,
|
||||||
|
|||||||
Reference in New Issue
Block a user