From c27404cc3a6c0931e99d9403b55ec35f49dcd184 Mon Sep 17 00:00:00 2001 From: Mo Tarbin Date: Sat, 27 Sep 2025 13:47:41 -0400 Subject: [PATCH] Refactor chore management modals and actions for centralized handling - Removed individual modal states and handlers from ChoreCard and CompactChoreCard components. - Introduced a centralized modal state and handler in MyChores component to manage modals for changing due dates, completing with past dates, changing assignees, adding notes, writing NFC, and nudging. - Updated action handlers to utilize the new centralized approach for better maintainability and readability. - Cleaned up unused imports and code related to modal management in ChoreCard and CompactChoreCard components. --- src/components/common/BottomSheetModal.jsx | 5 +- src/components/common/FadeModal.jsx | 4 +- src/queries/ChoreQueries.jsx | 133 ++++-- src/views/ChoreEdit/ChoreEdit.jsx | 90 ++-- src/views/Chores/ChoreCard.jsx | 179 +------- src/views/Chores/CompactChoreCard.jsx | 174 +------ src/views/Chores/MyChores.jsx | 507 ++++++++++++++------- 7 files changed, 544 insertions(+), 548 deletions(-) diff --git a/src/components/common/BottomSheetModal.jsx b/src/components/common/BottomSheetModal.jsx index 606c0d6..51a3b12 100644 --- a/src/components/common/BottomSheetModal.jsx +++ b/src/components/common/BottomSheetModal.jsx @@ -78,6 +78,9 @@ const BottomSheetModal = forwardRef( // Calculate current height const currentHeight = isExpanded ? expandedHeight : height + // Filter out DOM props that shouldn't be passed to Modal + const { fullWidth: _fullWidth, unmountDelay: _unmountDelay, ...modalProps } = props + return ( { + // Filter out props that shouldn't be passed to Modal + const { unmountDelay: _unmountDelay, ...modalProps } = props return ( { }, }) } +export const useDeleteChores = () => { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: async choreIds => { + // If offline mode is enabled and we're offline, handle deletion locally + if (!networkManager.isOnline && isFeatureEnabled(FEATURES.OFFLINE_MODE)) { + const offlineTasks = + (await localStore.getFromCache('offlineTasks')) || [] + const updatedOfflineTasks = offlineTasks.filter( + task => + !choreIds.includes(task.id) && !choreIds.includes(task.tempId), + ) + await localStore.saveToCache('offlineTasks', updatedOfflineTasks) + // Force the chores query to refetch + queryClient.invalidateQueries(['chores']) + return + } + + // If online, proceed with server-side deletion + await Promise.all( + choreIds.map(async id => { + const resp = await DeleteChore(id) + if (!resp || !resp.ok) { + throw new Error(`Failed to delete chore with ID: ${id}`) + } + }), + ) + }, + onSuccess: () => { + queryClient.invalidateQueries(['chores']) + }, + }) +} export const useCreateChore = () => { const queryClient = useQueryClient() return useMutation({ - mutationFn: CreateChore, - onMutate: async newTask => { - if (!networkManager.isOnline && isFeatureEnabled(FEATURES.OFFLINE_MODE)) { - const tempId = crypto.randomUUID() // Generate temp ID - const offlineTasks = - (await localStore.getFromCache('offlineTasks')) || [] - const updateOfflineTasks = [ - ...offlineTasks, - { ...newTask, id: tempId, tempId }, // Use the tempId for offline tracking - ] - await localStore.saveToCache('offlineTasks', updateOfflineTasks) // Save to local storage - // force useChores to refetch: - queryClient.invalidateQueries(['chores']) - // Force the chores query to refetch - queryClient.refetchQueries(['chores']) - // Update the chores query cache immediately - // queryClient.setQueryData(['chores'], oldData => { - // console.log('ATTEMPT TO SAVE OFFLINE TASKS:', updateOfflineTasks) - - // if (!oldData) - // return { - // res: [{ ...newTask, id: tempId, tempId }], - // } // If no data, return offline tasks - // return { - // res: [...oldData.res, { ...newTask, id: tempId, tempId }], - // } - // }) - return { tempId } + mutationFn: async newTask => { + const resp = await CreateChore(newTask) + if (!resp || !resp.ok) { + throw new Error('Failed to create chore') } - return { tempId: null } + const createdChore = await resp.json() + if (!createdChore) { + throw new Error('Failed to get created chore data') + } + // Successfully created the chore on the server, return the created chore + // update the local chores cache with the new chore: + queryClient.setQueryData(['chores'], oldData => { + if (!oldData) return { res: [createdChore.res] } + return { res: [...oldData.res, createdChore.res] } + }) + return { res: createdChore } + }, + + // onMutate: async newTask => { + // if (!networkManager.isOnline && isFeatureEnabled(FEATURES.OFFLINE_MODE)) { + // const tempId = crypto.randomUUID() // Generate temp ID + // const offlineTasks = + // (await localStore.getFromCache('offlineTasks')) || [] + // const updateOfflineTasks = [ + // ...offlineTasks, + // { ...newTask, id: tempId, tempId }, // Use the tempId for offline tracking + // ] + // await localStore.saveToCache('offlineTasks', updateOfflineTasks) // Save to local storage + // // force useChores to refetch: + // queryClient.invalidateQueries(['chores']) + // // Force the chores query to refetch + // queryClient.refetchQueries(['chores']) + // // Update the chores query cache immediately + // // queryClient.setQueryData(['chores'], oldData => { + // // console.log('ATTEMPT TO SAVE OFFLINE TASKS:', updateOfflineTasks) + + // // if (!oldData) + // // return { + // // res: [{ ...newTask, id: tempId, tempId }], + // // } // If no data, return offline tasks + // // return { + // // res: [...oldData.res, { ...newTask, id: tempId, tempId }], + // // } + // // }) + // return { tempId } + // } + // const tempId = crypto.randomUUID() // Generate temp ID + // // Update the chores query cache immediately + // queryClient.setQueryData(['chores'], oldData => { + // if (!oldData) + // return { + // res: [{ ...newTask, id: tempId, tempId }], + // } // If no data, return offline tasks + // return { + // res: [...oldData.res, { ...newTask, id: tempId, tempId }], + // } + // }) + // return { tempId: null } + // }, + onSuccess: () => { + // Invalidate the chores query to refresh the data + queryClient.invalidateQueries(['chores']) }, }) } @@ -157,6 +223,15 @@ export const useUpdateChore = () => { throw new Error('Failed to get updated chore data') } // Successfully updated the chore on the server, return the updated chore + // update the local chores cache with the updated chore: + queryClient.setQueryData(['chores'], oldData => { + if (!oldData) return { res: [updatedChore] } + return { + res: oldData.res.map(chore => + chore.id === updatedChore.id ? updatedChore : chore, + ), + } + }) return updatedChoreRes?.res || updatedChoreRes } }, diff --git a/src/views/ChoreEdit/ChoreEdit.jsx b/src/views/ChoreEdit/ChoreEdit.jsx index 1ee90ef..beda7c8 100644 --- a/src/views/ChoreEdit/ChoreEdit.jsx +++ b/src/views/ChoreEdit/ChoreEdit.jsx @@ -26,8 +26,10 @@ import { useEffect, useState } from 'react' import { useNavigate, useParams, useSearchParams } from 'react-router-dom' import NotificationTemplate from '../../components/NotificationTemplate.jsx' import { + useArchiveChore, useChore, useCreateChore, + useUnArchiveChore, useUpdateChore, } from '../../queries/ChoreQueries.jsx' import { useCircleMembers, useUserProfile } from '../../queries/UserQueries.jsx' @@ -56,6 +58,7 @@ const ASSIGN_STRATEGIES = [ 'keep_last_assigned', 'random_except_last_assigned', 'round_robin', + 'no_assignee', ] const REPEAT_ON_TYPE = ['interval', 'days_of_the_week', 'day_of_the_month'] @@ -111,6 +114,8 @@ const ChoreEdit = () => { const { data: userLabelsRaw, isLoading: isUserLabelsLoading } = useLabels() const updateChoreMutation = useUpdateChore() const createChoreMutation = useCreateChore() + const archiveChore = useArchiveChore() + const unarchiveChore = useUnArchiveChore() const { data: choreData, isLoading: isChoreLoading, @@ -136,11 +141,13 @@ const ChoreEdit = () => { if (name.trim() === '') { errors.name = 'Name is required' } - if (assignees.length === 0) { - errors.assignees = 'At least 1 assignees is required' - } - if (assignedTo < 0) { - errors.assignedTo = 'Assigned to 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` @@ -228,7 +235,7 @@ const ChoreEdit = () => { frequencyType: frequencyType, frequency: Number(frequency), frequencyMetadata: frequencyMetadata, - assignedTo: assignedTo, + assignedTo: assignStrategy === 'no_assignee' ? null : assignedTo, assignStrategy: assignStrategy, isRolling: isRolling, isActive: isActive, @@ -379,20 +386,26 @@ const ChoreEdit = () => { }, [frequencyType]) useEffect(() => { - if (assignees.length === 1) { + if (assignees.length === 0) { + setAssignStrategy('no_assignee') + setAssignedTo(null) + } else if (assignees.length === 1) { setAssignedTo(assignees[0].userId) + if (assignStrategy === 'no_assignee') { + setAssignStrategy(ASSIGN_STRATEGIES[2]) // default to least_completed + } } - }, [assignees]) + }, [assignees, assignStrategy]) - useEffect(() => { - if (performers.length > 0 && assignees.length === 0 && userProfile) { - setAssignees([ - { - userId: userProfile?.id, - }, - ]) - } - }, [performers, userProfile]) + // useEffect(() => { + // if (performers.length > 0 && assignees.length === 0 && userProfile) { + // setAssignees([ + // { + // userId: userProfile?.id, + // }, + // ]) + // } + // }, [performers, userProfile]) // if user resolve the error trigger validation to remove the error message from the respective field useEffect(() => { @@ -1221,16 +1234,39 @@ const ChoreEdit = () => { }} > {choreId > 0 && ( - + <> + {isActive ? ( + + ) : ( + + )} + + )}