Merge branch 'dev'
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "donetick",
|
"name": "donetick",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.105",
|
"version": "0.1.106",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0",
|
"node": ">=20.0.0",
|
||||||
|
|||||||
29
src/queries/ThingQueries.jsx
Normal file
29
src/queries/ThingQueries.jsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { useInfiniteQuery } from '@tanstack/react-query'
|
||||||
|
import { GetThingHistory } from '../utils/Fetcher'
|
||||||
|
|
||||||
|
export const useThingHistory = (thingId, limit = 10) => {
|
||||||
|
return useInfiniteQuery({
|
||||||
|
queryKey: ['thingHistory', thingId],
|
||||||
|
queryFn: async ({ pageParam = 0 }) => {
|
||||||
|
const response = await GetThingHistory(thingId, pageParam)
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch thing history')
|
||||||
|
}
|
||||||
|
const data = await response.json()
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
getNextPageParam: (lastPage, allPages) => {
|
||||||
|
// If the last page has fewer items than the limit, there are no more pages
|
||||||
|
if (lastPage.res.length < limit) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
// Calculate the offset for the next page
|
||||||
|
const totalItems = allPages.reduce(
|
||||||
|
(acc, page) => acc + page.res.length,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
return totalItems
|
||||||
|
},
|
||||||
|
enabled: !!thingId, // Only run query if thingId exists
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ const AuthenticationLoading = () => {
|
|||||||
const getUserProfileAndNavigateToHome = () => {
|
const getUserProfileAndNavigateToHome = () => {
|
||||||
GetUserProfile().then(data => {
|
GetUserProfile().then(data => {
|
||||||
data.json().then(data => {
|
data.json().then(data => {
|
||||||
refetchUserProfile.then(() => {
|
refetchUserProfile().then(() => {
|
||||||
// check if redirect url is set in cookie:
|
// check if redirect url is set in cookie:
|
||||||
const redirectUrl = Cookies.get('ca_redirect')
|
const redirectUrl = Cookies.get('ca_redirect')
|
||||||
if (redirectUrl) {
|
if (redirectUrl) {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
} from '@mui/joy'
|
} from '@mui/joy'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { Link, useParams } from 'react-router-dom'
|
import { Link, useParams } from 'react-router-dom'
|
||||||
import {
|
import {
|
||||||
Line,
|
Line,
|
||||||
@@ -23,40 +22,27 @@ import {
|
|||||||
XAxis,
|
XAxis,
|
||||||
YAxis,
|
YAxis,
|
||||||
} from 'recharts'
|
} from 'recharts'
|
||||||
import { GetThingHistory } from '../../utils/Fetcher'
|
import { useTheme } from '@mui/joy/styles'
|
||||||
|
import { useThingHistory } from '../../queries/ThingQueries'
|
||||||
import LoadingComponent from '../components/Loading'
|
import LoadingComponent from '../components/Loading'
|
||||||
|
|
||||||
const ThingsHistory = () => {
|
const ThingsHistory = () => {
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
const [thingsHistory, setThingsHistory] = useState([])
|
const theme = useTheme()
|
||||||
const [noMoreHistory, setNoMoreHistory] = useState(false)
|
const {
|
||||||
const [errLoading, setErrLoading] = useState(false)
|
data,
|
||||||
useEffect(() => {
|
error,
|
||||||
GetThingHistory(id, 0, 10).then(resp => {
|
isLoading,
|
||||||
if (resp.ok) {
|
fetchNextPage,
|
||||||
resp.json().then(data => {
|
hasNextPage,
|
||||||
setThingsHistory(data.res)
|
isFetchingNextPage,
|
||||||
if (data.res.length < 10) {
|
} = useThingHistory(id)
|
||||||
setNoMoreHistory(true)
|
|
||||||
}
|
// Flatten all pages of history data
|
||||||
})
|
const thingsHistory = data?.pages.flatMap(page => page.res) || []
|
||||||
} else {
|
|
||||||
setErrLoading(true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [id])
|
|
||||||
|
|
||||||
const handleLoadMore = () => {
|
const handleLoadMore = () => {
|
||||||
GetThingHistory(id, thingsHistory.length).then(resp => {
|
fetchNextPage()
|
||||||
if (resp.ok) {
|
|
||||||
resp.json().then(data => {
|
|
||||||
setThingsHistory([...thingsHistory, ...data.res])
|
|
||||||
if (data.res.length < 10) {
|
|
||||||
setNoMoreHistory(true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatTimeDifference = (startDate, endDate) => {
|
const formatTimeDifference = (startDate, endDate) => {
|
||||||
@@ -79,11 +65,11 @@ const ThingsHistory = () => {
|
|||||||
return `${timeValue} ${unit}${timeValue !== 1 ? 's' : ''}`
|
return `${timeValue} ${unit}${timeValue !== 1 ? 's' : ''}`
|
||||||
}
|
}
|
||||||
// if loading show loading spinner:
|
// if loading show loading spinner:
|
||||||
if (thingsHistory.length === 0) {
|
if (isLoading) {
|
||||||
return <LoadingComponent />
|
return <LoadingComponent />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errLoading || !thingsHistory || thingsHistory.length === 0) {
|
if (error || !thingsHistory || thingsHistory.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
maxWidth='md'
|
maxWidth='md'
|
||||||
@@ -165,9 +151,17 @@ const ThingsHistory = () => {
|
|||||||
<Line
|
<Line
|
||||||
type='monotone'
|
type='monotone'
|
||||||
dataKey='state'
|
dataKey='state'
|
||||||
stroke='#8884d8'
|
stroke={theme.palette.primary[500]}
|
||||||
activeDot={{ r: 8 }}
|
activeDot={{
|
||||||
dot={{ r: 4 }}
|
r: 8,
|
||||||
|
fill: theme.palette.primary[600],
|
||||||
|
stroke: theme.palette.primary[300],
|
||||||
|
}}
|
||||||
|
dot={{
|
||||||
|
r: 4,
|
||||||
|
fill: theme.palette.primary[500],
|
||||||
|
stroke: theme.palette.primary[300],
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</LineChart>
|
</LineChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
@@ -305,9 +299,13 @@ const ThingsHistory = () => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
color='primary'
|
color='primary'
|
||||||
onClick={handleLoadMore}
|
onClick={handleLoadMore}
|
||||||
disabled={noMoreHistory}
|
disabled={!hasNextPage || isFetchingNextPage}
|
||||||
>
|
>
|
||||||
{noMoreHistory ? 'No more history' : 'Load more'}
|
{isFetchingNextPage
|
||||||
|
? 'Loading...'
|
||||||
|
: !hasNextPage
|
||||||
|
? 'No more history'
|
||||||
|
: 'Load more'}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import CancelIcon from '@mui/icons-material/Cancel'
|
import CancelIcon from '@mui/icons-material/Cancel'
|
||||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle'
|
import CheckCircleIcon from '@mui/icons-material/CheckCircle'
|
||||||
import CircleIcon from '@mui/icons-material/Circle'
|
import CircleIcon from '@mui/icons-material/Circle'
|
||||||
import { Cell, Legend, Pie, PieChart, Tooltip } from 'recharts'
|
import { Cell, Pie, PieChart, Tooltip } from 'recharts'
|
||||||
|
|
||||||
import { EventBusy, Group, Toll } from '@mui/icons-material'
|
import { EventBusy, Group, Toll } from '@mui/icons-material'
|
||||||
import {
|
import {
|
||||||
@@ -131,44 +131,202 @@ const ChoreHistoryTimeline = ({ history }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderPieChart = (data, size, isPrimary, chartType = null) => (
|
const renderPieChart = (data, size, isPrimary, chartType = null) => {
|
||||||
<PieChart width={size} height={size}>
|
// Filter out items with zero or negative values
|
||||||
<Pie
|
const validData = data.filter(item => item.value > 0)
|
||||||
data={data}
|
|
||||||
dataKey='value'
|
if (validData.length === 0) {
|
||||||
nameKey='label'
|
return (
|
||||||
cx='50%'
|
<Box
|
||||||
cy='50%'
|
sx={{
|
||||||
innerRadius={isPrimary ? size / 4 : size / 6}
|
width: size,
|
||||||
paddingAngle={5}
|
height: size,
|
||||||
cornerRadius={5}
|
display: 'flex',
|
||||||
>
|
alignItems: 'center',
|
||||||
{data.map((entry, index) => (
|
justifyContent: 'center',
|
||||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
border: '1px dashed',
|
||||||
))}
|
borderColor: 'divider',
|
||||||
</Pie>
|
borderRadius: '8px',
|
||||||
{isPrimary && (
|
|
||||||
<Tooltip
|
|
||||||
formatter={(value, name, props) => {
|
|
||||||
if (chartType === 'tasksTime' && props.payload.count) {
|
|
||||||
return [`${value}h (${props.payload.count} times)`, name]
|
|
||||||
}
|
|
||||||
return [`${value}`, name]
|
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
)}
|
<Typography level='body-sm' color='neutral'>
|
||||||
{isPrimary && (
|
No data available
|
||||||
<Legend
|
</Typography>
|
||||||
layout='horizontal'
|
</Box>
|
||||||
verticalAlign='bottom'
|
)
|
||||||
align='center'
|
}
|
||||||
// format as : {entry.payload.label}: {value}
|
|
||||||
iconType='circle'
|
// For primary charts, render chart and legend separately to control layout better
|
||||||
formatter={(label, value) => `${label}: ${value.payload.value}`}
|
if (isPrimary) {
|
||||||
/>
|
const chartSize = Math.min(size - 20, 220) // Reserve space and limit max size
|
||||||
)}
|
|
||||||
</PieChart>
|
return (
|
||||||
)
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: size,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: validData.length <= 3 ? 1.5 : 2, // Smaller gap for fewer items
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Chart Container */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PieChart width={chartSize} height={chartSize}>
|
||||||
|
<Pie
|
||||||
|
data={validData}
|
||||||
|
dataKey='value'
|
||||||
|
nameKey='label'
|
||||||
|
cx='50%'
|
||||||
|
cy='50%'
|
||||||
|
outerRadius={chartSize / 3}
|
||||||
|
innerRadius={chartSize / 8}
|
||||||
|
paddingAngle={validData.length > 1 ? 2 : 0}
|
||||||
|
cornerRadius={3}
|
||||||
|
minAngle={5}
|
||||||
|
>
|
||||||
|
{validData.map((entry, index) => (
|
||||||
|
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||||
|
))}
|
||||||
|
</Pie>
|
||||||
|
<Tooltip
|
||||||
|
formatter={(value, name, props) => {
|
||||||
|
if (chartType === 'tasksTime' && props.payload.count) {
|
||||||
|
return [`${value}h (${props.payload.count} times)`, name]
|
||||||
|
}
|
||||||
|
return [`${value}`, name]
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PieChart>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Scrollable Legend Container */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
maxHeight: validData.length <= 3 ? 'auto' : '120px', // Dynamic height based on data count
|
||||||
|
minHeight: validData.length <= 3 ? 'auto' : '60px', // No minimum height for few items
|
||||||
|
overflowY: validData.length <= 3 ? 'visible' : 'auto', // No scroll for few items
|
||||||
|
overflowX: 'hidden',
|
||||||
|
px: 1,
|
||||||
|
'&::-webkit-scrollbar': {
|
||||||
|
width: '6px',
|
||||||
|
},
|
||||||
|
'&::-webkit-scrollbar-track': {
|
||||||
|
backgroundColor: 'neutral.100',
|
||||||
|
borderRadius: '3px',
|
||||||
|
},
|
||||||
|
'&::-webkit-scrollbar-thumb': {
|
||||||
|
backgroundColor: 'neutral.400',
|
||||||
|
borderRadius: '3px',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'neutral.500',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 0.5,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{validData.map((entry, index) => (
|
||||||
|
<Chip
|
||||||
|
key={`legend-${index}`}
|
||||||
|
size='sm'
|
||||||
|
variant='soft'
|
||||||
|
sx={{
|
||||||
|
// backgroundColor: `${entry.color}20`, // 20% opacity
|
||||||
|
// borderColor: entry.color,
|
||||||
|
// border: '1px solid',
|
||||||
|
color: 'text.primary',
|
||||||
|
fontSize: '0.7rem',
|
||||||
|
py: 0.5,
|
||||||
|
px: 1,
|
||||||
|
maxWidth: '100%',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: `${entry.color}30`, // 30% opacity on hover
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
startDecorator={
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
backgroundColor: entry.color,
|
||||||
|
borderRadius: '50%',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
level='body-xs'
|
||||||
|
sx={{
|
||||||
|
fontSize: '0.7rem',
|
||||||
|
fontWeight: 500,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
maxWidth: '150px',
|
||||||
|
}}
|
||||||
|
title={`${entry.label}: ${entry.value}${
|
||||||
|
chartType === 'tasksTime' && entry.count
|
||||||
|
? ` (${entry.count} times)`
|
||||||
|
: ''
|
||||||
|
}${
|
||||||
|
chartType === 'labelsDuration' || chartType === 'tasksTime'
|
||||||
|
? 'h'
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{entry.label}: {entry.value}
|
||||||
|
{chartType === 'tasksTime' && entry.count
|
||||||
|
? ` (${entry.count}x)`
|
||||||
|
: ''}
|
||||||
|
{chartType === 'labelsDuration' || chartType === 'tasksTime'
|
||||||
|
? 'h'
|
||||||
|
: ''}
|
||||||
|
</Typography>
|
||||||
|
</Chip>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For small preview charts, keep it simple without legend
|
||||||
|
return (
|
||||||
|
<PieChart width={size} height={size}>
|
||||||
|
<Pie
|
||||||
|
data={validData}
|
||||||
|
dataKey='value'
|
||||||
|
nameKey='label'
|
||||||
|
cx='50%'
|
||||||
|
cy='50%'
|
||||||
|
outerRadius={size / 3.5}
|
||||||
|
innerRadius={size / 8}
|
||||||
|
paddingAngle={validData.length > 1 ? 1 : 0}
|
||||||
|
cornerRadius={2}
|
||||||
|
>
|
||||||
|
{validData.map((entry, index) => (
|
||||||
|
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||||
|
))}
|
||||||
|
</Pie>
|
||||||
|
</PieChart>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const USER_FILTER = (history, userId) => {
|
const USER_FILTER = (history, userId) => {
|
||||||
if (userId === undefined || userId === 'all') return true
|
if (userId === undefined || userId === 'all') return true
|
||||||
@@ -185,9 +343,6 @@ const UserActivites = () => {
|
|||||||
|
|
||||||
const [historyPieChartData, setHistoryPieChartData] = React.useState([])
|
const [historyPieChartData, setHistoryPieChartData] = React.useState([])
|
||||||
const [choreDuePieChartData, setChoreDuePieChartData] = React.useState([])
|
const [choreDuePieChartData, setChoreDuePieChartData] = React.useState([])
|
||||||
const [choresAssignedChartData, setChoresAssignedChartData] = React.useState(
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
const [choresPriorityChartData, setChoresPriorityChartData] = React.useState(
|
const [choresPriorityChartData, setChoresPriorityChartData] = React.useState(
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
@@ -247,6 +402,13 @@ const UserActivites = () => {
|
|||||||
|
|
||||||
// Generate tasks time chart data
|
// Generate tasks time chart data
|
||||||
setTasksTimeChartData(generateTasksTimeChartData(filteredHistory))
|
setTasksTimeChartData(generateTasksTimeChartData(filteredHistory))
|
||||||
|
} else {
|
||||||
|
// Reset data when loading or no data
|
||||||
|
setEnrichedHistory([])
|
||||||
|
setSelectedHistory([])
|
||||||
|
setHistoryPieChartData([])
|
||||||
|
setChoresLabelsDurationChartData([])
|
||||||
|
setTasksTimeChartData([])
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
isChoresHistoryLoading,
|
isChoresHistoryLoading,
|
||||||
@@ -264,35 +426,6 @@ const UserActivites = () => {
|
|||||||
? choresData.res
|
? choresData.res
|
||||||
: choresData.res.filter(chore => chore.assignedTo === selectedUser)
|
: choresData.res.filter(chore => chore.assignedTo === selectedUser)
|
||||||
|
|
||||||
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 generateChorePriorityPieChartData = chores => {
|
const generateChorePriorityPieChartData = chores => {
|
||||||
const groups = ChoresGrouper('priority', chores, null)
|
const groups = ChoresGrouper('priority', chores, null)
|
||||||
return groups
|
return groups
|
||||||
@@ -400,7 +533,6 @@ const UserActivites = () => {
|
|||||||
|
|
||||||
const choreDuePieChartData = generateChoreDuePieChartData(filteredChores)
|
const choreDuePieChartData = generateChoreDuePieChartData(filteredChores)
|
||||||
setChoreDuePieChartData(choreDuePieChartData)
|
setChoreDuePieChartData(choreDuePieChartData)
|
||||||
setChoresAssignedChartData(generateChoreAssignedChartData(filteredChores))
|
|
||||||
setChoresPriorityChartData(
|
setChoresPriorityChartData(
|
||||||
generateChorePriorityPieChartData(filteredChores),
|
generateChorePriorityPieChartData(filteredChores),
|
||||||
)
|
)
|
||||||
@@ -412,6 +544,10 @@ const UserActivites = () => {
|
|||||||
}, [isChoresLoading, choresData, userProfile?.id, circleUsers, selectedUser])
|
}, [isChoresLoading, choresData, userProfile?.id, circleUsers, selectedUser])
|
||||||
|
|
||||||
const generateChoreLabelsWithDurationChartData = (chores, history) => {
|
const generateChoreLabelsWithDurationChartData = (chores, history) => {
|
||||||
|
if (!chores || !history || chores.length === 0 || history.length === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
const labelDurations = {}
|
const labelDurations = {}
|
||||||
let unlabeledDuration = 0
|
let unlabeledDuration = 0
|
||||||
|
|
||||||
@@ -467,6 +603,10 @@ const UserActivites = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const generateTasksTimeChartData = history => {
|
const generateTasksTimeChartData = history => {
|
||||||
|
if (!history || history.length === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
const taskDurations = {}
|
const taskDurations = {}
|
||||||
const colorValues = Object.values(COLORS)
|
const colorValues = Object.values(COLORS)
|
||||||
|
|
||||||
@@ -504,6 +644,10 @@ const UserActivites = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const generateChoreDuePieChartData = chores => {
|
const generateChoreDuePieChartData = chores => {
|
||||||
|
if (!chores || chores.length === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
const groups = ChoresGrouper('due_date', chores, null)
|
const groups = ChoresGrouper('due_date', chores, null)
|
||||||
return groups
|
return groups
|
||||||
.map(group => {
|
.map(group => {
|
||||||
@@ -518,44 +662,58 @@ const UserActivites = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const generateHistoryPieChartData = history => {
|
const generateHistoryPieChartData = history => {
|
||||||
|
if (!history || history.length === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
const totalCompleted =
|
const totalCompleted =
|
||||||
history.filter(item => item.dueDate > item.performedAt).length || 0
|
history.filter(item => item.dueDate > item.performedAt).length || 0
|
||||||
const totalLate =
|
const totalLate =
|
||||||
history.filter(item => item.dueDate < item.performedAt).length || 0
|
history.filter(item => item.dueDate < item.performedAt).length || 0
|
||||||
const totalNoDueDate = history.filter(item => !item.dueDate).length || 0
|
const totalNoDueDate = history.filter(item => !item.dueDate).length || 0
|
||||||
|
|
||||||
return [
|
const result = []
|
||||||
{
|
|
||||||
|
if (totalCompleted > 0) {
|
||||||
|
result.push({
|
||||||
label: `On time`,
|
label: `On time`,
|
||||||
value: totalCompleted,
|
value: totalCompleted,
|
||||||
color: TASK_COLOR.COMPLETED,
|
color: TASK_COLOR.COMPLETED,
|
||||||
id: 1,
|
id: 1,
|
||||||
},
|
})
|
||||||
{
|
}
|
||||||
|
|
||||||
|
if (totalLate > 0) {
|
||||||
|
result.push({
|
||||||
label: `Late`,
|
label: `Late`,
|
||||||
value: totalLate,
|
value: totalLate,
|
||||||
color: TASK_COLOR.LATE,
|
color: TASK_COLOR.LATE,
|
||||||
id: 2,
|
id: 2,
|
||||||
},
|
})
|
||||||
{
|
}
|
||||||
|
|
||||||
|
if (totalNoDueDate > 0) {
|
||||||
|
result.push({
|
||||||
label: `Completed`,
|
label: `Completed`,
|
||||||
value: totalNoDueDate,
|
value: totalNoDueDate,
|
||||||
color: TASK_COLOR.ANYTIME,
|
color: TASK_COLOR.ANYTIME,
|
||||||
id: 3,
|
id: 3,
|
||||||
},
|
})
|
||||||
]
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
if (isChoresHistoryLoading || isChoresLoading) {
|
if (isChoresHistoryLoading || isChoresLoading) {
|
||||||
return <LoadingComponent />
|
return <LoadingComponent />
|
||||||
}
|
}
|
||||||
const chartData = {
|
const chartData = {
|
||||||
history: {
|
history: {
|
||||||
data: historyPieChartData,
|
data: historyPieChartData || [],
|
||||||
title: 'Status',
|
title: 'Status',
|
||||||
description: 'Completed tasks status',
|
description: 'Completed tasks status',
|
||||||
},
|
},
|
||||||
due: {
|
due: {
|
||||||
data: choreDuePieChartData,
|
data: choreDuePieChartData || [],
|
||||||
title: 'Due Date',
|
title: 'Due Date',
|
||||||
description: 'Current tasks due date',
|
description: 'Current tasks due date',
|
||||||
},
|
},
|
||||||
@@ -565,27 +723,27 @@ const UserActivites = () => {
|
|||||||
// description: 'Tasks assigned to you vs others',
|
// description: 'Tasks assigned to you vs others',
|
||||||
// },
|
// },
|
||||||
priority: {
|
priority: {
|
||||||
data: choresPriorityChartData,
|
data: choresPriorityChartData || [],
|
||||||
title: 'Priority',
|
title: 'Priority',
|
||||||
description: 'Tasks by priority',
|
description: 'Tasks by priority',
|
||||||
},
|
},
|
||||||
labels: {
|
labels: {
|
||||||
data: choresLabelsChartData,
|
data: choresLabelsChartData || [],
|
||||||
title: 'Labels',
|
title: 'Labels',
|
||||||
description: 'Tasks by labels',
|
description: 'Tasks by labels',
|
||||||
},
|
},
|
||||||
labelsDuration: {
|
labelsDuration: {
|
||||||
data: choresLabelsDurationChartData,
|
data: choresLabelsDurationChartData || [],
|
||||||
title: 'Labels (time)',
|
title: 'Labels (time)',
|
||||||
description: 'Time spent by labels (hours)',
|
description: 'Time spent by labels (hours)',
|
||||||
},
|
},
|
||||||
tasksTime: {
|
tasksTime: {
|
||||||
data: tasksTimeChartData,
|
data: tasksTimeChartData || [],
|
||||||
title: 'Tasks (time)',
|
title: 'Tasks (time)',
|
||||||
description: 'Time spent by individual tasks (hours)',
|
description: 'Time spent by individual tasks (hours)',
|
||||||
},
|
},
|
||||||
assigneeBreakdown: {
|
assigneeBreakdown: {
|
||||||
data: choresAssigneeBreakdownChartData,
|
data: choresAssigneeBreakdownChartData || [],
|
||||||
title: 'by Assignee',
|
title: 'by Assignee',
|
||||||
description: 'Tasks grouped by assignee',
|
description: 'Tasks grouped by assignee',
|
||||||
},
|
},
|
||||||
@@ -629,7 +787,7 @@ const UserActivites = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
maxWidth='xl'
|
maxWidth='lg'
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
@@ -871,7 +1029,7 @@ const UserActivites = () => {
|
|||||||
sx={{
|
sx={{
|
||||||
width: { xs: '100%', lg: '350px' },
|
width: { xs: '100%', lg: '350px' },
|
||||||
position: { xs: 'static', lg: 'sticky' },
|
position: { xs: 'static', lg: 'sticky' },
|
||||||
top: { lg: '20px' },
|
top: { lg: '60px' },
|
||||||
alignSelf: { lg: 'flex-start' },
|
alignSelf: { lg: 'flex-start' },
|
||||||
maxHeight: { lg: 'calc(100vh - 40px)' },
|
maxHeight: { lg: 'calc(100vh - 40px)' },
|
||||||
overflowY: { lg: 'auto' },
|
overflowY: { lg: 'auto' },
|
||||||
@@ -880,12 +1038,26 @@ const UserActivites = () => {
|
|||||||
>
|
>
|
||||||
{/* Charts Container */}
|
{/* Charts Container */}
|
||||||
<Card
|
<Card
|
||||||
variant='outlined'
|
variant='plain'
|
||||||
sx={{
|
sx={{
|
||||||
|
// maxHeight: { lg: '90vh' },
|
||||||
p: 2,
|
p: 2,
|
||||||
borderRadius: 12,
|
display: 'flex',
|
||||||
backdropFilter: 'blur(10px)',
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
mr: 10,
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
boxShadow: 'sm',
|
||||||
|
borderRadius: 20,
|
||||||
|
width: '315px',
|
||||||
|
mb: 1,
|
||||||
}}
|
}}
|
||||||
|
// variant='outlined'
|
||||||
|
// sx={{
|
||||||
|
// p: 2,
|
||||||
|
// borderRadius: 12,
|
||||||
|
// backdropFilter: 'blur(10px)',
|
||||||
|
// }}
|
||||||
>
|
>
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
{/* Main Chart */}
|
{/* Main Chart */}
|
||||||
@@ -894,9 +1066,20 @@ const UserActivites = () => {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'flex-start',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
minHeight: { lg: '400px' },
|
minHeight: {
|
||||||
|
lg:
|
||||||
|
chartData[selectedChart].data.length <= 3
|
||||||
|
? '350px'
|
||||||
|
: '450px',
|
||||||
|
}, // Dynamic height based on legend needs
|
||||||
|
maxHeight: {
|
||||||
|
lg:
|
||||||
|
chartData[selectedChart].data.length <= 3
|
||||||
|
? '400px'
|
||||||
|
: '500px',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography level='h4' textAlign='center' sx={{ mb: 1 }}>
|
<Typography level='h4' textAlign='center' sx={{ mb: 1 }}>
|
||||||
@@ -907,14 +1090,16 @@ const UserActivites = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
flex: 1,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'flex-start',
|
||||||
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{renderPieChart(
|
{renderPieChart(
|
||||||
chartData[selectedChart].data,
|
chartData[selectedChart].data,
|
||||||
240,
|
300, // Increased size for better chart container
|
||||||
true,
|
true,
|
||||||
selectedChart,
|
selectedChart,
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user