import { Link, usePage } from "@inertiajs/react";
import {
ChevronDownIcon,
HomeIcon,
MoreHorizontalIcon,
SquareArrowOutUpRightIcon,
TelescopeIcon,
} from "lucide-react";
import { type ElementType, type ReactNode, useEffect, useRef } from "react";
import ApplicationLogo from "@/back/Components/ApplicationLogo";
import { SidebarRegistry } from "@/back/Components/Layout/Sidebar/SidebarRegistry";
import Views from "@/back/lib/actions";
import type { AuthProp, ConfigBackend, UserModel } from "@/back/types/global";
import { Button } from "@/components/ui/button";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from "@/components/ui/sidebar";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import str from "@/hooks/use-string";
import trans from "@/hooks/use-translations";
export interface NavListLinksProps {
/** 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;
}
export type NavListProps = {
/** Title of the group. */
title: string;
/** Condition to set the visibility of the group. */
show: boolean;
/** Links presents in the group. */
links: NavListLinksProps[];
}[];
/**
* Get sidenav actions.
*
* @param ReactNode items
* @param string model
* @return
*/
export const filterActions = ({
auth,
configBackend,
items,
model,
}: {
auth: AuthProp;
configBackend: ConfigBackend;
items?: ReactNode[];
model?: string;
}): ReactNode => {
let computedItems = items;
const transDefaultSideAction = str(
trans(`uwil-backend::crud.actions.${Views.CREATION.label}`),
)
.ucFirst()
.value();
// Set create action by default.
if (!computedItems && model) {
const modelCamelCase = str(model).camelCase().value();
computedItems = [
auth.policies[modelCamelCase].create && (
),
];
}
const visibleItems = computedItems ? computedItems.filter(Boolean) : null;
return visibleItems && visibleItems.length > 0 ? visibleItems : null;
};
export default function AppSidebar() {
const { appEnv, auth, configBackend, enumUserRole } = usePage()
.props as unknown as {
appEnv: AppEnv;
auth: AuthProp;
configBackend: ConfigBackend;
enumUserRole: UserModel["role"][];
};
const { state } = useSidebar();
const prevStateRef = useRef(state);
const isConceptor = enumUserRole.find((role) => role.name === "conceptor");
const isCollapsed = state === "collapsed";
// Translations.
const transPages = trans("uwil-backend::back.pages");
const transDashboard = trans("uwil-backend::back.dashboard");
const transWebsite = trans("uwil-backend::back.your_website");
const transModules = trans("uwil-backend::back.modules");
const transAdmin = trans("uwil-backend::enums.user-role.admin");
const transTelescope = trans("uwil-backend::back.telescope");
const navList: NavListProps = [
{
title: transPages,
show: true,
links: [
{
title: transDashboard,
model: "dashboard",
show: true,
icon: HomeIcon,
},
{
title: transWebsite,
link: "/",
show: true,
icon: SquareArrowOutUpRightIcon,
},
...SidebarRegistry.getAll(auth, configBackend, "pages"),
],
},
{
title: transModules,
show: SidebarRegistry.getAll(auth, configBackend, "modules").length > 0,
links: SidebarRegistry.getAll(auth, configBackend, "modules"),
},
{
title: str(transAdmin).plural().value(),
show: auth.user.role.value === isConceptor?.value,
links: [
...SidebarRegistry.getAll(auth, configBackend, "admin"),
{
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");
const timeoutIds: NodeJS.Timeout[] = [];
elmts.forEach((element) => {
element.classList.add("animation-transition");
const id = setTimeout(() => {
element.classList.remove("animation-transition");
}, 400);
timeoutIds.push(id);
});
prevStateRef.current = state;
return () => {
timeoutIds.forEach((id) => {
clearTimeout(id);
});
};
}
}, [state]);
return (
{navList.map(
(navGroup) =>
navGroup.show && (
{!isCollapsed && (
{navGroup.title}
)}
{navGroup.links
.filter((navLink) => navLink.show)
.map((navLink) => {
const isActive = navLink.model
? route().current(
`${configBackend.routes.name}${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)}
)}
);
})}
),
)}
);
}