71cfc29f9ad4046cc69d52fda964a6853e266800
24 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
71cfc29f9a |
feat(geoportal-v2): export toolbar + Semnez ca picker + CF intern/Extras split
V2 panel toolbar replaces the single "Comandă CF" button with two rows:
[Încadrare] [Pl. situație] [Coord.] [DXF] ← 4 exports
[CF intern] [Extras CF] ← 2 CF flows
Each export button pops an inline modal:
- PIZ / PAD: SignAsPicker (PFA / PJA radio list, manual-add inline,
co-signer slot on PIZ) + basemap toggle (google / orto for PIZ).
- Coord / DXF: no picker — single-click download via JWT proxy.
"CF intern" is the free copycf flow from eTerra (proxied via gis-api);
"Extras CF" keeps the existing CfOrderModal (1 credit ePay). The two
modes are now visually balanced as a 2-button row.
Sign-as picker rows merge user-owned Signatory table entries with the
SIGN_AS_DEFAULT_OPTIONS env-driven fallback (org-wide hardcoded options;
defaults seed two Studii de teren entries — Tiurbe PFA + SRL PJA). New
rows added via the picker's "Adaugă autorizație" inline form write to
the Signatory table; ENV rows are read-only.
Architots side ships fully:
- prisma Signatory model + ALTER TABLE applied (per the schema-drift
feedback memory).
- /api/sign-as-options (GET, POST) + /api/sign-as-options/[id]
(PATCH, DELETE).
- /api/cf-intern/order and /api/gis/parcel/[id]/{piz,pad,coords,dxf}
proxy routes — auth check + JWT forward, stream binary back.
- gis-api thin client extended with the matching exports.* namespace.
Until the gis-api endpoints ship (next session — full spec in
docs/plans/005-gis-api-export-endpoints.md), each export proxy returns
501 "…urmează" with a Romanian message so the modal shows what's
coming instead of a hard error.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
36840f31f6 |
fix(geoportal-v2): gate condo-owners on IS_CONDOMINIUM + visible empty state
Two issues from Marius's cladiri screenshots: 1. APARTAMENTE "se încarcă…" sat for ~10s then vanished — useEffect fired for every CLADIRI click regardless of whether the building was actually a condo. Orchestrator's /api/v1/building/condo-owners hit eTerra live, got back an empty list for non-condos, returned [], section auto-hid → user saw the spinner blink and disappear. New gate: useEffect waits for `detail` to land, then reads IS_CONDOMINIUM / PARCEL_IS_CONDOMINIUM from enrichment. If neither is `1`, skip the fetch entirely. Non-condos no longer pay the 10s eTerra round-trip just to show nothing. 2. EMPTY CONDO LISTS WERE HIDING SILENTLY — for buildings flagged condo where ANCPI hasn't registered units yet, the section would still vanish (`condoOwners.length > 0` check). Now: if the fetch returns [] AND the building is a condo, render the section with "Fără apartamente înregistrate la ANCPI." That's the truthful UX. Same fallback when the fetch errors — treat as empty rather than swallow. Render trigger flipped from (condoLoading || (condoOwners && condoOwners.length > 0)) to (condoLoading || condoOwners != null) so the section shows whenever the gate decided to fetch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
c7cf1aee49 |
feat(geoportal-v2): re-enable deep-enrich for cladiri + forward layerId
Orchestrator team shipped CLADIRI support in
/api/v1/parcel/enrich (PR4):
- DeepEnrichLayerId enum + optional layerId in input
- inferLayerIdFromCadref() regex /-C\d+(-U\d+)?$/ — covers apartments too
- loadFeature() now layer-aware (no more hardcoded
layerId='TERENURI_ACTIVE')
- fetchBuildingsForParcel skipped when running for a building
- HTTP layer accepts layerId in body
Confirmed live via gis-api container logs — manual-override calls
on "304629-C2" / "304629-C3" (CLADIRI) reach orchestrator and complete.
Architots side:
1. Re-enabled the "Actualizează" / "Încarcă din ANCPI" button for
cladiri. The disabled+tooltip gate I added last commit was the
right thing while the orchestrator path didn't accept buildings —
no longer needed.
2. refreshFromAncpi now forwards feature.layerId in the request body.
Skips the orchestrator's dash-suffix auto-detect — more reliable
than parsing "-C3" out of cadref each time.
3. ParcelRefBody type gained optional manualOverride + layerId fields
so callers can pass both through the thin client.
Note from orchestrator team: extractEnrichment populates only the
generic NR_CF / ADRESA / PROPRIETARI / PROPRIETARI_VECHI / SOLICITANT
keys today. The CLADIRE_TYPE / CLADIRE_DESTINATIE / CLADIRE_OBSERVATII
/ CLADIRE_NIVELURI etc. fields the V2 building panel renders come
from a different orchestrator pipeline (building-tech), already wired
+ populating those rows when the orchestrator's bulk-enrich runs.
Single-parcel deep-enrich for a building updates NR_CF/ADRESA/
PROPRIETARI but leaves CLADIRE_* alone unless that pipeline runs in
parallel — separate iteration if needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
49dcdadc44 |
fix(geoportal-v2): cladiri-aware deep-enrich button + clearer error
Marius hit "Parcela nu există în baza centrală gis_core." on building
304629-C3 (siruta=54975) — feature exists in gis_core (verified, id
70fd7485-fa39-4c38-9074-cfad154ed288) but orchestrator's
parcel-deep-enrich hardcodes layerId='TERENURI_ACTIVE' in its
pre-flight lookup:
// gis-sync-orchestrator src/lib/parcel-deep-enrich/fetch.ts L232–240
SELECT id, attributes, enrichment, "enrichedAt"
FROM gis_core."GisFeature"
WHERE "layerId" = 'TERENURI_ACTIVE'
AND siruta = $1 AND "cadastralRef" = $2
LIMIT 1
→ throws ParcelNotFoundError for any CLADIRI cadref. Architots
mapped that to a misleading "doesn't exist" message even though
by-ref correctly retrieved the building row up-front.
Architots-side fix (the proper fix is on orchestrator):
1. Error message for `parcel_not_found` now branches on
feature.layerId. For CLADIRI_ACTIVE the user sees:
"Deep-enrich nu suportă încă construcțiile — datele clădirii
vin via parcela părinte (gis-api orchestrator side)."
No more "parcela nu există" confusion when the parcel obviously
does.
2. "Actualizează" / "Încarcă din ANCPI" button in the Date eTerra
header is now disabled when isCladiri. Tooltip explains why.
The button is the one that triggers refreshFromAncpi →
/parcel/enrich → the very orchestrator path that doesn't handle
buildings.
Result: building panels show whatever's in gis_core (CLADIRE_TYPE,
CLADIRE_DESTINATIE, etc. if previously enriched via eterra.live's own
flow) and don't pretend they can be re-fetched.
Next step (orchestrator session): teach loadFeature() to accept an
optional layerId param OR auto-detect from cadref-suffix pattern
(/-C\\d+$/) so /parcel/enrich works for both layers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
52c31e3c4d |
feat(geoportal-v2): UAT name + SOLICITANT into Înscriere + Google Maps inline
Iteration on the info panel per Marius's feedback.
1. UAT NAME IN HEADER
New uat-lookup.ts hook loads public/uat.json (3,186 rows, ~95 KB,
one-shot fetch + Map cache + subscribers) and exposes
useUatName(siruta). Header reads:
Terenuri · 2.400 m² · FELEACU · 57582
instead of just "SIRUTA 57582". The localitate name lives in front
of the bare siruta number (muted, smaller weight) — siruta is
still there for ops + tooltip, just not the primary signal.
2. SOLICITANT MOVED INTO ÎNSCRIERE
Was rendered as a prominent User-icon line right above PROPRIETARI,
which led to "BOJAN ELENA = current owner?" confusion. The two
fields semantically differ: SOLICITANT is the person who filed the
most recent ANCPI application (e.g. the new buyer initiating a
transfer), PROPRIETARI is who's currently registered as owner. Now
SOLICITANT is collapsed into the existing Înscriere <details> next
to TIP_INSCRIERE / DATA_CERERE / ACT_PROPRIETATE — the
registration-metadata bucket where it belongs.
3. GOOGLE MAPS INLINE WITH ADDRESS
When ADRESA exists, the Google Maps text-link sits right of the
address (using feature.lat/lng for the query). One-tap go-to-map
without a separate Localizare section.
4. LOCALIZARE → COLLAPSIBLE
Bottom Localizare card becomes a closed-by-default <details>.
Inside: WGS84 lat/lng, SIRUTA, and a separate Google Maps link.
ID (objectId) shows in the summary line. Mirrors eterra.live's
approach. The redundant Feleacu/coords echo at the bottom is
gone — coords are still one click away when needed.
NOT in this commit (parked for follow-up):
- PIZ / Plan situație / Coord. / DXF actions — would mean porting
eterra.live's three /api/geoportal/{piz,pad,coords-xlsx} document
generators. Substantial work (mapbox-static-image render +
server-side PDF layout); needs its own session.
- CF intern (gratuit) vs Extras CF (1 credit) split — current
"Comandă CF" modal already handles both pool/connection states,
but the two-button visual split mirroring eterra.live's catalog-
hit fast path is a smaller follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
653cffeee3 |
fix(geoportal-v2): use siruta from search response — no more N+1 misses
Marius hit "Date ne-încărcate" / "Parcela nu există" on Feleacu parcels
(SIRUTA 57582, cadref 61745 / 61746) even though gis_core has 28 rich
enrichment keys for them. Root cause: 232 features in gis_core share
cadref `61745` across different UATs. Our find proxy was doing:
1. gisApi.search(cad, limit=20)
2. for each candidate (up to 20): parcela.get(id), check siruta
Feleacu's parcel sat past position 20 in the search ranking, so we
never tried parcela.get on it — fallback returned a sibling parcel
with 0 keys (the "Date ne-încărcate" UI) or no readable candidate at
all (the "nu există în DB centrală" 404 UI).
This was wrong on two counts:
1. WE WERE DOING N+1: gis-api's /api/v1/search already returns siruta
per feature (see gis-api src/routes/search.ts:41). One round-trip
would have given us the answer; we just weren't reading the field.
Updated src/lib/gis-api-client.ts to declare siruta in the
response type + bumped default limit from 20 → 50 (gis-api's
server-side cap).
2. WE WERE FAILING SILENTLY: when search-cap was the actual bottleneck
the proxy returned 404 with no hint that gis-api had more
data we just couldn't reach. New find proxy:
- First pass: direct match on cadref + layerId + siruta from the
search response. Single follow-up parcela.get to fetch full
detail. No more sequential probing.
- If no direct match: log + report distinctively. When the search
returned MAX_LIMIT (50) features all with the same cadref, we
return 422 search_limit_exceeded with a hint about the missing
siruta filter. Otherwise 404 (genuinely not in gis_core).
3. Panel surfaces the 422 with a plain-language explanation rather
than the raw "Eroare: ..." dump.
For the long-term fix: gis-api needs either a `siruta` query param on
/api/v1/search OR a dedicated /api/v1/parcela/by-ref?siruta&cad&layerId
endpoint that does a single indexed lookup. Today's patch handles the
top-50 case (was top-20); the 422 surfaces the residual cases for
follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
52e16e7807 |
fix(cf-modal): portal to body + auto-close on parcel switch
Two related issues with the modal when the user kept clicking around the map while in CF order mode: 1. LAYOUT BREAK (Marius screenshot — modal header clipped above viewport): The V2 panel wrapper uses `backdrop-blur-md`. Per CSS spec, an element with non-none backdrop-filter establishes a containing block for `fixed`-positioned descendants. So `fixed inset-0` on the modal was relative to the panel (top-right, ~50px tall at min) instead of the viewport — the modal anchored to the panel and overflowed up. Fix: render via React's createPortal to document.body. The modal now escapes the panel's stacking context entirely and centers in the viewport. Also bumped z-index from 50 to 100 so the modal stays above the MapLibre canvas + panel itself. 2. STATE CARRY-OVER: clicking a different parcel while the modal was open silently re-targeted the modal at the new parcel — same modal showing different cadref/sold mid-flow could mislead the user about which parcel they were buying CF for. Fix: FeatureInfoPanel now has a useEffect that closes the modal when feature.cadastralRef / siruta / layerId changes. Modal stays scoped to a single decision. SSR guard: if (typeof document === "undefined") return null; before the portal call so the modal doesn't blow up during server-side render. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
5e4618b309 |
feat(geoportal-v2): inline CF order modal — confirmation + animated steps
Marius: click "Comandă CF" from the card itself, no new-tab to
parcel-sync. Show "Ești sigur? Costă 1 credit, mai ai X" first.
Animate the order through its phases until done.
New component cf-order-modal.tsx — a 7-state machine over a single
shadcn-style dialog:
loading-status — checks /api/ancpi/session for connection + credits
not-connected — ePay session offline → prompt to connect via
parcel-sync (the only place credentials live)
no-credits — 0 credits, can't proceed
ready — confirmation: 1 credit cost, current balance,
projected balance after the order, all in
rounded chips with Coins icon
placing — POST /api/ancpi/order, spinner on step 1
processing — poll /api/ancpi/orders every 3s until status
becomes completed/done/minioPath populated.
Shows live elapsed seconds; 90s timeout falls
through to error with "verifică din nou peste
câteva minute".
done — checkmark anim + "Descarcă PDF" if document URL
came back
error — destructive panel + Reîncearcă button
Animations (tailwindcss-animate utilities):
- Modal backdrop: fade-in 200ms
- Modal card: zoom-in-95 + slide-in-from-bottom 200ms
- Step rows: active row gets primary-tinted bg + Loader2 spin,
done rows turn emerald + Check icon zooms in 300ms
- Success/error final state: rounded badge + icon zooms in 500ms
Footer adapts per phase: Anulează+Confirmă (ready), Conectează ePay
(not-connected), Închide (loading/no-credits), Închide fereastra
(placing/processing — order continues in bg), Gata (done), Închide+
Reîncearcă (error).
Wires into feature-info-panel by replacing the "open /parcel-sync"
click handler with setCfModalOpen(true). Modal mounts at the
panel's root with fixed positioning + z-50 so it overlays the map.
Backdrop click dismisses except during placing/processing.
Uses the legacy /api/ancpi/* endpoints (not /api/cf/* gis-ac route)
per Marius's earlier decision to keep credit tracking on his own
ePay session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
8f86bab337 |
fix(geoportal-v2): remove eterra.live links + fix Actualizează wrap
Per Marius's feedback: eterra.live is a separate product, ArchiTools
shouldn't link out to it. Removed both touch-points:
1. The "eterra.live" button I'd added beside "Actualizează" in the
Date eTerra header — gone. This was also breaking layout (the
second button forced "acum câteva secunde" to wrap into "acum /
câteva / secunde" stacked above the button text).
2. The "Export GPKG" action in the toolbar — gone. It used to deep-
link to eterra.live/harta?…&autoexport=geopackage. Toolbar now
holds just "Comandă CF" which stays internal (/parcel-sync).
While in there:
- Actualizează button: `whitespace-nowrap` + `shrink-0` so it stops
wrapping when the panel is at min-width.
- Dropped the inline " · acum X min" beside the button label —
cleaner button, less truncation risk.
- Resurfaced the relative time as a small line at the bottom of the
Date eTerra body ("Actualizat din ANCPI · acum 3 min"). Same info,
no layout pressure.
- Cleaned unused lucide imports (Download, ExternalLink, Hash,
Layers, CalendarDays) that were leftover from the removed
eterra.live button + Cladire icon experiments.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
3004790ad2 |
feat(geoportal-v2): cladire characteristics + eterra.live link + collapsible Înscriere
Per Marius's iteration on the panel: 1. AUTO-ENRICH DISABLED — the panel no longer fires /parcel/enrich on sparse-data load. Refresh only via the explicit "Actualizează" button in the Date eTerra header. Keeps the eTerra account pool safe from browse-spam (was draining 500/h on rapid clicks). 2. eterra.live link button — sits beside "Actualizează" in the Date eTerra header. Opens https://eterra.live/harta?siruta=...&cad=... in a new tab so the user can cross-check the full eterra.live panel. 3. ÎNSCRIERE collapsible — Tip înscriere / Data cererii / Act proprietate now hide inside a <details> closed by default (per the highlighted-block screenshot). Keeps the "above the fold" info trimmed to what matters at a glance. 4. CARACTERISTICI CORP — new section, only rendered for CLADIRI_ACTIVE clicks. Shows the cladire-specific enrichment fields the orchestrator populates after a deep-enrich: - Chip row: tip / destinație / subtype (chips) + Condominium chip with unit count + Cu/Fără acte status pill - 3-col metric strip: Regim înălțime / Niveluri / An construire - Suprafață CF, CF IE, Clasă energetică, Părți comune (rows) - Observații (multi-line InfoBlock) Fields wired: CLADIRE_TYPE, CLADIRE_DESTINATIE, CLADIRE_SUBTYPE, CLADIRE_REGIM, CLADIRE_NIVELURI, CLADIRE_AN_CONSTRUIRE, CLADIRE_AREA_CF, CLADIRE_OBSERVATII, CLADIRE_LANDBOOK_IE, CLADIRE_COMMON_PARTS, CLADIRE_UNITS_NO_ANCPI, CLADIRE_ENERGETIC_CLASS, IS_LEGAL_BUILDING, IS_CONDOMINIUM. All show only when populated (no empty "-" rows). LABEL map extended with Romanian translations. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4f38fd1070 |
feat(geoportal-v2): compact eterra.live-style layout + buildings list
Significant restructure of the parcel info panel based on Marius's
side-by-side comparison with eterra.live. Same data + same workflow,
much denser layout.
Layout changes:
1. HEADER — status dot + cadref + small uat/area/SIRUTA line. Removed
the redundant "Activ"/"Inactiv" chip (the dot is the signal).
Building suffixes (C1, C2…) still resolve via the search-by-cadref
path; header shows the full cadref ("354686-C1") so the user sees
both parent and suffix.
2. CARACTERISTICI — chips row only; tighter padding (px-2 py-1.5
vs px-3 py-2.5). Same intravilan / categorie / corpuri.
3. METRIC STRIP — new. Three cells in a single divided pill: GIS /
2D eTerra / Legală. Same pattern eterra.live uses. Saves a whole
section worth of vertical space.
4. DATE ETERRA CARD — wrapped in a bordered subtle-bg container with
the refresh button INLINE in the section header (vs at the bottom
of the panel). Shows "acum X min" relative time when enriched.
Two-column NR. CF + Nr. topo. Adresă with pin icon. Solicitant
with user icon. Proprietari as InfoBlock (multi-line preserved).
Foști proprietari as <details> collapsible (closed by default).
Înscriere group (tip / data / act) as a small subsection.
5. CONSTRUCȚII LIST — new. For TERENURI parcels, fetches the
building siblings via gisApi.search(parentCadref) + filter on
"<parent>-" prefix. Renders BuildingRow per cladire:
- Icon (Home / Building2 / Factory / Warehouse from destinatie)
- C1/C2/C3… suffix (mono, font-semibold)
- Area
- "Cu acte" (green) / "Fără acte" (amber) / "Necunoscut"
pill from BUILD_LEGAL / PARCEL_HAS_LANDBOOK enrichment
Click row → onSelectFeature switches panel to that building.
Lazy isLegal hydration: row first shows "Necunoscut", then
parallel parcela.get for each building fills the pill (5ms per
cache hit, no blocking).
6. APARTAMENTE — same content as before (for CLADIRI clicks), now
sits beside Construcții in the same flow. Header consistent with
the other section labels.
7. LOCALIZARE — moved to a single tight strip (lat/lng + Google
Maps link). Removed the SIRUTA repetition since it's already in
the header.
8. ACTIONS toolbar — compressed. Removed the in-toolbar "Citește din
ANCPI" button since the refresh button is now inside the Date
eTerra card header (where it belongs contextually). Kept Export
GPKG + Comandă CF.
GeoportalV2 wires onSelectFeature={setClicked} so building rows
propagate. Building clicks reuse the same panel + same auto-enrich
flow — the second feature.layerId === CLADIRI_ACTIVE branch in the
condo-owners useEffect kicks in for buildings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
a4f61bf3d8 |
feat(geoportal-v2): manual fetch flag + friendlier pool-exhausted error
Two operational gaps observed after PR3 deep-enrich rollout:
1. Raw \"Eroare: no_available_account\" surfaced when the eTerra
account pool hit its hourly quota. Replace with a plain-language
note ("Pool-ul ANCPI e temporar epuizat — încearcă peste câteva
minute"). Same friendly treatment for the other common
orchestrator errors: no_immovable_match, parcel_not_found,
eterra_fetch_failed.
2. Marius wants the auto-trigger (fires on sparse-data load) and the
explicit "Citește din ANCPI" button to be separable on the
orchestrator side. Casual map browsing burns through the 500/h
quota with auto-triggers; a working session that needs 20-30
specific parcels shouldn't be starved.
refreshFromAncpi now takes { manual?: boolean }. The button passes
manual: true → request body includes manualOverride: true. The
auto-trigger useEffect calls it with no argument (manual defaults
to false). gis-api / orchestrator can later route manualOverride
to a separate-quota bucket or skip the per-hour check entirely.
Until then the flag is harmless (orchestrator ignores unknown
fields).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
02a466ccaa |
feat(geoportal-v2): swap refresh path to /parcel/enrich (deep-enrich)
gis-api session shipped PR3 (gis-api 09f1ab8 + gis-sync-orchestrator
0371d81): new POST /api/v1/parcel/enrich does the full eTerra
round-trip (searchImmovableByIdentifier → fetchDocumentationData
→ fetchImmovableParcelDetails) and merges NR_CF / ADRESA / PROPRIETARI
+ 20-plus fields into gis_core.GisFeature.enrichment with a 30-day
cache. Verified on 266888 + 328607 → 27 keys with full PII.
Wired in three places:
1. src/lib/gis-api-client.ts — gisApi.parcel.enrich({siruta,
cadastralRef, force?}) thin wrapper.
2. src/app/api/gis/parcel/enrich/route.ts — architots-side proxy,
matches the parcel/tech pattern (auth check → forward → bubble up
GisApiError status codes).
3. src/modules/geoportal/v2/feature-info-panel.tsx — refreshFromAncpi
now POSTs to /api/gis/parcel/enrich instead of /api/gis/parcel/tech.
After the orchestrator returns, the panel re-fetches the canonical
record via parcela.get (when uuid known) or parcela.find (when
not), so it sees exactly what gis_core stores rather than the
orchestrator response shape.
The existing auto-trigger (fires when detail has no NR_CF/ADRESA/
PROPRIETARI) now actually fills those fields. Subsequent clicks on the
same parcel hit gis-api's 30-day cache (5ms vs 1-2s live fetch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
87f9d72e4f |
feat(geoportal-v2): auto-fetch enrichment when DB only has tech keys
Parcel 328607 in Cluj-Napoca (and many others) is in gis_core with
only 10 enrichment keys, all tech-level (PARCEL_HAS_LANDBOOK,
PARCEL_IS_CONDOMINIUM, etc.) — no NR_CF, no ADRESA, no PROPRIETARI.
The panel renders correctly but with nothing of substance shown.
User had to manually click "Citește din ANCPI" to backfill.
Now auto-fires the fetch when:
- Panel mounts a fresh detail (or after parcel switch)
- AND enrichment lacks ALL three of NR_CF / ADRESA / PROPRIETARI
- AND we haven't already auto-fetched for this parcel this tab
session (sessionStorage dedupe keyed by uuid OR siruta+cad+layerId)
Visible feedback while it runs: a quiet "Se preiau date suplimentare
din ANCPI…" strip below the loading area. The user can keep reading
whatever is already on screen.
Side fixes:
- refreshFromAncpi → useCallback (stable deps) so it can sit in the
auto-trigger useEffect's dep array without infinite loops.
- Refresh path now uses /api/gis/parcela/find as the fallback when no
uuid is available, matching the initial-load logic. The old
orchestrator-shape projection still exists as a last-resort fallback
but is rarely hit.
Note: orchestrator's parcel/tech only re-populates PARCEL_* tech
fields. Truly enriching rich PII (NR_CF/ADRESA/PROPRIETARI) needs the
"deep enrich" orchestrator path which the gis-api proxy contract
doesn't expose yet — separate gis-api task. So parcels that only ever
got tech-level enrichment will stay at tech-level even after this
auto-fetch. The visible improvement is: parcels that DO have rich
data load it in the first second instead of needing a manual click.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
342bdca648 |
fix(geoportal-v2): structured panel sections + readable labels (back to basics)
Marius's feedback: "datele arată foarte ciudat" — the flat dl renderer
showed enrichment keys in whatever order gis-api returned them, with
raw key names (PARCEL_POSTAL_NO instead of "Nr. poștal") and no visual
hierarchy. With 20+ keys the important things (NR_CF, ADRESA,
PROPRIETARI) ended up below the fold under PARCEL_* tech metadata.
Restructure to mirror eterra.live's parcel-info layout:
1. HERO BLOCK at the top of the data area
- "Nr. Carte Funciară" big mono number (NR_CF)
- NR_CF_VECHI shown below when different
- Adresă with pin icon and proper text wrapping
2. NAMED SECTIONS (rendered in fixed order, only when populated)
- Proprietari — splits comma/semicolon-separated names into a list
- Cadastru — NR_CAD, NR_TOPO, PARCEL_TOPO_NO
- Suprafețe — SUPRAFATA_R, SUPRAFATA_2D, PARCEL_LEGAL_AREA, SUPRAFATA
(values parsed + " m²" suffix)
- Înscriere — SOLICITANT, TIP_INSCRIERE, DATA_CERERE, DOC
- (CF/Adresă removed from the list because they're in the hero)
3. CARACTERISTICI CHIPS (existing) — Intravilan / Categorie / nr corpuri /
status stay at the top. CARACTERISTICI_KEYS excludes them from the
sections below so nothing is shown twice.
4. DETALII TEHNICE (collapsed by default) — anything not in a named
section: PARCEL_HAS_LANDBOOK, PARCEL_IS_CONDOMINIUM,
PARCEL_TECH_ENRICHED_AT, etc. Renders with friendly labels (Da/Nu
for flags, dd/mm/yyyy hh:mm for dates) instead of "1" / ISO strings.
5. Value renderers per key type:
- formatAreaValue("456" | "456.06" | "456 mp") → "456 m²"
- formatFlag(0/1, da/nu) → "Da" / "Nu"
- formatDate(ISO) → "19.05.2026, 04:40" (Romanian locale)
- parseOwners("MATHBOUT MOHAMED MAHER, DR.") → list items
6. enrichedAt moved to a single small line at the bottom of the data
area instead of a per-section caption.
LABEL map covers all 25 enrichment keys observed in DB. Anything new
falls back to raw key name (visible — easy to spot and add).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
a23ba1957f |
fix(geoportal-v2): silent auto re-grant on scope-missing 403
Removes the "Re-loghează-te" button + scope-mismatch warning prose.
On 403 from /api/gis/parcela/find the panel now:
1. Checks sessionStorage flag — false on first 403 of the tab
2. Sets the flag, fires signIn("authentik", { callbackUrl: current
URL }) silently. For an SSO'd user this is a sub-second Authentik
redirect cycle that mints a fresh access_token with the right
scope claims, lands the user back on the same panel, and the
re-mount fetches successfully — no visible message, no prompt.
3. If another 403 happens after the retry (i.e., Authentik genuinely
can't grant the scope — config issue, not a stale-token issue),
falls through to a discreet "Datele detaliate nu pot fi încărcate
momentan." note. No call-to-action, no jargon.
4. On any successful 200 fetch, clears the sessionStorage flag so a
future 403 in the same tab can re-trigger the silent retry.
Per Marius: "vreau doar să meargă, safe și fix" — no auth-flow
chrome shown to the user. The recovery is part of the system's
correctness contract, not a feature for the user to manage.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
71df1ee9ec |
fix(geoportal-v2): surface scope-insufficient instead of silent 404
The other-session's gis-api investigation found that gis-api is
working correctly — full/basic/none scopes all behave per spec.
The bug was in our /api/gis/parcela/find proxy: when EVERY candidate
returned 403 from gis-api (because the caller's JWT carried no
enrichment_scope claim), the proxy swallowed the 403s and returned
silent 404. The panel then rendered the "not in central DB" empty
state instead of prompting re-login.
This was the case for Marius today — his pre-refresh-fix session
held a token without the enrichment claim. After the auth self-heal
fix (commit
|
||
|
|
7afba6e1a9 |
fix(geoportal-v2): siruta-aware parcela lookup (B1 round 2)
Previous fix searched by cadastralRef and picked the first layerId-matching result. But cadastral refs collide across UATs: "354686" exists in multiple counties. The Cluj-Napoca f9bf2ca4-... parcel with full enrichment got passed over for a same-cad parcel in another UAT that has no enrichment → panel rendered header + "Caracteristici" with empty Intravilan, no "Date eTerra" section. New server-side /api/gis/parcela/find?siruta&cad&layerId proxy: - gisApi.search(cad) → filter by layerId → up to ~20 candidates - For each candidate, parcela.get and check stored siruta - Return the siruta-matching detail - Fallback: first readable candidate (so the panel still has data even if siruta mismatch — better than empty) Panel useEffect simplified: fast path = parcela.get by uuid when the tile has one, slow path = parcela/find when not. 404 from find sets the "not in central DB yet" empty state (user can hit Citește din ANCPI to trigger orchestrator live-fetch). Diagnostic logs: [gis-parcela-find] siruta=… cad=… layerId=… candidates=N + per-hit "has_enrich=true keys=N" so we can tell from container logs whether the right parcel resolved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b5eff5acc1 |
fix(geoportal-v2): rewrite info panel — auto-fetch + sections + condo + basic mode
Root cause of B1 (panel showed "Apasă din ANCPI" even with full enrichment
in DB): PMTiles overview tiles don't carry the GisFeature uuid, only
siruta/cadastral_ref/object_id. The panel's useEffect bailed out at
`!feature.id` and never fetched. So the data was there, the UI just
refused to ask for it.
Fix: when the click feature has no uuid, the panel now calls
`/api/gis/search?q=<cadref>`, filters by layerId match, and uses the
returned id to do `parcela.get(id)`. One extra round trip (~50ms with
the trigram-idx fix from 2026-05-18). For features arriving from the
search dropdown the uuid is already known — that path is unchanged.
Panel redesign — same data shape as eterra.live, ArchiTools styling
(shadcn instead of HeroUI), single-file:
- Header: cadref + layer + area + status chip + close
- Caracteristici: intravilan + categorie folosință + nr corpuri (chips)
- Date eTerra: all enrichment fields, PII passes through gis-api scope
redaction (scope=basic → PROPRIETARI/NR_CF/DOC already null)
- Apartamente (condominium): for CLADIRI_ACTIVE clicks, fetches
/api/gis/building/condo-owners and renders units with owners + cf + area
- Localizare: click lat/lng + Google Maps link + SIRUTA echo
Two new proxy routes (thin wrappers over gis-api):
- POST /api/gis/parcel/units-fetch
- POST /api/gis/building/condo-owners
Basic-panel mode for restricted users (per Marius: "for users I don't
want to give full access to"):
- New env BASIC_PANEL_USERS (csv emails) → session.basicPanel flag
- Optional PANEL_BASIC_GLOBAL=1 to force-basic everyone
- When true, panel renders only header + cadref + suprafață + a
restriction notice; all sections + condo fetch are skipped
- Defaults to off; pilot user Marius gets full panel as before
map-viewer now forwards lngLat on click so the Localizare section has
coordinates without a second lookup.
Type-check clean. Production build (NODE_ENV=production npx next build)
passes. The dev-mode prerender error on / page is pre-existing (Next 16
useContext-null on client component during static export, unrelated).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
77da69e29f |
fix(geoportal-v2): CF button → deep-link to parcel-sync ePay tab
User pushback on the pool-based CF flow: he wants his own ePay account (per-user creds, visible credit balance, decrement per order) — not the shared orchestrator pool which hides cost attribution. V2 panel "Comandă CF" now opens /parcel-sync?tab=epay&cad=<ref> in a new tab where the legacy UI handles ordering with credits. The /api/cf/* gis-api routes stay (used elsewhere + future SaaS consumers) but the V2 button doesn't hit them. Plus diagnostic on /api/gis/parcela showing enrichment presence + key count to debug "data nu raman" — should reveal whether Marius's session is getting full enrichment back from gis-api. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b85e074e3a |
feat(geoportal-v2): wire Comanda CF button to /api/cf/order
Was a disabled placeholder ("Va fi disponibil la Faza F"). Now
POSTs to /api/cf/order with nrCadastral + siruta + gisFeatureId.
Forwards 409 catalog_hit as "Extras CF valid deja există".
Spinner during request, result text shown below the toolbar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
e6432b13f0 |
fix(geoportal-v2): hydrate siruta when refresh fires before parcela.get
Audit-flagged blocker: search-dropdown feature select → setClicked with siruta:"" because gis-api /search response doesn't include siruta per feature. If user clicks "Citește din ANCPI" before the panel's auto-fetch of parcela.get completes, refresh fails with missing_siruta_or_cad. Fix: refresh handler now awaits parcela.get inline when siruta is empty + feature.id is present, then hydrates detail before posting to /api/gis/parcel/tech. Proper followup: extend gis-api /search response with siruta per feature so the race goes away entirely. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
e0610b0573 |
fix(geoportal-v2): handle PMTiles features without uuid id
PMTiles overview tiles may carry only object_id + siruta + cadastral_ref (not the gis_core.GisFeature uuid). Old click handler required `id` and silently dropped clicks. Now: - map-viewer click handler: extract objectId AND id; require only siruta+cadastral_ref to dispatch. Logs the props when fields are missing for further diagnosis. - feature-info-panel: skip auto-fetch when feature.id is empty; show basic info from tile properties + nudge user to "Citește din ANCPI" to populate enrichment. - Refresh button: project orchestrator response straight into the panel when no gis-uuid available (no second parcela.get roundtrip). Falls back to detail.siruta when click came from search (which only returns id + cadastralRef, no siruta). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
99a673de3d |
feat(geoportal): Faza E v2 thin client (PMTiles + gis.ac)
New geoportal module flag-gated by session.useGisAc. Legacy code path preserved as GeoportalV1Legacy (rename only — same logic). When session.useGisAc=true (Infisical USE_GIS_AC=1 OR email in GIS_AC_PILOT_USERS), the page renders GeoportalV2 instead. V2 layout (851 LOC across 5 files): - map-viewer.tsx (~295 LOC): MapLibre + PMTiles src `pmtiles://pmtiles.gis.ac/overview.pmtiles`. Layers: UAT boundaries (z5, z8), parcels (gis_terenuri line + invisible hit-test fill), buildings (gis_cladiri fill+line). Click → resolves layerId from sourceLayer, emits ClickedFeatureLite (id, siruta, cadastralRef, layerId). - search-bar.tsx (~160 LOC): debounced 300ms, calls /api/gis/search, dropdown grouped by UATs / Parcele. - feature-info-panel.tsx (~270 LOC): fetches /api/gis/parcela/[id], renders enrichment block (scope-aware — 403 shown explicitly as "permisiuni insuficiente"). Buttons: "Citește din ANCPI" (POST /api/gis/parcel/tech force:true), "Export GeoPackage" (deep-link to eterra.live/harta?…&autoexport=geopackage), "Comandă CF" placeholder pending Faza F. - basemap-switcher.tsx (~40 LOC): liberty / dark / satellite / google. Dropped orto + topo50/25 (require ANCPI session — orto/topo via raster.gis.ac is TBD Sprint 2). - geoportal-v2.tsx (~85 LOC): wraps MapViewer + SearchBar + BasemapSwitcher + FeatureInfoPanel. API routes (90 LOC across 3 files): - GET /api/gis/search → gisApi.search - GET /api/gis/parcela/[id] → gisApi.parcela.get - POST /api/gis/parcel/tech → gisApi.parcel.tech (refresh ANCPI) All routes 401 if no NextAuth session, forward GisApiError status+code, hit api.gis.ac with the Authentik access_token from session. Per project_audit_correlation_echo memory: no correlationId set on client side (gis-api overwrites server-side). Cutover-bottom-right badge "gis.ac · v2" visible until full rollout for ops visibility. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |