aboutsummaryrefslogtreecommitdiffstats
path: root/src/views/History
diff options
context:
space:
mode:
authorLibravatar Mo Tarbin <mhed.t91@gmail.com>2024-06-30 18:55:39 -0400
committerLibravatar Mo Tarbin <mhed.t91@gmail.com>2024-06-30 18:55:39 -0400
commit2657469964e24ffbeb905024532120395f6e797c (patch)
tree2fe9db8a4ecfa92d854ca94f7586d81163c8bd25 /src/views/History
downloaddonetick-frontend-2657469964e24ffbeb905024532120395f6e797c.tar.gz
donetick-frontend-2657469964e24ffbeb905024532120395f6e797c.tar.bz2
donetick-frontend-2657469964e24ffbeb905024532120395f6e797c.zip
move to Donetick Org, First commit frontend
Diffstat (limited to 'src/views/History')
-rw-r--r--src/views/History/BigChip.jsx26
-rw-r--r--src/views/History/ChoreHistory.jsx344
-rw-r--r--src/views/History/InfoCard.jsx26
3 files changed, 396 insertions, 0 deletions
diff --git a/src/views/History/BigChip.jsx b/src/views/History/BigChip.jsx
new file mode 100644
index 0000000..4987367
--- /dev/null
+++ b/src/views/History/BigChip.jsx
@@ -0,0 +1,26 @@
+import Chip from '@mui/joy/Chip'
+import * as React from 'react'
+
+function BigChip(props) {
+ return (
+ <Chip
+ variant='outlined'
+ color='primary'
+ size='lg' // Adjust to your desired size
+ sx={{
+ fontSize: '1rem', // Example: Increase font size
+ padding: '1rem', // Example: Increase padding
+ height: '1rem', // Adjust to your desired height
+ // Add other custom styles as needed
+ }}
+ {...props}
+ >
+ {props.children}
+ </Chip>
+ )
+}
+
+export default BigChip
+BigChip.propTypes = {
+ ...Chip.propTypes,
+}
diff --git a/src/views/History/ChoreHistory.jsx b/src/views/History/ChoreHistory.jsx
new file mode 100644
index 0000000..22ea6a9
--- /dev/null
+++ b/src/views/History/ChoreHistory.jsx
@@ -0,0 +1,344 @@
+import { Checklist, EventBusy, Timelapse } from '@mui/icons-material'
+import {
+ Avatar,
+ Box,
+ Button,
+ Chip,
+ CircularProgress,
+ Container,
+ Grid,
+ List,
+ ListDivider,
+ ListItem,
+ ListItemContent,
+ ListItemDecorator,
+ Sheet,
+ Typography,
+} from '@mui/joy'
+import moment from 'moment'
+import React, { useEffect, useState } from 'react'
+import { Link, useParams } from 'react-router-dom'
+import { API_URL } from '../../Config'
+import { GetAllCircleMembers } from '../../utils/Fetcher'
+import { Fetch } from '../../utils/TokenManager'
+
+const ChoreHistory = () => {
+ const [choreHistory, setChoresHistory] = useState([])
+ const [userHistory, setUserHistory] = useState([])
+ const [performers, setPerformers] = useState([])
+ const [historyInfo, setHistoryInfo] = useState([])
+
+ const [isLoading, setIsLoading] = useState(true) // Add loading state
+ const { choreId } = useParams()
+
+ useEffect(() => {
+ setIsLoading(true) // Start loading
+
+ Promise.all([
+ Fetch(`${API_URL}/chores/${choreId}/history`).then(res => res.json()),
+ GetAllCircleMembers().then(res => res.json()),
+ ])
+ .then(([historyData, usersData]) => {
+ setChoresHistory(historyData.res)
+
+ const newUserChoreHistory = {}
+ historyData.res.forEach(choreHistory => {
+ const userId = choreHistory.completedBy
+ newUserChoreHistory[userId] = (newUserChoreHistory[userId] || 0) + 1
+ })
+ setUserHistory(newUserChoreHistory)
+
+ setPerformers(usersData.res)
+ updateHistoryInfo(historyData.res, newUserChoreHistory, usersData.res)
+ })
+ .catch(error => {
+ console.error('Error fetching data:', error)
+ // Handle errors, e.g., show an error message to the user
+ })
+ .finally(() => {
+ setIsLoading(false) // Finish loading
+ })
+ }, [choreId])
+
+ const updateHistoryInfo = (histories, userHistories, performers) => {
+ // average delay for task completaion from due date:
+
+ const averageDelay =
+ histories.reduce((acc, chore) => {
+ if (chore.dueDate) {
+ // Only consider chores with a due date
+ return acc + moment(chore.completedAt).diff(chore.dueDate, 'hours')
+ }
+ return acc
+ }, 0) / histories.length
+ const averageDelayMoment = moment.duration(averageDelay, 'hours')
+ const maximumDelay = histories.reduce((acc, chore) => {
+ if (chore.dueDate) {
+ // Only consider chores with a due date
+ const delay = moment(chore.completedAt).diff(chore.dueDate, 'hours')
+ return delay > acc ? delay : acc
+ }
+ return acc
+ }, 0)
+
+ const maxDelayMoment = moment.duration(maximumDelay, 'hours')
+
+ // find max value in userHistories:
+ const userCompletedByMost = Object.keys(userHistories).reduce((a, b) =>
+ userHistories[a] > userHistories[b] ? a : b,
+ )
+ const userCompletedByLeast = Object.keys(userHistories).reduce((a, b) =>
+ userHistories[a] < userHistories[b] ? a : b,
+ )
+
+ const historyInfo = [
+ {
+ icon: (
+ <Avatar>
+ <Checklist />
+ </Avatar>
+ ),
+ text: `${histories.length} completed`,
+ subtext: `${Object.keys(userHistories).length} users contributed`,
+ },
+ {
+ icon: (
+ <Avatar>
+ <Timelapse />
+ </Avatar>
+ ),
+ text: `Completed within ${moment
+ .duration(averageDelayMoment)
+ .humanize()}`,
+ subtext: `Maximum delay was ${moment
+ .duration(maxDelayMoment)
+ .humanize()}`,
+ },
+ {
+ icon: <Avatar></Avatar>,
+ text: `${
+ performers.find(p => p.userId === Number(userCompletedByMost))
+ ?.displayName
+ } completed most`,
+ subtext: `${userHistories[userCompletedByMost]} time/s`,
+ },
+ ]
+ 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)
+ }
+
+ function formatTimeDifference(startDate, endDate) {
+ const diffInMinutes = moment(startDate).diff(endDate, 'minutes')
+ let timeValue = diffInMinutes
+ let unit = 'minute'
+
+ if (diffInMinutes >= 60) {
+ const diffInHours = moment(startDate).diff(endDate, 'hours')
+ timeValue = diffInHours
+ unit = 'hour'
+
+ if (diffInHours >= 24) {
+ const diffInDays = moment(startDate).diff(endDate, 'days')
+ timeValue = diffInDays
+ unit = 'day'
+ }
+ }
+
+ return `${timeValue} ${unit}${timeValue !== 1 ? 's' : ''}`
+ }
+ if (isLoading) {
+ return <CircularProgress /> // Show loading indicator
+ }
+ if (!choreHistory.length) {
+ return (
+ <Container
+ maxWidth='md'
+ sx={{
+ textAlign: 'center',
+ display: 'flex',
+ // make sure the content is centered vertically:
+ alignItems: 'center',
+ justifyContent: 'center',
+ flexDirection: 'column',
+ height: '50vh',
+ }}
+ >
+ <EventBusy
+ sx={{
+ fontSize: '6rem',
+ // color: 'text.disabled',
+ mb: 1,
+ }}
+ />
+
+ <Typography level='h3' gutterBottom>
+ No History Yet
+ </Typography>
+ <Typography level='body1'>
+ You haven't completed any tasks. Once you start finishing tasks,
+ they'll show up here.
+ </Typography>
+ <Button variant='soft' sx={{ mt: 2 }}>
+ <Link to='/my/chores'>Go back to chores</Link>
+ </Button>
+ </Container>
+ )
+ }
+
+ return (
+ <Container maxWidth='md'>
+ <Typography level='h3' 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>
+ <ListItemContent>
+ <Typography level='body1' sx={{ fontWeight: 'md' }}>
+ {info.text}
+ </Typography>
+ <Typography level='body1' color='text.tertiary'>
+ {info.subtext}
+ </Typography>
+ </ListItemContent>
+ </ListItem>
+ </Sheet>
+ </Grid>
+ ))}
+ </Grid>
+ {/* User History Cards */}
+ <Typography level='h3' my={1.5}>
+ History:
+ </Typography>
+ <Box sx={{ borderRadius: 'sm', p: 2, boxShadow: 'md' }}>
+ {/* Chore History List (Updated Style) */}
+
+ <List sx={{ p: 0 }}>
+ {choreHistory.map((chore, index) => (
+ <>
+ <ListItem sx={{ gap: 1.5, alignItems: 'flex-start' }}>
+ {' '}
+ {/* Adjusted spacing and alignment */}
+ <ListItemDecorator>
+ <Avatar sx={{ mr: 1 }}>
+ {performers
+ .find(p => p.userId === chore.completedBy)
+ ?.displayName?.charAt(0) || '?'}
+ </Avatar>
+ </ListItemDecorator>
+ <ListItemContent sx={{ my: 0 }}>
+ {' '}
+ {/* Removed vertical margin */}
+ <Box
+ sx={{
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ }}
+ >
+ <Typography level='body1' sx={{ fontWeight: 'md' }}>
+ {moment(chore.completedAt).format('ddd MM/DD/yyyy HH:mm')}
+ </Typography>
+
+ <Chip>
+ {chore.dueDate && chore.completedAt > chore.dueDate
+ ? 'Late'
+ : 'On Time'}
+ </Chip>
+ </Box>
+ <Typography level='body2' color='text.tertiary'>
+ <Chip>
+ {
+ performers.find(p => p.userId === chore.completedBy)
+ ?.displayName
+ }
+ </Chip>{' '}
+ completed
+ {chore.completedBy !== chore.assignedTo && (
+ <>
+ {', '}
+ assigned to{' '}
+ <Chip>
+ {
+ performers.find(p => p.userId === chore.assignedTo)
+ ?.displayName
+ }
+ </Chip>
+ </>
+ )}
+ </Typography>
+ {chore.dueDate && (
+ <Typography level='body2' color='text.tertiary'>
+ Due: {moment(chore.dueDate).format('ddd MM/DD/yyyy')}
+ </Typography>
+ )}
+ {chore.notes && (
+ <Typography level='body2' color='text.tertiary'>
+ Note: {chore.notes}
+ </Typography>
+ )}
+ </ListItemContent>
+ </ListItem>
+ {index < choreHistory.length - 1 && (
+ <>
+ <ListDivider component='li'>
+ {/* time between two completion: */}
+ {index < choreHistory.length - 1 &&
+ choreHistory[index + 1].completedAt && (
+ <Typography level='body3' color='text.tertiary'>
+ {formatTimeDifference(
+ chore.completedAt,
+ choreHistory[index + 1].completedAt,
+ )}{' '}
+ before
+ </Typography>
+ )}
+ </ListDivider>
+ </>
+ )}
+ </>
+ ))}
+ </List>
+ </Box>
+ </Container>
+ )
+}
+
+export default ChoreHistory
diff --git a/src/views/History/InfoCard.jsx b/src/views/History/InfoCard.jsx
new file mode 100644
index 0000000..bf1ada8
--- /dev/null
+++ b/src/views/History/InfoCard.jsx
@@ -0,0 +1,26 @@
+import { AddTask } from '@mui/icons-material'
+import { Box } from '@mui/joy'
+import Card from '@mui/joy/Card'
+import CardContent from '@mui/joy/CardContent'
+import Typography from '@mui/joy/Typography'
+import * as React from 'react'
+
+function InfoCard() {
+ return (
+ <Card sx={{ minWidth: 200, maxWidth: 200 }}>
+ <CardContent>
+ <Box mb={2} sx={{ textAlign: 'left' }}>
+ <AddTask
+ sx={{
+ fontSize: '2.5em' /* Increase the font size */,
+ }}
+ />
+ </Box>
+ <Typography level='title-md'>You've completed</Typography>
+ <Typography level='body-sm'>12345 Chores</Typography>
+ </CardContent>
+ </Card>
+ )
+}
+
+export default InfoCard