Add SkipChore function to Fetcher utility module, Update Loading Comp

This commit is contained in:
Mo Tarbin 2024-07-16 19:37:18 -04:00
parent 93512eb666
commit 7f4e592849
7 changed files with 296 additions and 171 deletions

View file

@ -26,6 +26,7 @@ import {
styled, styled,
Typography, Typography,
} from '@mui/joy' } from '@mui/joy'
import { Divider } from '@mui/material'
import moment from 'moment' import moment from 'moment'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useNavigate, useParams, useSearchParams } from 'react-router-dom' import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
@ -135,7 +136,7 @@ const ChoreView = () => {
? `${ ? `${
performers.find(p => p.id === chore.lastCompletedBy)?.displayName performers.find(p => p.id === chore.lastCompletedBy)?.displayName
}` }`
: 'Never', : '--',
}, },
{ {
size: 6, size: 6,
@ -230,8 +231,8 @@ const ChoreView = () => {
level='h3' level='h3'
// textAlign={'center'} // textAlign={'center'}
sx={{ sx={{
mt: 2, mt: 1,
mb: 1, mb: 0.5,
}} }}
> >
{chore.name} {chore.name}
@ -243,7 +244,7 @@ const ChoreView = () => {
</Chip> </Chip>
</Box> </Box>
<Box> <Box>
<Typography level='title-md' sx={{ mb: 1 }}> <Typography level='title-md' sx={{ mb: 0.5 }}>
Details Details
</Typography> </Typography>
@ -265,9 +266,13 @@ const ChoreView = () => {
<Typography level='body-xs' sx={{ fontWeight: 'md' }}> <Typography level='body-xs' sx={{ fontWeight: 'md' }}>
{detail.text} {detail.text}
</Typography> </Typography>
<Typography level='body-sm' color='text.tertiary'> <Chip
color='primary'
size='md'
startDecorator={detail.icon}
>
{detail.subtext ? detail.subtext : '--'} {detail.subtext ? detail.subtext : '--'}
</Typography> </Chip>
</ListItemContent> </ListItemContent>
</ListItem> </ListItem>
</Grid> </Grid>
@ -286,28 +291,6 @@ const ChoreView = () => {
</Sheet> </Sheet>
</> </>
)} )}
<Box
sx={{
display: 'flex',
flexDirection: 'row',
gap: 1,
alignContent: 'center',
justifyContent: 'center',
}}
>
<Button
startDecorator={<History />}
size='lg'
color='success'
variant='plain'
onClick={() => {
navigate(`/chores/${choreId}/history`)
}}
>
View History
</Button>
</Box>
</Box> </Box>
{/* <Divider {/* <Divider
sx={{ sx={{
@ -325,25 +308,63 @@ const ChoreView = () => {
mt: 2, mt: 2,
}} }}
> >
<Typography level='title-md'>Additional Notes</Typography> <Typography level='body-md' sx={{ mb: 1 }}>
<Input Complete the task
fullWidth </Typography>
multiline
label='Additional Notes' <FormControl size='sm'>
placeholder='note or information about the task' <Checkbox
value={note || ''} defaultChecked={note !== null}
onChange={e => { checked={note !== null}
if (e.target.value.trim() === '') { value={note !== null}
setNote(null) size='lg'
return onChange={e => {
if (e.target.checked) {
setNote('')
} else {
setNote(null)
}
}}
overlay
sx={
{
// my: 1,
}
} }
setNote(e.target.value) label={
}} <Typography
size='md' level='body-sm'
sx={{ sx={{
mb: 1, // center vertically
}} display: 'flex',
/> alignItems: 'center',
}}
>
Add Additional Notes
</Typography>
}
/>
</FormControl>
{note !== null && (
<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={{
mb: 1,
}}
/>
)}
<FormControl size='sm'> <FormControl size='sm'>
<Checkbox <Checkbox
@ -366,7 +387,18 @@ const ChoreView = () => {
// my: 1, // my: 1,
} }
} }
label={<Typography level='body2'>Set completion date</Typography>} label={
<Typography
level='body-sm'
sx={{
// center vertically
display: 'flex',
alignItems: 'center',
}}
>
Specify completion date
</Typography>
}
/> />
</FormControl> </FormControl>
{completedDate !== null && ( {completedDate !== null && (
@ -390,30 +422,55 @@ const ChoreView = () => {
> >
<Box>Mark as done</Box> <Box>Mark as done</Box>
</Button> </Button>
<Button <Divider sx={{ my: 0.5 }}>or</Divider>
fullWidth
size='lg'
onClick={() => {
setConfirmModelConfig({
isOpen: true,
title: 'Skip Task',
message: 'Are you sure you want to skip this task?', <Box
sx={{
confirmText: 'Skip', display: 'flex',
cancelText: 'Cancel', flexDirection: 'row',
onClose: confirmed => { gap: 1,
if (confirmed) { alignContent: 'center',
handleSkippingTask() justifyContent: 'center',
}
setConfirmModelConfig({})
},
})
}} }}
startDecorator={<SwitchAccessShortcut />}
> >
<Box>Skip</Box> <Button
</Button> fullWidth
size='lg'
onClick={() => {
setConfirmModelConfig({
isOpen: true,
title: 'Skip Task',
message: 'Are you sure you want to skip this task?',
confirmText: 'Skip',
cancelText: 'Cancel',
onClose: confirmed => {
if (confirmed) {
handleSkippingTask()
}
setConfirmModelConfig({})
},
})
}}
startDecorator={<SwitchAccessShortcut />}
>
<Box>Skip</Box>
</Button>
<Button
startDecorator={<History />}
size='lg'
color='primary'
variant='outlined'
fullWidth
onClick={() => {
navigate(`/chores/${choreId}/history`)
}}
>
History
</Button>
</Box>
<Snackbar <Snackbar
open={isPendingCompletion} open={isPendingCompletion}
endDecorator={ endDecorator={

View file

@ -17,6 +17,7 @@ import {
SwitchAccessShortcut, SwitchAccessShortcut,
TimesOneMobiledata, TimesOneMobiledata,
Update, Update,
ViewCarousel,
Webhook, Webhook,
} from '@mui/icons-material' } from '@mui/icons-material'
import { import {
@ -107,6 +108,9 @@ const ChoreCard = ({
const handleEdit = () => { const handleEdit = () => {
navigate(`/chores/${chore.id}/edit`) navigate(`/chores/${chore.id}/edit`)
} }
const handleView = () => {
navigate(`/chores/${chore.id}`)
}
const handleDelete = () => { const handleDelete = () => {
setConfirmModelConfig({ setConfirmModelConfig({
isOpen: true, isOpen: true,
@ -579,6 +583,10 @@ const ChoreCard = ({
<Edit /> <Edit />
Edit Edit
</MenuItem> </MenuItem>
<MenuItem onClick={handleView}>
<ViewCarousel />
View
</MenuItem>
<MenuItem onClick={handleDelete} color='danger'> <MenuItem onClick={handleDelete} color='danger'>
<Delete /> <Delete />
Delete Delete

View file

@ -3,7 +3,6 @@ import {
Badge, Badge,
Box, Box,
Checkbox, Checkbox,
CircularProgress,
Container, Container,
IconButton, IconButton,
Input, Input,
@ -18,8 +17,8 @@ import Fuse from 'fuse.js'
import { useContext, useEffect, useRef, useState } from 'react' import { useContext, useEffect, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { UserContext } from '../../contexts/UserContext' import { UserContext } from '../../contexts/UserContext'
import Logo from '../../Logo'
import { GetAllUsers, GetChores, GetUserProfile } from '../../utils/Fetcher' import { GetAllUsers, GetChores, GetUserProfile } from '../../utils/Fetcher'
import LoadingComponent from '../components/Loading'
import ChoreCard from './ChoreCard' import ChoreCard from './ChoreCard'
const MyChores = () => { const MyChores = () => {
@ -184,18 +183,7 @@ const MyChores = () => {
} }
if (userProfile === null) { if (userProfile === null) {
return ( return <LoadingComponent />
<Container className='flex h-full items-center justify-center'>
<Box className='flex flex-col items-center justify-center'>
<CircularProgress
color='success'
sx={{ '--CircularProgress-size': '200px' }}
>
<Logo />
</CircularProgress>
</Box>
</Container>
)
} }
return ( return (

View file

@ -1,15 +1,13 @@
import { Checklist, EventBusy, Timelapse } from '@mui/icons-material' import { Checklist, EventBusy, Group, Timelapse } from '@mui/icons-material'
import { import {
Avatar, Avatar,
Box,
Button, Button,
CircularProgress, Chip,
Container, Container,
Grid, Grid,
List, List,
ListItem, ListItem,
ListItemContent, ListItemContent,
ListItemDecorator,
Sheet, Sheet,
Typography, Typography,
} from '@mui/joy' } from '@mui/joy'
@ -19,6 +17,7 @@ import { Link, useParams } from 'react-router-dom'
import { API_URL } from '../../Config' import { API_URL } from '../../Config'
import { GetAllCircleMembers } from '../../utils/Fetcher' import { GetAllCircleMembers } from '../../utils/Fetcher'
import { Fetch } from '../../utils/TokenManager' import { Fetch } from '../../utils/TokenManager'
import LoadingComponent from '../components/Loading'
import HistoryCard from './HistoryCard' import HistoryCard from './HistoryCard'
const ChoreHistory = () => { const ChoreHistory = () => {
@ -92,59 +91,49 @@ const ChoreHistory = () => {
const historyInfo = [ const historyInfo = [
{ {
icon: ( icon: <Checklist />,
<Avatar> text: 'Total Completed',
<Checklist /> subtext: `${histories.length} times`,
</Avatar>
),
text: `${histories.length} completed`,
subtext: `${Object.keys(userHistories).length} users contributed`,
}, },
{ {
icon: ( icon: <Timelapse />,
<Avatar> text: 'Usually Within',
<Timelapse /> subtext: moment.duration(averageDelayMoment).humanize(),
</Avatar>
),
text: `Completed within ${moment
.duration(averageDelayMoment)
.humanize()}`,
subtext: `Maximum delay was ${moment
.duration(maxDelayMoment)
.humanize()}`,
}, },
{ {
icon: <Avatar></Avatar>, icon: <Timelapse />,
text: `${ text: 'Maximum Delay',
subtext: moment.duration(maxDelayMoment).humanize(),
},
{
icon: <Avatar />,
text: ' Completed Most',
subtext: `${
performers.find(p => p.userId === Number(userCompletedByMost)) performers.find(p => p.userId === Number(userCompletedByMost))
?.displayName ?.displayName
} completed most`, } `,
subtext: `${userHistories[userCompletedByMost]} time/s`, },
// contributes:
{
icon: <Group />,
text: 'Total Performers',
subtext: `${Object.keys(userHistories).length} users`,
},
{
icon: <Avatar />,
text: 'Last Completed',
subtext: `${
performers.find(p => p.userId === Number(histories[0].completedBy))
?.displayName
}`,
}, },
] ]
if (userCompletedByLeast !== userCompletedByMost) {
historyInfo.push({
icon: (
<Avatar>
{
performers.find(p => p.userId === userCompletedByLeast)
?.displayName
}
</Avatar>
),
text: `${
performers.find(p => p.userId === Number(userCompletedByLeast))
.displayName
} completed least`,
subtext: `${userHistories[userCompletedByLeast]} time/s`,
})
}
setHistoryInfo(historyInfo) setHistoryInfo(historyInfo)
} }
if (isLoading) { if (isLoading) {
return <CircularProgress /> // Show loading indicator return <LoadingComponent />
} }
if (!choreHistory.length) { if (!choreHistory.length) {
return ( return (
@ -184,50 +173,43 @@ const ChoreHistory = () => {
return ( return (
<Container maxWidth='md'> <Container maxWidth='md'>
<Typography level='h3' mb={1.5}> <Typography level='title-md' mb={1.5}>
Summary: Summary:
</Typography> </Typography>
{/* <Sheet sx={{ mb: 1, borderRadius: 'sm', p: 2, boxShadow: 'md' }}> <Sheet
<ListItem sx={{ gap: 1.5 }}> // sx={{
<ListItemDecorator> // mb: 1,
<Avatar> // borderRadius: 'lg',
<AccountCircle /> // p: 2,
</Avatar> // }}
</ListItemDecorator> sx={{ borderRadius: 'sm', p: 2 }}
<ListItemContent> variant='outlined'
<Typography level='body1' sx={{ fontWeight: 'md' }}> >
{choreHistory.length} completed <Grid container spacing={1}>
</Typography> {historyInfo.map((info, index) => (
<Typography level='body2' color='text.tertiary'> <Grid item xs={4} key={index}>
{Object.keys(userHistory).length} users contributed {/* divider between the list items: */}
</Typography>
</ListItemContent> <ListItem key={index}>
</ListItem>
</Sheet> */}
<Grid container>
{historyInfo.map((info, index) => (
<Grid key={index} item xs={12} sm={6}>
<Sheet sx={{ mb: 1, borderRadius: 'sm', p: 2, boxShadow: 'md' }}>
<ListItem sx={{ gap: 1.5 }}>
<ListItemDecorator>{info.icon}</ListItemDecorator>
<ListItemContent> <ListItemContent>
<Typography level='body1' sx={{ fontWeight: 'md' }}> <Typography level='body-xs' sx={{ fontWeight: 'md' }}>
{info.text} {info.text}
</Typography> </Typography>
<Typography level='body1' color='text.tertiary'> <Chip color='primary' size='md' startDecorator={info.icon}>
{info.subtext} {info.subtext ? info.subtext : '--'}
</Typography> </Chip>
</ListItemContent> </ListItemContent>
</ListItem> </ListItem>
</Sheet> </Grid>
</Grid> ))}
))} </Grid>
</Grid> </Sheet>
{/* User History Cards */} {/* User History Cards */}
<Typography level='h3' my={1.5}> <Typography level='title-md' my={1.5}>
History: History:
</Typography> </Typography>
<Box sx={{ borderRadius: 'sm', p: 2, boxShadow: 'md' }}> <Sheet sx={{ borderRadius: 'sm', p: 2, boxShadow: 'md' }}>
{/* Chore History List (Updated Style) */} {/* Chore History List (Updated Style) */}
<List sx={{ p: 0 }}> <List sx={{ p: 0 }}>
@ -241,7 +223,7 @@ const ChoreHistory = () => {
/> />
))} ))}
</List> </List>
</Box> </Sheet>
</Container> </Container>
) )
} }

