initial commit after split

This commit is contained in:
2026-06-24 01:43:10 +02:00
parent b1d6c0150f
commit 30c11a6dcf
173 changed files with 25380 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
import { Activity, FileText, Folder, Form, LayoutDashboard, MailCheck, Users, type LucideIcon } from "lucide-react";
import campaignModule from "@govoplan/campaign-webui";
import filesModule from "@govoplan/files-webui";
import mailModule from "@govoplan/mail-webui";
import type { AuthInfo, PlatformModuleInfo, PlatformNavItem, PlatformWebModule } from "../types";
import { adminReadScopes, hasAnyScope, hasScope } from "../utils/permissions";
export const shellNavItems: PlatformNavItem[] = [
{ to: "/dashboard", label: "Dashboard", icon: LayoutDashboard, order: 10 }
];
const legacyModules: PlatformWebModule[] = [campaignModule, filesModule, mailModule];
const iconByName: Record<string, LucideIcon> = {
activity: Activity,
campaign: MailCheck,
file: Folder,
files: Folder,
folder: Folder,
form: Form,
mail: MailCheck,
reports: FileText,
users: Users
};
function navFromMetadata(item: PlatformModuleInfo["nav"][number]): PlatformNavItem {
return {
to: item.path,
label: item.label,
icon: item.icon ? iconByName[item.icon] : undefined,
allOf: item.required_all,
anyOf: item.required_any,
order: item.order
};
}
function applyServerMetadata(module: PlatformWebModule, info: PlatformModuleInfo): PlatformWebModule {
const backendNav = info.frontend?.nav.length ? info.frontend.nav : info.nav;
return {
...module,
label: info.name,
version: info.version,
dependencies: info.dependencies,
optionalDependencies: info.optional_dependencies,
navItems: backendNav.length ? backendNav.map(navFromMetadata) : module.navItems
};
}
export function resolveInstalledWebModules(platformModules: PlatformModuleInfo[] | null | undefined): PlatformWebModule[] {
if (!platformModules?.length) return legacyModules;
const localById = new Map(legacyModules.map((module) => [module.id, module]));
return platformModules
.filter((module) => module.enabled)
.map((module) => {
const local = localById.get(module.id);
return local ? applyServerMetadata(local, module) : null;
})
.filter((module): module is PlatformWebModule => module !== null);
}
export function moduleInstalled(moduleId: string, modules: PlatformWebModule[] = legacyModules): boolean {
return modules.some((module) => module.id === moduleId);
}
export function moduleIntegrationEnabled(moduleId: string, dependencyId: string, modules: PlatformWebModule[] = legacyModules): boolean {
const module = modules.find((item) => item.id === moduleId);
if (!module) return false;
return Boolean(module.dependencies?.includes(dependencyId) || (module.optionalDependencies?.includes(dependencyId) && moduleInstalled(dependencyId, modules)));
}
export function navItemsForModules(modules: PlatformWebModule[]): PlatformNavItem[] {
return [...shellNavItems, ...modules.flatMap((module) => module.navItems ?? [])].sort((left, right) => (left.order ?? 100) - (right.order ?? 100));
}
export function visibleNavItems(auth: AuthInfo | null | undefined, modules: PlatformWebModule[] = legacyModules): PlatformNavItem[] {
return navItemsForModules(modules).filter((item) => {
if (item.allOf?.length && !item.allOf.every((scope) => hasScope(auth, scope))) return false;
if (item.anyOf?.length && !hasAnyScope(auth, item.anyOf)) return false;
return true;
});
}
export function firstAccessibleRoute(auth: AuthInfo, modules: PlatformWebModule[] = legacyModules): string {
const preferred = visibleNavItems(auth, modules).find((item) => item.to !== "/dashboard");
if (preferred) return preferred.to;
if (hasAnyScope(auth, adminReadScopes)) return "/admin";
return "/dashboard";
}