186 lines
5.7 KiB
JavaScript
186 lines
5.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";
|
|
import { useProjectName } from "../../hooks/useProjects";
|
|
import { SpinnerLoader } from "../common/Loader";
|
|
import flatColors from "../Charts/flatColor";
|
|
|
|
const ExpenseAnalysis = () => {
|
|
const projectId = useSelectedProject();
|
|
const [projectName, setProjectName] = useState("All Project");
|
|
const { projectNames } = useProjectName();
|
|
|
|
const methods = useForm({
|
|
defaultValues: { startDate: "", endDate: "" },
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (projectId && projectNames?.length) {
|
|
const project = projectNames.find((p) => p.id === projectId);
|
|
setProjectName(project?.name || "All Project");
|
|
} else {
|
|
setProjectName("All Project");
|
|
}
|
|
}, [projectNames, projectId]);
|
|
|
|
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 },
|
|
tooltip: {
|
|
y: {
|
|
formatter: (value) => formatCurrency(value),
|
|
},
|
|
},
|
|
dataLabels: { enabled: true, formatter: (val) => `${val.toFixed(0)}%` },
|
|
colors: flatColors,
|
|
plotOptions: {
|
|
pie: {
|
|
donut: {
|
|
size: "70%",
|
|
labels: {
|
|
show: true,
|
|
total: {
|
|
show: true,
|
|
label: "Total",
|
|
fontSize: "16px",
|
|
formatter: () => `${total}`,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responsive: [
|
|
{
|
|
breakpoint: 576, // mobile breakpoint
|
|
options: {
|
|
chart: { width: "100%" },
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<div className="card-header d-flex flex-column flex-sm-row justify-content-between align-items-start align-items-sm-center gap-2">
|
|
<div className="text-start">
|
|
<h5 className="mb-1 card-title">Expense Breakdown</h5>
|
|
<p className="card-subtitle m-0">{projectName}</p>
|
|
</div>
|
|
|
|
<div className="text-end text-sm-end">
|
|
<FormProvider {...methods}>
|
|
<DateRangePicker1 />
|
|
</FormProvider>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="card-body position-relative">
|
|
{isLoading && (
|
|
<div
|
|
className="d-flex justify-content-center align-items-center"
|
|
style={{ minHeight: "50vh" }}
|
|
>
|
|
<SpinnerLoader />
|
|
</div>
|
|
)}
|
|
|
|
{!isLoading && report.length === 0 && (
|
|
<div
|
|
className="d-flex justify-content-center align-items-center text-muted"
|
|
style={{ height: "300px" }}
|
|
>
|
|
No data found
|
|
</div>
|
|
)}
|
|
|
|
{!isLoading && report.length > 0 && (
|
|
<>
|
|
{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="row">
|
|
{/* Chart Column */}
|
|
<div className="col-12 col-lg-6 d-flex justify-content-center mt-5 mb-3 mb-lg-0">
|
|
<Chart
|
|
options={donutOptions}
|
|
series={series}
|
|
type="donut"
|
|
width="75%"
|
|
height={320}
|
|
/>
|
|
</div>
|
|
|
|
{/* Data/Legend Column */}
|
|
<div className="col-12 mt-6 col-lg-6">
|
|
<div className="row g-4">
|
|
{report.map((item, idx) => (
|
|
<div
|
|
className="col-6"
|
|
key={idx}
|
|
style={{
|
|
borderLeft: `3px solid ${flatColors[idx % flatColors.length]}`,
|
|
}}
|
|
>
|
|
<div className="d-flex flex-column text-start">
|
|
<small
|
|
className="fw-semibold text-wrap text-dark"
|
|
style={{
|
|
fontSize: "0.8rem",
|
|
whiteSpace: "normal",
|
|
wordBreak: "break-word",
|
|
lineHeight: "1.2",
|
|
}}
|
|
>
|
|
{item.projectName}
|
|
</small>
|
|
<span
|
|
className="fw-semibold text-muted"
|
|
style={{
|
|
fontSize: "0.75rem",
|
|
}}
|
|
>
|
|
{formatCurrency(item.totalApprovedAmount)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default ExpenseAnalysis;
|