Files
RasadDam_Frontend/src/routes/RouteLayout.tsx
2026-01-19 13:08:58 +03:30

224 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
);
};