fix: update assignee filter logic and improve display options in MyChores and ChoreToolbarPrototype

This commit is contained in:
Mo Tarbin
2026-08-16 02:07:44 -04:00
parent 79d92ca8c9
commit 325d5e6df7
2 changed files with 70 additions and 21 deletions

View File

@@ -74,6 +74,16 @@ import NotificationAccessSnackbar from './NotificationAccessSnackbar'
import Sidepanel from './Sidepanel' import Sidepanel from './Sidepanel'
import { INSIGHT_FILTER_DEFS } from './SmartInsightsCard' import { INSIGHT_FILTER_DEFS } from './SmartInsightsCard'
// Mirrors the assignee options in the toolbar, phrased to drop into a
// sentence ("none of them are assigned to you").
const ASSIGNEE_FILTER_LABELS = {
assigned_to_me: 'assigned to you',
available_for_me: 'available for you to pick up',
assigned_to_others: 'assigned to someone else',
assigned_to_me_tasks: 'assigned to you',
created_by_me: 'created by you',
}
const MyChores = () => { const MyChores = () => {
const { data: userProfile, isLoading: isUserProfileLoading } = const { data: userProfile, isLoading: isUserProfileLoading } =
useUserProfile() useUserProfile()
@@ -884,20 +894,51 @@ const MyChores = () => {
}) })
}, [getFilteredChores, selectedCalendarDate]) }, [getFilteredChores, selectedCalendarDate])
// The assignee filter ("Mine", "Available to me", ...) is applied inside
// ChoresGrouper, not in projectFilteredChores, so it can hide every task
// while the unfiltered list still looks full. It narrows like any other.
const assigneeFilterLabel = ASSIGNEE_FILTER_LABELS[selectedChoreFilter]
const hasAssigneeFilter = Boolean(
selectedChoreFilter && selectedChoreFilter !== 'anyone',
)
// "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, the assignee filter). Picking a project is not
// project is an empty place, not a filtered-away result. // narrowing: an empty project is an empty place, not a filtered-away result.
const isNarrowed = Boolean( const isNarrowed = Boolean(
searchTerm?.length > 0 || hasQuickFilters || activeFilterId, searchTerm?.length > 0 ||
hasQuickFilters ||
activeFilterId ||
hasAssigneeFilter,
) )
const isCustomProjectSelected = Boolean( const isCustomProjectSelected = Boolean(
selectedProject && selectedProject.id !== 'default', selectedProject && selectedProject.id !== 'default',
) )
// Worth its own wording: the assignee filter is the one narrowing that is
// easy to forget you left on, so name it rather than saying "filters".
const isAssigneeOnlyNarrowing = Boolean(
assigneeFilterLabel &&
!searchTerm?.length &&
!hasQuickFilters &&
!activeFilterId,
)
// What the list actually renders. Sections are the source of truth outside
// of search, since they are the only place the assignee filter is applied.
const visibleChoreCount = useMemo(
() =>
choreSections.reduce(
(total, section) => total + (section.content?.length || 0),
0,
),
[choreSections],
)
const clearNarrowing = () => { const clearNarrowing = () => {
clearQuickFilters() clearQuickFilters()
setSearchTerm('') setSearchTerm('')
clearActiveFilter() clearActiveFilter()
setSelectedChoreFilterWithCache('anyone')
updateFilterUrl(null, null) updateFilterUrl(null, null)
} }
@@ -1106,10 +1147,13 @@ const MyChores = () => {
{/* Empty state. Three different situations, three different messages: {/* Empty state. Three different situations, three different messages:
nothing created yet, nothing left after narrowing, or an empty nothing created yet, nothing left after narrowing, or an empty
project. Only the middle one is about filters. */} project. Only the middle one is about filters.
{(isNarrowed The trigger is what the list actually renders, not the pre-filter
count, so a view emptied purely by the assignee filter still
explains itself instead of showing a blank page. */}
{(searchTerm?.length > 0
? getFilteredChores.length === 0 ? getFilteredChores.length === 0
: projectFilteredChores.length === 0) && : visibleChoreCount === 0) &&
// only if not in calendar view: // only if not in calendar view:
viewMode !== 'calendar' && viewMode !== 'calendar' &&
(chores.length === 0 ? ( (chores.length === 0 ? (
@@ -1129,7 +1173,10 @@ const MyChores = () => {
onClick: () => Navigate('/chores/create'), onClick: () => Navigate('/chores/create'),
}} }}
/> />
) : isNarrowed ? ( ) : isNarrowed &&
(searchTerm?.length > 0 ||
activeFilterId ||
projectFilteredChores.length > 0) ? (
<EmptyState <EmptyState
variant='no-results' variant='no-results'
fullHeight fullHeight
@@ -1138,11 +1185,17 @@ const MyChores = () => {
description={ description={
searchTerm?.length > 0 searchTerm?.length > 0
? `Nothing matches "${searchTerm}". Try a different search, or clear what is narrowing the list.` ? `Nothing matches "${searchTerm}". Try a different search, or clear what is narrowing the list.`
: 'You have tasks, but none of them fit the filters that are currently on.' : isAssigneeOnlyNarrowing
? `There are tasks here, but none of them are ${assigneeFilterLabel}. Switch back to everyone to see the rest.`
: 'You have tasks, but none of them fit the filters that are currently on.'
} }
primaryAction={{ primaryAction={{
label: label:
searchTerm?.length > 0 ? 'Clear search' : 'Clear filters', searchTerm?.length > 0
? 'Clear search'
: isAssigneeOnlyNarrowing
? "Show everyone's tasks"
: 'Clear filters',
onClick: clearNarrowing, onClick: clearNarrowing,
}} }}
/> />

View File

@@ -23,13 +23,13 @@ import {
Check, Check,
CheckBox, CheckBox,
CheckBoxOutlineBlank, CheckBoxOutlineBlank,
DisplaySettings,
FilterList, FilterList,
Save, Save,
Sort, Sort,
Tune, Tune,
ViewAgenda, ViewAgenda,
ViewComfy, ViewComfy,
ViewModule,
} from '@mui/icons-material' } from '@mui/icons-material'
import { import {
Badge, Badge,
@@ -594,23 +594,19 @@ const ChoreToolbar = ({
/> />
)} )}
{/* Display button — View + Group combined */} {/* Display button — View + Group combined.
Icon stays fixed: mirroring viewMode made this read as a toggle
showing the current view rather than a button that opens a sheet. */}
<IconButton <IconButton
variant='outlined' variant='outlined'
color='neutral' color='neutral'
size='sm' size='sm'
sx={{ height: 32, width: 32, borderRadius: '50%' }} sx={{ height: 32, width: 32, borderRadius: '50%' }}
onClick={() => setDisplaySheetOpen(true)} onClick={() => setDisplaySheetOpen(true)}
aria-label='View and group options' aria-label='Display options'
title='View & Group' title='Display'
> >
{viewMode === 'calendar' ? ( <DisplaySettings />
<CalendarMonth />
) : viewMode === 'compact' ? (
<ViewModule />
) : (
<ViewAgenda />
)}
</IconButton> </IconButton>
{/* Multiselect */} {/* Multiselect */}
@@ -884,7 +880,7 @@ const ChoreToolbar = ({
onClose={() => setDisplaySheetOpen(false)} onClose={() => setDisplaySheetOpen(false)}
title={ title={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<ViewAgenda sx={{ fontSize: 20 }} /> <DisplaySettings sx={{ fontSize: 20 }} />
Display Display
</Box> </Box>
} }