// * 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). */ // eslint-disable-next-line no-unused-vars actions?: (model: string) => ReactNode; }>; }>; type CustomDropdownMenuItemProps = { /** Link of the action. */ targetRoute: string; /** Lucide icon of the action. */ icon: ElementType; /** Translation of the action. */ trans: string; }; /** * Sidenav action button. * * @param string targetRoute * @param ElementType icon * @param string trans * @return ReactNode */ function CustomDropdownMenuItem({ targetRoute, icon: Icon, trans, }: CustomDropdownMenuItemProps): ReactNode { return ( ); } 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"; // Translations. const transDefaultSideAction = str( useTrans(`crud.actions.${Views.CREATION.label}`), ) .ucFirst() .value(); const transPages = useTrans("back.pages"); const transDashboard = useTrans("back.dashboard"); const transWebsite = useTrans("back.your_website"); const transDynamicPages = useTrans("models.classes.dynamic-page", { choice: 2, }); const transModules = useTrans("back.modules"); const transNews = useTrans("models.classes.news", { choice: 2 }); const transNewsCategories = useTrans("models.classes.news-category", { choice: 2, }); const transNewsTags = useTrans("models.classes.news-tag", { choice: 2 }); const transContactCount = useTrans("models.classes.contact-count"); const transDocuments = useTrans("models.classes.document", { choice: 2 }); const transDocumentCategories = useTrans( "models.classes.document-category", { choice: 2 }, ); const transDocumentTags = useTrans("models.classes.document-tag", { choice: 2, }); const transCompanies = useTrans("models.classes.company", { choice: 2 }); const transCompanyActivities = useTrans("models.classes.company-activity", { choice: 2, }); const transCompanySectors = useTrans("models.classes.company-sector", { choice: 2, }); const transAdmin = useTrans("enums.user-role.admin"); const transUsers = useTrans("models.classes.user", { choice: 2 }); const transActivityLogs = useTrans("models.classes.activity-log"); const transPageConfigs = useTrans("models.classes.page-config", { choice: 2, }); const transArchives = str(useTrans("back.archives")).ucFirst().value(); const transTelescope = useTrans("back.telescope"); /** * Get sidenav actions. * * @param ReactNode items * @param string model * @return */ const filterActions = ({ items, model, }: { items?: ReactNode[]; model?: string; }): ReactNode => { let computedItems = items; // Set create action by default. if (!computedItems && model) { computedItems = [ authPolicies[model].create && ( ), ]; } const visibleItems = computedItems ? computedItems.filter(Boolean) : null; return visibleItems && visibleItems.length > 0 ? visibleItems : null; }; const navList: NavListProps = [ { title: transPages, show: true, links: [ { title: transDashboard, model: "dashboard", show: true, icon: HomeIcon, }, { title: transWebsite, link: "/", show: true, icon: SquareArrowOutUpRightIcon, }, { title: str(transDynamicPages).ucFirst().value(), model: "dynamic-pages", show: authPolicies.dynamicPages.viewAny, icon: StickyNoteIcon, actions: (model: string) => filterActions({ items: [ authPolicies.dynamicPages.create && ( ), authPolicies.dynamicPages.viewTrash && ( ), ], }), }, ], }, { title: transModules, show: authPolicies.news.viewAny || auth.policies.contact_counts.viewAny, links: [ { title: str(transNews).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(transContactCount).ucFirst().value(), model: "contact-counts", icon: ContactIcon, show: auth.policies.contact_counts.viewAny, }, { title: str(transDocuments).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(transCompanies).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(transAdmin).plural().value(), show: auth.user.role === enumUserRole.conceptor.value, links: [ { title: str(transUsers).ucFirst().plural().value(), model: "users", icon: UsersIcon, show: authPolicies.users.viewAny, actions: (model: string) => filterActions({ model }), }, { title: str(transActivityLogs).ucFirst().plural().value(), model: "activity-logs", icon: HistoryIcon, show: authPolicies.activityLogs.viewAny, }, { title: str(transPageConfigs).ucFirst().value(), model: "page-configs", show: authPolicies.pageConfigs.viewAny, icon: CogIcon, actions: (model: string) => filterActions({ items: [ authPolicies.pageConfigs.create && ( ), authPolicies.pageConfigs.viewTrash && ( ), ], }), }, { title: transTelescope, link: route().has("telescope") ? route("telescope") : undefined, icon: TelescopeIcon, show: appEnv.mode === "local" && route().has("telescope"), }, ], }, ]; 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 .filter( (navLink) => navLink.show, ) .map((navLink) => { 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, )} )}
); })}
), )}
); }