enhance chore management with status updates and default assignee settings
This commit is contained in:
@@ -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())
|
||||
|
||||
@@ -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 = () => {
|
||||
<FormControl error={Boolean(errors.assignee)}>
|
||||
<FormHelperText error>{Boolean(errors.assignee)}</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
{showSaveAssigneeDefault && (
|
||||
<Box sx={{ mt: 2, display: 'flex', justifyContent: 'start' }}>
|
||||
<Button
|
||||
variant='outlined'
|
||||
size='sm'
|
||||
color='neutral'
|
||||
startDecorator={<Save />}
|
||||
sx={{
|
||||
borderRadius: 6,
|
||||
fontWeight: 500,
|
||||
'&:hover': {
|
||||
background: 'neutral.softHoverBg',
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
localStorage.setItem(
|
||||
'defaultAssigneeSetting',
|
||||
JSON.stringify(assignees),
|
||||
)
|
||||
setShowSaveAssigneeDefault(false)
|
||||
}}
|
||||
>
|
||||
Save Assignee Preference
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{assignees.length > 1 && (
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user