View file

@ -1,3 +1,4 @@
import { CalendarViewDay, Check, Timelapse } from '@mui/icons-material'
import { import {
Avatar, Avatar,
Box, Box,
@ -30,6 +31,50 @@ const HistoryCard = ({ allHistory, performers, historyEntry, index }) => {
return `${timeValue} ${unit}${timeValue !== 1 ? 's' : ''}` return `${timeValue} ${unit}${timeValue !== 1 ? 's' : ''}`
} }
const getCompletedChip = historyEntry => {
var text = 'No Due Date'
var color = 'info'
var icon = <CalendarViewDay />
// if completed few hours +-6 hours
if (
historyEntry.dueDate &&
historyEntry.completedAt > historyEntry.dueDate - 1000 * 60 * 60 * 6 &&
historyEntry.completedAt < historyEntry.dueDate + 1000 * 60 * 60 * 6
) {
text = 'On Time'
color = 'success'
icon = <Check />
} else if (
historyEntry.dueDate &&
historyEntry.completedAt < historyEntry.dueDate
) {
text = 'On Time'
color = 'success'
icon = <Check />
}
// if completed after due date then it's late
else if (
historyEntry.dueDate &&
historyEntry.completedAt > historyEntry.dueDate
) {
text = 'Late'
color = 'warning'
icon = <Timelapse />
} else {
text = 'No Due Date'
color = 'info'
icon = <CalendarViewDay />
}
return (
<Chip startDecorator={icon} color={color}>
{text}
</Chip>
)
}
return ( return (
<> <>
<ListItem sx={{ gap: 1.5, alignItems: 'flex-start' }}> <ListItem sx={{ gap: 1.5, alignItems: 'flex-start' }}>
@ -55,13 +100,7 @@ const HistoryCard = ({ allHistory, performers, historyEntry, index }) => {
<Typography level='body1' sx={{ fontWeight: 'md' }}> <Typography level='body1' sx={{ fontWeight: 'md' }}>
{moment(historyEntry.completedAt).format('ddd MM/DD/yyyy HH:mm')} {moment(historyEntry.completedAt).format('ddd MM/DD/yyyy HH:mm')}
</Typography> </Typography>
{getCompletedChip(historyEntry)}
<Chip>
{historyEntry.dueDate &&
historyEntry.completedAt > historyEntry.dueDate
? 'Late'
: 'On Time'}
</Chip>
</Box> </Box>
<Typography level='body2' color='text.tertiary'> <Typography level='body2' color='text.tertiary'>
<Chip> <Chip>

View file

@ -7,7 +7,7 @@ const DemoHistory = () => {
{ {
id: 32, id: 32,
choreId: 12, choreId: 12,
completedAt: moment().format(), completedAt: moment().hour(4).format(),
completedBy: 1, completedBy: 1,
assignedTo: 1, assignedTo: 1,
notes: null, notes: null,
@ -25,8 +25,8 @@ const DemoHistory = () => {
{ {
id: 31, id: 31,
choreId: 12, choreId: 12,
completedAt: moment().day(-10).format(), completedAt: moment().day(-10).hour(1).format(),
completedBy: 1, completedBy: 2,
assignedTo: 1, assignedTo: 1,
notes: null, notes: null,
dueDate: moment().day(-10).format(), dueDate: moment().day(-10).format(),

View file

@ -0,0 +1,51 @@
import { Box, CircularProgress, Container } from '@mui/joy'
import { Typography } from '@mui/material'
import { useEffect, useState } from 'react'
import Logo from '../../Logo'
const LoadingComponent = () => {
const [message, setMessage] = useState('Loading...')
const [subMessage, setSubMessage] = useState('')
useEffect(() => {
// if loading took more than 5 seconds update submessage to mention there might be an error:
const timeout = setTimeout(() => {
setSubMessage(
'This is taking longer than usual. There might be an issue.',
)
}, 5000)
return () => clearTimeout(timeout)
}, [])
return (
<Container className='flex h-full items-center justify-center'>
<Box
className='flex flex-col items-center justify-center'
sx={{
minHeight: '80vh',
}}
>
<CircularProgress
color='success'
sx={{ '--CircularProgress-size': '200px' }}
>
<Logo />
</CircularProgress>
<Box
className='flex items-center gap-2'
sx={{
fontWeight: 700,
fontSize: 24,
mt: 2,
}}
>
{message}
</Box>
<Typography level='h2' fontWeight={500} textAlign={'center'}>
{subMessage}
</Typography>
</Box>
</Container>
)
}
export default LoadingComponent