191 lines
5.6 KiB
JavaScript
191 lines
5.6 KiB
JavaScript
import React, {
|
|
useState,
|
|
createContext,
|
|
useEffect,
|
|
useContext,
|
|
useCallback,
|
|
useMemo,
|
|
} from "react";
|
|
import { useForm } from "react-hook-form";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { useDispatch } from "react-redux";
|
|
|
|
// ------ Components -------
|
|
import Breadcrumb from "../../components/common/Breadcrumb";
|
|
import TenantsList from "../../components/Tenant/TenantsList";
|
|
import TenantFilterPanel from "../../components/Tenant/TenantFilterPanel";
|
|
|
|
// ------ Context & Utils -------
|
|
import { useDebounce } from "../../utils/appUtils";
|
|
import { useFab } from "../../Context/FabContext";
|
|
import { setCurrentTenant } from "../../slices/globalVariablesSlice";
|
|
import { hasUserPermission } from "../../utils/authUtils";
|
|
|
|
// ------ Schema -------
|
|
import {
|
|
defaultFilterValues,
|
|
filterSchema,
|
|
} from "../../components/Tenant/TenantSchema";
|
|
|
|
// ------ Constants -------
|
|
import {
|
|
MANAGE_TENANTS,
|
|
SUPPER_TENANT,
|
|
VIEW_TENANTS,
|
|
} from "../../utils/constants";
|
|
import { useProfile } from "../../hooks/useProfile";
|
|
|
|
// ---------- Context ----------
|
|
export const TenantContext = createContext();
|
|
export const useTenantContext = () => {
|
|
const context = useContext(TenantContext);
|
|
if (!context) {
|
|
throw new Error(
|
|
"useTenantContext must be used within a TenantContext.Provider"
|
|
);
|
|
}
|
|
return context;
|
|
};
|
|
|
|
const TenantPage = () => {
|
|
const dispatch = useDispatch();
|
|
const navigate = useNavigate();
|
|
const { profile } = useProfile();
|
|
|
|
// ---------- State ----------
|
|
const [searchText, setSearchText] = useState("");
|
|
const [isRefetching, setIsRefetching] = useState(false);
|
|
const [refetchFn, setRefetchFn] = useState(null);
|
|
const [filters, setFilters] = useState();
|
|
|
|
// ---------- Hooks ----------
|
|
const debouncedSearch = useDebounce(searchText, 500);
|
|
const { setOffcanvasContent, setShowTrigger } = useFab();
|
|
|
|
const isSuperTenant = hasUserPermission(SUPPER_TENANT);
|
|
const canManageTenants = hasUserPermission(MANAGE_TENANTS);
|
|
const isSelfTenant = hasUserPermission(VIEW_TENANTS);
|
|
|
|
const methods = useForm({
|
|
resolver: zodResolver(filterSchema),
|
|
defaultValues: defaultFilterValues,
|
|
});
|
|
const { reset } = methods;
|
|
|
|
const handleApplyFilters = useCallback((values) => {
|
|
setFilters(values);
|
|
}, []);
|
|
|
|
const filterPanelElement = useMemo(
|
|
() => <TenantFilterPanel onApply={handleApplyFilters} />,
|
|
[handleApplyFilters]
|
|
);
|
|
console.log(isSelfTenant)
|
|
// ---------- Fab Filter Panel ----------
|
|
useEffect(() => {
|
|
if (!isSuperTenant) return;
|
|
setShowTrigger(true);
|
|
setOffcanvasContent("Tenant Filters", filterPanelElement);
|
|
|
|
return () => {
|
|
setShowTrigger(false);
|
|
setOffcanvasContent("", null);
|
|
};
|
|
}, [isSuperTenant, filterPanelElement, profile]);
|
|
|
|
// ---------- Redirect for Self Tenant ----------
|
|
useEffect(() => {
|
|
if (!isSuperTenant && isSelfTenant) {
|
|
// Delay navigation to next tick to avoid "update during render" warning
|
|
setTimeout(() => {
|
|
navigate("/tenant/self");
|
|
}, 0);
|
|
}
|
|
}, [isSuperTenant, isSelfTenant, navigate]);
|
|
|
|
// ---------- Handlers ----------
|
|
const handleNewTenant = () => {
|
|
dispatch(setCurrentTenant(null));
|
|
navigate("/tenants/new-tenant");
|
|
};
|
|
|
|
// ---------- Context Value ----------
|
|
const contextValue = {};
|
|
|
|
return (
|
|
<TenantContext.Provider value={contextValue}>
|
|
<div className="container-fluid">
|
|
<Breadcrumb
|
|
data={[
|
|
{ label: "Home", link: "/dashboard" },
|
|
{ label: "Tenant", link: null },
|
|
]}
|
|
/>
|
|
|
|
{/* Super Tenant Actions */}
|
|
{isSuperTenant && (
|
|
<div className="card d-flex p-2">
|
|
<div className="row align-items-center">
|
|
{/* Search */}
|
|
<div className="col-6 col-md-6 col-lg-3 mb-md-0">
|
|
<input
|
|
type="search"
|
|
value={searchText}
|
|
onChange={(e) => setSearchText(e.target.value)}
|
|
className="form-control form-control-sm"
|
|
placeholder="Search Tenant"
|
|
/>
|
|
</div>
|
|
|
|
{/* Actions */}
|
|
<div className="col-6 col-md-6 col-lg-9 text-end">
|
|
<span
|
|
className="text-tiny text-muted p-1 border-0 bg-none lead mx-3 cursor-pointer"
|
|
disabled={isRefetching}
|
|
onClick={() => refetchFn && refetchFn()}
|
|
>
|
|
Refresh{" "}
|
|
<i
|
|
className={`bx bx-refresh ms-1 ${
|
|
isRefetching ? "bx-spin" : ""
|
|
}`}
|
|
></i>
|
|
</span>
|
|
|
|
<button
|
|
type="button"
|
|
title="Add New Tenant"
|
|
className="p-1 bg-primary rounded-circle cursor-pointer"
|
|
onClick={handleNewTenant}
|
|
>
|
|
<i className="bx bx-plus fs-4 text-white"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Tenant List or Access Denied */}
|
|
{isSuperTenant ? (
|
|
<TenantsList
|
|
filters={filters}
|
|
searchText={debouncedSearch}
|
|
setIsRefetching={setIsRefetching}
|
|
setRefetchFn={setRefetchFn}
|
|
/>
|
|
) : !isSelfTenant ? (
|
|
<div className="card text-center my-4 p-2">
|
|
<i className="fa-solid fa-triangle-exclamation fs-5"></i>
|
|
<p>
|
|
Access Denied: You don't have permission to perform this action!
|
|
</p>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</TenantContext.Provider>
|
|
);
|
|
};
|
|
|
|
export default TenantPage;
|