Commit Graph

29 Commits

Author SHA1 Message Date
AI Assistant b0927ee075 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)
2026-03-07 11:12:54 +02:00
AI Assistant 0d0b1f8c9f feat(parcel-sync): native PostGIS geometry support for QGIS
- Remove postgresqlExtensions/postgis from Prisma schema (PostGIS not yet installed)
- Add prisma/postgis-setup.sql: trigger auto-converts GeoJSON→native geometry,
  GiST spatial index, QGIS-friendly views (gis_terenuri, gis_cladiri, etc.)
- Add POST /api/eterra/setup-postgis endpoint (idempotent, runs all SQL setup)
- Add safety-net raw SQL in sync-service: backfills geom after upsert phase
- Add QGIS/PostGIS setup card in layer catalog UI with connection info
- Schema comment documents the trigger-managed 'geom' column approach
2026-03-07 10:25:30 +02:00
AI Assistant b0c4bf91d7 feat(parcel-sync): sync-to-DB + local export + layer catalog enhancements
Layer catalog now has 3 actions per layer:
- Sync: downloads from eTerra, stores in PostgreSQL (GisFeature table),
  incremental — only new OBJECTIDs fetched, removed ones deleted
- GPKG: direct download from eTerra (existing behavior)
- Local export: generates GPKG from local DB (no eTerra needed)

New features:
- /api/eterra/export-local endpoint — builds GPKG from DB, ZIP for multi-layer
- /api/eterra/sync now uses session-based auth (no credentials in request)
- Category headers show both remote + local feature counts
- Each layer shows local DB count (violet badge) + last sync timestamp
- 'Export local' button in action bar when any layer has local data
- Sync progress message with auto-dismiss

DB schema already had GisFeature + GisSyncRun tables from prior work.
2026-03-07 10:05:39 +02:00
AI Assistant f73e639e4f fix(parcel-sync): quote all CSV fields + layer feature counts + drumul de azi
- CSV export: all fields properly quoted to prevent column misalignment
  when values contain commas (e.g. nrTopo with multiple topo numbers)
- Layer catalog: 'Numara toate' button fetches feature count per layer
  via /api/eterra/layers/summary (now supports session auth)
- Feature counts displayed as badges on each layer and category total
- 'Drumul de azi' section: persists today's layer counts in localStorage
  grouped by SIRUTA with timestamps
2026-03-06 23:19:58 +02:00
AI Assistant 0b049274b1 fix(search): robust address from all structured fields, multi-address support
- Always build from structured fields first (street, postalNo, building, locality)
- Fall back to addressDescription ONLY when no structured fields exist
- Support multiple addresses per immovable (joined with |)
- Deduplicate identical addresses
- Handle addressDescription as last-resort fallback
2026-03-06 22:57:11 +02:00
AI Assistant 742acb2d74 fix(search): proper address from all fields, parcel details endpoint, remove strikethrough
- Address: use street.dictionaryItem.name (Strada/Alee/etc) + street.name,
  postalNo as house number, buildingEntryNo/FloorNo/UnitNo/SectionNo
  for apartment details, locality.name, county.name
- Area+intravilan: fetch from /api/immovable/details/parcels/list (direct
  endpoint with area, intravilan, useCategory) before trying immApps
- Owners: remove strikethrough, use smaller neutral font (text-[11px]
  text-muted-foreground/80), rename label to 'Proprietari anteriori'
