diff --git a/src/queries/ThingQueries.jsx b/src/queries/ThingQueries.jsx
new file mode 100644
index 0000000..d655795
--- /dev/null
+++ b/src/queries/ThingQueries.jsx
@@ -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
+ })
+}
diff --git a/src/views/Authorization/Authenticating.jsx b/src/views/Authorization/Authenticating.jsx
index b90215c..36ce15c 100644
--- a/src/views/Authorization/Authenticating.jsx
+++ b/src/views/Authorization/Authenticating.jsx
@@ -29,7 +29,7 @@ const AuthenticationLoading = () => {
const getUserProfileAndNavigateToHome = () => {
GetUserProfile().then(data => {
data.json().then(data => {
- refetchUserProfile.then(() => {
+ refetchUserProfile().then(() => {
// check if redirect url is set in cookie:
const redirectUrl = Cookies.get('ca_redirect')
if (redirectUrl) {
diff --git a/src/views/Things/ThingsHistory.jsx b/src/views/Things/ThingsHistory.jsx
index fe8b97a..12fb591 100644
--- a/src/views/Things/ThingsHistory.jsx
+++ b/src/views/Things/ThingsHistory.jsx
@@ -13,7 +13,6 @@ import {
Typography,
} from '@mui/joy'
import moment from 'moment'
-import { useEffect, useState } from 'react'
import { Link, useParams } from 'react-router-dom'
import {
Line,
@@ -23,40 +22,27 @@ import {
XAxis,
YAxis,
} from 'recharts'
-import { GetThingHistory } from '../../utils/Fetcher'
+import { useTheme } from '@mui/joy/styles'
+import { useThingHistory } from '../../queries/ThingQueries'
import LoadingComponent from '../components/Loading'
const ThingsHistory = () => {
const { id } = useParams()
- const [thingsHistory, setThingsHistory] = useState([])
- const [noMoreHistory, setNoMoreHistory] = useState(false)
- const [errLoading, setErrLoading] = useState(false)
- useEffect(() => {
- GetThingHistory(id, 0, 10).then(resp => {
- if (resp.ok) {
- resp.json().then(data => {
- setThingsHistory(data.res)
- if (data.res.length < 10) {
- setNoMoreHistory(true)
- }
- })
- } else {
- setErrLoading(true)
- }
- })
- }, [id])
+ const theme = useTheme()
+ const {
+ data,
+ error,
+ isLoading,
+ fetchNextPage,
+ hasNextPage,
+ isFetchingNextPage,
+ } = useThingHistory(id)
+
+ // Flatten all pages of history data
+ const thingsHistory = data?.pages.flatMap(page => page.res) || []
const handleLoadMore = () => {
- GetThingHistory(id, thingsHistory.length).then(resp => {
- if (resp.ok) {
- resp.json().then(data => {
- setThingsHistory([...thingsHistory, ...data.res])
- if (data.res.length < 10) {
- setNoMoreHistory(true)
- }
- })
- }
- })
+ fetchNextPage()
}
const formatTimeDifference = (startDate, endDate) => {
@@ -79,11 +65,11 @@ const ThingsHistory = () => {
return `${timeValue} ${unit}${timeValue !== 1 ? 's' : ''}`
}
// if loading show loading spinner:
- if (thingsHistory.length === 0) {
+ if (isLoading) {
return
}
- if (errLoading || !thingsHistory || thingsHistory.length === 0) {
+ if (error || !thingsHistory || thingsHistory.length === 0) {
return (
{
@@ -305,9 +299,13 @@ const ThingsHistory = () => {
fullWidth
color='primary'
onClick={handleLoadMore}
- disabled={noMoreHistory}
+ disabled={!hasNextPage || isFetchingNextPage}
>
- {noMoreHistory ? 'No more history' : 'Load more'}
+ {isFetchingNextPage
+ ? 'Loading...'
+ : !hasNextPage
+ ? 'No more history'
+ : 'Load more'}
diff --git a/src/views/User/UserActivities.jsx b/src/views/User/UserActivities.jsx
index 598f2f7..0d4d16c 100644
--- a/src/views/User/UserActivities.jsx
+++ b/src/views/User/UserActivities.jsx
@@ -1,7 +1,7 @@
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 { Cell, Pie, PieChart, Tooltip } from 'recharts'
import { EventBusy, Group, Toll } from '@mui/icons-material'
import {
@@ -131,44 +131,202 @@ const ChoreHistoryTimeline = ({ history }) => {
)
}
-const renderPieChart = (data, size, isPrimary, chartType = null) => (
-
-
- {data.map((entry, index) => (
- |
- ))}
-
- {isPrimary && (
- {
- if (chartType === 'tasksTime' && props.payload.count) {
- return [`${value}h (${props.payload.count} times)`, name]
- }
- return [`${value}`, name]
+const renderPieChart = (data, size, isPrimary, chartType = null) => {
+ // Filter out items with zero or negative values
+ const validData = data.filter(item => item.value > 0)
+
+ if (validData.length === 0) {
+ return (
+
- )}
- {isPrimary && (
-
-)
+ >
+
+ No data available
+
+
+ )
+ }
+
+ // For primary charts, render chart and legend separately to control layout better
+ if (isPrimary) {
+ const chartSize = Math.min(size - 20, 220) // Reserve space and limit max size
+
+ return (
+
+ {/* Chart Container */}
+
+
+ 1 ? 2 : 0}
+ cornerRadius={3}
+ minAngle={5}
+ >
+ {validData.map((entry, index) => (
+ |
+ ))}
+
+ {
+ if (chartType === 'tasksTime' && props.payload.count) {
+ return [`${value}h (${props.payload.count} times)`, name]
+ }
+ return [`${value}`, name]
+ }}
+ />
+
+
+
+ {/* Scrollable Legend Container */}
+
+
+ {validData.map((entry, index) => (
+
+ }
+ >
+
+ {entry.label}: {entry.value}
+ {chartType === 'tasksTime' && entry.count
+ ? ` (${entry.count}x)`
+ : ''}
+ {chartType === 'labelsDuration' || chartType === 'tasksTime'
+ ? 'h'
+ : ''}
+
+
+ ))}
+
+
+
+ )
+ }
+
+ // For small preview charts, keep it simple without legend
+ return (
+
+ 1 ? 1 : 0}
+ cornerRadius={2}
+ >
+ {validData.map((entry, index) => (
+ |
+ ))}
+
+
+ )
+}
const USER_FILTER = (history, userId) => {
if (userId === undefined || userId === 'all') return true
@@ -185,9 +343,6 @@ const UserActivites = () => {
const [historyPieChartData, setHistoryPieChartData] = React.useState([])
const [choreDuePieChartData, setChoreDuePieChartData] = React.useState([])
- const [choresAssignedChartData, setChoresAssignedChartData] = React.useState(
- [],
- )
const [choresPriorityChartData, setChoresPriorityChartData] = React.useState(
[],
)
@@ -247,6 +402,13 @@ const UserActivites = () => {
// Generate tasks time chart data
setTasksTimeChartData(generateTasksTimeChartData(filteredHistory))
+ } else {
+ // Reset data when loading or no data
+ setEnrichedHistory([])
+ setSelectedHistory([])
+ setHistoryPieChartData([])
+ setChoresLabelsDurationChartData([])
+ setTasksTimeChartData([])
}
}, [
isChoresHistoryLoading,
@@ -264,35 +426,6 @@ const UserActivites = () => {
? choresData.res
: 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 groups = ChoresGrouper('priority', chores, null)
return groups
@@ -400,7 +533,6 @@ const UserActivites = () => {
const choreDuePieChartData = generateChoreDuePieChartData(filteredChores)
setChoreDuePieChartData(choreDuePieChartData)
- setChoresAssignedChartData(generateChoreAssignedChartData(filteredChores))
setChoresPriorityChartData(
generateChorePriorityPieChartData(filteredChores),
)
@@ -412,6 +544,10 @@ const UserActivites = () => {
}, [isChoresLoading, choresData, userProfile?.id, circleUsers, selectedUser])
const generateChoreLabelsWithDurationChartData = (chores, history) => {
+ if (!chores || !history || chores.length === 0 || history.length === 0) {
+ return []
+ }
+
const labelDurations = {}
let unlabeledDuration = 0
@@ -467,6 +603,10 @@ const UserActivites = () => {
}
const generateTasksTimeChartData = history => {
+ if (!history || history.length === 0) {
+ return []
+ }
+
const taskDurations = {}
const colorValues = Object.values(COLORS)
@@ -504,6 +644,10 @@ const UserActivites = () => {
}
const generateChoreDuePieChartData = chores => {
+ if (!chores || chores.length === 0) {
+ return []
+ }
+
const groups = ChoresGrouper('due_date', chores, null)
return groups
.map(group => {
@@ -518,44 +662,58 @@ const UserActivites = () => {
}
const generateHistoryPieChartData = history => {
+ if (!history || history.length === 0) {
+ return []
+ }
+
const totalCompleted =
history.filter(item => item.dueDate > item.performedAt).length || 0
const totalLate =
history.filter(item => item.dueDate < item.performedAt).length || 0
const totalNoDueDate = history.filter(item => !item.dueDate).length || 0
- return [
- {
+ const result = []
+
+ if (totalCompleted > 0) {
+ result.push({
label: `On time`,
value: totalCompleted,
color: TASK_COLOR.COMPLETED,
id: 1,
- },
- {
+ })
+ }
+
+ if (totalLate > 0) {
+ result.push({
label: `Late`,
value: totalLate,
color: TASK_COLOR.LATE,
id: 2,
- },
- {
+ })
+ }
+
+ if (totalNoDueDate > 0) {
+ result.push({
label: `Completed`,
value: totalNoDueDate,
color: TASK_COLOR.ANYTIME,
id: 3,
- },
- ]
+ })
+ }
+
+ return result
}
if (isChoresHistoryLoading || isChoresLoading) {
return
}
const chartData = {
history: {
- data: historyPieChartData,
+ data: historyPieChartData || [],
title: 'Status',
description: 'Completed tasks status',
},
due: {
- data: choreDuePieChartData,
+ data: choreDuePieChartData || [],
title: 'Due Date',
description: 'Current tasks due date',
},
@@ -565,27 +723,27 @@ const UserActivites = () => {
// description: 'Tasks assigned to you vs others',
// },
priority: {
- data: choresPriorityChartData,
+ data: choresPriorityChartData || [],
title: 'Priority',
description: 'Tasks by priority',
},
labels: {
- data: choresLabelsChartData,
+ data: choresLabelsChartData || [],
title: 'Labels',
description: 'Tasks by labels',
},
labelsDuration: {
- data: choresLabelsDurationChartData,
+ data: choresLabelsDurationChartData || [],
title: 'Labels (time)',
description: 'Time spent by labels (hours)',
},
tasksTime: {
- data: tasksTimeChartData,
+ data: tasksTimeChartData || [],
title: 'Tasks (time)',
description: 'Time spent by individual tasks (hours)',
},
assigneeBreakdown: {
- data: choresAssigneeBreakdownChartData,
+ data: choresAssigneeBreakdownChartData || [],
title: 'by Assignee',
description: 'Tasks grouped by assignee',
},
@@ -629,7 +787,7 @@ const UserActivites = () => {
return (
{
sx={{
width: { xs: '100%', lg: '350px' },
position: { xs: 'static', lg: 'sticky' },
- top: { lg: '20px' },
+ top: { lg: '60px' },
alignSelf: { lg: 'flex-start' },
maxHeight: { lg: 'calc(100vh - 40px)' },
overflowY: { lg: 'auto' },
@@ -880,12 +1038,26 @@ const UserActivites = () => {
>
{/* Charts Container */}
{/* Main Chart */}
@@ -894,9 +1066,20 @@ const UserActivites = () => {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
- justifyContent: 'center',
+ justifyContent: 'flex-start',
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',
+ },
}}
>
@@ -907,14 +1090,16 @@ const UserActivites = () => {
{renderPieChart(
chartData[selectedChart].data,
- 240,
+ 300, // Increased size for better chart container
true,
selectedChart,
)}