+ {/* Optional subtle loading overlay */}
+ {isLoading && (
+
+ Loading...
+
+ )}
+
{/* Header */}
@@ -88,27 +85,15 @@ const AttendanceOverview = () => {
Role-wise present count
-
@@ -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 171b8cae..362b53da 100644
--- a/src/components/Dashboard/Dashboard.jsx
+++ b/src/components/Dashboard/Dashboard.jsx
@@ -14,25 +14,29 @@ import { useDispatch, useSelector } from "react-redux";
// import ProjectProgressChart from "./ProjectProgressChart";
// import ProjectOverview from "../Project/ProjectOverview";
import AttendanceOverview from "./AttendanceChart";
-import { useSelectedProject } from "../../slices/apiDataManager";
-import { useProjectName } from "../../hooks/useProjects";
+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;
return (
- {/* {shouldShowAttendance && ( */}
+
+
+
+
+ {!isAllProjectsSelected && (
-
+
{/* Removed unnecessary projectId prop */}
+ )}
);
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/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" }}
>
+
+
+ handleProjectChange(null)}
+ >All Project
+
+
{[...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 8800d48a..74731695 100644
--- a/src/hooks/useDashboard_Data.jsx
+++ b/src/hooks/useDashboard_Data.jsx
@@ -200,57 +200,74 @@ 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 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;
+ },
+ enabled:shouldFetch
+ });
+};
+
+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;
}
})
}
\ 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;
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
+}