added skeleton and refresh button

This commit is contained in:
pramod mahajan 2025-08-09 13:01:32 +05:30
parent 4bff065153
commit 25f5ff27b1
4 changed files with 148 additions and 33 deletions

View File

@ -0,0 +1,97 @@
import React from "react";
const SkeletonCell = ({ width = "100%", height = 20, style = {} }) => (
<div
className="skeleton"
style={{
width,
height,
borderRadius: 4,
...style,
}}
/>
);
export const TenantTableSkeleton = ({ columns, rows = 5 }) => {
return (
<div className="card p-2 mt-3">
<div className="card-datatable text-nowrap table-responsive">
<table className="table border-top dataTable text-nowrap">
<thead>
<tr>
{columns.map((col) => (
<th key={col.key} className="sorting d-table-cell">
<div className={col.align}>{col.label}</div>
</th>
))}
</tr>
</thead>
<tbody>
{[...Array(rows)].map((_, rowIdx) => (
<tr key={rowIdx}>
{columns.map((col, colIdx) => (
<td
key={col.key || colIdx}
className={`d-table-cell px-3 py-2 align-middle ${
col.align ?? ""
}`}
>
{/* Icon + text skeleton for first few columns */}
{col.key === "name" && (
<div className="d-flex align-items-center">
<div
className="me-2"
style={{
width: 24,
height: 24,
borderRadius: "5px",
background: "#ddd",
}}
/>
<SkeletonCell width="120px" />
</div>
)}
{col.key === "domainName" && (
<div className="d-flex align-items-center">
<div
className="me-2"
style={{
width: 14,
height: 14,
borderRadius: "50%",
background: "#ddd",
}}
/>
<SkeletonCell width="140px" />
</div>
)}
{col.key === "contactName" && (
<div className="d-flex align-items-center ">
<div
className="me-2"
style={{
width: 20,
height: 20,
borderRadius: "50%",
background: "#ddd",
}}
/>
<SkeletonCell width="100px" />
</div>
)}
{col.key === "contactNumber" && (
<SkeletonCell width="100px"/>
)}
{col.key === "status" && (
<SkeletonCell width="60px" height={24} />
)}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
</div>
);
};

View File

@ -1,31 +1,38 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { useTenants } from "../../hooks/useTenant";
import { ITEMS_PER_PAGE } from "../../utils/constants";
import { getTenantStatus } from "../../utils/dateUtils";
import IconButton from "../common/IconButton";
import Pagination from "../common/Pagination";
import { TenantTableSkeleton } from "./TenanatSkeleton";
import { useTenantContext } from "../../pages/Tenant/TenantPage";
const TenantsList = ({searchText}) => {
const TenantsList = ({searchText,setIsRefetching, setRefetchFn}) => {
const [currentPage, setCurrentPage] = useState(1);
const { data, isLoading, isError, isInitialLoading, error } = useTenants(
const { data, isLoading, isError, isInitialLoading, error,refetch, isFetching } = useTenants(
currentPage,
{},
searchText,
);
const {setRefetching} = useTenantContext()
const paginate = (page) => {
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
setCurrentPage(page);
}
};
if (isInitialLoading)
return (
<div>
<p>Loading...</p>
</div>
);
if (isError) return <div>{error.message}</div>;
// Pass the refetch function to parent when component mounts
useEffect(() => {
setRefetchFn(() => refetch); // store in parent
}, [setRefetchFn, refetch]);
// Sync fetching status with parent
useEffect(() => {
setIsRefetching(isFetching);
}, [isFetching, setIsRefetching]);
const TenantColumns = [
{
@ -60,7 +67,7 @@ const TenantsList = ({searchText}) => {
key: "contactName",
label: "Contact Person",
getValue: (t) => (
<div className="d-flex align-items-center">
<div className="d-flex align-items-center text-start">
<i className="bx bx-sm bx-user me-1" />
{t.contactName || "N/A"}
</div>
@ -86,16 +93,21 @@ const TenantsList = ({searchText}) => {
{t.tenantStatus?.name || "Unknown"}
</span>
),
align:"text-center"
},
];
if (isInitialLoading)
return (
<TenantTableSkeleton columns={TenantColumns} rows={13} />
);
if (isError) return <div>{error.message}</div>;
return (
<>
<div className="card p-2 mt-3">
<div className="card-datatable text-nowrap table-responsive">
<table className="table border-top dataTable text-nowrap">
<thead>
<tr>
<tr className="shadow-sm">
{TenantColumns.map((col) => (
<th key={col.key} className="sorting d-table-cell">
<div className={col.align}>{col.label}</div>
@ -125,7 +137,7 @@ const TenantsList = ({searchText}) => {
<tr>
<td
colSpan={TenantColumns.length + 1}
className="text-center py-4"
className="text-center py-4 border-0"
>
No Tenants Found
</td>

View File

@ -1,6 +1,6 @@
import React, { useState, createContext, useEffect } from "react";
import React, { useState, createContext, useEffect, useContext } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { zodResolver } from "@hookform/resolvers/zod";
// ------Components-------
import Breadcrumb from "../../components/common/Breadcrumb";
import TenantsList from "../../components/Tenanat/TenantsList";
@ -9,11 +9,13 @@ import { useDebounce } from "../../utils/appUtils";
import { useFab } from "../../Context/FabContext";
//---------- Schema and defaultValues----
import { defaultFilterValues, filterSchema } from "../../components/Tenanat/TenantSchema";
import {
defaultFilterValues,
filterSchema,
} from "../../components/Tenanat/TenantSchema";
import TenantFilterPanel from "../../components/Tenanat/TenantFilterPanel";
// This is context that wrapping all components tenant releated , but must pass inside 'TenantContext.Provider'
// This is context that wrapping all components tenant releated , but must pass inside 'TenantContext.Provider'
export const TenantContext = createContext();
export const useTenantContext = () => {
const context = useContext(TenantContext);
@ -25,22 +27,20 @@ export const useTenantContext = () => {
const TenantPage = () => {
const [searchText, setSearchText] = useState("");
const [isRefetching,setRefetching] = useState(false)
const [refetchFn, setRefetchFn] = useState(null);
const debouncedSearch = useDebounce(searchText, 500);
const contextValue = {};
const contextValue = {
};
const navigate = useNavigate();
const { setOffcanvasContent, setShowTrigger } = useFab();
// This Hook allow us to right-side bar for filter Tenants
// This Hook allow us to right-side bar for filter Tenants
useEffect(() => {
useEffect(() => {
setShowTrigger(true);
setOffcanvasContent(
"Expense Filters",
<TenantFilterPanel/>
);
setOffcanvasContent("Tenant Filters", <TenantFilterPanel />);
return () => {
setShowTrigger(false);
setOffcanvasContent("", null);
@ -63,13 +63,17 @@ const TenantPage = () => {
<input
type="search"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
onChange={(e)=>setSearchText(e.target.value)}
className="form-control form-control-sm"
placeholder="Search..."
/>
</div>
<div className="col-6 col-md-6 col-lg-9 text-end">
<div className="col-6 col-md-6 col-lg-9 text-end cursor-pointer">
<span className="text-tiny text-muted p-1 border-0 bg-none lead mx-3 " disabled={isRefetching} onClick={() => refetchFn && refetchFn()}>
Refresh <i className={`bx bx-refresh ms-1 ${isRefetching ? "bx-spin":""}`}></i>
</span>
<button
type="button"
data-bs-toggle="tooltip"
@ -86,7 +90,9 @@ const TenantPage = () => {
</div>
</div>
<TenantsList searchText={debouncedSearch} />
<TenantsList searchText={debouncedSearch} setIsRefetching={setRefetching}
setRefetchFn={setRefetchFn}
/>
</div>
</TenantContext.Provider>
);

View File

@ -1,6 +1,6 @@
export const THRESH_HOLD = 48; // hours
export const DURATION_TIME = 20; // minutes
export const ITEMS_PER_PAGE = 10;
export const DURATION_TIME = 10; // minutes
export const ITEMS_PER_PAGE = 20;
export const OTP_EXPIRY_SECONDS = 600 // OTP time
export const MANAGE_MASTER = "588a8824-f924-4955-82d8-fc51956cf323";