added skeleton and refresh button
This commit is contained in:
parent
4bff065153
commit
25f5ff27b1
97
src/components/Tenanat/TenanatSkeleton.jsx
Normal file
97
src/components/Tenanat/TenanatSkeleton.jsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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";
|
||||
|
Loading…
x
Reference in New Issue
Block a user