FactuGES_ERP/apps/web/src/components/module-routes.tsx
2026-06-02 18:40:23 +02:00

147 lines
3.7 KiB
TypeScript

import type {
IModuleClient,
ModuleClientParams,
ModuleRouteLayout,
ModuleRouteObject,
} from "@erp/core/client";
import type { ReactNode } from "react";
import type { RouteObject } from "react-router-dom";
import { RequireAuthRouteGuard } from "@/routes/require-auth-guard";
interface BuildModuleRoutesParams {
modules: IModuleClient[];
params: ModuleClientParams;
layout: ModuleRouteLayout;
}
interface NormalizeRouteParams {
route: ModuleRouteObject;
module: IModuleClient;
targetLayout: ModuleRouteLayout;
inheritedLayout?: ModuleRouteLayout;
inheritedProtected?: boolean;
}
const DEFAULT_LAYOUT: ModuleRouteLayout = "app-sidebar";
const resolveRouteLayout = (
route: ModuleRouteObject,
module: IModuleClient,
inheritedLayout?: ModuleRouteLayout
): ModuleRouteLayout => {
return route.handle?.layout ?? inheritedLayout ?? module.layout ?? DEFAULT_LAYOUT;
};
const resolveRouteProtected = (
route: ModuleRouteObject,
module: IModuleClient,
inheritedProtected?: boolean
): boolean => {
return route.handle?.protected ?? inheritedProtected ?? module.protected ?? false;
};
const wrapProtectedElement = (element: ReactNode, isProtected: boolean): ReactNode => {
if (!isProtected) {
return element;
}
return <RequireAuthRouteGuard>{element}</RequireAuthRouteGuard>;
};
const normalizeRoute = ({
route,
module,
targetLayout,
inheritedLayout,
inheritedProtected,
}: NormalizeRouteParams): RouteObject | null => {
const routeLayout = resolveRouteLayout(route, module, inheritedLayout);
const routeProtected = resolveRouteProtected(route, module, inheritedProtected);
const normalizedChildren = route.children
?.map((child) =>
normalizeRoute({
route: child,
module,
targetLayout,
inheritedLayout: routeLayout,
inheritedProtected: routeProtected,
})
)
.filter((child): child is RouteObject => child !== null);
const hasMatchingLayout = routeLayout === targetLayout;
const hasChildren = Boolean(normalizedChildren?.length);
const hasElement = route.element !== undefined || route.Component !== undefined;
if (!(hasMatchingLayout || hasChildren)) {
return null;
}
if (!hasMatchingLayout && hasChildren) {
return {
path: route.path,
index: route.index,
children: normalizedChildren,
} as RouteObject;
}
const { children: _children, handle, element, ...rest } = route;
const normalizedRoute = {
...rest,
handle,
element: element ? wrapProtectedElement(element, routeProtected) : undefined,
children: normalizedChildren,
} as RouteObject;
if (!(hasElement || hasChildren)) {
console.warn(
`[ModuleRoutes] Ruta sin element ni children válidos: "${String(route.path ?? "index")}"`
);
return null;
}
return normalizedRoute;
};
export const buildModuleRoutes = ({
modules,
params,
layout,
}: BuildModuleRoutesParams): RouteObject[] => {
const routes: RouteObject[] = [];
for (const module of modules) {
if (typeof module.routes !== "function") {
console.warn(`[ModuleRoutes] El módulo "${module.name}" no define una función routes().`);
continue;
}
const moduleRoutes = module.routes(params);
if (!Array.isArray(moduleRoutes)) {
console.error(
`[ModuleRoutes] El módulo "${module.name}" debe devolver un ModuleRouteObject[], pero devolvió:`,
moduleRoutes
);
continue;
}
for (const route of moduleRoutes) {
const normalizedRoute = normalizeRoute({
route,
module,
targetLayout: layout,
});
if (normalizedRoute) {
routes.push(normalizedRoute);
}
}
}
return routes;
};