refactor(parcel-sync): split 4800-line module into 9 files + Harta tab + enrichment views
Split parcel-sync-module.tsx (4800 lines) into modular files: - Orchestrator (452 lines): shared state (session, UAT, sync) + tab routing - Types + helpers, ConnectionPill, 6 tab components (search, layers, export, database, cf, map) New ParcelSync Harta tab: - UAT-scoped map: zoom to extent, filter parcels/buildings by siruta - Data-driven styling via gis_terenuri_status enrichment overlay (green=no enrichment, dark green=enriched, blue outline=building, red=no legal docs) - Reuses Geoportal components (MapViewer, SelectionToolbar, FeatureInfoPanel, BasemapSwitcher) - Export DXF/GPKG for selection, legend New PostGIS views (gis_terenuri_status, gis_cladiri_status): - has_enrichment, has_building, build_legal columns from enrichment JSON - Auto-created via /api/geoportal/setup-enrichment-views - Does not modify existing Geoportal views New API: /api/geoportal/uat-bounds (WGS84 bbox from PostGIS geometry) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Loader2,
|
||||
LogOut,
|
||||
Wifi,
|
||||
WifiOff,
|
||||
AlertTriangle,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/shared/components/ui/dropdown-menu";
|
||||
import { cn } from "@/shared/lib/utils";
|
||||
import type { SessionStatus } from "./parcel-sync-types";
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Connection Status Pill */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
export function ConnectionPill({
|
||||
session,
|
||||
connecting,
|
||||
connectionError,
|
||||
onDisconnect,
|
||||
}: {
|
||||
session: SessionStatus;
|
||||
connecting: boolean;
|
||||
connectionError: string;
|
||||
onDisconnect: () => void;
|
||||
}) {
|
||||
const elapsed = session.connectedAt
|
||||
? Math.floor(
|
||||
(Date.now() - new Date(session.connectedAt).getTime()) / 60_000,
|
||||
)
|
||||
: 0;
|
||||
const elapsedLabel =
|
||||
elapsed < 1
|
||||
? "acum"
|
||||
: elapsed < 60
|
||||
? `${elapsed} min`
|
||||
: `${Math.floor(elapsed / 60)}h ${elapsed % 60}m`;
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"flex items-center gap-2 rounded-full border px-3 py-1.5 text-xs font-medium transition-all",
|
||||
"hover:shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||
session.connected
|
||||
? "border-emerald-200 bg-emerald-50/80 text-emerald-700 dark:border-emerald-800 dark:bg-emerald-950/40 dark:text-emerald-400"
|
||||
: session.eterraMaintenance
|
||||
? "border-amber-200 bg-amber-50/80 text-amber-600 dark:border-amber-800 dark:bg-amber-950/40 dark:text-amber-400"
|
||||
: connectionError
|
||||
? "border-rose-200 bg-rose-50/80 text-rose-600 dark:border-rose-800 dark:bg-rose-950/40 dark:text-rose-400"
|
||||
: "border-muted-foreground/20 bg-muted/50 text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{connecting ? (
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
) : session.connected ? (
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" />
|
||||
<span className="relative inline-flex h-2 w-2 rounded-full bg-emerald-500" />
|
||||
</span>
|
||||
) : session.eterraMaintenance ? (
|
||||
<AlertTriangle className="h-3 w-3" />
|
||||
) : connectionError ? (
|
||||
<WifiOff className="h-3 w-3" />
|
||||
) : (
|
||||
<Wifi className="h-3 w-3 opacity-50" />
|
||||
)}
|
||||
<span className="hidden sm:inline">
|
||||
{connecting
|
||||
? "Se conecteaz\u0103\u2026"
|
||||
: session.connected
|
||||
? "eTerra"
|
||||
: session.eterraMaintenance
|
||||
? "Mentenan\u021b\u0103"
|
||||
: connectionError
|
||||
? "Eroare"
|
||||
: "Deconectat"}
|
||||
</span>
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align="end" className="w-72 p-0">
|
||||
{/* Status header */}
|
||||
<div
|
||||
className={cn(
|
||||
"px-3 py-2.5 border-b",
|
||||
session.connected
|
||||
? "bg-emerald-50/50 dark:bg-emerald-950/20"
|
||||
: "bg-muted/30",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<DropdownMenuLabel className="p-0 text-xs font-semibold">
|
||||
Conexiune eTerra
|
||||
</DropdownMenuLabel>
|
||||
{session.connected && (
|
||||
<span className="text-[10px] text-emerald-600 dark:text-emerald-400 font-mono">
|
||||
{elapsedLabel}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{session.connected && session.username && (
|
||||
<p className="text-[11px] text-muted-foreground mt-0.5 truncate">
|
||||
{session.username}
|
||||
</p>
|
||||
)}
|
||||
{connectionError && (
|
||||
<p className="text-[11px] text-rose-500 mt-0.5">
|
||||
{connectionError}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Maintenance banner */}
|
||||
{!session.connected && session.eterraMaintenance && (
|
||||
<div className="px-3 py-3 text-xs border-b bg-amber-50/50 dark:bg-amber-950/20">
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertTriangle className="h-3.5 w-3.5 text-amber-500 mt-0.5 shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium text-amber-700 dark:text-amber-400">
|
||||
eTerra este \u00een mentenan\u021b\u0103
|
||||
</p>
|
||||
<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
|
||||
reactivat\u0103 automat c\u00e2nd serviciul revine online.
|
||||
</p>
|
||||
{session.eterraHealthMessage && (
|
||||
<p className="mt-1 text-[10px] opacity-60 font-mono">
|
||||
{session.eterraHealthMessage}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Info when not connected (and not in maintenance) */}
|
||||
{!session.connected &&
|
||||
!connectionError &&
|
||||
!session.eterraMaintenance && (
|
||||
<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 className="mt-1 text-[11px] opacity-70">
|
||||
Creden\u021bialele sunt preluate din configurarea serverului.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error detail (only when NOT maintenance) */}
|
||||
{!session.connected &&
|
||||
connectionError &&
|
||||
!session.eterraMaintenance && (
|
||||
<div className="px-3 py-3 text-xs text-muted-foreground">
|
||||
<p>
|
||||
Conexiunea automat\u0103 a e\u0219uat. Verific\u0103 creden\u021bialele din
|
||||
variabilele de mediu (ETERRA_USERNAME / ETERRA_PASSWORD).
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Connected — active jobs info + disconnect */}
|
||||
{session.connected && (
|
||||
<>
|
||||
{session.activeJobCount > 0 && (
|
||||
<div className="px-3 py-2 border-b bg-amber-50/50 dark:bg-amber-950/20">
|
||||
<p className="text-[11px] text-amber-700 dark:text-amber-400">
|
||||
<span className="font-semibold">
|
||||
{session.activeJobCount} job
|
||||
{session.activeJobCount > 1 ? "-uri" : ""} activ
|
||||
{session.activeJobCount > 1 ? "e" : ""}
|
||||
</span>
|
||||
{session.activeJobPhase && (
|
||||
<span className="opacity-70">
|
||||
{" "}
|
||||
\u2014 {session.activeJobPhase}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<DropdownMenuSeparator className="m-0" />
|
||||
<div className="p-1.5">
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-xs text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
|
||||
onClick={onDisconnect}
|
||||
>
|
||||
<LogOut className="h-3 w-3" />
|
||||
Deconectare
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user