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 +}