101 lines
3.7 KiB
TypeScript
101 lines
3.7 KiB
TypeScript
import SearchState from '@/components/search-state';
|
|
import { useEffect, useState } from 'react';
|
|
import TerritoryGeometryPreview from './territory-geometry-preview';
|
|
import InteractiveInvestmentMap, {
|
|
type InvestmentMapFeature,
|
|
type InvestmentMapArea,
|
|
} from './interactive-investment-map';
|
|
|
|
export default function InvestmentMapPanel({
|
|
filters,
|
|
territoryId,
|
|
}: {
|
|
filters: Record<string, string | number>;
|
|
territoryId?: string;
|
|
}) {
|
|
const key = JSON.stringify(filters);
|
|
const [points, setPoints] = useState<InvestmentMapFeature[] | null>(null);
|
|
const [missing, setMissing] = useState<
|
|
{ id: number; title: string; url: string }[]
|
|
>([]);
|
|
const [error, setError] = useState('');
|
|
const [retry, setRetry] = useState(0);
|
|
const [areas, setAreas] = useState<InvestmentMapArea[]>([]);
|
|
useEffect(() => {
|
|
const controller = new AbortController();
|
|
const params = new URLSearchParams(JSON.parse(key));
|
|
setPoints(null);
|
|
setError('');
|
|
setMissing([]);
|
|
setAreas([]);
|
|
fetch(`/api/v1/investment/map?${params}`, {
|
|
signal: controller.signal,
|
|
headers: { Accept: 'application/json' },
|
|
})
|
|
.then(async (response) => {
|
|
if (!response.ok) throw new Error('Mapa indisponível.');
|
|
return response.json();
|
|
})
|
|
.then((data) => {
|
|
if (controller.signal.aborted) return;
|
|
setPoints(data.features);
|
|
setAreas(data.areas ?? []);
|
|
setMissing(data.unlocated ?? []);
|
|
setError(
|
|
data.truncated
|
|
? 'Cobertura limitada: refine os filtros.'
|
|
: '',
|
|
);
|
|
})
|
|
.catch(() => {
|
|
if (!controller.signal.aborted)
|
|
setError('Não foi possível concluir a pesquisa.');
|
|
});
|
|
return () => controller.abort();
|
|
}, [key, retry]);
|
|
return (
|
|
<section className="space-y-3">
|
|
<SearchState
|
|
scope="Registos consultados pelo mapa"
|
|
countLabel="registos, incluindo os que não têm coordenadas"
|
|
loading={points === null && !error}
|
|
error={error}
|
|
total={(points?.length ?? 0) + missing.length}
|
|
retry={() => setRetry((value) => value + 1)}
|
|
/>
|
|
<InteractiveInvestmentMap points={points} areas={areas} />
|
|
<TerritoryGeometryPreview
|
|
territoryId={
|
|
territoryId ||
|
|
String(
|
|
filters.locality_id ||
|
|
filters.admin_post_id ||
|
|
filters.district_id ||
|
|
filters.province_id ||
|
|
filters.geo_location_id ||
|
|
'',
|
|
)
|
|
}
|
|
/>
|
|
{missing.length > 0 && (
|
|
<div>
|
|
<p>
|
|
{missing.length} investimentos sem ponto público
|
|
utilizável. Consulte as fichas para localizações
|
|
documentadas sem coordenadas:
|
|
</p>
|
|
<ul>
|
|
{missing.map((record) => (
|
|
<li key={record.id}>
|
|
<a href={record.url} className="underline">
|
|
{record.title}
|
|
</a>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
</section>
|
|
);
|
|
}
|