// * 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)}
)}
}
})}
),
)}
);
}