From 33f3aa230b52f77284e4c18a704f46762836633e Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sun, 8 Feb 2026 20:35:16 +0000 Subject: [PATCH] Auto commit: 2026-02-08T20:35:16.167Z --- backend/src/db/api/tasks.js | 35 ++++++++++++++-- backend/src/routes/tasks.js | 8 +++- backend/src/services/tasks.js | 8 ++-- frontend/src/components/NavBarItem.tsx | 5 +-- frontend/src/layouts/Authenticated.tsx | 5 +-- frontend/src/pages/dashboard.tsx | 56 +++++++++++++++++++++++++- 6 files changed, 102 insertions(+), 15 deletions(-) diff --git a/backend/src/db/api/tasks.js b/backend/src/db/api/tasks.js index 37e3f6e..93c5b38 100644 --- a/backend/src/db/api/tasks.js +++ b/backend/src/db/api/tasks.js @@ -1,4 +1,3 @@ - const db = require('../models'); const FileDBApi = require('./file'); const crypto = require('crypto'); @@ -566,6 +565,36 @@ module.exports = class TasksDBApi { })); } - -}; + static async getStats(options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + const total = await db.tasks.count({ + transaction, + }); + + const pending = await db.tasks.count({ + where: { status: 'pending' }, + transaction, + }); + + const in_progress = await db.tasks.count({ + where: { status: 'in_progress' }, + transaction, + }); + + const completed = await db.tasks.count({ + where: { status: 'completed' }, + transaction, + }); + + return { + total, + pending, + in_progress, + completed, + }; + } + + +}; \ No newline at end of file diff --git a/backend/src/routes/tasks.js b/backend/src/routes/tasks.js index 10b165b..1aee571 100644 --- a/backend/src/routes/tasks.js +++ b/backend/src/routes/tasks.js @@ -1,4 +1,3 @@ - const express = require('express'); const TasksService = require('../services/tasks'); @@ -350,6 +349,11 @@ router.get('/count', wrapAsync(async (req, res) => { res.status(200).send(payload); })); +router.get('/stats', wrapAsync(async (req, res) => { + const payload = await TasksService.getStats(req.currentUser); + res.status(200).send(payload); +})); + /** * @swagger * /api/tasks/autocomplete: @@ -431,4 +435,4 @@ router.get('/:id', wrapAsync(async (req, res) => { router.use('/', require('../helpers').commonErrorHandler); -module.exports = router; +module.exports = router; \ No newline at end of file diff --git a/backend/src/services/tasks.js b/backend/src/services/tasks.js index e62bdba..cdf2843 100644 --- a/backend/src/services/tasks.js +++ b/backend/src/services/tasks.js @@ -132,7 +132,9 @@ module.exports = class TasksService { } } + static async getStats(currentUser) { + return await TasksDBApi.getStats({ currentUser }); + } + -}; - - +}; \ No newline at end of file diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx index eb155e3..1986306 100644 --- a/frontend/src/components/NavBarItem.tsx +++ b/frontend/src/components/NavBarItem.tsx @@ -1,6 +1,5 @@ -import React, {useEffect, useRef} from 'react' +import React, {useEffect, useRef, useState} from 'react' import Link from 'next/link' -import { useState } from 'react' import { mdiChevronUp, mdiChevronDown } from '@mdi/js' import BaseDivider from './BaseDivider' import BaseIcon from './BaseIcon' @@ -129,4 +128,4 @@ export default function NavBarItem({ item }: Props) { } return
{NavBarItemComponentContents}
-} +} \ No newline at end of file diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx index 1b9907d..26c3572 100644 --- a/frontend/src/layouts/Authenticated.tsx +++ b/frontend/src/layouts/Authenticated.tsx @@ -1,5 +1,4 @@ -import React, { ReactNode, useEffect } from 'react' -import { useState } from 'react' +import React, { ReactNode, useEffect, useState } from 'react' import jwt from 'jsonwebtoken'; import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js' import menuAside from '../menuAside' @@ -126,4 +125,4 @@ export default function LayoutAuthenticated({ ) -} +} \ No newline at end of file diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx index 6686232..7524b39 100644 --- a/frontend/src/pages/dashboard.tsx +++ b/frontend/src/pages/dashboard.tsx @@ -37,6 +37,8 @@ const Dashboard = () => { const [task_exports, setTask_exports] = React.useState(loadingMessage); const [audit_events, setAudit_events] = React.useState(loadingMessage); + const [taskStats, setTaskStats] = React.useState({ total: 0, pending: 0, in_progress: 0, completed: 0 }); + const [widgetsRole, setWidgetsRole] = React.useState({ role: { value: '', label: '' }, @@ -72,6 +74,17 @@ const Dashboard = () => { }); }); } + + async function loadTaskStats() { + if (hasPermission(currentUser, 'READ_TASKS')) { + try { + const response = await axios.get('/tasks/stats'); + setTaskStats(response.data); + } catch (error) { + console.error('Error fetching task stats:', error); + } + } + } async function getWidgets(roleId) { await dispatch(fetchWidgets(roleId)); @@ -79,6 +92,7 @@ const Dashboard = () => { React.useEffect(() => { if (!currentUser) return; loadData().then(); + loadTaskStats().then(); setWidgetsRole({ role: { value: currentUser?.app_role?.id, label: currentUser?.app_role?.name } }); }, [currentUser]); @@ -142,6 +156,46 @@ const Dashboard = () => { {!!rolesWidgets.length &&
} + +
+ {hasPermission(currentUser, 'READ_TASKS') && ( + <> + +
+
+
+
Total Tasks
+
{taskStats.total}
+
+ +
+
+ + +
+
+
+
Pending
+
{taskStats.pending}
+
+ +
+
+ + +
+
+
+
Completed
+
{taskStats.completed}
+
+ +
+
+ + + )} +
@@ -465,4 +519,4 @@ Dashboard.getLayout = function getLayout(page: ReactElement) { return {page} } -export default Dashboard +export default Dashboard \ No newline at end of file