first commit
This commit is contained in:
223
src/routes/RouteLayout.tsx
Normal file
223
src/routes/RouteLayout.tsx
Normal file
@@ -0,0 +1,223 @@
|
||||
import { Outlet, useRouterState } from "@tanstack/react-router";
|
||||
import Drawer from "../components/Drawer/Drawer";
|
||||
import { useDrawerStore } from "../context/zustand-store/appStore";
|
||||
import { checkIsMobile } from "../utils/checkIsMobile";
|
||||
import BottomNavigation from "../screen/BottomNavigation";
|
||||
import Navbar from "../screen/NavBar";
|
||||
import Backdrop from "../components/BackDrop/Backdrop";
|
||||
import Modal from "../components/Modal/Modal";
|
||||
import {
|
||||
useUserProfileStore,
|
||||
useUserStore,
|
||||
} from "../context/zustand-store/userStore";
|
||||
import "../App.css";
|
||||
import { SideBar } from "../screen/SideBar";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { ItemWithSubItems } from "../types/userPermissions";
|
||||
import { getUserPermissions } from "../utils/getUserAvalableItems";
|
||||
import { getFaPermissions } from "../utils/getFaPermissions";
|
||||
import Typography from "../components/Typography/Typography";
|
||||
import { ArrowLeftIcon } from "@heroicons/react/24/outline";
|
||||
import Divider from "../components/Divider/Divider";
|
||||
import { RolesContextMenu } from "../components/Button/RolesContextMenu";
|
||||
import { useApiRequest } from "../utils/useApiRequest";
|
||||
|
||||
const sidebarVariants = {
|
||||
hidden: { opacity: 0, x: 50 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
x: 0,
|
||||
transition: {
|
||||
type: "spring",
|
||||
stiffness: 300,
|
||||
damping: 30,
|
||||
},
|
||||
},
|
||||
exit: {
|
||||
opacity: 0,
|
||||
x: 50,
|
||||
transition: {
|
||||
type: "tween",
|
||||
ease: "easeInOut",
|
||||
duration: 0.25,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const RootLayout = () => {
|
||||
const { drawerState } = useDrawerStore();
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const { auth } = useUserStore();
|
||||
const isMobile = checkIsMobile();
|
||||
const location = useRouterState({ select: (state) => state.location });
|
||||
const { profile } = useUserProfileStore();
|
||||
// const [, setIsDark] = useDarkMode();
|
||||
const [contextMenu, setContextMenu] = useState<{
|
||||
x: number;
|
||||
y: number;
|
||||
} | null>(null);
|
||||
const [selectedAccess, setSelectedAccess] = useState<string | null>(null);
|
||||
|
||||
const menuItems: ItemWithSubItems[] = getUserPermissions(
|
||||
profile?.permissions
|
||||
);
|
||||
|
||||
const currentPath = window.location.pathname;
|
||||
const matchedItem = menuItems.find((item) =>
|
||||
item.subItems.some(
|
||||
(sub) =>
|
||||
sub.path === currentPath ||
|
||||
sub.path === "/" + currentPath.split("/")[1] ||
|
||||
sub.path.includes(currentPath.split("/")[1] + "/")
|
||||
)
|
||||
);
|
||||
|
||||
const matchedSubItem = menuItems
|
||||
.flatMap((item) => item.subItems)
|
||||
.find(
|
||||
(sub) =>
|
||||
sub.path === currentPath ||
|
||||
sub.path.includes(currentPath.split("/")[1] + "/")
|
||||
);
|
||||
|
||||
const isAdmin = profile?.role?.type?.key === "ADM";
|
||||
|
||||
const { data: permissionsData } = useApiRequest({
|
||||
api: "/auth/api/v1/permission/",
|
||||
method: "get",
|
||||
params: { page: 1, page_size: 1000 },
|
||||
queryKey: ["all-permissions"],
|
||||
enabled: !!contextMenu && isAdmin,
|
||||
});
|
||||
|
||||
const handleSitemapContextMenu = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (!isAdmin) return;
|
||||
const pageKey = matchedSubItem?.name;
|
||||
if (!pageKey) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setContextMenu({ x: e.clientX, y: e.clientY });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (contextMenu && permissionsData?.results && matchedSubItem?.name) {
|
||||
const firstNonModify =
|
||||
permissionsData.results.find(
|
||||
(p: any) => p.page === matchedSubItem.name && p.modify_state === false
|
||||
) || null;
|
||||
setSelectedAccess(firstNonModify?.name || null);
|
||||
}
|
||||
}, [contextMenu, permissionsData, matchedSubItem?.name]);
|
||||
|
||||
useEffect(() => {
|
||||
const closeOnClick = () => {
|
||||
if (contextMenu) setContextMenu(null);
|
||||
};
|
||||
if (contextMenu) {
|
||||
document.addEventListener("click", closeOnClick);
|
||||
}
|
||||
return () => {
|
||||
document.removeEventListener("click", closeOnClick);
|
||||
};
|
||||
}, [contextMenu]);
|
||||
|
||||
useEffect(() => {
|
||||
contentRef.current?.scrollTo(0, 0);
|
||||
// if (!auth) {
|
||||
// setIsDark(false);
|
||||
// }
|
||||
}, [location.pathname]);
|
||||
|
||||
return (
|
||||
<div className="h-screen flex flex-col relative bg-white dark:bg-dark-900 overflow-hidden">
|
||||
{auth && <Navbar />}
|
||||
{drawerState?.isOpen && <Drawer />}
|
||||
|
||||
<motion.div
|
||||
layout
|
||||
layoutId="root-container"
|
||||
className={`flex flex-1 overflow-hidden`}
|
||||
>
|
||||
{auth && (
|
||||
<AnimatePresence mode="popLayout">
|
||||
<motion.div
|
||||
layout
|
||||
key="sidebar"
|
||||
variants={sidebarVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
exit="exit"
|
||||
className={`hidden sm:block`}
|
||||
style={{ zIndex: 10 }}
|
||||
>
|
||||
<SideBar />
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
)}
|
||||
|
||||
<motion.div
|
||||
ref={contentRef}
|
||||
layout
|
||||
layoutId="content"
|
||||
className={`flex-1 overflow-y-auto ${auth && "pt-2 px-2"} ${
|
||||
auth && isMobile ? "select-none" : "pb-4"
|
||||
} ${drawerState.isOpen && "blur-[1px] "}`}
|
||||
key="content"
|
||||
>
|
||||
{window.location.pathname !== "/" &&
|
||||
matchedItem?.fa &&
|
||||
auth &&
|
||||
!isMobile && (
|
||||
<div
|
||||
className="select-none mb-4 grid"
|
||||
onContextMenu={handleSitemapContextMenu}
|
||||
>
|
||||
<div className="flex justify-between mb-2">
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="text-[#727272] dark:text-dark-100"
|
||||
>
|
||||
{matchedItem?.fa} /{" "}
|
||||
{getFaPermissions(matchedSubItem?.name || "") || "نمایش"}
|
||||
</Typography>
|
||||
{location.pathname !== "/" && (
|
||||
<div
|
||||
onClick={() => window.history.back()}
|
||||
className="p-1 rounded-full cursor-pointer w-25"
|
||||
>
|
||||
<div className="gap-2 flex items-center justify-center">
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="text-[#727272] dark:text-dark-100"
|
||||
>
|
||||
بازگشت
|
||||
</Typography>
|
||||
<ArrowLeftIcon className="h-4 w-4 text-[#727272] dark:text-dark-100 " />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Divider />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Outlet />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
<Backdrop />
|
||||
<Modal />
|
||||
{isMobile && auth && <BottomNavigation />}
|
||||
{contextMenu && isAdmin && matchedSubItem?.name && selectedAccess && (
|
||||
<RolesContextMenu
|
||||
page={matchedSubItem.name}
|
||||
access={selectedAccess}
|
||||
position={contextMenu}
|
||||
onClose={() => setContextMenu(null)}
|
||||
isPage={true}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
54
src/routes/paths.ts
Normal file
54
src/routes/paths.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
//General
|
||||
export const HOME = "/";
|
||||
export const ABOUT = "/about";
|
||||
export const PROFILE = "/profile";
|
||||
export const MENU = "/menu";
|
||||
export const TRAINING = "/training";
|
||||
|
||||
//Management
|
||||
export const PERMISSION_ACCESS = "/permission-access";
|
||||
export const USERS = "/users";
|
||||
export const ORGANIZATIONS = "/organizations";
|
||||
export const ROLES = "/roles";
|
||||
export const PRODUCT_CATEGORIES = "/product-categories";
|
||||
export const PRODUCT_PRICING = "/pricing";
|
||||
|
||||
export const FEED_INPUT_PRODUCTS = "/feed-input-products";
|
||||
|
||||
//Wages
|
||||
export const QUOTA_INCENTIVE_PLANS = "/incentive-plans";
|
||||
export const QUOTAS = "/quota";
|
||||
export const QUOTA_DISTRIBUTION = "/quota/$code";
|
||||
|
||||
//Inventory
|
||||
export const INVENTORY = "/inventory";
|
||||
export const REPORTING = "/reporting";
|
||||
export const REPORTING_DETAIL = "/reporting/$type/$itemid/$product";
|
||||
export const INVENTORY_ENTRIES = "/inventory/$code";
|
||||
|
||||
//Livestock
|
||||
export const LIVESTOCK_FARMERS = "/farmers";
|
||||
export const LIVESTOCK_FARMERS_INCENTIVE_PLANS = "/farmers/plans/$farmid/$name";
|
||||
export const LIVESTOCK_FARMER_DETAIL = "/farmers/$farmid/$name";
|
||||
export const HERDS = "/herds";
|
||||
export const LIVESTOCKS = "/livestocks";
|
||||
export const LIVESTOCKS_HERDS_DETAIL = "/livestocks/$herdid/$name";
|
||||
|
||||
//POS
|
||||
export const POS_POS_LIST = "/pos";
|
||||
export const POS_COMPANIES = "/pos-companies";
|
||||
export const POS_COMPANY_DETAIL = "/pos-companies/$id/$name";
|
||||
export const POS_COMPANY_ACCOUNTS = "/pos/$id";
|
||||
|
||||
//TRANSACTIONS
|
||||
export const TRANSACTIONS = "/transactions";
|
||||
|
||||
//UNITS
|
||||
export const UNION_LIST = "/unions";
|
||||
export const COOPERATIVE_LIST = "/cooperatives";
|
||||
export const COOPERATIVE_RANCHERS_LIST = "/cooperatives/ranchers/$id/$name";
|
||||
export const UNION_COOPERATIVE_LIST = "/cooperatives/$id/$name";
|
||||
export const UNITS_SETTINGS = "/unit-settings";
|
||||
|
||||
//TAGGING
|
||||
export const TAGGING = "/tagging";
|
||||
45
src/routes/routeConfigs.ts
Normal file
45
src/routes/routeConfigs.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Auth } from "../Pages/Auth";
|
||||
import { Menu } from "../Pages/Menu";
|
||||
import { checkIsMobile } from "../utils/checkIsMobile";
|
||||
import * as R from "./paths";
|
||||
import Dashboard from "../Pages/Dashboard";
|
||||
import { ItemWithSubItems } from "../types/userPermissions";
|
||||
import UserProfile from "../Pages/UserProfile";
|
||||
import Training from "../Pages/Training";
|
||||
|
||||
interface Route {
|
||||
path: string;
|
||||
component: React.ComponentType | any;
|
||||
}
|
||||
|
||||
export const getRoutes = (
|
||||
auth: string | null,
|
||||
roles: ItemWithSubItems[]
|
||||
): Route[] => {
|
||||
let generalRoutes: Route[] = auth
|
||||
? [
|
||||
{ path: R.HOME, component: Dashboard },
|
||||
{ path: R.PROFILE, component: UserProfile },
|
||||
{ path: R.TRAINING, component: Training },
|
||||
]
|
||||
: [{ path: R.HOME, component: Auth }];
|
||||
|
||||
if (checkIsMobile()) {
|
||||
generalRoutes.push({ path: R.MENU, component: Menu });
|
||||
}
|
||||
|
||||
let availableRoutes: Route[] = [];
|
||||
|
||||
const routableItems = roles?.flatMap((role) =>
|
||||
role?.subItems.map((item) => ({
|
||||
path: item.path,
|
||||
component: item.component,
|
||||
}))
|
||||
);
|
||||
|
||||
if (roles) {
|
||||
availableRoutes = [...availableRoutes, ...routableItems];
|
||||
}
|
||||
|
||||
return [...generalRoutes, ...availableRoutes];
|
||||
};
|
||||
41
src/routes/routes.ts
Normal file
41
src/routes/routes.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
createRouter,
|
||||
RootRoute,
|
||||
Route,
|
||||
AnyRoute,
|
||||
} from "@tanstack/react-router";
|
||||
import { RootLayout } from "./RouteLayout";
|
||||
import { getRoutes } from "./routeConfigs";
|
||||
import NotFound from "../Pages/NotFound";
|
||||
import { Auth } from "../Pages/Auth";
|
||||
import { ItemWithSubItems } from "../types/userPermissions";
|
||||
|
||||
export const rootRoute = new RootRoute({
|
||||
component: RootLayout,
|
||||
});
|
||||
|
||||
export function makeRouter(auth: string | null, roles: ItemWithSubItems[]) {
|
||||
const routeConfigs = getRoutes(auth, roles);
|
||||
|
||||
const childRoutes: AnyRoute[] = routeConfigs.map(
|
||||
({ path, component }) =>
|
||||
new Route({
|
||||
getParentRoute: () => rootRoute,
|
||||
path,
|
||||
component,
|
||||
}) as AnyRoute
|
||||
);
|
||||
|
||||
const notFoundRoute = new Route({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: "*",
|
||||
component: auth ? NotFound : Auth,
|
||||
});
|
||||
|
||||
const routeTree = rootRoute.addChildren([...childRoutes, notFoundRoute]);
|
||||
|
||||
return createRouter({
|
||||
routeTree,
|
||||
defaultPreload: "intent",
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user