feat(parcel-sync): fix click, color styling, UAT boundary cross-check
Click fix: - Keep l-terenuri-fill visible but transparent (opacity 0) so it still catches click events for FeatureInfoPanel. Enrichment overlay renders underneath. Color changes: - No enrichment: amber/yellow fill (was light green) - With enrichment: green fill - Buildings: red fill = no legal docs, blue = legal, gray = unknown - Parcel outline: red = building no legal, blue = building legal Boundary cross-check (/api/geoportal/boundary-check?siruta=X): - Finds "foreign" parcels: registered in other UATs but geometrically within this UAT boundary (orange dashed) - Finds "edge" parcels: registered here but centroid outside boundary (purple dashed) - Alert banner shows count, legend updated with mismatch indicators Martin config: added gis_cladiri_status source with build_legal property. Enrichment views: gis_cladiri_status now JOINs parent parcel's BUILD_LEGAL. Requires: docker restart martin + POST /api/geoportal/setup-enrichment-views Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+18
@@ -112,6 +112,24 @@ postgres:
|
||||
has_building: int4
|
||||
build_legal: int4
|
||||
|
||||
# ── Cladiri cu status legal (ParcelSync Harta tab) ──
|
||||
|
||||
gis_cladiri_status:
|
||||
schema: public
|
||||
table: gis_cladiri_status
|
||||
geometry_column: geom
|
||||
srid: 3844
|
||||
bounds: [20.2, 43.5, 30.0, 48.3]
|
||||
minzoom: 12
|
||||
maxzoom: 18
|
||||
properties:
|
||||
object_id: text
|
||||
siruta: text
|
||||
cadastral_ref: text
|
||||
area_value: float8
|
||||
layer_id: text
|
||||
build_legal: int4
|
||||
|
||||
# ── Cladiri (buildings) — NO simplification ──
|
||||
|
||||
gis_cladiri:
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* GET /api/geoportal/boundary-check?siruta=57582
|
||||
*
|
||||
* Spatial cross-check: finds parcels that geometrically fall within the
|
||||
* given UAT boundary but are registered under a DIFFERENT siruta.
|
||||
*
|
||||
* Also detects the reverse: parcels registered in this UAT whose centroid
|
||||
* falls outside its boundary (edge parcels).
|
||||
*
|
||||
* Returns GeoJSON FeatureCollection in EPSG:4326 (WGS84) for direct
|
||||
* map overlay.
|
||||
*/
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { prisma } from "@/core/storage/prisma";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type RawRow = {
|
||||
id: string;
|
||||
siruta: string;
|
||||
object_id: number;
|
||||
cadastral_ref: string | null;
|
||||
area_value: number | null;
|
||||
layer_id: string;
|
||||
mismatch_type: string;
|
||||
geojson: string;
|
||||
};
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const siruta = req.nextUrl.searchParams.get("siruta");
|
||||
if (!siruta) {
|
||||
return NextResponse.json({ error: "siruta required" }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Foreign parcels: registered in OTHER UATs but geometrically overlap this UAT
|
||||
const foreign = await prisma.$queryRaw`
|
||||
SELECT
|
||||
f.id,
|
||||
f.siruta,
|
||||
f."objectId" AS object_id,
|
||||
f."cadastralRef" AS cadastral_ref,
|
||||
f."areaValue" AS area_value,
|
||||
f."layerId" AS layer_id,
|
||||
'foreign' AS mismatch_type,
|
||||
ST_AsGeoJSON(ST_Transform(f.geom, 4326)) AS geojson
|
||||
FROM "GisFeature" f
|
||||
JOIN "GisUat" u ON u.siruta = ${siruta}
|
||||
WHERE f.siruta != ${siruta}
|
||||
AND ST_Intersects(f.geom, u.geom)
|
||||
AND (f."layerId" LIKE 'TERENURI%' OR f."layerId" LIKE 'CADGEN_LAND%')
|
||||
AND f.geom IS NOT NULL
|
||||
LIMIT 500
|
||||
` as RawRow[];
|
||||
|
||||
// 2. Edge parcels: registered in this UAT but centroid falls outside boundary
|
||||
const edge = await prisma.$queryRaw`
|
||||
SELECT
|
||||
f.id,
|
||||
f.siruta,
|
||||
f."objectId" AS object_id,
|
||||
f."cadastralRef" AS cadastral_ref,
|
||||
f."areaValue" AS area_value,
|
||||
f."layerId" AS layer_id,
|
||||
'edge' AS mismatch_type,
|
||||
ST_AsGeoJSON(ST_Transform(f.geom, 4326)) AS geojson
|
||||
FROM "GisFeature" f
|
||||
JOIN "GisUat" u ON u.siruta = f.siruta AND u.siruta = ${siruta}
|
||||
WHERE NOT ST_Contains(u.geom, ST_Centroid(f.geom))
|
||||
AND (f."layerId" LIKE 'TERENURI%' OR f."layerId" LIKE 'CADGEN_LAND%')
|
||||
AND f.geom IS NOT NULL
|
||||
LIMIT 500
|
||||
` as RawRow[];
|
||||
|
||||
const allRows = [...foreign, ...edge];
|
||||
|
||||
// Build GeoJSON FeatureCollection
|
||||
const features = allRows
|
||||
.map((row) => {
|
||||
try {
|
||||
const geometry = JSON.parse(row.geojson) as GeoJSON.Geometry;
|
||||
return {
|
||||
type: "Feature" as const,
|
||||
geometry,
|
||||
properties: {
|
||||
id: row.id,
|
||||
siruta: row.siruta,
|
||||
object_id: row.object_id,
|
||||
cadastral_ref: row.cadastral_ref,
|
||||
area_value: row.area_value,
|
||||
layer_id: row.layer_id,
|
||||
mismatch_type: row.mismatch_type,
|
||||
},
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
return NextResponse.json({
|
||||
type: "FeatureCollection",
|
||||
features,
|
||||
summary: {
|
||||
foreign: foreign.length,
|
||||
edge: edge.length,
|
||||
total: allRows.length,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
const msg = error instanceof Error ? error.message : "Eroare";
|
||||
return NextResponse.json({ error: msg }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -47,8 +47,14 @@ const VIEWS = [
|
||||
f."cadastralRef" AS cadastral_ref,
|
||||
f."areaValue" AS area_value,
|
||||
f."isActive" AS is_active,
|
||||
COALESCE((p.enrichment->>'BUILD_LEGAL')::int, -1) AS build_legal,
|
||||
f.geom
|
||||
FROM "GisFeature" f
|
||||
LEFT JOIN "GisFeature" p
|
||||
ON p.siruta = f.siruta
|
||||
AND p."cadastralRef" = f."cadastralRef"
|
||||
AND (p."layerId" LIKE 'TERENURI%' OR p."layerId" LIKE 'CADGEN_LAND%')
|
||||
AND p.enrichment IS NOT NULL
|
||||
WHERE f.geom IS NOT NULL
|
||||
AND (f."layerId" LIKE 'CLADIRI%' OR f."layerId" LIKE 'CADGEN_BUILDING%')`,
|
||||
},
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
import { useState, useRef, useCallback, useEffect } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { Map as MapIcon, Loader2 } from "lucide-react";
|
||||
import { Map as MapIcon, Loader2, AlertTriangle } from "lucide-react";
|
||||
import { Card, CardContent } from "@/shared/components/ui/card";
|
||||
import { Badge } from "@/shared/components/ui/badge";
|
||||
import { BasemapSwitcher } from "@/modules/geoportal/components/basemap-switcher";
|
||||
import {
|
||||
SelectionToolbar,
|
||||
@@ -56,7 +57,7 @@ type MapTabProps = {
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Typed map handle (avoids importing maplibregl types) */
|
||||
/* Typed map handle */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
type MapLike = {
|
||||
@@ -64,10 +65,9 @@ type MapLike = {
|
||||
getSource(id: string): unknown;
|
||||
addSource(id: string, source: Record<string, unknown>): void;
|
||||
addLayer(layer: Record<string, unknown>, before?: string): void;
|
||||
removeLayer(id: string): void;
|
||||
removeSource(id: string): void;
|
||||
setFilter(id: string, filter: unknown[] | null): void;
|
||||
setLayoutProperty(id: string, prop: string, value: unknown): void;
|
||||
setPaintProperty(id: string, prop: string, value: unknown): void;
|
||||
fitBounds(
|
||||
bounds: [number, number, number, number],
|
||||
opts?: Record<string, unknown>,
|
||||
@@ -98,7 +98,13 @@ export function MapTab({ siruta, sirutaValid }: MapTabProps) {
|
||||
const appliedSirutaRef = useRef("");
|
||||
const boundsRef = useRef<[number, number, number, number] | null>(null);
|
||||
|
||||
/* Layer visibility: show terenuri + cladiri, hide admin + UATs */
|
||||
/* Boundary check results */
|
||||
const [mismatchSummary, setMismatchSummary] = useState<{
|
||||
foreign: number;
|
||||
edge: number;
|
||||
} | null>(null);
|
||||
|
||||
/* Layer visibility: show terenuri + cladiri, hide admin */
|
||||
const [layerVisibility] = useState<LayerVisibility>({
|
||||
terenuri: true,
|
||||
cladiri: true,
|
||||
@@ -133,8 +139,6 @@ export function MapTab({ siruta, sirutaValid }: MapTabProps) {
|
||||
if (data?.bounds) {
|
||||
const [[minLng, minLat], [maxLng, maxLat]] = data.bounds;
|
||||
boundsRef.current = [minLng, minLat, maxLng, maxLat];
|
||||
|
||||
// If map already ready, fitBounds immediately
|
||||
const map = asMap(mapHandleRef.current);
|
||||
if (map) {
|
||||
map.fitBounds([minLng, minLat, maxLng, maxLat], {
|
||||
@@ -177,10 +181,10 @@ export function MapTab({ siruta, sirutaValid }: MapTabProps) {
|
||||
}
|
||||
}
|
||||
|
||||
// Hide base terenuri fill — we'll add enrichment overlay instead
|
||||
// Keep l-terenuri-fill VISIBLE but fully transparent (catches clicks!)
|
||||
try {
|
||||
if (map.getLayer("l-terenuri-fill"))
|
||||
map.setLayoutProperty("l-terenuri-fill", "visibility", "none");
|
||||
map.setPaintProperty("l-terenuri-fill", "fill-opacity", 0);
|
||||
} catch {
|
||||
/* noop */
|
||||
}
|
||||
@@ -190,7 +194,7 @@ export function MapTab({ siruta, sirutaValid }: MapTabProps) {
|
||||
? `${window.location.origin}/tiles`
|
||||
: "/tiles";
|
||||
|
||||
// Add enrichment source + layers (or update filter if already added)
|
||||
// ── Enrichment overlay for PARCELS ──
|
||||
if (!map.getSource("gis_terenuri_status")) {
|
||||
map.addSource("gis_terenuri_status", {
|
||||
type: "vector",
|
||||
@@ -199,7 +203,7 @@ export function MapTab({ siruta, sirutaValid }: MapTabProps) {
|
||||
maxzoom: 18,
|
||||
});
|
||||
|
||||
// Data-driven fill: color by enrichment status
|
||||
// Data-driven fill: yellowish = no enrichment, dark green = enriched
|
||||
map.addLayer(
|
||||
{
|
||||
id: "l-ps-terenuri-fill",
|
||||
@@ -212,16 +216,16 @@ export function MapTab({ siruta, sirutaValid }: MapTabProps) {
|
||||
"fill-color": [
|
||||
"case",
|
||||
["==", ["get", "has_enrichment"], 1],
|
||||
"#15803d", // dark green: enriched
|
||||
"#86efac", // light green: no enrichment
|
||||
"#22c55e", // green: enriched
|
||||
"#fbbf24", // amber/yellow: no enrichment
|
||||
],
|
||||
"fill-opacity": 0.25,
|
||||
"fill-opacity": 0.3,
|
||||
},
|
||||
},
|
||||
"l-terenuri-line", // insert before outline
|
||||
"l-terenuri-fill", // below the transparent click-catcher
|
||||
);
|
||||
|
||||
// Data-driven outline: color by building status
|
||||
// Data-driven outline: red = building no legal, blue = building legal, green = default
|
||||
map.addLayer(
|
||||
{
|
||||
id: "l-ps-terenuri-line",
|
||||
@@ -233,31 +237,27 @@ export function MapTab({ siruta, sirutaValid }: MapTabProps) {
|
||||
paint: {
|
||||
"line-color": [
|
||||
"case",
|
||||
// Has building without legal docs: red
|
||||
[
|
||||
"all",
|
||||
["==", ["get", "has_building"], 1],
|
||||
["==", ["get", "build_legal"], 0],
|
||||
],
|
||||
"#ef4444",
|
||||
// Has building with legal: blue
|
||||
"#ef4444", // red: building without legal
|
||||
["==", ["get", "has_building"], 1],
|
||||
"#3b82f6",
|
||||
// Default: green
|
||||
"#15803d",
|
||||
"#3b82f6", // blue: building with legal
|
||||
"#15803d", // green: no building
|
||||
],
|
||||
"line-width": [
|
||||
"case",
|
||||
["==", ["get", "has_building"], 1],
|
||||
1.8,
|
||||
2,
|
||||
0.8,
|
||||
],
|
||||
},
|
||||
},
|
||||
"l-cladiri-fill",
|
||||
"l-terenuri-fill",
|
||||
);
|
||||
} else {
|
||||
// Source already exists — update filters for new siruta
|
||||
try {
|
||||
if (map.getLayer("l-ps-terenuri-fill"))
|
||||
map.setFilter("l-ps-terenuri-fill", filter);
|
||||
@@ -267,6 +267,155 @@ export function MapTab({ siruta, sirutaValid }: MapTabProps) {
|
||||
/* noop */
|
||||
}
|
||||
}
|
||||
|
||||
// ── Enrichment overlay for BUILDINGS ──
|
||||
if (!map.getSource("gis_cladiri_status")) {
|
||||
map.addSource("gis_cladiri_status", {
|
||||
type: "vector",
|
||||
tiles: [`${martinBase}/gis_cladiri_status/{z}/{x}/{y}`],
|
||||
minzoom: 12,
|
||||
maxzoom: 18,
|
||||
});
|
||||
|
||||
// Hide base cladiri layers
|
||||
try {
|
||||
if (map.getLayer("l-cladiri-fill"))
|
||||
map.setPaintProperty("l-cladiri-fill", "fill-opacity", 0);
|
||||
} catch {
|
||||
/* noop */
|
||||
}
|
||||
|
||||
// Buildings: red fill = no legal, blue fill = legal, gray = unknown
|
||||
map.addLayer(
|
||||
{
|
||||
id: "l-ps-cladiri-fill",
|
||||
type: "fill",
|
||||
source: "gis_cladiri_status",
|
||||
"source-layer": "gis_cladiri_status",
|
||||
minzoom: 14,
|
||||
filter,
|
||||
paint: {
|
||||
"fill-color": [
|
||||
"case",
|
||||
["==", ["get", "build_legal"], 0],
|
||||
"#ef4444", // red: no legal
|
||||
["==", ["get", "build_legal"], 1],
|
||||
"#3b82f6", // blue: legal
|
||||
"#6b7280", // gray: unknown (-1)
|
||||
],
|
||||
"fill-opacity": 0.5,
|
||||
},
|
||||
},
|
||||
"l-cladiri-line",
|
||||
);
|
||||
|
||||
map.addLayer(
|
||||
{
|
||||
id: "l-ps-cladiri-line",
|
||||
type: "line",
|
||||
source: "gis_cladiri_status",
|
||||
"source-layer": "gis_cladiri_status",
|
||||
minzoom: 14,
|
||||
filter,
|
||||
paint: {
|
||||
"line-color": [
|
||||
"case",
|
||||
["==", ["get", "build_legal"], 0],
|
||||
"#dc2626", // dark red
|
||||
"#1e3a5f", // dark blue
|
||||
],
|
||||
"line-width": 0.8,
|
||||
},
|
||||
},
|
||||
"l-cladiri-line",
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
if (map.getLayer("l-ps-cladiri-fill"))
|
||||
map.setFilter("l-ps-cladiri-fill", filter);
|
||||
if (map.getLayer("l-ps-cladiri-line"))
|
||||
map.setFilter("l-ps-cladiri-line", filter);
|
||||
} catch {
|
||||
/* noop */
|
||||
}
|
||||
}
|
||||
}, [mapReady, siruta, sirutaValid]);
|
||||
|
||||
/* ── Boundary cross-check: load mismatched parcels ─────────── */
|
||||
const prevCheckSirutaRef = useRef("");
|
||||
useEffect(() => {
|
||||
if (!mapReady || !sirutaValid || !siruta) return;
|
||||
if (prevCheckSirutaRef.current === siruta) return;
|
||||
prevCheckSirutaRef.current = siruta;
|
||||
|
||||
fetch(`/api/geoportal/boundary-check?siruta=${siruta}`)
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then(
|
||||
(
|
||||
data: {
|
||||
type?: string;
|
||||
features?: GeoJSON.Feature[];
|
||||
summary?: { foreign: number; edge: number };
|
||||
} | null,
|
||||
) => {
|
||||
if (!data || !data.features || data.features.length === 0) {
|
||||
setMismatchSummary(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setMismatchSummary(data.summary ?? null);
|
||||
|
||||
const map = asMap(mapHandleRef.current);
|
||||
if (!map) return;
|
||||
|
||||
// Add or update GeoJSON source for mismatched parcels
|
||||
if (map.getSource("boundary-mismatch")) {
|
||||
// Update data
|
||||
const src = map.getSource("boundary-mismatch") as unknown as {
|
||||
setData(d: unknown): void;
|
||||
};
|
||||
src.setData(data);
|
||||
} else {
|
||||
map.addSource("boundary-mismatch", {
|
||||
type: "geojson",
|
||||
data: data as unknown as Record<string, unknown>,
|
||||
});
|
||||
|
||||
// Orange dashed fill for mismatched parcels
|
||||
map.addLayer({
|
||||
id: "l-mismatch-fill",
|
||||
type: "fill",
|
||||
source: "boundary-mismatch",
|
||||
paint: {
|
||||
"fill-color": [
|
||||
"case",
|
||||
["==", ["get", "mismatch_type"], "foreign"],
|
||||
"#f97316", // orange: foreign parcel in this UAT
|
||||
"#a855f7", // purple: edge parcel (registered here but centroid outside)
|
||||
],
|
||||
"fill-opacity": 0.35,
|
||||
},
|
||||
});
|
||||
|
||||
map.addLayer({
|
||||
id: "l-mismatch-line",
|
||||
type: "line",
|
||||
source: "boundary-mismatch",
|
||||
paint: {
|
||||
"line-color": [
|
||||
"case",
|
||||
["==", ["get", "mismatch_type"], "foreign"],
|
||||
"#ea580c",
|
||||
"#9333ea",
|
||||
],
|
||||
"line-width": 2.5,
|
||||
"line-dasharray": [4, 2],
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
)
|
||||
.catch(() => {});
|
||||
}, [mapReady, siruta, sirutaValid]);
|
||||
|
||||
/* ── Feature click handler ─────────────────────────────────── */
|
||||
@@ -304,6 +453,34 @@ export function MapTab({ siruta, sirutaValid }: MapTabProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{/* Boundary mismatch alert */}
|
||||
{mismatchSummary && (mismatchSummary.foreign > 0 || mismatchSummary.edge > 0) && (
|
||||
<div className="flex items-center gap-2 rounded-lg border border-orange-200 bg-orange-50/50 dark:border-orange-800 dark:bg-orange-950/20 px-3 py-2 text-xs">
|
||||
<AlertTriangle className="h-3.5 w-3.5 text-orange-500 shrink-0" />
|
||||
<span>
|
||||
<strong>Verificare limită UAT:</strong>{" "}
|
||||
{mismatchSummary.foreign > 0 && (
|
||||
<>
|
||||
<Badge variant="outline" className="text-[10px] border-orange-300 text-orange-700 dark:text-orange-400 mx-0.5">
|
||||
{mismatchSummary.foreign} parcele străine
|
||||
</Badge>
|
||||
(din alt UAT dar geometric în acest UAT)
|
||||
</>
|
||||
)}
|
||||
{mismatchSummary.foreign > 0 && mismatchSummary.edge > 0 && " · "}
|
||||
{mismatchSummary.edge > 0 && (
|
||||
<>
|
||||
<Badge variant="outline" className="text-[10px] border-purple-300 text-purple-700 dark:text-purple-400 mx-0.5">
|
||||
{mismatchSummary.edge} parcele la limită
|
||||
</Badge>
|
||||
(înregistrate aici dar centroid în afara limitei)
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="relative h-[600px] rounded-lg border overflow-hidden">
|
||||
{boundsLoading && (
|
||||
<div className="absolute top-3 left-1/2 -translate-x-1/2 z-20 flex items-center gap-2 rounded-full bg-background/90 border px-3 py-1.5 text-xs shadow-sm">
|
||||
@@ -348,11 +525,12 @@ export function MapTab({ siruta, sirutaValid }: MapTabProps) {
|
||||
|
||||
{/* Bottom-right: legend */}
|
||||
<div className="absolute bottom-3 right-3 z-10 rounded-lg bg-background/90 border p-2 text-[10px] space-y-1">
|
||||
<div className="font-semibold text-[11px] mb-1">Parcele</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span
|
||||
className="inline-block h-3 w-3 rounded-sm border"
|
||||
style={{
|
||||
backgroundColor: "rgba(134,239,172,0.25)",
|
||||
backgroundColor: "rgba(251,191,36,0.3)",
|
||||
borderColor: "#15803d",
|
||||
}}
|
||||
/>
|
||||
@@ -362,7 +540,7 @@ export function MapTab({ siruta, sirutaValid }: MapTabProps) {
|
||||
<span
|
||||
className="inline-block h-3 w-3 rounded-sm border"
|
||||
style={{
|
||||
backgroundColor: "rgba(21,128,61,0.25)",
|
||||
backgroundColor: "rgba(34,197,94,0.3)",
|
||||
borderColor: "#15803d",
|
||||
}}
|
||||
/>
|
||||
@@ -373,7 +551,7 @@ export function MapTab({ siruta, sirutaValid }: MapTabProps) {
|
||||
className="inline-block h-3 w-3 rounded-sm"
|
||||
style={{ border: "2px solid #3b82f6" }}
|
||||
/>
|
||||
Cu clădire
|
||||
Cu clădire (legal)
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span
|
||||
@@ -382,6 +560,47 @@ export function MapTab({ siruta, sirutaValid }: MapTabProps) {
|
||||
/>
|
||||
Clădire fără acte
|
||||
</div>
|
||||
<div className="font-semibold text-[11px] mt-1.5 mb-1">Clădiri</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span
|
||||
className="inline-block h-3 w-3 rounded-sm"
|
||||
style={{ backgroundColor: "rgba(59,130,246,0.5)" }}
|
||||
/>
|
||||
Legală
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span
|
||||
className="inline-block h-3 w-3 rounded-sm"
|
||||
style={{ backgroundColor: "rgba(239,68,68,0.5)" }}
|
||||
/>
|
||||
Fără acte
|
||||
</div>
|
||||
{mismatchSummary && (mismatchSummary.foreign > 0 || mismatchSummary.edge > 0) && (
|
||||
<>
|
||||
<div className="font-semibold text-[11px] mt-1.5 mb-1">Limită UAT</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span
|
||||
className="inline-block h-3 w-3 rounded-sm"
|
||||
style={{
|
||||
backgroundColor: "rgba(249,115,22,0.35)",
|
||||
border: "2px dashed #ea580c",
|
||||
}}
|
||||
/>
|
||||
Parcele străine
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span
|
||||
className="inline-block h-3 w-3 rounded-sm"
|
||||
style={{
|
||||
backgroundColor: "rgba(168,85,247,0.35)",
|
||||
border: "2px dashed #9333ea",
|
||||
}}
|
||||
/>
|
||||
La limită
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user