2026-05-10 09:46:07 +12:00
|
|
|
import { hasModuleAccess, hasPermission, type AppSession } from '$lib/session';
|
|
|
|
|
|
|
|
|
|
export type WorkspaceRole = 'admin' | 'operations' | 'full' | 'client' | 'unknown';
|
|
|
|
|
|
|
|
|
|
type RouteAccessRule = {
|
|
|
|
|
path: string;
|
|
|
|
|
roles: WorkspaceRole[];
|
|
|
|
|
matches: (pathname: string) => boolean;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function hasPathPrefix(pathname: string, prefix: string) {
|
|
|
|
|
return pathname === prefix || pathname.startsWith(`${prefix}/`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function canAccessWorkspaceArea(
|
|
|
|
|
session: AppSession | null | undefined,
|
|
|
|
|
moduleKey: string,
|
|
|
|
|
permissionKeys: string[],
|
|
|
|
|
minimumLevel: 'view' | 'edit' | 'manage' = 'view'
|
|
|
|
|
) {
|
|
|
|
|
if (!session) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (session.role === 'internal') {
|
|
|
|
|
return permissionKeys.some((permissionKey) => hasPermission(session, permissionKey));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hasModuleAccess(session, moduleKey, minimumLevel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getWorkspaceRole(session: AppSession | null | undefined): WorkspaceRole {
|
|
|
|
|
if (!session) {
|
|
|
|
|
return 'unknown';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (session.role === 'admin') {
|
|
|
|
|
return 'admin';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (session.role !== 'internal') {
|
|
|
|
|
return 'client';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (session.role_name === 'Admin') {
|
|
|
|
|
return 'admin';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (session.role_name === 'Operations') {
|
|
|
|
|
return 'operations';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (session.role_name === 'Full Access') {
|
|
|
|
|
return 'full';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 'unknown';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function canOpenDashboard(session: AppSession | null | undefined) {
|
|
|
|
|
return canAccessWorkspaceArea(session, 'dashboard', ['view_dashboard']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function canOpenRawMaterials(session: AppSession | null | undefined) {
|
|
|
|
|
return canAccessWorkspaceArea(session, 'raw_materials', ['view_raw_materials', 'edit_raw_materials']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function canOpenMixMaster(session: AppSession | null | undefined) {
|
|
|
|
|
return canAccessWorkspaceArea(session, 'mix_master', ['view_mixes', 'edit_mixes']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function canCreateMixWorksheet(session: AppSession | null | undefined) {
|
|
|
|
|
return canAccessWorkspaceArea(session, 'mix_master', ['edit_mixes'], 'edit');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function canOpenMixCalculator(session: AppSession | null | undefined) {
|
|
|
|
|
return canAccessWorkspaceArea(session, 'mix_calculator', ['view_mix_calculator', 'use_mix_calculator']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function canCreateMixSession(session: AppSession | null | undefined) {
|
|
|
|
|
return canAccessWorkspaceArea(session, 'mix_calculator', ['use_mix_calculator', 'save_mix_calculator_session'], 'edit');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function canOpenProducts(session: AppSession | null | undefined) {
|
|
|
|
|
return canAccessWorkspaceArea(session, 'products', ['view_products', 'edit_products']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function canOpenScenarios(session: AppSession | null | undefined) {
|
|
|
|
|
return !!session && hasModuleAccess(session, 'scenarios');
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-31 20:19:44 +12:00
|
|
|
export function canOpenThroughput(session: AppSession | null | undefined) {
|
|
|
|
|
return canAccessWorkspaceArea(session, 'operations_throughput', ['view_throughput', 'edit_throughput']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function canEditThroughput(session: AppSession | null | undefined) {
|
|
|
|
|
return canAccessWorkspaceArea(session, 'operations_throughput', ['edit_throughput'], 'edit');
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 09:46:07 +12:00
|
|
|
export function canOpenReporting(session: AppSession | null | undefined) {
|
|
|
|
|
return canOpenProducts(session);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function canOpenSettings(session: AppSession | null | undefined) {
|
|
|
|
|
if (!session) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return session.role === 'internal'
|
|
|
|
|
? hasPermission(session, 'view_settings') || hasPermission(session, 'edit_settings')
|
|
|
|
|
: true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function canOpenClientAccess(session: AppSession | null | undefined) {
|
|
|
|
|
return !!session && hasModuleAccess(session, 'client_access', 'manage');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const routeAccessRules: RouteAccessRule[] = [
|
|
|
|
|
{ path: '/', roles: ['admin', 'full', 'client'], matches: (pathname) => pathname === '/' },
|
|
|
|
|
{
|
|
|
|
|
path: '/mix-calculator',
|
|
|
|
|
roles: ['admin', 'operations', 'full', 'client'],
|
|
|
|
|
matches: (pathname) => hasPathPrefix(pathname, '/mix-calculator')
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: '/raw-materials',
|
|
|
|
|
roles: ['admin', 'full', 'client'],
|
|
|
|
|
matches: (pathname) => hasPathPrefix(pathname, '/raw-materials')
|
|
|
|
|
},
|
|
|
|
|
{ path: '/mixes', roles: ['admin', 'full', 'client'], matches: (pathname) => hasPathPrefix(pathname, '/mixes') },
|
|
|
|
|
{ path: '/products', roles: ['admin', 'full', 'client'], matches: (pathname) => hasPathPrefix(pathname, '/products') },
|
|
|
|
|
{ path: '/reporting', roles: ['admin', 'full', 'client'], matches: (pathname) => hasPathPrefix(pathname, '/reporting') },
|
|
|
|
|
{ path: '/scenarios', roles: ['admin', 'full', 'client'], matches: (pathname) => hasPathPrefix(pathname, '/scenarios') },
|
|
|
|
|
{
|
|
|
|
|
path: '/settings',
|
|
|
|
|
roles: ['admin', 'full', 'operations', 'client'],
|
|
|
|
|
matches: (pathname) => hasPathPrefix(pathname, '/settings')
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: '/client-access',
|
|
|
|
|
roles: ['admin', 'client'],
|
|
|
|
|
matches: (pathname) => hasPathPrefix(pathname, '/client-access')
|
2026-05-31 20:19:44 +12:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: '/throughput',
|
|
|
|
|
roles: ['admin', 'operations', 'full', 'client'],
|
|
|
|
|
matches: (pathname) => hasPathPrefix(pathname, '/throughput')
|
2026-05-10 09:46:07 +12:00
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
export function getDefaultRouteForRole(session: AppSession | null | undefined) {
|
|
|
|
|
const role = getWorkspaceRole(session);
|
|
|
|
|
|
|
|
|
|
if (role === 'operations') {
|
2026-05-31 20:19:44 +12:00
|
|
|
return '/mix-calculator';
|
2026-05-10 09:46:07 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (role === 'admin' || role === 'full' || role === 'client') {
|
|
|
|
|
if (canOpenDashboard(session)) return '/';
|
2026-05-31 20:19:44 +12:00
|
|
|
if (canOpenMixCalculator(session)) return '/mix-calculator';
|
2026-05-10 09:46:07 +12:00
|
|
|
if (canOpenRawMaterials(session)) return '/raw-materials';
|
|
|
|
|
if (canOpenMixMaster(session)) return '/mixes';
|
|
|
|
|
if (canOpenProducts(session)) return '/products';
|
|
|
|
|
if (canOpenScenarios(session)) return '/scenarios';
|
|
|
|
|
if (canOpenSettings(session)) return '/settings';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return '/';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function canAccessRoute(session: AppSession | null | undefined, pathname: string) {
|
|
|
|
|
const rule = routeAccessRules.find((candidate) => candidate.matches(pathname));
|
|
|
|
|
if (!rule) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const role = getWorkspaceRole(session);
|
|
|
|
|
if (!rule.roles.includes(role)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pathname === '/') return canOpenDashboard(session);
|
|
|
|
|
if (pathname.startsWith('/mix-calculator')) return canOpenMixCalculator(session);
|
|
|
|
|
if (pathname.startsWith('/raw-materials')) return canOpenRawMaterials(session);
|
|
|
|
|
if (pathname.startsWith('/mixes')) return canOpenMixMaster(session);
|
|
|
|
|
if (pathname.startsWith('/products')) return canOpenProducts(session);
|
|
|
|
|
if (pathname.startsWith('/scenarios')) return canOpenScenarios(session);
|
|
|
|
|
if (pathname.startsWith('/reporting')) return canOpenReporting(session);
|
|
|
|
|
if (pathname.startsWith('/settings')) return canOpenSettings(session);
|
|
|
|
|
if (pathname.startsWith('/client-access')) return canOpenClientAccess(session);
|
2026-05-31 20:19:44 +12:00
|
|
|
if (pathname.startsWith('/throughput')) return canOpenThroughput(session);
|
2026-05-10 09:46:07 +12:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function canUseWorkspaceSearch(session: AppSession | null | undefined) {
|
|
|
|
|
return (
|
|
|
|
|
canOpenDashboard(session) ||
|
|
|
|
|
canOpenRawMaterials(session) ||
|
|
|
|
|
canOpenMixMaster(session) ||
|
|
|
|
|
canOpenMixCalculator(session) ||
|
|
|
|
|
canOpenProducts(session) ||
|
|
|
|
|
canOpenScenarios(session)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const getWorkspaceHomeHref = getDefaultRouteForRole;
|
|
|
|
|
export const isWorkspaceRouteAllowed = canAccessRoute;
|