fix(parcel-sync): replace Unicode escapes with actual Romanian diacritics

The \u0103, \u00ee etc. escape sequences were rendering literally in JSX
text nodes instead of displaying ă, î, ț, ș characters.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-24 15:24:11 +02:00
parent d48a2bbf5d
commit 2b8d144924
8 changed files with 167 additions and 167 deletions
@@ -77,11 +77,11 @@ export function ConnectionPill({
)} )}
<span className="hidden sm:inline"> <span className="hidden sm:inline">
{connecting {connecting
? "Se conecteaz\u0103\u2026" ? "Se conectează…"
: session.connected : session.connected
? "eTerra" ? "eTerra"
: session.eterraMaintenance : session.eterraMaintenance
? "Mentenan\u021b\u0103" ? "Mentenanță"
: connectionError : connectionError
? "Eroare" ? "Eroare"
: "Deconectat"} : "Deconectat"}
@@ -128,11 +128,11 @@ export function ConnectionPill({
<AlertTriangle className="h-3.5 w-3.5 text-amber-500 mt-0.5 shrink-0" /> <AlertTriangle className="h-3.5 w-3.5 text-amber-500 mt-0.5 shrink-0" />
<div> <div>
<p className="font-medium text-amber-700 dark:text-amber-400"> <p className="font-medium text-amber-700 dark:text-amber-400">
eTerra este \u00een mentenan\u021b\u0103 eTerra este în mentenanță
</p> </p>
<p className="mt-1 text-[11px] text-amber-600/80 dark:text-amber-400/70"> <p className="mt-1 text-[11px] text-amber-600/80 dark:text-amber-400/70">
Platforma ANCPI nu este disponibil\u0103 momentan. Conectarea va fi Platforma ANCPI nu este disponibilă momentan. Conectarea va fi
reactivat\u0103 automat c\u00e2nd serviciul revine online. reactivată automat când serviciul revine online.
</p> </p>
{session.eterraHealthMessage && ( {session.eterraHealthMessage && (
<p className="mt-1 text-[10px] opacity-60 font-mono"> <p className="mt-1 text-[10px] opacity-60 font-mono">
@@ -149,9 +149,9 @@ export function ConnectionPill({
!connectionError && !connectionError &&
!session.eterraMaintenance && ( !session.eterraMaintenance && (
<div className="px-3 py-3 text-xs text-muted-foreground"> <div className="px-3 py-3 text-xs text-muted-foreground">
<p>Conexiunea se face automat c\u00e2nd \u00eencepi s\u0103 scrii un UAT.</p> <p>Conexiunea se face automat când începi scrii un UAT.</p>
<p className="mt-1 text-[11px] opacity-70"> <p className="mt-1 text-[11px] opacity-70">
Creden\u021bialele sunt preluate din configurarea serverului. Credențialele sunt preluate din configurarea serverului.
</p> </p>
</div> </div>
)} )}
@@ -162,7 +162,7 @@ export function ConnectionPill({
!session.eterraMaintenance && ( !session.eterraMaintenance && (
<div className="px-3 py-3 text-xs text-muted-foreground"> <div className="px-3 py-3 text-xs text-muted-foreground">
<p> <p>
Conexiunea automat\u0103 a e\u0219uat. Verific\u0103 creden\u021bialele din Conexiunea automată a eșuat. Verifică credențialele din
variabilele de mediu (ETERRA_USERNAME / ETERRA_PASSWORD). variabilele de mediu (ETERRA_USERNAME / ETERRA_PASSWORD).
</p> </p>
</div> </div>
@@ -182,7 +182,7 @@ export function ConnectionPill({
{session.activeJobPhase && ( {session.activeJobPhase && (
<span className="opacity-70"> <span className="opacity-70">
{" "} {" "}
\u2014 {session.activeJobPhase} {session.activeJobPhase}
</span> </span>
)} )}
</p> </p>
@@ -194,14 +194,14 @@ export function ParcelSyncModule() {
...prev, ...prev,
eterraMaintenance: true, eterraMaintenance: true,
eterraAvailable: false, eterraAvailable: false,
eterraHealthMessage: data.error ?? "eTerra \u00een mentenan\u021b\u0103", eterraHealthMessage: data.error ?? "eTerra în mentenanță",
})); }));
autoConnectAttempted.current = false; autoConnectAttempted.current = false;
} else { } else {
setConnectionError(data.error ?? "Eroare conectare"); setConnectionError(data.error ?? "Eroare conectare");
} }
} catch { } catch {
setConnectionError("Eroare re\u021bea"); setConnectionError("Eroare rețea");
} }
setConnecting(false); setConnecting(false);
}, [session.connected, session.eterraMaintenance, connecting, fetchSession]); }, [session.connected, session.eterraMaintenance, connecting, fetchSession]);
@@ -222,7 +222,7 @@ export function ParcelSyncModule() {
setConnectionError(data.error ?? "Nu se poate deconecta"); setConnectionError(data.error ?? "Nu se poate deconecta");
} }
} catch { } catch {
setConnectionError("Eroare re\u021bea"); setConnectionError("Eroare rețea");
} }
}, []); }, []);
@@ -282,7 +282,7 @@ export function ParcelSyncModule() {
<MapPin className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground pointer-events-none" /> <MapPin className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground pointer-events-none" />
<Input <Input
id="uat-search" id="uat-search"
placeholder="Selecteaz\u0103 UAT \u2014 scrie nume sau cod SIRUTA\u2026" placeholder="Selectează UAT — scrie nume sau cod SIRUTA"
value={uatQuery} value={uatQuery}
onChange={(e) => { onChange={(e) => {
setUatQuery(e.target.value); setUatQuery(e.target.value);
@@ -329,7 +329,7 @@ export function ParcelSyncModule() {
<span className="text-muted-foreground">({item.siruta})</span> <span className="text-muted-foreground">({item.siruta})</span>
{item.county && ( {item.county && (
<span className="text-muted-foreground"> <span className="text-muted-foreground">
{"\u2013 "} {" "}
<span className="font-medium text-foreground/70"> <span className="font-medium text-foreground/70">
jud. {item.county} jud. {item.county}
</span> </span>
@@ -363,7 +363,7 @@ export function ParcelSyncModule() {
<TabsList> <TabsList>
<TabsTrigger value="search" className="gap-1.5"> <TabsTrigger value="search" className="gap-1.5">
<Search className="h-4 w-4" /> <Search className="h-4 w-4" />
C\u0103utare Parcele Căutare Parcele
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="layers" className="gap-1.5"> <TabsTrigger value="layers" className="gap-1.5">
<Layers className="h-4 w-4" /> <Layers className="h-4 w-4" />
@@ -85,7 +85,7 @@ export const normalizeText = (text: string) =>
.trim(); .trim();
export function formatDate(iso?: string | null) { export function formatDate(iso?: string | null) {
if (!iso) return "\u2014"; if (!iso) return "";
return new Date(iso).toLocaleDateString("ro-RO", { return new Date(iso).toLocaleDateString("ro-RO", {
day: "2-digit", day: "2-digit",
month: "2-digit", month: "2-digit",
@@ -96,13 +96,13 @@ export function formatDate(iso?: string | null) {
} }
export function formatArea(val?: number | null) { export function formatArea(val?: number | null) {
if (val == null) return "\u2014"; if (val == null) return "";
return val.toLocaleString("ro-RO", { maximumFractionDigits: 2 }) + " mp"; return val.toLocaleString("ro-RO", { maximumFractionDigits: 2 }) + " mp";
} }
/** Format ISO date as DD.MM.YYYY (no time) */ /** Format ISO date as DD.MM.YYYY (no time) */
export function formatShortDate(iso?: string | null) { export function formatShortDate(iso?: string | null) {
if (!iso) return "\u2014"; if (!iso) return "";
const d = new Date(iso); const d = new Date(iso);
const dd = String(d.getDate()).padStart(2, "0"); const dd = String(d.getDate()).padStart(2, "0");
const mm = String(d.getMonth() + 1).padStart(2, "0"); const mm = String(d.getMonth() + 1).padStart(2, "0");
@@ -110,7 +110,7 @@ export function formatShortDate(iso?: string | null) {
} }
export function relativeTime(date: Date | null) { export function relativeTime(date: Date | null) {
if (!date) return "niciodat\u0103"; if (!date) return "niciodată";
const mins = Math.floor((Date.now() - date.getTime()) / 60_000); const mins = Math.floor((Date.now() - date.getTime()) / 60_000);
if (mins < 1) return "acum"; if (mins < 1) return "acum";
if (mins < 60) return `acum ${mins} min`; if (mins < 60) return `acum ${mins} min`;
@@ -52,7 +52,7 @@ export function DatabaseTab({
<Card> <Card>
<CardContent className="py-12 text-center text-muted-foreground"> <CardContent className="py-12 text-center text-muted-foreground">
<Loader2 className="h-8 w-8 mx-auto mb-3 animate-spin opacity-40" /> <Loader2 className="h-8 w-8 mx-auto mb-3 animate-spin opacity-40" />
<p>Se \u00eencarc\u0103 datele din baza de date\u2026</p> <p>Se încarcă datele din baza de date</p>
</CardContent> </CardContent>
</Card> </Card>
); );
@@ -64,9 +64,9 @@ export function DatabaseTab({
<Card> <Card>
<CardContent className="py-12 text-center text-muted-foreground"> <CardContent className="py-12 text-center text-muted-foreground">
<Database className="h-10 w-10 mx-auto mb-3 opacity-30" /> <Database className="h-10 w-10 mx-auto mb-3 opacity-30" />
<p className="font-medium">Nicio dat\u0103 \u00een baza de date</p> <p className="font-medium">Nicio dată în baza de date</p>
<p className="text-xs mt-1"> <p className="text-xs mt-1">
Folose\u0219te tab-ul Export pentru a sincroniza date din eTerra. Folosește tab-ul Export pentru a sincroniza date din eTerra.
</p> </p>
</CardContent> </CardContent>
</Card> </Card>
@@ -81,7 +81,7 @@ export function DatabaseTab({
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Database className="h-4 w-4 text-muted-foreground" /> <Database className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium"> <span className="text-sm font-medium">
{dbSummary.totalFeatures.toLocaleString("ro-RO")} entit\u0103\u021bi {dbSummary.totalFeatures.toLocaleString("ro-RO")} entități
</span> </span>
<span className="text-xs text-muted-foreground"> <span className="text-xs text-muted-foreground">
din {dbSummary.totalUats} UAT-uri din {dbSummary.totalUats} UAT-uri
@@ -99,7 +99,7 @@ export function DatabaseTab({
) : ( ) : (
<RefreshCw className="h-3 w-3 mr-1" /> <RefreshCw className="h-3 w-3 mr-1" />
)} )}
Re\u00eencarc\u0103 Reîncarcă
</Button> </Button>
</div> </div>
@@ -169,7 +169,7 @@ export function DatabaseTab({
Dashboard Dashboard
</Button> </Button>
<span className="text-xs text-muted-foreground"> <span className="text-xs text-muted-foreground">
{oldestSync ? relativeTime(oldestSync) : "\u2014"} {oldestSync ? relativeTime(oldestSync) : ""}
</span> </span>
</span> </span>
</div> </div>
@@ -212,7 +212,7 @@ export function DatabaseTab({
{noGeomTotal > 0 && ( {noGeomTotal > 0 && (
<span className="inline-flex items-center gap-1"> <span className="inline-flex items-center gap-1">
<span className="text-muted-foreground"> <span className="text-muted-foreground">
F\u0103r\u0103 geom: Fără geom:
</span> </span>
<span className="font-medium tabular-nums text-amber-600 dark:text-amber-400"> <span className="font-medium tabular-nums text-amber-600 dark:text-amber-400">
{noGeomTotal.toLocaleString("ro-RO")} {noGeomTotal.toLocaleString("ro-RO")}
@@ -239,7 +239,7 @@ export function DatabaseTab({
? "border-teal-200 bg-teal-50/50 dark:border-teal-800 dark:bg-teal-950/30" ? "border-teal-200 bg-teal-50/50 dark:border-teal-800 dark:bg-teal-950/30"
: "border-muted bg-muted/30", : "border-muted bg-muted/30",
)} )}
title={`${label}: ${layer.count} entit\u0103\u021bi${isEnriched ? `, ${layer.enrichedCount} \u00eembog\u0103\u021bite` : ""}${layer.lastSynced ? `, sync: ${new Date(layer.lastSynced).toLocaleDateString("ro-RO")}` : ""}`} title={`${label}: ${layer.count} entități${isEnriched ? `, ${layer.enrichedCount} îmbogățite` : ""}${layer.lastSynced ? `, sync: ${new Date(layer.lastSynced).toLocaleDateString("ro-RO")}` : ""}`}
> >
<span className="truncate max-w-[120px]"> <span className="truncate max-w-[120px]">
{label} {label}
@@ -244,7 +244,7 @@ export function ExportTab({
phase: "Finalizat", phase: "Finalizat",
downloaded: prev.total ?? 100, downloaded: prev.total ?? 100,
total: prev.total ?? 100, total: prev.total ?? 100,
message: `Desc\u0103rcare complet\u0103 \u2014 ${filename}`, message: `Descărcare completă — ${filename}`,
note: undefined, note: undefined,
} }
: null, : null,
@@ -365,7 +365,7 @@ export function ExportTab({
err instanceof DOMException && err.name === "AbortError"; err instanceof DOMException && err.name === "AbortError";
if (isTimeout) { if (isTimeout) {
console.warn( console.warn(
"[no-geom-scan] Timeout after 2 min \u2014 server eTerra lent", "[no-geom-scan] Timeout after 2 min server eTerra lent",
); );
} }
setNoGeomScan({ setNoGeomScan({
@@ -469,7 +469,7 @@ export function ExportTab({
} else if (data.status === "done") { } else if (data.status === "done") {
setBgJobId(saved.jobId); setBgJobId(saved.jobId);
setBgProgress(data); setBgProgress(data);
if (data.phase) setBgPhaseTrail(["Sincronizare complet\u0103"]); if (data.phase) setBgPhaseTrail(["Sincronizare completă"]);
localStorage.removeItem("parcel-sync:bg-job"); localStorage.removeItem("parcel-sync:bg-job");
} else { } else {
localStorage.removeItem("parcel-sync:bg-job"); localStorage.removeItem("parcel-sync:bg-job");
@@ -532,7 +532,7 @@ export function ExportTab({
} }
startBgPolling(jid); startBgPolling(jid);
} catch (error) { } catch (error) {
const msg = error instanceof Error ? error.message : "Eroare re\u021bea"; const msg = error instanceof Error ? error.message : "Eroare rețea";
console.warn("[sync-background]", msg); console.warn("[sync-background]", msg);
} }
}, },
@@ -573,7 +573,7 @@ export function ExportTab({
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
} catch (error) { } catch (error) {
const msg = const msg =
error instanceof Error ? error.message : "Eroare desc\u0103rcare"; error instanceof Error ? error.message : "Eroare descărcare";
console.warn("[download-from-db]", msg); console.warn("[download-from-db]", msg);
} }
setDownloadingFromDb(false); setDownloadingFromDb(false);
@@ -597,7 +597,7 @@ export function ExportTab({
<span className="font-medium text-foreground"> <span className="font-medium text-foreground">
{dbTotalFeatures.toLocaleString("ro-RO")} {dbTotalFeatures.toLocaleString("ro-RO")}
</span>{" "} </span>{" "}
entit\u0103\u021bi \u00een DB din{" "} entități în DB din{" "}
<span className="font-medium text-foreground"> <span className="font-medium text-foreground">
{dbLayersSummary.length} {dbLayersSummary.length}
</span>{" "} </span>{" "}
@@ -664,10 +664,10 @@ export function ExportTab({
)} )}
<div className="text-left"> <div className="text-left">
<div className="font-semibold"> <div className="font-semibold">
Descarc\u0103 Terenuri \u0219i Cl\u0103diri Descarcă Terenuri și Clădiri
</div> </div>
<div className="text-xs opacity-70 font-normal"> <div className="text-xs opacity-70 font-normal">
Sync + GPKG (din cache dac\u0103 e proasp\u0103t) Sync + GPKG (din cache dacă e proaspăt)
</div> </div>
</div> </div>
</Button> </Button>
@@ -686,7 +686,7 @@ export function ExportTab({
<div className="text-left"> <div className="text-left">
<div className="font-semibold">Magic</div> <div className="font-semibold">Magic</div>
<div className="text-xs opacity-70 font-normal"> <div className="text-xs opacity-70 font-normal">
Sync + \u00eembog\u0103\u021bire (CF, proprietari, adres\u0103) + GPKG + CSV Sync + îmbogățire (CF, proprietari, adresă) + GPKG + CSV
</div> </div>
</div> </div>
</Button> </Button>
@@ -697,12 +697,12 @@ export function ExportTab({
{!session.connected ? ( {!session.connected ? (
<> <>
<Wifi className="h-10 w-10 mx-auto mb-3 opacity-30" /> <Wifi className="h-10 w-10 mx-auto mb-3 opacity-30" />
<p>Conecteaz\u0103-te la eTerra pentru a activa exportul.</p> <p>Conectează-te la eTerra pentru a activa exportul.</p>
</> </>
) : ( ) : (
<> <>
<MapPin className="h-10 w-10 mx-auto mb-3 opacity-30" /> <MapPin className="h-10 w-10 mx-auto mb-3 opacity-30" />
<p>Selecteaz\u0103 un UAT pentru a activa exportul.</p> <p>Selectează un UAT pentru a activa exportul.</p>
</> </>
)} )}
</CardContent> </CardContent>
@@ -730,10 +730,10 @@ export function ExportTab({
<CardContent className="py-3 px-4"> <CardContent className="py-3 px-4">
<div className="flex items-center gap-2 text-sm text-muted-foreground"> <div className="flex items-center gap-2 text-sm text-muted-foreground">
<Loader2 className="h-4 w-4 animate-spin text-amber-500" /> <Loader2 className="h-4 w-4 animate-spin text-amber-500" />
Se scaneaz\u0103 lista de imobile din eTerra\u2026 (max 2 min) Se scanează lista de imobile din eTerra (max 2 min)
</div> </div>
<p className="text-[11px] text-muted-foreground mt-1 ml-6"> <p className="text-[11px] text-muted-foreground mt-1 ml-6">
Po\u021bi folosi butoanele de mai jos f\u0103r\u0103 s\u0103 a\u0219tep\u021bi scanarea. Poți folosi butoanele de mai jos fără aștepți scanarea.
</p> </p>
</CardContent> </CardContent>
</Card> </Card>
@@ -746,10 +746,10 @@ export function ExportTab({
<CardContent className="py-3 px-4"> <CardContent className="py-3 px-4">
<div className="flex items-center gap-2 text-sm text-amber-600 dark:text-amber-400"> <div className="flex items-center gap-2 text-sm text-amber-600 dark:text-amber-400">
<Clock className="h-4 w-4 shrink-0" /> <Clock className="h-4 w-4 shrink-0" />
Scanarea a dep\u0103\u0219it 2 minute \u2014 serverul eTerra e lent. Scanarea a depășit 2 minute serverul eTerra e lent.
</div> </div>
<p className="text-[11px] text-muted-foreground mt-1 ml-6"> <p className="text-[11px] text-muted-foreground mt-1 ml-6">
Po\u021bi lansa sincronizarea fundal f\u0103r\u0103 rezultate de scanare. Poți lansa sincronizarea fundal fără rezultate de scanare.
Include no-geom nu va fi disponibil. Include no-geom nu va fi disponibil.
</p> </p>
<Button <Button
@@ -759,7 +759,7 @@ export function ExportTab({
onClick={() => void handleNoGeomScan()} onClick={() => void handleNoGeomScan()}
> >
<RefreshCw className="h-3 w-3 mr-1" /> <RefreshCw className="h-3 w-3 mr-1" />
Re\u00eencearc\u0103 scanarea Reîncearcă scanarea
</Button> </Button>
</CardContent> </CardContent>
</Card> </Card>
@@ -779,7 +779,7 @@ export function ExportTab({
<div className="flex items-center gap-1.5 flex-wrap text-[11px] text-muted-foreground"> <div className="flex items-center gap-1.5 flex-wrap text-[11px] text-muted-foreground">
<Database className="h-3 w-3 shrink-0" /> <Database className="h-3 w-3 shrink-0" />
<span> <span>
Baza de date local\u0103:{" "} Baza de date locală:{" "}
<span className="font-medium text-foreground"> <span className="font-medium text-foreground">
{noGeomScan.localDbWithGeom.toLocaleString("ro-RO")} {noGeomScan.localDbWithGeom.toLocaleString("ro-RO")}
</span>{" "} </span>{" "}
@@ -790,16 +790,16 @@ export function ExportTab({
<span className="font-medium text-amber-600 dark:text-amber-400"> <span className="font-medium text-amber-600 dark:text-amber-400">
{noGeomScan.localDbNoGeom.toLocaleString("ro-RO")} {noGeomScan.localDbNoGeom.toLocaleString("ro-RO")}
</span>{" "} </span>{" "}
f\u0103r\u0103 geometrie fără geometrie
</> </>
)} )}
{noGeomScan.localDbEnriched > 0 && ( {noGeomScan.localDbEnriched > 0 && (
<> <>
{" \u00b7 "} {" · "}
<span className="font-medium text-teal-600 dark:text-teal-400"> <span className="font-medium text-teal-600 dark:text-teal-400">
{noGeomScan.localDbEnriched.toLocaleString("ro-RO")} {noGeomScan.localDbEnriched.toLocaleString("ro-RO")}
</span>{" "} </span>{" "}
\u00eembog\u0103\u021bite îmbogățite
{staleEnrichment && ( {staleEnrichment && (
<span className="text-orange-600 dark:text-orange-400"> <span className="text-orange-600 dark:text-orange-400">
{" "} {" "}
@@ -810,7 +810,7 @@ export function ExportTab({
)} )}
{noGeomScan.localSyncFresh && ( {noGeomScan.localSyncFresh && (
<span className="text-emerald-600 dark:text-emerald-400 ml-1"> <span className="text-emerald-600 dark:text-emerald-400 ml-1">
(proasp\u0103t) (proaspăt)
</span> </span>
)} )}
</span> </span>
@@ -819,9 +819,9 @@ export function ExportTab({
<div className="flex items-center gap-1.5 text-[11px] text-orange-600 dark:text-orange-400 ml-[18px]"> <div className="flex items-center gap-1.5 text-[11px] text-orange-600 dark:text-orange-400 ml-[18px]">
<AlertTriangle className="h-3 w-3 shrink-0" /> <AlertTriangle className="h-3 w-3 shrink-0" />
<span> <span>
{staleCount.toLocaleString("ro-RO")} parcele au \u00eembog\u0103\u021bire {staleCount.toLocaleString("ro-RO")} parcele au îmbogățire
veche (lips\u0103 PROPRIETARI_VECHI). Vor fi re-\u00eembog\u0103\u021bite la veche (lipsă PROPRIETARI_VECHI). Vor fi re-îmbogățite la
urm\u0103torul export Magic. următorul export Magic.
</span> </span>
</div> </div>
)} )}
@@ -832,11 +832,11 @@ export function ExportTab({
const workflowPreview = scanDone && ( const workflowPreview = scanDone && (
<div className="mt-2 ml-7 space-y-0.5"> <div className="mt-2 ml-7 space-y-0.5">
<p className="text-[11px] font-medium text-muted-foreground"> <p className="text-[11px] font-medium text-muted-foreground">
La ap\u0103sarea Magic, pa\u0219ii vor fi: La apăsarea Magic, pașii vor fi:
</p> </p>
<ol className="text-[11px] text-muted-foreground list-decimal ml-4 space-y-px"> <ol className="text-[11px] text-muted-foreground list-decimal ml-4 space-y-px">
<li> <li>
{"Sync GIS \u2014 "} {"Sync GIS "}
<span <span
className={cn( className={cn(
"font-medium", "font-medium",
@@ -848,16 +848,16 @@ export function ExportTab({
> >
{noGeomScan.localSyncFresh && {noGeomScan.localSyncFresh &&
noGeomScan.localDbWithGeom > 0 noGeomScan.localDbWithGeom > 0
? "skip (date proaspete \u00een DB)" ? "skip (date proaspete în DB)"
: `descarc\u0103 ${noGeomScan.remoteGisCount.toLocaleString("ro-RO")} terenuri` + : `descarcă ${noGeomScan.remoteGisCount.toLocaleString("ro-RO")} terenuri` +
(noGeomScan.remoteCladiriCount > 0 (noGeomScan.remoteCladiriCount > 0
? ` + ${noGeomScan.remoteCladiriCount.toLocaleString("ro-RO")} cl\u0103diri` ? ` + ${noGeomScan.remoteCladiriCount.toLocaleString("ro-RO")} clădiri`
: "")} : "")}
</span> </span>
</li> </li>
{includeNoGeom && ( {includeNoGeom && (
<li> <li>
Import parcele f\u0103r\u0103 geometrie \u2014{" "} Import parcele fără geometrie {" "}
<span className="font-medium text-amber-600 dark:text-amber-400"> <span className="font-medium text-amber-600 dark:text-amber-400">
{(() => { {(() => {
const usefulNoGeom = const usefulNoGeom =
@@ -878,7 +878,7 @@ export function ExportTab({
</li> </li>
)} )}
<li> <li>
\u00cembog\u0103\u021bire CF, proprietari, adrese \u2014{" "} \u00cembogățire CF, proprietari, adrese {" "}
<span className="font-medium text-teal-600 dark:text-teal-400"> <span className="font-medium text-teal-600 dark:text-teal-400">
{(() => { {(() => {
// What will be in DB after sync + optional no-geom import: // What will be in DB after sync + optional no-geom import:
@@ -900,12 +900,12 @@ export function ExportTab({
totalAfter - noGeomScan.localDbEnrichedComplete; totalAfter - noGeomScan.localDbEnrichedComplete;
return remaining > 0 return remaining > 0
? `~${remaining.toLocaleString("ro-RO")} de procesat (~${Math.ceil((remaining * 0.25) / 60)} min)` ? `~${remaining.toLocaleString("ro-RO")} de procesat (~${Math.ceil((remaining * 0.25) / 60)} min)`
: "deja \u00eembog\u0103\u021bite"; : "deja îmbogățite";
})()} })()}
</span> </span>
</li> </li>
<li>Generare GPKG + CSV</li> <li>Generare GPKG + CSV</li>
<li>Comprimare ZIP + desc\u0103rcare</li> <li>Comprimare ZIP + descărcare</li>
</ol> </ol>
</div> </div>
); );
@@ -939,10 +939,10 @@ export function ExportTab({
"ro-RO", "ro-RO",
)} )}
</span>{" "} </span>{" "}
cl\u0103diri clădiri
</> </>
)} )}
{" \u00b7 "} {" · "}
Lista imobile:{" "} Lista imobile:{" "}
<span className="font-semibold"> <span className="font-semibold">
{noGeomScan.totalImmovables.toLocaleString("ro-RO")} {noGeomScan.totalImmovables.toLocaleString("ro-RO")}
@@ -956,11 +956,11 @@ export function ExportTab({
noGeomScan.remoteGisCount, noGeomScan.remoteGisCount,
).toLocaleString("ro-RO")} ).toLocaleString("ro-RO")}
</span> </span>
{" f\u0103r\u0103 geometrie)"} {" fără geometrie)"}
</p> </p>
<p className="text-[11px] text-muted-foreground mt-0.5"> <p className="text-[11px] text-muted-foreground mt-0.5">
Cele f\u0103r\u0103 geometrie exist\u0103 \u00een baza de date eTerra dar Cele fără geometrie există în baza de date eTerra dar
nu au contur desenat \u00een layerul GIS. nu au contur desenat în layerul GIS.
</p> </p>
{localDbLine} {localDbLine}
</div> </div>
@@ -984,7 +984,7 @@ export function ExportTab({
className="h-4 w-4 rounded border-muted-foreground/30 accent-amber-600" className="h-4 w-4 rounded border-muted-foreground/30 accent-amber-600"
/> />
<span className="text-sm font-medium"> <span className="text-sm font-medium">
Include \u0219i parcelele f\u0103r\u0103 geometrie la export Include și parcelele fără geometrie la export
</span> </span>
</label> </label>
{/* Quality breakdown of no-geom items */} {/* Quality breakdown of no-geom items */}
@@ -992,7 +992,7 @@ export function ExportTab({
<div className="ml-7 p-2 rounded-md bg-muted/40 space-y-1"> <div className="ml-7 p-2 rounded-md bg-muted/40 space-y-1">
<p className="text-[11px] font-medium text-muted-foreground"> <p className="text-[11px] font-medium text-muted-foreground">
Calitate date (din{" "} Calitate date (din{" "}
{noGeomScan.noGeomCount.toLocaleString("ro-RO")} f\u0103r\u0103 {noGeomScan.noGeomCount.toLocaleString("ro-RO")} fără
geometrie): geometrie):
</p> </p>
<div className="grid grid-cols-2 gap-x-4 gap-y-0.5 text-[11px] text-muted-foreground"> <div className="grid grid-cols-2 gap-x-4 gap-y-0.5 text-[11px] text-muted-foreground">
@@ -1013,7 +1013,7 @@ export function ExportTab({
</span> </span>
</span> </span>
<span> <span>
Cu nr. cad. pe h\u00e2rtie:{" "} Cu nr. cad. pe hârtie:{" "}
<span className="font-medium text-foreground"> <span className="font-medium text-foreground">
{noGeomScan.qualityBreakdown.withPaperCad.toLocaleString( {noGeomScan.qualityBreakdown.withPaperCad.toLocaleString(
"ro-RO", "ro-RO",
@@ -1021,7 +1021,7 @@ export function ExportTab({
</span> </span>
</span> </span>
<span> <span>
Cu suprafa\u021b\u0103:{" "} Cu suprafață:{" "}
<span className="font-medium text-foreground"> <span className="font-medium text-foreground">
{noGeomScan.qualityBreakdown.withArea.toLocaleString( {noGeomScan.qualityBreakdown.withArea.toLocaleString(
"ro-RO", "ro-RO",
@@ -1037,7 +1037,7 @@ export function ExportTab({
</span> </span>
</span> </span>
<span> <span>
Cu carte funciar\u0103:{" "} Cu carte funciară:{" "}
<span className="font-medium text-foreground"> <span className="font-medium text-foreground">
{noGeomScan.qualityBreakdown.withLandbook.toLocaleString( {noGeomScan.qualityBreakdown.withLandbook.toLocaleString(
"ro-RO", "ro-RO",
@@ -1056,7 +1056,7 @@ export function ExportTab({
</span> </span>
{noGeomScan.qualityBreakdown.empty > 0 && ( {noGeomScan.qualityBreakdown.empty > 0 && (
<span> <span>
Filtrate (f\u0103r\u0103 CF/inactive/f\u0103r\u0103 date):{" "} Filtrate (fără CF/inactive/fără date):{" "}
<span className="font-semibold text-rose-600 dark:text-rose-400"> <span className="font-semibold text-rose-600 dark:text-rose-400">
{noGeomScan.qualityBreakdown.empty.toLocaleString( {noGeomScan.qualityBreakdown.empty.toLocaleString(
"ro-RO", "ro-RO",
@@ -1070,9 +1070,9 @@ export function ExportTab({
{includeNoGeom && ( {includeNoGeom && (
<p className="text-[11px] text-muted-foreground ml-7"> <p className="text-[11px] text-muted-foreground ml-7">
{noGeomScan.qualityBreakdown.empty > 0 {noGeomScan.qualityBreakdown.empty > 0
? `Din ${noGeomScan.noGeomCount.toLocaleString("ro-RO")} f\u0103r\u0103 geometrie, ~${noGeomScan.qualityBreakdown.useful.toLocaleString("ro-RO")} vor fi importate (imobile electronice cu CF). ${noGeomScan.qualityBreakdown.empty.toLocaleString("ro-RO")} vor fi filtrate (f\u0103r\u0103 carte funciar\u0103, inactive sau f\u0103r\u0103 date).` ? `Din ${noGeomScan.noGeomCount.toLocaleString("ro-RO")} fără geometrie, ~${noGeomScan.qualityBreakdown.useful.toLocaleString("ro-RO")} vor fi importate (imobile electronice cu CF). ${noGeomScan.qualityBreakdown.empty.toLocaleString("ro-RO")} vor fi filtrate (fără carte funciară, inactive sau fără date).`
: "Vor fi importate \u00een DB \u0219i incluse \u00een CSV + Magic GPKG (coloana HAS_GEOMETRY=0/1)."}{" "} : "Vor fi importate în DB și incluse în CSV + Magic GPKG (coloana HAS_GEOMETRY=0/1)."}{" "}
\u00cen GPKG de baz\u0103 apar doar cele cu geometrie. \u00cen GPKG de bază apar doar cele cu geometrie.
</p> </p>
)} )}
{workflowPreview} {workflowPreview}
@@ -1091,14 +1091,14 @@ export function ExportTab({
<CheckCircle2 className="h-3.5 w-3.5 text-emerald-500" /> <CheckCircle2 className="h-3.5 w-3.5 text-emerald-500" />
Toate cele{" "} Toate cele{" "}
{noGeomScan.totalImmovables.toLocaleString("ro-RO")}{" "} {noGeomScan.totalImmovables.toLocaleString("ro-RO")}{" "}
imobile din eTerra au geometrie \u2014 nimic de importat imobile din eTerra au geometrie nimic de importat
suplimentar. suplimentar.
{noGeomScan.localDbTotal > 0 && ( {noGeomScan.localDbTotal > 0 && (
<span className="ml-1"> <span className="ml-1">
({noGeomScan.localDbTotal.toLocaleString("ro-RO")}{" "} ({noGeomScan.localDbTotal.toLocaleString("ro-RO")}{" "}
\u00een DB local în DB local
{noGeomScan.localDbEnriched > 0 && {noGeomScan.localDbEnriched > 0 &&
`, ${noGeomScan.localDbEnriched.toLocaleString("ro-RO")} \u00eembog\u0103\u021bite`} `, ${noGeomScan.localDbEnriched.toLocaleString("ro-RO")} îmbogățite`}
{noGeomScan.localDbEnriched > 0 && {noGeomScan.localDbEnriched > 0 &&
noGeomScan.localDbEnrichedComplete < noGeomScan.localDbEnrichedComplete <
noGeomScan.localDbEnriched && ( noGeomScan.localDbEnriched && (
@@ -1106,15 +1106,15 @@ export function ExportTab({
{` (${(noGeomScan.localDbEnriched - noGeomScan.localDbEnrichedComplete).toLocaleString("ro-RO")} incomplete)`} {` (${(noGeomScan.localDbEnriched - noGeomScan.localDbEnrichedComplete).toLocaleString("ro-RO")} incomplete)`}
</span> </span>
)} )}
{noGeomScan.localSyncFresh && ", proasp\u0103t"}) {noGeomScan.localSyncFresh && ", proaspăt"})
</span> </span>
)} )}
</> </>
) : ( ) : (
<> <>
<AlertTriangle className="h-3.5 w-3.5 text-muted-foreground" /> <AlertTriangle className="h-3.5 w-3.5 text-muted-foreground" />
Nu s-au g\u0103sit imobile \u00een lista eTerra pentru acest Nu s-au găsit imobile în lista eTerra pentru acest
UAT. Verific\u0103 sesiunea eTerra. UAT. Verifică sesiunea eTerra.
</> </>
)} )}
</div> </div>
@@ -1133,10 +1133,10 @@ export function ExportTab({
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<HardDrive className="h-4 w-4 text-muted-foreground" /> <HardDrive className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium"> <span className="text-sm font-medium">
Procesare fundal &amp; desc\u0103rcare din DB Procesare fundal &amp; descărcare din DB
</span> </span>
<span className="text-[10px] text-muted-foreground"> <span className="text-[10px] text-muted-foreground">
\u2014 porne\u0219te sincronizarea, \u00eenchide pagina, descarc\u0103 mai t\u00e2rziu pornește sincronizarea, închide pagina, descarcă mai târziu
</span> </span>
</div> </div>
@@ -1154,11 +1154,11 @@ export function ExportTab({
className="h-4 w-4 rounded border-muted-foreground/30 accent-amber-600" className="h-4 w-4 rounded border-muted-foreground/30 accent-amber-600"
/> />
<span className="text-xs"> <span className="text-xs">
Include \u0219i parcelele f\u0103r\u0103 geometrie Include și parcelele fără geometrie
</span> </span>
{noGeomScanning && ( {noGeomScanning && (
<span className="text-[10px] text-muted-foreground"> <span className="text-[10px] text-muted-foreground">
(scanare \u00een curs\u2026) (scanare în curs)
</span> </span>
)} )}
</label> </label>
@@ -1179,17 +1179,17 @@ export function ExportTab({
> >
{bgJobId && {bgJobId &&
bgProgress?.status === "running" && bgProgress?.status === "running" &&
!bgPhaseTrail.some((p) => p.includes("\u00cembog\u0103\u021bire")) ? ( !bgPhaseTrail.some((p) => p.includes("\u00cembogățire")) ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> <Loader2 className="mr-2 h-4 w-4 animate-spin" />
) : ( ) : (
<ArrowDownToLine className="mr-2 h-4 w-4" /> <ArrowDownToLine className="mr-2 h-4 w-4" />
)} )}
<div className="text-left"> <div className="text-left">
<div className="text-xs font-semibold"> <div className="text-xs font-semibold">
Sync fundal \u2014 Baz\u0103 Sync fundal Bază
</div> </div>
<div className="text-[10px] opacity-60 font-normal"> <div className="text-[10px] opacity-60 font-normal">
Terenuri + cl\u0103diri \u2192 salveaz\u0103 \u00een DB Terenuri + clădiri \u2192 salvează în DB
</div> </div>
</div> </div>
</Button> </Button>
@@ -1205,17 +1205,17 @@ export function ExportTab({
> >
{bgJobId && {bgJobId &&
bgProgress?.status === "running" && bgProgress?.status === "running" &&
bgPhaseTrail.some((p) => p.includes("\u00cembog\u0103\u021bire")) ? ( bgPhaseTrail.some((p) => p.includes("\u00cembogățire")) ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin text-teal-600" /> <Loader2 className="mr-2 h-4 w-4 animate-spin text-teal-600" />
) : ( ) : (
<Sparkles className="mr-2 h-4 w-4 text-teal-600" /> <Sparkles className="mr-2 h-4 w-4 text-teal-600" />
)} )}
<div className="text-left"> <div className="text-left">
<div className="text-xs font-semibold"> <div className="text-xs font-semibold">
Sync fundal \u2014 Magic Sync fundal Magic
</div> </div>
<div className="text-[10px] opacity-60 font-normal"> <div className="text-[10px] opacity-60 font-normal">
Sync + \u00eembog\u0103\u021bire \u2192 salveaz\u0103 \u00een DB Sync + îmbogățire \u2192 salvează în DB
</div> </div>
</div> </div>
</Button> </Button>
@@ -1239,10 +1239,10 @@ export function ExportTab({
)} )}
<div className="text-left"> <div className="text-left">
<div className="text-xs font-semibold"> <div className="text-xs font-semibold">
Descarc\u0103 din DB \u2014 Baz\u0103 Descarcă din DB Bază
</div> </div>
<div className="text-[10px] opacity-60 font-normal"> <div className="text-[10px] opacity-60 font-normal">
GPKG terenuri + cl\u0103diri (instant, f\u0103r\u0103 eTerra) GPKG terenuri + clădiri (instant, fără eTerra)
</div> </div>
</div> </div>
</Button> </Button>
@@ -1260,7 +1260,7 @@ export function ExportTab({
)} )}
<div className="text-left"> <div className="text-left">
<div className="text-xs font-semibold"> <div className="text-xs font-semibold">
Descarc\u0103 din DB \u2014 Magic Descarcă din DB Magic
</div> </div>
<div className="text-[10px] opacity-60 font-normal"> <div className="text-[10px] opacity-60 font-normal">
GPKG + CSV + raport calitate (instant) GPKG + CSV + raport calitate (instant)
@@ -1272,8 +1272,8 @@ export function ExportTab({
{!session.connected && dbTotalFeatures === 0 && ( {!session.connected && dbTotalFeatures === 0 && (
<p className="text-xs text-muted-foreground ml-6"> <p className="text-xs text-muted-foreground ml-6">
Conecteaz\u0103-te la eTerra pentru a porni sincronizarea fundal, Conectează-te la eTerra pentru a porni sincronizarea fundal,
sau sincronizeaz\u0103 mai \u00eent\u00e2i date \u00een baza local\u0103. sau sincronizează mai întâi date în baza locală.
</p> </p>
)} )}
</CardContent> </CardContent>
@@ -1302,7 +1302,7 @@ export function ExportTab({
</span> </span>
{bgProgress.status === "running" && ( {bgProgress.status === "running" && (
<span className="text-muted-foreground"> <span className="text-muted-foreground">
(po\u021bi \u00eenchide pagina) (poți închide pagina)
</span> </span>
)} )}
</div> </div>
@@ -1396,7 +1396,7 @@ export function ExportTab({
) : ( ) : (
<Database className="mr-1.5 h-3 w-3" /> <Database className="mr-1.5 h-3 w-3" />
)} )}
Descarc\u0103 din DB (Magic) Descarcă din DB (Magic)
</Button> </Button>
<Button <Button
variant="ghost" variant="ghost"
@@ -1466,7 +1466,7 @@ export function ExportTab({
{exportProgress.phase} {exportProgress.phase}
{exportProgress.phaseCurrent != null && {exportProgress.phaseCurrent != null &&
exportProgress.phaseTotal exportProgress.phaseTotal
? ` \u2014 ${exportProgress.phaseCurrent} / ${exportProgress.phaseTotal}` ? ` ${exportProgress.phaseCurrent} / ${exportProgress.phaseTotal}`
: ""} : ""}
</p> </p>
{exportProgress.note && ( {exportProgress.note && (
@@ -239,7 +239,7 @@ export function LayersTab({
async (layerId: string) => { async (layerId: string) => {
if (!siruta || syncingLayer) return; if (!siruta || syncingLayer) return;
setSyncingLayer(layerId); setSyncingLayer(layerId);
setSyncProgress("Sincronizare pornit\u0103\u2026"); setSyncProgress("Sincronizare pornită…");
try { try {
const res = await fetch("/api/eterra/sync", { const res = await fetch("/api/eterra/sync", {
method: "POST", method: "POST",
@@ -261,13 +261,13 @@ export function LayersTab({
setSyncProgress(`Eroare: ${data.error}`); setSyncProgress(`Eroare: ${data.error}`);
} else { } else {
setSyncProgress( setSyncProgress(
`Finalizat \u2014 ${data.newFeatures ?? 0} noi, ${data.removedFeatures ?? 0} \u0219terse, ${data.totalLocal ?? 0} total local`, `Finalizat ${data.newFeatures ?? 0} noi, ${data.removedFeatures ?? 0} șterse, ${data.totalLocal ?? 0} total local`,
); );
// Refresh sync status // Refresh sync status
onSyncRefresh(); onSyncRefresh();
} }
} catch { } catch {
setSyncProgress("Eroare re\u021bea"); setSyncProgress("Eroare rețea");
} }
// Clear progress after 8s // Clear progress after 8s
setTimeout(() => { setTimeout(() => {
@@ -334,7 +334,7 @@ export function LayersTab({
for (const layerId of layerIds) { for (const layerId of layerIds) {
setSyncingLayer(layerId); setSyncingLayer(layerId);
setSyncProgress( setSyncProgress(
`Sincronizare ${LAYER_CATALOG.find((l) => l.id === layerId)?.label ?? layerId}\u2026`, `Sincronizare ${LAYER_CATALOG.find((l) => l.id === layerId)?.label ?? layerId}`,
); );
try { try {
const res = await fetch("/api/eterra/sync", { const res = await fetch("/api/eterra/sync", {
@@ -356,7 +356,7 @@ export function LayersTab({
setSyncProgress(`Eroare: ${data.error}`); setSyncProgress(`Eroare: ${data.error}`);
} }
} catch { } catch {
setSyncProgress("Eroare re\u021bea"); setSyncProgress("Eroare rețea");
} }
// Remove from queue // Remove from queue
syncQueueRef.current = syncQueueRef.current.filter( syncQueueRef.current = syncQueueRef.current.filter(
@@ -445,7 +445,7 @@ export function LayersTab({
phase: "Finalizat", phase: "Finalizat",
downloaded: prev.total ?? 100, downloaded: prev.total ?? 100,
total: prev.total ?? 100, total: prev.total ?? 100,
message: `Desc\u0103rcare complet\u0103 \u2014 ${filename}`, message: `Descărcare completă — ${filename}`,
note: undefined, note: undefined,
} }
: null, : null,
@@ -489,8 +489,8 @@ export function LayersTab({
<Layers className="h-10 w-10 mx-auto mb-3 opacity-30" /> <Layers className="h-10 w-10 mx-auto mb-3 opacity-30" />
<p> <p>
{!session.connected {!session.connected
? "Conecteaz\u0103-te la eTerra \u0219i selecteaz\u0103 un UAT." ? "Conectează-te la eTerra și selectează un UAT."
: "Selecteaz\u0103 un UAT pentru a vedea catalogul de layere."} : "Selectează un UAT pentru a vedea catalogul de layere."}
</p> </p>
</CardContent> </CardContent>
</Card> </Card>
@@ -504,8 +504,8 @@ export function LayersTab({
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
{layerCountSiruta === siruta && {layerCountSiruta === siruta &&
Object.keys(layerCounts).length > 0 Object.keys(layerCounts).length > 0
? `Num\u0103r features pentru SIRUTA ${siruta}` ? `Număr features pentru SIRUTA ${siruta}`
: "Apas\u0103 pentru a num\u0103ra features-urile din fiecare layer."} : "Apasă pentru a număra features-urile din fiecare layer."}
</p> </p>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{/* Export from local DB */} {/* Export from local DB */}
@@ -537,7 +537,7 @@ export function LayersTab({
) : ( ) : (
<Search className="h-3.5 w-3.5 mr-1.5" /> <Search className="h-3.5 w-3.5 mr-1.5" />
)} )}
{countingLayers ? "Se num\u0103r\u0103\u2026" : "Num\u0103r\u0103"} {countingLayers ? "Se numără…" : "Numără"}
</Button> </Button>
</div> </div>
</div> </div>
@@ -712,7 +712,7 @@ export function LayersTab({
void handleSyncLayer(layer.id) void handleSyncLayer(layer.id)
} }
className="border-violet-200 dark:border-violet-800" className="border-violet-200 dark:border-violet-800"
title="Sincronizeaz\u0103 \u00een baza de date" title="Sincronizează în baza de date"
> >
{isSyncing ? ( {isSyncing ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" /> <Loader2 className="h-3.5 w-3.5 animate-spin" />
@@ -733,8 +733,8 @@ export function LayersTab({
} }
title={ title={
localCount > 0 localCount > 0
? "Descarc\u0103 GPKG (din cache dac\u0103 e proasp\u0103t)" ? "Descarcă GPKG (din cache dacă e proaspăt)"
: "Sincronizeaz\u0103 + descarc\u0103 GPKG" : "Sincronizează + descarcă GPKG"
} }
> >
{isDownloading ? ( {isDownloading ? (
@@ -787,7 +787,7 @@ export function LayersTab({
<p className="text-[11px] font-semibold text-muted-foreground"> <p className="text-[11px] font-semibold text-muted-foreground">
SIRUTA {sir}{" "} SIRUTA {sir}{" "}
<span className="font-normal opacity-70"> <span className="font-normal opacity-70">
\u2014{" "} {" "}
{new Date(entries[0]!.time).toLocaleTimeString( {new Date(entries[0]!.time).toLocaleTimeString(
"ro-RO", "ro-RO",
{ hour: "2-digit", minute: "2-digit" }, { hour: "2-digit", minute: "2-digit" },
@@ -845,7 +845,7 @@ export function LayersTab({
<Database className="h-3.5 w-3.5 mr-1.5" /> <Database className="h-3.5 w-3.5 mr-1.5" />
)} )}
{postgisRunning {postgisRunning
? "Se activeaza\u2026" ? "Se activeaza"
: "Activeaza compatibilitate QGIS"} : "Activeaza compatibilitate QGIS"}
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
@@ -871,7 +871,7 @@ export function LayersTab({
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<CheckCircle2 className="h-4 w-4 text-emerald-500" /> <CheckCircle2 className="h-4 w-4 text-emerald-500" />
<span className="text-sm font-medium text-emerald-700 dark:text-emerald-400"> <span className="text-sm font-medium text-emerald-700 dark:text-emerald-400">
QGIS compatibil \u2014 gata de conectare QGIS compatibil gata de conectare
</span> </span>
</div> </div>
<div className="rounded bg-muted/50 p-3 text-xs space-y-2"> <div className="rounded bg-muted/50 p-3 text-xs space-y-2">
@@ -946,7 +946,7 @@ export function LayersTab({
si limitelor UAT. si limitelor UAT.
</p> </p>
<p> <p>
Apasa butonul pentru a activa \u2014 creeaza view-uri read-only Apasa butonul pentru a activa creeaza view-uri read-only
(nu modifica datele, nu afecteaza performanta aplicatiei). (nu modifica datele, nu afecteaza performanta aplicatiei).
</p> </p>
</div> </div>
@@ -965,7 +965,7 @@ export function LayersTab({
{exportProgress.phase} {exportProgress.phase}
{exportProgress.phaseCurrent != null && {exportProgress.phaseCurrent != null &&
exportProgress.phaseTotal exportProgress.phaseTotal
? ` \u2014 ${exportProgress.phaseCurrent} / ${exportProgress.phaseTotal}` ? ` ${exportProgress.phaseCurrent} / ${exportProgress.phaseTotal}`
: ""} : ""}
</p> </p>
</div> </div>
@@ -321,7 +321,7 @@ export function MapTab({ siruta, sirutaValid }: MapTabProps) {
<Card> <Card>
<CardContent className="py-12 text-center text-muted-foreground"> <CardContent className="py-12 text-center text-muted-foreground">
<MapIcon className="h-10 w-10 mx-auto mb-3 opacity-30" /> <MapIcon className="h-10 w-10 mx-auto mb-3 opacity-30" />
<p>Selecteaz\u0103 un UAT din lista de mai sus</p> <p>Selectează un UAT din lista de mai sus</p>
</CardContent> </CardContent>
</Card> </Card>
); );
@@ -332,7 +332,7 @@ export function MapTab({ siruta, sirutaValid }: MapTabProps) {
{boundsLoading && ( {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"> <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">
<Loader2 className="h-3 w-3 animate-spin" /> <Loader2 className="h-3 w-3 animate-spin" />
Se \u00eencarc\u0103 zona UAT... Se încarcă zona UAT...
</div> </div>
)} )}
@@ -376,7 +376,7 @@ export function MapTab({ siruta, sirutaValid }: MapTabProps) {
<div className="absolute bottom-3 right-3 z-10 rounded-lg bg-background/90 border p-2 text-[10px] space-y-1"> <div className="absolute bottom-3 right-3 z-10 rounded-lg bg-background/90 border p-2 text-[10px] space-y-1">
<div className="flex items-center gap-1.5"> <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)", borderColor: "#15803d" }} /> <span className="inline-block h-3 w-3 rounded-sm border" style={{ backgroundColor: "rgba(134,239,172,0.25)", borderColor: "#15803d" }} />
F\u0103r\u0103 enrichment Fără enrichment
</div> </div>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<span className="inline-block h-3 w-3 rounded-sm border" style={{ backgroundColor: "rgba(21,128,61,0.25)", borderColor: "#15803d" }} /> <span className="inline-block h-3 w-3 rounded-sm border" style={{ backgroundColor: "rgba(21,128,61,0.25)", borderColor: "#15803d" }} />
@@ -384,11 +384,11 @@ export function MapTab({ siruta, sirutaValid }: MapTabProps) {
</div> </div>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<span className="inline-block h-3 w-3 rounded-sm" style={{ border: "2px solid #3b82f6" }} /> <span className="inline-block h-3 w-3 rounded-sm" style={{ border: "2px solid #3b82f6" }} />
Cu cl\u0103dire Cu clădire
</div> </div>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<span className="inline-block h-3 w-3 rounded-sm" style={{ border: "2px solid #ef4444" }} /> <span className="inline-block h-3 w-3 rounded-sm" style={{ border: "2px solid #ef4444" }} />
Cl\u0103dire f\u0103r\u0103 acte Clădire fără acte
</div> </div>
</div> </div>
</div> </div>
@@ -123,7 +123,7 @@ export function SearchTab({
setSearchError(""); setSearchError("");
} }
} catch { } catch {
setSearchError("Eroare de re\u021bea."); setSearchError("Eroare de rețea.");
} }
setLoadingFeatures(false); setLoadingFeatures(false);
}, [siruta, featuresSearch, workspacePk]); }, [siruta, featuresSearch, workspacePk]);
@@ -178,12 +178,12 @@ export function SearchTab({
if (data.eterraNote) notes.push(data.eterraNote); if (data.eterraNote) notes.push(data.eterraNote);
setOwnerNote( setOwnerNote(
notes.length > 0 notes.length > 0
? `Surse: ${notes.join(" + ")}${data.total ? ` \u00b7 ${data.total} rezultate` : ""}` ? `Surse: ${notes.join(" + ")}${data.total ? ` · ${data.total} rezultate` : ""}`
: "", : "",
); );
} }
} catch { } catch {
setOwnerError("Eroare de re\u021bea."); setOwnerError("Eroare de rețea.");
} }
setOwnerLoading(false); setOwnerLoading(false);
}, [siruta, ownerSearch, workspacePk]); }, [siruta, ownerSearch, workspacePk]);
@@ -596,7 +596,7 @@ export function SearchTab({
<Card> <Card>
<CardContent className="py-12 text-center text-muted-foreground"> <CardContent className="py-12 text-center text-muted-foreground">
<Search className="h-10 w-10 mx-auto mb-3 opacity-30" /> <Search className="h-10 w-10 mx-auto mb-3 opacity-30" />
<p>Selecteaz\u0103 un UAT mai sus pentru a c\u0103uta parcele.</p> <p>Selectează un UAT mai sus pentru a căuta parcele.</p>
</CardContent> </CardContent>
</Card> </Card>
); );
@@ -640,7 +640,7 @@ export function SearchTab({
<div className="flex gap-3 items-end"> <div className="flex gap-3 items-end">
<div className="space-y-1 flex-1"> <div className="space-y-1 flex-1">
<Label className="text-xs"> <Label className="text-xs">
Numere cadastrale (separate prin virgul\u0103 sau Enter) Numere cadastrale (separate prin virgulă sau Enter)
</Label> </Label>
<div className="relative"> <div className="relative">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" /> <Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
@@ -655,8 +655,8 @@ export function SearchTab({
</div> </div>
{!session.connected && ( {!session.connected && (
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
Necesit\u0103 conexiune eTerra. Folose\u0219te modul Proprietar Necesită conexiune eTerra. Folosește modul Proprietar
pentru a c\u0103uta offline \u00een DB. pentru a căuta offline în DB.
</p> </p>
)} )}
</div> </div>
@@ -673,7 +673,7 @@ export function SearchTab({
) : ( ) : (
<Search className="mr-2 h-4 w-4" /> <Search className="mr-2 h-4 w-4" />
)} )}
Caut\u0103 Caută
</Button> </Button>
</div> </div>
)} )}
@@ -683,7 +683,7 @@ export function SearchTab({
<div className="flex gap-3 items-end"> <div className="flex gap-3 items-end">
<div className="space-y-1 flex-1"> <div className="space-y-1 flex-1">
<Label className="text-xs"> <Label className="text-xs">
Nume proprietar (caut\u0103 \u00een DB local + eTerra) Nume proprietar (caută în DB local + eTerra)
</Label> </Label>
<div className="relative"> <div className="relative">
<User className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" /> <User className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
@@ -705,7 +705,7 @@ export function SearchTab({
) : ( ) : (
<Search className="mr-2 h-4 w-4" /> <Search className="mr-2 h-4 w-4" />
)} )}
Caut\u0103 Caută
</Button> </Button>
</div> </div>
)} )}
@@ -729,10 +729,10 @@ export function SearchTab({
<Card> <Card>
<CardContent className="py-12 text-center text-muted-foreground"> <CardContent className="py-12 text-center text-muted-foreground">
<Loader2 className="h-10 w-10 mx-auto mb-3 animate-spin opacity-50" /> <Loader2 className="h-10 w-10 mx-auto mb-3 animate-spin opacity-50" />
<p>Se caut\u0103 \u00een eTerra...</p> <p>Se caută în eTerra...</p>
<p className="text-xs mt-1 opacity-60"> <p className="text-xs mt-1 opacity-60">
Prima c\u0103utare pe un UAT nou poate dura ~10-30s (se Prima căutare pe un UAT nou poate dura ~10-30s (se
\u00eencarc\u0103 lista de jude\u021be). încarcă lista de județe).
</p> </p>
</CardContent> </CardContent>
</Card> </Card>
@@ -747,7 +747,7 @@ export function SearchTab({
{searchResults.length > 1 ? "e" : ""} {searchResults.length > 1 ? "e" : ""}
{searchList.length > 0 && ( {searchList.length > 0 && (
<span className="ml-2"> <span className="ml-2">
· <strong>{searchList.length}</strong> \u00een list\u0103 · <strong>{searchList.length}</strong> în listă
</span> </span>
)} )}
</span> </span>
@@ -761,7 +761,7 @@ export function SearchTab({
}} }}
> >
<Plus className="mr-1 h-3.5 w-3.5" /> <Plus className="mr-1 h-3.5 w-3.5" />
Adaug\u0103 toate \u00een list\u0103 Adaugă toate în listă
</Button> </Button>
)} )}
<Button <Button
@@ -773,7 +773,7 @@ export function SearchTab({
} }
> >
<FileDown className="mr-1 h-3.5 w-3.5" /> <FileDown className="mr-1 h-3.5 w-3.5" />
Descarc\u0103 CSV Descarcă CSV
</Button> </Button>
</div> </div>
</div> </div>
@@ -796,7 +796,7 @@ export function SearchTab({
</h3> </h3>
{!p.immovablePk && ( {!p.immovablePk && (
<p className="text-xs text-destructive"> <p className="text-xs text-destructive">
Parcela nu a fost g\u0103sit\u0103 \u00een eTerra. Parcela nu a fost găsită în eTerra.
</p> </p>
)} )}
</div> </div>
@@ -805,7 +805,7 @@ export function SearchTab({
size="sm" size="sm"
variant="ghost" variant="ghost"
className="h-7 w-7 p-0" className="h-7 w-7 p-0"
title="Adaug\u0103 \u00een list\u0103" title="Adaugă în listă"
onClick={() => addToList(p)} onClick={() => addToList(p)}
disabled={!p.immovablePk} disabled={!p.immovablePk}
> >
@@ -819,7 +819,7 @@ export function SearchTab({
onClick={() => { onClick={() => {
const text = [ const text = [
`Nr. Cad: ${p.nrCad}`, `Nr. Cad: ${p.nrCad}`,
`Nr. CF: ${p.nrCF || "\u2014"}`, `Nr. CF: ${p.nrCF || ""}`,
p.nrCFVechi p.nrCFVechi
? `CF vechi: ${p.nrCFVechi}` ? `CF vechi: ${p.nrCFVechi}`
: null, : null,
@@ -829,7 +829,7 @@ export function SearchTab({
p.suprafata != null p.suprafata != null
? `Suprafata: ${p.suprafata.toLocaleString("ro-RO")} mp` ? `Suprafata: ${p.suprafata.toLocaleString("ro-RO")} mp`
: null, : null,
`Intravilan: ${p.intravilan || "\u2014"}`, `Intravilan: ${p.intravilan || ""}`,
p.categorieFolosinta p.categorieFolosinta
? `Categorie: ${p.categorieFolosinta}` ? `Categorie: ${p.categorieFolosinta}`
: null, : null,
@@ -870,7 +870,7 @@ export function SearchTab({
Nr. CF Nr. CF
</span> </span>
<span className="font-medium"> <span className="font-medium">
{p.nrCF || "\u2014"} {p.nrCF || ""}
</span> </span>
</div> </div>
{p.nrCFVechi && ( {p.nrCFVechi && (
@@ -885,16 +885,16 @@ export function SearchTab({
<span className="text-xs text-muted-foreground block"> <span className="text-xs text-muted-foreground block">
Nr. Topo Nr. Topo
</span> </span>
<span>{p.nrTopo || "\u2014"}</span> <span>{p.nrTopo || ""}</span>
</div> </div>
<div> <div>
<span className="text-xs text-muted-foreground block"> <span className="text-xs text-muted-foreground block">
Suprafa\u021b\u0103 Suprafață
</span> </span>
<span className="tabular-nums"> <span className="tabular-nums">
{p.suprafata != null {p.suprafata != null
? formatArea(p.suprafata) ? formatArea(p.suprafata)
: "\u2014"} : ""}
</span> </span>
</div> </div>
<div> <div>
@@ -911,13 +911,13 @@ export function SearchTab({
} }
className="text-[11px]" className="text-[11px]"
> >
{p.intravilan || "\u2014"} {p.intravilan || ""}
</Badge> </Badge>
</div> </div>
{p.categorieFolosinta && ( {p.categorieFolosinta && (
<div className="col-span-2"> <div className="col-span-2">
<span className="text-xs text-muted-foreground block"> <span className="text-xs text-muted-foreground block">
Categorii folosin\u021b\u0103 Categorii folosință
</span> </span>
<span className="text-xs"> <span className="text-xs">
{p.categorieFolosinta} {p.categorieFolosinta}
@@ -927,7 +927,7 @@ export function SearchTab({
{p.adresa && ( {p.adresa && (
<div className="col-span-2"> <div className="col-span-2">
<span className="text-xs text-muted-foreground block"> <span className="text-xs text-muted-foreground block">
Adres\u0103 Adresă
</span> </span>
<span>{p.adresa}</span> <span>{p.adresa}</span>
</div> </div>
@@ -991,10 +991,10 @@ export function SearchTab({
<Card> <Card>
<CardContent className="py-12 text-center text-muted-foreground"> <CardContent className="py-12 text-center text-muted-foreground">
<Search className="h-10 w-10 mx-auto mb-3 opacity-30" /> <Search className="h-10 w-10 mx-auto mb-3 opacity-30" />
<p>Introdu un num\u0103r cadastral \u0219i apas\u0103 Caut\u0103.</p> <p>Introdu un număr cadastral și apasă Caută.</p>
<p className="text-xs mt-1 opacity-60"> <p className="text-xs mt-1 opacity-60">
Po\u021bi c\u0103uta mai multe parcele simultan, separate prin Poți căuta mai multe parcele simultan, separate prin
virgul\u0103. virgulă.
</p> </p>
</CardContent> </CardContent>
</Card> </Card>
@@ -1009,9 +1009,9 @@ export function SearchTab({
<Card> <Card>
<CardContent className="py-12 text-center text-muted-foreground"> <CardContent className="py-12 text-center text-muted-foreground">
<Loader2 className="h-10 w-10 mx-auto mb-3 animate-spin opacity-50" /> <Loader2 className="h-10 w-10 mx-auto mb-3 animate-spin opacity-50" />
<p>Se caut\u0103 proprietar...</p> <p>Se caută proprietar...</p>
<p className="text-xs mt-1 opacity-60"> <p className="text-xs mt-1 opacity-60">
Caut\u0103 mai \u00eent\u00e2i \u00een DB local (date \u00eembog\u0103\u021bite), apoi pe Caută mai întâi în DB local (date îmbogățite), apoi pe
eTerra. eTerra.
</p> </p>
</CardContent> </CardContent>
@@ -1036,7 +1036,7 @@ export function SearchTab({
}} }}
> >
<Plus className="mr-1 h-3.5 w-3.5" /> <Plus className="mr-1 h-3.5 w-3.5" />
Adaug\u0103 toate \u00een list\u0103 Adaugă toate în listă
</Button> </Button>
<Button <Button
size="sm" size="sm"
@@ -1047,7 +1047,7 @@ export function SearchTab({
} }
> >
<FileDown className="mr-1 h-3.5 w-3.5" /> <FileDown className="mr-1 h-3.5 w-3.5" />
Descarc\u0103 CSV Descarcă CSV
</Button> </Button>
</div> </div>
</div> </div>
@@ -1075,7 +1075,7 @@ export function SearchTab({
size="sm" size="sm"
variant="ghost" variant="ghost"
className="h-7 w-7 p-0" className="h-7 w-7 p-0"
title="Adaug\u0103 \u00een list\u0103" title="Adaugă în listă"
onClick={() => onClick={() =>
addToList(ownerResultToParcelDetail(r)) addToList(ownerResultToParcelDetail(r))
} }
@@ -1128,7 +1128,7 @@ export function SearchTab({
{r.suprafata && ( {r.suprafata && (
<div> <div>
<span className="text-xs text-muted-foreground block"> <span className="text-xs text-muted-foreground block">
Suprafa\u021b\u0103 Suprafață
</span> </span>
<span className="tabular-nums"> <span className="tabular-nums">
{typeof r.suprafata === "number" {typeof r.suprafata === "number"
@@ -1159,7 +1159,7 @@ export function SearchTab({
{r.categorieFolosinta && ( {r.categorieFolosinta && (
<div className="col-span-2"> <div className="col-span-2">
<span className="text-xs text-muted-foreground block"> <span className="text-xs text-muted-foreground block">
Categorii folosin\u021b\u0103 Categorii folosință
</span> </span>
<span className="text-xs"> <span className="text-xs">
{r.categorieFolosinta} {r.categorieFolosinta}
@@ -1169,7 +1169,7 @@ export function SearchTab({
{r.adresa && ( {r.adresa && (
<div className="col-span-2"> <div className="col-span-2">
<span className="text-xs text-muted-foreground block"> <span className="text-xs text-muted-foreground block">
Adres\u0103 Adresă
</span> </span>
<span>{r.adresa}</span> <span>{r.adresa}</span>
</div> </div>
@@ -1206,12 +1206,12 @@ export function SearchTab({
<Card> <Card>
<CardContent className="py-12 text-center text-muted-foreground"> <CardContent className="py-12 text-center text-muted-foreground">
<User className="h-10 w-10 mx-auto mb-3 opacity-30" /> <User className="h-10 w-10 mx-auto mb-3 opacity-30" />
<p>Introdu numele proprietarului \u0219i apas\u0103 Caut\u0103.</p> <p>Introdu numele proprietarului și apasă Caută.</p>
<p className="text-xs mt-1 opacity-60"> <p className="text-xs mt-1 opacity-60">
Caut\u0103 \u00een datele \u00eembog\u0103\u021bite (DB local) \u0219i pe eTerra. Caută în datele îmbogățite (DB local) și pe eTerra.
<br /> <br />
Pentru rezultate complete, lanseaz\u0103 &quot;Sync fundal \u2014 Pentru rezultate complete, lansează &quot;Sync fundal
Magic&quot; \u00een tab-ul Export. Magic&quot; în tab-ul Export.
</p> </p>
</CardContent> </CardContent>
</Card> </Card>
@@ -1377,15 +1377,15 @@ export function SearchTab({
{p.nrCad} {p.nrCad}
</td> </td>
<td className="px-3 py-2 text-xs"> <td className="px-3 py-2 text-xs">
{p.nrCF || "\u2014"} {p.nrCF || ""}
</td> </td>
<td className="px-3 py-2 text-right hidden sm:table-cell tabular-nums text-xs"> <td className="px-3 py-2 text-right hidden sm:table-cell tabular-nums text-xs">
{p.suprafata != null {p.suprafata != null
? formatArea(p.suprafata) ? formatArea(p.suprafata)
: "\u2014"} : ""}
</td> </td>
<td className="px-3 py-2 hidden md:table-cell text-xs truncate max-w-[300px]"> <td className="px-3 py-2 hidden md:table-cell text-xs truncate max-w-[300px]">
{p.proprietari || "\u2014"} {p.proprietari || ""}
</td> </td>
<td className="px-3 py-2 text-center"> <td className="px-3 py-2 text-center">
<TooltipProvider> <TooltipProvider>