Improve MarkChoreComplete, allow complete in past ,etc
This commit is contained in:
parent
df83cc8948
commit
e039b732a8
3 changed files with 228 additions and 92 deletions
|
@ -57,11 +57,20 @@ const GetChoreDetailById = id => {
|
||||||
headers: HEADERS(),
|
headers: HEADERS(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const MarkChoreComplete = (id, note, completedDate) => {
|
||||||
const MarkChoreComplete = id => {
|
const body = {
|
||||||
return Fetch(`${API_URL}/chores/${id}/do`, {
|
note,
|
||||||
|
}
|
||||||
|
let completedDateFormated = ''
|
||||||
|
if (completedDate) {
|
||||||
|
completedDateFormated = `?completedDate=${new Date(
|
||||||
|
completedDate,
|
||||||
|
).toISOString()}`
|
||||||
|
}
|
||||||
|
return Fetch(`${API_URL}/chores/${id}/do${completedDateFormated}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: HEADERS(),
|
headers: HEADERS(),
|
||||||
|
body: JSON.stringify(body),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const CreateChore = chore => {
|
const CreateChore = chore => {
|
||||||
|
|
|
@ -3,14 +3,19 @@ import {
|
||||||
CancelScheduleSend,
|
CancelScheduleSend,
|
||||||
Check,
|
Check,
|
||||||
Checklist,
|
Checklist,
|
||||||
|
Note,
|
||||||
PeopleAlt,
|
PeopleAlt,
|
||||||
Person,
|
Person,
|
||||||
} from '@mui/icons-material'
|
} from '@mui/icons-material'
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
|
Checkbox,
|
||||||
Container,
|
Container,
|
||||||
|
Divider,
|
||||||
|
FormControl,
|
||||||
Grid,
|
Grid,
|
||||||
|
Input,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemContent,
|
ListItemContent,
|
||||||
ListItemDecorator,
|
ListItemDecorator,
|
||||||
|
@ -21,7 +26,7 @@ import {
|
||||||
} from '@mui/joy'
|
} from '@mui/joy'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useParams, useSearchParams } from 'react-router-dom'
|
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
|
||||||
import {
|
import {
|
||||||
GetAllUsers,
|
GetAllUsers,
|
||||||
GetChoreDetailById,
|
GetChoreDetailById,
|
||||||
|
@ -43,14 +48,16 @@ const ChoreView = () => {
|
||||||
const [performers, setPerformers] = useState([])
|
const [performers, setPerformers] = useState([])
|
||||||
const [infoCards, setInfoCards] = useState([])
|
const [infoCards, setInfoCards] = useState([])
|
||||||
const { choreId } = useParams()
|
const { choreId } = useParams()
|
||||||
|
const [note, setNote] = useState(null)
|
||||||
|
|
||||||
// query param `complete=true`
|
const Navigate = useNavigate()
|
||||||
|
|
||||||
const [searchParams] = useSearchParams()
|
const [searchParams] = useSearchParams()
|
||||||
|
|
||||||
const [isPendingCompletion, setIsPendingCompletion] = useState(false)
|
const [isPendingCompletion, setIsPendingCompletion] = useState(false)
|
||||||
const [timeoutId, setTimeoutId] = useState(null)
|
const [timeoutId, setTimeoutId] = useState(null)
|
||||||
const [secondsLeftToCancel, setSecondsLeftToCancel] = useState(null)
|
const [secondsLeftToCancel, setSecondsLeftToCancel] = useState(null)
|
||||||
|
const [completedDate, setCompletedDate] = useState(null)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Promise.all([
|
Promise.all([
|
||||||
GetChoreDetailById(choreId).then(resp => {
|
GetChoreDetailById(choreId).then(resp => {
|
||||||
|
@ -82,7 +89,7 @@ const ChoreView = () => {
|
||||||
{
|
{
|
||||||
icon: <CalendarMonth />,
|
icon: <CalendarMonth />,
|
||||||
text: 'Due Date',
|
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 />,
|
icon: <PeopleAlt />,
|
||||||
|
@ -119,12 +126,21 @@ const ChoreView = () => {
|
||||||
subtext: chore.lastCompletedDate
|
subtext: chore.lastCompletedDate
|
||||||
? `${
|
? `${
|
||||||
chore.lastCompletedDate &&
|
chore.lastCompletedDate &&
|
||||||
moment(chore.lastCompletedDate).format('MM/DD/YYYY hh:mm A')
|
moment(chore.lastCompletedDate).fromNow()
|
||||||
}(${
|
// moment(chore.lastCompletedDate).format('MM/DD/YYYY hh:mm A'))
|
||||||
performers.find(p => p.id === chore.lastCompletedBy)?.displayName
|
}(
|
||||||
})`
|
${
|
||||||
|
performers.find(p => p.id === chore.lastCompletedBy)
|
||||||
|
?.displayName
|
||||||
|
}
|
||||||
|
)`
|
||||||
: 'Never',
|
: 'Never',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: <Note />,
|
||||||
|
text: 'Recent Note',
|
||||||
|
subtext: chore.notes || '--',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
setInfoCards(cards)
|
setInfoCards(cards)
|
||||||
}
|
}
|
||||||
|
@ -143,10 +159,11 @@ const ChoreView = () => {
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
const id = setTimeout(() => {
|
const id = setTimeout(() => {
|
||||||
MarkChoreComplete(choreId)
|
MarkChoreComplete(choreId, note, completedDate)
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
return resp.json().then(data => {
|
return resp.json().then(data => {
|
||||||
|
setNote(null)
|
||||||
setChore(data.res)
|
setChore(data.res)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -174,22 +191,18 @@ const ChoreView = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container maxWidth='sm'>
|
<Container
|
||||||
<Sheet
|
maxWidth='sm'
|
||||||
variant='plain'
|
|
||||||
sx={{
|
sx={{
|
||||||
borderRadius: 'sm',
|
|
||||||
p: 2,
|
|
||||||
boxShadow: 'md',
|
|
||||||
minHeight: '90vh',
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
|
// space between :
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography
|
<Typography
|
||||||
level='h4'
|
level='h3'
|
||||||
textAlign={'center'}
|
textAlign={'center'}
|
||||||
sx={{
|
sx={{
|
||||||
mt: 2,
|
mt: 2,
|
||||||
|
@ -202,9 +215,7 @@ const ChoreView = () => {
|
||||||
<Grid container spacing={1}>
|
<Grid container spacing={1}>
|
||||||
{infoCards.map((info, index) => (
|
{infoCards.map((info, index) => (
|
||||||
<Grid key={index} item xs={12} sm={6}>
|
<Grid key={index} item xs={12} sm={6}>
|
||||||
<Sheet
|
<Sheet sx={{ mb: 1, borderRadius: 'md', p: 1, boxShadow: 'sm' }}>
|
||||||
sx={{ mb: 1, borderRadius: 'md', p: 1, boxShadow: 'sm' }}
|
|
||||||
>
|
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemDecorator>
|
<ListItemDecorator>
|
||||||
<IconCard>{info.icon}</IconCard>
|
<IconCard>{info.icon}</IconCard>
|
||||||
|
@ -223,11 +234,72 @@ const ChoreView = () => {
|
||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</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
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
mt: 6,
|
height: 56,
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
fullWidth
|
fullWidth
|
||||||
size='lg'
|
size='lg'
|
||||||
|
@ -259,7 +331,7 @@ const ChoreView = () => {
|
||||||
<Box>Mark as {isPendingCompletion ? 'completed' : 'done'}</Box>
|
<Box>Mark as {isPendingCompletion ? 'completed' : 'done'}</Box>
|
||||||
</Button> */}
|
</Button> */}
|
||||||
</Box>
|
</Box>
|
||||||
</Sheet>
|
|
||||||
<Snackbar
|
<Snackbar
|
||||||
open={isPendingCompletion}
|
open={isPendingCompletion}
|
||||||
endDecorator={
|
endDecorator={
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
CancelScheduleSend,
|
||||||
Check,
|
Check,
|
||||||
Delete,
|
Delete,
|
||||||
Edit,
|
Edit,
|
||||||
|
@ -21,6 +22,7 @@ import {
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Box,
|
Box,
|
||||||
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Chip,
|
Chip,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
|
@ -29,6 +31,7 @@ import {
|
||||||
IconButton,
|
IconButton,
|
||||||
Menu,
|
Menu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
|
Snackbar,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@mui/joy'
|
} from '@mui/joy'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
@ -59,6 +62,9 @@ const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [isDisabled, setIsDisabled] = React.useState(false)
|
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(() => {
|
// useEffect(() => {
|
||||||
// GetAllUsers()
|
// GetAllUsers()
|
||||||
// .then(response => response.json())
|
// .then(response => response.json())
|
||||||
|
@ -117,18 +123,41 @@ const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCompleteChore = () => {
|
const handleTaskCompletion = () => {
|
||||||
MarkChoreComplete(chore.id).then(response => {
|
setIsPendingCompletion(true)
|
||||||
if (response.ok) {
|
let seconds = 3 // Starting countdown from 3 seconds
|
||||||
response.json().then(data => {
|
setSecondsLeftToCancel(seconds)
|
||||||
const newChore = data.res
|
|
||||||
onChoreUpdate(newChore, 'completed')
|
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)
|
.then(() => {
|
||||||
setTimeout(() => setIsDisabled(false), 3000) // Re-enable the button after 5 seconds
|
setIsPendingCompletion(false)
|
||||||
|
clearTimeout(id)
|
||||||
|
clearInterval(countdownInterval) // Ensure to clear this interval as well
|
||||||
|
setTimeoutId(null)
|
||||||
|
setSecondsLeftToCancel(null)
|
||||||
|
})
|
||||||
|
}, 3000)
|
||||||
|
|
||||||
|
setTimeoutId(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleChangeDueDate = newDate => {
|
const handleChangeDueDate = newDate => {
|
||||||
if (activeUserId === null) {
|
if (activeUserId === null) {
|
||||||
alert('Please select a performer')
|
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 in top right with Chip showing next due date */}
|
||||||
<Box display='flex' justifyContent='start' alignItems='center'>
|
<Box display='flex' justifyContent='start' alignItems='center'>
|
||||||
<Avatar sx={{ mr: 1, fontSize: 22 }}>
|
<Avatar sx={{ mr: 1, fontSize: 22 }}>
|
||||||
{chore.name.charAt(0).toUpperCase()}
|
{Array.from(chore.name)[0]}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Box display='flex' flexDirection='column'>
|
<Box display='flex' flexDirection='column'>
|
||||||
<Typography level='title-md'>{chore.name}</Typography>
|
<Typography level='title-md'>{chore.name}</Typography>
|
||||||
|
@ -409,8 +438,8 @@ const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => {
|
||||||
<IconButton
|
<IconButton
|
||||||
variant='solid'
|
variant='solid'
|
||||||
color='success'
|
color='success'
|
||||||
onClick={handleCompleteChore}
|
onClick={handleTaskCompletion}
|
||||||
disabled={isDisabled}
|
disabled={isPendingCompletion}
|
||||||
sx={{
|
sx={{
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
minWidth: 50,
|
minWidth: 50,
|
||||||
|
@ -420,7 +449,7 @@ const ChoreCard = ({ chore, performers, onChoreUpdate, onChoreRemove, sx }) => {
|
||||||
>
|
>
|
||||||
<div className='relative grid place-items-center'>
|
<div className='relative grid place-items-center'>
|
||||||
<Check />
|
<Check />
|
||||||
{isDisabled && (
|
{isPendingCompletion && (
|
||||||
<CircularProgress
|
<CircularProgress
|
||||||
variant='solid'
|
variant='solid'
|
||||||
color='success'
|
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>
|
</Card>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Reference in a new issue