marco.pms.web/src/components/Dashboard/ExpenseAnalysis.jsx

156 lines
4.7 KiB
JavaScript

import React, { useEffect, useMemo, 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 { formatCurrency, localToUtc } from "../../utils/appUtils";
const ExpenseAnalysis = () => {
const projectId = useSelectedProject();
const methods = useForm({
defaultValues: {
startDate: "",
endDate: "",
},
});
const { watch } = methods;
const [startDate, endDate] = watch(["startDate", "endDate"]);
const { data, isLoading, isError, error, isFetching } = useExpenseAnalysis(
projectId,
startDate ? localToUtc(startDate) : null,
endDate ? localToUtc(endDate) : null
);
if (isError) return <div>{error.message}</div>;
const report = data?.report ?? [];
const { labels, series, total } = useMemo(() => {
const labels = report.map((item) => item.projectName);
const series = report.map((item) => item.totalApprovedAmount || 0);
const total = formatCurrency(data?.totalAmount || 0);
return { labels, series, total };
}, [report, data?.totalAmount]);
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}`,
},
},
},
},
},
};
return (
<>
<div className="card-header d-flex justify-content-between align-items-center ">
<div>
<h5 className="mb-1 card-title text-start">Expense Breakdown</h5>
<p className="card-subtitle">Category Wise Expense Breakdown</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 py-5 text-muted ">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-3">
<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-semibold ">{item.projectName}</small>
<span className="fw-semibold text-muted ms-1">
{formatCurrency(item.totalApprovedAmount)}
</span>
</div>
</div>
))}
</div>
</div>
</>
)}
</div>
</>
);
};
export default ExpenseAnalysis;