diff options
Diffstat (limited to 'src/views')
-rw-r--r-- | src/views/ChoreEdit/ChoreView.jsx | 193 | ||||
-rw-r--r-- | src/views/Chores/ChoreCard.jsx | 8 | ||||
-rw-r--r-- | src/views/Chores/MyChores.jsx | 16 | ||||
-rw-r--r-- | src/views/History/ChoreHistory.jsx | 140 | ||||
-rw-r--r-- | src/views/History/HistoryCard.jsx | 53 | ||||
-rw-r--r-- | src/views/Landing/DemoHistory.jsx | 6 | ||||
-rw-r--r-- | src/views/components/Loading.jsx | 51 |
7 files changed, 296 insertions, 171 deletions
diff --git a/src/views/ChoreEdit/ChoreView.jsx b/src/views/ChoreEdit/ChoreView.jsx index afdc379..172ad9b 100644 --- a/src/views/ChoreEdit/ChoreView.jsx +++ b/src/views/ChoreEdit/ChoreView.jsx @@ -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={ diff --git a/src/views/Chores/ChoreCard.jsx b/src/views/Chores/ChoreCard.jsx index 6edcbd3..97e407a 100644 --- a/src/views/Chores/ChoreCard.jsx +++ b/src/views/Chores/ChoreCard.jsx @@ -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 diff --git a/src/views/Chores/MyChores.jsx b/src/views/Chores/MyChores.jsx index cd90942..d4eed5a 100644 --- a/src/views/Chores/MyChores.jsx +++ b/src/views/Chores/MyChores.jsx @@ -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 ( diff --git a/src/views/History/ChoreHistory.jsx b/src/views/History/ChoreHistory.jsx index b85e456..1db1f06 100644 --- a/src/views/History/ChoreHistory.jsx +++ b/src/views/History/ChoreHistory.jsx @@ -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> ) } diff --git a/src/views/History/HistoryCard.jsx b/src/views/History/HistoryCard.jsx index c606fbf..5e71f52 100644 --- a/src/views/History/HistoryCard.jsx +++ b/src/views/History/HistoryCard.jsx @@ -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> diff --git a/src/views/Landing/DemoHistory.jsx b/src/views/Landing/DemoHistory.jsx index 2c713f1..4e0f45e 100644 --- a/src/views/Landing/DemoHistory.jsx +++ b/src/views/Landing/DemoHistory.jsx @@ -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(), diff --git a/src/views/components/Loading.jsx b/src/views/components/Loading.jsx new file mode 100644 index 0000000..a31cf20 --- /dev/null +++ b/src/views/components/Loading.jsx @@ -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 |