From a16309c69abc727cb6a24bd90c682c48cf311d6f Mon Sep 17 00:00:00 2001 From: Mo Tarbin Date: Wed, 5 Mar 2025 19:43:32 -0500 Subject: [PATCH] - Support nest sub tasks - Support filters in ChoreGrouper - completion window only available if due date selected - Add SortAndGrouping Component : support Filter by Assignee - update Notification Switch to align left of the text - Support caching filters --- src/utils/Chores.jsx | 15 +- src/views/ChoreEdit/ChoreEdit.jsx | 120 +++--- src/views/Chores/MyChores.jsx | 87 ++-- src/views/Chores/Sidepanel.jsx | 2 +- src/views/Chores/SortAndGrouping.jsx | 194 +++++++++ src/views/Settings/NotificationSetting.jsx | 18 +- src/views/User/UserActivities.jsx | 4 +- src/views/components/AddTaskModal.jsx | 12 +- src/views/components/SubTask.jsx | 451 +++++++++++++++------ 9 files changed, 679 insertions(+), 224 deletions(-) create mode 100644 src/views/Chores/SortAndGrouping.jsx diff --git a/src/utils/Chores.jsx b/src/utils/Chores.jsx index 4366bfa..729a9ac 100644 --- a/src/utils/Chores.jsx +++ b/src/utils/Chores.jsx @@ -3,7 +3,11 @@ import { TASK_COLOR } from './Colors.jsx' const priorityOrder = [1, 2, 3, 4, 0] -export const ChoresGrouper = (groupBy, chores) => { +export const ChoresGrouper = (groupBy, chores, filter) => { + if (filter) { + chores = chores.filter(chore => filter(chore)) + } + // sort by priority then due date: chores.sort(ChoreSorter) var groups = [] @@ -172,3 +176,12 @@ export const notInCompletionWindow = chore => { moment().add(chore.completionWindow, 'hours') < moment(chore.nextDueDate) ) } +export const ChoreFilters = userProfile => ({ + anyone: () => true, + assigned_to_me: chore => { + return chore.assignedTo && chore.assignedTo === userProfile.id + }, + assigned_to_others: chore => { + return chore.assignedTo && chore.assignedTo !== userProfile.id + }, +}) diff --git a/src/views/ChoreEdit/ChoreEdit.jsx b/src/views/ChoreEdit/ChoreEdit.jsx index ddcb853..8b93b44 100644 --- a/src/views/ChoreEdit/ChoreEdit.jsx +++ b/src/views/ChoreEdit/ChoreEdit.jsx @@ -208,7 +208,9 @@ const ChoreEdit = () => { notificationMetadata: notificationMetadata, thingTrigger: thingTrigger, points: points < 0 ? null : points, - completionWindow: completionWindow < 0 ? null : completionWindow, + completionWindow: + // if completionWindow is -1 then set it to null or dueDate is null + completionWindow < 0 || dueDate === null ? null : completionWindow, priority: priority, } let SaveFunction = CreateChore @@ -596,62 +598,65 @@ const ChoreEdit = () => { {errors.dueDate} )} - - - { - event.preventDefault() - if (completionWindow != -1) { - setCompletionWindow(-1) - } else { - setCompletionWindow(1) - } - }} - color={completionWindow !== -1 ? 'success' : 'neutral'} - variant={completionWindow !== -1 ? 'solid' : 'outlined'} - // endDecorator={points !== -1 ? 'On' : 'Off'} - sx={{ - mr: 2, - }} - /> -
- {/* Completion window (hours) */} - Completion window (hours) - - - {"Set a time window that task can't be completed before"} - -
-
- {completionWindow != -1 && ( - - - Hours: - - + + { + event.preventDefault() + if (completionWindow != -1) { + setCompletionWindow(-1) + } else { + setCompletionWindow(1) + } }} - placeholder='Hours' - onChange={e => { - setCompletionWindow(parseInt(e.target.value)) + color={completionWindow !== -1 ? 'success' : 'neutral'} + variant={completionWindow !== -1 ? 'solid' : 'outlined'} + // endDecorator={points !== -1 ? 'On' : 'Off'} + sx={{ + mr: 2, }} /> - - +
+ {/* Completion window (hours) */} + Completion window (hours) + + + {"Set a time window that task can't be completed before"} + +
+ + {completionWindow != -1 && ( + + + Hours: + + { + setCompletionWindow(parseInt(e.target.value)) + }} + /> + + + )} + )} {!['once', 'no_repeat'].includes(frequencyType) && ( @@ -975,7 +980,12 @@ const ChoreEdit = () => { p: 1, }} > - + )} diff --git a/src/views/Chores/MyChores.jsx b/src/views/Chores/MyChores.jsx index 2a1cf36..94c2fd7 100644 --- a/src/views/Chores/MyChores.jsx +++ b/src/views/Chores/MyChores.jsx @@ -44,7 +44,7 @@ import { useLabels } from '../Labels/LabelQueries' import ChoreCard from './ChoreCard' import IconButtonWithMenu from './IconButtonWithMenu' -import { ChoresGrouper, ChoreSorter } from '../../utils/Chores' +import { ChoreFilters, ChoresGrouper, ChoreSorter } from '../../utils/Chores' import TaskInput from '../components/AddTaskModal' import { canScheduleNotification, @@ -52,6 +52,7 @@ import { } from './LocalNotificationScheduler' import NotificationAccessSnackbar from './NotificationAccessSnackbar' import Sidepanel from './Sidepanel' +import SortAndGrouping from './SortAndGrouping' const MyChores = () => { const { userProfile, setUserProfile } = useContext(UserContext) @@ -60,7 +61,7 @@ const MyChores = () => { const [chores, setChores] = useState([]) const [archivedChores, setArchivedChores] = useState(null) const [filteredChores, setFilteredChores] = useState([]) - const [selectedFilter, setSelectedFilter] = useState('All') + const [searchFilter, setSearchFilter] = useState('All') const [choreSections, setChoreSections] = useState([]) const [activeTextField, setActiveTextField] = useState('task') const [taskInputFocus, setTaskInputFocus] = useState(0) @@ -72,6 +73,9 @@ const MyChores = () => { const [openChoreSections, setOpenChoreSections] = useState( JSON.parse(localStorage.getItem('openChoreSections')) || {}, ) + const [selectedChoreFilter, setSelectedChoreFilter] = useState( + JSON.parse(localStorage.getItem('selectedChoreFilter')) || 'anyone', + ) const [searchTerm, setSearchTerm] = useState('') const [performers, setPerformers] = useState([]) const [anchorEl, setAnchorEl] = useState(null) @@ -172,6 +176,10 @@ const MyChores = () => { setOpenChoreSections(value) localStorage.setItem('openChoreSections', JSON.stringify(value)) } + const setSelectedChoreFilterWithCache = value => { + setSelectedChoreFilter(value) + localStorage.setItem('selectedChoreFilter', JSON.stringify(value)) + } const updateChores = newChore => { const newChores = chores @@ -179,7 +187,7 @@ const MyChores = () => { setChores(newChores) setFilteredChores(newChores) setChoreSections(ChoresGrouper(selectedChoreSection, newChores)) - setSelectedFilter('All') + setSearchFilter('All') } const handleMenuOutsideClick = event => { if ( @@ -208,14 +216,14 @@ const MyChores = () => { ), ) setFilteredChores(labelFiltered) - setSelectedFilter('Label: ' + label.name) + setSearchFilter('Label: ' + label.name) } else if (chipClicked.priority) { const priority = chipClicked.priority const priorityFiltered = chores.filter( chore => chore.priority === priority, ) setFilteredChores(priorityFiltered) - setSelectedFilter('Priority: ' + priority) + setSearchFilter('Priority: ' + priority) } } @@ -305,8 +313,8 @@ const MyChores = () => { ) const handleSearchChange = e => { - if (selectedFilter !== 'All') { - setSelectedFilter('All') + if (searchFilter !== 'All') { + setSearchFilter('All') } const search = e.target.value if (search === '') { @@ -418,17 +426,28 @@ const MyChores = () => { )} - - } - options={[ - { name: 'Due Date', value: 'due_date' }, - { name: 'Priority', value: 'priority' }, - { name: 'Labels', value: 'labels' }, - ]} selectedItem={selectedChoreSection} + selectedFilter={selectedChoreFilter} + setFilter={filter => { + setSelectedChoreFilterWithCache(filter) + const section = ChoresGrouper( + selectedChoreSection, + chores, + ChoreFilters(userProfile)[filter], + ) + setChoreSections(section) + setOpenChoreSectionsWithCache( + // open all sections by default + Object.keys(section).reduce((acc, key) => { + acc[key] = true + return acc + }, {}), + ) + }} onItemSelect={selected => { const section = ChoresGrouper(selected.value, chores) setChoreSections(section) @@ -441,7 +460,7 @@ const MyChores = () => { }, {}), ) setFilteredChores(chores) - setSelectedFilter('All') + setSearchFilter('All') }} mouseClickHandler={handleMenuOutsideClick} /> @@ -454,12 +473,12 @@ const MyChores = () => { k={'icon-menu-priority-filter'} icon={} options={Priorities} - selectedItem={selectedFilter} + selectedItem={searchFilter} onItemSelect={selected => { handleLabelFiltering({ priority: selected.value }) }} mouseClickHandler={handleMenuOutsideClick} - isActive={selectedFilter.startsWith('Priority: ')} + isActive={searchFilter.startsWith('Priority: ')} /> { label={' Labels'} icon={