UI updation in Expense Breakdown #471

Merged
pramod.mahajan merged 1 commits from Kartik_Bug#1463 into OnFieldWork_V1 2025-10-10 11:24:05 +00:00

View File

@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from "react"; import React, { useEffect, useMemo } from "react";
import Chart from "react-apexcharts"; import Chart from "react-apexcharts";
import { useExpenseAnalysis } from "../../hooks/useDashboard_Data"; import { useExpenseAnalysis } from "../../hooks/useDashboard_Data";
import { useSelectedProject } from "../../slices/apiDataManager"; import { useSelectedProject } from "../../slices/apiDataManager";
@ -10,36 +10,27 @@ const ExpenseAnalysis = () => {
const projectId = useSelectedProject(); const projectId = useSelectedProject();
const methods = useForm({ const methods = useForm({
defaultValues: { defaultValues: { startDate: "", endDate: "" },
startDate: "",
endDate: "",
},
}); });
const { watch } = methods; const { watch } = methods;
const [startDate, endDate] = watch(["startDate", "endDate"]); const [startDate, endDate] = watch(["startDate", "endDate"]);
const { data, isLoading, isError, error, isFetching } = useExpenseAnalysis( const { data, isLoading, isError, error, isFetching } = useExpenseAnalysis(
projectId, projectId,
startDate ? localToUtc(startDate) : null, startDate ? localToUtc(startDate) : null,
endDate ? localToUtc(endDate) : null endDate ? localToUtc(endDate) : null
); );
if (isError) return <div>{error.message}</div>; if (isError) return <div>{error.message}</div>;
const report = data?.report ?? []; const report = data?.report ?? [];
const { labels, series, total } = useMemo(() => {
const { labels, series, total } = useMemo(() => { const labels = report.map((item) => item.projectName);
const labels = report.map((item) => item.projectName); const series = report.map((item) => item.totalApprovedAmount || 0);
const series = report.map((item) => item.totalApprovedAmount || 0); const total = formatCurrency(data?.totalAmount || 0);
const total = formatCurrency(data?.totalAmount || 0); return { labels, series, total };
}, [report, data?.totalAmount]);
return { labels, series, total };
}, [report, data?.totalAmount]);
const donutOptions = { const donutOptions = {
chart: { type: "donut" }, chart: { type: "donut" },
@ -63,26 +54,34 @@ const { labels, series, total } = useMemo(() => {
}, },
}, },
}, },
responsive: [
{
breakpoint: 576, // mobile breakpoint
options: {
chart: { width: "100%" },
},
},
],
}; };
return ( return (
<> <>
<div className="card-header d-flex justify-content-between align-items-center "> {/* Header */}
<div> <div className="card-header d-flex flex-column flex-sm-row justify-content-between align-items-start align-items-sm-center gap-2">
<h5 className="mb-1 card-title text-start">Expense Breakdown</h5> <div className="text-start w-100">
<p className="card-subtitle">Category Wise Expense Breakdown</p> <h5 className="mb-1 card-title">Expense Breakdown</h5>
<p className="card-subtitle mb-0">Category Wise Expense Breakdown</p>
</div> </div>
<div className="text-end"> <div className="text-start text-sm-end w-75">
<FormProvider {...methods}> <FormProvider {...methods}>
<DateRangePicker1 /> <DateRangePicker1 />
</FormProvider> </FormProvider>
</div> </div>
</div> </div>
{/* Card body */}
<div className="card-body position-relative"> <div className="card-body position-relative">
{/* Initial loading: show full loader */}
{isLoading && ( {isLoading && (
<div <div
className="d-flex justify-content-center align-items-center" className="d-flex justify-content-center align-items-center"
@ -92,14 +91,12 @@ const { labels, series, total } = useMemo(() => {
</div> </div>
)} )}
{/* Data display */}
{!isLoading && report.length === 0 && ( {!isLoading && report.length === 0 && (
<div className="text-center py-5 text-muted ">No data found</div> <div className="text-center py-5 text-muted">No data found</div>
)} )}
{!isLoading && report.length > 0 && ( {!isLoading && report.length > 0 && (
<> <>
{/* Overlay spinner for refetch */}
{isFetching && ( {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"> <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> <span>Loading...</span>
@ -109,17 +106,18 @@ const { labels, series, total } = useMemo(() => {
<div className="d-flex justify-content-center mb-3"> <div className="d-flex justify-content-center mb-3">
<Chart <Chart
options={donutOptions} options={donutOptions}
series={report.map((item) => item.totalApprovedAmount || 0)} series={series}
type="donut" type="donut"
width="320" width="100%"
height={320}
/> />
</div> </div>
<div className="mb-2 w-100"> <div className="mb-2 w-100">
<div className="row"> <div className="row g-2">
{report.map((item, idx) => ( {report.map((item, idx) => (
<div <div
className="col-6 d-flex justify-content-start align-items-start mb-2" className="col-12 col-sm-6 d-flex align-items-start"
key={idx} key={idx}
> >
<div className="avatar me-2"> <div className="avatar me-2">
@ -127,16 +125,14 @@ const { labels, series, total } = useMemo(() => {
className="avatar-initial rounded-2" className="avatar-initial rounded-2"
style={{ style={{
backgroundColor: backgroundColor:
donutOptions.colors[ donutOptions.colors[idx % donutOptions.colors.length],
idx % donutOptions.colors.length
],
}} }}
> >
<i className="bx bx-receipt fs-4"></i> <i className="bx bx-receipt fs-4"></i>
</span> </span>
</div> </div>
<div className="d-flex flex-column gap-1 text-start"> <div className="d-flex flex-column gap-1 text-start">
<small className="fw-semibold ">{item.projectName}</small> <small className="fw-semibold">{item.projectName}</small>
<span className="fw-semibold text-muted ms-1"> <span className="fw-semibold text-muted ms-1">
{formatCurrency(item.totalApprovedAmount)} {formatCurrency(item.totalApprovedAmount)}
</span> </span>