Implementing Spinner and Removing error in Dashboard.

This commit is contained in:
Kartik Sharma 2025-11-08 11:00:29 +05:30
parent ec38e8f9e0
commit d18eabf6f5
5 changed files with 116 additions and 72 deletions

View File

@ -1,11 +1,14 @@
import React, { useState, useMemo } from "react"; import React, { useState, useMemo } from "react";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import ReactApexChart from "react-apexcharts"; import ReactApexChart from "react-apexcharts";
import moment from "moment";
import { useAttendanceOverviewData } from "../../hooks/useDashboard_Data"; import { useAttendanceOverviewData } from "../../hooks/useDashboard_Data";
import flatColors from "../Charts/flatColor"; import flatColors from "../Charts/flatColor";
import ChartSkeleton from "../Charts/Skelton"; import ChartSkeleton from "../Charts/Skelton";
import { useSelectedProject } from "../../slices/apiDataManager"; import { useSelectedProject } from "../../slices/apiDataManager";
import { formatDate_DayMonth } from "../../utils/dateUtils"; import { SpinnerLoader } from "../common/Loader";
const formatDate_DayMonth = (dateStr) => moment(dateStr).format("DD MMM YY");
const AttendanceOverview = () => { const AttendanceOverview = () => {
const [dayRange, setDayRange] = useState(7); const [dayRange, setDayRange] = useState(7);
@ -22,6 +25,7 @@ const AttendanceOverview = () => {
// Use empty array while loading // Use empty array while loading
const attendanceData = attendanceOverviewData || []; const attendanceData = attendanceOverviewData || [];
// Prepare data for chart and table
const { tableData, roles, dates } = useMemo(() => { const { tableData, roles, dates } = useMemo(() => {
if (!attendanceData || attendanceData.length === 0) { if (!attendanceData || attendanceData.length === 0) {
return { tableData: [], roles: [], dates: [] }; return { tableData: [], roles: [], dates: [] };
@ -49,34 +53,60 @@ const AttendanceOverview = () => {
return { tableData, roles: uniqueRoles, dates: sortedDates }; return { tableData, roles: uniqueRoles, dates: sortedDates };
}, [attendanceData]); }, [attendanceData]);
// Chart data
const chartSeries = roles.map((role) => ({ const chartSeries = roles.map((role) => ({
name: role, name: role,
data: tableData.map((row) => row[role]), data: tableData.map((row) => row[role]),
})); }));
// Chart options
const chartOptions = { const chartOptions = {
chart: { chart: {
type: "bar", type: "bar",
stacked: true, stacked: true, // make false if you want side-by-side bars
height: 400, height: 400,
toolbar: { show: false }, toolbar: { show: false },
}, },
plotOptions: { bar: { borderRadius: 2, columnWidth: "60%" } }, plotOptions: {
xaxis: { categories: tableData.map((row) => row.date) }, bar: {
yaxis: { borderRadius: 4,
show: true, columnWidth: "55%",
axisBorder: { show: true, color: "#78909C" }, },
axisTicks: { show: true, color: "#78909C", width: 6 }, },
xaxis: {
categories: tableData.map((row) => row.date),
labels: {
rotate: -45,
style: { fontSize: "12px" },
},
},
yaxis: {
axisBorder: { show: true, color: "#78909C" },
axisTicks: { show: true, color: "#78909C" },
},
legend: {
position: "bottom",
horizontalAlign: "center",
fontSize: "12px",
},
grid: {
borderColor: "#e0e0e0",
strokeDashArray: 3,
}, },
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]),
tooltip: {
y: {
formatter: (val) => `${val} present`,
},
},
}; };
return ( return (
<div className="bg-white p-4 rounded shadow d-flex flex-column position-relative"> <div className="bg-white p-4 rounded shadow d-flex flex-column h-100">
{/* Header */}
<div className="row mb-3 align-items-center"> <div className="row mb-3 align-items-center">
<div className="col-md-6 text-start"> <div className="col-md-6 text-start mb-6">
<p className="mb-1 fs-6 fs-md-5 fw-medium">Attendance Overview</p> <p className="mb-1 fs-6 fs-md-5 fw-medium">Attendance Overview</p>
<p className="card-subtitle text-muted mb-0"> <p className="card-subtitle text-muted mb-0">
Role-wise present count Role-wise present count
@ -84,6 +114,7 @@ const AttendanceOverview = () => {
</div> </div>
<div className="col-md-6 d-flex flex-column align-items-end gap-2"> <div className="col-md-6 d-flex flex-column align-items-end gap-2">
{/* Day range dropdown */}
<select <select
className="form-select form-select-sm w-auto" className="form-select form-select-sm w-auto"
value={dayRange} value={dayRange}
@ -94,10 +125,10 @@ const AttendanceOverview = () => {
<option value={30}>Last 30 Days</option> <option value={30}>Last 30 Days</option>
</select> </select>
{/* View toggle buttons */}
<div className="d-flex gap-2 justify-content-end"> <div className="d-flex gap-2 justify-content-end">
<button <button
className={`btn btn-sm p-1 ${ className={`btn btn-sm p-1 ${view === "chart" ? "btn-primary" : "btn-outline-primary"
view === "chart" ? "btn-primary" : "btn-outline-primary"
}`} }`}
onClick={() => setView("chart")} onClick={() => setView("chart")}
title="Chart View" title="Chart View"
@ -106,8 +137,7 @@ const AttendanceOverview = () => {
</button> </button>
<button <button
className={`btn btn-sm p-1 ${ className={`btn btn-sm p-1 ${view === "table" ? "btn-primary" : "btn-outline-primary"
view === "table" ? "btn-primary" : "btn-outline-primary"
}`} }`}
onClick={() => setView("table")} onClick={() => setView("table")}
title="Table View" title="Table View"
@ -118,28 +148,34 @@ const AttendanceOverview = () => {
</div> </div>
</div> </div>
{/* Content Section */} {/* Content */}
<div className="flex-grow-1 d-flex align-items-center justify-content-center position-relative"> <div className="flex-grow-1 d-flex align-items-center justify-content-center ">
{isLoading && ( {/* {isLoading ? (
<div className="position-absolute top-0 start-0 w-100 h-100 d-flex align-items-center justify-content-center bg-white bg-opacity-50"> <div className="position-absolute top-0 start-0 w-100 h-100 d-flex align-items-center justify-content-center bg-white bg-opacity-50">
<span>Loading...</span> <span>Loading...</span>
</div> </div>
)} ) : !attendanceData || attendanceData.length === 0 ? (
{!isLoading && (!attendanceData || attendanceData.length === 0) ? (
<div <div
className="text-muted fw-semibold d-flex align-items-center justify-content-center" className="text-muted fw-semibold d-flex align-items-center justify-content-center"
style={{ minHeight: "250px" }} style={{ minHeight: "250px" }}
> >
No data found No data found
</div> </div>
) : view === "chart" ? ( */}
{isLoading ? (
<SpinnerLoader />
) : error ? (
<p className="text-danger">{error}</p>
) : attendanceOverviewData.length === 0 ||
attendanceOverviewData.every((item) => item.present === 0) ? (
<div className="text-center text-dark">No data found</div>
) : view === "chart" ? ( ) : view === "chart" ? (
<div className="w-100"> <div className="w-100">
<ReactApexChart <ReactApexChart
options={chartOptions} options={chartOptions}
series={chartSeries} series={chartSeries}
type="bar" type="bar"
height={300} height={350}
/> />
</div> </div>
) : ( ) : (
@ -156,7 +192,7 @@ const AttendanceOverview = () => {
<th style={{ background: "#f8f9fa" }}>Role</th> <th style={{ background: "#f8f9fa" }}>Role</th>
{dates.map((date, idx) => ( {dates.map((date, idx) => (
<th key={idx} style={{ background: "#f8f9fa" }}> <th key={idx} style={{ background: "#f8f9fa" }}>
{date} {moment(date, "DD MMM YY").format("DD MMM")}
</th> </th>
))} ))}
</tr> </tr>
@ -165,15 +201,13 @@ const AttendanceOverview = () => {
<tbody> <tbody>
{roles.map((role) => ( {roles.map((role) => (
<tr key={role}> <tr key={role}>
<td className="fw-medium text-start table-cell">{role}</td> <td className="fw-medium text-start">{role}</td>
{tableData.map((row, idx) => { {tableData.map((row, idx) => {
const value = row[role]; const value = row[role];
return ( return (
<td <td
key={idx} key={idx}
style={ style={value > 0 ? { backgroundColor: "#d5d5d5" } : {}}
value > 0 ? { backgroundColor: "#e9ecef" } : {}
}
> >
{value} {value}
</td> </td>

View File

@ -59,7 +59,7 @@ const Dashboard = () => {
</div> </div>
)} )}
<div className="row vh-100"> <div className="row">
{!isAllProjectsSelected && ( {!isAllProjectsSelected && (
<div className="col-12 col-md-6 mb-sm-0 mb-4 "> <div className="col-12 col-md-6 mb-sm-0 mb-4 ">
<AttendanceOverview /> <AttendanceOverview />

View File

@ -5,6 +5,7 @@ import { useSelectedProject } from "../../slices/apiDataManager";
import { DateRangePicker1 } from "../common/DateRangePicker"; 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 { SpinnerLoader } from "../common/Loader";
const ExpenseAnalysis = () => { const ExpenseAnalysis = () => {
const projectId = useSelectedProject(); const projectId = useSelectedProject();
@ -103,14 +104,19 @@ const ExpenseAnalysis = () => {
{isLoading && ( {isLoading && (
<div <div
className="d-flex justify-content-center align-items-center" className="d-flex justify-content-center align-items-center"
style={{ height: "200px" }} style={{ minHeight: "50vh" }}
> >
<span>Loading...</span> <SpinnerLoader />
</div> </div>
)} )}
{!isLoading && report.length === 0 && ( {!isLoading && report.length === 0 && (
<div className="d-flex justify-content-center align-items-center">No data found</div> <div
className="d-flex justify-content-center align-items-center text-muted"
style={{ height: "300px" }}
>
No data found
</div>
)} )}
{!isLoading && report.length > 0 && ( {!isLoading && report.length > 0 && (

View File

@ -5,6 +5,7 @@ import { useSelector } from "react-redux";
import { useExpenseDataByProject } from "../../hooks/useDashboard_Data"; import { useExpenseDataByProject } from "../../hooks/useDashboard_Data";
import { formatCurrency } from "../../utils/appUtils"; import { formatCurrency } from "../../utils/appUtils";
import { formatDate_DayMonth } from "../../utils/dateUtils"; import { formatDate_DayMonth } from "../../utils/dateUtils";
import { SpinnerLoader } from "../common/Loader";
const ExpenseByProject = () => { const ExpenseByProject = () => {
const projectId = useSelector((store) => store.localVariables.projectId); const projectId = useSelector((store) => store.localVariables.projectId);
@ -74,10 +75,10 @@ const ExpenseByProject = () => {
] ]
return ( return (
<div className="card shadow-sm rounded "> <div className="card shadow-sm h-100 rounded ">
{/* Header */} {/* Header */}
<div className="card-header"> <div className="card-header">
<div className="d-flex justify-content-between align-items-center mb-3 mt-3"> <div className="d-flex justify-content-between align-items-center mb-3 mt-1">
<div className="text-start"> <div className="text-start">
<p className="mb-1 fw-medium fs-6 fs-md-5 ">Monthly Expense -</p> <p className="mb-1 fw-medium fs-6 fs-md-5 ">Monthly Expense -</p>
<p className="card-subtitle me-5 mb-0">Detailed project expenses</p> <p className="card-subtitle me-5 mb-0">Detailed project expenses</p>
@ -93,7 +94,7 @@ const ExpenseByProject = () => {
</button> </button>
<ul className="dropdown-menu dropdown-menu-end "> <ul className="dropdown-menu dropdown-menu-end ">
{ExpenseCategoryType.map((cat) => ( {ExpenseCategoryType.map((cat) => (
<li> <li key={cat.id}>
<button <button
className="dropdown-item" className="dropdown-item"
onClick={() => { onClick={() => {
@ -146,12 +147,15 @@ const ExpenseByProject = () => {
{/* Chart */} {/* Chart */}
<div className="card-body bg-white text-dark p-3 rounded" style={{ minHeight: "210px" }}> <div className="card-body bg-white text-dark p-3 rounded" style={{ minHeight: "210px" }}>
{isLoading ? ( {isLoading ? (
<p>Loading chart...</p> <div className="d-flex justify-content-center align-items-center py-5">
<SpinnerLoader />
</div>
) : !expenseApiData || expenseApiData.length === 0 ? ( ) : !expenseApiData || expenseApiData.length === 0 ? (
<div className="text-center text-muted py-5">No data found</div> <div className="text-center text-muted py-12">No data found</div>
) : ( ) : (
<Chart options={options} series={series} type="bar" height={235} /> <Chart options={options} series={series} type="bar" height={235} />
)} )}
</div> </div>
</div> </div>

View File

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