From 23a7a56e323fac49aa2ca2931aeb001c7f673e0a Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Fri, 5 Dec 2025 18:24:23 +0530 Subject: [PATCH 1/4] added collection overview widget --- .../Dashboard/CollectionOverview.jsx | 345 ++++++++++++++++++ src/components/Dashboard/Dashboard.jsx | 237 ++++++++---- src/hooks/useDashboard_Data.jsx | 10 + src/repositories/GlobalRepository.jsx | 4 +- 4 files changed, 523 insertions(+), 73 deletions(-) create mode 100644 src/components/Dashboard/CollectionOverview.jsx diff --git a/src/components/Dashboard/CollectionOverview.jsx b/src/components/Dashboard/CollectionOverview.jsx new file mode 100644 index 00000000..448072b4 --- /dev/null +++ b/src/components/Dashboard/CollectionOverview.jsx @@ -0,0 +1,345 @@ +import React from "react"; +import Chart from "react-apexcharts"; +import { useGetCollectionOverview } from "../../hooks/useDashboard_Data"; +import { formatFigure } from "../../utils/appUtils"; + +const CollectionOverview = ({ data }) => { + const borderColor = "#ddd"; + const labelColor = "#6c757d"; + + // Extract bucket values + const labels = ["0–30 Days", "30–60 Days", "60–90 Days", "90+ Days"]; + + const amounts = [ + data.bucket0To30Amount, + data.bucket30To60Amount, + data.bucket60To90Amount, + data.bucket90PlusAmount, + ]; + + // Colors (Zoho-style distributed) + const colors = ["#7367F0", "#00cfe8", "#28c76f", "#ea5455"]; + + const options = { + chart: { + type: "bar", + height: 260, + toolbar: { show: false }, + }, + + plotOptions: { + bar: { + horizontal: true, + barHeight: "65%", + distributed: true, + borderRadius: 8, + startingShape: "rounded", + }, + }, + + colors: colors, + + grid: { + borderColor: borderColor, + strokeDashArray: 6, + padding: { top: -10, bottom: -10 }, + xaxis: { lines: { show: true } }, + }, + + dataLabels: { + enabled: true, + formatter: (_, opts) => labels[opts.dataPointIndex], + style: { + colors: ["#fff"], + fontSize: "13px", + fontWeight: 500, + }, + offsetX: 0, + }, + + xaxis: { + categories: amounts.map((a) => a), + labels: { + style: { colors: labelColor, fontSize: "12px" }, + formatter: (val) => `₹${val.toLocaleString()}`, + }, + }, + + yaxis: { + labels: { + style: { + colors: labelColor, + fontSize: "13px", + }, + formatter: () => "", // hide duplicate labels + }, + }, + + tooltip: { + custom: ({ series, seriesIndex, dataPointIndex }) => { + return ` +
+ ${labels[dataPointIndex]}
+ ₹${series[seriesIndex][dataPointIndex].toLocaleString()} +
+ `; + }, + }, + + legend: { show: false }, + }; + + const series = [ + { + name: "Amount", + data: amounts, + }, + ]; + + return ( +
+ +
+ ); +}; + +export default CollectionOverview; +export const TopicBarChart = ({ data }) => { + const data1 = { + totalDueAmount: 213590, + totalCollectedAmount: 5000, + totalValue: 218590, + pendingPercentage: 97.71, + collectedPercentage: 2.29, + + bucket0To30Invoices: 10, + bucket30To60Invoices: 4, + bucket60To90Invoices: 2, + bucket90PlusInvoices: 1, + + bucket0To30Amount: 2130, + bucket30To60Amount: 2003, + bucket60To90Amount: 4500, + bucket90PlusAmount: 8800, + + topClientBalance: 55300, + topClient: { + id: "4e3a6d31-c640-40f7-8d67-6c109fcdb9ea", + name: "Marco Secure Solutions Ltd.", + email: "admin@marcoaiot.com", + contactPerson: "Admin", + address: + "2nd Floor, Fullora Building, Tejas CHS, behind Kothrud Stand, Tejas Society, Dahanukar Colony, Kothrud, Pune, Maharashtra 411038", + gstNumber: null, + contactNumber: "123456789", + sprid: 5400, + }, + }; + + const borderColor = "#ddd"; + const labelColor = "#6c757d"; + + // COLORS + const config = { + colors: { + b0: "#7367F0", + b30: "#00cfe8", + b60: "#28c76f", + b90: "#ea5455", + }, + }; + + // NEW LABELS (BUCKETS) + const chartLabels = ["0–30 Days", "30–60 Days", "60–90 Days", "90+ Days"]; + + // NEW VALUES (BUCKET AMOUNT) + const chartValues = [ + data.bucket0To30Amount, + data.bucket30To60Amount, + data.bucket60To90Amount, + data.bucket90PlusAmount, + ]; + + const options = { + chart: { + height: 300, + type: "bar", + toolbar: { show: false }, + }, + + plotOptions: { + bar: { + horizontal: true, + barHeight: "40%", + distributed: true, + startingShape: "rounded", + borderRadius: 7, + }, + }, + + grid: { + strokeDashArray: 10, + borderColor, + xaxis: { lines: { show: true } }, + yaxis: { lines: { show: false } }, + padding: { top: -35, bottom: -12 }, + }, + + colors: [ + config.colors.b0, + config.colors.b30, + config.colors.b60, + config.colors.b90, + ], + + labels: chartLabels, + + fill: { opacity: 1 }, + + dataLabels: { + enabled: true, + style: { + colors: ["#fff"], + fontWeight: 400, + fontSize: "13px", + fontFamily: "Public Sans", + }, + formatter: (_, opts) => chartLabels[opts.dataPointIndex], + }, + + xaxis: { + categories: chartValues.map((x) => formatFigure(x, { type: "currency" })), + axisBorder: { show: false }, + axisTicks: { show: false }, + labels: { + style: { + colors: labelColor, + fontFamily: "Public Sans", + fontSize: "13px", + }, + formatter: (val) => `₹${Number(val).toLocaleString()}`, + }, + }, + + yaxis: { + labels: { + style: { + colors: labelColor, + fontFamily: "Public Sans", + fontSize: "13px", + }, + }, + }, + + tooltip: { + enabled: true, + custom: ({ series, seriesIndex, dataPointIndex }) => { + return ` +
+ ₹${series[seriesIndex][ + dataPointIndex + ].toLocaleString()} +
+ `; + }, + }, + + legend: { show: false }, + }; + + const series = [ + { + data: chartValues, + }, + ]; + + return ( +
+
+
+
Collection Overview
+
+
+

Due Amount

+ + {formatFigure(data.totalDueAmount, { type: "currency" })} + +

Collected Amount

+ + {formatFigure(data.totalCollectedAmount, { type: "currency" })} + +
+ + +
+ +
+
+
+ {/*

Top Client

+ {data.topClient.name} */} +
+ + {/* 0–30 Days */} +
+
+ {formatFigure(data.bucket0To30Amount, { type: "currency" })} +
+

0–30 Days

+
+ + {/* 30–60 Days */} +
+
+ {formatFigure(data.bucket30To60Amount, { type: "currency" })} +
+

30–60 Days

+
+ + {/* 60–90 Days */} +
+
+ {formatFigure(data.bucket60To90Amount, { type: "currency" })} +
+

60–90 Days

+
+ + {/* 90+ Days */} +
+
+ {formatFigure(data.bucket90PlusAmount, { type: "currency" })} +
+

90+ Days

+
+
+
+ ); +}; diff --git a/src/components/Dashboard/Dashboard.jsx b/src/components/Dashboard/Dashboard.jsx index e918629d..a400d7a8 100644 --- a/src/components/Dashboard/Dashboard.jsx +++ b/src/components/Dashboard/Dashboard.jsx @@ -1,10 +1,11 @@ import React from "react"; import { useSelector } from "react-redux"; import { - useDashboardProjectsCardData, - useDashboardTeamsCardData, - useDashboardTasksCardData, - useAttendanceOverviewData + useDashboardProjectsCardData, + useDashboardTeamsCardData, + useDashboardTasksCardData, + useAttendanceOverviewData, + useGetCollectionOverview, } from "../../hooks/useDashboard_Data"; import Projects from "./Projects"; @@ -19,77 +20,171 @@ import ExpenseByProject from "./ExpenseByProject"; import ProjectStatistics from "../Project/ProjectStatistics"; import ServiceJobs from "./ServiceJobs"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; -import { REGULARIZE_ATTENDANCE, SELF_ATTENDANCE, TEAM_ATTENDANCE } from "../../utils/constants"; +import { + REGULARIZE_ATTENDANCE, + SELF_ATTENDANCE, + TEAM_ATTENDANCE, +} from "../../utils/constants"; +import CollectionOverview, { TopicBarChart } from "./CollectionOverview"; const Dashboard = () => { + // Get the selected project ID from Redux store + const projectId = useSelector((store) => store.localVariables.projectId); + const isAllProjectsSelected = projectId === null; + const canRegularize = useHasUserPermission(REGULARIZE_ATTENDANCE); + const canTeamAttendance = useHasUserPermission(TEAM_ATTENDANCE); + const canSelfAttendance = useHasUserPermission(SELF_ATTENDANCE); - // Get the selected project ID from Redux store - const projectId = useSelector((store) => store.localVariables.projectId); - const isAllProjectsSelected = projectId === null; - const canRegularize = useHasUserPermission(REGULARIZE_ATTENDANCE); - const canTeamAttendance = useHasUserPermission(TEAM_ATTENDANCE); - const canSelfAttendance = useHasUserPermission(SELF_ATTENDANCE); + const { data } = useGetCollectionOverview(); + console.log("data-->", data); + return ( +
+
+ {isAllProjectsSelected && ( +
+ +
+ )} - - return ( -
-
- {isAllProjectsSelected && ( -
- -
- )} - -
- -
- -
- -
-
-
- -
-
- -
-
- -
-
-
- -
- - {isAllProjectsSelected && ( -
- -
- )} -
- -
- {!isAllProjectsSelected && (canRegularize || canTeamAttendance || canSelfAttendance) && ( -
- -
- )} - - {!isAllProjectsSelected && ( -
-
- -
-
- )} - - {/*
- -
*/} -
+
+
- ); + +
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+ + {isAllProjectsSelected && ( +
+ +
+ )} +
+ +
+ {!isAllProjectsSelected && + (canRegularize || canTeamAttendance || canSelfAttendance) && ( +
+ +
+ )} + + {!isAllProjectsSelected && ( +
+
+ +
+
+ )} +
+ {data &&
} + +
+ + +
+ +
+ ); }; -export default Dashboard; \ No newline at end of file +export default Dashboard; + +//
+//
+//
+//
Topic you are interested in
+// +//
+//
+//
+//
+//
+//
+//
+//
+// +//
+//

UI Design

+//
35%
+//
+//
+//
+// +//
+//

Music

+//
14%
+//
+//
+//
+// +//
+//

React

+//
10%
+//
+//
+//
+ +//
+//
+// +//
+//

UX Design

+//
20%
+//
+//
+//
+// +//
+//

Animation

+//
12%
+//
+//
+//
+// +//
+//

SEO

+//
9%
+//
+//
+//
+//
+//
+//
+//
diff --git a/src/hooks/useDashboard_Data.jsx b/src/hooks/useDashboard_Data.jsx index 1a9d724a..8b3863df 100644 --- a/src/hooks/useDashboard_Data.jsx +++ b/src/hooks/useDashboard_Data.jsx @@ -192,3 +192,13 @@ export const useExpenseDataByProject = (projectId, categoryId, months) => { }, }); }; + +export const useGetCollectionOverview = (projectId) => { + return useQuery({ + queryKey: ["collection_overview", projectId], + queryFn: async () => { + const resp = await GlobalRepository.getCollectionOverview(projectId); + return resp.data; + }, + }); +}; diff --git a/src/repositories/GlobalRepository.jsx b/src/repositories/GlobalRepository.jsx index 2516020a..e95a109a 100644 --- a/src/repositories/GlobalRepository.jsx +++ b/src/repositories/GlobalRepository.jsx @@ -82,9 +82,9 @@ const GlobalRepository = { 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}`), + getCollectionOverview:(projectId) =>api.get(`/api/Dashboard/collection-overview`) }; From 88053d1286d5e2fcdcf521591e6883496dcc2fc0 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Fri, 5 Dec 2025 18:55:23 +0530 Subject: [PATCH 2/4] added optional chain for handle error --- src/components/Layout/Sidebar.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Layout/Sidebar.jsx b/src/components/Layout/Sidebar.jsx index c169859a..2533abd4 100644 --- a/src/components/Layout/Sidebar.jsx +++ b/src/components/Layout/Sidebar.jsx @@ -69,7 +69,7 @@ const Sidebar = () => { )} {data && - data?.data.map((section) => ( + data?.data?.map((section) => ( From 94fbeef5d1c85a4090524aef988d92495af8ec5b Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Fri, 5 Dec 2025 19:32:23 +0530 Subject: [PATCH 3/4] added skeleton --- .../Dashboard/CollectionOverview.jsx | 4 +- .../Dashboard/CollectionOverviewSkeleton.jsx | 40 +++++++++++++++++++ src/components/Dashboard/Dashboard.jsx | 19 +++++---- .../ServiceProjectJob/JobStatusLog.jsx | 5 ++- 4 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 src/components/Dashboard/CollectionOverviewSkeleton.jsx diff --git a/src/components/Dashboard/CollectionOverview.jsx b/src/components/Dashboard/CollectionOverview.jsx index 448072b4..d20cd0d3 100644 --- a/src/components/Dashboard/CollectionOverview.jsx +++ b/src/components/Dashboard/CollectionOverview.jsx @@ -3,7 +3,7 @@ import Chart from "react-apexcharts"; import { useGetCollectionOverview } from "../../hooks/useDashboard_Data"; import { formatFigure } from "../../utils/appUtils"; -const CollectionOverview = ({ data }) => { +const CollectionOverview = ({ data, isLoading }) => { const borderColor = "#ddd"; const labelColor = "#6c757d"; @@ -104,7 +104,7 @@ const CollectionOverview = ({ data }) => { }; export default CollectionOverview; -export const TopicBarChart = ({ data }) => { +export const TopicBarChart = ({ data,isLoading }) => { const data1 = { totalDueAmount: 213590, totalCollectedAmount: 5000, diff --git a/src/components/Dashboard/CollectionOverviewSkeleton.jsx b/src/components/Dashboard/CollectionOverviewSkeleton.jsx new file mode 100644 index 00000000..b4e85709 --- /dev/null +++ b/src/components/Dashboard/CollectionOverviewSkeleton.jsx @@ -0,0 +1,40 @@ + +const SkeletonLine = ({ height = 20, width = "100%", className = "" }) => ( +
+); +export const CollectionOverviewSkeleton = () => { + return ( +
+ + {/* LEFT SIDE */} +
+
+ + {/* Header */} +
+ +
+ + {/* Due & Collected summary */} +
+ + + + +
+ + {/* Chart Skeleton */} + + +
+ + + +
+
+ + ); +}; diff --git a/src/components/Dashboard/Dashboard.jsx b/src/components/Dashboard/Dashboard.jsx index a400d7a8..dbedb169 100644 --- a/src/components/Dashboard/Dashboard.jsx +++ b/src/components/Dashboard/Dashboard.jsx @@ -26,6 +26,7 @@ import { TEAM_ATTENDANCE, } from "../../utils/constants"; import CollectionOverview, { TopicBarChart } from "./CollectionOverview"; +import { CollectionOverviewSkeleton } from "./CollectionOverviewSkeleton"; const Dashboard = () => { // Get the selected project ID from Redux store @@ -35,7 +36,7 @@ const Dashboard = () => { const canTeamAttendance = useHasUserPermission(TEAM_ATTENDANCE); const canSelfAttendance = useHasUserPermission(SELF_ATTENDANCE); - const { data } = useGetCollectionOverview(); + const { data,isLoading,isError } = useGetCollectionOverview(); console.log("data-->", data); return (
@@ -98,14 +99,18 @@ const Dashboard = () => {
)} -
- {data &&
} - +
+ {isLoading ? ( + + ) : ( + data && ( +
+ +
+ ) + )}
- -
- ); }; diff --git a/src/components/ServiceProject/ServiceProjectJob/JobStatusLog.jsx b/src/components/ServiceProject/ServiceProjectJob/JobStatusLog.jsx index 719f13de..b2fce0d7 100644 --- a/src/components/ServiceProject/ServiceProjectJob/JobStatusLog.jsx +++ b/src/components/ServiceProject/ServiceProjectJob/JobStatusLog.jsx @@ -1,6 +1,7 @@ import React, { useMemo } from "react"; -import Timeline from "../../common/Timeline"; + import { getColorNameFromHex } from "../../../utils/appUtils"; +import Timeline from "../../common/TimeLine"; const JobStatusLog = ({ data }) => { // Prepare timeline items @@ -31,7 +32,7 @@ const JobStatusLog = ({ data }) => { return (
- +
); From cdf9bbbe48ece0c3274657d6bc7c829011936318 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Sat, 6 Dec 2025 09:48:15 +0530 Subject: [PATCH 4/4] added padding to better space --- src/components/Dashboard/CollectionOverview.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Dashboard/CollectionOverview.jsx b/src/components/Dashboard/CollectionOverview.jsx index d20cd0d3..34f0504b 100644 --- a/src/components/Dashboard/CollectionOverview.jsx +++ b/src/components/Dashboard/CollectionOverview.jsx @@ -254,7 +254,7 @@ export const TopicBarChart = ({ data,isLoading }) => { ]; return ( -
+
Collection Overview