import { Archive, ArrowBack, Cancel, CopyAll, Delete, DriveFileMove, Edit, ManageSearch, MoreTime, MoreVert, NextWeek, Nfc, NoteAdd, Notifications, RecordVoiceOver, SwitchAccessShortcut, Today, Unarchive, Update, ViewCarousel, WbSunny, Weekend, } from '@mui/icons-material' import { Avatar, Divider, IconButton, ListItemContent, ListItemDecorator, Menu, MenuItem, Tooltip, Typography, } from '@mui/joy' import React, { useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' import LABEL_COLORS, { getTextColorFromBackgroundColor, } from '../../utils/Colors' import { isOfficialDonetickInstanceSync } from '../../utils/FeatureToggle' import { getIconComponent } from '../../utils/ProjectIcons' import { useProjects } from '../Projects/ProjectQueries' const ChoreActionMenu = ({ chore, onAction, onCompleteWithNote, onCompleteWithPastDate, onChangeAssignee, onChangeDueDate, onWriteNFC, onNudge, onDelete, onOpen, onMouseEnter, onMouseLeave, sx = {}, variant = 'soft', }) => { const [anchorEl, setAnchorEl] = React.useState(null) const [isOfficialInstance, setIsOfficialInstance] = useState(false) const [showProjectPicker, setShowProjectPicker] = useState(false) const menuRef = React.useRef(null) const navigate = useNavigate() const { data: projects = [] } = useProjects() useEffect(() => { try { setIsOfficialInstance(isOfficialDonetickInstanceSync()) } catch (error) { console.warn('Error checking instance type:', error) setIsOfficialInstance(false) } }, []) useEffect(() => { const handleMenuOutsideClick = event => { if ( anchorEl && !anchorEl.contains(event.target) && !menuRef.current.contains(event.target) ) { handleMenuClose() } } document.addEventListener('mousedown', handleMenuOutsideClick) if (anchorEl && onOpen) { onOpen() } return () => { document.removeEventListener('mousedown', handleMenuOutsideClick) } }, [anchorEl, onOpen]) const handleMenuOpen = event => { event.stopPropagation() setAnchorEl(event.currentTarget) } const handleMenuClose = () => { setAnchorEl(null) setShowProjectPicker(false) } const handleMoveToProject = project => { onAction?.('moveToProject', chore, { project }) handleMenuClose() } 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 { onAction?.('delete', chore) } handleMenuClose() } const handleArchive = () => { if (chore.isActive) { onAction?.('archive', chore) } else { onAction?.('unarchive', chore) } handleMenuClose() } const handleSkip = () => { onAction?.('skip', chore) handleMenuClose() } const handleHistory = () => { navigate(`/chores/${chore.id}/history`) handleMenuClose() } const getQuickScheduleDate = option => { const now = new Date() const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()) switch (option) { case 'today': { const nowHour = now.getHours() const scheduled = new Date(today) if (nowHour < 9) { scheduled.setHours(9, 0, 0, 0) } else if (nowHour < 12) { scheduled.setHours(12, 0, 0, 0) } else if (nowHour < 17) { scheduled.setHours(17, 0, 0, 0) } else { scheduled.setHours( now.getHours(), now.getMinutes(), now.getSeconds(), now.getMilliseconds(), ) } return scheduled } case 'tomorrow-morning': { const tomorrowMorning = new Date(today) tomorrowMorning.setDate(today.getDate() + 1) tomorrowMorning.setHours(9, 0, 0, 0) return tomorrowMorning } case 'tomorrow': { const tomorrow = new Date(today) tomorrow.setDate(today.getDate() + 1) tomorrow.setHours(12, 0, 0, 0) return tomorrow } case 'tomorrow-afternoon': { const tomorrowAfternoon = new Date(today) tomorrowAfternoon.setDate(today.getDate() + 1) tomorrowAfternoon.setHours(14, 0, 0, 0) return tomorrowAfternoon } case 'weekend': { const weekend = new Date(today) const daysUntilSaturday = (6 - today.getDay() + 7) % 7 || 7 weekend.setDate(today.getDate() + daysUntilSaturday) return weekend } case 'next-week': { const nextWeek = new Date(today) const daysUntilMonday = (1 - today.getDay() + 7) % 7 || 7 nextWeek.setDate(today.getDate() + daysUntilMonday) return nextWeek } default: return today } } const handleQuickSchedule = option => { const date = option === 'remove' ? null : getQuickScheduleDate(option) onAction?.('changeDueDate', chore, { date }) handleMenuClose() } const renderProjectAvatar = (color, icon) => { const bg = color || LABEL_COLORS[0].value const IconComponent = getIconComponent(icon || 'FolderOpen') return ( ) } return ( <> {showProjectPicker ? ( <> { e.stopPropagation() setShowProjectPicker(false) }} sx={{ gap: 1 }} > Move to project { e.stopPropagation() handleMoveToProject({ id: null, name: 'Default Project' }) }} > {renderProjectAvatar(LABEL_COLORS[0].value, 'FolderOpen')} Default Project {projects.map(project => ( { e.stopPropagation() handleMoveToProject(project) }} > {renderProjectAvatar(project.color, project.icon)} {project.name} ))} ) : ( <> { 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 {isOfficialInstance && ( { e.stopPropagation() onNudge?.() handleMenuClose() }} > Send nudge )} { e.stopPropagation() handleHistory() }} > History e.stopPropagation()} > { e.stopPropagation() handleQuickSchedule('today') }} > { e.stopPropagation() handleQuickSchedule('tomorrow') }} > { e.stopPropagation() handleQuickSchedule('weekend') }} > { e.stopPropagation() handleQuickSchedule('next-week') }} > { e.stopPropagation() handleQuickSchedule('remove') }} > { 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'} {projects.length > 0 && ( { e.stopPropagation() setShowProjectPicker(true) }} > Move to project )} { e.stopPropagation() handleDelete() }} color='danger' > Delete )} ) } export default ChoreActionMenu