diff options
-rw-r--r-- | src/utils/Fetcher.jsx | 15 | ||||
-rw-r--r-- | src/views/ChoreEdit/ChoreView.jsx | 214 | ||||
-rw-r--r-- | src/views/Chores/ChoreCard.jsx | 83 |
3 files changed, 224 insertions, 88 deletions
diff --git a/src/utils/Fetcher.jsx b/src/utils/Fetcher.jsx index 0acc91f..afa9235 100644 --- a/src/utils/Fetcher.jsx +++ b/src/utils/Fetcher.jsx @@ -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 => { diff --git a/src/views/ChoreEdit/ChoreView.jsx b/src/views/ChoreEdit/ChoreView.jsx index 8116270..29268bc 100644 --- a/src/views/ChoreEdit/ChoreView.jsx +++ b/src/views/ChoreEdit/ChoreView.jsx @@ -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,75 +191,130 @@ const ChoreView = () => { } return ( - <Container maxWidth='sm'> - <Sheet - variant='plain' + <Container + maxWidth='sm' + sx={{ + display: 'flex', + flexDirection: 'column', + // space between : + justifyContent: 'space-between', + }} + > + <Box> + <Typography + level='h3' + textAlign={'center'} + sx={{ + mt: 2, + mb: 4, + }} + > + {chore.name} + </Typography> + + <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' }}> + <ListItem> + <ListItemDecorator> + <IconCard>{info.icon}</IconCard> + </ListItemDecorator> + <ListItemContent> + <Typography level='body1' sx={{ fontWeight: 'md' }}> + {info.text} + </Typography> + <Typography level='body1' color='text.tertiary'> + {info.subtext ? info.subtext : '--'} + </Typography> + </ListItemContent> + </ListItem> + </Sheet> + </Grid> + ))} + </Grid> + </Box> + <Divider sx={{ - borderRadius: 'sm', - p: 2, - boxShadow: 'md', - minHeight: '90vh', - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', + my: 2, }} - > - <Box> - <Typography - level='h4' - textAlign={'center'} + /> + + <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={{ - mt: 2, - mb: 4, + my: 1, }} - > - {chore.name} - </Typography> + 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={{ + height: 56, + }} + /> + )} - <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' }} - > - <ListItem> - <ListItemDecorator> - <IconCard>{info.icon}</IconCard> - </ListItemDecorator> - <ListItemContent> - <Typography level='body1' sx={{ fontWeight: 'md' }}> - {info.text} - </Typography> - <Typography level='body1' color='text.tertiary'> - {info.subtext ? info.subtext : '--'} - </Typography> - </ListItemContent> - </ListItem> - </Sheet> - </Grid> - ))} - </Grid> - </Box> - <Box + <Button + fullWidth + size='lg' sx={{ - mt: 6, + height: 50, + mb: 2, }} + onClick={handleTaskCompletion} + disabled={isPendingCompletion} + color={isPendingCompletion ? 'danger' : 'success'} + startDecorator={<Check />} > - <Button - fullWidth - size='lg' - sx={{ - height: 50, - mb: 2, - }} - onClick={handleTaskCompletion} - disabled={isPendingCompletion} - color={isPendingCompletion ? 'danger' : 'success'} - startDecorator={<Check />} - > - <Box>Mark as done</Box> - </Button> - {/* <Button + <Box>Mark as done</Box> + </Button> + {/* <Button sx={{ borderRadius: '32px', mt: 1, @@ -258,8 +330,8 @@ const ChoreView = () => { > <Box>Mark as {isPendingCompletion ? 'completed' : 'done'}</Box> </Button> */} - </Box> - </Sheet> + </Box> + <Snackbar open={isPendingCompletion} endDecorator={ diff --git a/src/views/Chores/ChoreCard.jsx b/src/views/Chores/ChoreCard.jsx index 305f7c4..6a38e0b 100644 --- a/src/views/Chores/ChoreCard.jsx +++ b/src/views/Chores/ChoreCard.jsx @@ -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 } - }) - setIsDisabled(true) - setTimeout(() => setIsDisabled(false), 3000) // Re-enable the button after 5 seconds + }, 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') @@ -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> </> ) |