From 2657469964e24ffbeb905024532120395f6e797c Mon Sep 17 00:00:00 2001 From: Mo Tarbin Date: Sun, 30 Jun 2024 18:55:39 -0400 Subject: move to Donetick Org, First commit frontend --- src/views/Chores/ChoreCard.jsx | 578 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 578 insertions(+) create mode 100644 src/views/Chores/ChoreCard.jsx (limited to 'src/views/Chores/ChoreCard.jsx') diff --git a/src/views/Chores/ChoreCard.jsx b/src/views/Chores/ChoreCard.jsx new file mode 100644 index 0000000..0b2a408 --- /dev/null +++ b/src/views/Chores/ChoreCard.jsx @@ -0,0 +1,578 @@ +import { + Check, + Delete, + Edit, + HowToReg, + KeyboardDoubleArrowUp, + LocalOffer, + ManageSearch, + MoreTime, + MoreVert, + NoteAdd, + RecordVoiceOver, + Repeat, + Report, + SwitchAccessShortcut, + TimesOneMobiledata, + Update, + Webhook, +} from '@mui/icons-material' +import { + Avatar, + Box, + Card, + Chip, + CircularProgress, + Divider, + Grid, + IconButton, + Menu, + MenuItem, + 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 { 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' +const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => { + 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 [anchorEl, setAnchorEl] = React.useState(null) + const menuRef = React.useRef(null) + const navigate = useNavigate() + const [isDisabled, setIsDisabled] = React.useState(false) + + // 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 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 handleCompleteChore = () => { + Fetch(`${API_URL}/chores/${chore.id}/do`, { + method: 'POST', + }).then(response => { + if (response.ok) { + response.json().then(data => { + const newChore = data.res + onChoreUpdate(newChore, 'completed') + }) + } + }) + setIsDisabled(true) + setTimeout(() => setIsDisabled(false), 5000) // Re-enable the button after 5 seconds + } + 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 => { + // TODO: Implement assignee change + } + 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 === '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 + } + } + + return ( + <> + + {getDueDateChipText(chore.nextDueDate)} + + + +
+ {getFrequencyIcon(chore)} + + {getRecurrentChipText(chore)} +
+
+ + + + + {/* Box in top right with Chip showing next due date */} + + + {chore.name.charAt(0).toUpperCase()} + + + {chore.name} + + Assigned to{' '} + + { + performers.find(p => p.id === chore.assignedTo) + ?.displayName + } + + + + {chore.labels?.split(',').map(label => ( + + {label} + + ))} + + + + {/* + + {chore.nextDueDate === null + ? '--' + : 'Due ' + moment(chore.nextDueDate).fromNow()} + + */} + + + + {/* */} + +
+ + {isDisabled && ( + + )} +
+
+ + + + {/*
*/} + + { + setIsCompleteWithNoteModalOpen(true) + }} + > + + Complete with note + + { + setIsCompleteWithPastDateModalOpen(true) + }} + > + + Complete in past + + { + Fetch(`${API_URL}/chores/${chore.id}/skip`, { + 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, 'skipped') + handleMenuClose() + }) + } + }) + }} + > + + Skip to next due date + + { + setIsChangeAssigneeModalOpen(true) + }} + > + + Delegate to someone else + + + + Complete as someone else + + + { + navigate(`/chores/${chore.id}/history`) + }} + > + + History + + + { + setIsChangeDueDateModalOpen(true) + }} + > + + Change due date + + + + Edit + + + + Delete + + +
+
+
+ { + setIsChangeDueDateModalOpen(false) + }} + onSave={handleChangeDueDate} + /> + { + setIsCompleteWithPastDateModalOpen(false) + }} + onSave={handleCompleteWithPastDate} + /> + { + setIsChangeAssigneeModalOpen(false) + }} + onSave={handleAssigneChange} + /> + + { + setIsCompleteWithNoteModalOpen(false) + }} + okText={'Complete'} + onSave={handleCompleteWithNote} + /> +
+ + ) +} + +export default ChoreCard -- cgit