diff --git a/cockpit/app/routes/export_intel.py b/cockpit/app/routes/export_intel.py index 4a5027b..aba47aa 100644 --- a/cockpit/app/routes/export_intel.py +++ b/cockpit/app/routes/export_intel.py @@ -86,6 +86,13 @@ async def api_entities( return await _proxy("GET", "/entities", params=params) + + +@router.patch("/api/export-intel/entities/{entity_id}") +async def api_update_entity(entity_id: int, request: Request): + body = await request.json() + return await _proxy("PATCH", f"/entities/{entity_id}", json=body) + @router.get("/api/export-intel/entities/{entity_id}") async def api_entity(entity_id: int): return await _proxy("GET", f"/entities/{entity_id}") diff --git a/cockpit/static/css/export-intel.css b/cockpit/static/css/export-intel.css index 135b860..26f082d 100644 --- a/cockpit/static/css/export-intel.css +++ b/cockpit/static/css/export-intel.css @@ -40,6 +40,56 @@ .ei-filter-meta { display: flex; align-items: center; gap: 0.5rem; margin-left: auto; } .ei-filter-count { font-size: 0.75rem; color: #64748b; white-space: nowrap; } .ei-filter-clear { font-size: 0.72rem; opacity: 0.85; } + +.ei-split { align-items: stretch; } +.ei-split > .ei-map-wrap { grid-column: 1; } +.ei-split > .ei-panel { grid-column: 2; } + +.ei-progress-overlay { + position: fixed; inset: 0; z-index: 1200; + display: flex; align-items: center; justify-content: center; + background: rgba(4, 8, 14, 0.55); backdrop-filter: blur(2px); +} +.ei-progress-card { + min-width: 260px; max-width: 90vw; padding: 1.1rem 1.25rem; + border-radius: 14px; border: 1px solid rgba(56, 189, 248, 0.35); + background: rgba(10, 14, 20, 0.95); text-align: center; + box-shadow: 0 12px 40px rgba(0,0,0,0.45); +} +.ei-progress-card p { margin: 0.65rem 0 0.85rem; color: #e2e8f0; font-size: 0.88rem; } +.ei-progress-spinner { + width: 28px; height: 28px; margin: 0 auto; border-radius: 50%; + border: 3px solid rgba(56, 189, 248, 0.2); border-top-color: #38bdf8; + animation: ei-spin 0.75s linear infinite; +} +.ei-progress-bar { + height: 4px; border-radius: 999px; background: rgba(148, 163, 184, 0.2); overflow: hidden; +} +.ei-progress-bar-fill { + height: 100%; width: 35%; border-radius: 999px; + background: linear-gradient(90deg, #0ea5e9, #38bdf8, #0ea5e9); + background-size: 200% 100%; animation: ei-progress-slide 1.2s ease-in-out infinite; +} +@keyframes ei-progress-slide { + 0% { transform: translateX(-120%); } + 100% { transform: translateX(320%); } +} + +.ei-th-actions, .ei-td-actions { white-space: nowrap; width: 1%; } +.ei-td-actions { display: flex; gap: 0.25rem; flex-wrap: wrap; } +.ei-td-actions .btn { padding: 0.2rem 0.45rem; font-size: 0.68rem; } + +.ei-drawer-edit { margin: 1rem 0; padding: 0.85rem; border-radius: 12px; border: 1px solid rgba(56,189,248,0.25); background: rgba(8,12,18,0.6); } +.ei-drawer-edit h4 { margin: 0 0 0.65rem; font-size: 0.85rem; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.06em; } +.ei-edit-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.55rem; } +.ei-edit-grid label { display: flex; flex-direction: column; gap: 0.2rem; font-size: 0.68rem; color: #64748b; text-transform: uppercase; } +.ei-edit-grid input, .ei-edit-grid select, .ei-edit-grid textarea { + padding: 0.45rem 0.55rem; border-radius: 8px; border: 1px solid rgba(148,163,184,0.2); + background: rgba(15,23,42,0.8); color: #e2e8f0; font-size: 0.82rem; +} +.ei-edit-full { grid-column: 1 / -1; } +.ei-edit-actions { display: flex; gap: 0.5rem; margin-top: 0.75rem; } + .ei-split { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; min-height: 420px; } @media (max-width: 900px) { .ei-split { grid-template-columns: 1fr; } } diff --git a/cockpit/static/js/export-intel-map.js b/cockpit/static/js/export-intel-map.js index 613e76e..1559bf5 100644 --- a/cockpit/static/js/export-intel-map.js +++ b/cockpit/static/js/export-intel-map.js @@ -221,7 +221,8 @@ window.ExportIntelMap = (function () { return map; } - function setFeatures(geojson) { + function setFeatures(geojson, opts) { + opts = opts || {}; if (!map || !clusterLayer) return; clusterLayer.clearLayers(); markersById = {}; @@ -247,10 +248,12 @@ window.ExportIntelMap = (function () { invalidateMapSize(); - if (features.length === 1) { + if (opts.autoFit === false) { + map.setView([20, 10], 2); + } else if (features.length === 1) { var c = features[0].geometry.coordinates; map.setView([c[1], c[0]], 10); - } else if (markers.length > 1 && clusterLayer.getBounds) { + } else if (opts.autoFit && markers.length > 1 && clusterLayer.getBounds) { try { map.fitBounds(clusterLayer.getBounds(), { padding: [40, 40], maxZoom: 12 }); } catch (e) { /* empty */ } diff --git a/cockpit/static/js/export-intel.js b/cockpit/static/js/export-intel.js index fa4b128..83d95a1 100644 --- a/cockpit/static/js/export-intel.js +++ b/cockpit/static/js/export-intel.js @@ -42,6 +42,8 @@ function exportIntelApp() { crmModalOpen: false, crmCreateDeals: true, crmPushBusy: false, + editMode: false, + editForm: {}, resultCount: 0, _searchTimer: null, @@ -193,7 +195,19 @@ function exportIntelApp() { }, showSelectionBar() { - return this.tab === 'map' || this.tab === 'contacts'; + return ['map', 'contacts', 'distributors', 'customers', 'caterers'].indexOf(this.tab) >= 0; + }, + + isWorking() { + return this.busy || this.crmPushBusy || this.mapLoading || this.drawerLoading; + }, + + progressLabel() { + if (this.busy && this.syncMsg) return this.syncMsg; + if (this.crmPushBusy) return 'CRM import bezig…'; + if (this.mapLoading) return 'Kaart laden…'; + if (this.drawerLoading) return 'Profiel laden…'; + return 'Bezig…'; }, entityTypeOptions() { @@ -343,7 +357,8 @@ function exportIntelApp() { window.ExportIntelMap.init('ei-map'); window.ExportIntelMap.setOnEntityClick(function (id) { self.openEntity(id); }); window.ExportIntelMap.setHalalMode(this.mapHalalMode); - window.ExportIntelMap.setFeatures(bundle.entities || { features: [] }); + var autoFit = !!(this.country || this.region); + window.ExportIntelMap.setFeatures(bundle.entities || { features: [] }, { autoFit: autoFit }); if (this.country) { var t = this.territories.find((x) => x.country_iso2 === this.country); if (t && t.lat && t.lon) window.ExportIntelMap.flyTo(t.lat, t.lon, t.map_zoom || 6); @@ -456,6 +471,59 @@ function exportIntelApp() { this.drawerOpen = false; this.selected = null; this.drawerLoading = false; + this.editMode = false; + this.editForm = {}; + }, + + startEdit() { + if (!this.selected) return; + var s = this.selected; + this.editForm = { + name: s.name || '', + entity_type: s.entity_type || '', + country_iso2: s.country_iso2 || '', + city: s.city || '', + address_line: s.address_line || '', + postal_code: s.postal_code || '', + phone: s.phone || '', + email: s.email || '', + website: s.website || '', + volume_band: s.volume_band || '', + pipeline_stage: s.pipeline_stage || 'identified', + confidence: s.confidence != null ? s.confidence : 50, + halal_cert_notes: s.halal_cert_notes || '', + cold_chain: !!s.cold_chain, + }; + this.editMode = true; + }, + + cancelEdit() { + this.editMode = false; + this.editForm = {}; + }, + + async saveEdit() { + if (!this.selected || !this.selected.id) return; + this.busy = true; + this.syncMsg = 'Opslaan…'; + try { + var r = await fetch('/api/export-intel/entities/' + this.selected.id, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(this.editForm), + }); + if (!r.ok) { + var err = await r.json().catch(function () { return {}; }); + throw new Error(err.detail || err.error || 'Opslaan mislukt'); + } + this.selected = await r.json(); + this.editMode = false; + this.syncMsg = 'Opgeslagen'; + await this.refresh(); + } catch (e) { + this.syncMsg = 'Opslaan mislukt: ' + e.message; + } + this.busy = false; }, async syncKind(kind) { diff --git a/cockpit/templates/export_intel.html b/cockpit/templates/export_intel.html index b0d28c3..7d647c9 100644 --- a/cockpit/templates/export_intel.html +++ b/cockpit/templates/export_intel.html @@ -4,14 +4,21 @@ - + - + {% endblock %} {% block content %}
Wereldoverzicht toont de belangrijkste ~2.500 locaties. Filter op regio of land voor meer detail.
Wereldoverzicht toont de belangrijkste ~2.500 locaties. Filter op regio of land voor meer detail.
| Naam | Land | Stad | Telefoon | Confidence | |||
|---|---|---|---|---|---|---|---|
| + | Naam | Land | Stad | Telefoon | Confidence | Acties | +|
| + | |||||||
| + + | ++ + CRM + | + | + + CRM ✓ + + |