AppMenu : Dynamic Menus #356
@ -22,6 +22,7 @@ const EmpAttendance = ({ employee }) => {
|
|||||||
data = [],
|
data = [],
|
||||||
isLoading: loading,
|
isLoading: loading,
|
||||||
isFetching,
|
isFetching,
|
||||||
|
isError,
|
||||||
error,
|
error,
|
||||||
refetch,
|
refetch,
|
||||||
} = useAttendanceByEmployee(employee, dateRange.startDate, dateRange.endDate);
|
} = useAttendanceByEmployee(employee, dateRange.startDate, dateRange.endDate);
|
||||||
@ -145,7 +146,7 @@ const EmpAttendance = ({ employee }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="table-responsive text-nowrap">
|
<div className="table-responsive text-nowrap">
|
||||||
{!loading && data.length === 0 && <span>No employee logs</span>}
|
{!loading && data.length === 0 && <span>No employee logs</span>}
|
||||||
{error && <div className="text-center">{error}</div>}
|
{isError && <div className="text-center">{error.message}</div>}
|
||||||
{loading && !data && <div className="text-center">Loading...</div>}
|
{loading && !data && <div className="text-center">Loading...</div>}
|
||||||
{data && data.length > 0 && (
|
{data && data.length > 0 && (
|
||||||
<table className="table mb-0">
|
<table className="table mb-0">
|
||||||
|
36
src/components/Layout/MenuItemSkeleton.jsx
Normal file
36
src/components/Layout/MenuItemSkeleton.jsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
const SkeletonLine = ({ height = 50, width = "100%", className = "" }) => (
|
||||||
|
<div
|
||||||
|
className={`skeleton mb-2 ${className}`}
|
||||||
|
style={{
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
);
|
||||||
|
export const MenuItemSkeleton = ({ hasSubmenu = false, submenuCount = 3 }) => {
|
||||||
|
return (
|
||||||
|
<li className="menu-item">
|
||||||
|
<div className="menu-link d-flex align-items-center gap-2 ">
|
||||||
|
{/* icon placeholder */}
|
||||||
|
<SkeletonLine height={25} width="25px" className="rounded" />
|
||||||
|
{/* text placeholder */}
|
||||||
|
<SkeletonLine height={25} width="100%" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Submenu skeletons */}
|
||||||
|
{hasSubmenu && (
|
||||||
|
<ul className="menu-sub mt-1 ms-4">
|
||||||
|
{[...Array(submenuCount)].map((_, idx) => (
|
||||||
|
<li key={idx} className="menu-item">
|
||||||
|
<div className="menu-link d-flex align-items-center gap-2">
|
||||||
|
<SkeletonLine height={20} width="20px" className="rounded" />
|
||||||
|
<SkeletonLine height={24} width="100px" />
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
@ -2,10 +2,13 @@ import React from "react";
|
|||||||
import { Link, NavLink, useLocation, useNavigate } from "react-router-dom";
|
import { Link, NavLink, useLocation, useNavigate } from "react-router-dom";
|
||||||
import menuData from "../../data/menuData.json";
|
import menuData from "../../data/menuData.json";
|
||||||
import { getCachedProfileData } from "../../slices/apiDataManager";
|
import { getCachedProfileData } from "../../slices/apiDataManager";
|
||||||
|
import { useSidBarMenu } from "../../hooks/useProfile";
|
||||||
|
import { MenuItemSkeleton } from "./MenuItemSkeleton";
|
||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { data, isError, isLoading, isFetched,error } = useSidBarMenu();
|
||||||
|
console.log(data)
|
||||||
return (
|
return (
|
||||||
<aside
|
<aside
|
||||||
id="layout-menu"
|
id="layout-menu"
|
||||||
@ -33,33 +36,46 @@ const Sidebar = () => {
|
|||||||
<div className="menu-inner-shadow"></div>
|
<div className="menu-inner-shadow"></div>
|
||||||
|
|
||||||
<ul className="menu-inner py-1">
|
<ul className="menu-inner py-1">
|
||||||
{menuData.map((section) => (
|
|
||||||
<React.Fragment key={(Math.random() + 1).toString(36)}>
|
{isError && (
|
||||||
{section.header && (
|
<div className="text-center text-small">{error.message}</div>
|
||||||
|
)}
|
||||||
|
{isLoading && (
|
||||||
|
<>
|
||||||
|
{[...Array(7)].map((_, idx) => (
|
||||||
|
<MenuItemSkeleton
|
||||||
|
key={idx}
|
||||||
|
hasSubmenu={idx % 2 === 1}
|
||||||
|
submenuCount={Math.floor(Math.random() * 3) + 2}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{data &&
|
||||||
|
data?.data.map((section) => (
|
||||||
|
<React.Fragment key={(Math.random() + 1).toString(36)}>
|
||||||
|
{/* {section.header && (
|
||||||
<li className="menu-header small text-uppercase">
|
<li className="menu-header small text-uppercase">
|
||||||
<span className="menu-header-text">{section.header}</span>
|
<span className="menu-header-text">{section.header}</span>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)} */}
|
||||||
{section.items.map(MenuItem)}
|
{section.items.map(MenuItem)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const MenuItem = (item) => {
|
const MenuItem = (item) => {
|
||||||
item.id = Math.random();
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isActive = location.pathname === item.link;
|
const isActive = location.pathname === item.link;
|
||||||
const hasSubmenu = item.submenu && item.submenu.length > 0;
|
const hasSubmenu = Array.isArray(item.submenu) && item.submenu.length > 0;
|
||||||
const isSubmenuActive =
|
const isSubmenuActive =
|
||||||
hasSubmenu &&
|
hasSubmenu && item.submenu.some((sub) => location.pathname === sub.link);
|
||||||
item.submenu.some((subitem) => location.pathname === subitem.link);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
key={(Math.random() + 1).toString(36)}
|
|
||||||
className={`menu-item ${isActive || isSubmenuActive ? "active" : ""} ${
|
className={`menu-item ${isActive || isSubmenuActive ? "active" : ""} ${
|
||||||
hasSubmenu && isSubmenuActive ? "open" : ""
|
hasSubmenu && isSubmenuActive ? "open" : ""
|
||||||
}`}
|
}`}
|
||||||
@ -67,21 +83,24 @@ const MenuItem = (item) => {
|
|||||||
<NavLink
|
<NavLink
|
||||||
aria-label={`Navigate to ${item.text} ${!item.available ? "Pro" : ""}`}
|
aria-label={`Navigate to ${item.text} ${!item.available ? "Pro" : ""}`}
|
||||||
to={item.link}
|
to={item.link}
|
||||||
className={`menu-link ${item.submenu ? "menu-toggle" : ""}`}
|
className={`menu-link ${hasSubmenu ? "menu-toggle" : ""}`}
|
||||||
key={(Math.random() + 1).toString(36)}
|
target={item.link?.includes("http") ? "_blank" : undefined}
|
||||||
target={item.link.includes("http") ? "_blank" : undefined}
|
|
||||||
>
|
>
|
||||||
<i className={`menu-icon tf-icons ${item.icon}`}></i>
|
<i className={`menu-icon tf-icons ${item.icon}`}></i>
|
||||||
<div>{item.text}</div>{" "}
|
<div>{item.text}</div>
|
||||||
{item.available === false && (
|
{item.available === false && (
|
||||||
<div className="badge bg-label-primary fs-tiny rounded-pill ms-auto">
|
<div className="badge bg-label-primary fs-tiny rounded-pill ms-auto">
|
||||||
Pro
|
Pro
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
{item.submenu && (
|
|
||||||
<ul className="menu-sub" key={(Math.random() + 1).toString(36)}>
|
{/* Only render submenu if exists */}
|
||||||
{item.submenu.map(MenuItem)}
|
{hasSubmenu && (
|
||||||
|
<ul className="menu-sub">
|
||||||
|
{item.submenu.map((sub) => (
|
||||||
|
<MenuItem key={sub.id || sub.link} {...sub} />
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
|
@ -99,3 +99,13 @@ export const useProfile = () => {
|
|||||||
refetch,
|
refetch,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const useSidBarMenu = ()=>{
|
||||||
|
const userLogged = useSelector((store)=>store.globalVariables.loginUser);
|
||||||
|
return useQuery({
|
||||||
|
queryKey:["AppMenu"],
|
||||||
|
queryFn:async()=> await AuthRepository.appmenu(),
|
||||||
|
enabled: !!userLogged
|
||||||
|
})
|
||||||
|
}
|
@ -15,6 +15,8 @@ const AuthRepository = {
|
|||||||
logout: (data) => api.post("/api/auth/logout", data),
|
logout: (data) => api.post("/api/auth/logout", data),
|
||||||
profile: () => api.get("/api/user/profile"),
|
profile: () => api.get("/api/user/profile"),
|
||||||
changepassword: (data) => api.post("/api/auth/change-password", data),
|
changepassword: (data) => api.post("/api/auth/change-password", data),
|
||||||
|
appmenu:()=>api.get('/api/AppMenu/sidebar/menu-section')
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AuthRepository;
|
export default AuthRepository;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user