Initial commit
This commit is contained in:
231
src/layouts/app/App.tsx
Normal file
231
src/layouts/app/App.tsx
Normal file
@@ -0,0 +1,231 @@
|
||||
import {
|
||||
LogoutOutlined,
|
||||
QuestionOutlined,
|
||||
SettingOutlined,
|
||||
UserOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import {
|
||||
Dropdown,
|
||||
Flex,
|
||||
FloatButton,
|
||||
Layout,
|
||||
MenuProps,
|
||||
message,
|
||||
theme,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from "antd";
|
||||
import { ReactNode, useEffect, useRef, useState } from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import { logout } from "features/auth/authSlice.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppDispatch } from "redux/hooks.ts";
|
||||
import { PATH_AUTH } from "utils/constants.ts";
|
||||
import LangSelect from "./components/langSelect/LangSelect.tsx";
|
||||
import HeaderNav from "./HeaderNav.tsx";
|
||||
import SideNav from "./SideNav.tsx";
|
||||
import "./styles.css";
|
||||
|
||||
const { Content } = Layout;
|
||||
const { Title } = Typography;
|
||||
type AppLayoutProps = {
|
||||
withSidebar?: boolean;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const AppLayout = ({ children, withSidebar = true }: AppLayoutProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
token: { borderRadius },
|
||||
} = theme.useToken();
|
||||
const [navFill, setNavFill] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const nodeRef = useRef(null);
|
||||
const floatBtnRef = useRef(null);
|
||||
const location = useLocation();
|
||||
const isMenu = location.pathname === "/menu";
|
||||
|
||||
const items: MenuProps["items"] = [
|
||||
{
|
||||
key: "user-profile-link",
|
||||
label: t("profile"),
|
||||
icon: <UserOutlined />,
|
||||
},
|
||||
{
|
||||
key: "user-settings-link",
|
||||
label: t("settings"),
|
||||
icon: <SettingOutlined />,
|
||||
},
|
||||
{
|
||||
key: "user-help-link",
|
||||
label: t("help center"),
|
||||
icon: <QuestionOutlined />,
|
||||
},
|
||||
{
|
||||
type: "divider",
|
||||
},
|
||||
{
|
||||
key: "user-logout-link",
|
||||
label: t("logout"),
|
||||
icon: <LogoutOutlined />,
|
||||
danger: true,
|
||||
onClick: () => {
|
||||
message.open({
|
||||
type: "loading",
|
||||
content: t("msg-success-logout"),
|
||||
});
|
||||
|
||||
dispatch(logout());
|
||||
|
||||
setTimeout(() => {
|
||||
navigate(PATH_AUTH.signin);
|
||||
}, 1000);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("scroll", () => {
|
||||
if (window.scrollY > 5) {
|
||||
setNavFill(true);
|
||||
} else {
|
||||
setNavFill(false);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Layout
|
||||
style={{
|
||||
minHeight: "100vh",
|
||||
// backgroundColor: 'white',
|
||||
}}
|
||||
>
|
||||
{withSidebar && (
|
||||
<SideNav
|
||||
trigger={null}
|
||||
style={{
|
||||
overflow: "auto",
|
||||
background: "none",
|
||||
border: "none",
|
||||
transition: "all .2s",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<HeaderNav
|
||||
style={{
|
||||
padding: "0px 20px 0px 20px",
|
||||
background: navFill ? "rgba(255, 255, 255, .5)" : "none",
|
||||
backdropFilter: navFill ? "blur(8px)" : "none",
|
||||
boxShadow: navFill ? "0 0 8px 2px rgba(0, 0, 0, 0.05)" : "none",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
height: "7vh",
|
||||
zIndex: 10,
|
||||
transition: "all .25s",
|
||||
}}
|
||||
>
|
||||
<Flex gap="middle" align="start" style={{ gap: 25 }}>
|
||||
<Tooltip title={dayjs().format("DD/MM/YYYY")}>
|
||||
<Title level={3} style={{ position: "relative", top: -2 }}>
|
||||
{dayjs().format("DD/MM/YYYY")}{" "}
|
||||
</Title>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title={t("language")}>
|
||||
<LangSelect />
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
{/* <Flex align="center">
|
||||
<Input.Search
|
||||
placeholder="search"
|
||||
style={{
|
||||
width: isMobile ? "100%" : "400px",
|
||||
marginLeft: isMobile ? 0 : ".5rem",
|
||||
}}
|
||||
size="middle"
|
||||
/>
|
||||
</Flex> */}
|
||||
|
||||
<Flex align="end" gap="small">
|
||||
{/* <Tooltip title="Apps">
|
||||
<Button icon={<AppstoreOutlined />} type="text" size="large" />
|
||||
</Tooltip>
|
||||
<Tooltip title="Messages">
|
||||
<Button icon={<MessageOutlined />} type="text" size="large" />
|
||||
</Tooltip> */}
|
||||
{/* <Tooltip title="Theme">
|
||||
<Switch
|
||||
className=" hidden sm:inline py-1"
|
||||
checkedChildren={<MoonOutlined />}
|
||||
unCheckedChildren={<SunOutlined />}
|
||||
checked={mytheme === "light" ? true : false}
|
||||
onClick={() => dispatch(toggleTheme())}
|
||||
/>
|
||||
</Tooltip> */}
|
||||
|
||||
{/* <Badge
|
||||
count={2}
|
||||
size="small"
|
||||
style={{
|
||||
top: 15,
|
||||
right: 5,
|
||||
left: 5,
|
||||
}}
|
||||
>
|
||||
<BellOutlined
|
||||
style={{
|
||||
fontSize: 20,
|
||||
marginTop: 15,
|
||||
marginLeft: 15,
|
||||
marginRight: 15,
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => setNotificationsDrawerOpen(true)}
|
||||
/>
|
||||
</Badge> */}
|
||||
<Dropdown menu={isMenu ? {} : { items }} trigger={["click"]}>
|
||||
<Flex>
|
||||
{!isMenu && (
|
||||
<img
|
||||
src={"/avatar.png"}
|
||||
alt="user profile photo"
|
||||
height={36}
|
||||
width={36}
|
||||
style={{
|
||||
borderRadius,
|
||||
objectFit: "cover",
|
||||
position: "relative",
|
||||
top: 5,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</Dropdown>
|
||||
</Flex>
|
||||
</HeaderNav>
|
||||
<Content>
|
||||
<div ref={nodeRef} style={{ background: "none" }}>
|
||||
{children}
|
||||
</div>
|
||||
<div ref={floatBtnRef}>
|
||||
<FloatButton.BackTop />
|
||||
</div>
|
||||
</Content>
|
||||
{/* <FooterNav
|
||||
style={{
|
||||
textAlign: "center",
|
||||
marginLeft: collapsed ? 0 : "200px",
|
||||
background: "none",
|
||||
}}
|
||||
/> */}
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
13
src/layouts/app/FooterNav.tsx
Normal file
13
src/layouts/app/FooterNav.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Layout } from 'antd';
|
||||
|
||||
const { Footer } = Layout;
|
||||
|
||||
type FooterNavProps = React.HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
const FooterNav = ({ ...others }: FooterNavProps) => {
|
||||
return (
|
||||
<Footer {...others}>AntD Dashboard © 2023 Created by Design Sparx</Footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default FooterNav;
|
||||
135
src/layouts/app/HeaderMenuDrawer.tsx
Normal file
135
src/layouts/app/HeaderMenuDrawer.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import { CloseOutlined } from "@ant-design/icons";
|
||||
import { Button, Drawer, Grid } from "antd";
|
||||
import BackIcon from "components/Icons/BackIcon";
|
||||
import NextIcon from "components/Icons/NextIcon";
|
||||
import ProText from "components/ProText";
|
||||
import { useState } from "react";
|
||||
import { useAppSelector } from "redux/hooks";
|
||||
import useHeaderMenu from "./hooks/useHeaderMenu";
|
||||
|
||||
const {useBreakpoint} = Grid;
|
||||
|
||||
export default function HeaderMenuDrawer() {
|
||||
const { isRTL, locale } = useAppSelector((state) => state.locale);
|
||||
const {themeName} = useAppSelector(state => state.theme)
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const { menuItems } = useHeaderMenu();
|
||||
const {xs } = useBreakpoint();
|
||||
const isMobile = xs
|
||||
const closeMobileMenu = () => setMobileMenuOpen(false);
|
||||
|
||||
const mobileMenuLinkStyle = (isActive: boolean): React.CSSProperties => ({
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 16,
|
||||
padding: "10px 20px",
|
||||
borderRadius: 12,
|
||||
fontSize: 14,
|
||||
fontWeight: isActive ? 600 : 400,
|
||||
marginBottom: 4,
|
||||
color: "#1f2937",
|
||||
});
|
||||
|
||||
const actionButtonStyle: React.CSSProperties = {
|
||||
color: themeName === "dark" ? "white" : "#1f2937",
|
||||
height: 32, // isMobile ? 44 : 40,
|
||||
minWidth: 32, //isMobile ? 44 : "auto",
|
||||
width: 32, //isMobile ? 44 : "auto",
|
||||
borderRadius: 8,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: isMobile ? 0 : 8,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
position: "fixed",
|
||||
zIndex: 999,
|
||||
[isRTL ? "left" : "right"]: 0,
|
||||
top: "30%",
|
||||
backgroundColor: themeName === "dark" ? "#111827" : "#FFF",
|
||||
[isRTL ? "borderTopRightRadius" : "borderTopLeftRadius"]: 10,
|
||||
[isRTL ? "borderBottomRightRadius" : "borderBottomLeftRadius"]: 10,
|
||||
border:
|
||||
themeName === "dark" ? "1px solid #374151" : "1px solid #FDF1D9",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
icon={isRTL ? <NextIcon /> : <BackIcon />}
|
||||
onClick={() => setMobileMenuOpen(true)}
|
||||
style={actionButtonStyle}
|
||||
/>
|
||||
</div>
|
||||
<Drawer
|
||||
placement={isRTL ? "left" : "right"}
|
||||
maskClosable={false}
|
||||
onClose={closeMobileMenu}
|
||||
open={mobileMenuOpen}
|
||||
width={"50%"}
|
||||
styles={{
|
||||
body: {
|
||||
padding: 0,
|
||||
},
|
||||
header: {
|
||||
padding: "5px 24px",
|
||||
borderBottom: `1px solid ${
|
||||
themeName === "dark" ? "#374151" : "#e5e7eb"
|
||||
}`,
|
||||
},
|
||||
}}
|
||||
closeIcon={null}
|
||||
>
|
||||
<div className="mobile-drawer-content">
|
||||
<div
|
||||
style={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
backgroundColor: "#5F6C7B1F",
|
||||
borderRadius: "50%",
|
||||
gap: 10,
|
||||
[isRTL ? "marginRight" : "marginLeft"]: "80%",
|
||||
marginTop: "50px",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<CloseOutlined
|
||||
onClick={() => {
|
||||
closeMobileMenu();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* Navigation Links */}
|
||||
<div className="mobile-drawer-section">
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
{menuItems.map((item) => (
|
||||
<div
|
||||
key={item.key}
|
||||
onClick={() => {
|
||||
closeMobileMenu();
|
||||
item.onClick?.();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
key={item.key}
|
||||
style={mobileMenuLinkStyle(locale === item.key)}
|
||||
>
|
||||
{item.icon}
|
||||
<ProText>{item.label.props.children}</ProText>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
16
src/layouts/app/HeaderNav.tsx
Normal file
16
src/layouts/app/HeaderNav.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Layout } from 'antd';
|
||||
import { useRef } from 'react';
|
||||
|
||||
const { Header } = Layout;
|
||||
|
||||
type HeaderNavProps = {
|
||||
navFill?: boolean;
|
||||
} & React.HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
const HeaderNav = ({ ...others }: HeaderNavProps) => {
|
||||
const nodeRef = useRef(null);
|
||||
|
||||
return <Header ref={nodeRef} {...others} />;
|
||||
};
|
||||
|
||||
export default HeaderNav;
|
||||
68
src/layouts/app/SideNav.tsx
Normal file
68
src/layouts/app/SideNav.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import { LoginOutlined } from "@ant-design/icons";
|
||||
import { ConfigProvider, Layout, Menu, SiderProps } from "antd";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { COLOR } from "ThemeConstants";
|
||||
import useSidebarItems from "./useSidebarItems";
|
||||
|
||||
const { Sider } = Layout;
|
||||
type SideNavProps = SiderProps;
|
||||
|
||||
const SideNav = ({ ...others }: SideNavProps) => {
|
||||
const nodeRef = useRef(null);
|
||||
const { pathname } = useLocation();
|
||||
const [current, setCurrent] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const paths = pathname.split("/");
|
||||
setCurrent(paths[paths.length - 1]);
|
||||
}, [pathname]);
|
||||
|
||||
const items = useSidebarItems();
|
||||
|
||||
return (
|
||||
<Sider
|
||||
ref={nodeRef}
|
||||
collapsedWidth={105}
|
||||
breakpoint="lg"
|
||||
trigger={null}
|
||||
collapsed={true}
|
||||
{...others}
|
||||
style={{ background: "#FFF", overflow: "hidden" }}
|
||||
className="side-container"
|
||||
>
|
||||
<LoginOutlined
|
||||
color="blue"
|
||||
// asLink
|
||||
// href={PATHS.menu}
|
||||
// justify="center"
|
||||
// gap="small"
|
||||
// imgSize={{ h: 105, w: 105 }}
|
||||
// style={{ padding: "1rem 0" }}
|
||||
// src="/sidebar-logo.jpg"
|
||||
/>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
components: {
|
||||
Menu: {
|
||||
itemBg: "none",
|
||||
itemSelectedBg: COLOR["100"],
|
||||
itemHoverBg: COLOR["50"],
|
||||
itemSelectedColor: COLOR["600"],
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Menu
|
||||
mode="inline"
|
||||
items={items}
|
||||
selectedKeys={[current]}
|
||||
style={{ border: "none" }}
|
||||
className="sidebar-menu-container"
|
||||
/>
|
||||
</ConfigProvider>
|
||||
</Sider>
|
||||
);
|
||||
};
|
||||
|
||||
export default SideNav;
|
||||
14
src/layouts/app/components/langSelect/LangSelect.css
Normal file
14
src/layouts/app/components/langSelect/LangSelect.css
Normal file
@@ -0,0 +1,14 @@
|
||||
.lang-item {
|
||||
margin: 8px 0px;
|
||||
padding: 0px 16px;
|
||||
}
|
||||
|
||||
.lang-item .avatar {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 35%;
|
||||
}
|
||||
|
||||
.lang-dropdown-container ul {
|
||||
padding: 10px 0px !important;
|
||||
}
|
||||
79
src/layouts/app/components/langSelect/LangSelect.tsx
Normal file
79
src/layouts/app/components/langSelect/LangSelect.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import type { MenuProps } from "antd";
|
||||
import { Avatar, Dropdown } from "antd";
|
||||
|
||||
import { setLocalesThunk } from "features/locale/localeSlice";
|
||||
import { LOCALES } from "i18n/helper";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import i18n from "i18n/i18n";
|
||||
import { useAppDispatch } from "redux/hooks";
|
||||
import { DEFAULT_LANGUAGE } from "utils/constants";
|
||||
import "./LangSelect.css";
|
||||
|
||||
type Locale = {
|
||||
id: string;
|
||||
title: string;
|
||||
iconPath: string;
|
||||
};
|
||||
|
||||
export default function LangSelect() {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [currentLanguage, setCurrentLanguage] = useState<Locale>(LOCALES[0]);
|
||||
|
||||
useEffect(() => {
|
||||
const selectedLocale = LOCALES.find(
|
||||
(item) => item.id === localStorage.getItem(DEFAULT_LANGUAGE)
|
||||
);
|
||||
if (selectedLocale && selectedLocale.id !== currentLanguage.id)
|
||||
setCurrentLanguage(selectedLocale);
|
||||
}, [currentLanguage?.id]);
|
||||
|
||||
const changeLocale = useCallback(
|
||||
(key: string) => {
|
||||
dispatch(setLocalesThunk(key));
|
||||
setCurrentLanguage(LOCALES.find((item) => item.id === key) || LOCALES[0]);
|
||||
i18n.changeLanguage(key);
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const items: MenuProps["items"] = useMemo(
|
||||
() =>
|
||||
LOCALES.map((_locale) => ({
|
||||
className: "lang-item",
|
||||
key: _locale.id,
|
||||
icon: (
|
||||
<span>
|
||||
<Avatar className="avatar" src={_locale.iconPath} />
|
||||
</span>
|
||||
),
|
||||
label: _locale.title,
|
||||
})),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
menu={{
|
||||
items,
|
||||
onClick: ({ key }) => {
|
||||
changeLocale(key as string);
|
||||
},
|
||||
}}
|
||||
overlayClassName="lang-dropdown-container"
|
||||
>
|
||||
<Avatar
|
||||
style={{
|
||||
position: "relative",
|
||||
top: 25,
|
||||
borderRadius: "35%",
|
||||
width: 30,
|
||||
height: 30,
|
||||
}}
|
||||
|
||||
src={currentLanguage?.iconPath}
|
||||
/>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
90
src/layouts/app/hooks/useHeaderMenu.tsx
Normal file
90
src/layouts/app/hooks/useHeaderMenu.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import {
|
||||
BgColorsOutlined,
|
||||
HomeOutlined,
|
||||
LoginOutlined,
|
||||
MenuOutlined,
|
||||
TranslationOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { setLocale, setLocalesThunk } from "features/locale/localeSlice";
|
||||
import { toggleTheme } from "features/theme/themeSlice";
|
||||
import i18n from "i18n/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
||||
import { useAppDispatch, useAppSelector } from "redux/hooks";
|
||||
|
||||
export default function useHeaderMenu() {
|
||||
const { id } = useParams();
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const { isRTL } = useAppSelector((state) => state.locale);
|
||||
const { themeName } = useAppSelector((state) => state.theme);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
key: "language",
|
||||
icon: (
|
||||
<TranslationOutlined
|
||||
style={{ color: themeName === "dark" ? "white" : "#1f2937" }}
|
||||
/>
|
||||
),
|
||||
label: <div>{isRTL ? t("common.arabic") : t("common.english")}</div>,
|
||||
onClick: () => {
|
||||
const key = isRTL ? "en" : "ar";
|
||||
dispatch(setLocale(key));
|
||||
dispatch(setLocalesThunk(key));
|
||||
i18n.changeLanguage(key);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "theme",
|
||||
icon: (
|
||||
<BgColorsOutlined
|
||||
style={{ color: themeName === "dark" ? "white" : "#1f2937" }}
|
||||
/>
|
||||
),
|
||||
label: (
|
||||
<div>
|
||||
{themeName === "light" ? t("common.dark") : t("common.light")}
|
||||
</div>
|
||||
),
|
||||
onClick: () => {
|
||||
dispatch(toggleTheme());
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "orders",
|
||||
icon: (
|
||||
<HomeOutlined
|
||||
style={{ color: themeName === "dark" ? "white" : "#1f2937" }}
|
||||
/>
|
||||
),
|
||||
label: <Link to={`/${id}/orders`}>{t("common.myOrder")}</Link>,
|
||||
onClick: () => {
|
||||
navigate(`/${id}/orders`);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "branches",
|
||||
icon: (
|
||||
<MenuOutlined
|
||||
style={{ color: themeName === "dark" ? "white" : "#1f2937" }}
|
||||
/>
|
||||
),
|
||||
label: <Link to={`/${id}/menu`}>{t("common.branches")}</Link>,
|
||||
},
|
||||
{
|
||||
key: "login",
|
||||
icon: (
|
||||
<LoginOutlined
|
||||
style={{ color: themeName === "dark" ? "white" : "#1f2937" }}
|
||||
/>
|
||||
),
|
||||
label: <Link to={`/${id}/login`}>{t("common.login")}</Link>,
|
||||
onClick: () => {
|
||||
navigate(`/${id}/login`);
|
||||
},
|
||||
},
|
||||
];
|
||||
return { menuItems };
|
||||
}
|
||||
1
src/layouts/app/index.ts
Normal file
1
src/layouts/app/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { AppLayout } from './App.tsx';
|
||||
66
src/layouts/app/styles.css
Normal file
66
src/layouts/app/styles.css
Normal file
@@ -0,0 +1,66 @@
|
||||
.trigger {
|
||||
padding: 0 8px;
|
||||
font-size: 18px;
|
||||
line-height: 45px;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
background-color: #005488;
|
||||
border-radius: 50%;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 18px;
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.trigger:hover {
|
||||
color: #fff;
|
||||
background-color: rgba(255, 255, 255, 0.36) !;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
/* margin-left: 15px; */
|
||||
/* padding-left: 11px; */
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.sidebar-menu-container .ant-menu-item,
|
||||
.sidebar-menu-container .ant-menu-submenu {
|
||||
margin-top: 25px !important;
|
||||
}
|
||||
|
||||
.home-icon {
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
.translations-icon {
|
||||
right: 25px !important;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
margin-top: -17px;
|
||||
right: 30px !important;
|
||||
width: 38px;
|
||||
}
|
||||
|
||||
.warehouse-icon {
|
||||
color: black !important;
|
||||
width: 35px !important;
|
||||
}
|
||||
|
||||
.tax-icon {
|
||||
right: 18px !important;
|
||||
margin-top: -17px;
|
||||
}
|
||||
|
||||
.report-icon {
|
||||
right: 30px !important;
|
||||
width: 32px;
|
||||
}
|
||||
70
src/layouts/app/useSidebarItems.tsx
Normal file
70
src/layouts/app/useSidebarItems.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { BugFilled } from "@ant-design/icons";
|
||||
import { MenuProps } from "antd";
|
||||
import { PATHS } from "utils/constants";
|
||||
|
||||
// import WarehouseIcon from "components/Icons/WarehouseIcon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export default function useSidebarItems() {
|
||||
type MenuItem = Required<MenuProps>["items"][number] & {
|
||||
permission: string;
|
||||
children?: MenuItem[];
|
||||
};
|
||||
const { t } = useTranslation();
|
||||
// const [isAuth] = useAuth();
|
||||
const getItem = (
|
||||
label: React.ReactNode,
|
||||
key: React.Key,
|
||||
permission?: string,
|
||||
icon?: React.ReactNode,
|
||||
children?: MenuItem[],
|
||||
type?: "group"
|
||||
): MenuItem => {
|
||||
return {
|
||||
key,
|
||||
icon,
|
||||
children,
|
||||
label,
|
||||
type,
|
||||
permission,
|
||||
} as MenuItem;
|
||||
};
|
||||
|
||||
// Recursive function to filter items based on permissions
|
||||
const getGrantedItems = (items: any[]): MenuItem[] => {
|
||||
return items
|
||||
.filter(() => true)// Filter out items without permission
|
||||
.map((item: any) => {
|
||||
if (item.children) {
|
||||
// Recursively filter children
|
||||
return {
|
||||
...item,
|
||||
children: getGrantedItems(item.children),
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
};
|
||||
|
||||
const items: MenuProps["items"] = [
|
||||
getItem(
|
||||
t("menu"),
|
||||
PATHS.menu,
|
||||
undefined,
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Link style={{}} to={PATHS.menu}>
|
||||
<BugFilled className="icon-container pos-icon" />
|
||||
</Link>
|
||||
</div>
|
||||
),
|
||||
];
|
||||
|
||||
// if we have a menu with empty children after applying "getGrantedItems"
|
||||
// we going to remove it
|
||||
const grantedItems = getGrantedItems(items).filter(
|
||||
(i) => (i.children && i.children.length !== 0) || !i.children
|
||||
);
|
||||
|
||||
return grantedItems;
|
||||
}
|
||||
Reference in New Issue
Block a user