Files
dh-inspeccion-v2/web-v2/src/pages/MapPage.tsx
T

75 lines
5.2 KiB
TypeScript

import { SearchableSelect } from '../components/SearchableSelect';
import { useEffect, useMemo, useState } from 'react';
import { Link } from 'react-router';
import { Alert, LoadingBlock, errorMessage } from '../components/Feedback';
import { Icon } from '../components/Icon';
import { AssetCenterTabs } from '../features/assets/AssetCenterTabs';
import { DhMap } from '../features/map/DhMap';
import {
assetStatusClass,
assetStatusLabel,
ASSET_STATUSES,
} from '../features/assets/assetPresentation';
import { getMapAssets, listAssetTypes } from '../lib/api';
import type {
AssetGeometryType,
AssetInformationStatus,
AssetType,
MapAssetFeatureCollection,
} from '../lib/api';
import { formatDate } from '../lib/format';
const emptyCollection: MapAssetFeatureCollection = {
type: 'FeatureCollection', features: [], meta: { count: 0, truncated: false },
};
export function MapPage() {
const [types, setTypes] = useState<AssetType[]>([]);
const [data, setData] = useState<MapAssetFeatureCollection>(emptyCollection);
const [typeId, setTypeId] = useState('');
const [status, setStatus] = useState<AssetInformationStatus | ''>('');
const [geometryType, setGeometryType] = useState<AssetGeometryType | ''>('');
const [selectedId, setSelectedId] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
listAssetTypes().then(setTypes).catch(() => undefined);
}, []);
useEffect(() => {
setLoading(true); setError('');
getMapAssets({ typeId, status, geometryType })
.then((result) => {
setData(result);
setSelectedId((current) => result.features.some((item) => item.id === current) ? current : null);
})
.catch((requestError) => setError(errorMessage(requestError)))
.finally(() => setLoading(false));
}, [typeId, status, geometryType]);
const selected = useMemo(
() => data.features.find((feature) => feature.id === selectedId) ?? null,
[data, selectedId],
);
return <section>
<div className="page-heading"><div><span className="eyebrow">INVENTARIOS</span><h1>Mapa de inventarios</h1><p>Vista territorial de las ubicaciones registradas.</p></div><span className="map-count"><strong>{data.meta.count}</strong> geometría{data.meta.count === 1 ? '' : 's'}</span></div>
<AssetCenterTabs active="map" />
{error && <Alert>{error}</Alert>}
{data.meta.truncated && <Alert type="info">Se muestran los primeros 5000 registros. Aplicá filtros para reducir el resultado.</Alert>}
<div className="map-layout operational-map-layout">
<aside className="filters map-sidebar">
<div><span className="eyebrow">FILTROS</span><h2>Vista territorial</h2></div>
<label className="field"><span>Tipo de elemento</span><SearchableSelect value={typeId} onChange={(event) => setTypeId(event.target.value)}><option value="">Todos</option>{types.map((type) => <option key={type.id} value={type.id}>{type.name}</option>)}</SearchableSelect></label>
<label className="field"><span>Estado de información</span><SearchableSelect value={status} onChange={(event) => setStatus(event.target.value as AssetInformationStatus | '')}><option value="">Todos</option>{ASSET_STATUSES.map((item) => <option key={item.value} value={item.value}>{item.label}</option>)}</SearchableSelect></label>
<label className="field"><span>Geometría</span><SearchableSelect value={geometryType} onChange={(event) => setGeometryType(event.target.value as AssetGeometryType | '')}><option value="">Todas</option><option value="POINT">Puntos</option><option value="LINESTRING">Líneas</option><option value="POLYGON">Polígonos</option></SearchableSelect></label>
<button className="button secondary wide" onClick={() => { setTypeId(''); setStatus(''); setGeometryType(''); }}>Limpiar filtros</button>
{selected && <div className="map-selection"><span className="eyebrow">REGISTRO SELECCIONADO</span><h3>{selected.properties.name}</h3><code>{selected.properties.code}</code><div className="map-selection-meta"><span className="tag">{selected.properties.typeName}</span><span className={`status-badge ${assetStatusClass(selected.properties.informationStatus)}`}>{assetStatusLabel(selected.properties.informationStatus)}</span></div>{selected.properties.parentName && <p>Depende de <strong>{selected.properties.parentName}</strong></p>}<p>{selected.properties.geometryType === 'POINT' ? 'Punto' : selected.properties.geometryType === 'LINESTRING' ? 'Línea' : 'Polígono'} · actualizado {formatDate(selected.properties.updatedAt)}</p>{selected.properties.accuracyM != null && <p>Precisión informada: {selected.properties.accuracyM} m</p>}<Link className="button primary wide" to={`/inventarios/${selected.id}`}>Abrir registro <Icon name="chevron" /></Link></div>}
</aside>
<div className="map-stage">{loading && <div className="map-loading"><LoadingBlock label="Actualizando mapa…" /></div>}<DhMap data={data} selectedId={selectedId} onSelect={setSelectedId} />{!loading && data.features.length === 0 && <div className="map-empty"><Icon name="map" size={30} /><strong>No hay geometrías para mostrar</strong><span>Agregá una ubicación desde el detalle de un registro.</span></div>}</div>
</div>
</section>;
}