diff --git a/src/hooks/useSSE.js b/src/hooks/useSSE.js index cbebd5f..07d6d63 100644 --- a/src/hooks/useSSE.js +++ b/src/hooks/useSSE.js @@ -59,13 +59,15 @@ export const useSSE = () => { if (eventData.type === 'heartbeat') { lastHeartbeatRef.current = Date.now() } - console.log('SSE Message received:', eventData) + + console.debug('SSE Message received:', eventData) // Handle different event types and update React Query cache accordingly switch (eventData.type) { case 'chore.created': case 'chore.updated': case 'chore.completed': + case 'chore.status': case 'chore.skipped': { if (eventData?.data?.user?.id !== userProfile?.id) { showNotification({ @@ -83,19 +85,25 @@ export const useSSE = () => { return { res: { ...oldData.res, ...updatedChore } } }) + // If chore update then also refetch chore details: + if ( + eventData.type === 'chore.updated' || + eventData.type === 'chore.status' + ) { + queryClient.invalidateQueries(['choreDetails', updatedChore.id]) + queryClient.refetchQueries({ + queryKey: ['choreDetails', updatedChore.id], + }) + } + // Update chores list cache - add debugging - queryClient.setQueryData(['chores'], oldData => { + queryClient.setQueryData(['chores', false], oldData => { if (!oldData) return { res: [updatedChore] } if (!oldData.res || !Array.isArray(oldData.res)) { return { res: [updatedChore] } } - // Check if the chore exists in the cache - const choreExists = oldData.res.some( - chore => chore.id === updatedChore.id, - ) - // If it's a one-time chore that's completed, we might need to remove it if ( eventData.type === 'chore.completed' && @@ -108,25 +116,15 @@ export const useSSE = () => { } } - // If chore update then also refetch chore details: - if (eventData.type === 'chore.updated') { - queryClient.invalidateQueries(['choreDetails', updatedChore.id]) - queryClient.refetchQueries({ - queryKey: ['choreDetails', updatedChore.id], - }) - } - // Otherwise update the existing chore or add if it doesn't exist - return { - res: choreExists - ? oldData.res.map(chore => { - if (chore.id === updatedChore.id) { - return { ...chore, ...updatedChore } - } - return chore - }) - : [...oldData.res, updatedChore], - } + const newData = oldData.res.map(chore => { + if (chore.id === updatedChore.id) { + return { ...updatedChore } + } + return chore + }) + + return { res: newData } }) break @@ -134,7 +132,16 @@ export const useSSE = () => { case 'chore.deleted': // update chores list cache - queryClient.setQueryData(['chores'], oldData => { + queryClient.setQueryData(['chores', false], oldData => { + if (!oldData || !oldData.res) return oldData + return { + res: oldData.res.filter( + chore => chore.id !== eventData.data.choreId, + ), + } + }) + // same logic for archived chores view: + queryClient.setQueryData(['chores', true], oldData => { if (!oldData || !oldData.res) return oldData return { res: oldData.res.filter( @@ -147,9 +154,27 @@ export const useSSE = () => { case 'subtask.updated': case 'subtask.completed': - queryClient.refetchQueries({ - queryKey: ['choreDetails', eventData.data.choreId], - }) + queryClient.setQueryData( + ['choreDetails', String(eventData.data.choreId)], // this should be string to match the query key type which is param in the url in choreView + oldData => { + if (!oldData) return oldData + console.log('Old choreDetails data:', oldData) + + // Update the specific subtask within the chore details + const newChoreData = { ...oldData.res } + newChoreData.subTasks = newChoreData.subTasks.map(subtask => { + if (subtask.id === eventData.data.subtaskId) { + return { + ...subtask, + completedAt: eventData.data.completedAt, + completedBy: eventData.data.user.id, + } + } + return subtask + }) + return { res: newChoreData } + }, + ) // Invalidate the specific chore that contains this subtask // if (eventData.data.choreId) { @@ -162,10 +187,7 @@ export const useSSE = () => { // Also invalidate general chores list // queryClient.invalidateQueries(['chores']) break - case 'chore.status': - console.log('SSE chore.status event received:', eventData.data) - break case 'heartbeat': // Heartbeat events don't need cache invalidation console.debug('SSE Heartbeat received at', new Date().toISOString()) diff --git a/src/views/ChoreEdit/ChoreEdit.jsx b/src/views/ChoreEdit/ChoreEdit.jsx index beda7c8..e97ec8f 100644 --- a/src/views/ChoreEdit/ChoreEdit.jsx +++ b/src/views/ChoreEdit/ChoreEdit.jsx @@ -110,6 +110,7 @@ const ChoreEdit = () => { const [privacySaved, setPrivacySaved] = useState(false) const [showSaveNotificationDefault, setShowSaveNotificationDefault] = useState(false) + const [showSaveAssigneeDefault, setShowSaveAssigneeDefault] = useState(false) const { data: userLabelsRaw, isLoading: isUserLabelsLoading } = useLabels() const updateChoreMutation = useUpdateChore() @@ -300,6 +301,14 @@ const ChoreEdit = () => { if (defaultNotificationSetting !== null) { setIsNotificable(JSON.parse(defaultNotificationSetting)) } + + const defaultAssigneeSetting = localStorage.getItem( + 'defaultAssigneeSetting', + ) + if (defaultAssigneeSetting !== null) { + const savedAssignees = JSON.parse(defaultAssigneeSetting) + setAssignees(savedAssignees) + } } }, []) useEffect(() => { @@ -665,6 +674,7 @@ const ChoreEdit = () => { } else { setAssignees([...assignees, { userId: item.userId }]) } + setShowSaveAssigneeDefault(true) }} overlay disableIcon @@ -678,6 +688,33 @@ const ChoreEdit = () => { {Boolean(errors.assignee)} + + {showSaveAssigneeDefault && ( + + + + )} {assignees.length > 1 && ( diff --git a/src/views/ChoreEdit/ChoreView.jsx b/src/views/ChoreEdit/ChoreView.jsx index a43e299..96fb4ab 100644 --- a/src/views/ChoreEdit/ChoreView.jsx +++ b/src/views/ChoreEdit/ChoreView.jsx @@ -44,6 +44,13 @@ import { useNavigate, useParams, useSearchParams } from 'react-router-dom' import { useImpersonateUser } from '../../contexts/ImpersonateUserContext.jsx' import { useChoreDetails } from '../../queries/ChoreQueries.jsx' +import { + useChoreTimer, + useDeleteTimeSession, + usePauseChore, + useResetChoreTimer, + useStartChore, +} from '../../queries/TimeQueries' import { useCircleMembers, useUserProfile } from '../../queries/UserQueries.jsx' import { ChoreStatus, notInCompletionWindow } from '../../utils/Chores.jsx' import { getTextColorFromBackgroundColor } from '../../utils/Colors.jsx' @@ -55,13 +62,6 @@ import { SkipChore, UpdateChorePriority, } from '../../utils/Fetcher' -import { - useChoreTimer, - useDeleteTimeSession, - usePauseChore, - useResetChoreTimer, - useStartChore, -} from '../../queries/TimeQueries' import Priorities from '../../utils/Priorities' import { getSafeBottomPadding } from '../../utils/SafeAreaUtils.js' import ConfirmationModal from '../Modals/Inputs/ConfirmationModal' diff --git a/src/views/Settings/ProfileSettings.jsx b/src/views/Settings/ProfileSettings.jsx index 7f0eb1d..92d9067 100644 --- a/src/views/Settings/ProfileSettings.jsx +++ b/src/views/Settings/ProfileSettings.jsx @@ -9,6 +9,7 @@ import { } from '@mui/joy' import Modal from '@mui/joy/Modal' import ModalDialog from '@mui/joy/ModalDialog' +import { useQueryClient } from '@tanstack/react-query' import imageCompression from 'browser-image-compression' import { useRef, useState } from 'react' import Cropper from 'react-easy-crop' @@ -21,6 +22,7 @@ import { UploadFile } from '../../utils/TokenManager' import SettingsLayout from './SettingsLayout' const ProfileSettings = () => { + const queryClient = useQueryClient() const { data: userProfile } = useUserProfile() const { showSuccess, showError } = useNotification() const [displayName, setDisplayName] = useState(userProfile?.displayName || '') @@ -113,6 +115,9 @@ const ProfileSettings = () => { try { const userDetails = { displayName, timezone } const response = await UpdateUserDetails(userDetails) + // invalidate user profile cache here if using react-query or similar: + queryClient.invalidateQueries(['userProfile']) + queryClient.refetchQueries(['userProfile']) if (response.ok) { showSuccess({ @@ -123,6 +128,8 @@ const ProfileSettings = () => { throw new Error('Failed to update profile') } } catch (err) { + console.log(err) + showError({ title: 'Update Failed', message: