Adding Spinner in Dashbard.

This commit is contained in:
Kartik Sharma 2025-10-31 10:57:56 +05:30
parent 9fbe3cf6a5
commit 86f931929a
4 changed files with 128 additions and 100 deletions

View File

@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import ReactApexChart from "react-apexcharts"; import ReactApexChart from "react-apexcharts";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { SpinnerLoader } from "../common/Loader";
const HorizontalBarChart = ({ const HorizontalBarChart = ({
seriesData = [], seriesData = [],
@ -23,7 +24,12 @@ const HorizontalBarChart = ({
if (loading) { if (loading) {
return ( return (
<div className="w-full h-[380px] flex items-center justify-center bg-gray-100 rounded-xl"> <div className="w-full h-[380px] flex items-center justify-center bg-gray-100 rounded-xl">
<span className="text-gray-500">Loading chart...</span> <div
className="d-flex justify-content-center align-items-center"
style={{ minHeight: "50vh" }}
>
<SpinnerLoader />
</div>
{/* Replace this with a skeleton or spinner if you prefer */} {/* Replace this with a skeleton or spinner if you prefer */}
</div> </div>
); );

View File

@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import ReactApexChart from "react-apexcharts"; import ReactApexChart from "react-apexcharts";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { SpinnerLoader } from "../common/Loader";
const LineChart = ({ const LineChart = ({
seriesData = [], seriesData = [],
@ -9,24 +10,28 @@ const LineChart = ({
loading = false, loading = false,
lineChartCategoriesDates = [], lineChartCategoriesDates = [],
}) => { }) => {
const hasValidData = const hasValidData =
Array.isArray(seriesData) && Array.isArray(seriesData) &&
seriesData.length > 0 && seriesData.length > 0 &&
Array.isArray(categories) && Array.isArray(categories) &&
categories.length > 0; categories.length > 0;
if (loading) { if (loading) {
return ( return (
<div className="flex justify-center items-center h-[350px] text-gray-500"> <div className="flex justify-center items-center h-[350px] text-gray-500">
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500 mr-2" /> <div
Loading chart... className="d-flex justify-content-center align-items-center"
</div> style={{ minHeight: "50vh" }}
); >
} <SpinnerLoader />
</div>
</div>
);
}
if (!hasValidData) { if (!hasValidData) {
return <div className="text-center text-gray-500">No data to display</div>; return <div className="text-center text-gray-500">No data to display</div>;
} }
const chartOptions = { const chartOptions = {
chart: { chart: {
@ -129,16 +134,16 @@ const LineChart = ({
}; };
LineChart.propTypes = { LineChart.propTypes = {
seriesData: PropTypes.arrayOf( seriesData: PropTypes.arrayOf(
PropTypes.shape({ PropTypes.shape({
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
data: PropTypes.arrayOf(PropTypes.number).isRequired data: PropTypes.arrayOf(PropTypes.number).isRequired
}) })
), ),
categories: PropTypes.arrayOf(PropTypes.string), categories: PropTypes.arrayOf(PropTypes.string),
colors: PropTypes.arrayOf(PropTypes.string), colors: PropTypes.arrayOf(PropTypes.string),
title: PropTypes.string, title: PropTypes.string,
loading: PropTypes.bool loading: PropTypes.bool
}; };
export default LineChart; export default LineChart;

View File

@ -6,6 +6,7 @@ import { DateRangePicker1 } from "../common/DateRangePicker";
import { FormProvider, useForm } from "react-hook-form"; import { FormProvider, useForm } from "react-hook-form";
import { formatCurrency, localToUtc } from "../../utils/appUtils"; import { formatCurrency, localToUtc } from "../../utils/appUtils";
import { useProjectName } from "../../hooks/useProjects"; import { useProjectName } from "../../hooks/useProjects";
import { SpinnerLoader } from "../common/Loader";
const ExpenseAnalysis = () => { const ExpenseAnalysis = () => {
const projectId = useSelectedProject(); const projectId = useSelectedProject();
@ -78,86 +79,92 @@ const ExpenseAnalysis = () => {
return ( 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 w-100">
<h5 className="mb-1 card-title">Expense Breakdown</h5>
{/* <p className="card-subtitle mb-0">Category Wise Expense Breakdown</p> */}
<p className="card-subtitle m-0">{projectName}</p>
</div>
<div className="text-start text-sm-end w-75"> <div className="card-header d-flex flex-column flex-sm-row justify-content-between align-items-start align-items-sm-center gap-2">
<FormProvider {...methods}> <div className="text-start w-100">
<DateRangePicker1 /> <h5 className="mb-1 card-title">Expense Breakdown</h5>
</FormProvider> {/* <p className="card-subtitle mb-0">Category Wise Expense Breakdown</p> */}
</div> <p className="card-subtitle m-0">{projectName}</p>
</div> </div>
{/* Card body */} <div className="text-start text-sm-end w-75">
<div className="card-body position-relative"> <FormProvider {...methods}>
{isLoading && ( <DateRangePicker1 />
<div </FormProvider>
className="d-flex justify-content-center align-items-center" </div>
style={{ height: "200px" }} </div>
>
<span>Loading...</span> {/* Card body */}
<div className="card-body position-relative">
{isLoading && (
<div
className="d-flex justify-content-center align-items-center"
style={{ height: "200px" }}
>
<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="d-flex justify-content-center mb-3">
<Chart
options={donutOptions}
series={series}
type="donut"
width="100%"
height={320}
/>
</div> </div>
)}
{!isLoading && report.length === 0 && ( <div className="mb-2 w-100">
<div className="text-center py-5 text-muted">No data found</div> <div className="row g-2">
)} {report.map((item, idx) => (
<div
{!isLoading && report.length > 0 && ( className="col-12 col-sm-6 d-flex align-items-start"
<> key={idx}
{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="avatar me-2">
<span>Loading...</span> <span
</div> className="avatar-initial rounded-2"
)} style={{
backgroundColor:
<div className="d-flex justify-content-center mb-3"> donutOptions.colors[idx % donutOptions.colors.length],
<Chart }}
options={donutOptions} >
series={series} <i className="bx bx-receipt fs-4"></i>
type="donut" </span>
width="100%"
height={320}
/>
</div>
<div className="mb-2 w-100">
<div className="row g-2">
{report.map((item, idx) => (
<div
className="col-12 col-sm-6 d-flex align-items-start"
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 className="d-flex flex-column gap-1 text-start">
</div> <small className="fw-semibold">{item.projectName}</small>
<span className="fw-semibold text-muted ms-1">
{formatCurrency(item.totalApprovedAmount)}
</span>
</div>
</div>
))}
</div> </div>
</> </div>
)} </>
</div> )}
</div>
{/* Header */} {/* Header */}
</> </>

View File

@ -19,3 +19,13 @@ const Loader = () => {
export default Loader; export default Loader;
export const SpinnerLoader = () => {
return (
<div className="text-center">
<div className="spinner-border text-primary mb-3" role="status">
<span className="visually-hidden">Loading...</span>
</div>
<p className="text-secondary mb-0">Loading data...</p>
</div>
)
}