147 lines
3.7 KiB
TypeScript
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;
|
|
};
|