diff --git a/src/components/NotificationTemplate.jsx b/src/components/NotificationTemplate.jsx index faad23d..0556e39 100644 --- a/src/components/NotificationTemplate.jsx +++ b/src/components/NotificationTemplate.jsx @@ -14,12 +14,9 @@ import Select from '@mui/joy/Select' import Typography from '@mui/joy/Typography' import { useCallback, useEffect, useState } from 'react' import { NOTIFICATION_TYPE, TASK_COLOR } from '../utils/Colors' +import { TIME_UNITS } from '../utils/DurationUtils' -const timeUnits = [ - { label: 'Mins', value: 'm' }, - { label: 'Hours', value: 'h' }, - { label: 'Days', value: 'd' }, -] +const timeUnits = TIME_UNITS const timingOptions = [ { label: 'Before', value: 'before' }, diff --git a/src/components/common/DurationInput.jsx b/src/components/common/DurationInput.jsx new file mode 100644 index 0000000..a1bac5f --- /dev/null +++ b/src/components/common/DurationInput.jsx @@ -0,0 +1,104 @@ +import { Add, Remove } from '@mui/icons-material' +import { Box, IconButton, Input, Option, Select } from '@mui/joy' +import { useEffect, useState } from 'react' +import { + secondsToValueAndUnit, + TIME_UNITS, + valueAndUnitToSeconds, +} from '../../utils/DurationUtils' + +/** + * A reusable duration picker: [−] number [+] unit-select + * + * Props: + * value – duration in seconds (positive integer) + * onChange – called with new duration in seconds + * size – Joy UI size ('sm' | 'md') + * minValue – minimum numeric value (default 1) + */ +const DurationInput = ({ value, onChange, size = 'md', minValue = 1 }) => { + const derived = + value != null && value >= 0 + ? secondsToValueAndUnit(value) + : { value: 1, unit: 'h' } + const [displayValue, setDisplayValue] = useState(derived.value) + const [unit, setUnit] = useState(derived.unit) + + useEffect(() => { + if (value != null && value >= 0) { + const { value: v, unit: u } = secondsToValueAndUnit(value) + setDisplayValue(v) + setUnit(u) + } + }, [value]) + + const emit = (v, u) => { + onChange(valueAndUnitToSeconds(v, u)) + } + + const handleDecrement = () => { + const next = Math.max(minValue, displayValue - 1) + setDisplayValue(next) + emit(next, unit) + } + + const handleIncrement = () => { + const next = displayValue + 1 + setDisplayValue(next) + emit(next, unit) + } + + return ( + + + + + + { + const v = Math.max(minValue, parseInt(e.target.value) || minValue) + setDisplayValue(v) + emit(v, unit) + }} + /> + + + + + + + + ) +} + +export default DurationInput diff --git a/src/queries/ChoreQueries.jsx b/src/queries/ChoreQueries.jsx index d8869d3..c17bf8b 100644 --- a/src/queries/ChoreQueries.jsx +++ b/src/queries/ChoreQueries.jsx @@ -25,6 +25,7 @@ import { localStore } from '../utils/LocalStore' export const useChores = includeArchive => { return useQuery({ queryKey: ['chores', includeArchive], + refetchOnWindowFocus: true, queryFn: async () => { const onlineChores = await GetChoresNew(includeArchive) @@ -272,6 +273,7 @@ export const useChoresHistory = (initialLimit, includeMembers) => { export const useChoreDetails = choreId => { return useQuery({ queryKey: ['choreDetails', choreId], + refetchOnWindowFocus: true, queryFn: async () => { var onlineChore = null diff --git a/src/utils/Chores.jsx b/src/utils/Chores.jsx index 74e5226..6813603 100644 --- a/src/utils/Chores.jsx +++ b/src/utils/Chores.jsx @@ -324,7 +324,7 @@ export const notInCompletionWindow = chore => { chore.completionWindow && chore.completionWindow > -1 && chore.nextDueDate && - moment().add(chore.completionWindow, 'hours') < moment(chore.nextDueDate) + moment() < moment(chore.nextDueDate).add(-chore.completionWindow, 'seconds') ) } export const ChoreFilters = userId => ({ diff --git a/src/utils/DurationUtils.js b/src/utils/DurationUtils.js new file mode 100644 index 0000000..1a4849f --- /dev/null +++ b/src/utils/DurationUtils.js @@ -0,0 +1,18 @@ +export const TIME_UNITS = [ + { label: 'Mins', value: 'm', seconds: 60 }, + { label: 'Hours', value: 'h', seconds: 3600 }, + { label: 'Days', value: 'd', seconds: 86400 }, +] + +export function secondsToValueAndUnit(totalSeconds) { + if (totalSeconds % 86400 === 0) + return { value: totalSeconds / 86400, unit: 'd' } + if (totalSeconds % 3600 === 0) + return { value: totalSeconds / 3600, unit: 'h' } + return { value: Math.round(totalSeconds / 60), unit: 'm' } +} + +export function valueAndUnitToSeconds(value, unit) { + const unitInfo = TIME_UNITS.find(u => u.value === unit) + return value * (unitInfo?.seconds ?? 1) +} diff --git a/src/views/ChoreEdit/ChoreEdit.jsx b/src/views/ChoreEdit/ChoreEdit.jsx index 98c413e..c1b4af3 100644 --- a/src/views/ChoreEdit/ChoreEdit.jsx +++ b/src/views/ChoreEdit/ChoreEdit.jsx @@ -19,12 +19,12 @@ import { RadioGroup, Select, Sheet, - Switch, Typography, } from '@mui/joy' 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' import { @@ -97,9 +97,7 @@ const ChoreEdit = () => { const [isPrivate, setIsPrivate] = useState(false) const [subTasks, setSubTasks] = useState(null) const [completionWindow, setCompletionWindow] = useState(-1) - const [deadline, setDeadline] = useState(null) const [deadlineOffset, setDeadlineOffset] = useState(-1) - const [deadlineUnit, setDeadlineUnit] = useState('hours') const [allUserThings, setAllUserThings] = useState([]) const [thingTrigger, setThingTrigger] = useState(null) const [isThingValid, setIsThingValid] = useState(false) @@ -352,6 +350,7 @@ const ChoreEdit = () => { completionWindow: // if completionWindow is -1 then set it to null or dueDate is null completionWindow < 0 || dueDate === null ? null : completionWindow, + deadlineOffset: deadlineOffset < 0 ? null : deadlineOffset, priority: priority, projectId: projectId === 'default' ? null : projectId, } @@ -477,6 +476,11 @@ const ChoreEdit = () => { ? data.res.completionWindow : -1, ) + setDeadlineOffset( + data.res.deadlineOffset && data.res.deadlineOffset > -1 + ? data.res.deadlineOffset + : -1, + ) setLabelsV2(data.res.labelsV2) @@ -1174,181 +1178,88 @@ const ChoreEdit = () => { {dueDate && ( - Completion Window - - { - event.preventDefault() - if (completionWindow != -1) { - setCompletionWindow(-1) - } else { - setCompletionWindow(1) - } - }} - color={completionWindow !== -1 ? 'success' : 'neutral'} - variant={completionWindow !== -1 ? 'solid' : 'outlined'} - sx={{ - mr: 2, - }} - /> -
- - Completion window (hours) - - - {"Set a time window that task can't be completed before"} - -
-
- {completionWindow != -1 && ( - - - Hours: - { - setCompletionWindow(parseInt(e.target.value)) - }} - /> - - - )} -
- )} - - {dueDate && ( - - Deadline + Task Window - When should this task be considered expired? + Define when this task can be completed and when it expires - {/* One-time tasks: Date picker */} - {['once', 'no_repeat'].includes(frequencyType) ? ( - - { - if (e.target.checked) { - // Set deadline to 24 hours after due date by default - const deadlineDate = moment(dueDate) - .add(1, 'day') - .format('YYYY-MM-DDTHH:mm:00') - setDeadline(deadlineDate) - } else { - setDeadline(null) - } - }} - checked={deadline !== null} - overlay - label='Set a deadline for this task' + {/* Available From (Completion Window) */} + + { + if (e.target.checked) { + setCompletionWindow(3600) // default 1 hour in seconds + } else { + setCompletionWindow(-1) + } + }} + overlay + label='Set earliest completion time' + /> + + Task becomes available to complete X hours before the due date + + + + {completionWindow !== -1 && ( + + - - Task will be considered expired after this date - - - ) : ( - /* Recurring tasks: Offset input */ - - { - if (e.target.checked) { - setDeadlineOffset(24) // Default to 24 hours - } else { - setDeadlineOffset(-1) - } - }} - checked={deadlineOffset !== -1} - overlay - label='Set a deadline for this task' - /> - - Task will be considered expired after the specified time from - due date - - + before due date + )} - {/* Date picker for one-time tasks */} - {deadline && ['once', 'no_repeat'].includes(frequencyType) && ( - - - - Deadline Date: - - setDeadline(e.target.value)} - slotProps={{ - input: { - min: dueDate, // Deadline cannot be before due date - }, - }} - /> - - - )} + {/* Expires After (Deadline) */} + + { + if (e.target.checked) { + setDeadlineOffset(86400) // default 1 day in seconds + } else { + setDeadlineOffset(-1) + } + }} + overlay + label='Set a deadline' + /> + + Task will be considered expired after the due date + + - {/* Offset input for recurring tasks */} - {deadlineOffset !== -1 && - !['once', 'no_repeat'].includes(frequencyType) && ( - - - - - Time after due date: - - { - setDeadlineOffset(parseInt(e.target.value) || 1) - }} - /> - - - - Unit: - - - - - - )} + {deadlineOffset !== -1 && ( + + + after due date + + )} )} diff --git a/src/views/ChoreEdit/ChoreView.jsx b/src/views/ChoreEdit/ChoreView.jsx index 51f8748..d2f4993 100644 --- a/src/views/ChoreEdit/ChoreView.jsx +++ b/src/views/ChoreEdit/ChoreView.jsx @@ -149,10 +149,10 @@ const ChoreView = () => { 'N/A' }`, subtext: ` Last: ${ - chore.lastCompletedDate + chore.lastCompletedDate && chore.lastCompletedBy ? performers.find(p => p.userId === chore.lastCompletedBy) ?.displayName - : '--' + : 'N/A' }`, }, { @@ -167,6 +167,11 @@ const ChoreView = () => { ? moment(chore.lastCompletedDate).fromNow() : 'N/A' }`, + + subtext2: + chore.deadlineOffset > 0 && chore.nextDueDate + ? `Deadline: ${moment(chore.nextDueDate).add(chore.deadlineOffset, 'seconds').fromNow()}` + : null, }, { size: 6, @@ -518,7 +523,10 @@ const ChoreView = () => { handleAction={action => { if (action === 'pause') { handleChorePause() - } else if (action === 'resume') { + } else if ( + action === 'resume' && + !notInCompletionWindow(chore) + ) { handleChoreStart() } }} @@ -576,6 +584,14 @@ const ChoreView = () => { > {card.subtext} + {card.subtext2 && ( + + {card.subtext2} + + )} @@ -1034,8 +1050,9 @@ const ChoreView = () => { }) }} disabled={ - chore.lastCompletedDate !== null && - chore.frequencyType === 'once' + notInCompletionWindow(chore) || + (chore.lastCompletedDate !== null && + chore.frequencyType === 'once') } startDecorator={} sx={{ @@ -1047,12 +1064,25 @@ const ChoreView = () => { )} + {notInCompletionWindow(chore) && ( + + Available to complete starting{' '} + {moment(chore.nextDueDate) + .subtract(chore.completionWindow, 'seconds') + .format('MM/DD/YYYY hh:mm A')} + + )} {/* Timer Button - Show split button when timer is active, regular button otherwise */} {[ChoreStatus.ACTIVE, ChoreStatus.PAUSED].includes(chore.status) ? ( { @@ -1078,8 +1108,9 @@ const ChoreView = () => { variant='soft' color='success' disabled={ - chore.lastCompletedDate !== null && - chore.frequencyType === 'once' + notInCompletionWindow(chore) || + (chore.lastCompletedDate !== null && + chore.frequencyType === 'once') } startDecorator={} sx={{ diff --git a/src/views/Chores/ActivitesCard.jsx b/src/views/Chores/ActivitesCard.jsx index 55c7fcd..6f0f0e9 100644 --- a/src/views/Chores/ActivitesCard.jsx +++ b/src/views/Chores/ActivitesCard.jsx @@ -17,6 +17,7 @@ import { Chip, Divider, IconButton, + Link, List, ListItem, ListItemContent, @@ -25,16 +26,31 @@ import { Typography, } from '@mui/joy' import moment from 'moment' +import { useState } from 'react' import { useChores, useChoresHistory } from '../../queries/ChoreQueries' import { useCircleMembers } from '../../queries/UserQueries' import { resolvePhotoURL } from '../../utils/Helpers' +import NoteViewerModal from '../Modals/Inputs/NoteViewerModal' -const ActivityItem = ({ activity, members }) => { +const ActivityItem = ({ activity, members, onViewNote }) => { // Find the member who completed the activity const completedByMember = members?.find( member => member.userId === activity.completedBy, ) + // Strip HTML tags from notes for plain text display + const stripHtmlTags = html => { + if (!html) return '' + const div = document.createElement('div') + div.innerHTML = html + return div.textContent || div.innerText || '' + } + + const plainTextNotes = activity.notes ? stripHtmlTags(activity.notes) : '' + + // Calculate if notes should be truncated (more than 2 lines in the UI, which is roughly 100 characters) + const shouldTruncate = plainTextNotes && plainTextNotes.length > 80 + const getTimeDisplay = dateToDisplay => { const now = moment() const completed = moment(dateToDisplay) @@ -177,7 +193,7 @@ const ActivityItem = ({ activity, members }) => { > {/* Notes */} - {activity.notes && ( + {plainTextNotes && ( { color: 'text.secondary', }} > - - {activity.notes} + + + + {plainTextNotes} + + {shouldTruncate && ( + onViewNote(activity.notes)} + sx={{ + cursor: 'pointer', + mt: 0.25, + display: 'inline-block', + }} + > + Show more + + )} + )} @@ -217,6 +261,8 @@ const groupActivitiesByDate = activities => { } const ActivitiesCard = ({ title = 'Recent Activities' }) => { + const [noteViewerConfig, setNoteViewerConfig] = useState({ isOpen: false }) + // Use hooks to fetch data const { data: choresData, @@ -430,6 +476,14 @@ const ActivitiesCard = ({ title = 'Recent Activities' }) => { key={activity.id} activity={activity} members={members} + onViewNote={notes => { + setNoteViewerConfig({ + isOpen: true, + title: `Note - ${activity.choreName}`, + content: notes, + onClose: () => setNoteViewerConfig({ isOpen: false }), + }) + }} /> ))} @@ -437,6 +491,8 @@ const ActivitiesCard = ({ title = 'Recent Activities' }) => { ) })} + + ) } diff --git a/src/views/Chores/MyChores.jsx b/src/views/Chores/MyChores.jsx index d800c93..2f219a5 100644 --- a/src/views/Chores/MyChores.jsx +++ b/src/views/Chores/MyChores.jsx @@ -62,6 +62,7 @@ import ChoreListView from './ChoreListView.jsx' import ChoreModals from './components/ChoreModals' import FilterSection from './components/FilterSection' import MultiSelectToolbar from './components/MultiSelectToolbar' +import MyChoreHeader from './components/MyChoreHeader' import SearchBar from './components/SearchBar' import { useChoreActions } from './hooks/useChoreActions' import { useChoreFilters } from './hooks/useChoreFilters' @@ -76,6 +77,7 @@ import { } from './LocalNotificationScheduler' import NotificationAccessSnackbar from './NotificationAccessSnackbar' import Sidepanel from './Sidepanel' +import { INSIGHT_FILTER_DEFS } from './SmartInsightsCard' import SortAndGrouping from './SortAndGrouping' const MyChores = () => { @@ -167,6 +169,7 @@ const MyChores = () => { activeFilter, activeFilterId, tempFilter, + tempFilterMeta, filteredChores: customFilteredChores, applyCustomFilter, clearActiveFilter, @@ -376,6 +379,18 @@ const MyChores = () => { const oldFilter = searchParams.get('filter') + // Restore smart insight temp filter from URL (e.g. on page reload) + // Insight IDs are strings (e.g. 'overdue'), saved filter IDs are numeric + if ( + filterId && + INSIGHT_FILTER_DEFS[filterId] && + tempFilterMeta?.id !== filterId + ) { + const def = INSIGHT_FILTER_DEFS[filterId] + applyTempFilter(def.filter, { id: filterId, name: def.name }) + return + } + // If filterId is no longer in URL but filter is still active in state, clear it if (!filterId && !oldFilter && activeFilterId) { clearActiveFilter() @@ -422,6 +437,7 @@ const MyChores = () => { activeFilterId, savedFilters, applyCustomFilter, + applyTempFilter, clearActiveFilter, selectedProject, projectFilteredChores, @@ -431,6 +447,35 @@ const MyChores = () => { setSelectedCalendarDate, ]) + // Sync tempFilterMeta (smart insight) → URL using filterId param + // Insight IDs are strings (e.g. 'overdue') so they don't conflict with numeric saved filter IDs + useEffect(() => { + const insightId = tempFilterMeta?.id + const params = new URLSearchParams(searchParams) + const currentFilterId = params.get('filterId') + + if (insightId && currentFilterId !== insightId) { + params.delete('filter_id') + params.delete('filter') + params.set('filterId', insightId) + Navigate( + { pathname: '/chores', search: params.toString() }, + { replace: true }, + ) + } else if ( + !insightId && + currentFilterId && + INSIGHT_FILTER_DEFS[currentFilterId] + ) { + // Only clear filterId if it was set by an insight (not a numeric saved filter) + params.delete('filterId') + Navigate( + { pathname: '/chores', search: params.toString() }, + { replace: true }, + ) + } + }, [tempFilterMeta?.id, searchParams]) + const { handleChoreAction, handleChangeDueDate, @@ -830,6 +875,13 @@ const MyChores = () => { }} > + { + if ( + !activeFilterId && + !tempFilter && + (!selectedProject || selectedProject.id === 'default') + ) + return null + + const renderIcon = () => { + if (tempFilter) { + return tempFilterMeta?.icon ? ( + + {tempFilterMeta.icon} + + ) : ( + + ) + } + if (activeFilterId) { + return + } + if (selectedProject) { + const iconValue = selectedProject.icon || 'FolderOpen' + const IconComponent = getIconComponent(iconValue) + return ( + + ) + } + return null + } + + const name = tempFilter + ? tempFilterMeta?.name || 'Smart Filter' + : activeFilter?.name || selectedProject?.name + + const description = tempFilter + ? tempFilterMeta?.description + : activeFilter?.description || selectedProject?.description + + return ( + + {renderIcon()} + + + {name} + + {description && ( + + {description} + + )} + + + ) +} + +export default MyChoreHeader diff --git a/src/views/Chores/hooks/useCustomFilters.js b/src/views/Chores/hooks/useCustomFilters.js index c773f26..c8c6c32 100644 --- a/src/views/Chores/hooks/useCustomFilters.js +++ b/src/views/Chores/hooks/useCustomFilters.js @@ -26,6 +26,7 @@ export const useCustomFilters = (chores, membersData, labels, projects) => { const [activeFilterId, setActiveFilterId] = useState(null) const [tempFilter, setTempFilter] = useState(null) + const [tempFilterMeta, setTempFilterMeta] = useState(null) const context = useMemo( () => ({ @@ -95,15 +96,18 @@ export const useCustomFilters = (chores, membersData, labels, projects) => { const clearActiveFilter = useCallback(() => { setActiveFilterId(null) setTempFilter(null) + setTempFilterMeta(null) }, []) - const applyTempFilter = useCallback(filter => { + const applyTempFilter = useCallback((filter, meta = null) => { setTempFilter(filter) + setTempFilterMeta(meta) setActiveFilterId(null) }, []) const clearTempFilter = useCallback(() => { setTempFilter(null) + setTempFilterMeta(null) }, []) const saveFilter = useCallback( @@ -256,6 +260,7 @@ export const useCustomFilters = (chores, membersData, labels, projects) => { activeFilter, activeFilterId, tempFilter, + tempFilterMeta, filteredChores, applyCustomFilter, clearActiveFilter, diff --git a/src/views/History/HistoryCard.jsx b/src/views/History/HistoryCard.jsx index a4dbe28..811179a 100644 --- a/src/views/History/HistoryCard.jsx +++ b/src/views/History/HistoryCard.jsx @@ -7,6 +7,7 @@ import { MoreVert, Person, Redo, + RunningWithErrors, ThumbDown, Timelapse, Toll, @@ -16,9 +17,10 @@ import moment from 'moment' import { TASK_COLOR } from '../../utils/Colors.jsx' const getCompletedChip = historyEntry => { - if (historyEntry.status === 0) { + if (historyEntry.status === 0 || historyEntry.status === 5) { return null } + if (!historyEntry.dueDate) { return null // , color: 'warning' }, // Skipped 3: { icon: , color: 'neutral' }, // Pending Approval 4: { icon: , color: 'danger' }, // Rejected + 5: { icon: , color: 'danger' }, // Missed } const config = statusMap[historyEntry.status] || statusMap[1] @@ -187,14 +190,17 @@ const HistoryCard = ({ ? 'Pending Approval' : historyEntry.status === 4 ? 'Rejected' - : 'Completed'} + : historyEntry.status === 5 + ? 'Missed' + : 'Completed'} - - }> - {moment( - historyEntry.performedAt || historyEntry.updatedAt, - ).format('MMM DD, h:mm A')} - + {historyEntry.performedAt && ( + }> + {moment( + historyEntry.performedAt || historyEntry.updatedAt, + ).format('MMM DD, h:mm A')} + + )} {getCompletedChip(historyEntry)} @@ -231,16 +237,21 @@ const HistoryCard = ({ mt: 0.5, }} > - - } - > - {performer?.displayName || 'Unknown'} - + {performer && ( + + } + > + {performer?.displayName || 'Unknown'} + + )} {historyEntry.completedBy !== historyEntry.assignedTo && assignedTo && ( @@ -260,7 +271,11 @@ const HistoryCard = ({ variant='plain' color='neutral' startDecorator={} - sx={{ maxWidth: '120px', overflow: 'hidden', cursor: 'pointer' }} + sx={{ + maxWidth: '120px', + overflow: 'hidden', + cursor: 'pointer', + }} onClick={e => { e.stopPropagation() onViewNote?.(historyEntry.notes) diff --git a/src/views/components/AddTaskModal.jsx b/src/views/components/AddTaskModal.jsx index 2442d39..4e83499 100644 --- a/src/views/components/AddTaskModal.jsx +++ b/src/views/components/AddTaskModal.jsx @@ -20,6 +20,7 @@ import { } from './CustomParsers' import SmartTaskTitleInput from './SmartTaskTitleInput' +import DurationInput from '../../components/common/DurationInput' import KeyboardShortcutHint from '../../components/common/KeyboardShortcutHint' import NotificationTemplate from '../../components/NotificationTemplate' import LearnMoreButton from './LearnMore' @@ -90,6 +91,8 @@ const TaskInput = ({ autoFocus, onChoreUpdate, isModalOpen, onClose }) => { const [hasDescription, setHasDescription] = useState(false) const [hasSubTasks, setHasSubTasks] = useState(false) const [hasNotifications, setHasNotifications] = useState(false) + const [hasDeadline, setHasDeadline] = useState(false) + const [deadlineOffset, setDeadlineOffset] = useState(-1) const [showKeyboardShortcuts, setShowKeyboardShortcuts] = useState(false) const [projectId, setProjectId] = useState(getInitialProject()) @@ -491,6 +494,8 @@ const TaskInput = ({ autoFocus, onChoreUpdate, isModalOpen, onClose }) => { setLabelsV2([]) setAssignees([]) setProjectId(getInitialProject()) + setHasDeadline(false) + setDeadlineOffset(-1) } const createChore = () => { @@ -529,6 +534,7 @@ const TaskInput = ({ autoFocus, onChoreUpdate, isModalOpen, onClose }) => { labelsV2: labelsV2, priority: priority ? Number(priority) : 0, points: points > -1 ? points : null, + deadlineOffset: deadlineOffset < 0 ? null : deadlineOffset, status: 0, frequencyType: 'once', frequencyMetadata: {}, @@ -798,6 +804,19 @@ const TaskInput = ({ autoFocus, onChoreUpdate, isModalOpen, onClose }) => { Edit Notifications )} + {!hasDeadline && dueDate && ( + + )} {hasDescription && ( @@ -959,6 +978,27 @@ const TaskInput = ({ autoFocus, onChoreUpdate, isModalOpen, onClose }) => { )} */} + {hasDeadline && dueDate && ( + + Deadline + + + after due date + + + )} {hasNotifications && dueDate && ( { } if ( ['/chores', '/'].includes(location.pathname) && - !searchParams.get('filter') + !searchParams.get('filterId') ) { return menuRounded }