import { CancelScheduleSend, Check, Delete, Edit, HowToReg, KeyboardDoubleArrowUp, LocalOffer, ManageSearch, MoreTime, MoreVert, Nfc, NoteAdd, RecordVoiceOver, Repeat, Report, SwitchAccessShortcut, TimesOneMobiledata, Update, ViewCarousel, Webhook, } from '@mui/icons-material' import { Avatar, Box, Button, Card, Chip, CircularProgress, Divider, Grid, IconButton, Menu, MenuItem, Snackbar, Typography, } from '@mui/joy' import moment from 'moment' import React, { useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { API_URL } from '../../Config' import { MarkChoreComplete, SkipChore, UpdateChoreAssignee } from '../../utils/Fetcher' import { Fetch } from '../../utils/TokenManager' import ConfirmationModal from '../Modals/Inputs/ConfirmationModal' import DateModal from '../Modals/Inputs/DateModal' import SelectModal from '../Modals/Inputs/SelectModal' import TextModal from '../Modals/Inputs/TextModal' import WriteNFCModal from '../Modals/Inputs/WriteNFCModal' const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx, viewOnly, }) => { const [activeUserId, setActiveUserId] = React.useState(0) const [isChangeDueDateModalOpen, setIsChangeDueDateModalOpen] = React.useState(false) const [isCompleteWithPastDateModalOpen, setIsCompleteWithPastDateModalOpen] = React.useState(false) const [isChangeAssigneeModalOpen, setIsChangeAssigneeModalOpen] = React.useState(false) const [isCompleteWithNoteModalOpen, setIsCompleteWithNoteModalOpen] = 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) const [timeoutId, setTimeoutId] = React.useState(null) // useEffect(() => { // GetAllUsers() // .then(response => response.json()) // .then(data => { // setPerformers(data.res) // }) // }, []) 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 handleView = () => { navigate(`/chores/${chore.id}`) } const handleDelete = () => { setConfirmModelConfig({ isOpen: true, title: 'Delete Chore', confirmText: 'Delete', cancelText: 'Cancel', message: 'Are you sure you want to delete this chore?', onClose: isConfirmed => { console.log('isConfirmed', isConfirmed) if (isConfirmed === true) { Fetch(`${API_URL}/chores/${chore.id}`, { method: 'DELETE', }).then(response => { if (response.ok) { onChoreRemove(chore) } }) } setConfirmModelConfig({}) }, }) } const handleTaskCompletion = () => { setIsPendingCompletion(true) let seconds = 3 // Starting countdown from 3 seconds setSecondsLeftToCancel(seconds) const countdownInterval = setInterval(() => { seconds -= 1 setSecondsLeftToCancel(seconds) if (seconds <= 0) { clearInterval(countdownInterval) // Stop the countdown when it reaches 0 } }, 1000) const id = setTimeout(() => { MarkChoreComplete(chore.id) .then(resp => { if (resp.ok) { return resp.json().then(data => { onChoreUpdate(data.res, 'completed') }) } }) .then(() => { setIsPendingCompletion(false) clearTimeout(id) clearInterval(countdownInterval) // Ensure to clear this interval as well setTimeoutId(null) setSecondsLeftToCancel(null) }) }, 3000) setTimeoutId(id) } const handleChangeDueDate = newDate => { if (activeUserId === null) { alert('Please select a performer') return } Fetch(`${API_URL}/chores/${chore.id}/dueDate`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ dueDate: newDate ? new Date(newDate).toISOString() : null, UpdatedBy: activeUserId, }), }).then(response => { if (response.ok) { response.json().then(data => { const newChore = data.res onChoreUpdate(newChore, 'rescheduled') }) } }) } const handleCompleteWithPastDate = newDate => { if (activeUserId === null) { alert('Please select a performer') return } Fetch( `${API_URL}/chores/${chore.id}/do?completedDate=${new Date( newDate, ).toISOString()}`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({}), }, ).then(response => { if (response.ok) { response.json().then(data => { const newChore = data.res onChoreUpdate(newChore, 'completed') }) } }) } const handleAssigneChange = assigneeId => { UpdateChoreAssignee(chore.id, assigneeId).then(response => { if (response.ok) { response.json().then(data => { const newChore = data.res onChoreUpdate(newChore, 'assigned') }) } } ) } const handleCompleteWithNote = note => { Fetch(`${API_URL}/chores/${chore.id}/do`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ note: note, }), }).then(response => { if (response.ok) { response.json().then(data => { const newChore = data.res onChoreUpdate(newChore, 'completed') }) } }) } const getDueDateChipText = nextDueDate => { if (chore.nextDueDate === null) return 'No Due Date' // if due in next 48 hours, we should it in this format : Tomorrow 11:00 AM const diff = moment(nextDueDate).diff(moment(), 'hours') if (diff < 48 && diff > 0) { return moment(nextDueDate).calendar().replace(' at', '') } return 'Due ' + moment(nextDueDate).fromNow() } const getDueDateChipColor = nextDueDate => { if (chore.nextDueDate === null) return 'neutral' const diff = moment(nextDueDate).diff(moment(), 'hours') if (diff < 48 && diff > 0) { return 'warning' } if (diff < 0) { return 'danger' } return 'neutral' } const getIconForLabel = label => { if (!label || label.trim() === '') return <>> switch (String(label).toLowerCase()) { case 'high': return case 'important': return default: return } } const getRecurrentChipText = chore => { const dayOfMonthSuffix = n => { if (n >= 11 && n <= 13) { return 'th' } switch (n % 10) { case 1: return 'st' case 2: return 'nd' case 3: return 'rd' default: return 'th' } } if (chore.frequencyType === 'once') { return 'Once' } else if (chore.frequencyType === 'trigger') { return 'Trigger' } else if (chore.frequencyType === 'daily') { return 'Daily' } else if (chore.frequencyType === 'adaptive') { return 'Adaptive' } else if (chore.frequencyType === 'weekly') { return 'Weekly' } else if (chore.frequencyType === 'monthly') { return 'Monthly' } else if (chore.frequencyType === 'yearly') { return 'Yearly' } else if (chore.frequencyType === 'days_of_the_week') { let days = JSON.parse(chore.frequencyMetadata).days days = days.map(d => moment().day(d).format('ddd')) return days.join(', ') } else if (chore.frequencyType === 'day_of_the_month') { let freqData = JSON.parse(chore.frequencyMetadata) const months = freqData.months.map(m => moment().month(m).format('MMM')) return `${chore.frequency}${dayOfMonthSuffix( chore.frequency, )} of ${months.join(', ')}` } else if (chore.frequencyType === 'interval') { return `Every ${chore.frequency} ${ JSON.parse(chore.frequencyMetadata).unit }` } else { return chore.frequencyType } } const getFrequencyIcon = chore => { if (['once', 'no_repeat'].includes(chore.frequencyType)) { return } else if (chore.frequencyType === 'trigger') { return } else { return } } const getName = name => { const split = Array.from(chore.name) // if the first character is emoji then remove it from the name if (/\p{Emoji}/u.test(split[0])) { return split.slice(1).join('').trim() } return name } return ( <> {getDueDateChipText(chore.nextDueDate)} {getFrequencyIcon(chore)} {getRecurrentChipText(chore)} { navigate(`/chores/${chore.id}`) }} > {/* Box in top right with Chip showing next due date */} {Array.from(chore.name)[0]} {getName(chore.name)} Assigned to{' '} { performers.find(p => p.id === chore.assignedTo) ?.displayName } {chore.labels?.split(',').map((label, index) => ( {label} ))} {/* {chore.nextDueDate === null ? '--' : 'Due ' + moment(chore.nextDueDate).fromNow()} */} {/* */} {isPendingCompletion && ( )} {/* */} { 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() }) } }) }} > 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 View Delete { setIsChangeDueDateModalOpen(false) }} onSave={handleChangeDueDate} /> { setIsCompleteWithPastDateModalOpen(false) }} onSave={handleCompleteWithPastDate} /> { setIsChangeAssigneeModalOpen(false) }} onSave={(selected)=>{ handleAssigneChange(selected.id) } } /> { setIsCompleteWithNoteModalOpen(false) }} okText={'Complete'} onSave={handleCompleteWithNote} /> { setIsNFCModalOpen(false) }, }} /> { if (timeoutId) { clearTimeout(timeoutId) setIsPendingCompletion(false) setTimeoutId(null) setSecondsLeftToCancel(null) // Reset or adjust as needed } }} size='md' variant='outlined' color='primary' startDecorator={} > Cancel } > Task will be marked as completed in {secondsLeftToCancel} seconds > ) } export default ChoreCard