Files
Investidor-V2/resources/js/components/structure-geography.tsx

322 lines
13 KiB
TypeScript

import { Link } from '@inertiajs/react';
import TerritorialSources, {
type TerritorialSourceEvidence,
} from './territorial-source-evidence';
import type { GeoJsonObject } from 'geojson';
import InteractiveInvestmentMap from './interactive-investment-map';
export type DocumentedLocation = {
id: string;
name: string;
role: string;
source_reference?: string;
source_date?: string | null;
boundaries?: {
geometry_id: string;
source_reference: string | null;
source_date: string | null;
geometry: GeoJsonObject;
}[];
location_type: string;
path: { id: string; name: string; location_type: string }[];
coordinates: {
geometry_id: string;
source_reference: string | null;
source_date?: string | null;
geometry: { coordinates: [number, number] };
}[];
};
export type StructureGeography = {
source_evidence?: TerritorialSourceEvidence[];
locations: DocumentedLocation[];
members: {
id: string;
dossier_id: string;
title: string;
code: string;
role: string;
validation_status: string;
source_reference: string;
locations: DocumentedLocation[];
}[];
clusters: {
id: string;
dossier_id: string;
title: string;
role: string;
validation_status: string;
source_reference: string;
}[];
chainLinks: {
id: string;
origin: string;
destination: string;
link_type: string;
}[];
chains?: { id: string; code: string; name: string; stage: string }[];
};
const roles: Record<string, string> = {
opportunity: 'Localização declarada da oportunidade',
primary: 'Localização principal',
secondary: 'Localização secundária',
impact: 'Área de impacto',
coverage: 'Área de abrangência documentada',
};
export function LocationList({
locations,
}: {
locations: DocumentedLocation[];
}) {
return (
<div className="space-y-4">
{locations.map((location, index) => (
<article
key={`${location.id}-${location.role}-${index}`}
className="rounded-lg border p-4"
>
<h3 className="font-bold">
{roles[location.role] ?? location.role}: {location.name}
</h3>
<dl className="grid gap-2 sm:grid-cols-2">
{[
['province', 'Província'],
['district', 'Distrito'],
['admin_post', 'Posto administrativo'],
['locality', 'Localidade'],
].map(([type, label]) => (
<div key={type}>
<dt className="font-semibold">{label}</dt>
<dd>
{location.path.find(
(node) => node.location_type === type,
)?.name ?? 'Não documentado'}
</dd>
</div>
))}
</dl>
{location.coordinates.map((point) => (
<p key={point.geometry_id}>
Coordenadas documentadas (
{location.location_type === 'site'
? 'local cadastrado; não confirma a implantação da oportunidade'
: 'referência territorial; não representa a implantação'}
): {point.geometry.coordinates[1]},{' '}
{point.geometry.coordinates[0]} · Fonte:{' '}
{point.source_reference ?? 'Não indicada'}
{' · Data da fonte: '}
{point.source_date ?? 'Não documentada'}
</p>
))}
{!location.coordinates.length && (
<p>Coordenadas não documentadas.</p>
)}
<p className="text-sm">
Fonte da relação:{' '}
{location.source_reference ?? 'Não indicada'}
{' · Data da fonte: '}
{location.source_date ?? 'Não documentada'}
</p>
{location.boundaries?.map((boundary) => (
<p key={boundary.geometry_id}>
Polígono cadastrado · Fonte:{' '}
{boundary.source_reference ?? 'Não indicada'} ·
Data: {boundary.source_date ?? 'Não documentada'}. O
cadastro não certifica limites oficiais.
</p>
))}
{!location.boundaries?.length && (
<p>Polígono não documentado.</p>
)}
<Link
className="underline"
href={`/oportunidades?geo_location_id=${location.id}`}
>
Pesquisar oportunidades neste território
</Link>
</article>
))}
</div>
);
}
export default function StructureGeographyPanel({
geography,
cluster = false,
dossierId,
}: {
geography: StructureGeography;
cluster?: boolean;
dossierId: string;
}) {
const items = cluster
? geography.members.flatMap((member) =>
member.locations.map((location) => ({
location,
title: member.title,
url: `/carteira/${member.dossier_id}`,
})),
)
: geography.locations.map((location) => ({
location,
title: location.name,
url: `/carteira/${dossierId}`,
}));
const points = items
.flatMap((item) =>
item.location.location_type === 'site'
? item.location.coordinates.map((point) => ({
geometry: point.geometry,
properties: {
id: 0,
title: item.title,
location: `${roles[item.location.role] ?? item.location.role} · ${item.location.name}`,
url: item.url,
},
}))
: [],
)
.map((point, index) => ({
...point,
properties: { ...point.properties, id: index + 1 },
}));
return (
<section
className="space-y-4 rounded-xl border p-5"
aria-labelledby={`documented-geography-${dossierId}`}
>
<h2
id={`documented-geography-${dossierId}`}
className="text-xl font-bold"
>
{cluster
? 'Abrangência do cluster e localizações dos membros'
: 'Localização documentada do projecto'}
</h2>
<LocationList locations={geography.locations} />
<TerritorialSources sources={geography.source_evidence} />
{!geography.locations.length && (
<p>
{cluster
? 'A área própria de abrangência do cluster não está documentada em referências territoriais. As localizações dos membros abaixo não definem o seu perímetro.'
: 'Sem associação territorial estruturada. Consulte a descrição de localização no dossier; os níveis desconhecidos permanecem não documentados.'}
</p>
)}
{cluster && (
<p>
O mapa apresenta locais documentados dos membros. Não existe
um ponto único atribuído ao cluster.
</p>
)}
<InteractiveInvestmentMap
points={points}
areas={[
...items.flatMap((item) =>
(item.location.boundaries ?? []).map((area) => ({
...area,
title: `${roles[item.location.role] ?? item.location.role} · ${item.title}`,
url: item.url,
})),
),
...(cluster
? geography.locations.flatMap((location) =>
(location.boundaries ?? []).map((area) => ({
...area,
title: `Abrangência do cluster · ${location.name}`,
url: `/carteira/${dossierId}`,
})),
)
: []),
]}
/>
{!points.length && (
<p>
Sem coordenadas documentadas dos locais. Nenhum ponto foi
estimado.
</p>
)}
{geography.members.map((member) => (
<article key={member.id} className="space-y-3 border-t pt-4">
<h3 className="font-bold">
<Link
className="underline"
href={`/carteira/${member.dossier_id}?from=${dossierId}`}
>
{member.code} · {member.title}
</Link>
</h3>
<p>
Papel: {member.role} · Relação{' '}
{member.validation_status === 'validated'
? 'validada no cadastro'
: 'documental de planeamento; validação pendente'}{' '}
· Fonte: {member.source_reference}
</p>
<LocationList locations={member.locations} />
{!member.locations.length && (
<p>
Localização estruturada do membro não documentada.
</p>
)}
</article>
))}
{geography.clusters.map((item) => (
<p key={item.id}>
Cluster:{' '}
<Link
className="underline"
href={`/carteira/${item.dossier_id}?from=${dossierId}`}
>
{item.title}
</Link>{' '}
·{' '}
{item.validation_status === 'validated'
? 'validada no cadastro'
: 'relação documental de planeamento; validação pendente'}{' '}
· Fonte: {item.source_reference}
</p>
))}
{cluster && (
<div>
<h3 className="font-bold">
Elos da cadeia de valor cadastrados
</h3>
{geography.chains?.map((chain) => (
<p key={chain.id}>
{chain.code} · {chain.name} ·{' '}
{(
{
input: 'Insumos',
production: 'Produção',
processing: 'Transformação',
logistics: 'Logística',
market: 'Mercado',
} as Record<string, string>
)[chain.stage] ?? chain.stage}
</p>
))}
{!geography.chains?.length && (
<p>
Sem elos estruturados cadastrados. As actividades e
lacunas descritas no dossier continuam disponíveis.
</p>
)}
</div>
)}
{!!geography.chainLinks.length && (
<div>
<h3 className="font-bold">
Relações entre elos da cadeia de valor
</h3>
{geography.chainLinks.map((link) => (
<p key={link.id}>
{link.origin} → {link.destination} ·{' '}
{link.link_type}
</p>
))}
</div>
)}
</section>
);
}