Add SkipChore function to Fetcher utility module, Update Loading Comp
This commit is contained in:
parent
93512eb666
commit
7f4e592849
7 changed files with 296 additions and 171 deletions
|
@ -26,6 +26,7 @@ import {
|
|||
styled,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import { Divider } from '@mui/material'
|
||||
import moment from 'moment'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
|
||||
|
@ -135,7 +136,7 @@ const ChoreView = () => {
|
|||
? `${
|
||||
performers.find(p => p.id === chore.lastCompletedBy)?.displayName
|
||||
}`
|
||||
: 'Never',
|
||||
: '--',
|
||||
},
|
||||
{
|
||||
size: 6,
|
||||
|
@ -230,8 +231,8 @@ const ChoreView = () => {
|
|||
level='h3'
|
||||
// textAlign={'center'}
|
||||
sx={{
|
||||
mt: 2,
|
||||
mb: 1,
|
||||
mt: 1,
|
||||
mb: 0.5,
|
||||
}}
|
||||
>
|
||||
{chore.name}
|
||||
|
@ -243,7 +244,7 @@ const ChoreView = () => {
|
|||
</Chip>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography level='title-md' sx={{ mb: 1 }}>
|
||||
<Typography level='title-md' sx={{ mb: 0.5 }}>
|
||||
Details
|
||||
</Typography>
|
||||
|
||||
|
@ -265,9 +266,13 @@ const ChoreView = () => {
|
|||
<Typography level='body-xs' sx={{ fontWeight: 'md' }}>
|
||||
{detail.text}
|
||||
</Typography>
|
||||
<Typography level='body-sm' color='text.tertiary'>
|
||||
<Chip
|
||||
color='primary'
|
||||
size='md'
|
||||
startDecorator={detail.icon}
|
||||
>
|
||||
{detail.subtext ? detail.subtext : '--'}
|
||||
</Typography>
|
||||
</Chip>
|
||||
</ListItemContent>
|
||||
</ListItem>
|
||||
</Grid>
|
||||
|
@ -286,28 +291,6 @@ const ChoreView = () => {
|
|||
</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>
|
||||
{/* <Divider
|
||||
sx={{
|
||||
|
@ -325,25 +308,63 @@ const ChoreView = () => {
|
|||
mt: 2,
|
||||
}}
|
||||
>
|
||||
<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
|
||||
<Typography level='body-md' sx={{ mb: 1 }}>
|
||||
Complete the task
|
||||
</Typography>
|
||||
|
||||
<FormControl size='sm'>
|
||||
<Checkbox
|
||||
defaultChecked={note !== null}
|
||||
checked={note !== null}
|
||||
value={note !== null}
|
||||
size='lg'
|
||||
onChange={e => {
|
||||
if (e.target.checked) {
|
||||
setNote('')
|
||||
} else {
|
||||
setNote(null)
|
||||
}
|
||||
}}
|
||||
overlay
|
||||
sx={
|
||||
{
|
||||
// my: 1,
|
||||
}
|
||||
}
|
||||
setNote(e.target.value)
|
||||
}}
|
||||
size='md'
|
||||
sx={{
|
||||
mb: 1,
|
||||
}}
|
||||
/>
|
||||
label={
|
||||
<Typography
|
||||
level='body-sm'
|
||||
sx={{
|
||||
// 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'>
|
||||
<Checkbox
|
||||
|
@ -366,7 +387,18 @@ const ChoreView = () => {
|
|||
// 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>
|
||||
{completedDate !== null && (
|
||||
|
@ -390,30 +422,55 @@ const ChoreView = () => {
|
|||
>
|
||||
<Box>Mark as done</Box>
|
||||
</Button>
|
||||
<Button
|
||||
fullWidth
|
||||
size='lg'
|
||||
onClick={() => {
|
||||
setConfirmModelConfig({
|
||||
isOpen: true,
|
||||
title: 'Skip Task',
|
||||
<Divider sx={{ my: 0.5 }}>or</Divider>
|
||||
|
||||
message: 'Are you sure you want to skip this task?',
|
||||
|
||||
confirmText: 'Skip',
|
||||
cancelText: 'Cancel',
|
||||
onClose: confirmed => {
|
||||
if (confirmed) {
|
||||
handleSkippingTask()
|
||||
}
|
||||
setConfirmModelConfig({})
|
||||
},
|
||||
})
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: 1,
|
||||
alignContent: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
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
|
||||
open={isPendingCompletion}
|
||||
endDecorator={
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
SwitchAccessShortcut,
|
||||
TimesOneMobiledata,
|
||||
Update,
|
||||
ViewCarousel,
|
||||
Webhook,
|
||||
} from '@mui/icons-material'
|
||||
import {
|
||||
|
@ -107,6 +108,9 @@ const ChoreCard = ({
|
|||
const handleEdit = () => {
|
||||
navigate(`/chores/${chore.id}/edit`)
|
||||
}
|
||||
const handleView = () => {
|
||||
navigate(`/chores/${chore.id}`)
|
||||
}
|
||||
const handleDelete = () => {
|
||||
setConfirmModelConfig({
|
||||
isOpen: true,
|
||||
|
@ -579,6 +583,10 @@ const ChoreCard = ({
|
|||
<Edit />
|
||||
Edit
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleView}>
|
||||
<ViewCarousel />
|
||||
View
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleDelete} color='danger'>
|
||||
<Delete />
|
||||
Delete
|
||||
|
|
|
@ -3,7 +3,6 @@ import {
|
|||
Badge,
|
||||
Box,
|
||||
Checkbox,
|
||||
CircularProgress,
|
||||
Container,
|
||||
IconButton,
|
||||
Input,
|
||||
|
@ -18,8 +17,8 @@ import Fuse from 'fuse.js'
|
|||
import { useContext, useEffect, useRef, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { UserContext } from '../../contexts/UserContext'
|
||||
import Logo from '../../Logo'
|
||||
import { GetAllUsers, GetChores, GetUserProfile } from '../../utils/Fetcher'
|
||||
import LoadingComponent from '../components/Loading'
|
||||
import ChoreCard from './ChoreCard'
|
||||
|
||||
const MyChores = () => {
|
||||
|
@ -184,18 +183,7 @@ const MyChores = () => {
|
|||
}
|
||||
|
||||
if (userProfile === null) {
|
||||
return (
|
||||
<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 <LoadingComponent />
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
import { Checklist, EventBusy, Timelapse } from '@mui/icons-material'
|
||||
import { Checklist, EventBusy, Group, Timelapse } from '@mui/icons-material'
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Chip,
|
||||
Container,
|
||||
Grid,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemContent,
|
||||
ListItemDecorator,
|
||||
Sheet,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
|
@ -19,6 +17,7 @@ import { Link, useParams } from 'react-router-dom'
|
|||
import { API_URL } from '../../Config'
|
||||
import { GetAllCircleMembers } from '../../utils/Fetcher'
|
||||
import { Fetch } from '../../utils/TokenManager'
|
||||
import LoadingComponent from '../components/Loading'
|
||||
import HistoryCard from './HistoryCard'
|
||||
|
||||
const ChoreHistory = () => {
|
||||
|
@ -92,59 +91,49 @@ const ChoreHistory = () => {
|
|||
|
||||
const historyInfo = [
|
||||
{
|
||||
icon: (
|
||||
<Avatar>
|
||||
<Checklist />
|
||||
</Avatar>
|
||||
),
|
||||
text: `${histories.length} completed`,
|
||||
subtext: `${Object.keys(userHistories).length} users contributed`,
|
||||
icon: <Checklist />,
|
||||
text: 'Total Completed',
|
||||
subtext: `${histories.length} times`,
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<Avatar>
|
||||
<Timelapse />
|
||||
</Avatar>
|
||||
),
|
||||
text: `Completed within ${moment
|
||||
.duration(averageDelayMoment)
|
||||
.humanize()}`,
|
||||
subtext: `Maximum delay was ${moment
|
||||
.duration(maxDelayMoment)
|
||||
.humanize()}`,
|
||||
icon: <Timelapse />,
|
||||
text: 'Usually Within',
|
||||
subtext: moment.duration(averageDelayMoment).humanize(),
|
||||
},
|
||||
{
|
||||
icon: <Avatar></Avatar>,
|
||||
text: `${
|
||||
icon: <Timelapse />,
|
||||
text: 'Maximum Delay',
|
||||
subtext: moment.duration(maxDelayMoment).humanize(),
|
||||
},
|
||||
{
|
||||
icon: <Avatar />,
|
||||
text: ' Completed Most',
|
||||
subtext: `${
|
||||
performers.find(p => p.userId === Number(userCompletedByMost))
|
||||
?.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)
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <CircularProgress /> // Show loading indicator
|
||||
return <LoadingComponent />
|
||||
}
|
||||
if (!choreHistory.length) {
|
||||
return (
|
||||
|
@ -184,50 +173,43 @@ const ChoreHistory = () => {
|
|||
|
||||
return (
|
||||
<Container maxWidth='md'>
|
||||
<Typography level='h3' mb={1.5}>
|
||||
<Typography level='title-md' mb={1.5}>
|
||||
Summary:
|
||||
</Typography>
|
||||
{/* <Sheet sx={{ mb: 1, borderRadius: 'sm', p: 2, boxShadow: 'md' }}>
|
||||
<ListItem sx={{ gap: 1.5 }}>
|
||||
<ListItemDecorator>
|
||||
<Avatar>
|
||||
<AccountCircle />
|
||||
</Avatar>
|
||||
</ListItemDecorator>
|
||||
<ListItemContent>
|
||||
<Typography level='body1' sx={{ fontWeight: 'md' }}>
|
||||
{choreHistory.length} completed
|
||||
</Typography>
|
||||
<Typography level='body2' color='text.tertiary'>
|
||||
{Object.keys(userHistory).length} users contributed
|
||||
</Typography>
|
||||
</ListItemContent>
|
||||
</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>
|
||||
<Sheet
|
||||
// sx={{
|
||||
// mb: 1,
|
||||
// borderRadius: 'lg',
|
||||
// p: 2,
|
||||
// }}
|
||||
sx={{ borderRadius: 'sm', p: 2 }}
|
||||
variant='outlined'
|
||||
>
|
||||
<Grid container spacing={1}>
|
||||
{historyInfo.map((info, index) => (
|
||||
<Grid item xs={4} key={index}>
|
||||
{/* divider between the list items: */}
|
||||
|
||||
<ListItem key={index}>
|
||||
<ListItemContent>
|
||||
<Typography level='body1' sx={{ fontWeight: 'md' }}>
|
||||
<Typography level='body-xs' sx={{ fontWeight: 'md' }}>
|
||||
{info.text}
|
||||
</Typography>
|
||||
<Typography level='body1' color='text.tertiary'>
|
||||
{info.subtext}
|
||||
</Typography>
|
||||
<Chip color='primary' size='md' startDecorator={info.icon}>
|
||||
{info.subtext ? info.subtext : '--'}
|
||||
</Chip>
|
||||
</ListItemContent>
|
||||
</ListItem>
|
||||
</Sheet>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Sheet>
|
||||
|
||||
{/* User History Cards */}
|
||||
<Typography level='h3' my={1.5}>
|
||||
<Typography level='title-md' my={1.5}>
|
||||
History:
|
||||
</Typography>
|
||||
<Box sx={{ borderRadius: 'sm', p: 2, boxShadow: 'md' }}>
|
||||
<Sheet sx={{ borderRadius: 'sm', p: 2, boxShadow: 'md' }}>
|
||||
{/* Chore History List (Updated Style) */}
|
||||
|
||||
<List sx={{ p: 0 }}>
|
||||
|
@ -241,7 +223,7 @@ const ChoreHistory = () => {
|
|||
/>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
</Sheet>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { CalendarViewDay, Check, Timelapse } from '@mui/icons-material'
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
|
@ -30,6 +31,50 @@ const HistoryCard = ({ allHistory, performers, historyEntry, index }) => {
|
|||
|
||||
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 (
|
||||
<>
|
||||
<ListItem sx={{ gap: 1.5, alignItems: 'flex-start' }}>
|
||||
|
@ -55,13 +100,7 @@ const HistoryCard = ({ allHistory, performers, historyEntry, index }) => {
|
|||
<Typography level='body1' sx={{ fontWeight: 'md' }}>
|
||||
{moment(historyEntry.completedAt).format('ddd MM/DD/yyyy HH:mm')}
|
||||
</Typography>
|
||||
|
||||
<Chip>
|
||||
{historyEntry.dueDate &&
|
||||
historyEntry.completedAt > historyEntry.dueDate
|
||||
? 'Late'
|
||||
: 'On Time'}
|
||||
</Chip>
|
||||
{getCompletedChip(historyEntry)}
|
||||
</Box>
|
||||
<Typography level='body2' color='text.tertiary'>
|
||||
<Chip>
|
||||
|
|
|
@ -7,7 +7,7 @@ const DemoHistory = () => {
|
|||
{
|
||||
id: 32,
|
||||
choreId: 12,
|
||||
completedAt: moment().format(),
|
||||
completedAt: moment().hour(4).format(),
|
||||
completedBy: 1,
|
||||
assignedTo: 1,
|
||||
notes: null,
|
||||
|
@ -25,8 +25,8 @@ const DemoHistory = () => {
|
|||
{
|
||||
id: 31,
|
||||
choreId: 12,
|
||||
completedAt: moment().day(-10).format(),
|
||||
completedBy: 1,
|
||||
completedAt: moment().day(-10).hour(1).format(),
|
||||
completedBy: 2,
|
||||
assignedTo: 1,
|
||||
notes: null,
|
||||
dueDate: moment().day(-10).format(),
|
||||
|
|
51
src/views/components/Loading.jsx
Normal file
51
src/views/components/Loading.jsx
Normal 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
|
Loading…
Add table
Reference in a new issue