Files
Investidor-V2/resources/js/lib/portal-navigation.ts
T
paulomassigue 14ad2f60c5
tests / ci (push) Canceled after 0s
header:navbar e footer
2026-10-19 13:07:16 +02:00

578 lines
21 KiB
TypeScript

import type { ProfileAccess } from '@/types/profile-access';
import type { OpportunityManagementAccess } from '@/types/opportunity-management';
export type PortalNavItem = {
id: string;
label: string;
href?: string;
children?: PortalNavItem[];
};
export type PortalNavAccess = {
authenticated: boolean;
manager: boolean;
businesses?: boolean;
businessReviewUrl?: string | null;
education?: boolean;
ai?: boolean;
legacyContent?: boolean;
profileAccess?: ProfileAccess | null;
opportunityManagement?: OpportunityManagementAccess | null;
context?: {
id: string;
simulator?: string | null;
land?: string | null;
desk?: string | null;
guidance?: string | null;
};
};
const link = (id: string, label: string, href: string): PortalNavItem => ({
id,
label,
href,
});
/** One route catalogue for the public shell and authenticated pages. */
export function legacyPortalNavigation(
access: PortalNavAccess,
): PortalNavItem[] {
const opportunities = access.manager ? access.opportunityManagement : null;
const groups: PortalNavItem[] = [
{
id: 'explore',
label: 'Explorar investimentos',
children: [
link('opportunities', 'Oportunidades', '/oportunidades'),
link('sectors', 'Sectores', '/sectores'),
...(access.legacyContent
? [
link(
'legacy-agronegocio',
'Explorar agronegócio',
'/explorar/agronegocio',
),
]
: []),
{
id: 'structures',
label: 'Carteira e estruturas',
children: [
link(
'portfolio',
'Carteira de investimento',
'/carteira',
),
link(
'projects',
'Projectos âncora',
'/carteira?kind=project',
),
link('clusters', 'Clusters', '/carteira?kind=cluster'),
link(
'corridors',
'Corredores',
'/carteira?kind=corridor',
),
],
},
{
id: 'zones',
label: 'Zonas e parques',
children: [
link('all-zones', 'Todas as zonas', '/zonas'),
link('zee', 'ZEE', '/zonas?category=zee'),
link('zfi', 'ZFI', '/zonas?category=zfi'),
link(
'parks',
'Parques industriais',
'/zonas?physical_category=industrial_park',
),
],
},
],
},
{
id: 'prepare',
label: 'Preparar o projecto',
children: [
link('roadmap', 'Como investir', '/como-investir'),
link('dossier', 'Preparar dossier', '/simulador-fiscal'),
link('simulator', 'Simulador', '/simulador-fiscal'),
link('land', 'Terra e DUAT', '/terra-duat'),
...(access.education
? [
link(
'education',
'Educação e competências',
'/educacao',
),
]
: []),
],
},
{
id: 'support',
label: 'Serviços e apoio',
children: [
link('desk', 'Balcão Virtual', '/balcao-virtual'),
link(
'help',
'Apoio ao investidor',
access.context?.guidance || '/apoio',
),
link('documents', 'Documentos e formulários', '/documentos'),
link('law', 'Legislação', '/legislacao'),
],
},
{
id: 'partnerships',
label: 'Parcerias',
children: [
link('exchange', 'Bolsa de Parcerias', '/parcerias'),
link('businesses', 'Negócios e participações', '/negocios'),
...(access.authenticated
? [
link(
'my-partnerships',
'As minhas parcerias',
'/parcerias/painel',
),
]
: []),
...(access.authenticated && access.businesses
? [
link(
'my-businesses',
'Os meus negócios',
'/investidor/negocios',
),
]
: []),
...(access.authenticated &&
access.businesses &&
access.businessReviewUrl
? [
link(
'review-businesses',
'Revisão de negócios',
access.businessReviewUrl,
),
]
: []),
],
},
{
id: 'account',
label: 'A minha área',
children: access.authenticated
? [
link(
'dashboard',
opportunities
? 'Painel da gestão'
: 'Dossiers e percursos',
opportunities ? '/dashboard' : '/investidor',
),
link(
'profile',
'Perfil e empresas',
'/investidor/profile',
),
link('dossiers', 'Dossiers e percursos', '/investidor'),
link(
'requests',
'Pedidos e tarefas de acompanhamento',
'/balcao-virtual',
),
link(
'interests',
'Manifestações e favoritos',
'/investidor/manifestacoes',
),
link(
'notifications',
'Notificações',
'/investidor/notifications',
),
]
: [
link('login', 'Entrar', '/login'),
link('register', 'Criar conta', '/register'),
],
},
];
if (access.manager)
groups.push({
id: 'management',
label: 'Gestão',
children: [
link(
'manage-interests',
'Pedidos de interesse',
'/management/interests',
),
link('crm', 'CRM interno', '/management/crm/leads'),
...(opportunities
? [
link(
'manage-opportunities',
opportunities.manage.label,
opportunities.manage.href,
),
...(opportunities.installed
? [
link(
'create-opportunity',
opportunities.create.label,
opportunities.create.href,
),
]
: []),
]
: []),
{
id: 'manage-catalogue',
label: 'Catálogo de investimento',
children: [
link(
'manage-sectors',
'Sectores',
'/management/sectors',
),
...(!opportunities
? [
link(
'manage-opportunities',
'Oportunidades',
'/management/opportunities',
),
]
: []),
link(
'manage-projects',
'Projectos',
'/management/projects',
),
link(
'manage-clusters',
'Clusters',
'/management/clusters',
),
],
},
link(
'manage-portfolio',
'Carteira e estruturas',
'/management/portfolio',
),
link(
'manage-documents',
'Acervo documental',
'/management/portal-services',
),
link(
'manage-zones',
'Zonas e parques',
'/management/investment/zones',
),
link(
'manage-territory',
'Associações territoriais',
'/management/territory-associations',
),
link(
'manage-references',
'Referências e formulários',
'/management/investment/references',
),
link(
'desk-installation',
'Estado do Balcão e orientação',
'/management/balcao-install',
),
link(
'exchange-installation',
'Estado da Bolsa de Parcerias',
'/management/partnerships-install',
),
...(access.ai
? [
link(
'manage-ai',
'Assistência interna',
'/management/ai',
),
]
: []),
...(access.education
? [
link(
'manage-education',
'Educação e competências',
'/management/education',
),
]
: []),
],
});
if (access.authenticated && access.profileAccess) {
const profile = access.profileAccess;
groups.push({
id: 'profile-opportunities',
label: 'Perfil e oportunidades',
children: [
...(profile.mine
? [
link(
'my-opportunities',
'Minhas oportunidades',
profile.mine,
),
]
: []),
...(profile.create
? [
link(
'new-my-opportunity',
'Nova oportunidade',
profile.create,
),
]
: []),
link(
'promoter-profile',
profile.mine
? 'Autorizações de Promotor'
: 'Solicitar perfil de Promotor',
profile.request,
),
...(!profile.mine && profile.review
? [
link(
'profile-review',
'Revisão de oportunidades',
profile.review,
),
]
: []),
...(profile.audit
? [
link(
'profile-audit',
'Auditoria de acessos',
profile.audit,
),
]
: []),
...(profile.administration
? [
link(
'profile-administration',
'Gestão de perfis',
profile.administration,
),
]
: []),
],
});
}
return groups;
}
export function currentNavigationGroup(
groups: PortalNavItem[],
url: string,
): string | null {
const current = new URL(url, 'https://portal.invalid');
let match: { group: string; score: number } | null = null;
const visit = (items: PortalNavItem[], group: string) =>
items.forEach((item) => {
if (item.children) visit(item.children, group);
if (!item.href) return;
const target = new URL(item.href, current.origin);
if (target.hash && target.hash !== current.hash) return;
if (
current.pathname !== target.pathname &&
!current.pathname.startsWith(target.pathname + '/')
)
return;
for (const [key, value] of target.searchParams)
if (current.searchParams.get(key) !== value) return;
const score =
target.pathname.length + target.searchParams.size * 100;
if (!match || score > match.score) match = { group, score };
});
for (const group of groups) visit(group.children ?? [], group.id);
return (match as { group: string; score: number } | null)?.group ?? null;
}
/** Select one destination, preferring a matching catalogue filter. */
export function currentNavigationItem(
groups: PortalNavItem[],
url: string,
): string | null {
const current = new URL(url, 'https://portal.invalid');
const best = { id: null as string | null, score: -1 };
const visit = (items: PortalNavItem[]) => {
for (const item of items) {
if (item.children) visit(item.children);
if (!item.href) continue;
const target = new URL(item.href, current.origin);
if (target.hash && target.hash !== current.hash) continue;
if (current.pathname !== target.pathname) continue;
if (
[...target.searchParams].some(
([key, value]) => current.searchParams.get(key) !== value,
)
)
continue;
const score = target.searchParams.size;
if (score > best.score) {
best.id = item.id;
best.score = score;
}
}
};
for (const group of groups) visit(group.children ?? []);
return best.id;
}
/** Investor organization; existing capability filters still determine private destinations. */
export function portalNavigation(access: PortalNavAccess): PortalNavItem[] {
const previous = legacyPortalNavigation(access);
return uniqueNavigationDestinations([
link('home', 'Início', '/'),
{
id: 'discovery',
label: 'Descoberta',
children: [
link(
'why',
'Porquê investir em Moçambique',
'/porque-investir',
),
link(
'opportunities',
'Oportunidades de investimento',
'/oportunidades',
),
link('sectors', 'Sectores de investimento', '/sectores'),
link('locations', 'Localizações', '/oportunidades?view=map'),
],
},
{
id: 'realization',
label: 'Realização',
children: [
link('start', 'Iniciar investimento', '/investimentos/iniciar'),
link(
'simulator',
'Incentivos e simulador fiscal',
access.context?.simulator || '/simulador-fiscal',
),
link(
'licensing',
'Licenciamento',
access.context
? `/investimentos/${access.context.id}#S08`
: '/investimentos/iniciar?step=S08',
),
link(
'land',
'Acesso à terra',
access.context?.land || '/terra-duat',
),
],
},
{
id: 'support',
label: 'Suporte',
children: [
link(
'services',
'Serviços ao investidor',
access.context?.desk || '/balcao-virtual',
),
link(
'followup',
'Acompanhamento do projecto',
access.context
? `/investimentos/${access.context.id}`
: '/investimentos',
),
link(
'help',
'Apoio ao investidor',
access.context?.guidance || '/apoio',
),
],
},
...previous.filter((group) =>
['account', 'management', 'profile-opportunities'].includes(
group.id,
),
),
...(access.authenticated &&
access.businesses &&
access.businessReviewUrl
? [
{
id: 'business-work',
label: 'Revisão de negócios',
children: [
link(
'review-businesses',
'Revisão de negócios',
access.businessReviewUrl,
),
],
},
]
: []),
...(access.authenticated
? [
{
id: 'personal-activity',
label: 'A minha atividade',
children: [
link(
'my-partnerships',
'As minhas parcerias',
'/parcerias/painel',
),
...(access.businesses
? [
link(
'my-businesses',
'Os meus negócios',
'/investidor/negocios',
),
]
: []),
],
},
]
: []),
]);
}
/** One menu entry per exact destination; filters and anchors retain their meaning. */
export function uniqueNavigationDestinations(
groups: PortalNavItem[],
): PortalNavItem[] {
const seen = new Set<string>();
const visit = (items: PortalNavItem[]): PortalNavItem[] =>
items.flatMap((item) => {
const children = item.children ? visit(item.children) : undefined;
if (children && !children.length && !item.href) return [];
if (item.href) {
const url = new URL(item.href, 'https://portal.invalid');
url.searchParams.sort();
const identity = url.href;
if (seen.has(identity)) return [];
seen.add(identity);
}
return [{ ...item, ...(children ? { children } : {}) }];
});
return visit(groups);
}