added expense analysis card inisde dashboard
This commit is contained in:
parent
0c1889e1c1
commit
25f4f1e7a7
@ -17,28 +17,30 @@ const formatDate = (dateStr) => {
|
|||||||
const AttendanceOverview = () => {
|
const AttendanceOverview = () => {
|
||||||
const [dayRange, setDayRange] = useState(7);
|
const [dayRange, setDayRange] = useState(7);
|
||||||
const [view, setView] = useState("chart");
|
const [view, setView] = useState("chart");
|
||||||
const selectedProject = useSelectedProject()
|
const selectedProject = useSelectedProject();
|
||||||
|
|
||||||
const { data: attendanceOverviewData, isLoading, isError, error } = useAttendanceOverviewData(
|
const { data: attendanceOverviewData, isLoading, isError, error } = useAttendanceOverviewData(
|
||||||
selectedProject,
|
selectedProject,
|
||||||
dayRange
|
dayRange
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Use empty array while loading
|
||||||
|
const attendanceData = attendanceOverviewData || [];
|
||||||
|
|
||||||
const { tableData, roles, dates } = useMemo(() => {
|
const { tableData, roles, dates } = useMemo(() => {
|
||||||
if (!attendanceOverviewData || attendanceOverviewData.length === 0) {
|
if (!attendanceData || attendanceData.length === 0) {
|
||||||
return { tableData: [], roles: [], dates: [] };
|
return { tableData: [], roles: [], dates: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
const map = new Map();
|
const map = new Map();
|
||||||
|
|
||||||
attendanceOverviewData.forEach((entry) => {
|
attendanceData.forEach((entry) => {
|
||||||
const date = formatDate(entry.date);
|
const date = formatDate(entry.date);
|
||||||
if (!map.has(date)) map.set(date, {});
|
if (!map.has(date)) map.set(date, {});
|
||||||
map.get(date)[entry.role.trim()] = entry.present;
|
map.get(date)[entry.role.trim()] = entry.present;
|
||||||
});
|
});
|
||||||
|
|
||||||
const uniqueRoles = [
|
const uniqueRoles = [...new Set(attendanceData.map((e) => e.role.trim()))];
|
||||||
...new Set(attendanceOverviewData.map((e) => e.role.trim())),
|
|
||||||
];
|
|
||||||
const sortedDates = [...map.keys()];
|
const sortedDates = [...map.keys()];
|
||||||
|
|
||||||
const tableData = sortedDates.map((date) => {
|
const tableData = sortedDates.map((date) => {
|
||||||
@ -50,7 +52,7 @@ const AttendanceOverview = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return { tableData, roles: uniqueRoles, dates: sortedDates };
|
return { tableData, roles: uniqueRoles, dates: sortedDates };
|
||||||
}, [attendanceOverviewData,isLoading,selectedProject,dayRange]);
|
}, [attendanceData]);
|
||||||
|
|
||||||
const chartSeries = roles.map((role) => ({
|
const chartSeries = roles.map((role) => ({
|
||||||
name: role,
|
name: role,
|
||||||
@ -58,29 +60,24 @@ const AttendanceOverview = () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const chartOptions = {
|
const chartOptions = {
|
||||||
chart: {
|
chart: { type: "bar", stacked: true, height: 400, toolbar: { show: false } },
|
||||||
type: "bar",
|
|
||||||
stacked: true,
|
|
||||||
height: 400,
|
|
||||||
toolbar: { show: false },
|
|
||||||
},
|
|
||||||
plotOptions: { bar: { borderRadius: 2, columnWidth: "60%" } },
|
plotOptions: { bar: { borderRadius: 2, columnWidth: "60%" } },
|
||||||
xaxis: { categories: tableData.map((row) => row.date) },
|
xaxis: { categories: tableData.map((row) => row.date) },
|
||||||
yaxis: {
|
yaxis: { show: true, axisBorder: { show: true, color: "#78909C" }, axisTicks: { show: true, color: "#78909C", width: 6 } },
|
||||||
show: true,
|
|
||||||
axisBorder: { show: true, color: "#78909C" },
|
|
||||||
axisTicks: { show: true, color: "#78909C", width: 6 },
|
|
||||||
},
|
|
||||||
legend: { position: "bottom" },
|
legend: { position: "bottom" },
|
||||||
fill: { opacity: 1 },
|
fill: { opacity: 1 },
|
||||||
colors: roles.map((_, i) => flatColors[i % flatColors.length]),
|
colors: roles.map((_, i) => flatColors[i % flatColors.length]),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading) return <div>Loading...</div>;
|
|
||||||
if (isError) return <p className="text-danger">{error.message}</p>;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white p-4 rounded shadow d-flex flex-column">
|
<div className="bg-white p-4 rounded shadow d-flex flex-column position-relative">
|
||||||
|
{/* Optional subtle loading overlay */}
|
||||||
|
{isLoading && (
|
||||||
|
<div className="position-absolute w-100 h-100 d-flex align-items-center justify-content-center bg-white bg-opacity-50 z-index-1">
|
||||||
|
<span>Loading...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="d-flex justify-content-between align-items-center mb-3">
|
<div className="d-flex justify-content-between align-items-center mb-3">
|
||||||
<div className="card-title mb-0 text-start">
|
<div className="card-title mb-0 text-start">
|
||||||
@ -88,27 +85,15 @@ const AttendanceOverview = () => {
|
|||||||
<p className="card-subtitle">Role-wise present count</p>
|
<p className="card-subtitle">Role-wise present count</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="d-flex gap-2">
|
<div className="d-flex gap-2">
|
||||||
<select
|
<select className="form-select form-select-sm" value={dayRange} onChange={(e) => setDayRange(Number(e.target.value))}>
|
||||||
className="form-select form-select-sm"
|
|
||||||
value={dayRange}
|
|
||||||
onChange={(e) => setDayRange(Number(e.target.value))}
|
|
||||||
>
|
|
||||||
<option value={7}>Last 7 Days</option>
|
<option value={7}>Last 7 Days</option>
|
||||||
<option value={15}>Last 15 Days</option>
|
<option value={15}>Last 15 Days</option>
|
||||||
<option value={30}>Last 30 Days</option>
|
<option value={30}>Last 30 Days</option>
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button className={`btn btn-sm p-1 ${view === "chart" ? "btn-primary" : "btn-outline-primary"}`} onClick={() => setView("chart")} title="Chart View">
|
||||||
className={`btn btn-sm p-1 ${view === "chart" ? "btn-primary" : "btn-outline-primary"}`}
|
|
||||||
onClick={() => setView("chart")}
|
|
||||||
title="Chart View"
|
|
||||||
>
|
|
||||||
<i className="bx bx-bar-chart-alt-2"></i>
|
<i className="bx bx-bar-chart-alt-2"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button className={`btn btn-sm p-1 ${view === "table" ? "btn-primary" : "btn-outline-primary"}`} onClick={() => setView("table")} title="Table View">
|
||||||
className={`btn btn-sm p-1 ${view === "table" ? "btn-primary" : "btn-outline-primary"}`}
|
|
||||||
onClick={() => setView("table")}
|
|
||||||
title="Table View"
|
|
||||||
>
|
|
||||||
<i className="bx bx-list-ul fs-5"></i>
|
<i className="bx bx-list-ul fs-5"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -127,9 +112,7 @@ const AttendanceOverview = () => {
|
|||||||
<tr>
|
<tr>
|
||||||
<th style={{ background: "#f8f9fa", textTransform: "none" }}>Role</th>
|
<th style={{ background: "#f8f9fa", textTransform: "none" }}>Role</th>
|
||||||
{dates.map((date, idx) => (
|
{dates.map((date, idx) => (
|
||||||
<th key={idx} style={{ background: "#f8f9fa", textTransform: "none" }}>
|
<th key={idx} style={{ background: "#f8f9fa", textTransform: "none" }}>{date}</th>
|
||||||
{date}
|
|
||||||
</th>
|
|
||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
@ -13,26 +13,33 @@ import { useDispatch, useSelector } from "react-redux";
|
|||||||
// import ProjectCompletionChart from "./ProjectCompletionChart";
|
// import ProjectCompletionChart from "./ProjectCompletionChart";
|
||||||
// import ProjectProgressChart from "./ProjectProgressChart";
|
// import ProjectProgressChart from "./ProjectProgressChart";
|
||||||
// import ProjectOverview from "../Project/ProjectOverview";
|
// import ProjectOverview from "../Project/ProjectOverview";
|
||||||
import AttendanceOverview from "./AttendanceChart";
|
import AttendanceOverview from "./AttendanceOverview";
|
||||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
import { useProjectName } from "../../hooks/useProjects";
|
import { useProjectName } from "../../hooks/useProjects";
|
||||||
|
import ExpenseAnalysis from "./ExpenseAnalysis";
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
|
|
||||||
// const { projectsCardData } = useDashboardProjectsCardData();
|
// const { projectsCardData } = useDashboardProjectsCardData();
|
||||||
// const { teamsCardData } = useDashboardTeamsCardData();
|
// const { teamsCardData } = useDashboardTeamsCardData();
|
||||||
// const { tasksCardData } = useDashboardTasksCardData();
|
// const { tasksCardData } = useDashboardTasksCardData();
|
||||||
|
|
||||||
// Get the selected project ID from Redux store
|
// Get the selected project ID from Redux store
|
||||||
|
const projectId = useSelector((store) => store.localVariables.projectId);
|
||||||
|
const isAllProjectsSelected = projectId === null;
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container-fluid mt-5">
|
<div className="container-fluid mt-5">
|
||||||
<div className="row gy-4">
|
<div className="row gy-4">
|
||||||
{/* {shouldShowAttendance && ( */}
|
<div className="col-xxl-6 col-lg-6">
|
||||||
|
<ExpenseAnalysis />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!isAllProjectsSelected && (
|
||||||
<div className="col-xxl-6 col-lg-6">
|
<div className="col-xxl-6 col-lg-6">
|
||||||
<AttendanceOverview />
|
<AttendanceOverview />
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
142
src/components/Dashboard/ExpenseAnalysis.jsx
Normal file
142
src/components/Dashboard/ExpenseAnalysis.jsx
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import Chart from "react-apexcharts";
|
||||||
|
import { useExpenseAnalysis } 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 ExpenseAnalysis = () => {
|
||||||
|
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 } = useExpenseAnalysis(
|
||||||
|
projectId,
|
||||||
|
localToUtc(startDate),
|
||||||
|
localToUtc(endDate)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isError) return <div>{error.message}</div>;
|
||||||
|
|
||||||
|
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 <div>No data found</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card shadow-sm" style={{ minHeight: "500px" }}>
|
||||||
|
<div className="card-header d-flex justify-content-between align-items-center mt-3">
|
||||||
|
<div>
|
||||||
|
<h5 className="mb-1 fw-bold">Expense Breakdown</h5>
|
||||||
|
<p className="card-subtitle me-3">Detailed project expenses</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-end">
|
||||||
|
<FormProvider {...methods}>
|
||||||
|
<DateRangePicker1 />
|
||||||
|
</FormProvider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="card-body position-relative">
|
||||||
|
{/* Initial loading: show full loader */}
|
||||||
|
{isLoading && (
|
||||||
|
<div className="d-flex justify-content-center align-items-center" style={{ height: "200px" }}>
|
||||||
|
<span>Loading...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Data display */}
|
||||||
|
{!isLoading && report.length === 0 && (
|
||||||
|
<div className="text-center text-muted py-5">No data found</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isLoading && report.length > 0 && (
|
||||||
|
<>
|
||||||
|
{/* Overlay spinner for refetch */}
|
||||||
|
{isFetching && (
|
||||||
|
<div className="position-absolute top-0 start-0 w-100 h-100 d-flex justify-content-center align-items-center bg-white bg-opacity-75">
|
||||||
|
<span>Loading...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="d-flex justify-content-center mb-8">
|
||||||
|
<Chart
|
||||||
|
options={donutOptions}
|
||||||
|
series={report.map((item) => item.totalApprovedAmount || 0)}
|
||||||
|
type="donut"
|
||||||
|
width="320"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-2 w-100">
|
||||||
|
<div className="row">
|
||||||
|
{report.map((item, idx) => (
|
||||||
|
<div className="col-6 d-flex justify-content-start align-items-start mb-2" key={idx}>
|
||||||
|
<div className="avatar me-2">
|
||||||
|
<span
|
||||||
|
className="avatar-initial rounded-2"
|
||||||
|
style={{
|
||||||
|
backgroundColor: donutOptions.colors[idx % donutOptions.colors.length],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="bx bx-receipt fs-4"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex flex-column gap-1 text-start">
|
||||||
|
<small className="fw-bold fs-6">{item.projectName}</small>
|
||||||
|
<span className="fw-bold text-muted ms-1">{item.totalApprovedAmount}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExpenseAnalysis;
|
@ -189,7 +189,9 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
const editPayload = { ...payload, id: data.id };
|
const editPayload = { ...payload, id: data.id };
|
||||||
ExpenseUpdate({ id: data.id, payload: editPayload });
|
ExpenseUpdate({ id: data.id, payload: editPayload });
|
||||||
} else {
|
} else {
|
||||||
CreateExpense(payload);
|
console.log(fromdata)
|
||||||
|
console.log(payload)
|
||||||
|
// CreateExpense(payload);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const ExpenseTypeId = watch("expensesTypeId");
|
const ExpenseTypeId = watch("expensesTypeId");
|
||||||
|
@ -67,7 +67,7 @@ const Header = () => {
|
|||||||
if (projectLoading) return "Loading...";
|
if (projectLoading) return "Loading...";
|
||||||
if (!projectNames?.length) return "No Projects Assigned";
|
if (!projectNames?.length) return "No Projects Assigned";
|
||||||
if (projectNames.length === 1) return projectNames[0].name;
|
if (projectNames.length === 1) return projectNames[0].name;
|
||||||
|
if (selectedProject === null) return "All Projects";
|
||||||
const selectedObj = projectNames.find((p) => p.id === selectedProject);
|
const selectedObj = projectNames.find((p) => p.id === selectedProject);
|
||||||
return selectedObj
|
return selectedObj
|
||||||
? selectedObj.name
|
? selectedObj.name
|
||||||
@ -199,6 +199,14 @@ const Header = () => {
|
|||||||
className="dropdown-menu"
|
className="dropdown-menu"
|
||||||
style={{ overflow: "auto", maxHeight: "300px" }}
|
style={{ overflow: "auto", maxHeight: "300px" }}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => handleProjectChange(null)}
|
||||||
|
>All Project</button>
|
||||||
|
</li>
|
||||||
|
|
||||||
{[...projectsForDropdown]
|
{[...projectsForDropdown]
|
||||||
.sort((a, b) => a?.name?.localeCompare(b.name))
|
.sort((a, b) => a?.name?.localeCompare(b.name))
|
||||||
.map((project) => (
|
.map((project) => (
|
||||||
|
@ -85,6 +85,7 @@ export const DateRangePicker1 = ({
|
|||||||
resetSignal,
|
resetSignal,
|
||||||
defaultRange = true,
|
defaultRange = true,
|
||||||
maxDate = null,
|
maxDate = null,
|
||||||
|
sm,md,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
@ -168,11 +169,12 @@ export const DateRangePicker1 = ({
|
|||||||
const formattedValue = start && end ? `${start} To ${end}` : "";
|
const formattedValue = start && end ? `${start} To ${end}` : "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`position-relative ${className}`}>
|
<div className={`col-${sm} col-sm-${md} px-1 position-relative`}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm ps-2 pe-5 me-4 cursor-pointer"
|
||||||
placeholder={placeholder}
|
placeholder="From to End"
|
||||||
|
id="flatpickr-range"
|
||||||
defaultValue={formattedValue}
|
defaultValue={formattedValue}
|
||||||
ref={(el) => {
|
ref={(el) => {
|
||||||
inputRef.current = el;
|
inputRef.current = el;
|
||||||
@ -181,12 +183,10 @@ export const DateRangePicker1 = ({
|
|||||||
readOnly={!allowText}
|
readOnly={!allowText}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
<span
|
<i
|
||||||
className="position-absolute top-50 end-0 pe-1 translate-middle-y cursor-pointer"
|
className="bx bx-calendar calendar-icon cursor-pointer position-absolute top-50 end-0 translate-middle-y me-2"
|
||||||
onClick={() => inputRef.current?._flatpickr?.open()}
|
onClick={() => inputRef.current?._flatpickr?.open()}
|
||||||
>
|
></i>
|
||||||
<i className="bx bx-calendar bx-sm fs-5 text-muted"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -253,4 +253,21 @@ export const useDashboardProjectsCardData = () => {
|
|||||||
return resp.data;
|
return resp.data;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useExpenseAnalysis = (projectId, startDate, endDate) => {
|
||||||
|
const hasBothDates = !!startDate && !!endDate;
|
||||||
|
const noDatesSelected = !startDate && !endDate;
|
||||||
|
|
||||||
|
const shouldFetch =
|
||||||
|
noDatesSelected ||
|
||||||
|
hasBothDates;
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["expenseAnalysis", projectId, startDate, endDate],
|
||||||
|
queryFn: async () => {
|
||||||
|
const resp = await GlobalRepository.getExpenseData(projectId, startDate, endDate);
|
||||||
|
return resp.data;
|
||||||
|
},
|
||||||
|
enabled:shouldFetch
|
||||||
|
});
|
||||||
|
};
|
@ -15,7 +15,7 @@ import { useProjectDetails, useProjectName } from "../../hooks/useProjects";
|
|||||||
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
import ProjectProgressChart from "../../components/Dashboard/ProjectProgressChart";
|
import ProjectProgressChart from "../../components/Dashboard/ProjectProgressChart";
|
||||||
import AttendanceOverview from "../../components/Dashboard/AttendanceChart";
|
import AttendanceOverview from "../../components/Dashboard/AttendanceOverview";
|
||||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||||
import ProjectDocuments from "../../components/Project/ProjectDocuments";
|
import ProjectDocuments from "../../components/Project/ProjectDocuments";
|
||||||
import ProjectSetting from "../../components/Project/ProjectSetting";
|
import ProjectSetting from "../../components/Project/ProjectSetting";
|
||||||
|
@ -41,7 +41,32 @@ const GlobalRepository = {
|
|||||||
return api.get(url);
|
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 );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -69,14 +69,24 @@ export const normalizeAllowedContentTypes = (allowedContentType) => {
|
|||||||
return allowedContentType.split(",");
|
return allowedContentType.split(",");
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
export function localToUtc(localDateString) {
|
export function localToUtc(dateString) {
|
||||||
if (!localDateString || typeof localDateString !== "string") return null;
|
if (!dateString || typeof dateString !== "string") return null;
|
||||||
|
|
||||||
|
const parts = dateString.trim().split("-");
|
||||||
const [year, month, day] = localDateString.trim().split("-");
|
if (parts.length !== 3) return null;
|
||||||
if (!year || !month || !day) return null;
|
|
||||||
|
let day, month, year;
|
||||||
|
|
||||||
|
if (parts[0].length === 4) {
|
||||||
|
// Format: yyyy-mm-dd
|
||||||
|
[year, month, day] = parts;
|
||||||
|
} else {
|
||||||
|
// Format: dd-mm-yyyy
|
||||||
|
[day, month, year] = parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!day || !month || !year) return null;
|
||||||
|
|
||||||
const date = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day), 0, 0, 0));
|
const date = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day), 0, 0, 0));
|
||||||
|
|
||||||
return isNaN(date.getTime()) ? null : date.toISOString();
|
return isNaN(date.getTime()) ? null : date.toISOString();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user