Files
Investidor-V2/resources/js/lib/catalogue-context.ts
T
2026-11-15 18:56:39 +02:00

139 lines
4.4 KiB
TypeScript

import { searchTerritoryHref } from './search-territory.ts';
const geography = [
'province_id',
'district_id',
'admin_post_id',
'locality_id',
];
const catalogueKeys: Record<string, string[]> = {
'/carteira': ['q', 'kind', 'sector', 'geo_location_id'],
'/zonas': [
'q',
'category',
'physical_category',
'legal_regime',
'development_status',
'availability_status',
'province',
],
'/oportunidades': [
'q',
'query',
'sector',
'province',
'district',
'geo_location_id',
'include_national',
'cluster_id',
'zee_id',
'zfi_id',
'park_id',
'corridor_id',
'project_id',
'education',
'min_amount',
'max_amount',
'currency',
'maturity_level',
'preparation',
'before',
'sort',
'facets[]',
'view',
'dimension',
'association_type',
'association_target',
'association_state',
'association_purpose',
'chain_id',
'subsector_id',
],
};
function family(path: string): string {
if (path.startsWith('/management/portfolio')) return '/carteira';
if (path.startsWith('/management/investment/zones')) return '/zonas';
if (path === '/educacao' || path.startsWith('/educacao/'))
return '/oportunidades';
return (
Object.keys(catalogueKeys).find(
(route) => path === route || path.startsWith(route + '/'),
) ?? ''
);
}
/** Explicit URL criteria only. No ambient storage, dossier IDs or nested return URLs. */
export function catalogueQuery(path: string, query: string): URLSearchParams {
const group = family(path);
const allowed = new Set(
group
? [...geography, ...catalogueKeys[group], 'page', 'per_page']
: [],
);
const result = new URLSearchParams();
for (const [key, value] of new URLSearchParams(query)) {
if (!allowed.has(key) || !value || value.length > 2000) continue;
if (
['page', 'per_page'].includes(key) &&
(!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(Number(value)))
)
continue;
if (!key.endsWith('[]')) result.delete(key);
result.append(key, value);
}
return result;
}
/** Only a query string is carried; the return destination is always local. */
export function catalogueDetailHref(
path: string,
query: string,
pageUrl?: string,
): string {
const target = new URL(path, 'https://portal.invalid');
if (target.origin !== 'https://portal.invalid') return path;
if (pageUrl) {
const page = new URL(
pageUrl,
'https://portal.invalid',
).searchParams.get('page');
if (
page &&
/^[1-9]\d*$/.test(page) &&
Number.isSafeInteger(Number(page))
) {
const params = new URLSearchParams(query);
params.set('page', page);
query = params.toString();
}
}
const selected = catalogueQuery(target.pathname, query).toString();
if (selected) target.searchParams.set('catalogue_query', selected);
return target.pathname + target.search + target.hash;
}
export function catalogueReturnHref(base: string, pageUrl: string): string {
const source = new URL(pageUrl, 'https://portal.invalid');
const target = new URL(base, 'https://portal.invalid');
if (target.origin !== 'https://portal.invalid') return base;
const query = source.searchParams.get('catalogue_query');
const selected = catalogueQuery(target.pathname, query ?? '');
let changedAncestor = false;
for (const key of geography) {
if (target.searchParams.has(key))
changedAncestor ||=
target.searchParams.get(key) !== selected.get(key);
else if (changedAncestor) selected.delete(key);
}
const explicit = new Set(target.searchParams.keys());
for (const [key, value] of selected) {
// Text, kind, price and page belong to their original catalogue only.
if (
family(source.pathname) !== family(target.pathname) &&
!geography.includes(key)
)
continue;
if (!explicit.has(key)) target.searchParams.append(key, value);
}
return searchTerritoryHref(
target.pathname + target.search + target.hash,
pageUrl,
);
}