From b5084ad99d1498951406b2c8fd33b17384654ff2 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 3 Oct 2025 12:30:52 +0530 Subject: [PATCH 1/5] Creating a new weidget in Dashboard for expense. --- src/components/Dashboard/Dashboard.jsx | 4 + src/components/Dashboard/ExpenseByProject.jsx | 123 ++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 src/components/Dashboard/ExpenseByProject.jsx diff --git a/src/components/Dashboard/Dashboard.jsx b/src/components/Dashboard/Dashboard.jsx index 9435d3bc..7e32df7a 100644 --- a/src/components/Dashboard/Dashboard.jsx +++ b/src/components/Dashboard/Dashboard.jsx @@ -17,6 +17,7 @@ import AttendanceOverview from "./AttendanceOverview"; import { useSelectedProject } from "../../slices/apiDataManager"; import { useProjectName } from "../../hooks/useProjects"; import ExpenseAnalysis from "./ExpenseAnalysis"; +import ExpenseByProject from "./ExpenseByProject"; const Dashboard = () => { // const { projectsCardData } = useDashboardProjectsCardData(); @@ -34,6 +35,9 @@ const Dashboard = () => {
+
+ +
{!isAllProjectsSelected && (
diff --git a/src/components/Dashboard/ExpenseByProject.jsx b/src/components/Dashboard/ExpenseByProject.jsx new file mode 100644 index 00000000..178d837e --- /dev/null +++ b/src/components/Dashboard/ExpenseByProject.jsx @@ -0,0 +1,123 @@ +import React, { useState } from "react"; +import Chart from "react-apexcharts"; + +const ExpenseByProject = () => { + const [range, setRange] = useState("12M"); + + // Dummy data grouped by year + const expenseData = { + "6M": { + categories: [ + { quarter: "Q1" }, + { quarter: "Q2" }, + { quarter: "Q3" }, + { quarter: "Q4" }, + ], + data: [400, 430, 448, 470] + }, + "12M": { + categories: [ + { quarter: "Q1" }, + { quarter: "Q2" }, + { quarter: "Q3" }, + { quarter: "Q4" }, + { quarter: "Q1" }, + { quarter: "Q2" }, + { quarter: "Q3" }, + { quarter: "Q4" }, + ], + data: [400, 430, 448, 470, 540, 580, 690, 690] + }, + All: { + categories: [ + { quarter: "Q1" }, + { quarter: "Q2" }, + { quarter: "Q3" }, + { quarter: "Q4" }, + { quarter: "Q1" }, + { quarter: "Q2" }, + { quarter: "Q3" }, + { quarter: "Q4" }, + ], + data: [300, 350, 370, 390, 420, 460, 500, 530] + } + }; + + const options = { + chart: { type: "bar", toolbar: { show: false } }, + plotOptions: { + bar: { + horizontal: false, + columnWidth: "55%", + borderRadius: 4 + } + }, + dataLabels: { + enabled: true, + formatter: (val) => val + }, + xaxis: { + categories: expenseData[range].categories.map(c => `${c.quarter}`), + labels: { + style: { fontSize: "12px" }, + rotate: -45 + }, + group: { + groups: [ + { + title: "2022", + cols: 4 + }, + { + title: "2023", + cols: 4 + }, + { + title: "2024", + cols: 4 + } + ] + } + }, + yaxis: { + title: { text: "Expense" } + }, + fill: { opacity: 1 }, + colors: ["#2196f3"] + }; + + const series = [ + { + name: "Expense", + data: expenseData[range].data + } + ]; + + return ( +
+
+
+
Expense Breakdown
+

Detailed project expenses

+
+
+ {["6M", "12M", "All"].map((item) => ( + + ))} +
+
+
+ +
+
+ ); +}; + +export default ExpenseByProject; \ No newline at end of file -- 2.43.0 From 2397be3b8ca5f333cac5c5dedbc21476ce233fcd Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 3 Oct 2025 15:02:25 +0530 Subject: [PATCH 2/5] Adding API for ExpenseProjects. --- src/components/Dashboard/ExpenseByProject.jsx | 160 ++++++++---------- src/hooks/useDashboard_Data.jsx | 83 +++++---- src/repositories/GlobalRepository.jsx | 70 +++++--- 3 files changed, 166 insertions(+), 147 deletions(-) diff --git a/src/components/Dashboard/ExpenseByProject.jsx b/src/components/Dashboard/ExpenseByProject.jsx index 178d837e..11de982c 100644 --- a/src/components/Dashboard/ExpenseByProject.jsx +++ b/src/components/Dashboard/ExpenseByProject.jsx @@ -1,111 +1,87 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import Chart from "react-apexcharts"; +import { useExpenseType } from "../../hooks/masterHook/useMaster"; +import { useSelectedProject } from "../../slices/apiDataManager"; +import { useExpenseDataByProject } from "../../hooks/useDashboard_Data"; const ExpenseByProject = () => { + const projectId = useSelectedProject(); const [range, setRange] = useState("12M"); + const [selectedType, setSelectedType] = useState(""); + const [chartData, setChartData] = useState({ categories: [], data: [] }); - // Dummy data grouped by year - const expenseData = { - "6M": { - categories: [ - { quarter: "Q1" }, - { quarter: "Q2" }, - { quarter: "Q3" }, - { quarter: "Q4" }, - ], - data: [400, 430, 448, 470] - }, - "12M": { - categories: [ - { quarter: "Q1" }, - { quarter: "Q2" }, - { quarter: "Q3" }, - { quarter: "Q4" }, - { quarter: "Q1" }, - { quarter: "Q2" }, - { quarter: "Q3" }, - { quarter: "Q4" }, - ], - data: [400, 430, 448, 470, 540, 580, 690, 690] - }, - All: { - categories: [ - { quarter: "Q1" }, - { quarter: "Q2" }, - { quarter: "Q3" }, - { quarter: "Q4" }, - { quarter: "Q1" }, - { quarter: "Q2" }, - { quarter: "Q3" }, - { quarter: "Q4" }, - ], - data: [300, 350, 370, 390, 420, 460, 500, 530] + const { ExpenseTypes, loading: typeLoading } = useExpenseType(); + + // Fetch API data + const { data: expenseApiData, isLoading } = useExpenseDataByProject( + projectId, + selectedType, + range === "All" ? null : parseInt(range) + ); + + useEffect(() => { + if (expenseApiData) { + const categories = expenseApiData.map( + (item) => `${item.monthName} ${item.year}` + ); + const data = expenseApiData.map((item) => item.total); + + setChartData({ categories, data }); + } else { + setChartData({ categories: [], data: [] }); } - }; + }, [expenseApiData]); const options = { chart: { type: "bar", toolbar: { show: false } }, - plotOptions: { - bar: { - horizontal: false, - columnWidth: "55%", - borderRadius: 4 - } - }, - dataLabels: { - enabled: true, - formatter: (val) => val - }, - xaxis: { - categories: expenseData[range].categories.map(c => `${c.quarter}`), - labels: { - style: { fontSize: "12px" }, - rotate: -45 - }, - group: { - groups: [ - { - title: "2022", - cols: 4 - }, - { - title: "2023", - cols: 4 - }, - { - title: "2024", - cols: 4 - } - ] - } - }, - yaxis: { - title: { text: "Expense" } - }, + plotOptions: { bar: { horizontal: false, columnWidth: "55%", borderRadius: 4 } }, + dataLabels: { enabled: true, formatter: (val) => val }, + xaxis: { categories: chartData.categories, labels: { style: { fontSize: "12px" }, rotate: -45 } }, fill: { opacity: 1 }, colors: ["#2196f3"] }; const series = [ { - name: "Expense", - data: expenseData[range].data + name: selectedType || "Expense", + data: chartData.data } ]; return ( -
-
-
-
Expense Breakdown
-

Detailed project expenses

+
+ {/* Header */} +
+
+
+
Expense Breakdown
+

Detailed project expenses

+
+ +
+ {/* Expense Type Dropdown */} + +
-
+ + {/* Range Buttons */} +
{["6M", "12M", "All"].map((item) => (
-
- + + {/* Chart */} +
+ {isLoading ? ( +

Loading chart...

+ ) : ( + + )}
); }; -export default ExpenseByProject; \ No newline at end of file +export default ExpenseByProject; + + diff --git a/src/hooks/useDashboard_Data.jsx b/src/hooks/useDashboard_Data.jsx index 7c022b46..ddfbb03e 100644 --- a/src/hooks/useDashboard_Data.jsx +++ b/src/hooks/useDashboard_Data.jsx @@ -200,63 +200,63 @@ export const useDashboard_Data = ({ days, FromDate, projectId }) => { // }) // } -export const useDashboard_AttendanceData = (date,projectId)=>{ - return useQuery({ - queryKey:["dashboardAttendances",date,projectId], - queryFn:async()=> { - - const resp = await await GlobalRepository.getDashboardAttendanceData(date, projectId) - return resp.data; +export const useDashboard_AttendanceData = (date, projectId) => { + return useQuery({ + queryKey: ["dashboardAttendances", date, projectId], + queryFn: async () => { + + const resp = await await GlobalRepository.getDashboardAttendanceData(date, projectId) + return resp.data; } }) } -export const useDashboardTeamsCardData =(projectId)=>{ - return useQuery({ - queryKey:["dashboardTeams",projectId], - queryFn:async()=> { - - const resp = await GlobalRepository.getDashboardTeamsCardData(projectId) - return resp.data; +export const useDashboardTeamsCardData = (projectId) => { + return useQuery({ + queryKey: ["dashboardTeams", projectId], + queryFn: async () => { + + const resp = await GlobalRepository.getDashboardTeamsCardData(projectId) + return resp.data; } }) } export const useDashboardTasksCardData = (projectId) => { - return useQuery({ - queryKey:["dashboardTasks",projectId], - queryFn:async()=> { - - const resp = await GlobalRepository.getDashboardTasksCardData(projectId) - return resp.data; + return useQuery({ + queryKey: ["dashboardTasks", projectId], + queryFn: async () => { + + const resp = await GlobalRepository.getDashboardTasksCardData(projectId) + return resp.data; } }) } export const useAttendanceOverviewData = (projectId, days) => { - return useQuery({ - queryKey:["dashboardAttendanceOverView",projectId,days], - queryFn:async()=> { - - const resp = await GlobalRepository.getAttendanceOverview(projectId, days); - return resp.data; + return useQuery({ + queryKey: ["dashboardAttendanceOverView", projectId, days], + queryFn: async () => { + + const resp = await GlobalRepository.getAttendanceOverview(projectId, days); + return resp.data; }, - enabled:!!projectId + enabled: !!projectId }) } export const useDashboardProjectsCardData = () => { - return useQuery({ - queryKey:["dashboardProjects"], - queryFn:async()=> { - - const resp = await GlobalRepository.getDashboardProjectsCardData(); - return resp.data; + return useQuery({ + queryKey: ["dashboardProjects"], + queryFn: async () => { + + const resp = await GlobalRepository.getDashboardProjectsCardData(); + return resp.data; } }) } export const useExpenseAnalysis = (projectId, startDate, endDate) => { - const hasBothDates = !!startDate && !!endDate; + const hasBothDates = !!startDate && !!endDate; const noDatesSelected = !startDate && !endDate; const shouldFetch = @@ -266,8 +266,19 @@ export const useExpenseAnalysis = (projectId, startDate, endDate) => { queryKey: ["expenseAnalysis", projectId, startDate, endDate], queryFn: async () => { const resp = await GlobalRepository.getExpenseData(projectId, startDate, endDate); - return resp.data; + return resp.data; }, - enabled:shouldFetch + enabled: shouldFetch + }); +}; + +export const useExpenseDataByProject = (projectId, categoryId, months) => { + return useQuery({ + queryKey: ["expenseByProject", projectId, categoryId, months], + queryFn: async () => { + const resp = await GlobalRepository.getExpenseDataByProject(projectId, categoryId, months); + return resp.data; + }, + }); }; \ No newline at end of file diff --git a/src/repositories/GlobalRepository.jsx b/src/repositories/GlobalRepository.jsx index 9fc8b074..d9e628fb 100644 --- a/src/repositories/GlobalRepository.jsx +++ b/src/repositories/GlobalRepository.jsx @@ -3,12 +3,12 @@ import { api } from "../utils/axiosClient"; const GlobalRepository = { getDashboardProgressionData: ({ days = '', FromDate = '', projectId = '' }) => { let params; - if(projectId == null){ + if (projectId == null) { params = new URLSearchParams({ days: days.toString(), FromDate, }); - }else{ + } else { params = new URLSearchParams({ days: days.toString(), FromDate, @@ -19,35 +19,35 @@ const GlobalRepository = { return api.get(`/api/Dashboard/Progression?${params.toString()}`); }, - getDashboardAttendanceData: ( date,projectId ) => { + getDashboardAttendanceData: (date, projectId) => { - return api.get(`/api/Dashboard/project-attendance/${projectId}?date=${date}`); -}, + return api.get(`/api/Dashboard/project-attendance/${projectId}?date=${date}`); + }, getDashboardProjectsCardData: () => { return api.get(`/api/Dashboard/projects`); }, - + getDashboardTeamsCardData: (projectId) => { - const url = projectId - ? `/api/Dashboard/teams?projectId=${projectId}` - : `/api/Dashboard/teams`; - return api.get(url); -}, + const url = projectId + ? `/api/Dashboard/teams?projectId=${projectId}` + : `/api/Dashboard/teams`; + return api.get(url); + }, getDashboardTasksCardData: (projectId) => { - const url = projectId - ? `/api/Dashboard/tasks?projectId=${projectId}` - : `/api/Dashboard/tasks`; - return api.get(url); -}, + const url = projectId + ? `/api/Dashboard/tasks?projectId=${projectId}` + : `/api/Dashboard/tasks`; + return api.get(url); + }, - getAttendanceOverview:(projectId,days)=>api.get(`/api/dashboard/attendance-overview/${projectId}?days=${days}`), + getAttendanceOverview: (projectId, days) => api.get(`/api/dashboard/attendance-overview/${projectId}?days=${days}`), - getExpenseData: (projectId, startDate, endDate) => { - let url = `api/Dashboard/expense/type` - - const queryParams = []; + getExpenseData: (projectId, startDate, endDate) => { + let url = `api/Dashboard/expense/type` + + const queryParams = []; if (projectId) { queryParams.push(`projectId=${projectId}`); @@ -56,7 +56,7 @@ const GlobalRepository = { if (startDate) { queryParams.push(`startDate=${startDate}`); } - if (endDate) { + if (endDate) { queryParams.push(`endDate=${endDate}`); } @@ -64,7 +64,31 @@ const GlobalRepository = { url += `?${queryParams.join("&")}`; } - return api.get(url ); + return api.get(url); + }, + + + getExpenseDataByProject: (projectId, categoryId, months) => { + let url = `api/Dashboard/expense/monthly` + + const queryParams = []; + + if (projectId) { + queryParams.push(`projectId=${projectId}`); + } + + if (categoryId) { + queryParams.push(`categoryId=${categoryId}`); + } + if (months) { + queryParams.push(`months=${months}`); + } + + if (queryParams.length > 0) { + url += `?${queryParams.join("&")}`; + } + + return api.get(url); }, -- 2.43.0 From 80ac18ff264431d63a415e766f8dc49588c9d206 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 3 Oct 2025 17:00:57 +0530 Subject: [PATCH 3/5] Changes in Monthly Expense dashboard. --- src/components/Dashboard/ExpenseByProject.jsx | 100 ++++++++++++++---- src/components/Layout/Header.jsx | 28 ++--- 2 files changed, 91 insertions(+), 37 deletions(-) diff --git a/src/components/Dashboard/ExpenseByProject.jsx b/src/components/Dashboard/ExpenseByProject.jsx index 11de982c..89e914b8 100644 --- a/src/components/Dashboard/ExpenseByProject.jsx +++ b/src/components/Dashboard/ExpenseByProject.jsx @@ -1,18 +1,18 @@ import React, { useState, useEffect } from "react"; import Chart from "react-apexcharts"; import { useExpenseType } from "../../hooks/masterHook/useMaster"; -import { useSelectedProject } from "../../slices/apiDataManager"; +import { useSelector } from "react-redux"; import { useExpenseDataByProject } from "../../hooks/useDashboard_Data"; const ExpenseByProject = () => { - const projectId = useSelectedProject(); + const projectId = useSelector((store) => store.localVariables.projectId); const [range, setRange] = useState("12M"); const [selectedType, setSelectedType] = useState(""); + const [viewMode, setViewMode] = useState("Category"); const [chartData, setChartData] = useState({ categories: [], data: [] }); const { ExpenseTypes, loading: typeLoading } = useExpenseType(); - // Fetch API data const { data: expenseApiData, isLoading } = useExpenseDataByProject( projectId, selectedType, @@ -25,46 +25,95 @@ const ExpenseByProject = () => { (item) => `${item.monthName} ${item.year}` ); const data = expenseApiData.map((item) => item.total); - setChartData({ categories, data }); } else { setChartData({ categories: [], data: [] }); } }, [expenseApiData]); + const getSelectedTypeName = () => { + if (!selectedType) return "All Types"; + const found = ExpenseTypes.find((t) => t.id === selectedType); + return found ? found.name : "All Types"; + }; + const options = { chart: { type: "bar", toolbar: { show: false } }, plotOptions: { bar: { horizontal: false, columnWidth: "55%", borderRadius: 4 } }, dataLabels: { enabled: true, formatter: (val) => val }, - xaxis: { categories: chartData.categories, labels: { style: { fontSize: "12px" }, rotate: -45 } }, + xaxis: { + categories: chartData.categories, + labels: { style: { fontSize: "12px" }, rotate: -45 }, + }, + tooltip: { + y: { + formatter: (val) => `${val.toLocaleString()} (${getSelectedTypeName()})`, + }, + }, + annotations: { xaxis: [{ x: 0, strokeDashArray: 0, }] }, fill: { opacity: 1 }, - colors: ["#2196f3"] + colors: ["#2196f3"], }; const series = [ { - name: selectedType || "Expense", - data: chartData.data - } + name: getSelectedTypeName(), + data: chartData.data, + }, ]; return (
{/* Header */}
-
+
-
Expense Breakdown
-

Detailed project expenses

+
Monthly Expense -
+

Detailed project expenses

-
- {/* Expense Type Dropdown */} + {/* View Mode Dropdown → same style as Header */} +
+ +
    +
  • + +
  • +
  • + +
  • +
+
+ {viewMode === "Category" && ( -
+ )}
- {/* Range Buttons */} -
- {["6M", "12M", "All"].map((item) => ( + {/* Range Buttons + Expense Dropdown */} +
+ {["1M", "3M", "6M", "12M", "All"].map((item) => (
{/* Chart */} -
+
{isLoading ? (

Loading chart...

) : ( @@ -103,5 +159,3 @@ const ExpenseByProject = () => { }; export default ExpenseByProject; - - diff --git a/src/components/Layout/Header.jsx b/src/components/Layout/Header.jsx index 870672e3..ec8aadb0 100644 --- a/src/components/Layout/Header.jsx +++ b/src/components/Layout/Header.jsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState,useMemo } from "react"; +import { useCallback, useEffect, useState, useMemo } from "react"; import getGreetingMessage from "../../utils/greetingHandler"; import { cacheData, @@ -22,7 +22,7 @@ import { ALLOW_PROJECTSTATUS_ID, MANAGE_PROJECT, UUID_REGEX } from "../../utils/ import { useAuthModal, useLogout } from "../../hooks/useAuth"; const Header = () => { - const { profile } = useProfile(); + const { profile } = useProfile(); const { data: masterData } = useMaster(); const location = useLocation(); const dispatch = useDispatch(); @@ -58,8 +58,8 @@ const Header = () => { isDashboardPath ? projectNames : projectNames?.filter((project) => - ALLOW_PROJECTSTATUS_ID.includes(project.projectStatusId) - ), + ALLOW_PROJECTSTATUS_ID.includes(project.projectStatusId) + ), [projectNames, isDashboardPath] ); @@ -67,11 +67,11 @@ const Header = () => { if (projectLoading) return "Loading..."; if (!projectNames?.length) return "No Projects Assigned"; if (projectNames.length === 1) return projectNames[0].name; - if (selectedProject === null) return "All Projects"; + if (selectedProject === null) return "All Projects"; const selectedObj = projectNames.find((p) => p.id === selectedProject); return selectedObj - ? selectedObj.name - : projectNames[0]?.name || "No Projects Assigned"; + ? selectedObj.name + : projectNames[0]?.name || "No Projects Assigned"; }, [projectLoading, projectNames, selectedProject]); // ===== Role Helper ===== @@ -200,13 +200,13 @@ const Header = () => { style={{ overflow: "auto", maxHeight: "300px" }} > -
  • - -
  • - +
  • + +
  • + {[...projectsForDropdown] .sort((a, b) => a?.name?.localeCompare(b.name)) .map((project) => ( -- 2.43.0 From 60785cb246e7ff9b658d5664e8060a37129c0555 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Sat, 4 Oct 2025 09:50:44 +0530 Subject: [PATCH 4/5] chages in dashoard --- src/components/Dashboard/ExpenseByProject.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Dashboard/ExpenseByProject.jsx b/src/components/Dashboard/ExpenseByProject.jsx index 89e914b8..66cbdd45 100644 --- a/src/components/Dashboard/ExpenseByProject.jsx +++ b/src/components/Dashboard/ExpenseByProject.jsx @@ -73,7 +73,7 @@ const ExpenseByProject = () => {
    {/* View Mode Dropdown → same style as Header */} -
    +
    +
    + +
    ); }; diff --git a/src/components/Dashboard/ExpenseByProject.jsx b/src/components/Dashboard/ExpenseByProject.jsx index 66cbdd45..bd7e115d 100644 --- a/src/components/Dashboard/ExpenseByProject.jsx +++ b/src/components/Dashboard/ExpenseByProject.jsx @@ -3,6 +3,8 @@ import Chart from "react-apexcharts"; import { useExpenseType } from "../../hooks/masterHook/useMaster"; import { useSelector } from "react-redux"; import { useExpenseDataByProject } from "../../hooks/useDashboard_Data"; +import { formatCurrency } from "../../utils/appUtils"; +import { formatDate_DayMonth } from "../../utils/dateUtils"; const ExpenseByProject = () => { const projectId = useSelector((store) => store.localVariables.projectId); @@ -22,7 +24,7 @@ const ExpenseByProject = () => { useEffect(() => { if (expenseApiData) { const categories = expenseApiData.map( - (item) => `${item.monthName} ${item.year}` + (item) => formatDate_DayMonth(item.monthName, item.year) ); const data = expenseApiData.map((item) => item.total); setChartData({ categories, data }); @@ -40,16 +42,17 @@ const ExpenseByProject = () => { const options = { chart: { type: "bar", toolbar: { show: false } }, plotOptions: { bar: { horizontal: false, columnWidth: "55%", borderRadius: 4 } }, - dataLabels: { enabled: true, formatter: (val) => val }, + dataLabels: { enabled: true, formatter: (val) => formatCurrency(val) }, xaxis: { categories: chartData.categories, labels: { style: { fontSize: "12px" }, rotate: -45 }, }, tooltip: { y: { - formatter: (val) => `${val.toLocaleString()} (${getSelectedTypeName()})`, + formatter: (val) => `${formatCurrency(val)} (${getSelectedTypeName()})`, }, }, + annotations: { xaxis: [{ x: 0, strokeDashArray: 0, }] }, fill: { opacity: 1 }, colors: ["#2196f3"], @@ -131,8 +134,8 @@ const ExpenseByProject = () => {