feat: add nudge functionality to chore cards and action menu; enhance user notifications

fix Filtering when imposing user to respect the sort and grouping order
This commit is contained in:
Mo Tarbin
2025-09-18 01:14:51 -04:00
parent 707ba55e1d
commit eded4b2e16
5 changed files with 137 additions and 7 deletions

View File

@@ -133,7 +133,8 @@ const ChoreView = () => {
icon: <PeopleAlt />,
title: 'Assignment',
text: `Assigned: ${
performers.find(p => p.id === chore.assignedTo)?.displayName || 'N/A'
performers.find(p => p.userId === chore.assignedTo)?.displayName ||
'N/A'
}`,
subtext: ` Last: ${
chore.lastCompletedDate

View File

@@ -4,6 +4,7 @@ import {
Delete,
Edit,
HourglassEmpty,
Notifications,
Pause,
PlayArrow,
Repeat,
@@ -39,6 +40,7 @@ import {
ApproveChore,
DeleteChore,
MarkChoreComplete,
NudgeChore,
PauseChore,
RejectChore,
StartChore,
@@ -48,6 +50,7 @@ import {
import Priorities from '../../utils/Priorities'
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
import DateModal from '../Modals/Inputs/DateModal'
import NudgeModal from '../Modals/Inputs/NudgeModal'
import SelectModal from '../Modals/Inputs/SelectModal'
import TextModal from '../Modals/Inputs/TextModal'
import WriteNFCModal from '../Modals/Inputs/WriteNFCModal'
@@ -75,6 +78,7 @@ const ChoreCard = ({
React.useState(false)
const [confirmModelConfig, setConfirmModelConfig] = React.useState({})
const [isNFCModalOpen, setIsNFCModalOpen] = React.useState(false)
const [isNudgeModalOpen, setIsNudgeModalOpen] = React.useState(false)
const navigate = useNavigate()
const [isPendingCompletion, setIsPendingCompletion] = React.useState(false)
@@ -84,7 +88,7 @@ const ChoreCard = ({
const { impersonatedUser } = useImpersonateUser()
const { showError } = useNotification()
const { showError, showNotification } = useNotification()
// Swipe functionality state
const [swipeTranslateX, setSwipeTranslateX] = React.useState(0)
@@ -93,7 +97,7 @@ const ChoreCard = ({
const [hoverTimer, setHoverTimer] = React.useState(null)
const [isTouchDevice, setIsTouchDevice] = React.useState(false)
const swipeThreshold = 80 // Minimum swipe distance to reveal actions
const maxSwipeDistance = 220 // Maximum swipe distance
const maxSwipeDistance = 260 // Maximum swipe distance
const dragStartX = React.useRef(0)
const cardRef = React.useRef(null)
@@ -261,6 +265,30 @@ const ChoreCard = ({
})
}
const handleNudge = async ({ choreId, message, notifyAllAssignees }) => {
try {
const response = await NudgeChore(choreId, { message, notifyAllAssignees })
if (response.ok) {
const data = await response.json()
showNotification({
type: 'success',
title: 'Nudge Sent!',
message: data.message || 'Nudge sent successfully',
})
} else {
throw new Error('Failed to send nudge')
}
} catch (error) {
showError({
title: 'Failed to Send Nudge',
message: error.message || 'Unable to send nudge at this time',
})
} finally {
setIsNudgeModalOpen(false)
resetSwipe()
}
}
// Check if the current user can approve/reject (admin, manager, or task owner)
const canApproveReject = () => {
if (!performers || !chore) return false
@@ -798,6 +826,24 @@ const ChoreCard = ({
<Edit sx={{ fontSize: 20 }} />
</IconButton>
<IconButton
variant='soft'
color='warning'
size='md'
onClick={e => {
e.stopPropagation()
resetSwipe()
setIsNudgeModalOpen(true)
}}
sx={{
width: 40,
height: 40,
mx: 1,
}}
>
<Notifications sx={{ fontSize: 20 }} />
</IconButton>
<IconButton
variant='soft'
color='danger'
@@ -1192,6 +1238,7 @@ const ChoreCard = ({
onChangeAssignee={() => setIsChangeAssigneeModalOpen(true)}
onChangeDueDate={() => setIsChangeDueDateModalOpen(true)}
onWriteNFC={() => setIsNFCModalOpen(true)}
onNudge={() => setIsNudgeModalOpen(true)}
onDelete={handleDelete}
onMouseEnter={handleMouseEnter}
onOpen={() => {
@@ -1259,6 +1306,15 @@ const ChoreCard = ({
},
}}
/>
<NudgeModal
config={{
isOpen: isNudgeModalOpen,
choreId: chore.id,
onClose: () => setIsNudgeModalOpen(false),
onConfirm: handleNudge,
}}
/>
</Card>
</Box>
<Snackbar

View File

@@ -4,6 +4,7 @@ import {
Delete,
Edit,
HourglassEmpty,
Notifications,
Pause,
PlayArrow,
Repeat,
@@ -38,6 +39,7 @@ import {
ApproveChore,
DeleteChore,
MarkChoreComplete,
NudgeChore,
PauseChore,
RejectChore,
StartChore,
@@ -46,6 +48,7 @@ import {
} from '../../utils/Fetcher'
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
import DateModal from '../Modals/Inputs/DateModal'
import NudgeModal from '../Modals/Inputs/NudgeModal'
import SelectModal from '../Modals/Inputs/SelectModal'
import TextModal from '../Modals/Inputs/TextModal'
import WriteNFCModal from '../Modals/Inputs/WriteNFCModal'
@@ -74,6 +77,7 @@ const CompactChoreCard = ({
React.useState(false)
const [confirmModelConfig, setConfirmModelConfig] = React.useState({})
const [isNFCModalOpen, setIsNFCModalOpen] = React.useState(false)
const [isNudgeModalOpen, setIsNudgeModalOpen] = React.useState(false)
const navigate = useNavigate()
const [isPendingCompletion, setIsPendingCompletion] = React.useState(false)
@@ -84,7 +88,7 @@ const CompactChoreCard = ({
const { impersonatedUser } = useImpersonateUser()
const { showError } = useNotification()
const { showError, showNotification } = useNotification()
// Swipe functionality state
const [swipeTranslateX, setSwipeTranslateX] = React.useState(0)
@@ -93,7 +97,7 @@ const CompactChoreCard = ({
const [hoverTimer, setHoverTimer] = React.useState(null)
const [isTouchDevice, setIsTouchDevice] = React.useState(false)
const swipeThreshold = 80 // Minimum swipe distance to reveal actions
const maxSwipeDistance = 220 // Maximum swipe distance
const maxSwipeDistance = 260 // Maximum swipe distance
const dragStartX = React.useRef(0)
const cardRef = React.useRef(null)
@@ -435,6 +439,30 @@ const CompactChoreCard = ({
})
}
const handleNudge = async ({ choreId, message, notifyAllAssignees }) => {
try {
const response = await NudgeChore(choreId, { message, notifyAllAssignees })
if (response.ok) {
const data = await response.json()
showNotification({
type: 'success',
title: 'Nudge Sent!',
message: data.message || 'Nudge sent successfully',
})
} else {
throw new Error('Failed to send nudge')
}
} catch (error) {
showError({
title: 'Failed to Send Nudge',
message: error.message || 'Unable to send nudge at this time',
})
} finally {
setIsNudgeModalOpen(false)
resetSwipe()
}
}
// Check if the current user can approve/reject (admin, manager, or task owner)
const canApproveReject = () => {
if (!circleMembersData?.res || !chore) return false
@@ -795,6 +823,24 @@ const CompactChoreCard = ({
<Edit sx={{ fontSize: 16 }} />
</IconButton>
<IconButton
variant='soft'
color='warning'
size='sm'
onClick={e => {
e.stopPropagation()
resetSwipe()
setIsNudgeModalOpen(true)
}}
sx={{
width: 40,
height: 40,
mx: 1,
}}
>
<Notifications sx={{ fontSize: 16 }} />
</IconButton>
<IconButton
variant='soft'
color='danger'
@@ -1220,6 +1266,7 @@ const CompactChoreCard = ({
onChangeAssignee={() => setIsChangeAssigneeModalOpen(true)}
onChangeDueDate={() => setIsChangeDueDateModalOpen(true)}
onWriteNFC={() => setIsNFCModalOpen(true)}
onNudge={() => setIsNudgeModalOpen(true)}
onDelete={handleDelete}
onMouseEnter={handleMouseEnter}
// onMouseLeave={handleMouseLeave}
@@ -1290,6 +1337,15 @@ const CompactChoreCard = ({
}}
/>
<NudgeModal
config={{
isOpen: isNudgeModalOpen,
choreId: chore.id,
onClose: () => setIsNudgeModalOpen(false),
onConfirm: handleNudge,
}}
/>
{/* Snackbar for pending completion */}
<Snackbar
open={isPendingCompletion}

View File

@@ -132,7 +132,12 @@ const MyChores = () => {
// Filter chores based on impersonated user
if (impersonatedUser) {
sortedChores = sortedChores.filter(
chore => chore.assignedTo === impersonatedUser.userId,
chore =>
chore.assignedTo === impersonatedUser.userId ||
chore.assignees?.some(
a => a.userId === impersonatedUser.userId,
) ||
chore.isPrivate === false,
)
}
@@ -1150,7 +1155,7 @@ const MyChores = () => {
const section = ChoresGrouper(
selectedChoreSection,
chores,
ChoreFilters(userProfile)[filter],
ChoreFilters(impersonatedUser | userProfile)[filter],
)
setChoreSections(section)
setOpenChoreSectionsWithCache(

View File

@@ -9,6 +9,7 @@ import {
MoreVert,
NextWeek,
Nfc,
Notifications,
NoteAdd,
RecordVoiceOver,
SwitchAccessShortcut,
@@ -40,6 +41,7 @@ const ChoreActionMenu = ({
onChangeAssignee,
onChangeDueDate,
onWriteNFC,
onNudge,
onDelete,
onOpen,
onMouseEnter,
@@ -315,6 +317,16 @@ const ChoreActionMenu = ({
<RecordVoiceOver />
Delegate to someone else
</MenuItem>
<MenuItem
onClick={e => {
e.stopPropagation()
onNudge?.()
handleMenuClose()
}}
>
<Notifications />
Send nudge
</MenuItem>
<Divider />
<MenuItem
onClick={e => {