// * Packages import { Link, usePage } from "@inertiajs/react"; import { ElementType, ReactNode, useEffect, useRef } from "react"; import { ChevronDownIcon, MoreHorizontalIcon, HomeIcon, UsersIcon, SquareArrowOutUpRightIcon, TelescopeIcon, StickyNoteIcon, CogIcon, HistoryIcon, ContactIcon, NewspaperIcon, Building2Icon, RadarIcon, RollerCoasterIcon, FileInputIcon, TagIcon, FolderIcon, } from "lucide-react"; import { route } from "ziggy-js"; // * Components shadcn import { Sidebar, SidebarHeader, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarMenuAction, useSidebar, } from "@/components/ui/sidebar"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Collapsible, CollapsibleTrigger, CollapsibleContent, } from "@/components/ui/collapsible"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; import { Button } from "@/components/ui/button"; // * Other import ApplicationLogo from "@/back/Components/ApplicationLogo"; import { Views } from "@/lib/utils"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; import { AppEnv, AuthProp, LaravelEnum } from "@/back/types/global"; type NavListProps = Array<{ /** Title of the group. */ title: string, /** Condition to set the visibility of the group. */ show: boolean, /** Links presents in the group. */ links: Array<{ /** Title of the button. */ title: string, /** Main model of the button (used for route checking). */ model?: string, /** External link of the button (if it's a not CRUD, just a simple link). */ link?: string, /** Define some conditions for active or not the button (in addition to the route model checking if defined). */ activeCondition?: boolean[], /** Condition to set the visibility of the button. */ show: boolean, /** Lucide icon of the button. */ icon: ElementType, /** Define some actions in side popover (like create/trash). */ actions?: (model: string) => ReactNode, }> }> type CustomDropdownMenuItemProps = { /** Link of the action. */ route: string; /** Lucide icon of the action. */ icon?: ElementType; /** Translation of the action. */ trans?: string; }; export default function AppSidebar() { const { appEnv, auth, enumUserRole } = usePage().props as unknown as { appEnv: AppEnv; auth: AuthProp; enumUserRole: LaravelEnum; }; const authPolicies = auth.policies; const { state } = useSidebar(); const prevStateRef = useRef(state); const isCollapsed = state === "collapsed"; const navList: NavListProps = [ { title: useTrans("back.pages"), show: true, links: [ { title: useTrans("back.dashboard"), model: "dashboard", show: true, icon: HomeIcon, }, { title: useTrans("back.your_website"), link: "/", show: true, icon: SquareArrowOutUpRightIcon, }, { title: str(useTrans("models.classes.dynamic-page", { choice: 2 })).ucFirst().value(), model: "dynamic-pages", show: authPolicies.dynamicPages.viewAny, icon: StickyNoteIcon, actions: (model: string) => filterActions({ items: [ authPolicies.dynamicPages.create && ( ), authPolicies.dynamicPages.viewTrash && ( ), ] }) }, ], }, { title: useTrans("back.modules"), show: authPolicies.news.viewAny || auth.policies.contact_counts.viewAny, links: [ { title: str(useTrans("models.classes.news", { choice: 2 })).ucFirst().value(), model: "news", activeCondition: [route().current("back.news-categories.*"), route().current('back.news-tags.*')], icon: NewspaperIcon, show: auth.policies.news.viewAny, actions: (model: string) => filterActions({ items: [ authPolicies.news.create && ( ), authPolicies.news.viewTrash && ( ), authPolicies.newsCategories.viewAny && ( ), authPolicies.newsTags.viewAny && ( ), ] }) }, { title: str(useTrans("models.classes.contact-count")).ucFirst().value(), model: "contact-counts", icon: ContactIcon, show: auth.policies.contact_counts.viewAny, }, { title: str(useTrans("models.classes.document", { choice: 2 })).ucFirst().value(), model: "documents", activeCondition: [route().current("back.document-categories.*"), route().current('back.document-tags.*')], icon: FileInputIcon, show: auth.policies.documents.viewAny, actions: (model: string) => filterActions({ items: [ authPolicies.documents.create && ( ), authPolicies.documentCategories.create && ( ), authPolicies.documentTags.create && ( ) ] }) }, { title: str(useTrans("models.classes.company", { choice: 2 })).ucFirst().plural().value(), model: "companies", activeCondition: [route().current("back.company-activities.*"), route().current('back.company-sectors.*')], icon: Building2Icon, show: auth.policies.companies.viewAny, actions: (model: string) => filterActions({ items: [ authPolicies.companies.create && ( ), authPolicies.companies.viewTrash && ( ), authPolicies.companyActivities.viewAny && ( ), authPolicies.companySectors.viewAny && ( ), ] }) }, ], }, { title: str(useTrans("enums.user-role.admin")).plural().value(), show: auth.user.role === enumUserRole.conceptor.value, links: [ { title: str(useTrans("models.classes.user")).ucFirst().plural().value(), model: "users", icon: UsersIcon, show: authPolicies.users.viewAny, actions: (model: string) => filterActions({ model }), }, { title: str(useTrans("models.classes.activity-log")).ucFirst().plural().value(), model: "activity-logs", icon: HistoryIcon, show: authPolicies.activityLogs.viewAny, }, { title: str(useTrans("models.classes.page-config", { choice: 2 })).ucFirst().value(), model: "page-configs", show: authPolicies.pageConfigs.viewAny, icon: CogIcon, actions: (model: string) => filterActions({ items: [ authPolicies.pageConfigs.create && ( ), authPolicies.pageConfigs.viewTrash && ( ), ] }) }, { title: useTrans("back.telescope"), link: route().has("telescope") ? route("telescope") : undefined, icon: TelescopeIcon, show: appEnv.mode === "local" && route().has("telescope"), }, ], }, ]; const filterActions = ({ items, model }: { items?: ReactNode[], model?: string }) => { // Set create action by default. if (!items && model) { items = [authPolicies[model].create && ]; } const visibleItems = items ? items.filter(Boolean) : null; return visibleItems && visibleItems.length > 0 ? <>{visibleItems} : null; }; const CustomDropdownMenuItem = ({ route, icon: Icon = Views.CREATION.icon, trans = str(useTrans(`crud.actions.${Views.CREATION.label}`)).ucFirst().value(), }: CustomDropdownMenuItemProps) => { return ( ); }; useEffect(() => { if (prevStateRef.current !== state) { const elmts = document.querySelectorAll(".sidebar-transition"); elmts.forEach((element) => { element.classList.add("animation-transition"); setTimeout(() => { element.classList.remove("animation-transition"); }, 400); }); prevStateRef.current = state; } }, [state]); return ( {navList.map( (navGroup) => navGroup.show && ( {!isCollapsed && ( {navGroup.title} )} {navGroup.links.map((navLink) => { if (navLink.show) { const isActive = route().current(`back.${navLink.model}.*`) || navLink.activeCondition?.some(Boolean); const sidebarButton = ( {navLink.link ? ( {navLink.title} ) : ( {navLink.title} )} ); return {isCollapsed ? ( {sidebarButton}

{navLink.title}

) : ( sidebarButton )} {navLink.model && navLink.actions?.(navLink.model) && ( {navLink.actions(navLink.model)} )}
} })}
), )}
); }