added skeleton for dashboard card figures cards
This commit is contained in:
parent
32f16092db
commit
a070d23304
@ -25,6 +25,12 @@ font-size: 2rem;
|
|||||||
.text-md-b {
|
.text-md-b {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
.cursor-wait{
|
||||||
|
cursor:wait;
|
||||||
|
}
|
||||||
|
.cursor-notallowed {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
.text-xxs { font-size: 0.55rem; } /* 8px */
|
.text-xxs { font-size: 0.55rem; } /* 8px */
|
||||||
.text-xs { font-size: 0.75rem; } /* 12px */
|
.text-xs { font-size: 0.75rem; } /* 12px */
|
||||||
|
|||||||
@ -29,7 +29,9 @@ const TaskReportFilterPanel = ({ handleFilter }) => {
|
|||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = methods;
|
} = methods;
|
||||||
|
const closePanel = () => {
|
||||||
|
document.querySelector(".offcanvas.show .btn-close")?.click();
|
||||||
|
};
|
||||||
const onSubmit = (formData) => {
|
const onSubmit = (formData) => {
|
||||||
const filterPayload = {
|
const filterPayload = {
|
||||||
...formData,
|
...formData,
|
||||||
@ -37,12 +39,14 @@ const TaskReportFilterPanel = ({ handleFilter }) => {
|
|||||||
dateTo: localToUtc(formData.dateTo),
|
dateTo: localToUtc(formData.dateTo),
|
||||||
};
|
};
|
||||||
handleFilter(filterPayload);
|
handleFilter(filterPayload);
|
||||||
|
closePanel();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClear = () => {
|
const onClear = () => {
|
||||||
setResetKey((prev) => prev + 1);
|
setResetKey((prev) => prev + 1);
|
||||||
handleFilter(TaskReportDefaultValue);
|
handleFilter(TaskReportDefaultValue);
|
||||||
reset(TaskReportDefaultValue);
|
reset(TaskReportDefaultValue);
|
||||||
|
closePanel();
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
|
|||||||
@ -19,8 +19,6 @@ import ExpenseStatus from "./ExpenseStatus";
|
|||||||
import ExpenseByProject from "./ExpenseByProject";
|
import ExpenseByProject from "./ExpenseByProject";
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const { teamsCardData } = useDashboardTeamsCardData();
|
|
||||||
const { tasksCardData } = useDashboardTasksCardData();
|
|
||||||
|
|
||||||
// Get the selected project ID from Redux store
|
// Get the selected project ID from Redux store
|
||||||
const projectId = useSelector((store) => store.localVariables.projectId);
|
const projectId = useSelector((store) => store.localVariables.projectId);
|
||||||
@ -36,11 +34,11 @@ const Dashboard = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-4"}`}>
|
<div className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-4"}`}>
|
||||||
<Teams teamsCardData={teamsCardData} />
|
<Teams />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-4"}`}>
|
<div className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-4"}`}>
|
||||||
<TasksCard tasksCardData={tasksCardData} />
|
<TasksCard/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isAllProjectsSelected && (
|
{isAllProjectsSelected && (
|
||||||
|
|||||||
@ -52,3 +52,59 @@ export const ProjectCardSkeleton = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const TeamsSkeleton = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<style>{skeletonStyle}</style>
|
||||||
|
|
||||||
|
<div className="card p-3 h-100 text-center d-flex justify-content-between">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="d-flex justify-content-start align-items-center mb-3">
|
||||||
|
<h5 className="fw-bold mb-0 ms-2">
|
||||||
|
<i className="bx bx-group text-warning"></i> Teams
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Skeleton Body */}
|
||||||
|
<div className="d-flex justify-content-around align-items-start mt-n2 w-100">
|
||||||
|
<div className="text-center">
|
||||||
|
<SkeletonLine height={28} width="60px" className="mx-auto mb-2" />
|
||||||
|
<SkeletonLine height={14} width="90px" className="mx-auto" />
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<SkeletonLine height={28} width="60px" className="mx-auto mb-2" />
|
||||||
|
<SkeletonLine height={14} width="70px" className="mx-auto" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const TasksSkeleton = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<style>{skeletonStyle}</style>
|
||||||
|
|
||||||
|
<div className="card p-3 h-100 text-center d-flex justify-content-between">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="d-flex justify-content-start align-items-center mb-3">
|
||||||
|
<h5 className="fw-bold mb-0 ms-2">
|
||||||
|
<i className="bx bx-task text-success"></i> Tasks
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Skeleton Body */}
|
||||||
|
<div className="d-flex justify-content-around align-items-start mt-n2 w-100">
|
||||||
|
<div className="text-center">
|
||||||
|
<SkeletonLine height={28} width="60px" className="mx-auto mb-2" />
|
||||||
|
<SkeletonLine height={14} width="70px" className="mx-auto" />
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<SkeletonLine height={28} width="60px" className="mx-auto mb-2" />
|
||||||
|
<SkeletonLine height={14} width="90px" className="mx-auto" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@ -10,6 +10,7 @@ const Projects = () => {
|
|||||||
isLoading,
|
isLoading,
|
||||||
isError,
|
isError,
|
||||||
error,
|
error,
|
||||||
|
isFetching,
|
||||||
refetch,
|
refetch,
|
||||||
} = useDashboardProjectsCardData();
|
} = useDashboardProjectsCardData();
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ const Projects = () => {
|
|||||||
|
|
||||||
const totalProjects = projectsCardData?.totalProjects ?? 0;
|
const totalProjects = projectsCardData?.totalProjects ?? 0;
|
||||||
const ongoingProjects = projectsCardData?.ongoingProjects ?? 0;
|
const ongoingProjects = projectsCardData?.ongoingProjects ?? 0;
|
||||||
if(isLoading) return <ProjectCardSkeleton/>
|
if (isLoading) return <ProjectCardSkeleton />;
|
||||||
return (
|
return (
|
||||||
<div className="card p-3 h-100 text-center d-flex justify-content-between">
|
<div className="card p-3 h-100 text-center d-flex justify-content-between">
|
||||||
<div className="d-flex justify-content-start align-items-center mb-3">
|
<div className="d-flex justify-content-start align-items-center mb-3">
|
||||||
@ -35,15 +36,23 @@ const Projects = () => {
|
|||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isLoading ? (
|
{isError ? (
|
||||||
<div className="d-flex justify-content-center align-items-center flex-grow-1">
|
<div className="d-flex flex-column justify-content-center align-items-center p-1">
|
||||||
<div className="spinner-border text-primary" role="status">
|
<i className="bx bx-error-circle bx-sm fs-2 "></i>
|
||||||
<span className="visually-hidden">Loading...</span>
|
<small className="text-muted mb-2">
|
||||||
</div>
|
{error?.message || "Unable to load data at the moment."}
|
||||||
</div>
|
</small>
|
||||||
) : isError ? (
|
<span
|
||||||
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">
|
className={`text-muted ${
|
||||||
{error?.message || "Error loading data"}
|
isFetching ? "cursor-wait" : "cursor-pointer"
|
||||||
|
}`}
|
||||||
|
onClick={refetch}
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
className={`bx bx-refresh me-1 ${isFetching ? "bx-spin" : ""}`}
|
||||||
|
></i>{" "}
|
||||||
|
Retry
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="d-flex justify-content-around align-items-start mt-n2">
|
<div className="d-flex justify-content-around align-items-start mt-n2">
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
import { useDashboardTasksCardData } from "../../hooks/useDashboard_Data";
|
import { useDashboardTasksCardData } from "../../hooks/useDashboard_Data";
|
||||||
|
import { TasksSkeleton } from "./DashboardSkeleton";
|
||||||
|
|
||||||
const TasksCard = () => {
|
const TasksCard = () => {
|
||||||
const projectId = useSelectedProject();
|
const projectId = useSelectedProject();
|
||||||
@ -10,8 +11,10 @@ const TasksCard = () => {
|
|||||||
isLoading,
|
isLoading,
|
||||||
isError,
|
isError,
|
||||||
error,
|
error,
|
||||||
|
isFetching,
|
||||||
|
refetch,
|
||||||
} = useDashboardTasksCardData(projectId);
|
} = useDashboardTasksCardData(projectId);
|
||||||
|
if (isLoading) return <TasksSkeleton />;
|
||||||
return (
|
return (
|
||||||
<div className="card p-3 h-100 text-center d-flex justify-content-between">
|
<div className="card p-3 h-100 text-center d-flex justify-content-between">
|
||||||
<div className="d-flex justify-content-start align-items-center mb-3">
|
<div className="d-flex justify-content-start align-items-center mb-3">
|
||||||
@ -20,20 +23,26 @@ const TasksCard = () => {
|
|||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isLoading ? (
|
{isError ? (
|
||||||
// Loader while fetching
|
<div className="d-flex flex-column justify-content-center align-items-center p-1">
|
||||||
<div className="d-flex justify-content-center align-items-center flex-grow-1">
|
<i className="bx bx-error-circle bx-sm fs-2 "></i>
|
||||||
<div className="spinner-border text-primary" role="status">
|
|
||||||
<span className="visually-hidden">Loading...</span>
|
<small className="text-muted mb-2">
|
||||||
</div>
|
{error?.message || "Unable to load data at the moment."}
|
||||||
</div>
|
</small>
|
||||||
) : isError ? (
|
<span
|
||||||
// Show error
|
className={`text-muted ${
|
||||||
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">
|
isFetching ? "cursor-wait" : "cursor-pointer"
|
||||||
{error?.message || "Error loading data"}
|
}`}
|
||||||
|
onClick={refetch}
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
className={`bx bx-refresh me-1 ${isFetching ? "bx-spin" : ""}`}
|
||||||
|
></i>{" "}
|
||||||
|
Retry
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
// Show data
|
|
||||||
<div className="d-flex justify-content-around align-items-start mt-n2">
|
<div className="d-flex justify-content-around align-items-start mt-n2">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="mb-0 fw-bold">
|
<h4 className="mb-0 fw-bold">
|
||||||
|
|||||||
@ -4,16 +4,19 @@ import { useDashboardTeamsCardData } from "../../hooks/useDashboard_Data";
|
|||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
|
import { TeamsSkeleton } from "./DashboardSkeleton";
|
||||||
|
|
||||||
const Teams = () => {
|
const Teams = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const projectId = useSelectedProject()
|
const projectId = useSelectedProject();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: teamsCardData,
|
data: teamsCardData,
|
||||||
isLoading,
|
isLoading,
|
||||||
isError,
|
isError,
|
||||||
error,
|
error,
|
||||||
|
isFetching,
|
||||||
|
refetch,
|
||||||
} = useDashboardTeamsCardData(projectId);
|
} = useDashboardTeamsCardData(projectId);
|
||||||
|
|
||||||
// Handle real-time updates via eventBus
|
// Handle real-time updates via eventBus
|
||||||
@ -40,6 +43,7 @@ const Teams = () => {
|
|||||||
const inToday = teamsCardData?.inToday ?? 0;
|
const inToday = teamsCardData?.inToday ?? 0;
|
||||||
const totalEmployees = teamsCardData?.totalEmployees ?? 0;
|
const totalEmployees = teamsCardData?.totalEmployees ?? 0;
|
||||||
|
|
||||||
|
if (isLoading) return <TeamsSkeleton />;
|
||||||
return (
|
return (
|
||||||
<div className="card p-3 h-100 text-center d-flex justify-content-between">
|
<div className="card p-3 h-100 text-center d-flex justify-content-between">
|
||||||
<div className="d-flex justify-content-start align-items-center mb-3">
|
<div className="d-flex justify-content-start align-items-center mb-3">
|
||||||
@ -48,15 +52,24 @@ const Teams = () => {
|
|||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isLoading ? (
|
{isError ? (
|
||||||
<div className="d-flex justify-content-center align-items-center flex-grow-1">
|
<div className="d-flex flex-column justify-content-center align-items-center p-1">
|
||||||
<div className="spinner-border text-primary" role="status">
|
<i className="bx bx-error-circle bx-sm fs-2 "></i>
|
||||||
<span className="visually-hidden">Loading...</span>
|
|
||||||
</div>
|
<small className="text-muted mb-2">
|
||||||
</div>
|
{error?.message || "Unable to load data at the moment."}
|
||||||
) : isError ? (
|
</small>
|
||||||
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">
|
<span
|
||||||
{error?.message || "Error loading data"}
|
className={`text-muted ${
|
||||||
|
isFetching ? "cursor-wait" : "cursor-pointer"
|
||||||
|
}`}
|
||||||
|
onClick={refetch}
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
className={`bx bx-refresh me-1 ${isFetching ? "bx-spin" : ""}`}
|
||||||
|
></i>{" "}
|
||||||
|
Retry
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="d-flex justify-content-around align-items-start mt-n2">
|
<div className="d-flex justify-content-around align-items-start mt-n2">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user