feat(parcel-sync): live eTerra search by cadastral number
- Add /api/eterra/search queries eTerra ArcGIS REST API directly by NATIONAL_CADASTRAL_REFERENCE, NATIONAL_CADNR, or INSPIRE_ID across TERENURI_ACTIVE + CLADIRI_ACTIVE layers - Search tab now queries eTerra live (not local DB) with 600ms debounce - Requires session connected + UAT selected to search - Updated placeholder and empty state messages in Romanian
This commit is contained in:
@@ -595,38 +595,66 @@ export function ParcelSyncModule() {
|
||||
|
||||
/* ════════════════════════════════════════════════════════════ */
|
||||
/* Load features (parcel search tab) */
|
||||
/* - When search term is present → live query eTerra */
|
||||
/* - When search is empty → show nothing (prompt user) */
|
||||
/* ════════════════════════════════════════════════════════════ */
|
||||
|
||||
const searchDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const loadFeatures = useCallback(async () => {
|
||||
if (!siruta || !/^\d+$/.test(siruta)) return;
|
||||
if (!featuresSearch.trim()) {
|
||||
// No search term → clear results
|
||||
setFeatures([]);
|
||||
setFeaturesTotal(0);
|
||||
return;
|
||||
}
|
||||
setLoadingFeatures(true);
|
||||
try {
|
||||
const res = await fetch("/api/eterra/features", {
|
||||
// Live search against eTerra
|
||||
const res = await fetch("/api/eterra/search", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
siruta,
|
||||
search: featuresSearch.trim(),
|
||||
layerId: featuresLayerFilter || undefined,
|
||||
search: featuresSearch || undefined,
|
||||
page: featuresPage,
|
||||
pageSize: PAGE_SIZE,
|
||||
}),
|
||||
});
|
||||
const data = (await res.json()) as {
|
||||
features?: ParcelFeature[];
|
||||
total?: number;
|
||||
error?: string;
|
||||
};
|
||||
if (data.features) setFeatures(data.features);
|
||||
if (data.total != null) setFeaturesTotal(data.total);
|
||||
if (data.error) {
|
||||
setFeatures([]);
|
||||
setFeaturesTotal(0);
|
||||
} else {
|
||||
if (data.features) setFeatures(data.features);
|
||||
if (data.total != null) setFeaturesTotal(data.total);
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
setLoadingFeatures(false);
|
||||
}, [siruta, featuresLayerFilter, featuresSearch, featuresPage]);
|
||||
}, [siruta, featuresLayerFilter, featuresSearch]);
|
||||
|
||||
// Debounced search — waits 600ms after user stops typing
|
||||
useEffect(() => {
|
||||
if (siruta && /^\d+$/.test(siruta)) void loadFeatures();
|
||||
}, [siruta, featuresLayerFilter, featuresSearch, featuresPage, loadFeatures]);
|
||||
if (!siruta || !/^\d+$/.test(siruta)) return;
|
||||
if (!featuresSearch.trim()) {
|
||||
setFeatures([]);
|
||||
setFeaturesTotal(0);
|
||||
return;
|
||||
}
|
||||
if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current);
|
||||
searchDebounceRef.current = setTimeout(() => {
|
||||
void loadFeatures();
|
||||
}, 600);
|
||||
return () => {
|
||||
if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current);
|
||||
};
|
||||
}, [siruta, featuresLayerFilter, featuresSearch, loadFeatures]);
|
||||
|
||||
/* ════════════════════════════════════════════════════════════ */
|
||||
/* Derived data */
|
||||
@@ -754,11 +782,15 @@ export function ParcelSyncModule() {
|
||||
{/* Tab 1: Parcel search */}
|
||||
{/* ═══════════════════════════════════════════════════════ */}
|
||||
<TabsContent value="search" className="space-y-4">
|
||||
{!sirutaValid ? (
|
||||
{!sirutaValid || !session.connected ? (
|
||||
<Card>
|
||||
<CardContent className="py-12 text-center text-muted-foreground">
|
||||
<Search className="h-10 w-10 mx-auto mb-3 opacity-30" />
|
||||
<p>Selectează un UAT mai sus pentru a căuta parcele.</p>
|
||||
<p>
|
||||
{!session.connected
|
||||
? "Conectează-te la eTerra și selectează un UAT."
|
||||
: "Selectează un UAT mai sus pentru a căuta parcele."}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
@@ -772,7 +804,7 @@ export function ParcelSyncModule() {
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Ref. cadastrală sau INSPIRE ID..."
|
||||
placeholder="Număr cadastral (ex: 62580)..."
|
||||
className="pl-9"
|
||||
value={featuresSearch}
|
||||
onChange={(e) => {
|
||||
@@ -850,9 +882,9 @@ export function ParcelSyncModule() {
|
||||
colSpan={6}
|
||||
className="px-4 py-8 text-center text-muted-foreground"
|
||||
>
|
||||
{featuresSearch
|
||||
? "Nicio parcelă găsită pentru căutarea curentă."
|
||||
: "Nicio parcelă sincronizată. Exportează date din tabul Export."}
|
||||
{featuresSearch.trim()
|
||||
? "Nicio parcelă găsită în eTerra pentru căutarea curentă."
|
||||
: "Introdu un număr cadastral sau INSPIRE ID pentru a căuta în eTerra."}
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user