feat(parcel-sync): sync-first architecture — DB as ground truth
- Rewrite export-bundle to sync-first: check freshness -> sync layers -> enrich (magic) -> build GPKG/CSV from local DB - Rewrite export-layer-gpkg to sync-first: sync if stale -> export from DB - Create enrich-service.ts: extracted magic enrichment logic (CF, owners, addresses) with DB storage - Add enrichment + enrichedAt columns to GisFeature schema - Update PostGIS views to include enrichment data - UI: update button labels for sync-first semantics, refresh sync status after exports - Smart caching: skip sync if data is fresh (168h / 1 week default)
This commit is contained in:
@@ -337,6 +337,7 @@ export function ParcelSyncModule() {
|
||||
const [syncingLayer, setSyncingLayer] = useState<string | null>(null);
|
||||
const [syncProgress, setSyncProgress] = useState("");
|
||||
const [exportingLocal, setExportingLocal] = useState(false);
|
||||
const refreshSyncRef = useRef<(() => void) | null>(null);
|
||||
|
||||
/* ── PostGIS setup ───────────────────────────────────────────── */
|
||||
const [postgisRunning, setPostgisRunning] = useState(false);
|
||||
@@ -597,6 +598,8 @@ export function ParcelSyncModule() {
|
||||
pollingRef.current = null;
|
||||
}
|
||||
setExporting(false);
|
||||
// Refresh sync status — data was synced to DB
|
||||
refreshSyncRef.current?.();
|
||||
},
|
||||
[siruta, exporting, startPolling],
|
||||
);
|
||||
@@ -699,6 +702,9 @@ export function ParcelSyncModule() {
|
||||
}
|
||||
}, [siruta]);
|
||||
|
||||
// Keep ref in sync so callbacks defined earlier can trigger refresh
|
||||
refreshSyncRef.current = () => void fetchSyncStatus();
|
||||
|
||||
// Auto-fetch sync status when siruta changes
|
||||
useEffect(() => {
|
||||
if (siruta && /^\d+$/.test(siruta)) {
|
||||
@@ -876,6 +882,8 @@ export function ParcelSyncModule() {
|
||||
pollingRef.current = null;
|
||||
}
|
||||
setDownloadingLayer(null);
|
||||
// Refresh sync status — layer was synced to DB
|
||||
refreshSyncRef.current?.();
|
||||
},
|
||||
[siruta, downloadingLayer, startPolling],
|
||||
);
|
||||
@@ -1775,7 +1783,7 @@ export function ParcelSyncModule() {
|
||||
Sync
|
||||
</span>
|
||||
</Button>
|
||||
{/* Direct GPKG from eTerra */}
|
||||
{/* GPKG (sync-first: syncs to DB if needed, then exports from DB) */}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
@@ -1783,6 +1791,11 @@ export function ParcelSyncModule() {
|
||||
onClick={() =>
|
||||
void handleExportLayer(layer.id)
|
||||
}
|
||||
title={
|
||||
localCount > 0
|
||||
? "Descarcă GPKG (din cache dacă e proaspăt)"
|
||||
: "Sincronizează + descarcă GPKG"
|
||||
}
|
||||
>
|
||||
{isDownloading ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
@@ -1793,21 +1806,6 @@ export function ParcelSyncModule() {
|
||||
GPKG
|
||||
</span>
|
||||
</Button>
|
||||
{/* Export from local DB */}
|
||||
{localCount > 0 && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
disabled={exportingLocal}
|
||||
onClick={() =>
|
||||
void handleExportLocal([layer.id])
|
||||
}
|
||||
title="Exportă din baza de date locală"
|
||||
className="text-violet-600 dark:text-violet-400"
|
||||
>
|
||||
<HardDrive className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2033,7 +2031,7 @@ export function ParcelSyncModule() {
|
||||
Descarcă Terenuri și Clădiri
|
||||
</div>
|
||||
<div className="text-xs opacity-70 font-normal">
|
||||
GPKG — terenuri.gpkg + cladiri.gpkg
|
||||
Sync + GPKG (din cache dacă e proaspăt)
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
@@ -2052,7 +2050,7 @@ export function ParcelSyncModule() {
|
||||
<div className="text-left">
|
||||
<div className="font-semibold">Magic</div>
|
||||
<div className="text-xs opacity-70 font-normal">
|
||||
GPKG îmbogățit + CSV cu proprietari, CF, adresă
|
||||
Sync + îmbogățire (CF, proprietari, adresă) + GPKG + CSV
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user