Improve MarkChoreComplete, allow complete in past ,etc

This commit is contained in:
Mo Tarbin 2024-07-07 00:25:11 -04:00
parent df83cc8948
commit e039b732a8
3 changed files with 228 additions and 92 deletions

View file

@ -57,11 +57,20 @@ const GetChoreDetailById = id => {
headers: HEADERS(),
})
}
const MarkChoreComplete = id => {
return Fetch(`${API_URL}/chores/${id}/do`, {
const MarkChoreComplete = (id, note, completedDate) => {
const body = {
note,
}
let completedDateFormated = ''
if (completedDate) {
completedDateFormated = `?completedDate=${new Date(
completedDate,
).toISOString()}`
}
return Fetch(`${API_URL}/chores/${id}/do${completedDateFormated}`, {
method: 'POST',
headers: HEADERS(),
body: JSON.stringify(body),
})
}
const CreateChore = chore => {

View file

@ -3,14 +3,19 @@ import {
CancelScheduleSend,
Check,
Checklist,
Note,
PeopleAlt,
Person,
} from '@mui/icons-material'
import {
Box,
Button,
Checkbox,
Container,
Divider,
FormControl,
Grid,
Input,
ListItem,
ListItemContent,
ListItemDecorator,
@ -21,7 +26,7 @@ import {
} from '@mui/joy'
import moment from 'moment'
import { useEffect, useState } from 'react'
import { useParams, useSearchParams } from 'react-router-dom'
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
import {
GetAllUsers,
GetChoreDetailById,
@ -43,14 +48,16 @@ const ChoreView = () => {
const [performers, setPerformers] = useState([])
const [infoCards, setInfoCards] = useState([])
const { choreId } = useParams()
const [note, setNote] = useState(null)
// query param `complete=true`
const Navigate = useNavigate()
const [searchParams] = useSearchParams()
const [isPendingCompletion, setIsPendingCompletion] = useState(false)
const [timeoutId, setTimeoutId] = useState(null)
const [secondsLeftToCancel, setSecondsLeftToCancel] = useState(null)
const [completedDate, setCompletedDate] = useState(null)
useEffect(() => {
Promise.all([
GetChoreDetailById(choreId).then(resp => {
@ -82,7 +89,7 @@ const ChoreView = () => {
{
icon: <CalendarMonth />,
text: 'Due Date',
subtext: moment(chore.dueDate).format('MM/DD/YYYY hh:mm A'),
subtext: moment(chore.nextDueDate).format('MM/DD/YYYY hh:mm A'),
},
{
icon: <PeopleAlt />,
@ -119,12 +126,21 @@ const ChoreView = () => {
subtext: chore.lastCompletedDate
? `${
chore.lastCompletedDate &&
moment(chore.lastCompletedDate).format('MM/DD/YYYY hh:mm A')
}(${
performers.find(p => p.id === chore.lastCompletedBy)?.displayName
})`
moment(chore.lastCompletedDate).fromNow()
// moment(chore.lastCompletedDate).format('MM/DD/YYYY hh:mm A'))
}(
${
performers.find(p => p.id === chore.lastCompletedBy)
?.displayName
}
)`
: 'Never',
},
{
icon: <Note />,
text: 'Recent Note',
subtext: chore.notes || '--',
},
]
setInfoCards(cards)
}
@ -143,10 +159,11 @@ const ChoreView = () => {
}, 1000)
const id = setTimeout(() => {
MarkChoreComplete(choreId)
MarkChoreComplete(choreId, note, completedDate)
.then(resp => {
if (resp.ok) {
return resp.json().then(data => {
setNote(null)
setChore(data.res)
})
}
@ -174,22 +191,18 @@ const ChoreView = () => {
}
return (
<Container maxWidth='sm'>
<Sheet
variant='plain'
<Container
maxWidth='sm'
sx={{
borderRadius: 'sm',
p: 2,
boxShadow: 'md',
minHeight: '90vh',
display: 'flex',
flexDirection: 'column',
// space between :
justifyContent: 'space-between',
}}
>
<Box>
<Typography
level='h4'
level='h3'
textAlign={'center'}
sx={{
mt: 2,
@ -202,9 +215,7 @@ const ChoreView = () => {
<Grid container spacing={1}>
{infoCards.map((info, index) => (
<Grid key={index} item xs={12} sm={6}>
<Sheet
sx={{ mb: 1, borderRadius: 'md', p: 1, boxShadow: 'sm' }}
>
<Sheet sx={{ mb: 1, borderRadius: 'md', p: 1, boxShadow: 'sm' }}>
<ListItem>
<ListItemDecorator>
<IconCard>{info.icon}</IconCard>
@ -223,11 +234,72 @@ const ChoreView = () => {
))}
</Grid>
</Box>
<Divider
sx={{
my: 2,
}}
/>
<Box>
<Typography level='title-md'>Additional Notes</Typography>
<Input
fullWidth
multiline
label='Additional Notes'
placeholder='note or information about the task'
value={note || ''}
onChange={e => {
if (e.target.value.trim() === '') {
setNote(null)
return
}
setNote(e.target.value)
}}
size='md'
sx={{
my: 1,
}}
/>
<FormControl size='sm' sx={{ width: 400 }}>
<Checkbox
defaultChecked={completedDate !== null}
checked={completedDate !== null}
value={completedDate !== null}
onChange={e => {
if (e.target.checked) {
setCompletedDate(
moment(new Date()).format('YYYY-MM-DDTHH:00:00'),
)
} else {
setCompletedDate(null)
}
}}
overlay
sx={{
my: 1,
}}
label={<Typography level='body2'>Set completion date</Typography>}
/>
</FormControl>
{completedDate !== null && (
<Input
sx={{ mt: 1, mb: 1.5, width: 300 }}
type='datetime-local'
value={completedDate}
onChange={e => {
setCompletedDate(e.target.value)
}}
/>
)}
{completedDate === null && (
// placeholder for the completion date with margin:
<Box
sx={{
mt: 6,
height: 56,
}}
>
/>
)}
<Button
fullWidth
size='lg'
@ -259,7 +331,7 @@ const ChoreView = () => {
<Box>Mark as {isPendingCompletion ? 'completed' : 'done'}</Box>
</Button> */}
</Box>
</Sheet>
<Snackbar
open={isPendingCompletion}
endDecorator={

View file

@ -1,4 +1,5 @@
import {
CancelScheduleSend,
Check,
Delete,
Edit,
@ -21,6 +22,7 @@ import {
import {
Avatar,
Box,
Button,
Card,
Chip,
CircularProgress,
@ -29,6 +31,7 @@ import {
IconButton,
Menu,
MenuItem,
Snackbar,
Typography,
} from '@mui/joy'
import moment from 'moment'
@ -59,6 +62,9 @@ const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => {
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())
@ -117,18 +123,41 @@ const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => {
})
}
const handleCompleteChore = () => {
MarkChoreComplete(chore.id).then(response => {
if (response.ok) {
response.json().then(data => {
const newChore = data.res
onChoreUpdate(newChore, 'completed')
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')
})
}
})
setIsDisabled(true)
setTimeout(() => setIsDisabled(false), 3000) // Re-enable the button after 5 seconds
.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')
@ -353,7 +382,7 @@ const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => {
{/* Box in top right with Chip showing next due date */}
<Box display='flex' justifyContent='start' alignItems='center'>
<Avatar sx={{ mr: 1, fontSize: 22 }}>
{chore.name.charAt(0).toUpperCase()}
{Array.from(chore.name)[0]}
</Avatar>
<Box display='flex' flexDirection='column'>
<Typography level='title-md'>{chore.name}</Typography>
@ -409,8 +438,8 @@ const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => {
<IconButton
variant='solid'
color='success'
onClick={handleCompleteChore}
disabled={isDisabled}
onClick={handleTaskCompletion}
disabled={isPendingCompletion}
sx={{
borderRadius: '50%',
minWidth: 50,
@ -420,7 +449,7 @@ const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => {
>
<div className='relative grid place-items-center'>
<Check />
{isDisabled && (
{isPendingCompletion && (
<CircularProgress
variant='solid'
color='success'
@ -596,6 +625,32 @@ const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => {
},
}}
/>
<Snackbar
open={isPendingCompletion}
endDecorator={
<Button
onClick={() => {
if (timeoutId) {
clearTimeout(timeoutId)
setIsPendingCompletion(false)
setTimeoutId(null)
setSecondsLeftToCancel(null) // Reset or adjust as needed
}
}}
size='md'
variant='outlined'
color='primary'
startDecorator={<CancelScheduleSend />}
>
Cancel
</Button>
}
>
<Typography level='body2' textAlign={'center'}>
Task will be marked as completed in {secondsLeftToCancel} seconds
</Typography>
</Snackbar>
</Card>
</>
)