2026-03-06 22:50:57 +02:00
AI Assistant 4aa8e6c324 fix(search): use legalArea/measuredArea + nodeStatus=-1 for radiated owners
- Area: use measuredArea/legalArea from immovable list and documentation
  (actual fields from eTerra API, not area/areaValue which don't exist)
- Owners: detect radiated via nodeStatus === -1 on ancestor I (inscription)
  nodes. Walk up parentId tree from P (person)  I  A  C.
  nodeStatus: -1=radiated, 0=active, 2=pending
- Remove debug logging (data structure now understood)
2026-03-06 22:16:14 +02:00
AI Assistant 79c45adc37 fix(search): address [object Object], suprafata from folosinte, owner tree separation
- Address: handle street/locality/county as objects (extract .name)
  Fixes 'Str. [object Object], Feleacu'  'Str. X, Feleacu'
- Suprafata: fallback to total area from folosinte endpoint when
  immovable list and documentation APIs return null
- Owners: use tree traversal (nodeId/parentNodeId) to detect radiated
  inscriptions. Walk up parent chain to check radiationDate/cancelled/
  isActive/closed/status on ancestor inscription nodes.
- Enhanced logging: first/last 3 partTwoRegs entries + node types
  for debugging owner structure in Dozzle
2026-03-06 22:06:28 +02:00
AI Assistant 6eae4fa1c5 feat(search): separate active/old owners, improve address format, debug area fields
- Proprietari split into proprietariActuali + proprietariVechi (radiati)
  based on cancelled/isActive/radiat/status/radiationDate fields
- UI shows owners separated: actuali bold, vechi strikethrough
- CSV export has separate PROPRIETARI_ACTUALI / PROPRIETARI_VECHI columns
- Address: use addressDescription directly when present (>3 chars)
- Add county to address fallback
- Try area/areaValue/areaMP/suprafata fields for surface
- Debug logging: log immovable item keys + partTwoRegs sample on first search
2026-03-06 21:53:18 +02:00
AI Assistant 6b8feb9075 fix: workspace resolution via ArcGIS listLayer + seed UATs from uat.json
- resolveWorkspace: use listLayer() instead of listLayerByWhere() with
  hardcoded field names. Auto-discovers admin field (ADMIN_UNIT_ID/SIRUTA)
  from ArcGIS layer metadata via buildWhere().
- resolveWorkspace: persist WORKSPACE_ID to DB on first resolution for
  fast subsequent lookups.
- UATs POST: seed from uat.json (correct SIRUTA codes) instead of eTerra
  nomenclature API (nomenPk != SIRUTA, county nomenPk != WORKSPACE_ID).
- Remove eTerra nomenclature dependency from UATs endpoint.
- Fix activeJobs Set iteration error on container restart.
- Remove unused enrichedUatsFetched ref.
2026-03-06 21:24:51 +02:00
AI Assistant 1b72d641cd fix(parcel-sync): robust workspace resolution with direct nomen lookup
- Add fetchNomenByPk() to EterraClient  single nomen entry lookup
- resolveWorkspace() now tries fast path first: direct nomen lookup for
  SIRUTA  walk parentNomenPk chain to find COUNTY (1-3 calls vs 42+)
- Falls back to full county scan only if direct lookup fails
- Search route: DB lookup as middle fallback between workspacePk and resolve
- Debug logging to trace workspace resolution on production
- Fix: try all possible UAT identifier fields (nomenPk, siruta, code, pk)
2026-03-06 21:09:22 +02:00
AI Assistant ec5a866673 feat(parcel-sync): store UATs in PostgreSQL, eliminate repeated eTerra calls
- GisUat table now includes workspacePk column (created via raw SQL)
- GET /api/eterra/uats serves from PostgreSQL  instant, no eTerra login needed
- POST /api/eterra/uats triggers sync check: compares county count with DB,
  only does full eTerra fetch if data differs or DB is empty
- Frontend loads UATs from DB on mount (fast), falls back to uat.json if empty
- On eTerra connect, fires POST to sync-check; if data changed, reloads from DB
- Workspace cache populated from DB on GET for search route performance
2026-03-06 20:56:12 +02:00
AI Assistant d948e5c1cf feat(parcel-sync): county-aware UAT autocomplete with workspace resolution
- New /api/eterra/uats endpoint fetches all counties + UATs from eTerra,
  caches server-side for 1 hour, returns enriched data with county name
  and workspacePk for each UAT
- When eTerra is connected, auto-fetches enriched UAT list (replaces
  static uat.json fallback)  shows 'FELEACU (57582), CLUJ' format
- UAT autocomplete now searches both UAT name and county name
- Selected UAT stores workspacePk in state, passes it directly to
  /api/eterra/search  eliminates slow per-search county resolution
- Search route accepts optional workspacePk, falls back to resolveWorkspace()
- Dropdown shows UAT name, SIRUTA code, and county prominently
- Increased autocomplete results from 8 to 12 items
2026-03-06 20:46:44 +02:00
AI Assistant 540b02d8d2 feat(parcel-sync): search by cadastral number with full details
Search tab now uses eTerra application API (same as the web UI):

- POST /api/eterra/search queries /api/immovable/list with exact
  identifierDetails filter + /api/documentation/data for full details
- Returns: nr cad, nr CF, CF vechi, nr topo, suprafata, intravilan,
  categorii folosinta, adresa, proprietari, solicitant
- Automatic workspace (county) resolution from SIRUTA with cache
- Support for multiple cadastral numbers (comma separated)

UI changes:
- Detail cards instead of flat ArcGIS feature table
- Copy details to clipboard button per parcel
- Add parcels to list + CSV export
- Search list with summary table + CSV download
- No more layer filter or pagination (not needed for app API)

New EterraClient methods:
- searchImmovableByIdentifier (exact cadaster lookup)
- fetchCounties / fetchAdminUnitsByCounty (workspace resolution)
2026-03-06 19:58:33 +02:00
AI Assistant c98ce81cb7 feat(parcel-sync): live eTerra search by cadastral number
- Add /api/eterra/search  queries eTerra ArcGIS REST API directly
  by NATIONAL_CADASTRAL_REFERENCE, NATIONAL_CADNR, or INSPIRE_ID
  across TERENURI_ACTIVE + CLADIRI_ACTIVE layers
- Search tab now queries eTerra live (not local DB) with 600ms debounce
- Requires session connected + UAT selected to search
- Updated placeholder and empty state messages in Romanian
2026-03-06 19:18:18 +02:00
AI Assistant bd90c4e30f feat(parcel-sync): server-side eTerra session + auto-connect on UAT typing
- Add session-store.ts: global singleton for shared eTerra session state
  with job tracking (registerJob/unregisterJob/getRunningJobs)
- Add GET/POST /api/eterra/session: connect/disconnect with job-running guard
- Export routes: credential fallback chain (body > session > env vars),
  register/unregister active jobs for disconnect protection
- Login route: also creates server-side session
- ConnectionPill: session-aware display with job count, no credential form
- Auto-connect: triggers on first UAT keystroke via autoConnectAttempted ref
- Session polling: all clients poll GET /api/eterra/session every 30s
- Multi-client: any browser sees shared connection state
2026-03-06 19:06:39 +02:00
AI Assistant 129b62758c refactor(parcel-sync): global UAT bar, connection pill, reorder tabs
- UAT autocomplete always visible above tabs (all tabs share it)
- Connection status pill in top-right: breathing green dot when connected,
  dropdown with credentials form / disconnect button
- Tab order: Cautare Parcele (1st) -> Catalog Layere -> Export (last)
- Renamed 'Butonul Magic' to just 'Magic'
- Removed connection/UAT cards from inside Export tab
2026-03-06 18:41:11 +02:00
AI Assistant 09a24233bb feat(parcel-sync): full GPKG export workflow with UAT autocomplete, hero buttons, layer catalog
- Fix login button (return success instead of ok)
- Add UAT autocomplete with NFD-normalized search (3186 entries)
- Add export-bundle API: base mode (terenuri+cladiri) + magic mode (enriched parcels)
- Add export-layer-gpkg API: individual layer GPKG download
- Add gpkg-export service: ogr2ogr with GeoJSON fallback
- Add reproject service: EPSG:3844 projection support
- Add magic-mode methods to eterra-client (immApps, folosinte, immovableList, docs, parcelDetails)
- Rewrite UI: 3-tab layout (Export/Catalog/Search), progress tracking, phase trail
2026-03-06 06:53:49 +02:00
AI Assistant 7cdea66fa2 feat: add parcel-sync module (eTerra ANCPI integration with PostGIS)
- 31 eTerra layer catalog (terenuri, cladiri, documentatii, administrativ)
- Incremental sync engine (OBJECTID comparison, only downloads new features)
- PostGIS-ready Prisma schema (GisFeature, GisSyncRun, GisUat models)
- 7 API routes (/api/eterra/login, count, sync, features, layers/summary, progress, sync-status)
- Full UI with 3 tabs (Sincronizare, Parcele, Istoric)
- Env var auth (ETERRA_USERNAME / ETERRA_PASSWORD)
- Real-time sync progress tracking with polling
2026-03-06 00:36:29 +02:00
AI Assistant d34c722167 feat(3.15): AI Tools extindere si integrare
Prompt Generator:
- Search bar cu cautare in name/description/tags/category
- Filtru target type (text/image) cu toggle rapid 'Imagine'
- 4 template-uri noi imagine: Midjourney Exterior, SD Interior,
  Midjourney Infographic, SD Material Texture (18 total)
- Config v0.2.0

AI Chat  Real API Integration:
- /api/ai-chat route: multi-provider (OpenAI, Anthropic, Ollama, demo)
- System prompt default in romana pt context arhitectura
- GET: config status, POST: message routing
- use-chat.ts: sendMessage() cu fetch real, sending state,
  providerConfig fetch, updateSession() pt project linking
- UI: provider status badge (Wifi/WifiOff), Bot icon pe mesaje,
  loading spinner la generare, disable input while sending
- Config banner cu detalii provider/model/stare

AI Chat + Tag Manager:
- Project selector dropdown in chat header (useTags project)
- Session linking: projectTagId + projectName on ChatSession
- Project name display in session sidebar
- Project context injected in system prompt

Docker:
- AI env vars: AI_PROVIDER, AI_API_KEY, AI_MODEL, AI_BASE_URL, AI_MAX_TOKENS
2026-02-28 04:51:36 +02:00
AI Assistant 11b35c750f 3.13 Tag Manager ManicTime sync bidirectional sync, backup, hierarchy validation
- ManicTime parser service: parse/serialize Tags.txt format, classify lines into project/phase/activity
- API route /api/manictime: GET (read + sync plan), POST (pull/push/both with backup versioning)
- ManicTimeSyncPanel component: connection check, stats grid, import/export/full sync with confirmation dialog
- Integrated into Tag Manager module with live sync status
- Docker: MANICTIME_TAGS_PATH env var, SMB volume mount /mnt/manictime
- Hierarchy validation: project codes, company association, duplicate detection
- Version bump to 0.2.0
2026-02-28 04:38:57 +02:00
AI Assistant 85bdb59da4 3.14 Password Vault encryption AES-256-GCM server-side
- Created src/core/crypto/ with AES-256-GCM encrypt/decrypt (PBKDF2 key derivation)
- Created /api/vault route: CRUD with server-side password encryption
- PATCH /api/vault migration endpoint to re-encrypt legacy plaintext passwords
- Rewrote use-vault hook to use dedicated /api/vault instead of generic storage
- Updated UI: amber 'not encrypted' warning  green 'encrypted' badge
- Added ENCRYPTION_SECRET env var to docker-compose.yml and stack.env
- Module version bumped to 0.2.0
2026-02-28 04:12:44 +02:00
AI Assistant a0ec4aed3f fix: move blob migration server-side, restore lightweight list loading
The client-side migration was downloading 25-50MB of base64 data to the
browser before showing anything. getAllEntries also lost its lightweight flag.

Fix:
- New POST /api/storage/migrate-blobs endpoint runs entirely server-side
  (loads entries one-at-a-time from PostgreSQL, never sends heavy data to browser)
- Restore lightweight:true on getAllEntries (strips remaining base64 in API)
- Migration fires on mount (fire-and-forget) while list loads independently
- Remove client-side migrateEntryBlobs function
2026-02-28 00:03:26 +02:00
AI Assistant 578f6580a4 fix: remove raw SQL query that may cause Docker build/runtime issues
Replace complex prisma.\ with simple Prisma findMany + JS stripping.
Now that entries are inherently small (base64 in separate blob namespace),
JS-based stripping is instant. Also fix migration to check flag before loading.
2026-02-27 23:53:37 +02:00
AI Assistant 8385041bb0 perf: strip heavy base64 data at PostgreSQL level using raw SQL
Previous fix stripped data in Node.js AFTER Prisma loaded the full JSON
from PostgreSQL. For 5 entries with PDF attachments, this still meant
25-50MB transferring from DB to Node.js on every page load.

Now uses prisma.\ with nested jsonb_each/jsonb_object_agg to
strip data/fileData/imageUrl strings >1KB inside the database itself.
Heavy base64 never leaves PostgreSQL when lightweight=true.
2026-02-27 23:23:38 +02:00
AI Assistant c22848b471 perf(registratura): lightweight API mode strips base64 attachments from list
ROOT CAUSE: RegistryEntry stores file attachments as base64 strings in JSON.
A single 5MB PDF becomes ~6.7MB of base64. With 6 entries, the exportAll()
endpoint was sending 30-60MB of JSON on every page load  taking 2+ minutes.

Fix: Added ?lightweight=true parameter to /api/storage GET endpoint.
When enabled, stripHeavyFields() recursively removes large 'data' and
'fileData' string fields (>1KB) from JSON values, replacing with '__stripped__'.

Changes:
- /api/storage route.ts: stripHeavyFields() + lightweight query param
- StorageService.export(): accepts { lightweight?: boolean } option
- DatabaseStorageAdapter.export(): passes lightweight flag to API
- LocalStorageAdapter.export(): accepts option (no-op, localStorage is fast)
- useStorage.exportAll(): passes options through
- registry-service.ts: getAllEntries() uses lightweight=true by default
- registry-service.ts: new getFullEntry() loads single entry with full data
- use-registry.ts: exports loadFullEntry() for on-demand full loading
- registratura-module.tsx: handleEdit/handleNavigateEntry load full entry

Result: List loading transfers ~100KB instead of 30-60MB. Editing loads
full data for a single entry on demand (~5-10MB for one entry vs all).
2026-02-27 22:37:39 +02:00
AI Assistant 0ad7e835bd feat(core): setup postgres, minio, and authentik next-auth 2026-02-27 10:29:54 +02:00
AI Assistant 41036db659 fix(mini-utilities): Stirling PDF API key auth, Tesseract.js OCR, emoji removal in cleaner 2026-02-19 00:43:05 +02:00
AI Assistant 3154eb7f4a fix(mini-utilities): proxy compress-pdf through Next.js API route to bypass CORS 2026-02-19 00:32:13 +02:00