Initial Activity View
Add New Colors.jsx Allow Dark model Toggle from the Navbar
This commit is contained in:
395
src/views/User/UserActivities.jsx
Normal file
395
src/views/User/UserActivities.jsx
Normal file
@@ -0,0 +1,395 @@
|
||||
import CancelIcon from '@mui/icons-material/Cancel'
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle'
|
||||
import CircleIcon from '@mui/icons-material/Circle'
|
||||
import { Cell, Legend, Pie, PieChart, Tooltip } from 'recharts'
|
||||
|
||||
import { Toll } from '@mui/icons-material'
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
Chip,
|
||||
Container,
|
||||
Divider,
|
||||
Grid,
|
||||
Stack,
|
||||
Tab,
|
||||
TabList,
|
||||
Tabs,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import React, { useEffect } from 'react'
|
||||
import { UserContext } from '../../contexts/UserContext'
|
||||
import { useChores, useChoresHistory } from '../../queries/ChoreQueries'
|
||||
import { ChoresGrouper } from '../../utils/Chores'
|
||||
import { TASK_COLOR } from '../../utils/Colors.jsx'
|
||||
import LoadingComponent from '../components/Loading'
|
||||
|
||||
const groupByDate = history => {
|
||||
const aggregated = {}
|
||||
for (let i = 0; i < history.length; i++) {
|
||||
const item = history[i]
|
||||
const date = new Date(item.completedAt).toLocaleDateString()
|
||||
if (!aggregated[date]) {
|
||||
aggregated[date] = []
|
||||
}
|
||||
aggregated[date].push(item)
|
||||
}
|
||||
return aggregated
|
||||
}
|
||||
|
||||
const ChoreHistoryItem = ({ time, name, points, status }) => {
|
||||
const statusIcon = {
|
||||
completed: <CheckCircleIcon color='success' />,
|
||||
missed: <CancelIcon color='error' />,
|
||||
pending: <CircleIcon color='neutral' />,
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack direction='row' alignItems='center' spacing={2}>
|
||||
<Typography level='body-md' sx={{ minWidth: 80 }}>
|
||||
{time}
|
||||
</Typography>
|
||||
<Box>
|
||||
{statusIcon[status] ? statusIcon[status] : statusIcon['completed']}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
minHeight: 40,
|
||||
// center vertically:
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
maxWidth: '50vw',
|
||||
}}
|
||||
level='body-md'
|
||||
>
|
||||
{name}
|
||||
</Typography>
|
||||
{points && (
|
||||
<Chip size='sm' color='success' startDecorator={<Toll />}>
|
||||
{`${points} points`}
|
||||
</Chip>
|
||||
)}
|
||||
</Box>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
const ChoreHistoryTimeline = ({ history }) => {
|
||||
const groupedHistory = groupByDate(history)
|
||||
|
||||
return (
|
||||
<Container sx={{ p: 2 }}>
|
||||
<Typography level='h4' sx={{ mb: 2 }}>
|
||||
Activities Timeline
|
||||
</Typography>
|
||||
|
||||
{Object.entries(groupedHistory).map(([date, items]) => (
|
||||
<Box key={date} sx={{ mb: 4 }}>
|
||||
<Typography level='title-sm' sx={{ mb: 0.5 }}>
|
||||
{new Date(date).toLocaleDateString([], {
|
||||
weekday: 'long',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</Typography>
|
||||
<Divider />
|
||||
<Stack spacing={1}>
|
||||
{items.map(record => (
|
||||
<>
|
||||
<ChoreHistoryItem
|
||||
key={record.id}
|
||||
time={new Date(record.completedAt).toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})}
|
||||
name={record.choreName}
|
||||
points={record.points}
|
||||
status={record.status}
|
||||
/>
|
||||
</>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
))}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const renderPieChart = (data, size, isPrimary) => (
|
||||
<PieChart width={size} height={size}>
|
||||
<Pie
|
||||
data={data}
|
||||
dataKey='value'
|
||||
nameKey='label'
|
||||
cx='50%'
|
||||
cy='50%'
|
||||
innerRadius={isPrimary ? size / 4 : size / 6}
|
||||
paddingAngle={5}
|
||||
cornerRadius={5}
|
||||
>
|
||||
{data.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
{isPrimary && <Tooltip />}
|
||||
{isPrimary && (
|
||||
<Legend
|
||||
layout='horizontal'
|
||||
verticalAlign='bottom'
|
||||
align='center'
|
||||
// format as : {entry.payload.label}: {value}
|
||||
iconType='circle'
|
||||
formatter={(label, value) => `${label}: ${value.payload.value}`}
|
||||
/>
|
||||
)}
|
||||
</PieChart>
|
||||
)
|
||||
|
||||
const UserActivites = () => {
|
||||
const { userProfile } = React.useContext(UserContext)
|
||||
const [tabValue, setTabValue] = React.useState(30)
|
||||
const [selectedHistory, setSelectedHistory] = React.useState([])
|
||||
const [selectedChart, setSelectedChart] = React.useState('history')
|
||||
|
||||
const [historyPieChartData, setHistoryPieChartData] = React.useState([])
|
||||
const [choreDuePieChartData, setChoreDuePieChartData] = React.useState([])
|
||||
const [choresAssignedChartData, setChoresAssignedChartData] = React.useState(
|
||||
[],
|
||||
)
|
||||
const [choresPriorityChartData, setChoresPriorityChartData] = React.useState(
|
||||
[],
|
||||
)
|
||||
const { data: choresData, isLoading: isChoresLoading } = useChores(true)
|
||||
const {
|
||||
data: choresHistory,
|
||||
isChoresHistoryLoading,
|
||||
handleLimitChange: refetchHistory,
|
||||
} = useChoresHistory(tabValue ? tabValue : 30)
|
||||
useEffect(() => {
|
||||
if (!isChoresHistoryLoading && !isChoresLoading && choresHistory) {
|
||||
const enrichedHistory = choresHistory.res.map(item => {
|
||||
const chore = choresData.res.find(chore => chore.id === item.choreId)
|
||||
return {
|
||||
...item,
|
||||
choreName: chore?.name,
|
||||
}
|
||||
})
|
||||
|
||||
setSelectedHistory(enrichedHistory)
|
||||
setHistoryPieChartData(generateHistoryPieChartData(enrichedHistory))
|
||||
}
|
||||
}, [isChoresHistoryLoading, isChoresLoading, choresHistory])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isChoresLoading && choresData) {
|
||||
const choreDuePieChartData = generateChoreDuePieChartData(choresData.res)
|
||||
setChoreDuePieChartData(choreDuePieChartData)
|
||||
setChoresAssignedChartData(generateChoreAssignedChartData(choresData.res))
|
||||
setChoresPriorityChartData(
|
||||
generateChorePriorityPieChartData(choresData.res),
|
||||
)
|
||||
}
|
||||
}, [isChoresLoading, choresData])
|
||||
|
||||
const generateChoreAssignedChartData = chores => {
|
||||
var assignedToMe = 0
|
||||
var assignedToOthers = 0
|
||||
chores.forEach(chore => {
|
||||
if (chore.assignedTo === userProfile.id) {
|
||||
assignedToMe++
|
||||
} else assignedToOthers++
|
||||
})
|
||||
|
||||
const group = []
|
||||
if (assignedToMe > 0) {
|
||||
group.push({
|
||||
label: `Assigned to me`,
|
||||
value: assignedToMe,
|
||||
color: TASK_COLOR.ASSIGNED_TO_ME,
|
||||
id: 1,
|
||||
})
|
||||
}
|
||||
if (assignedToOthers > 0) {
|
||||
group.push({
|
||||
label: `Assigned to others`,
|
||||
value: assignedToOthers,
|
||||
color: TASK_COLOR.ASSIGNED_TO_OTHERS,
|
||||
id: 2,
|
||||
})
|
||||
}
|
||||
return group
|
||||
}
|
||||
|
||||
const generateChoreDuePieChartData = chores => {
|
||||
const groups = ChoresGrouper('due_date', chores)
|
||||
return groups
|
||||
.map(group => {
|
||||
return {
|
||||
label: group.name,
|
||||
value: group.content.length,
|
||||
color: group.color,
|
||||
id: group.name,
|
||||
}
|
||||
})
|
||||
.filter(item => item.value > 0)
|
||||
}
|
||||
const generateChorePriorityPieChartData = chores => {
|
||||
const groups = ChoresGrouper('priority', chores)
|
||||
return groups
|
||||
.map(group => {
|
||||
return {
|
||||
label: group.name,
|
||||
value: group.content.length,
|
||||
color: group.color,
|
||||
id: group.name,
|
||||
}
|
||||
})
|
||||
.filter(item => item.value > 0)
|
||||
}
|
||||
|
||||
const generateHistoryPieChartData = history => {
|
||||
const totalCompleted = history.filter(
|
||||
item => item.dueDate > item.completedAt,
|
||||
).length
|
||||
const totalLate = history.filter(
|
||||
item => item.dueDate < item.completedAt,
|
||||
).length
|
||||
|
||||
return [
|
||||
{
|
||||
label: `On time`,
|
||||
value: totalCompleted,
|
||||
color: '#4ec1a2',
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
label: `Late`,
|
||||
value: totalLate,
|
||||
color: '#f6ad55',
|
||||
id: 2,
|
||||
},
|
||||
]
|
||||
}
|
||||
if (isChoresHistoryLoading || isChoresLoading) {
|
||||
return <LoadingComponent />
|
||||
}
|
||||
const COLORS = historyPieChartData.map(item => item.color)
|
||||
const chartData = {
|
||||
history: {
|
||||
data: historyPieChartData,
|
||||
title: 'Status',
|
||||
description: 'Completed tasks status',
|
||||
},
|
||||
due: {
|
||||
data: choreDuePieChartData,
|
||||
title: 'Due Date',
|
||||
description: 'Current tasks due date',
|
||||
},
|
||||
assigned: {
|
||||
data: choresAssignedChartData,
|
||||
title: 'Assignee',
|
||||
description: 'Tasks assigned to you vs others',
|
||||
},
|
||||
priority: {
|
||||
data: choresPriorityChartData,
|
||||
title: 'Priority',
|
||||
description: 'Tasks by priority',
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<Container
|
||||
maxWidth='md'
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Tabs
|
||||
onChange={(e, tabValue) => {
|
||||
setTabValue(tabValue)
|
||||
refetchHistory(tabValue)
|
||||
}}
|
||||
defaultValue={7}
|
||||
sx={{
|
||||
py: 0.5,
|
||||
borderRadius: 16,
|
||||
maxWidth: 400,
|
||||
mb: 1,
|
||||
}}
|
||||
>
|
||||
<TabList
|
||||
disableUnderline
|
||||
sx={{
|
||||
borderRadius: 16,
|
||||
backgroundColor: 'background.paper',
|
||||
boxShadow: 1,
|
||||
justifyContent: 'space-evenly',
|
||||
}}
|
||||
>
|
||||
{[
|
||||
{ label: '7 Days', value: 7 },
|
||||
{ label: '30 Days', value: 30 },
|
||||
{ label: '90 Days', value: 90 },
|
||||
].map((tab, index) => (
|
||||
<Tab
|
||||
key={index}
|
||||
sx={{
|
||||
borderRadius: 16,
|
||||
color: 'text.secondary',
|
||||
'&.Mui-selected': {
|
||||
color: 'text.primary',
|
||||
backgroundColor: 'primary.light',
|
||||
},
|
||||
}}
|
||||
disableIndicator
|
||||
value={tab.value}
|
||||
>
|
||||
{tab.label}
|
||||
</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
</Tabs>
|
||||
<Box sx={{ mb: 4 }}>
|
||||
<Typography level='h4' textAlign='center'>
|
||||
{chartData[selectedChart].title}
|
||||
</Typography>
|
||||
<Typography level='body-xs' textAlign='center'>
|
||||
{chartData[selectedChart].description}
|
||||
</Typography>
|
||||
{renderPieChart(chartData[selectedChart].data, 250, true)}
|
||||
</Box>
|
||||
<Grid container spacing={1}>
|
||||
{Object.entries(chartData)
|
||||
.filter(([key]) => key !== selectedChart)
|
||||
.map(([key, { data, title }]) => (
|
||||
<Grid item key={key} xs={4}>
|
||||
<Card
|
||||
onClick={() => setSelectedChart(key)}
|
||||
sx={{ cursor: 'pointer', p: 1 }}
|
||||
>
|
||||
<Typography textAlign='center' level='body-xs' mb={-2}>
|
||||
{title}
|
||||
</Typography>
|
||||
{renderPieChart(data, 75, false)}
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
<ChoreHistoryTimeline history={selectedHistory} />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserActivites
|
||||
Reference in New Issue
Block a user