diff options
author | Mo Tarbin <mhed.t91@gmail.com> | 2024-07-16 19:37:59 -0400 |
---|---|---|
committer | Mo Tarbin <mhed.t91@gmail.com> | 2024-07-16 19:37:59 -0400 |
commit | 8e42e59a80fbb4009e6d871d998793ffa597802a (patch) | |
tree | 36278fab2f16b148b86b2760dfb19d8e138e1d4d | |
parent | c684bdb9ece99935c4c5f3a3eb81787cc2a0ef19 (diff) | |
parent | 7f4e5928492a71135f6817874461c80a0ecc155c (diff) | |
download | donetick-frontend-8e42e59a80fbb4009e6d871d998793ffa597802a.tar.gz donetick-frontend-8e42e59a80fbb4009e6d871d998793ffa597802a.tar.bz2 donetick-frontend-8e42e59a80fbb4009e6d871d998793ffa597802a.zip |
Merge branch 'dev'
Diffstat (limited to '')
-rw-r--r-- | src/utils/Fetcher.jsx | 11 | ||||
-rw-r--r-- | src/views/ChoreEdit/ChoreView.jsx | 387 | ||||
-rw-r--r-- | src/views/ChoreEdit/RepeatSection.jsx | 2 | ||||
-rw-r--r-- | src/views/Chores/ChoreCard.jsx | 18 | ||||
-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/Landing/FeaturesSection.jsx | 27 | ||||
-rw-r--r-- | src/views/Landing/Footer.jsx | 127 | ||||
-rw-r--r-- | src/views/Landing/Landing.jsx | 14 | ||||
-rw-r--r-- | src/views/components/Loading.jsx | 51 | ||||
-rw-r--r-- | src/views/components/NavBar.jsx | 10 |
13 files changed, 606 insertions, 256 deletions
diff --git a/src/utils/Fetcher.jsx b/src/utils/Fetcher.jsx index afa9235..1161543 100644 --- a/src/utils/Fetcher.jsx +++ b/src/utils/Fetcher.jsx @@ -73,6 +73,16 @@ const MarkChoreComplete = (id, note, completedDate) => { body: JSON.stringify(body), }) } + +const SkipChore = id => { + return Fetch(`${API_URL}/chores/${id}/skip`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({}), + }) +} const CreateChore = chore => { return Fetch(`${API_URL}/chores/`, { method: 'POST', @@ -277,6 +287,7 @@ export { SaveChore, SaveThing, signUp, + SkipChore, UpdateThingState, UpdateUserDetails, } diff --git a/src/views/ChoreEdit/ChoreView.jsx b/src/views/ChoreEdit/ChoreView.jsx index df45aa7..172ad9b 100644 --- a/src/views/ChoreEdit/ChoreView.jsx +++ b/src/views/ChoreEdit/ChoreView.jsx @@ -3,48 +3,53 @@ import { CancelScheduleSend, Check, Checklist, - Note, + History, PeopleAlt, Person, + SwitchAccessShortcut, + Timelapse, } from '@mui/icons-material' import { Box, Button, Card, Checkbox, + Chip, Container, FormControl, Grid, Input, ListItem, ListItemContent, - ListItemDecorator, Sheet, Snackbar, styled, Typography, } from '@mui/joy' +import { Divider } from '@mui/material' 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, MarkChoreComplete, + SkipChore, } from '../../utils/Fetcher' +import ConfirmationModal from '../Modals/Inputs/ConfirmationModal' const IconCard = styled('div')({ display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: '#f0f0f0', // Adjust the background color as needed borderRadius: '50%', - minWidth: '50px', - height: '50px', + minWidth: '40px', + height: '40px', marginRight: '16px', }) const ChoreView = () => { const [chore, setChore] = useState({}) - + const navigate = useNavigate() const [performers, setPerformers] = useState([]) const [infoCards, setInfoCards] = useState([]) const { choreId } = useParams() @@ -56,6 +61,8 @@ const ChoreView = () => { const [timeoutId, setTimeoutId] = useState(null) const [secondsLeftToCancel, setSecondsLeftToCancel] = useState(null) const [completedDate, setCompletedDate] = useState(null) + const [confirmModelConfig, setConfirmModelConfig] = useState({}) + useEffect(() => { Promise.all([ GetChoreDetailById(choreId).then(resp => { @@ -85,20 +92,20 @@ const ChoreView = () => { const generateInfoCards = chore => { const cards = [ { - icon: <CalendarMonth />, - text: 'Due Date', - subtext: moment(chore.nextDueDate).format('MM/DD/YYYY hh:mm A'), - }, - { + size: 6, icon: <PeopleAlt />, text: 'Assigned To', subtext: performers.find(p => p.id === chore.assignedTo)?.displayName, }, { - icon: <Person />, - text: 'Created By', - subtext: performers.find(p => p.id === chore.createdBy)?.displayName, + size: 6, + icon: <CalendarMonth />, + text: 'Due Date', + subtext: chore.nextDueDate + ? moment(chore.nextDueDate).fromNow() + : 'N/A', }, + // { // icon: <TextFields />, // text: 'Frequency', @@ -107,35 +114,42 @@ const ChoreView = () => { // chore.frequencyType.slice(1), // }, { + size: 6, icon: <Checklist />, text: 'Total Completed', - subtext: `${chore.totalCompletedCount}`, + subtext: `${chore.totalCompletedCount} times`, }, - // { - // icon: <Timelapse />, - // text: 'Last Completed', - // subtext: - // chore.lastCompletedDate && - // moment(chore.lastCompletedDate).format('MM/DD/YYYY hh:mm A'), - // }, { - icon: <Person />, + size: 6, + icon: <Timelapse />, text: 'Last Completed', + subtext: + // chore.lastCompletedDate && + // moment(chore.lastCompletedDate).format('MM/DD/YYYY hh:mm A'), + chore.lastCompletedDate && moment(chore.lastCompletedDate).fromNow(), + }, + { + size: 6, + icon: <Person />, + text: 'Last Performer', subtext: chore.lastCompletedDate ? `${ - chore.lastCompletedDate && - 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 || '--', + size: 6, + icon: <Person />, + text: 'Created By', + subtext: performers.find(p => p.id === chore.createdBy)?.displayName, }, + // { + // size: 12, + // icon: <Note />, + // text: 'Recent Note', + // subtext: chore.notes || '--', + // }, ] setInfoCards(cards) } @@ -184,7 +198,16 @@ const ChoreView = () => { setTimeoutId(id) } - + const handleSkippingTask = () => { + SkipChore(choreId).then(response => { + if (response.ok) { + response.json().then(data => { + const newChore = data.res + setChore(newChore) + }) + } + }) + } return ( <Container maxWidth='sm' @@ -195,46 +218,88 @@ const ChoreView = () => { justifyContent: 'space-between', }} > - <Box> + <Box + sx={{ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + textAlign: 'center', + }} + > <Typography level='h3' - textAlign={'center'} + // textAlign={'center'} sx={{ - mt: 2, - mb: 4, + mt: 1, + mb: 0.5, }} > {chore.name} </Typography> + <Chip startDecorator={<CalendarMonth />} size='lg' sx={{ mb: 4 }}> + {chore.nextDueDate + ? `Due at ${moment(chore.nextDueDate).format('MM/DD/YYYY hh:mm A')}` + : 'N/A'} + </Chip> + </Box> + <Box> + <Typography level='title-md' sx={{ mb: 0.5 }}> + Details + </Typography> + + <Sheet + sx={{ + mb: 1, + borderRadius: 'lg', + p: 2, + }} + variant='outlined' + > + <Grid container spacing={1}> + {infoCards.map((detail, index) => ( + <Grid item xs={4} key={index}> + {/* divider between the list items: */} - <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> + <ListItem key={index}> <ListItemContent> - <Typography level='body1' sx={{ fontWeight: 'md' }}> - {info.text} - </Typography> - <Typography level='body1' color='text.tertiary'> - {info.subtext ? info.subtext : '--'} + <Typography level='body-xs' sx={{ fontWeight: 'md' }}> + {detail.text} </Typography> + <Chip + color='primary' + size='md' + startDecorator={detail.icon} + > + {detail.subtext ? detail.subtext : '--'} + </Chip> </ListItemContent> </ListItem> - </Sheet> - </Grid> - ))} - </Grid> + </Grid> + ))} + </Grid> + </Sheet> + {chore.notes && ( + <> + <Typography level='title-md' sx={{ mb: 1 }}> + Previous note: + </Typography> + <Sheet variant='outlined' sx={{ p: 2, borderRadius: 'lg' }}> + <Typography level='body-md' sx={{ mb: 1 }}> + {chore.notes || '--'} + </Typography> + </Sheet> + </> + )} </Box> {/* <Divider sx={{ my: 2, }} /> */} - + <Typography level='title-md' sx={{ mt: 1 }}> + Actions + </Typography> <Card sx={{ p: 2, @@ -243,27 +308,65 @@ 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={{ - my: 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' sx={{ width: 400 }}> + <FormControl size='sm'> <Checkbox defaultChecked={completedDate !== null} checked={completedDate !== null} @@ -279,10 +382,23 @@ const ChoreView = () => { } }} overlay - sx={{ - my: 1, - }} - label={<Typography level='body2'>Set completion date</Typography>} + sx={ + { + // my: 1, + } + } + label={ + <Typography + level='body-sm' + sx={{ + // center vertically + display: 'flex', + alignItems: 'center', + }} + > + Specify completion date + </Typography> + } /> </FormControl> {completedDate !== null && ( @@ -295,22 +411,10 @@ const ChoreView = () => { }} /> )} - {completedDate === null && ( - // placeholder for the completion date with margin: - <Box - sx={{ - height: 56, - }} - /> - )} <Button fullWidth size='lg' - sx={{ - height: 50, - mb: 2, - }} onClick={handleTaskCompletion} disabled={isPendingCompletion} color={isPendingCompletion ? 'danger' : 'success'} @@ -318,49 +422,82 @@ const ChoreView = () => { > <Box>Mark as done</Box> </Button> - {/* <Button - sx={{ - borderRadius: '32px', - mt: 1, - height: 50, - zIndex: 1, - }} - onClick={() => { - Navigate('/my/chores') - }} - color={isPendingCompletion ? 'danger' : 'success'} - startDecorator={isPendingCompletion ? <Close /> : <Check />} - fullWidth - > - <Box>Mark as {isPendingCompletion ? 'completed' : 'done'}</Box> - </Button> */} - </Card> + <Divider sx={{ my: 0.5 }}>or</Divider> - <Snackbar - open={isPendingCompletion} - endDecorator={ + <Box + sx={{ + display: 'flex', + flexDirection: 'row', + gap: 1, + alignContent: 'center', + justifyContent: 'center', + }} + > <Button + fullWidth + size='lg' onClick={() => { - if (timeoutId) { - clearTimeout(timeoutId) - setIsPendingCompletion(false) - setTimeoutId(null) - setSecondsLeftToCancel(null) // Reset or adjust as needed - } + 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' - color='danger' - startDecorator={<CancelScheduleSend />} + fullWidth + onClick={() => { + navigate(`/chores/${choreId}/history`) + }} > - Cancel + History </Button> - } - > - <Typography level='body-md' textAlign={'center'}> - Task will be marked as completed in {secondsLeftToCancel} seconds - </Typography> - </Snackbar> + </Box> + + <Snackbar + open={isPendingCompletion} + endDecorator={ + <Button + onClick={() => { + if (timeoutId) { + clearTimeout(timeoutId) + setIsPendingCompletion(false) + setTimeoutId(null) + setSecondsLeftToCancel(null) // Reset or adjust as needed + } + }} + size='lg' + variant='outlined' + color='danger' + startDecorator={<CancelScheduleSend />} + > + Cancel + </Button> + } + > + <Typography level='body-md' textAlign={'center'}> + Task will be marked as completed in {secondsLeftToCancel} seconds + </Typography> + </Snackbar> + <ConfirmationModal config={confirmModelConfig} /> + </Card> </Container> ) } diff --git a/src/views/ChoreEdit/RepeatSection.jsx b/src/views/ChoreEdit/RepeatSection.jsx index 99f196f..bdf6738 100644 --- a/src/views/ChoreEdit/RepeatSection.jsx +++ b/src/views/ChoreEdit/RepeatSection.jsx @@ -509,7 +509,7 @@ const RepeatSection = ({ }} > Is this something that should be done when a thing state changes?{' '} - {!isPlusAccount(userProfile) && ( + {userProfile && !isPlusAccount(userProfile) && ( <Chip variant='soft' color='warning'> Not available in Basic Plan </Chip> diff --git a/src/views/Chores/ChoreCard.jsx b/src/views/Chores/ChoreCard.jsx index 08a5406..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 { @@ -38,7 +39,7 @@ import moment from 'moment' import React, { useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { API_URL } from '../../Config' -import { MarkChoreComplete } from '../../utils/Fetcher' +import { MarkChoreComplete, SkipChore } from '../../utils/Fetcher' import { Fetch } from '../../utils/TokenManager' import ConfirmationModal from '../Modals/Inputs/ConfirmationModal' import DateModal from '../Modals/Inputs/DateModal' @@ -107,6 +108,9 @@ const ChoreCard = ({ const handleEdit = () => { navigate(`/chores/${chore.id}/edit`) } + const handleView = () => { + navigate(`/chores/${chore.id}`) + } const handleDelete = () => { setConfirmModelConfig({ isOpen: true, @@ -521,13 +525,7 @@ const ChoreCard = ({ </MenuItem> <MenuItem onClick={() => { - Fetch(`${API_URL}/chores/${chore.id}/skip`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({}), - }).then(response => { + SkipChore(chore.id).then(response => { if (response.ok) { response.json().then(data => { const newChore = data.res @@ -585,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/Landing/FeaturesSection.jsx b/src/views/Landing/FeaturesSection.jsx index 4133d0a..32dcbc8 100644 --- a/src/views/Landing/FeaturesSection.jsx +++ b/src/views/Landing/FeaturesSection.jsx @@ -25,44 +25,39 @@ const FeatureIcon = styled('div')({ const CardData = [ { title: 'Open Source & Transparent', - headline: 'Built for the Community', + description: - 'Donetick is a community-driven, open-source project. Contribute, customize, and make task management truly yours.', + 'Donetick is open source software. You can view, modify, and contribute to the code on GitHub.', icon: CodeRounded, }, { title: 'Circles: Your Task Hub', - headline: 'Share & Conquer Together', description: - 'Create circles for your family, friends, or team. Easily share tasks and track progress within each group.', + 'build with sharing in mind. invite other to the circle and you can assign tasks to each other. and only see the tasks the should be shared', icon: GroupRounded, }, { title: 'Track Your Progress', - headline: "See Who's Done What", description: - 'View a history of task completion for each member of your circles. Celebrate successes and stay on top of your goals.', + 'View a history of completed tasks. or use things to track simply things!', icon: HistoryRounded, }, { - title: 'Automated Chore Scheduling', - headline: 'Fully Customizable Recurring Tasks', + title: 'Automated Task Scheduling', description: - 'Set up chores to repeat daily, weekly, or monthly. Donetick will automatically assign and track each task for you.', + 'Set up Tasks to repeat daily, weekly, or monthly, or maybe specifc day in specifc months? Donetick have a flexible scheduling system', icon: AutoAwesomeMosaicOutlined, }, { title: 'Automated Task Assignment', - headline: 'Share Responsibilities Equally', description: - 'can automatically assigns tasks to each member of your circle. Randomly or based on past completion.', + 'For shared tasks, Donetick can randomly rotate assignments or choose based on last completion or least assigned.', icon: AutoAwesomeRounded, }, { title: 'Integrations & Webhooks', - headline: 'API & 3rd Party Integrations', description: - 'Connect Donetick with your favorite apps and services. Trigger tasks based on events from other platforms.', + 'Donetick can update things programmatically with API call. you can update things from other services like IFTTT, Homeassistant or even your own service', icon: Webhook, }, ] @@ -80,7 +75,7 @@ function Feature2({ icon: Icon, title, headline, description, index }) { <FeatureIcon> <Icon color='primary' - style={{ Width: '30px', height: '30px' }} + style={{ Width: '30px', height: '30px', fontSize: '30px' }} stroke={1.5} /> </FeatureIcon> @@ -106,7 +101,7 @@ function FeaturesSection() { <Feature2 icon={feature.icon} title={feature.title} - headline={feature.headline} + // headline={feature.headline} description={feature.description} index={index} key={index} @@ -128,7 +123,7 @@ function FeaturesSection() { </Container> <Typography level='h4' mt={2} mb={4}> - Features Overview + Why Donetick? </Typography> <Container maxWidth={'lg'} sx={{ mb: 8 }}></Container> diff --git a/src/views/Landing/Footer.jsx b/src/views/Landing/Footer.jsx new file mode 100644 index 0000000..11a011a --- /dev/null +++ b/src/views/Landing/Footer.jsx @@ -0,0 +1,127 @@ +import LogoSVG from '@/assets/logo.svg' +import { Card, Grid } from '@mui/joy' +import Box from '@mui/joy/Box' +import Link from '@mui/joy/Link' +import Typography from '@mui/joy/Typography' +import * as React from 'react' + +function Footer() { + return ( + <Card + data-aos-landing-footer + data-aos-delay={200} + data-aos-anchor='[data-aos-landing-footer]' + data-aos='zoom-in-up' + > + <Grid + container + component='footer' + sx={{ + display: 'flex', + justifyContent: 'space-around', + p: 4, + // borderTop: '1px solid', + bottom: 0, + width: '100%', + }} + > + <Box sx={{ display: 'flex', alignItems: 'center' }}> + <div className='logo'> + <img src={LogoSVG} alt='logo' width='64px' height='64px' /> + </div> + <Box className='flex items-center gap-2'> + <Typography + level='title-lg' + sx={{ + fontWeight: 700, + fontSize: 24, + }} + > + Done + <span + style={{ + color: '#06b6d4', + fontWeight: 600, + }} + > + tick✓ + </span> + </Typography> + <span + style={{ + fontSize: 12, + fontWeight: 700, + position: 'relative', + top: 12, + right: 45, + }} + > + Beta + </span> + </Box> + </Box> + <Box> + <Typography level='body2' fontWeight='bold' mb={1}> + Github + </Typography> + <Link + href='https://github.com/donetick/core' + level='body2' + sx={{ display: 'block' }} + > + Core(Backend) + </Link> + <Link + href='https://github.com/donetick/frontend' + level='body2' + sx={{ display: 'block' }} + > + Frontend + </Link> + <Link + href='https://github.com/donetick/hassio-addons' + level='body2' + sx={{ display: 'block' }} + > + Home Assistant Addon + </Link> + <Link + href='https://github.com/orgs/Donetick/packages' + level='body2' + sx={{ display: 'block' }} + > + Packages + </Link> + </Box> + <Box> + <Typography level='body2' fontWeight='bold' mb={1}> + Links + </Typography> + + <Link disabled={true} level='body2' sx={{ display: 'block' }}> + Roadmap(soon) + </Link> + <Link disabled={true} level='body2' sx={{ display: 'block' }}> + Documentation(soon) + </Link> + <Link disabled={true} level='body2' sx={{ display: 'block' }}> + Changelog(soon) + </Link> + </Box> + {/* <Box> + <Typography level='body2' fontWeight='bold' mb={1}> + Others + </Typography> + <Link href='#' level='body2' sx={{ display: 'block' }}> + Telegram Integration + </Link> + <Link href='#' level='body2' sx={{ display: 'block' }}> + Slash Commands + </Link> + </Box> */} + </Grid> + </Card> + ) +} + +export default Footer diff --git a/src/views/Landing/Landing.jsx b/src/views/Landing/Landing.jsx index a8b650d..4ca1b60 100644 --- a/src/views/Landing/Landing.jsx +++ b/src/views/Landing/Landing.jsx @@ -1,4 +1,4 @@ -import { Container, Grid } from '@mui/joy' +import { Box, Container, Grid } from '@mui/joy' import AOS from 'aos' import 'aos/dist/aos.css' import { useEffect } from 'react' @@ -8,6 +8,7 @@ import DemoHistory from './DemoHistory' import DemoMyChore from './DemoMyChore' import DemoScheduler from './DemoScheduler' import FeaturesSection from './FeaturesSection' +import Footer from './Footer' import HomeHero from './HomeHero' const Landing = () => { const Navigate = useNavigate() @@ -39,6 +40,17 @@ const Landing = () => { </Grid> <FeaturesSection /> {/* <PricingSection /> */} + <Box + sx={{ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + mt: 5, + mb: 5, + }} + ></Box> + + <Footer /> </Container> ) } 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 diff --git a/src/views/components/NavBar.jsx b/src/views/components/NavBar.jsx index 2da09f5..d3708ae 100644 --- a/src/views/components/NavBar.jsx +++ b/src/views/components/NavBar.jsx @@ -21,7 +21,7 @@ import { Typography, } from '@mui/joy' import { useState } from 'react' -import { useLocation } from 'react-router-dom' +import { useLocation, useNavigate } from 'react-router-dom' import { version } from '../../../package.json' import NavBarLink from './NavBarLink' const links = [ @@ -63,6 +63,7 @@ const links = [ ] const NavBar = () => { + const navigate = useNavigate() const [drawerOpen, setDrawerOpen] = useState(false) const [openDrawer, closeDrawer] = [ () => setDrawerOpen(true), @@ -89,7 +90,12 @@ const NavBar = () => { <IconButton size='sm' variant='plain' onClick={() => setDrawerOpen(true)}> <MenuRounded /> </IconButton> - <Box className='flex items-center gap-2'> + <Box + className='flex items-center gap-2' + onClick={() => { + navigate('/my/chores') + }} + > <img component='img' src={Logo} width='34' /> <Typography level='title-lg' |