322 lines
13 KiB
TypeScript
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>
|
|
);
|
|
}
|