From ddf654a6411d01460d3c8a60e01814c3e7180b28 Mon Sep 17 00:00:00 2001 From: Mo Tarbin Date: Sun, 1 Jun 2025 00:47:47 -0400 Subject: [PATCH] refactor: Consolidate chore action handling into ChoreActionMenu component and remove unused code from ChoreCard and CompactChoreCard --- src/views/ChoreEdit/ChoreView.jsx | 11 - src/views/Chores/ChoreCard.jsx | 264 ++------------------ src/views/Chores/CompactChoreCard.jsx | 209 ++-------------- src/views/Settings/MFASettings.jsx | 6 +- src/views/components/CalendarView.jsx | 15 +- src/views/components/ChoreActionMenu.jsx | 303 +++++++++++++++++++++++ 6 files changed, 351 insertions(+), 457 deletions(-) create mode 100644 src/views/components/ChoreActionMenu.jsx diff --git a/src/views/ChoreEdit/ChoreView.jsx b/src/views/ChoreEdit/ChoreView.jsx index 1eeebad..d00035a 100644 --- a/src/views/ChoreEdit/ChoreView.jsx +++ b/src/views/ChoreEdit/ChoreView.jsx @@ -30,7 +30,6 @@ import { MenuItem, Sheet, Snackbar, - styled, Typography, } from '@mui/joy' import { Divider } from '@mui/material' @@ -54,16 +53,6 @@ import ConfirmationModal from '../Modals/Inputs/ConfirmationModal' import LoadingComponent from '../components/Loading.jsx' import RichTextEditor from '../components/RichTextEditor.jsx' import SubTasks from '../components/SubTask.jsx' -const IconCard = styled('div')({ - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - backgroundColor: '#f0f0f0', // Adjust the background color as needed - borderRadius: '50%', - minWidth: '40px', - height: '40px', - marginRight: '16px', -}) const ChoreView = () => { const [chore, setChore] = useState({}) diff --git a/src/views/Chores/ChoreCard.jsx b/src/views/Chores/ChoreCard.jsx index 4a9dc56..03e2085 100644 --- a/src/views/Chores/ChoreCard.jsx +++ b/src/views/Chores/ChoreCard.jsx @@ -1,29 +1,9 @@ import { - Archive, CancelScheduleSend, Check, - CopyAll, - Delete, - Edit, - HorizontalRule, - KeyboardControlKey, - KeyboardDoubleArrowUp, - LocalOffer, - ManageSearch, - MoreTime, - MoreVert, - Nfc, - NoteAdd, - PriorityHigh, - RecordVoiceOver, Repeat, - Report, - SwitchAccessShortcut, TimesOneMobiledata, Toll, - Unarchive, - Update, - ViewCarousel, Webhook, } from '@mui/icons-material' import { @@ -33,16 +13,13 @@ import { Card, Chip, CircularProgress, - Divider, Grid, IconButton, - Menu, - MenuItem, Snackbar, Typography, } from '@mui/joy' import moment from 'moment' -import React, { useEffect } from 'react' +import React from 'react' import { useNavigate } from 'react-router-dom' import { useImpersonateUser } from '../../contexts/ImpersonateUserContext.jsx' import { UserContext } from '../../contexts/UserContext' @@ -50,11 +27,8 @@ import { useError } from '../../service/ErrorProvider' import { notInCompletionWindow } from '../../utils/Chores.jsx' import { getTextColorFromBackgroundColor } from '../../utils/Colors.jsx' import { - ArchiveChore, DeleteChore, MarkChoreComplete, - SkipChore, - UnArchiveChore, UpdateChoreAssignee, UpdateDueDate, } from '../../utils/Fetcher' @@ -64,17 +38,16 @@ import DateModal from '../Modals/Inputs/DateModal' import SelectModal from '../Modals/Inputs/SelectModal' import TextModal from '../Modals/Inputs/TextModal' import WriteNFCModal from '../Modals/Inputs/WriteNFCModal' +import ChoreActionMenu from '../components/ChoreActionMenu' const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, - userLabels, sx, viewOnly, onChipClick, }) => { - const [activeUserId, setActiveUserId] = React.useState(0) const [isChangeDueDateModalOpen, setIsChangeDueDateModalOpen] = React.useState(false) const [isCompleteWithPastDateModalOpen, setIsCompleteWithPastDateModalOpen] = @@ -85,10 +58,7 @@ const ChoreCard = ({ React.useState(false) const [confirmModelConfig, setConfirmModelConfig] = React.useState({}) const [isNFCModalOpen, setIsNFCModalOpen] = React.useState(false) - const [anchorEl, setAnchorEl] = React.useState(null) - const menuRef = React.useRef(null) const navigate = useNavigate() - const [isDisabled, setIsDisabled] = React.useState(false) const [isPendingCompletion, setIsPendingCompletion] = React.useState(false) const [secondsLeftToCancel, setSecondsLeftToCancel] = React.useState(null) @@ -97,39 +67,7 @@ const ChoreCard = ({ const { impersonatedUser } = useImpersonateUser() const { showError } = useError() - useEffect(() => { - document.addEventListener('mousedown', handleMenuOutsideClick) - return () => { - document.removeEventListener('mousedown', handleMenuOutsideClick) - } - }, [anchorEl]) - const handleMenuOpen = event => { - setAnchorEl(event.currentTarget) - } - - const handleMenuClose = () => { - setAnchorEl(null) - } - - const handleMenuOutsideClick = event => { - if ( - anchorEl && - !anchorEl.contains(event.target) && - !menuRef.current.contains(event.target) - ) { - handleMenuClose() - } - } - const handleEdit = () => { - navigate(`/chores/${chore.id}/edit`) - } - const handleClone = () => { - navigate(`/chores/${chore.id}/edit?clone=true`) - } - const handleView = () => { - navigate(`/chores/${chore.id}`) - } const handleDelete = () => { setConfirmModelConfig({ isOpen: true, @@ -149,30 +87,6 @@ const ChoreCard = ({ }, }) } - const handleArchive = () => { - if (chore.isActive) { - ArchiveChore(chore.id).then(response => { - if (response.ok) { - response.json().then(data => { - const newChore = { ...chore, isActive: false } - - onChoreUpdate(newChore, 'archive') - }) - } - }) - } else { - UnArchiveChore(chore.id).then(response => { - if (response.ok) { - response.json().then(data => { - const newChore = { ...chore, isActive: true } - onChoreUpdate(newChore, 'unarchive') - }) - } - }) - } - - handleMenuClose() - } const handleTaskCompletion = () => { setIsPendingCompletion(true) @@ -235,10 +149,6 @@ const ChoreCard = ({ } const handleChangeDueDate = newDate => { - if (activeUserId === null) { - alert('Please select a performer') - return - } UpdateDueDate(chore.id, newDate).then(response => { if (response.ok) { response.json().then(data => { @@ -250,11 +160,6 @@ const ChoreCard = ({ } const handleCompleteWithPastDate = newDate => { - if (activeUserId === null) { - alert('Please select a performer') - return - } - MarkChoreComplete( chore.id, impersonatedUser ? { completedBy: impersonatedUser.userId } : null, @@ -318,29 +223,6 @@ const ChoreCard = ({ return 'neutral' } - const getIconForLabel = label => { - if (!label || label.trim() === '') return <> - switch (String(label).toLowerCase()) { - case 'high': - return - case 'important': - return - default: - return - } - } - const getPriorityIcon = priority => { - switch (Number(priority)) { - case 1: - return - case 2: - return - case 3: - return - default: - return - } - } const getRecurrentChipText = chore => { // if chore.frequencyMetadata is type string then parse it otherwise assigned to the metadata: const metadata = @@ -678,135 +560,19 @@ const ChoreCard = ({ )} - - - - {/* */} - - { - setIsCompleteWithNoteModalOpen(true) - }} - > - - Complete with note - - { - setIsCompleteWithPastDateModalOpen(true) - }} - > - - Complete in past - - { - SkipChore(chore.id) - .then(response => { - if (response.ok) { - response.json().then(data => { - const newChore = data.res - onChoreUpdate(newChore, 'skipped') - handleMenuClose() - }) - } - }) - .catch(error => { - if (error?.queued) { - showError({ - title: 'Failed to update', - message: - 'Request will be processed when you are online', - }) - } else { - showError({ - title: 'Failed to update', - message: error, - }) - } - }) - }} - > - - Skip to next due date - - { - setIsChangeAssigneeModalOpen(true) - }} - > - - Delegate to someone else - - - { - navigate(`/chores/${chore.id}/history`) - }} - > - - History - - - { - setIsChangeDueDateModalOpen(true) - }} - > - - Change due date - - { - // write current chore URL to NFC - // writeToNFC(`${window.location.origin}/chores/${chore.id}`) - setIsNFCModalOpen(true) - }} - > - - Write to NFC - - - - Edit - - - - Clone - - - - View - - - {chore.isActive ? : } - {chore.isActive ? 'Archive' : 'Unarchive'} - - - - - - Delete - - + setIsCompleteWithNoteModalOpen(true)} + onCompleteWithPastDate={() => + setIsCompleteWithPastDateModalOpen(true) + } + onChangeAssignee={() => setIsChangeAssigneeModalOpen(true)} + onChangeDueDate={() => setIsChangeDueDateModalOpen(true)} + onWriteNFC={() => setIsNFCModalOpen(true)} + onDelete={handleDelete} + /> diff --git a/src/views/Chores/CompactChoreCard.jsx b/src/views/Chores/CompactChoreCard.jsx index 78063bd..a3fa4dd 100644 --- a/src/views/Chores/CompactChoreCard.jsx +++ b/src/views/Chores/CompactChoreCard.jsx @@ -1,22 +1,8 @@ import { - Archive, CancelScheduleSend, Check, - CopyAll, - Delete, - Edit, - ManageSearch, - MoreTime, - MoreVert, - Nfc, - NoteAdd, - RecordVoiceOver, Repeat, - SwitchAccessShortcut, TimesOneMobiledata, - Unarchive, - Update, - ViewCarousel, Webhook, } from '@mui/icons-material' import { @@ -24,15 +10,12 @@ import { Button, Chip, CircularProgress, - Divider, IconButton, - Menu, - MenuItem, Snackbar, Typography, } from '@mui/joy' import moment from 'moment' -import React, { useEffect } from 'react' +import React from 'react' import { useNavigate } from 'react-router-dom' import { useImpersonateUser } from '../../contexts/ImpersonateUserContext.jsx' import { UserContext } from '../../contexts/UserContext' @@ -40,11 +23,8 @@ import { useError } from '../../service/ErrorProvider' import { notInCompletionWindow } from '../../utils/Chores.jsx' import { getTextColorFromBackgroundColor } from '../../utils/Colors.jsx' import { - ArchiveChore, DeleteChore, MarkChoreComplete, - SkipChore, - UnArchiveChore, UpdateChoreAssignee, UpdateDueDate, } from '../../utils/Fetcher' @@ -54,6 +34,7 @@ import DateModal from '../Modals/Inputs/DateModal' import SelectModal from '../Modals/Inputs/SelectModal' import TextModal from '../Modals/Inputs/TextModal' import WriteNFCModal from '../Modals/Inputs/WriteNFCModal' +import ChoreActionMenu from '../components/ChoreActionMenu' const CompactChoreCard = ({ chore, @@ -64,7 +45,6 @@ const CompactChoreCard = ({ viewOnly, onChipClick, }) => { - const [activeUserId, setActiveUserId] = React.useState(0) const [isChangeDueDateModalOpen, setIsChangeDueDateModalOpen] = React.useState(false) const [isCompleteWithPastDateModalOpen, setIsCompleteWithPastDateModalOpen] = @@ -75,10 +55,7 @@ const CompactChoreCard = ({ React.useState(false) const [confirmModelConfig, setConfirmModelConfig] = React.useState({}) const [isNFCModalOpen, setIsNFCModalOpen] = React.useState(false) - const [anchorEl, setAnchorEl] = React.useState(null) - const menuRef = React.useRef(null) const navigate = useNavigate() - const [isDisabled, setIsDisabled] = React.useState(false) const [isPendingCompletion, setIsPendingCompletion] = React.useState(false) const [secondsLeftToCancel, setSecondsLeftToCancel] = React.useState(null) @@ -88,44 +65,7 @@ const CompactChoreCard = ({ const { showError } = useError() - useEffect(() => { - document.addEventListener('mousedown', handleMenuOutsideClick) - return () => { - document.removeEventListener('mousedown', handleMenuOutsideClick) - } - }, [anchorEl]) - - const handleMenuOpen = event => { - setAnchorEl(event.currentTarget) - } - - const handleMenuClose = () => { - setAnchorEl(null) - } - - const handleMenuOutsideClick = event => { - if ( - anchorEl && - !anchorEl.contains(event.target) && - !menuRef.current.contains(event.target) - ) { - handleMenuClose() - } - } - // All the existing handler methods (same as original ChoreCard) - const handleEdit = () => { - navigate(`/chores/${chore.id}/edit`) - } - - const handleClone = () => { - navigate(`/chores/${chore.id}/edit?clone=true`) - } - - const handleView = () => { - navigate(`/chores/${chore.id}`) - } - const handleDelete = () => { setConfirmModelConfig({ isOpen: true, @@ -146,29 +86,6 @@ const CompactChoreCard = ({ }) } - const handleArchive = () => { - if (chore.isActive) { - ArchiveChore(chore.id).then(response => { - if (response.ok) { - response.json().then(data => { - const newChore = { ...chore, isActive: false } - onChoreUpdate(newChore, 'archive') - }) - } - }) - } else { - UnArchiveChore(chore.id).then(response => { - if (response.ok) { - response.json().then(data => { - const newChore = { ...chore, isActive: true } - onChoreUpdate(newChore, 'unarchive') - }) - } - }) - } - handleMenuClose() - } - const handleTaskCompletion = () => { setIsPendingCompletion(true) let seconds = 3 @@ -230,10 +147,6 @@ const CompactChoreCard = ({ } const handleChangeDueDate = newDate => { - if (activeUserId === null) { - alert('Please select a performer') - return - } UpdateDueDate(chore.id, newDate).then(response => { if (response.ok) { response.json().then(data => { @@ -245,11 +158,6 @@ const CompactChoreCard = ({ } const handleCompleteWithPastDate = newDate => { - if (activeUserId === null) { - alert('Please select a performer') - return - } - MarkChoreComplete( chore.id, impersonatedUser ? { completedBy: impersonatedUser.userId } : null, @@ -654,113 +562,30 @@ const CompactChoreCard = ({ )} - {/* Menu Button */} - { - e.stopPropagation() - handleMenuOpen(e) - }} + chore={chore} + onChoreUpdate={onChoreUpdate} + onChoreRemove={onChoreRemove} + onCompleteWithNote={() => setIsCompleteWithNoteModalOpen(true)} + onCompleteWithPastDate={() => + setIsCompleteWithPastDateModalOpen(true) + } + onChangeAssignee={() => setIsChangeAssigneeModalOpen(true)} + onChangeDueDate={() => setIsChangeDueDateModalOpen(true)} + onWriteNFC={() => setIsNFCModalOpen(true)} + onDelete={handleDelete} sx={{ width: 28, height: 28, - opacity: 0.6, + // opacity: 0.6, '&:hover': { opacity: 1, }, }} - > - - + /> - - {/* Menu */} - - setIsCompleteWithNoteModalOpen(true)}> - - Complete with note - - setIsCompleteWithPastDateModalOpen(true)}> - - Complete in past - - { - SkipChore(chore.id) - .then(response => { - if (response.ok) { - response.json().then(data => { - const newChore = data.res - onChoreUpdate(newChore, 'skipped') - handleMenuClose() - }) - } - }) - .catch(error => { - if (error?.queued) { - showError({ - title: 'Failed to update', - message: 'Request will be processed when you are online', - }) - } else { - showError({ - title: 'Failed to update', - message: error, - }) - } - }) - }} - > - - Skip to next due date - - setIsChangeAssigneeModalOpen(true)}> - - Delegate to someone else - - - navigate(`/chores/${chore.id}/history`)}> - - History - - - setIsChangeDueDateModalOpen(true)}> - - Change due date - - setIsNFCModalOpen(true)}> - - Write to NFC - - - - Edit - - - - Clone - - - - View - - - {chore.isActive ? : } - {chore.isActive ? 'Archive' : 'Unarchive'} - - - - - Delete - - {/* All modals (same as original) */} diff --git a/src/views/Settings/MFASettings.jsx b/src/views/Settings/MFASettings.jsx index 1089fc0..032ab5f 100644 --- a/src/views/Settings/MFASettings.jsx +++ b/src/views/Settings/MFASettings.jsx @@ -1,4 +1,4 @@ -import { CheckCircle, Key, Security, Smartphone } from '@mui/icons-material' +import { CheckCircle, Security, Smartphone } from '@mui/icons-material' import { Alert, Box, @@ -264,7 +264,7 @@ const MFASettings = () => { - + {/* {mfaEnabled && ( @@ -288,7 +288,7 @@ const MFASettings = () => { - )} + )} */} {/* Setup MFA Modal */} diff --git a/src/views/components/CalendarView.jsx b/src/views/components/CalendarView.jsx index 6f34e7f..286e388 100644 --- a/src/views/components/CalendarView.jsx +++ b/src/views/components/CalendarView.jsx @@ -122,7 +122,18 @@ const CalendarView = ({ chores }) => { } /> {!selectedDate && ( - + {/* Show legend with current user first, then other circle members who have assignments */} {(() => { const assignedUserIds = new Set( @@ -171,7 +182,7 @@ const CalendarView = ({ chores }) => { backgroundColor: item.color, }} /> - + {item.name} diff --git a/src/views/components/ChoreActionMenu.jsx b/src/views/components/ChoreActionMenu.jsx new file mode 100644 index 0000000..aecf099 --- /dev/null +++ b/src/views/components/ChoreActionMenu.jsx @@ -0,0 +1,303 @@ +import { + Archive, + CopyAll, + Delete, + Edit, + ManageSearch, + MoreTime, + MoreVert, + Nfc, + NoteAdd, + RecordVoiceOver, + SwitchAccessShortcut, + Unarchive, + Update, + ViewCarousel, +} from '@mui/icons-material' +import { Divider, IconButton, Menu, MenuItem } from '@mui/joy' +import React, { useEffect } from 'react' +import { useNavigate } from 'react-router-dom' +import { useError } from '../../service/ErrorProvider' +import { + ArchiveChore, + DeleteChore, + SkipChore, + UnArchiveChore, +} from '../../utils/Fetcher' + +const ChoreActionMenu = ({ + chore, + onChoreUpdate, + onChoreRemove, + onCompleteWithNote, + onCompleteWithPastDate, + onChangeAssignee, + onChangeDueDate, + onWriteNFC, + onDelete, + sx = {}, + variant = 'soft', +}) => { + const [anchorEl, setAnchorEl] = React.useState(null) + const menuRef = React.useRef(null) + const navigate = useNavigate() + const { showError } = useError() + + useEffect(() => { + const handleMenuOutsideClick = event => { + if ( + anchorEl && + !anchorEl.contains(event.target) && + !menuRef.current.contains(event.target) + ) { + handleMenuClose() + } + } + + document.addEventListener('mousedown', handleMenuOutsideClick) + return () => { + document.removeEventListener('mousedown', handleMenuOutsideClick) + } + }, [anchorEl]) + + const handleMenuOpen = event => { + event.stopPropagation() + setAnchorEl(event.currentTarget) + } + + const handleMenuClose = () => { + setAnchorEl(null) + } + + const handleEdit = () => { + navigate(`/chores/${chore.id}/edit`) + handleMenuClose() + } + + const handleClone = () => { + navigate(`/chores/${chore.id}/edit?clone=true`) + handleMenuClose() + } + + const handleView = () => { + navigate(`/chores/${chore.id}`) + handleMenuClose() + } + + const handleDelete = () => { + if (onDelete) { + onDelete() + } else { + // Default delete behavior + DeleteChore(chore.id).then(response => { + if (response.ok) { + onChoreRemove?.(chore) + } + }) + } + handleMenuClose() + } + + const handleArchive = () => { + if (chore.isActive) { + ArchiveChore(chore.id).then(response => { + if (response.ok) { + response.json().then(() => { + const newChore = { ...chore, isActive: false } + onChoreUpdate?.(newChore, 'archive') + }) + } + }) + } else { + UnArchiveChore(chore.id).then(response => { + if (response.ok) { + response.json().then(() => { + const newChore = { ...chore, isActive: true } + onChoreUpdate?.(newChore, 'unarchive') + }) + } + }) + } + handleMenuClose() + } + + const handleSkip = () => { + SkipChore(chore.id) + .then(response => { + if (response.ok) { + response.json().then(data => { + const newChore = data.res + onChoreUpdate?.(newChore, 'skipped') + handleMenuClose() + }) + } + }) + .catch(error => { + if (error?.queued) { + showError({ + title: 'Failed to update', + message: 'Request will be processed when you are online', + }) + } else { + showError({ + title: 'Failed to update', + message: error, + }) + } + }) + } + + const handleHistory = () => { + navigate(`/chores/${chore.id}/history`) + handleMenuClose() + } + + return ( + <> + + + + + + { + e.stopPropagation() + onCompleteWithNote?.() + handleMenuClose() + }} + > + + Complete with note + + { + e.stopPropagation() + onCompleteWithPastDate?.() + handleMenuClose() + }} + > + + Complete in past + + { + e.stopPropagation() + handleSkip() + }} + > + + Skip to next due date + + { + e.stopPropagation() + onChangeAssignee?.() + handleMenuClose() + }} + > + + Delegate to someone else + + + { + e.stopPropagation() + handleHistory() + }} + > + + History + + + { + e.stopPropagation() + onChangeDueDate?.() + handleMenuClose() + }} + > + + Change due date + + { + e.stopPropagation() + onWriteNFC?.() + handleMenuClose() + }} + > + + Write to NFC + + { + e.stopPropagation() + handleEdit() + }} + > + + Edit + + { + e.stopPropagation() + handleClone() + }} + > + + Clone + + { + e.stopPropagation() + handleView() + }} + > + + View + + { + e.stopPropagation() + handleArchive() + }} + color='neutral' + > + {chore.isActive ? : } + {chore.isActive ? 'Archive' : 'Unarchive'} + + + { + e.stopPropagation() + handleDelete() + }} + color='danger' + > + + Delete + + + + ) +} + +export default ChoreActionMenu