210 lines
7.9 KiB
TypeScript
210 lines
7.9 KiB
TypeScript
import { signIn, signOut, useSession } from "next-auth/react";
|
||
import styles from "../styles/header.module.css";
|
||
import React, { useState, useEffect, useRef } from "react";
|
||
import { useRouter } from 'next/router';
|
||
import sidemenu, { footerMenu } from './sidemenuData.js'; // Move sidemenu data to a separate file
|
||
import axiosInstance from "src/axiosSecure";
|
||
import common from "src/helpers/common";
|
||
//get package version from package.json
|
||
const packageVersion = require('../package.json').version;
|
||
|
||
function SidebarMenuItem({ item, session, isSubmenu }) {
|
||
const router = useRouter();
|
||
const isActive = router.pathname.includes(item.url);
|
||
|
||
const collapsable = item.collapsable === undefined ? true : item.collapsable;
|
||
// is open is always true if not collapsable; isOpen is true if not collapsable
|
||
//const [isOpen, setIsOpen] = useState(false && collapsable);
|
||
// Initialize isOpen to true for non-collapsible items, ensuring they are always "open" // xOR
|
||
|
||
const baseClass = `sidemenu-item flex items-center ${isSubmenu ? "px-3 py-1" : ""} mt-1 pr-0 transition-colors duration-3000 transform rounded-md`;
|
||
const activeClass = isActive ? "sidemenu-item-active text-blue-600 bg-gray-100 dark:text-blue-400 dark:bg-blue-900" : "text-gray-700 dark:text-gray-300";
|
||
const hoverClasses = "hover:bg-gray-100 dark:hover:bg-gray-800 dark:hover:text-gray-200 hover:text-gray-700";
|
||
|
||
const initialState = common.getLocalStorage(`sidebar-openState-${item.id}`, isActive);
|
||
const [isOpen, setIsOpen] = useState(() => common.getLocalStorage(`sidebar-openState-${item.id}`, isActive));
|
||
|
||
useEffect(() => {
|
||
// Only run this effect on the client-side and if it's a submenu item
|
||
if (typeof window !== 'undefined' && isSubmenu) {
|
||
common.setLocalStorage(`sidebar-openState-${item.id}`, isOpen);
|
||
}
|
||
}, [isOpen, item.id, isSubmenu]);
|
||
|
||
useEffect(() => {
|
||
// This effect should also check for window to ensure it's client-side
|
||
if (typeof window !== 'undefined' && isSubmenu) {
|
||
const isAnyChildActive = item.children?.some(child => router.pathname.includes(child.url));
|
||
if (isActive || isAnyChildActive) {
|
||
setIsOpen(true);
|
||
}
|
||
}
|
||
}, [router.pathname, isActive, item.children, isSubmenu]);
|
||
|
||
|
||
if (!session || (item.roles && !item.roles.includes(session?.user?.role))) {
|
||
return null;
|
||
}
|
||
|
||
const handleClick = () => {
|
||
//console.log("clicked", item);
|
||
if (item.children && collapsable) { // Toggle isOpen only if item is collapsable and has children
|
||
setIsOpen(!isOpen);
|
||
} else if (item.url) {
|
||
router.push(item.url);
|
||
}
|
||
};
|
||
|
||
const clickableClass = item.url || item.children ? "cursor-pointer" : "";
|
||
|
||
return (
|
||
<>
|
||
<div className={`${baseClass} ${activeClass} ${hoverClasses} ${clickableClass}`}
|
||
onClick={handleClick}>
|
||
{item.svgData && <svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<path d={item.svgData} stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||
</svg>}
|
||
<span className="ml-3 mr-1 font-medium">{item.text}</span>
|
||
{item.children && <DropDownIcon isOpen={isOpen} />}
|
||
</div>
|
||
{isOpen && item.children && (
|
||
// <ul className="relative accordion-collapse show">
|
||
<ul className="pl-2 mt-1">
|
||
{item.children.map((child, index) => (
|
||
<SidebarMenuItem key={index} item={child} session={session} isSubmenu={true} />
|
||
))}
|
||
</ul>
|
||
)}
|
||
</>
|
||
);
|
||
}
|
||
|
||
function DropDownIcon({ isOpen }) {
|
||
return (
|
||
<svg aria-hidden="false" focusable="false" className="w-3 h-3 ml-auto" viewBox="0 0 448 512">
|
||
{/* svg content */}
|
||
</svg>
|
||
);
|
||
}
|
||
|
||
export default function Sidebar({ isSidebarOpen, toggleSidebar }) {
|
||
const { data: session, status } = useSession();
|
||
const sidebarWidth = 226; // Simplify by using a constant
|
||
const sidebarRef = useRef(null);
|
||
//const [locations, setLocations] = useState([]);
|
||
|
||
|
||
useEffect(() => {
|
||
const fetchLocations = async () => {
|
||
try {
|
||
const response = await axiosInstance.get('/api/data/locations'); // Adjust the API endpoint as needed
|
||
const locationsData = response.data
|
||
.filter(location => location.isactive === true)
|
||
.map(location => ({
|
||
text: location.name,
|
||
url: `/cart/locations/${location.id}`,
|
||
}));
|
||
// Find the "Locations" menu item and populate its children with locationsData
|
||
const menuIndex = sidemenu.findIndex(item => item.id === "locations");
|
||
if (menuIndex !== -1) {
|
||
sidemenu[menuIndex].children = locationsData;
|
||
}
|
||
//setLocations(locationsData); // Optional, if you need to use locations elsewhere
|
||
} catch (error) {
|
||
console.error("Error fetching locations:", error);
|
||
}
|
||
};
|
||
fetchLocations();
|
||
}, []);
|
||
|
||
if (status === "loading") {
|
||
return <div>Loading...</div>;
|
||
}
|
||
|
||
|
||
|
||
return (
|
||
<>
|
||
<button onClick={toggleSidebar}
|
||
className="fixed top-1 left-4 z-40 m- text-xl bg-white border border-gray-200 p-2 rounded-full shadow-lg focus:outline-none"
|
||
style={{ transform: isSidebarOpen ? `translateX(${sidebarWidth - 64}px)` : 'translateX(-20px)' }}>☰</button>
|
||
<aside id="sidenav" ref={sidebarRef}
|
||
className="px-2 fixed top-0 left-0 z-30 h-screen overflow-y-auto bg-white border-r dark:bg-gray-900 dark:border-gray-700 transition-all duration-300 w-64"
|
||
style={{ width: `${sidebarWidth}px`, transform: isSidebarOpen ? 'translateX(0)' : `translateX(-${sidebarWidth - 16}px)` }}>
|
||
<h2 className="text-2xl font-semibold text-gray-800 dark:text-white pt-2 pl-4 pb-4"
|
||
title={`v.${packageVersion} ${process.env.GIT_COMMIT_ID}`} >Специално Свидетелстване София</h2>
|
||
<div className="flex flex-col justify-between pb-4">
|
||
<nav>
|
||
{sidemenu.map((item, index) => (
|
||
<SidebarMenuItem key={index} item={item} session={session} />
|
||
))}
|
||
<hr className="my-3 border-gray-200 dark:border-gray-600" />
|
||
{/* User section */}
|
||
<UserSection session={session} />
|
||
{/* Footer section: smaller lighter text */}
|
||
<div className="mt-auto">
|
||
<hr className="border-gray-200 dark:border-gray-600 text-align-bottom" />
|
||
<FooterSection />
|
||
</div>
|
||
</nav>
|
||
</div>
|
||
</aside>
|
||
</>
|
||
);
|
||
}
|
||
|
||
function UserSection({ session }) {
|
||
return (
|
||
<div className="sidemenu-item flex items-center">
|
||
{!session ? <SignInButton /> : <UserDetails session={session} />}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function SignInButton() {
|
||
return (
|
||
<div className="items-center py-2" onClick={() => signIn()}>
|
||
<a href="/api/auth/signin">Впишете се</a>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function UserDetails({ session }) {
|
||
return (
|
||
<>
|
||
<hr className="m-0" />
|
||
<div className="flex items-center">
|
||
{session.user.image && (
|
||
<img className="object-cover mx-2 rounded-full h-9 w-9" src={session.user.image} alt="avatar" />
|
||
)}
|
||
<div className="ml-3 overflow-hidden">
|
||
<p className="mx-1 mt-1 text-sm font-medium text-gray-800 dark:text-gray-200">{session.user.name}</p>
|
||
<p className="mx-1 mt-1 text-sm font-medium text-gray-600 dark:text-gray-400">{session.user.role}</p>
|
||
<a href="/api/auth/signout" className={styles.button} onClick={(e) => { e.preventDefault(); signOut(); }}>Изход</a>
|
||
</div>
|
||
</div>
|
||
</>
|
||
);
|
||
}
|
||
|
||
function FooterSection() {
|
||
const router = useRouter();
|
||
|
||
const navigateTo = (url) => {
|
||
router.push(url);
|
||
};
|
||
|
||
return (
|
||
footerMenu.map((item, index) => (
|
||
<div
|
||
key={index}
|
||
className="px-4 py-1 mt-2 cursor-pointer hover:underline hover:text-blue-600 dark:hover:text-blue-400 "
|
||
onClick={() => navigateTo(item.url)}
|
||
>
|
||
<span className="text-gray-700 dark:text-gray-300 font-medium">{item.text}</span>
|
||
</div>
|
||
))
|
||
);
|
||
}
|
||
|