initial setup sidebar menus from backend

This commit is contained in:
pramod mahajan 2025-08-18 18:21:16 +05:30
parent 18390e9368
commit f00e3a1a24
4 changed files with 82 additions and 20 deletions

View 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>
);
};

View File

@ -2,10 +2,12 @@ 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 } = useSidBarMenu();
return ( return (
<aside <aside
id="layout-menu" id="layout-menu"
@ -33,33 +35,42 @@ 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) => ( {isLoading && (
<React.Fragment key={(Math.random() + 1).toString(36)}> <>
{section.header && ( {[...Array(7)].map((_, idx) => (
<MenuItemSkeleton
key={idx}
hasSubmenu={idx % 2 === 1}
submenuCount={Math.floor(Math.random() * 3) + 2}
/>
))}
</>
)}
{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 +78,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>

View File

@ -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
})
}

View File

@ -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;