From 2657469964e24ffbeb905024532120395f6e797c Mon Sep 17 00:00:00 2001 From: Mo Tarbin Date: Sun, 30 Jun 2024 18:55:39 -0400 Subject: move to Donetick Org, First commit frontend --- src/views/History/BigChip.jsx | 26 +++ src/views/History/ChoreHistory.jsx | 344 +++++++++++++++++++++++++++++++++++++ src/views/History/InfoCard.jsx | 26 +++ 3 files changed, 396 insertions(+) create mode 100644 src/views/History/BigChip.jsx create mode 100644 src/views/History/ChoreHistory.jsx create mode 100644 src/views/History/InfoCard.jsx (limited to 'src/views/History') 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 ( + + {props.children} + + ) +} + +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: ( + + + + ), + text: `${histories.length} completed`, + subtext: `${Object.keys(userHistories).length} users contributed`, + }, + { + icon: ( + + + + ), + text: `Completed within ${moment + .duration(averageDelayMoment) + .humanize()}`, + subtext: `Maximum delay was ${moment + .duration(maxDelayMoment) + .humanize()}`, + }, + { + icon: , + text: `${ + performers.find(p => p.userId === Number(userCompletedByMost)) + ?.displayName + } completed most`, + subtext: `${userHistories[userCompletedByMost]} time/s`, + }, + ] + if (userCompletedByLeast !== userCompletedByMost) { + historyInfo.push({ + icon: ( + + { + performers.find(p => p.userId === userCompletedByLeast) + ?.displayName + } + + ), + 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 // Show loading indicator + } + if (!choreHistory.length) { + return ( + + + + + No History Yet + + + You haven't completed any tasks. Once you start finishing tasks, + they'll show up here. + + + + ) + } + + return ( + + + Summary: + + {/* + + + + + + + + + {choreHistory.length} completed + + + {Object.keys(userHistory).length} users contributed + + + + */} + + {historyInfo.map((info, index) => ( + + + + {info.icon} + + + {info.text} + + + {info.subtext} + + + + + + ))} + + {/* User History Cards */} + + History: + + + {/* Chore History List (Updated Style) */} + + + {choreHistory.map((chore, index) => ( + <> + + {' '} + {/* Adjusted spacing and alignment */} + + + {performers + .find(p => p.userId === chore.completedBy) + ?.displayName?.charAt(0) || '?'} + + + + {' '} + {/* Removed vertical margin */} + + + {moment(chore.completedAt).format('ddd MM/DD/yyyy HH:mm')} + + + + {chore.dueDate && chore.completedAt > chore.dueDate + ? 'Late' + : 'On Time'} + + + + + { + performers.find(p => p.userId === chore.completedBy) + ?.displayName + } + {' '} + completed + {chore.completedBy !== chore.assignedTo && ( + <> + {', '} + assigned to{' '} + + { + performers.find(p => p.userId === chore.assignedTo) + ?.displayName + } + + + )} + + {chore.dueDate && ( + + Due: {moment(chore.dueDate).format('ddd MM/DD/yyyy')} + + )} + {chore.notes && ( + + Note: {chore.notes} + + )} + + + {index < choreHistory.length - 1 && ( + <> + + {/* time between two completion: */} + {index < choreHistory.length - 1 && + choreHistory[index + 1].completedAt && ( + + {formatTimeDifference( + chore.completedAt, + choreHistory[index + 1].completedAt, + )}{' '} + before + + )} + + + )} + + ))} + + + + ) +} + +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 ( + + + + + + You've completed + 12345 Chores + + + ) +} + +export default InfoCard -- cgit