refactor: Consolidate chore action handling into ChoreActionMenu component and remove unused code from ChoreCard and CompactChoreCard
This commit is contained in:
@@ -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({})
|
||||
|
||||
@@ -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 <KeyboardDoubleArrowUp />
|
||||
case 'important':
|
||||
return <Report />
|
||||
default:
|
||||
return <LocalOffer />
|
||||
}
|
||||
}
|
||||
const getPriorityIcon = priority => {
|
||||
switch (Number(priority)) {
|
||||
case 1:
|
||||
return <PriorityHigh />
|
||||
case 2:
|
||||
return <KeyboardDoubleArrowUp />
|
||||
case 3:
|
||||
return <KeyboardControlKey />
|
||||
default:
|
||||
return <HorizontalRule />
|
||||
}
|
||||
}
|
||||
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 = ({
|
||||
)}
|
||||
</div>
|
||||
</IconButton>
|
||||
<IconButton
|
||||
// sx={{ width: 15 }}
|
||||
variant='soft'
|
||||
color='success'
|
||||
onClick={handleMenuOpen}
|
||||
sx={{
|
||||
borderRadius: '50%',
|
||||
width: 25,
|
||||
height: 25,
|
||||
position: 'relative',
|
||||
left: -10,
|
||||
}}
|
||||
>
|
||||
<MoreVert />
|
||||
</IconButton>
|
||||
{/* </ButtonGroup> */}
|
||||
<Menu
|
||||
size='lg'
|
||||
ref={menuRef}
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={handleMenuClose}
|
||||
>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setIsCompleteWithNoteModalOpen(true)
|
||||
}}
|
||||
>
|
||||
<NoteAdd />
|
||||
Complete with note
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setIsCompleteWithPastDateModalOpen(true)
|
||||
}}
|
||||
>
|
||||
<Update />
|
||||
Complete in past
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
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,
|
||||
})
|
||||
}
|
||||
})
|
||||
}}
|
||||
>
|
||||
<SwitchAccessShortcut />
|
||||
Skip to next due date
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setIsChangeAssigneeModalOpen(true)
|
||||
}}
|
||||
>
|
||||
<RecordVoiceOver />
|
||||
Delegate to someone else
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
navigate(`/chores/${chore.id}/history`)
|
||||
}}
|
||||
>
|
||||
<ManageSearch />
|
||||
History
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setIsChangeDueDateModalOpen(true)
|
||||
}}
|
||||
>
|
||||
<MoreTime />
|
||||
Change due date
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
// write current chore URL to NFC
|
||||
// writeToNFC(`${window.location.origin}/chores/${chore.id}`)
|
||||
setIsNFCModalOpen(true)
|
||||
}}
|
||||
>
|
||||
<Nfc />
|
||||
Write to NFC
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleEdit}>
|
||||
<Edit />
|
||||
Edit
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleClone}>
|
||||
<CopyAll />
|
||||
Clone
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleView}>
|
||||
<ViewCarousel />
|
||||
View
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleArchive} color='neutral'>
|
||||
{chore.isActive ? <Archive /> : <Unarchive />}
|
||||
{chore.isActive ? 'Archive' : 'Unarchive'}
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
|
||||
<MenuItem onClick={handleDelete} color='danger'>
|
||||
<Delete />
|
||||
Delete
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<ChoreActionMenu
|
||||
chore={chore}
|
||||
onChoreUpdate={onChoreUpdate}
|
||||
onChoreRemove={onChoreRemove}
|
||||
onCompleteWithNote={() => setIsCompleteWithNoteModalOpen(true)}
|
||||
onCompleteWithPastDate={() =>
|
||||
setIsCompleteWithPastDateModalOpen(true)
|
||||
}
|
||||
onChangeAssignee={() => setIsChangeAssigneeModalOpen(true)}
|
||||
onChangeDueDate={() => setIsChangeDueDateModalOpen(true)}
|
||||
onWriteNFC={() => setIsNFCModalOpen(true)}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -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 = ({
|
||||
)}
|
||||
</IconButton>
|
||||
|
||||
{/* Menu Button */}
|
||||
<IconButton
|
||||
{/* Chore Action Menu */}
|
||||
<ChoreActionMenu
|
||||
variant='plain'
|
||||
color='neutral'
|
||||
size='sm'
|
||||
onClick={e => {
|
||||
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,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<MoreVert sx={{ fontSize: 14 }} />
|
||||
</IconButton>
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Menu */}
|
||||
<Menu
|
||||
ref={menuRef}
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={handleMenuClose}
|
||||
>
|
||||
<MenuItem onClick={() => setIsCompleteWithNoteModalOpen(true)}>
|
||||
<NoteAdd />
|
||||
Complete with note
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => setIsCompleteWithPastDateModalOpen(true)}>
|
||||
<Update />
|
||||
Complete in past
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
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,
|
||||
})
|
||||
}
|
||||
})
|
||||
}}
|
||||
>
|
||||
<SwitchAccessShortcut />
|
||||
Skip to next due date
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => setIsChangeAssigneeModalOpen(true)}>
|
||||
<RecordVoiceOver />
|
||||
Delegate to someone else
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem onClick={() => navigate(`/chores/${chore.id}/history`)}>
|
||||
<ManageSearch />
|
||||
History
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem onClick={() => setIsChangeDueDateModalOpen(true)}>
|
||||
<MoreTime />
|
||||
Change due date
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => setIsNFCModalOpen(true)}>
|
||||
<Nfc />
|
||||
Write to NFC
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleEdit}>
|
||||
<Edit />
|
||||
Edit
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleClone}>
|
||||
<CopyAll />
|
||||
Clone
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleView}>
|
||||
<ViewCarousel />
|
||||
View
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleArchive} color='neutral'>
|
||||
{chore.isActive ? <Archive /> : <Unarchive />}
|
||||
{chore.isActive ? 'Archive' : 'Unarchive'}
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem onClick={handleDelete} color='danger'>
|
||||
<Delete />
|
||||
Delete
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</Box>
|
||||
|
||||
{/* All modals (same as original) */}
|
||||
|
||||
@@ -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 = () => {
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
|
||||
{/*
|
||||
{mfaEnabled && (
|
||||
<Card variant='outlined'>
|
||||
<Box className='flex items-center justify-between'>
|
||||
@@ -288,7 +288,7 @@ const MFASettings = () => {
|
||||
</Button>
|
||||
</Box>
|
||||
</Card>
|
||||
)}
|
||||
)} */}
|
||||
|
||||
{/* Setup MFA Modal */}
|
||||
<Modal open={setupModalOpen} onClose={closeSetupModal}>
|
||||
|
||||
@@ -122,7 +122,18 @@ const CalendarView = ({ chores }) => {
|
||||
}
|
||||
/>
|
||||
{!selectedDate && (
|
||||
<Grid container ml={-3} mt={1}>
|
||||
<Grid
|
||||
container
|
||||
ml={3}
|
||||
mt={1}
|
||||
// start from left:
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
// alignItems: 'center',
|
||||
justifyContent: 'start',
|
||||
}}
|
||||
>
|
||||
{/* 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,
|
||||
}}
|
||||
/>
|
||||
<Typography level='body-xs' ml={0.3}>
|
||||
<Typography level='body-xs' ml={0.5}>
|
||||
{item.name}
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
303
src/views/components/ChoreActionMenu.jsx
Normal file
303
src/views/components/ChoreActionMenu.jsx
Normal file
@@ -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 (
|
||||
<>
|
||||
<IconButton
|
||||
variant={variant}
|
||||
color='success'
|
||||
onClick={handleMenuOpen}
|
||||
sx={{
|
||||
borderRadius: '50%',
|
||||
width: 25,
|
||||
height: 25,
|
||||
position: 'relative',
|
||||
left: -10,
|
||||
...sx,
|
||||
}}
|
||||
>
|
||||
<MoreVert />
|
||||
</IconButton>
|
||||
|
||||
<Menu
|
||||
size='lg'
|
||||
ref={menuRef}
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={handleMenuClose}
|
||||
>
|
||||
<MenuItem
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
onCompleteWithNote?.()
|
||||
handleMenuClose()
|
||||
}}
|
||||
>
|
||||
<NoteAdd />
|
||||
Complete with note
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
onCompleteWithPastDate?.()
|
||||
handleMenuClose()
|
||||
}}
|
||||
>
|
||||
<Update />
|
||||
Complete in past
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
handleSkip()
|
||||
}}
|
||||
>
|
||||
<SwitchAccessShortcut />
|
||||
Skip to next due date
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
onChangeAssignee?.()
|
||||
handleMenuClose()
|
||||
}}
|
||||
>
|
||||
<RecordVoiceOver />
|
||||
Delegate to someone else
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
handleHistory()
|
||||
}}
|
||||
>
|
||||
<ManageSearch />
|
||||
History
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
onChangeDueDate?.()
|
||||
handleMenuClose()
|
||||
}}
|
||||
>
|
||||
<MoreTime />
|
||||
Change due date
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
onWriteNFC?.()
|
||||
handleMenuClose()
|
||||
}}
|
||||
>
|
||||
<Nfc />
|
||||
Write to NFC
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
handleEdit()
|
||||
}}
|
||||
>
|
||||
<Edit />
|
||||
Edit
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
handleClone()
|
||||
}}
|
||||
>
|
||||
<CopyAll />
|
||||
Clone
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
handleView()
|
||||
}}
|
||||
>
|
||||
<ViewCarousel />
|
||||
View
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
handleArchive()
|
||||
}}
|
||||
color='neutral'
|
||||
>
|
||||
{chore.isActive ? <Archive /> : <Unarchive />}
|
||||
{chore.isActive ? 'Archive' : 'Unarchive'}
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
handleDelete()
|
||||
}}
|
||||
color='danger'
|
||||
>
|
||||
<Delete />
|
||||
Delete
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChoreActionMenu
|
||||
Reference in New Issue
Block a user