From 014012abf3af88fa8a651fe1eeb05124f491f605 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Wed, 1 Oct 2025 16:04:30 +0530 Subject: [PATCH 1/3] Adding Weidgets for Expense. --- src/components/Dashboard/Dashboard.jsx | 19 ++- src/components/Dashboard/ExpenseChart.jsx | 145 ++++++++++++++++++ src/components/Dashboard/ExpenseChartBar.jsx | 48 ++++++ .../Dashboard/ExpenseChartDesign1.jsx | 72 +++++++++ .../Dashboard/ExpenseChartDesign2.jsx | 131 ++++++++++++++++ 5 files changed, 414 insertions(+), 1 deletion(-) create mode 100644 src/components/Dashboard/ExpenseChart.jsx create mode 100644 src/components/Dashboard/ExpenseChartBar.jsx create mode 100644 src/components/Dashboard/ExpenseChartDesign1.jsx create mode 100644 src/components/Dashboard/ExpenseChartDesign2.jsx diff --git a/src/components/Dashboard/Dashboard.jsx b/src/components/Dashboard/Dashboard.jsx index 53dc3710..a3cd6300 100644 --- a/src/components/Dashboard/Dashboard.jsx +++ b/src/components/Dashboard/Dashboard.jsx @@ -14,6 +14,10 @@ import { useSelector } from "react-redux"; // import ProjectProgressChart from "./ProjectProgressChart"; // import ProjectOverview from "../Project/ProjectOverview"; import AttendanceOverview from "./AttendanceChart"; +import ExpenseChart from "./ExpenseChart"; +import ExpenseChartDesign2 from "./ExpenseChartDesign2"; +import ExpenseChartDesign1 from "./ExpenseChartDesign1"; +import ExpenseChartBar from "./ExpenseChartBar"; const Dashboard = () => { // const { projectsCardData } = useDashboardProjectsCardData(); @@ -27,10 +31,23 @@ const Dashboard = () => { return (
+
+ +
+
+ +
+
+ +
+
+ +
+ {!isAllProjectsSelected && (
- {/* ✅ Removed unnecessary projectId prop */} + {/* Removed unnecessary projectId prop */}
)}
diff --git a/src/components/Dashboard/ExpenseChart.jsx b/src/components/Dashboard/ExpenseChart.jsx new file mode 100644 index 00000000..a0977f12 --- /dev/null +++ b/src/components/Dashboard/ExpenseChart.jsx @@ -0,0 +1,145 @@ +import React from "react"; +import Chart from "react-apexcharts"; + +const ExpenseChart = () => { + // Radial Bar chart config + const radialBarOptions = { + chart: { + type: "radialBar", + }, + plotOptions: { + radialBar: { + hollow: { + size: "60%", + }, + dataLabels: { + name: { + show: true, + }, + value: { + fontSize: "18px", + fontWeight: 600, + color: "#000", + }, + }, + }, + }, + labels: ["Expenses"], + series: [68], // % of budget spent + colors: ["#FF4560"], + }; + + // Dummy expense data + const expenses = { + food: 450, + transport: 300, + shopping: 150, + bills: 200, + }; + + const total = + expenses.food + expenses.transport + expenses.shopping + expenses.bills; + + return ( +
+ {/* Card Header */} +
+
Expense Breakdown-1
+

Detailed project expenses

+
+ + {/* Card Body */} +
+
    +
  • +
    + {/* Centered Chart */} +
    +
    + +
    +
    + + {/* Info Section */} +
    +
    + {/* Food */} +
    +
    + + + +
    +
    + Food +
    {expenses.food}
    +
    +
    + + {/* Transport */} +
    +
    + + + +
    +
    + Transport +
    {expenses.transport}
    +
    +
    + + {/* Shopping */} +
    +
    + + + +
    +
    + Shopping +
    {expenses.shopping}
    +
    +
    + + {/* Bills */} +
    +
    + + + +
    +
    + Bills +
    {expenses.bills}
    +
    +
    + + {/* Total */} +
    +
    + + + +
    +
    + Total +
    {total}
    +
    +
    +
    +
    +
    +
  • +
+
+
+ ); +}; + +export default ExpenseChart; \ No newline at end of file diff --git a/src/components/Dashboard/ExpenseChartBar.jsx b/src/components/Dashboard/ExpenseChartBar.jsx new file mode 100644 index 00000000..fa542380 --- /dev/null +++ b/src/components/Dashboard/ExpenseChartBar.jsx @@ -0,0 +1,48 @@ +import React from "react"; +import Chart from "react-apexcharts"; + +const ExpenseChartBar = () => { + const series = [ + { + name: "Expenses", + data: [450, 300, 150, 200], + }, + ]; + const options = { + chart: { type: "bar", height: 350 }, + plotOptions: { + bar: { + horizontal: true, + borderRadius: 6, + }, + }, + dataLabels: { enabled: true }, + xaxis: { + categories: ["Food", "Transport", "Shopping", "Bills"], + }, + colors: ["#1E90FF"], + }; + + const total = 450 + 300 + 150 + 200; + + return ( +
+
+
Expense Breakdown-2
+

Detailed project expenses

+
+
+ {/* Bar Chart */} + + + {/* Total */} +
+ Total Expenses + {total} +
+
+
+ ); +}; + +export default ExpenseChartBar; \ No newline at end of file diff --git a/src/components/Dashboard/ExpenseChartDesign1.jsx b/src/components/Dashboard/ExpenseChartDesign1.jsx new file mode 100644 index 00000000..8d870fc4 --- /dev/null +++ b/src/components/Dashboard/ExpenseChartDesign1.jsx @@ -0,0 +1,72 @@ +import React from "react"; +import Chart from "react-apexcharts"; + +const ExpenseChartDesign1 = () => { + const budget = 1500; + const expenses = { + food: 450, + transport: 300, + shopping: 250, + bills: 200, + }; + + const total = Object.values(expenses).reduce((a, b) => a + b, 0); + const percent = Math.round((total / budget) * 100); + + const radialOptions = { + chart: { type: "radialBar" }, + plotOptions: { + radialBar: { + hollow: { size: "65%" }, + dataLabels: { + name: { show: true, fontSize: "14px" }, + value: { fontSize: "20px", fontWeight: "bold" }, + }, + }, + }, + labels: ["Budget Used"], + colors: ["#7367F0"], + series: [percent], + }; + + return ( +
+
+
Expense Breakdown-4
+

Detailed project expenses

+
+ +
+ {/* Radial Chart */} + +
+ ${total} / ${budget} spent +
+ + {/* Categories */} +
+ {Object.entries(expenses).map(([key, value], idx) => ( +
+
+ + + +
+
+ {key} + ${value} +
+
+ ))} +
+
+
+ ); +}; + +export default ExpenseChartDesign1; \ No newline at end of file diff --git a/src/components/Dashboard/ExpenseChartDesign2.jsx b/src/components/Dashboard/ExpenseChartDesign2.jsx new file mode 100644 index 00000000..f01b348e --- /dev/null +++ b/src/components/Dashboard/ExpenseChartDesign2.jsx @@ -0,0 +1,131 @@ + +import React from "react"; +import Chart from "react-apexcharts"; + +const ExpenseChartDesign2 = () => { + const expenses = { + food: 450, + transport: 300, + shopping: 250, + bills: 200, + }; + + const total = Object.values(expenses).reduce((a, b) => a + b, 0); + + const donutOptions = { + chart: { type: "donut" }, + labels: ["Food", "Transport", "Shopping", "Bills"], + legend: { show: false }, + dataLabels: { + enabled: true, + formatter: (val) => `${val.toFixed(0)}%`, + }, + colors: ["#7367F0", "#28C76F", "#FF9F43", "#EA5455"], + plotOptions: { + pie: { + donut: { + size: "70%", + labels: { + show: true, + total: { + show: true, + label: "Total", + fontSize: "16px", + formatter: () => `$${total}`, + }, + }, + }, + }, + }, + }; + + const series = Object.values(expenses); + + return ( +
+
+
Expense Breakdown-3
+

Detailed project expenses

+
+ +
+ {/* Donut Chart */} +
+ +
+ + {/* Custom Legend with Icons and Right-Aligned Amount */} +
+
+ {/* Food */} +
+
+
+ + + +
+ Food +
+ ${expenses.food} +
+ + {/* Transport */} +
+
+
+ + + +
+ Transport +
+ ${expenses.transport} +
+ + {/* Shopping */} +
+
+
+ + + +
+ Shopping +
+ ${expenses.shopping} +
+ + {/* Bills */} +
+
+
+ + + +
+ Bills +
+ ${expenses.bills} +
+ + {/* Total */} +
+
+
+ + + +
+ Total +
+ ${total} +
+
+
+
+
+ ); +}; + +export default ExpenseChartDesign2; -- 2.43.0 From edebc2e471d31adb973b3d25b72ea4f1cd91b480 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Wed, 1 Oct 2025 18:56:30 +0530 Subject: [PATCH 2/3] Implementing api for Expense data --- src/components/Dashboard/Dashboard.jsx | 16 +- src/components/Dashboard/ExpenseChart.jsx | 145 ------------ src/components/Dashboard/ExpenseChartBar.jsx | 48 ---- .../Dashboard/ExpenseChartDesign1.jsx | 72 ------ .../Dashboard/ExpenseChartDesign2.jsx | 212 +++++++++--------- src/hooks/useDashboard_Data.jsx | 64 +++--- src/repositories/GlobalRepository.jsx | 58 +++-- 7 files changed, 180 insertions(+), 435 deletions(-) delete mode 100644 src/components/Dashboard/ExpenseChart.jsx delete mode 100644 src/components/Dashboard/ExpenseChartBar.jsx delete mode 100644 src/components/Dashboard/ExpenseChartDesign1.jsx diff --git a/src/components/Dashboard/Dashboard.jsx b/src/components/Dashboard/Dashboard.jsx index a3cd6300..bdd4e4a4 100644 --- a/src/components/Dashboard/Dashboard.jsx +++ b/src/components/Dashboard/Dashboard.jsx @@ -14,10 +14,7 @@ import { useSelector } from "react-redux"; // import ProjectProgressChart from "./ProjectProgressChart"; // import ProjectOverview from "../Project/ProjectOverview"; import AttendanceOverview from "./AttendanceChart"; -import ExpenseChart from "./ExpenseChart"; import ExpenseChartDesign2 from "./ExpenseChartDesign2"; -import ExpenseChartDesign1 from "./ExpenseChartDesign1"; -import ExpenseChartBar from "./ExpenseChartBar"; const Dashboard = () => { // const { projectsCardData } = useDashboardProjectsCardData(); @@ -31,20 +28,11 @@ const Dashboard = () => { return (
-
- -
+
-
- -
-
- -
- - + {!isAllProjectsSelected && (
{/* Removed unnecessary projectId prop */} diff --git a/src/components/Dashboard/ExpenseChart.jsx b/src/components/Dashboard/ExpenseChart.jsx deleted file mode 100644 index a0977f12..00000000 --- a/src/components/Dashboard/ExpenseChart.jsx +++ /dev/null @@ -1,145 +0,0 @@ -import React from "react"; -import Chart from "react-apexcharts"; - -const ExpenseChart = () => { - // Radial Bar chart config - const radialBarOptions = { - chart: { - type: "radialBar", - }, - plotOptions: { - radialBar: { - hollow: { - size: "60%", - }, - dataLabels: { - name: { - show: true, - }, - value: { - fontSize: "18px", - fontWeight: 600, - color: "#000", - }, - }, - }, - }, - labels: ["Expenses"], - series: [68], // % of budget spent - colors: ["#FF4560"], - }; - - // Dummy expense data - const expenses = { - food: 450, - transport: 300, - shopping: 150, - bills: 200, - }; - - const total = - expenses.food + expenses.transport + expenses.shopping + expenses.bills; - - return ( -
- {/* Card Header */} -
-
Expense Breakdown-1
-

Detailed project expenses

-
- - {/* Card Body */} -
-
    -
  • -
    - {/* Centered Chart */} -
    -
    - -
    -
    - - {/* Info Section */} -
    -
    - {/* Food */} -
    -
    - - - -
    -
    - Food -
    {expenses.food}
    -
    -
    - - {/* Transport */} -
    -
    - - - -
    -
    - Transport -
    {expenses.transport}
    -
    -
    - - {/* Shopping */} -
    -
    - - - -
    -
    - Shopping -
    {expenses.shopping}
    -
    -
    - - {/* Bills */} -
    -
    - - - -
    -
    - Bills -
    {expenses.bills}
    -
    -
    - - {/* Total */} -
    -
    - - - -
    -
    - Total -
    {total}
    -
    -
    -
    -
    -
    -
  • -
-
-
- ); -}; - -export default ExpenseChart; \ No newline at end of file diff --git a/src/components/Dashboard/ExpenseChartBar.jsx b/src/components/Dashboard/ExpenseChartBar.jsx deleted file mode 100644 index fa542380..00000000 --- a/src/components/Dashboard/ExpenseChartBar.jsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from "react"; -import Chart from "react-apexcharts"; - -const ExpenseChartBar = () => { - const series = [ - { - name: "Expenses", - data: [450, 300, 150, 200], - }, - ]; - const options = { - chart: { type: "bar", height: 350 }, - plotOptions: { - bar: { - horizontal: true, - borderRadius: 6, - }, - }, - dataLabels: { enabled: true }, - xaxis: { - categories: ["Food", "Transport", "Shopping", "Bills"], - }, - colors: ["#1E90FF"], - }; - - const total = 450 + 300 + 150 + 200; - - return ( -
-
-
Expense Breakdown-2
-

Detailed project expenses

-
-
- {/* Bar Chart */} - - - {/* Total */} -
- Total Expenses - {total} -
-
-
- ); -}; - -export default ExpenseChartBar; \ No newline at end of file diff --git a/src/components/Dashboard/ExpenseChartDesign1.jsx b/src/components/Dashboard/ExpenseChartDesign1.jsx deleted file mode 100644 index 8d870fc4..00000000 --- a/src/components/Dashboard/ExpenseChartDesign1.jsx +++ /dev/null @@ -1,72 +0,0 @@ -import React from "react"; -import Chart from "react-apexcharts"; - -const ExpenseChartDesign1 = () => { - const budget = 1500; - const expenses = { - food: 450, - transport: 300, - shopping: 250, - bills: 200, - }; - - const total = Object.values(expenses).reduce((a, b) => a + b, 0); - const percent = Math.round((total / budget) * 100); - - const radialOptions = { - chart: { type: "radialBar" }, - plotOptions: { - radialBar: { - hollow: { size: "65%" }, - dataLabels: { - name: { show: true, fontSize: "14px" }, - value: { fontSize: "20px", fontWeight: "bold" }, - }, - }, - }, - labels: ["Budget Used"], - colors: ["#7367F0"], - series: [percent], - }; - - return ( -
-
-
Expense Breakdown-4
-

Detailed project expenses

-
- -
- {/* Radial Chart */} - -
- ${total} / ${budget} spent -
- - {/* Categories */} -
- {Object.entries(expenses).map(([key, value], idx) => ( -
-
- - - -
-
- {key} - ${value} -
-
- ))} -
-
-
- ); -}; - -export default ExpenseChartDesign1; \ No newline at end of file diff --git a/src/components/Dashboard/ExpenseChartDesign2.jsx b/src/components/Dashboard/ExpenseChartDesign2.jsx index f01b348e..e5df1bb2 100644 --- a/src/components/Dashboard/ExpenseChartDesign2.jsx +++ b/src/components/Dashboard/ExpenseChartDesign2.jsx @@ -1,131 +1,119 @@ - -import React from "react"; +import React, { useState } from "react"; import Chart from "react-apexcharts"; +import DateRangePicker from "../common/DateRangePicker"; +import { useDashboard_ExpenseData } from "../../hooks/useDashboard_Data"; +import { useSelectedProject } from "../../slices/apiDataManager"; const ExpenseChartDesign2 = () => { - const expenses = { - food: 450, - transport: 300, - shopping: 250, - bills: 200, - }; + const projectId = useSelectedProject() + const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" }); - const total = Object.values(expenses).reduce((a, b) => a + b, 0); + const { data, isLoading, isError, error } = useDashboard_ExpenseData( + projectId, + dateRange.startDate, + dateRange.endDate + ); - const donutOptions = { - chart: { type: "donut" }, - labels: ["Food", "Transport", "Shopping", "Bills"], - legend: { show: false }, - dataLabels: { - enabled: true, - formatter: (val) => `${val.toFixed(0)}%`, - }, - colors: ["#7367F0", "#28C76F", "#FF9F43", "#EA5455"], - plotOptions: { - pie: { - donut: { - size: "70%", - labels: { - show: true, - total: { - show: true, - label: "Total", - fontSize: "16px", - formatter: () => `$${total}`, + if (isLoading) return
Loading....
+ if (isError) return
{error.message}
; + + const report = data?.report || []; + + // Map the API data to chart labels and series + const labels = report.map((item) => item.projectName); + const series = report.map((item) => item.totalApprovedAmount || 0); + const total = data?.totalAmount || 0; + + const donutOptions = { + chart: { type: "donut" }, + labels, + legend: { show: false }, + dataLabels: { enabled: true, formatter: (val) => `${val.toFixed(0)}%` }, + colors: ["#7367F0", "#28C76F", "#FF9F43", "#EA5455", "#00CFE8", "#FF78B8"], + plotOptions: { + pie: { + donut: { + size: "70%", + labels: { + show: true, + total: { + show: true, + label: "Total", + fontSize: "16px", + formatter: () => `${total}`, + }, + }, + }, }, - }, }, - }, - }, - }; + }; - const series = Object.values(expenses); - - return ( -
-
-
Expense Breakdown-3
-

Detailed project expenses

-
- -
- {/* Donut Chart */} -
- + if (data?.report === 0) { + return
+ No data found
+ } - {/* Custom Legend with Icons and Right-Aligned Amount */} -
-
- {/* Food */} -
-
-
- - - + return ( +
+
+
+
Expense Breakdown
+

Detailed project expenses

+
+
+
- Food -
- ${expenses.food}
- {/* Transport */} -
-
-
- - - -
- Transport -
- ${expenses.transport} -
+
+ {report.length === 0 ? ( +
+ No data found +
+ ) : ( + <> +
+ item.totalApprovedAmount || 0)} + type="donut" + width="320" + /> +
- {/* Shopping */} -
-
-
- - - -
- Shopping -
- ${expenses.shopping} +
+
+ {report.map((item, idx) => ( +
+
+ + + +
+
+ {item.projectName} + {item.totalApprovedAmount} +
+
+ ))} +
+
+ + )}
- - {/* Bills */} -
-
-
- - - -
- Bills -
- ${expenses.bills} -
- - {/* Total */} -
-
-
- - - -
- Total -
- ${total} -
-
-
-
- ); + ); }; export default ExpenseChartDesign2; diff --git a/src/hooks/useDashboard_Data.jsx b/src/hooks/useDashboard_Data.jsx index b9c393b7..9843f72a 100644 --- a/src/hooks/useDashboard_Data.jsx +++ b/src/hooks/useDashboard_Data.jsx @@ -200,35 +200,45 @@ export const useAttendanceOverviewData = (projectId, days) => { // }) // } -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 useDashboard_ExpenseData = (projectId, startDate, endDate) => { + return useQuery({ + queryKey: ["dashboardExpenses", projectId, startDate, endDate], + queryFn: async () => { + const resp = await GlobalRepository.getExpenseData(projectId, startDate, endDate); + return resp.data; // this will return the "data" object from API response + }, + }); +}; + +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; } }) } @@ -236,7 +246,7 @@ export const useDashboardTasksCardData = (projectId) => { // return useQuery({ // queryKey:["dashboardAttendanceOverView",projectId], // queryFn:async()=> { - + // const resp = await GlobalRepository.getAttendanceOverview(projectId, days); // return resp.data; // } @@ -244,12 +254,12 @@ export const useDashboardTasksCardData = (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; } }) } \ No newline at end of file diff --git a/src/repositories/GlobalRepository.jsx b/src/repositories/GlobalRepository.jsx index d3367fa6..96674b76 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,30 +19,54 @@ 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 = []; + + if (projectId) { + queryParams.push(`projectId=${projectId}`); + } + + if (startDate) { + queryParams.push(`startDate=${startDate}`); + } + if (endDate) { + queryParams.push(`endDate=${endDate}`); + } + + if (queryParams.length > 0) { + url += `?${queryParams.join("&")}`; + } + + return api.get(url ); + }, }; export default GlobalRepository; -- 2.43.0 From e26c87fd3daedd2d17b891213a5d328b20e827f7 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Thu, 2 Oct 2025 00:47:01 +0530 Subject: [PATCH 3/3] added expense graph --- src/components/Dashboard/AttendanceChart.jsx | 65 +++----- src/components/Dashboard/Dashboard.jsx | 38 +++-- .../Dashboard/ExpenseChartDesign.jsx | 142 ++++++++++++++++++ .../Dashboard/ExpenseChartDesign2.jsx | 119 --------------- src/components/Layout/Header.jsx | 10 +- src/components/common/DateRangePicker.jsx | 65 ++++---- src/hooks/useDashboard_Data.jsx | 28 ++-- src/utils/appUtils.js | 8 +- 8 files changed, 246 insertions(+), 229 deletions(-) create mode 100644 src/components/Dashboard/ExpenseChartDesign.jsx delete mode 100644 src/components/Dashboard/ExpenseChartDesign2.jsx diff --git a/src/components/Dashboard/AttendanceChart.jsx b/src/components/Dashboard/AttendanceChart.jsx index 3b58b897..0eefb1e8 100644 --- a/src/components/Dashboard/AttendanceChart.jsx +++ b/src/components/Dashboard/AttendanceChart.jsx @@ -17,28 +17,30 @@ const formatDate = (dateStr) => { const AttendanceOverview = () => { const [dayRange, setDayRange] = useState(7); const [view, setView] = useState("chart"); - const selectedProject = useSelectedProject() + const selectedProject = useSelectedProject(); + const { data: attendanceOverviewData, isLoading, isError, error } = useAttendanceOverviewData( selectedProject, dayRange ); + // Use empty array while loading + const attendanceData = attendanceOverviewData || []; + const { tableData, roles, dates } = useMemo(() => { - if (!attendanceOverviewData || attendanceOverviewData.length === 0) { + if (!attendanceData || attendanceData.length === 0) { return { tableData: [], roles: [], dates: [] }; } const map = new Map(); - attendanceOverviewData.forEach((entry) => { + attendanceData.forEach((entry) => { const date = formatDate(entry.date); if (!map.has(date)) map.set(date, {}); map.get(date)[entry.role.trim()] = entry.present; }); - const uniqueRoles = [ - ...new Set(attendanceOverviewData.map((e) => e.role.trim())), - ]; + const uniqueRoles = [...new Set(attendanceData.map((e) => e.role.trim()))]; const sortedDates = [...map.keys()]; const tableData = sortedDates.map((date) => { @@ -50,7 +52,7 @@ const AttendanceOverview = () => { }); return { tableData, roles: uniqueRoles, dates: sortedDates }; - }, [attendanceOverviewData,isLoading,selectedProject,dayRange]); + }, [attendanceData]); const chartSeries = roles.map((role) => ({ name: role, @@ -58,29 +60,24 @@ const AttendanceOverview = () => { })); const chartOptions = { - chart: { - type: "bar", - stacked: true, - height: 400, - toolbar: { show: false }, - }, + chart: { type: "bar", stacked: true, height: 400, toolbar: { show: false } }, plotOptions: { bar: { borderRadius: 2, columnWidth: "60%" } }, xaxis: { categories: tableData.map((row) => row.date) }, - yaxis: { - show: true, - axisBorder: { show: true, color: "#78909C" }, - axisTicks: { show: true, color: "#78909C", width: 6 }, - }, + yaxis: { show: true, axisBorder: { show: true, color: "#78909C" }, axisTicks: { show: true, color: "#78909C", width: 6 } }, legend: { position: "bottom" }, fill: { opacity: 1 }, colors: roles.map((_, i) => flatColors[i % flatColors.length]), }; - if (isLoading) return
Loading...
; - if (isError) return

{error.message}

; - return ( -
+
+ {/* Optional subtle loading overlay */} + {isLoading && ( +
+ Loading... +
+ )} + {/* Header */}
@@ -88,27 +85,15 @@ const AttendanceOverview = () => {

Role-wise present count

- setDayRange(Number(e.target.value))}> - -
@@ -127,9 +112,7 @@ const AttendanceOverview = () => { Role {dates.map((date, idx) => ( - - {date} - + {date} ))} @@ -152,4 +135,4 @@ const AttendanceOverview = () => { ); }; -export default AttendanceOverview; \ No newline at end of file +export default AttendanceOverview; diff --git a/src/components/Dashboard/Dashboard.jsx b/src/components/Dashboard/Dashboard.jsx index bef66711..362b53da 100644 --- a/src/components/Dashboard/Dashboard.jsx +++ b/src/components/Dashboard/Dashboard.jsx @@ -14,34 +14,32 @@ import { useDispatch, useSelector } from "react-redux"; // import ProjectProgressChart from "./ProjectProgressChart"; // import ProjectOverview from "../Project/ProjectOverview"; import AttendanceOverview from "./AttendanceChart"; -import ExpenseChartDesign2 from "./ExpenseChartDesign2"; +import ExpenseChartDesign from "./ExpenseChartDesign"; const Dashboard = () => { - // const { projectsCardData } = useDashboardProjectsCardData(); // const { teamsCardData } = useDashboardTeamsCardData(); // const { tasksCardData } = useDashboardTasksCardData(); - // Get the selected project ID from Redux store - const projectId = useSelector((store) => store.localVariables.projectId); - const isAllProjectsSelected = projectId === null; + // Get the selected project ID from Redux store + const projectId = useSelector((store) => store.localVariables.projectId); + const isAllProjectsSelected = projectId === null; - return ( -
-
- -
- -
- - {!isAllProjectsSelected && ( -
- {/* Removed unnecessary projectId prop */} -
- )} -
+ return ( +
+
+
+
- ); + + {!isAllProjectsSelected && ( +
+ {/* Removed unnecessary projectId prop */} +
+ )} +
+
+ ); }; export default Dashboard; diff --git a/src/components/Dashboard/ExpenseChartDesign.jsx b/src/components/Dashboard/ExpenseChartDesign.jsx new file mode 100644 index 00000000..b9ec9f91 --- /dev/null +++ b/src/components/Dashboard/ExpenseChartDesign.jsx @@ -0,0 +1,142 @@ +import React, { useEffect, useState } from "react"; +import Chart from "react-apexcharts"; +import { useDashboard_ExpenseData } from "../../hooks/useDashboard_Data"; +import { useSelectedProject } from "../../slices/apiDataManager"; +import { DateRangePicker1 } from "../common/DateRangePicker"; +import { FormProvider, useForm } from "react-hook-form"; +import { localToUtc } from "../../utils/appUtils"; + +const ExpenseChartDesign = () => { + const projectId = useSelectedProject(); + + const methods = useForm({ + defaultValues: { + startDate: "", + endDate: "", + }, + }); + + const { watch } = methods; + + const [startDate, endDate] = watch(["startDate", "endDate"]); + console.log(startDate,endDate) + const { data, isLoading, isError, error, isFetching } = useDashboard_ExpenseData( + projectId, + localToUtc(startDate), + localToUtc(endDate) +); + + if (isError) return
{error.message}
; + + const report = data?.report || []; + + const labels = report.map((item) => item.projectName); + const series = report.map((item) => item.totalApprovedAmount || 0); + const total = data?.totalAmount || 0; + + const donutOptions = { + chart: { type: "donut" }, + labels, + legend: { show: false }, + dataLabels: { enabled: true, formatter: (val) => `${val.toFixed(0)}%` }, + colors: ["#7367F0", "#28C76F", "#FF9F43", "#EA5455", "#00CFE8", "#FF78B8"], + plotOptions: { + pie: { + donut: { + size: "70%", + labels: { + show: true, + total: { + show: true, + label: "Total", + fontSize: "16px", + formatter: () => `${total}`, + }, + }, + }, + }, + }, + }; + + + if (data?.report === 0) { + return
No data found
; + } + + return ( +
+
+
+
Expense Breakdown
+

Detailed project expenses

+
+ +
+ + + +
+
+ +
+ {/* Initial loading: show full loader */} + {isLoading && ( +
+ Loading... +
+ )} + + {/* Data display */} + {!isLoading && report.length === 0 && ( +
No data found
+ )} + + {!isLoading && report.length > 0 && ( + <> + {/* Overlay spinner for refetch */} + {isFetching && ( +
+ Loading... +
+ )} + +
+ item.totalApprovedAmount || 0)} + type="donut" + width="320" + /> +
+ +
+
+ {report.map((item, idx) => ( +
+
+ + + +
+
+ {item.projectName} + {item.totalApprovedAmount} +
+
+ ))} +
+
+ + )} +
+
+ + ); +}; + +export default ExpenseChartDesign; diff --git a/src/components/Dashboard/ExpenseChartDesign2.jsx b/src/components/Dashboard/ExpenseChartDesign2.jsx deleted file mode 100644 index e5df1bb2..00000000 --- a/src/components/Dashboard/ExpenseChartDesign2.jsx +++ /dev/null @@ -1,119 +0,0 @@ -import React, { useState } from "react"; -import Chart from "react-apexcharts"; -import DateRangePicker from "../common/DateRangePicker"; -import { useDashboard_ExpenseData } from "../../hooks/useDashboard_Data"; -import { useSelectedProject } from "../../slices/apiDataManager"; - -const ExpenseChartDesign2 = () => { - const projectId = useSelectedProject() - const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" }); - - const { data, isLoading, isError, error } = useDashboard_ExpenseData( - projectId, - dateRange.startDate, - dateRange.endDate - ); - - if (isLoading) return
Loading....
- if (isError) return
{error.message}
; - - const report = data?.report || []; - - // Map the API data to chart labels and series - const labels = report.map((item) => item.projectName); - const series = report.map((item) => item.totalApprovedAmount || 0); - const total = data?.totalAmount || 0; - - const donutOptions = { - chart: { type: "donut" }, - labels, - legend: { show: false }, - dataLabels: { enabled: true, formatter: (val) => `${val.toFixed(0)}%` }, - colors: ["#7367F0", "#28C76F", "#FF9F43", "#EA5455", "#00CFE8", "#FF78B8"], - plotOptions: { - pie: { - donut: { - size: "70%", - labels: { - show: true, - total: { - show: true, - label: "Total", - fontSize: "16px", - formatter: () => `${total}`, - }, - }, - }, - }, - }, - }; - - if (data?.report === 0) { - return
- No data found -
- } - - return ( -
-
-
-
Expense Breakdown
-

Detailed project expenses

-
-
- -
-
- -
- {report.length === 0 ? ( -
- No data found -
- ) : ( - <> -
- item.totalApprovedAmount || 0)} - type="donut" - width="320" - /> -
- -
-
- {report.map((item, idx) => ( -
-
- - - -
-
- {item.projectName} - {item.totalApprovedAmount} -
-
- ))} -
-
- - )} -
-
- ); -}; - -export default ExpenseChartDesign2; diff --git a/src/components/Layout/Header.jsx b/src/components/Layout/Header.jsx index ec55b48c..870672e3 100644 --- a/src/components/Layout/Header.jsx +++ b/src/components/Layout/Header.jsx @@ -67,7 +67,7 @@ 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"; const selectedObj = projectNames.find((p) => p.id === selectedProject); return selectedObj ? selectedObj.name @@ -199,6 +199,14 @@ const Header = () => { className="dropdown-menu" style={{ overflow: "auto", maxHeight: "300px" }} > + +
  • + +
  • + {[...projectsForDropdown] .sort((a, b) => a?.name?.localeCompare(b.name)) .map((project) => ( diff --git a/src/components/common/DateRangePicker.jsx b/src/components/common/DateRangePicker.jsx index 72aad155..99fae06f 100644 --- a/src/components/common/DateRangePicker.jsx +++ b/src/components/common/DateRangePicker.jsx @@ -14,7 +14,6 @@ const DateRangePicker = ({ if (endDateMode === "yesterday") { endDate.setDate(endDate.getDate() - 1); } - endDate.setHours(0, 0, 0, 0); const startDate = new Date(endDate); @@ -30,9 +29,14 @@ const DateRangePicker = ({ static: true, clickOpens: true, maxDate: endDate, - onChange: (selectedDates, dateStr) => { - const [startDateString, endDateString] = dateStr.split(" to "); - onRangeChange?.({ startDate: startDateString, endDate: endDateString }); + onChange: (selectedDates) => { + if (selectedDates.length === 2) { + const [start, end] = selectedDates; + onRangeChange?.({ + startDate: start.toLocaleDateString("en-CA"), + endDate: end.toLocaleDateString("en-CA"), + }); + } }, }); @@ -47,9 +51,7 @@ const DateRangePicker = ({ }, [onRangeChange, DateDifference, endDateMode]); const handleIconClick = () => { - if (inputRef.current) { - inputRef.current._flatpickr.open(); // directly opens flatpickr - } + inputRef.current?._flatpickr?.open(); }; return ( @@ -61,9 +63,9 @@ const DateRangePicker = ({ id="flatpickr-range" ref={inputRef} /> -
    @@ -72,10 +74,6 @@ const DateRangePicker = ({ export default DateRangePicker; - - - - export const DateRangePicker1 = ({ startField = "startDate", endField = "endDate", @@ -85,6 +83,8 @@ export const DateRangePicker1 = ({ resetSignal, defaultRange = true, maxDate = null, + sm, + md, ...rest }) => { const inputRef = useRef(null); @@ -118,7 +118,7 @@ export const DateRangePicker1 = ({ mode: "range", dateFormat: "d-m-Y", allowInput: allowText, - maxDate , + maxDate, onChange: (selectedDates) => { if (selectedDates.length === 2) { const [start, end] = selectedDates; @@ -148,31 +148,31 @@ export const DateRangePicker1 = ({ }, []); useEffect(() => { - if (resetSignal !== undefined) { - if (defaultRange) { - applyDefaultDates(); - } else { - setValue(startField, "", { shouldValidate: true }); - setValue(endField, "", { shouldValidate: true }); + if (resetSignal !== undefined) { + if (defaultRange) { + applyDefaultDates(); + } else { + setValue(startField, "", { shouldValidate: true }); + setValue(endField, "", { shouldValidate: true }); - if (inputRef.current?._flatpickr) { - inputRef.current._flatpickr.clear(); + if (inputRef.current?._flatpickr) { + inputRef.current._flatpickr.clear(); + } } } - } -}, [resetSignal, defaultRange, setValue, startField, endField]); - + }, [resetSignal, defaultRange, setValue, startField, endField]); const start = getValues(startField); const end = getValues(endField); const formattedValue = start && end ? `${start} To ${end}` : ""; return ( -
    +
    { inputRef.current = el; @@ -181,13 +181,10 @@ export const DateRangePicker1 = ({ readOnly={!allowText} autoComplete="off" /> - inputRef.current?._flatpickr?.open()} - > - - + >
    ); }; - diff --git a/src/hooks/useDashboard_Data.jsx b/src/hooks/useDashboard_Data.jsx index a65320e0..74731695 100644 --- a/src/hooks/useDashboard_Data.jsx +++ b/src/hooks/useDashboard_Data.jsx @@ -212,12 +212,19 @@ export const useDashboard_AttendanceData = (date, projectId) => { } export const useDashboard_ExpenseData = (projectId, startDate, endDate) => { + const hasBothDates = !!startDate && !!endDate; + const noDatesSelected = !startDate && !endDate; + + const shouldFetch = + noDatesSelected || + hasBothDates; return useQuery({ queryKey: ["dashboardExpenses", projectId, startDate, endDate], queryFn: async () => { const resp = await GlobalRepository.getExpenseData(projectId, startDate, endDate); - return resp.data; // this will return the "data" object from API response + return resp.data; }, + enabled:shouldFetch }); }; @@ -242,16 +249,17 @@ export const useDashboardTasksCardData = (projectId) => { } }) } -// export const useAttendanceOverviewData = (projectId, days) => { -// return useQuery({ -// queryKey:["dashboardAttendanceOverView",projectId], -// queryFn:async()=> { +export const useAttendanceOverviewData = (projectId, days) => { + return useQuery({ + queryKey: ["dashboardAttendanceOverView", projectId, days], + queryFn: async () => { + const resp = await GlobalRepository.getAttendanceOverview(projectId, days); + return resp.data; + }, + enabled: !!projectId, + }); +}; -// const resp = await GlobalRepository.getAttendanceOverview(projectId, days); -// return resp.data; -// } -// }) -// } export const useDashboardProjectsCardData = () => { return useQuery({ diff --git a/src/utils/appUtils.js b/src/utils/appUtils.js index 0766514a..5f117ce6 100644 --- a/src/utils/appUtils.js +++ b/src/utils/appUtils.js @@ -72,11 +72,11 @@ export const normalizeAllowedContentTypes = (allowedContentType) => { export function localToUtc(localDateString) { if (!localDateString || typeof localDateString !== "string") return null; - - const [year, month, day] = localDateString.trim().split("-"); - if (!year || !month || !day) return null; + const [day, month, year] = localDateString.trim().split("-"); + if (!day || !month || !year) return null; + // Create date in UTC const date = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day), 0, 0, 0)); return isNaN(date.getTime()) ? null : date.toISOString(); -} \ No newline at end of file +} -- 2.43.0