Implementing Spinner and Removing error in Dashboard.
This commit is contained in:
parent
ec38e8f9e0
commit
d18eabf6f5
@ -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,11 +125,11 @@ 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,9 +137,8 @@ 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>
|
||||||
|
|||||||
@ -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 />
|
||||||
|
|||||||
@ -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();
|
||||||
@ -54,32 +55,32 @@ const ExpenseAnalysis = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
responsive: [
|
responsive: [
|
||||||
{
|
{
|
||||||
breakpoint: 1200,
|
breakpoint: 1200,
|
||||||
options: {
|
options: {
|
||||||
chart: { width: "100%", height: 350 },
|
chart: { width: "100%", height: 350 },
|
||||||
legend: { position: "bottom" },
|
legend: { position: "bottom" },
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
breakpoint: 992,
|
|
||||||
options: {
|
|
||||||
chart: { width: "100%", height: 300 },
|
|
||||||
dataLabels: { style: { fontSize: "11px" } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
breakpoint: 576,
|
|
||||||
options: {
|
|
||||||
chart: { width: "100%", height: 250 },
|
|
||||||
legend: { fontSize: "10px" },
|
|
||||||
plotOptions: {
|
|
||||||
pie: { donut: { size: "65%" } },
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
],
|
breakpoint: 992,
|
||||||
|
options: {
|
||||||
|
chart: { width: "100%", height: 300 },
|
||||||
|
dataLabels: { style: { fontSize: "11px" } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: 576,
|
||||||
|
options: {
|
||||||
|
chart: { width: "100%", height: 250 },
|
||||||
|
legend: { fontSize: "10px" },
|
||||||
|
plotOptions: {
|
||||||
|
pie: { donut: { size: "65%" } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -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 && (
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user