Files
foodlinkk-command-center/cockpit/templates/export_intel.html
T

849 lines
47 KiB
HTML
Raw Normal View History

{% extends "base.html" %}
{% block title %}Wereldexport · Foodlinkk B2B{% endblock %}
{% block extra_head %}
<link rel="stylesheet" href="/static/vendor/leaflet/leaflet.css?v=1" />
<link rel="stylesheet" href="/static/vendor/leaflet.markercluster/MarkerCluster.css?v=1" />
<link rel="stylesheet" href="/static/vendor/leaflet.markercluster/MarkerCluster.Default.css?v=1" />
2026-07-30 09:33:03 +00:00
<link rel="stylesheet" href="/static/css/export-intel.css?v=32" />
<link rel="stylesheet" href="/static/css/export-intel-tabs.css?v=20" />
<script src="/static/vendor/leaflet/leaflet.js?v=1"></script>
<script src="/static/vendor/leaflet.markercluster/leaflet.markercluster.js?v=1"></script>
2026-07-30 09:33:03 +00:00
<script src="/static/js/export-intel-map.js?v=21"></script>
<script src="/static/js/export-intel.js?v=32"></script>
<script src="/static/js/export-intel-profile.js?v=7"></script>
{% endblock %}
{% block content %}
<div class="ei-page" x-data="exportIntelApp()" x-init="init()">
<div class="ei-progress-overlay" x-show="isWorking()" x-cloak>
<div class="ei-progress-card">
<div class="ei-progress-spinner"></div>
<p x-text="progressLabel()"></p>
2026-07-30 09:33:03 +00:00
<div class="ei-progress-bar"><div class="ei-progress-bar-fill" :style="'width:' + (syncMonitorPercent() || 100) + '%'"></div></div>
</div>
</div>
<header class="ei-header page-header">
<div>
<h1>Wereldexport <span class="live-badge">Live</span></h1>
<p class="ei-subtitle">Foodlinkk B2B · wereldwijde markten · halal vlees · cateraars · distributeurs</p>
</div>
<div class="retail-actions">
<button class="btn btn-sm btn-pulse-green" @click="syncKind('world')" :disabled="busy" title="Europa · MENA · Afrika · Amerika">🌍 Sync wereld</button>
<button class="btn btn-sm btn-pulse" @click="syncKind('region')" :disabled="busy || !region" title="Sync huidige regio-filter">Sync regio</button>
<button class="btn btn-sm btn-pulse-green" @click="syncKind('all')" :disabled="busy">⚡ Sync alles</button>
<button class="btn btn-sm btn-pulse" @click="syncKind('distributors')" :disabled="busy">Distributeurs</button>
<button class="btn btn-sm btn-pulse" @click="syncKind('caterers')" :disabled="busy">Cateraars</button>
<button class="btn btn-sm btn-pulse-green" @click="syncKind('contacts')" :disabled="busy">Contacten</button>
<button class="btn btn-sm btn-pulse-purple" @click="syncKind('tenders')" :disabled="busy">TED</button>
<a class="btn btn-sm" :href="contactsExportUrl()" target="_blank">CSV export</a>
</div>
</header>
2026-07-30 09:33:03 +00:00
<div class="ei-sync-monitor" :class="{ 'ei-sync-monitor-open': syncMonitorOpen, 'ei-sync-monitor-active': syncMonitor && syncMonitor.any_running }">
<button type="button" class="ei-sync-monitor-toggle" @click="toggleSyncMonitor()">
<span x-show="syncMonitor && syncMonitor.any_running">⏳ Sync bezig</span>
<span x-show="!(syncMonitor && syncMonitor.any_running)">📡 Sync monitor</span>
<span class="ei-sync-monitor-badge" x-show="syncMonitor && syncMonitor.coverage" x-text="(syncMonitor.coverage.countries_with_data||0) + '/' + (syncMonitor.coverage.territories_total||0) + ' landen'"></span>
</button>
<div class="ei-sync-monitor-panel" x-show="syncMonitorOpen" x-cloak>
<div class="ei-sync-monitor-head">
<strong>Sync voortgang</strong>
<span x-show="syncMonitor && syncMonitor.any_running" class="ei-sync-live">● Live</span>
</div>
<template x-if="syncMonitor && syncMonitor.active_jobs && syncMonitor.active_jobs.length">
<div class="ei-sync-jobs">
<template x-for="j in syncMonitor.active_jobs" :key="j.id">
<div class="ei-sync-job">
<div class="ei-sync-job-title" x-text="j.label"></div>
<div class="ei-sync-job-msg" x-text="j.message || j.step || 'Bezig…'"></div>
<div class="ei-sync-job-bar"><div class="ei-sync-job-bar-fill" :style="'width:' + (j.percent||0) + '%'"></div></div>
<div class="ei-sync-job-meta" x-show="j.total" x-text="(j.current||0) + ' / ' + j.total + (j.country ? ' · ' + territoryName(j.country) : '')"></div>
</div>
</template>
</div>
</template>
<div class="ei-sync-coverage" x-show="syncMonitor && syncMonitor.coverage">
<div><span>Entiteiten</span><strong x-text="syncMonitor.coverage.entities_total||0"></strong></div>
<div><span>Landen met data</span><strong x-text="(syncMonitor.coverage.countries_with_data||0) + ' / ' + (syncMonitor.coverage.territories_total||0)"></strong></div>
<div><span>Lege landen</span><strong x-text="syncMonitor.coverage.countries_empty||0"></strong></div>
</div>
<div class="ei-sync-events" x-show="syncMonitor && syncMonitor.events && syncMonitor.events.length">
<div class="ei-sync-events-title">Laatste stappen</div>
<template x-for="(ev, idx) in (syncMonitor.events || []).slice().reverse().slice(0,12)" :key="idx">
<div class="ei-sync-event">
<span class="ei-sync-event-time" x-text="formatSyncTime(ev.ts)"></span>
<span class="ei-sync-event-text" x-text="syncEventLabel(ev)"></span>
</div>
</template>
</div>
</div>
</div>
<p class="ei-sync-msg" x-show="syncMsg" x-text="syncMsg"></p>
<div class="ei-selection-bar" x-show="showSelectionBar()" x-cloak>
<div class="ei-selection-left">
<span class="ei-selection-count" x-show="selectedIds.length" x-text="selectedIds.length + ' geselecteerd'"></span>
<span class="ei-selection-hint" x-show="!selectedIds.length" x-text="tab === 'contacts' ? 'Selecteer contacten met ☑ — organisatie gaat naar CRM' : 'Selecteer rijen met ☑ voor bulk-acties'"></span>
</div>
<div class="ei-selection-actions">
<button type="button" class="btn btn-sm" @click="toggleSelectAllVisible()" title="Alles op lijst">☑ Alles</button>
<button type="button" class="btn btn-sm" x-show="selectedIds.length" @click="clearSelection()">Wis selectie</button>
<button type="button" class="btn btn-sm btn-pulse" :disabled="!selectedIds.length || busy" @click="favoriteSelection()">★ Favoriet</button>
<button type="button" class="btn btn-sm" :disabled="!selectedIds.length || busy" @click="unfavoriteSelection()">☆ Verwijder favoriet</button>
<button type="button" class="btn btn-sm btn-pulse-green" :disabled="!selectedIds.length || crmPushBusy" @click="openCrmModal()">➕ Naar CRM</button>
</div>
</div>
<div class="ei-neo-kpi-row">
<div class="ei-neo-kpi">
<div class="ei-neo-kpi-top">
<div class="ei-neo-ring" style="--ring-color:#0ea5e9;--ring-pct:85"><div class="ei-neo-ring-inner">🌍</div></div>
<div><div class="ei-neo-kpi-label">Landen</div></div>
</div>
<div class="ei-neo-kpi-value" x-text="stats.territories||0"></div>
</div>
<div class="ei-neo-kpi">
<div class="ei-neo-kpi-top">
<div class="ei-neo-ring" style="--ring-color:#38bdf8;--ring-pct:70"><div class="ei-neo-ring-inner">📊</div></div>
<div><div class="ei-neo-kpi-label">Entities</div></div>
</div>
<div class="ei-neo-kpi-value" x-text="stats.entities||0"></div>
</div>
<div class="ei-neo-kpi">
<div class="ei-neo-kpi-top">
<div class="ei-neo-ring" style="--ring-color:#fbbf24;--ring-pct:75"><div class="ei-neo-ring-inner">📦</div></div>
<div><div class="ei-neo-kpi-label">Distributeurs</div></div>
</div>
<div class="ei-neo-kpi-value" x-text="stats.distributors||0"></div>
</div>
<div class="ei-neo-kpi">
<div class="ei-neo-kpi-top">
<div class="ei-neo-ring" style="--ring-color:#a78bfa;--ring-pct:65"><div class="ei-neo-ring-inner">🏢</div></div>
<div><div class="ei-neo-kpi-label">Cateraars</div></div>
</div>
<div class="ei-neo-kpi-value" x-text="stats.caterers||0"></div>
</div>
<div class="ei-neo-kpi">
<div class="ei-neo-kpi-top">
<div class="ei-neo-ring" style="--ring-color:#34d399;--ring-pct:60"><div class="ei-neo-ring-inner">📇</div></div>
<div><div class="ei-neo-kpi-label">Contacten</div></div>
</div>
<div class="ei-neo-kpi-value" x-text="stats.contacts||0"></div>
</div>
<div class="ei-neo-kpi">
<div class="ei-neo-kpi-top">
<div class="ei-neo-ring" style="--ring-color:#c084fc;--ring-pct:50"><div class="ei-neo-ring-inner">📋</div></div>
<div><div class="ei-neo-kpi-label">Tenders open</div></div>
</div>
<div class="ei-neo-kpi-value" x-text="stats.tenders_open||0"></div>
</div>
</div>
<div class="ei-filter-bar">
2026-07-30 09:33:03 +00:00
<div class="ei-filter-grid">
<label class="ei-filter-label ei-filter-cell ei-fa-regio">Regio
<select x-model="region" @change="country=''; refresh()">
<option value="">Alle regio's</option>
<template x-for="r in regions" :key="r.code">
<option :value="r.code" x-text="r.name_nl"></option>
</template>
</select>
</label>
2026-07-30 09:33:03 +00:00
<label class="ei-filter-label ei-filter-cell ei-fa-land">Land
<select x-model="country" @change="onCountryChange()">
<option value="">Alle landen</option>
2026-07-30 09:33:03 +00:00
<template x-for="t in filteredTerritories()" :key="t.country_iso2">
<option :value="t.country_iso2" x-text="countryOptionLabel(t)"></option>
</template>
</select>
</label>
2026-07-30 09:33:03 +00:00
<label class="ei-filter-label ei-filter-cell ei-filter-cell-search ei-fa-zoek">
<span>Zoeken</span>
<input type="search" placeholder="Organisatie, e-mail…" x-model="q" @input="onSearchInput()" />
</label>
<label class="ei-filter-label ei-filter-cell ei-fa-type" x-show="showEntityTypeFilter()">Type
<select x-model="entityTypeFilter" @change="applyEntityFilters()">
<template x-for="o in entityTypeOptions()" :key="o.v">
<option :value="o.v" x-text="o.l"></option>
</template>
</select>
</label>
<label class="ei-filter-label ei-filter-cell ei-fa-contact" x-show="showEmailFilter()">Contact
<select x-model="filterHasEmail" @change="applyEntityFilters()">
<option value="">Alle</option>
<option value="yes">Met e-mail/tel</option>
<option value="no">Zonder contact</option>
</select>
</label>
<label class="ei-filter-label ei-filter-cell ei-fa-conf" x-show="showConfidenceFilter()">Confidence
<select x-model="filterConfidenceMin" @change="applyEntityFilters()">
<option value="">Alle</option>
<option value="50">50%+</option>
<option value="70">70%+</option>
<option value="85">85%+</option>
</select>
</label>
2026-07-30 09:33:03 +00:00
<label class="ei-filter-label ei-filter-cell ei-fa-stad" x-show="showCityFilter()">Stad
<input type="text" placeholder="bv. Rotterdam" x-model="filterCity" @input="onCityFilterInput()" />
</label>
<label class="ei-filter-label ei-filter-cell ei-fa-extra" x-show="tab==='tenders'">Status
<select x-model="filterTenderStatus" @change="loadTabData()">
<option value="">Alle</option>
<option value="open">Open</option>
<option value="closed">Gesloten</option>
</select>
</label>
<label class="ei-filter-label ei-filter-cell ei-fa-extra" x-show="tab==='gov'">Categorie
<select x-model="filterGovCategory" @change="applyGovFilters()">
<option value="">Alle</option>
<option value="statistics">Statistiek</option>
<option value="trade">Handel</option>
<option value="regulation">Regelgeving</option>
<option value="demographics">Demografie</option>
</select>
</label>
<label class="ei-filter-label ei-filter-cell ei-fa-crm" x-show="showCollectionFilters()">CRM
<select x-model="filterCrmStatus" @change="onFilterCollectionChange()">
<option value="">Alle</option>
<option value="not_linked">Nog niet in CRM</option>
<option value="linked">In CRM</option>
</select>
</label>
<label class="ei-filter-check ei-filter-cell ei-filter-cell-check ei-fa-fav" x-show="showFavoritesFilter()">
<input type="checkbox" x-model="filterFavoritesOnly" @change="onFilterCollectionChange()" />
<span>★ Favorieten</span>
</label>
<div class="ei-filter-meta ei-filter-cell ei-filter-cell-meta ei-fa-meta">
<span class="ei-filter-count" x-show="resultCount > 0" x-text="resultCount + ' resultaten'"></span>
<span class="ei-filter-count ei-filter-fav" x-show="stats.favorites > 0" x-text="(stats.favorites||0) + ' ★'"></span>
<button type="button" class="btn btn-sm ei-filter-clear" x-show="hasActiveFilters()" @click="clearFilters()">Wis filters</button>
</div>
</div>
2026-07-30 09:33:03 +00:00
<div class="ei-filter-map-row" x-show="showMapTypeFilter()" x-cloak>
<span class="ei-filter-label ei-filter-map-label">Kaart toont</span>
<div class="ei-type-chips" role="group" aria-label="Types op kaart">
<button type="button" class="ei-type-chip" :class="{ active: !mapTypesSelected.length }" @click="clearMapTypes()">Alle</button>
<template x-for="chip in mapTypeChips" :key="chip.v">
<button type="button" class="ei-type-chip" :class="{ active: isMapTypeActive(chip.v) }" @click="toggleMapType(chip.v)">
2026-07-30 09:33:03 +00:00
<span x-text="chip.icon"></span> <span x-text="chip.l"></span> <span class="ei-chip-count" x-show="mapTypeCount(chip.v)" x-text="mapTypeCount(chip.v)"></span>
</button>
</template>
</div>
</div>
2026-07-30 09:33:03 +00:00
<div class="ei-filter-map-row ei-filter-map-row-halal" x-show="showMapTypeFilter()" x-cloak>
<label class="ei-filter-check">
<input type="checkbox" x-model="mapHalalMode" @change="applyMapFilters()" />
<span>🥩 Halal-kansen modus</span>
</label>
<label class="ei-filter-label ei-halal-min" x-show="mapHalalMode">
Min. score
<input type="range" min="30" max="90" step="5" x-model.number="mapHalalMin" @change="applyMapFilters()" />
<strong x-text="mapHalalMin"></strong>
</label>
</div>
</div>
2026-07-30 09:33:03 +00:00
<div class="vtabs-layout wereldexport-workspace" style="--vt-color:#0ea5e9" x-ref="vtabsWrap" >
<nav class="vtabs-nav" x-ref="vtabsNav" :style="'--vt-indicator-top:'+tabIndicatorTop+'px;--vt-indicator-h:'+tabIndicatorH+'px'">
<div class="vtabs-nav-header"><strong>Modules</strong></div>
<template x-for="t in tabs" :key="t.id">
<button type="button" class="vtab-btn vtab-btn-rich" :class="{active: tab===t.id}" :data-tab="t.id"
:style="'--tab-accent:'+t.color"
@click="setTab(t.id, $el)">
<span class="ei-vtab-icon-wrap" x-text="t.icon"></span>
<span class="ei-vtab-text">
<span class="ei-vtab-label" x-text="t.label"></span>
<span class="ei-vtab-sub" x-show="tabSub(t.id)" x-text="tabSub(t.id)"></span>
</span>
<span class="ei-vtab-meta">
<span class="ei-vtab-key" x-text="t.key"></span>
<span class="ei-vtab-badge" x-show="tabCount(t.id) > 0" x-text="tabCount(t.id)"></span>
</span>
</button>
</template>
</nav>
<div class="vtabs-content">
<!-- Kaart -->
<section x-show="tab==='map'" x-cloak>
<div class="ei-tab-panel">
<div class="ei-tab-panel-head">
<div>
<h2>Wereldkaart</h2>
<p>Klik pin voor preview · dubbelklik of “Volledig profiel” voor alle gegevens</p>
</div>
</div>
2026-07-30 09:33:03 +00:00
<div class="ei-tab-panel-body ei-workspace-body ei-workspace-body-map">
<div class="ei-map-legend">
<template x-if="!mapHalalMode">
<div class="ei-legend-row">
<span class="ei-legend-item"><i class="ei-leg-pin" style="--c:#f59e0b">📦</i> Distributeur</span>
<span class="ei-legend-item"><i class="ei-leg-pin" style="--c:#eab308">🏪</i> Groothandel</span>
<span class="ei-legend-item"><i class="ei-leg-pin" style="--c:#a78bfa">🏢</i> Cateraar</span>
<span class="ei-legend-item"><i class="ei-leg-pin" style="--c:#34d399">🍽️</i> Horeca</span>
<span class="ei-legend-item"><i class="ei-leg-pin" style="--c:#0ea5e9">📍</i> Overig</span>
</div>
</template>
<template x-if="mapHalalMode">
<div class="ei-legend-row ei-legend-halal">
<span class="ei-legend-item"><i class="ei-leg-pin" style="--c:#22c55e">●</i> Hoge kans (75+)</span>
<span class="ei-legend-item"><i class="ei-leg-pin" style="--c:#eab308">●</i> Gemiddeld (55+)</span>
<span class="ei-legend-item"><i class="ei-leg-pin" style="--c:#f97316">●</i> Matig (40+)</span>
<span class="ei-legend-item"><i class="ei-leg-pin" style="--c:#64748b">●</i> Laag</span>
</div>
</template>
</div>
<div class="ei-map-empty" x-show="tab==='map' && !entities.length && !busy" x-cloak>
<div class="ei-map-empty-inner">
<strong x-show="region || country">Nog geen data op de kaart voor deze selectie</strong>
<strong x-show="!region && !country">Zoom in of filter op regio</strong>
<p>Landen staan in het systeem, maar entities + contacten komen pas na een <em>OSM sync</em> (OpenStreetMap).</p>
<p x-show="region==='africa'">Afrika: 51 landen toegevoegd — sync nog niet gedraaid voor dit continent.</p>
<div class="ei-map-empty-actions">
<button type="button" class="btn btn-sm btn-pulse-green" x-show="region" @click="syncKind('region')" :disabled="busy">↻ Sync regio</button>
<button type="button" class="btn btn-sm btn-pulse" @click="syncKind('world')" :disabled="busy">🌍 Sync wereld</button>
</div>
</div>
</div>
<div class="ei-map-error" x-show="mapError" x-cloak x-text="mapError"></div>
2026-07-30 09:33:03 +00:00
<p class="ei-map-truncated" x-show="mapTruncated && !mapLoading" x-cloak>Filter actief: max. 8.000 pins op de kaart. Zoom in op regio/land voor alles.</p>
<p class="ei-map-truncated ei-map-filter-hint" x-show="resultCount === 0 && !mapLoading && (mapTypesSelected.length || entityTypeFilter)" x-cloak>Geen entities op de kaart voor dit filter — probeer B2B/groothandel of wis filters.</p>
<div class="ei-split ei-split-side">
<div class="ei-map-wrap"><div class="ei-map-loading" x-show="mapLoading" x-cloak><span class="ei-map-spinner"></span> Kaart laden…</div><div id="ei-map"></div></div>
2026-07-30 09:33:03 +00:00
<aside class="ei-side-panel">
<div class="ei-side-panel-head">
<h3 x-text="mapHalalMode ? 'Top halal-kansen' : 'Entities op kaart'"></h3>
<p x-show="!mapHalalMode">Klik een rij of kaart-pin om het volledige profiel te openen.</p>
<p x-show="mapHalalMode">Klik een rij voor contact & context · kaart zoomt mee.</p>
</div>
2026-07-30 09:33:03 +00:00
<div class="ei-side-panel-body">
<div class="ei-halal-markets" x-show="mapHalalMode && halalTopMarkets.length">
<h4>Landen met meeste kans</h4>
<template x-for="m in halalTopMarkets.slice(0,8)" :key="m.country_iso2">
<div class="ei-halal-market-row" @click="country=m.country_iso2; refresh()" style="cursor:pointer">
<span class="ei-halal-flag" x-text="m.country_iso2"></span>
2026-07-30 09:33:03 +00:00
<span class="ei-halal-market-name" x-text="territoryName(m.country_iso2)"></span>
<strong class="ei-halal-score" x-text="Math.round(m.combined_score||0)"></strong>
</div>
</template>
</div>
<div class="ei-table-wrap">
2026-07-30 09:33:03 +00:00
<table class="ei-table ei-table-compact">
<thead>
<tr>
<th class="ei-th-check"><input type="checkbox" @change="toggleSelectAllVisible()" title="Alles selecteren" /></th>
<th class="ei-th-fav">★</th>
2026-07-30 09:33:03 +00:00
<th class="ei-th-name">Naam</th>
<th class="ei-th-type">Type</th>
<th class="ei-th-city">Stad</th>
<th class="ei-th-contact ei-col-hide-side">Contact</th>
<th class="ei-th-score" x-show="mapHalalMode">Score</th>
<th class="ei-th-country">Land</th>
<th class="ei-th-open ei-col-hide-side"></th>
</tr>
</thead>
<tbody>
2026-07-30 09:33:03 +00:00
<template x-for="e in (mapHalalMode && halalTopEntities.length ? halalTopEntities : displayEntities())" :key="e.id">
<tr
2026-07-30 09:33:03 +00:00
:data-entity-id="e.id"
:class="{ 'ei-row-active': selected && selected.id === e.id && drawerOpen, 'ei-row-selected': isSelected(e.id), 'ei-row-crm': e.client_id }"
style="cursor:pointer"
2026-07-30 09:33:03 +00:00
title="Klik voor profiel"
>
<td class="ei-td-check" @click.stop>
<input type="checkbox" :checked="isSelected(e.id)" @change="toggleSelect(e.id, $event)" />
</td>
<td class="ei-td-fav" @click.stop>
<button type="button" class="ei-fav-btn" :class="{ active: e.is_favorite }" @click="toggleFavorite(e.id, e.is_favorite, $event)" x-text="e.is_favorite ? '★' : '☆'"></button>
</td>
2026-07-30 09:33:03 +00:00
<td class="ei-td-name">
<div class="ei-name-cell">
<button type="button" class="ei-row-open-btn" x-text="e.name" :title="e.name"></button>
<span class="ei-crm-badge" x-show="e.client_id" title="In CRM">CRM</span>
2026-07-30 09:33:03 +00:00
</div>
</td>
<td class="ei-td-type"><span class="ei-type-pill ei-type-pill-sm" :class="'ei-type-'+e.entity_type" x-text="typeLabel(e.entity_type)"></span></td>
<td class="ei-td-city" x-text="e.city ? (e.city + (e.country_iso2 ? ' · ' + e.country_iso2 : '')) : (e.country_iso2 || '—')"></td>
<td class="ei-td-contact ei-contact-cell" x-text="contactPreview(e)"></td>
<td class="ei-td-score" x-show="mapHalalMode"><span class="ei-halal-pill" :class="'ei-halal-'+(e.halal_score>=75?'hot':e.halal_score>=55?'warm':'mild')" x-text="Math.round(e.halal_score||0)"></span></td>
<td class="ei-td-country" x-text="territoryName(e.country_iso2) || e.country_iso2"></td>
<td class="ei-td-open"><span class="ei-open-chevron">Profiel →</span></td>
</tr>
</template>
</tbody>
</table>
</div>
<div class="ei-empty" x-show="!entities.length">
<p>Geen entities met geo in selectie.</p>
<button class="btn btn-sm btn-pulse ei-empty-cta" @click="syncKind('distributors')">Start sync — fase B vult data</button>
</div>
2026-07-30 09:33:03 +00:00
</div>
</aside>
</div>
</div>
</div>
</section>
<!-- Distributeurs -->
2026-07-30 09:33:03 +00:00
<section x-show="tab==='distributors'" x-cloak>
<div class="ei-tab-panel">
<div class="ei-tab-panel-head">
2026-07-30 09:33:03 +00:00
<div><h2>Distributeurs & groothandels</h2><p>OSM · website-scrape · handmatig (gratis)</p></div>
<button class="btn btn-sm btn-pulse" @click="syncKind('distributors')" :disabled="busy">↻ Sync</button>
</div>
2026-07-30 09:33:03 +00:00
<div class="ei-tab-panel-body ei-workspace-body">
<div class="ei-split ei-split-side">
<div class="ei-map-wrap">
<div class="ei-map-loading" x-show="tabMapLoading" x-cloak><span class="ei-map-spinner"></span> Kaart laden…</div>
<div id="ei-map-distributors"></div>
</div>
<aside class="ei-side-panel">
<div class="ei-side-panel-head">
<h3>Distributeurs op kaart</h3>
<p>Klik een pin of tabelrij voor het volledige profiel.</p>
</div>
<div class="ei-side-panel-body">
<div class="ei-table-wrap">
2026-07-30 09:33:03 +00:00
<table class="ei-table ei-table-compact">
<thead><tr>
<th class="ei-th-check"><input type="checkbox" @change="toggleSelectAllVisible()" title="Alles" /></th>
2026-07-30 09:33:03 +00:00
<th class="ei-th-name">Naam</th><th class="ei-th-type">Type</th><th class="ei-th-country">Land</th><th class="ei-th-city">Stad</th><th class="ei-th-contact ei-col-hide-side">Contact</th><th class="ei-th-conf ei-col-hide-side">Conf.</th><th class="ei-th-open ei-col-hide-side"></th>
</tr></thead>
<tbody>
2026-07-30 09:33:03 +00:00
<template x-for="e in displayEntities()" :key="e.id">
<tr :data-entity-id="e.id" :class="{ 'ei-row-selected': isSelected(e.id), 'ei-row-crm': e.client_id, 'ei-row-active': selected && selected.id === e.id && drawerOpen }" style="cursor:pointer" title="Klik voor profiel">
<td class="ei-td-check" @click.stop>
<input type="checkbox" :checked="isSelected(e.id)" @change="toggleSelect(e.id, $event)" />
</td>
2026-07-30 09:33:03 +00:00
<td class="ei-td-name">
<div class="ei-name-cell">
<button type="button" class="ei-row-open-btn" x-text="e.name" :title="e.name"></button>
<span class="ei-crm-badge" x-show="e.client_id">CRM</span>
2026-07-30 09:33:03 +00:00
</div>
</td>
2026-07-30 09:33:03 +00:00
<td class="ei-td-type"><span class="ei-type-pill ei-type-pill-sm" :class="'ei-type-'+e.entity_type" x-text="typeLabel(e.entity_type)"></span></td>
<td class="ei-td-country" x-text="territoryName(e.country_iso2) || e.country_iso2"></td>
<td class="ei-td-city" x-text="e.city ? (e.city + (e.country_iso2 ? ' · ' + e.country_iso2 : '')) : (e.country_iso2 || '—')"></td>
<td class="ei-td-contact ei-contact-cell" x-text="contactPreview(e)"></td>
<td class="ei-td-conf ei-confidence" x-text="(e.confidence||0)+'%'"></td>
<td class="ei-td-open"><span class="ei-open-chevron">Profiel →</span></td>
</tr>
</template>
</tbody>
</table>
</div>
<div class="ei-empty" x-show="!entities.length">
<p>Nog geen distributeurs in deze selectie.</p>
<button class="btn btn-sm btn-pulse" @click="syncKind('distributors')">Sync distributeurs (fase B)</button>
</div>
2026-07-30 09:33:03 +00:00
</div>
</aside>
</div>
</div>
</div>
</section>
<!-- Eindklanten -->
<section x-show="tab==='customers'" x-cloak>
<div class="ei-tab-panel">
<div class="ei-tab-panel-head">
<div><h2>Eindklanten</h2><p>Restaurants · döner · slagers · horeca</p></div>
<button class="btn btn-sm btn-pulse" @click="syncKind('customers')" :disabled="busy || !country">↻ Sync horeca</button>
</div>
2026-07-30 09:33:03 +00:00
<div class="ei-tab-panel-body ei-workspace-body">
<div class="ei-split ei-split-side">
<div class="ei-map-wrap">
<div class="ei-map-loading" x-show="tabMapLoading" x-cloak><span class="ei-map-spinner"></span> Kaart laden…</div>
<div id="ei-map-customers"></div>
</div>
<aside class="ei-side-panel">
<div class="ei-side-panel-head">
<h3>Eindklanten op kaart</h3>
<p>Klik een pin of tabelrij voor het volledige profiel.</p>
</div>
<div class="ei-side-panel-body">
<div class="ei-table-wrap">
2026-07-30 09:33:03 +00:00
<table class="ei-table ei-table-compact">
<thead><tr>
<th class="ei-th-check"><input type="checkbox" @change="toggleSelectAllVisible()" title="Alles" /></th>
<th class="ei-th-name">Naam</th><th class="ei-th-type">Type</th><th class="ei-th-country">Land</th><th class="ei-th-city">Stad</th><th class="ei-th-contact ei-col-hide-side">Contact</th><th class="ei-th-open ei-col-hide-side"></th>
</tr></thead>
<tbody>
2026-07-30 09:33:03 +00:00
<template x-for="e in displayEntities()" :key="e.id">
<tr :data-entity-id="e.id" :class="{ 'ei-row-selected': isSelected(e.id), 'ei-row-crm': e.client_id, 'ei-row-active': selected && selected.id === e.id && drawerOpen }" style="cursor:pointer" title="Klik voor profiel">
<td class="ei-td-check" @click.stop>
<input type="checkbox" :checked="isSelected(e.id)" @change="toggleSelect(e.id, $event)" />
</td>
<td class="ei-td-name">
<div class="ei-name-cell">
<button type="button" class="ei-row-open-btn" x-text="e.name" :title="e.name"></button>
<span class="ei-crm-badge" x-show="e.client_id">CRM</span>
</div>
</td>
<td class="ei-td-type"><span class="ei-type-pill ei-type-pill-sm" :class="'ei-type-'+e.entity_type" x-text="typeLabel(e.entity_type)"></span></td>
<td class="ei-td-country" x-text="territoryName(e.country_iso2) || e.country_iso2"></td>
<td class="ei-td-city" x-text="e.city ? (e.city + (e.country_iso2 ? ' · ' + e.country_iso2 : '')) : (e.country_iso2 || '—')"></td>
<td class="ei-td-contact ei-contact-cell" x-text="contactPreview(e)"></td>
<td class="ei-td-open"><span class="ei-open-chevron">Profiel →</span></td>
</tr>
</template>
</tbody>
</table>
</div>
<div class="ei-empty" x-show="!entities.length">
<p>Selecteer een land en sync horeca via OSM.</p>
</div>
2026-07-30 09:33:03 +00:00
</div>
</aside>
</div>
</div>
</div>
</section>
<!-- Cateraars -->
<section x-show="tab==='caterers'" x-cloak>
<div class="ei-tab-panel">
<div class="ei-tab-panel-head">
<div><h2>Facilitaire cateraars</h2><p>Albron · Sodexo · Compass · presence per land</p></div>
<button class="btn btn-sm btn-pulse" @click="syncKind('caterers')" :disabled="busy">↻ Sync</button>
</div>
2026-07-30 09:33:03 +00:00
<div class="ei-tab-panel-body ei-workspace-body">
<div class="ei-split ei-split-side">
<div class="ei-map-wrap">
<div class="ei-map-loading" x-show="tabMapLoading" x-cloak><span class="ei-map-spinner"></span> Kaart laden…</div>
<div id="ei-map-caterers"></div>
</div>
<aside class="ei-side-panel">
<div class="ei-side-panel-head">
<h3>Cateraars op kaart</h3>
<p>Klik een pin of tabelrij voor het volledige profiel.</p>
</div>
<div class="ei-side-panel-body">
<div class="ei-side-section">
<h4>Brand presence</h4>
<div class="ei-table-wrap">
2026-07-30 09:33:03 +00:00
<table class="ei-table ei-table-compact ei-table-brand">
<thead><tr><th class="ei-th-name">Brand</th><th class="ei-th-country">Land</th><th>Lokale naam</th><th>Takken</th></tr></thead>
<tbody>
<template x-for="p in catererPresence" :key="p.id">
2026-07-30 09:33:03 +00:00
<tr
:data-entity-id="p.entity_id || null"
:class="{ 'ei-row-active': p.entity_id && selected && selected.id === p.entity_id && drawerOpen, 'ei-row-crm': p.client_id }"
:style="p.entity_id ? 'cursor:pointer' : ''"
:title="p.entity_id ? 'Klik voor profiel' : ''"
>
<td class="ei-td-name">
<div class="ei-name-cell">
<button type="button" class="ei-row-open-btn" x-show="p.entity_id" x-text="p.brand_name"></button>
<span x-show="!p.entity_id" x-text="p.brand_name"></span>
</div>
</td>
<td class="ei-td-country" x-text="territoryName(p.country_iso2) || p.country_iso2"></td>
<td x-text="p.local_legal_name||'—'"></td>
<td x-text="(p.service_lines||[]).join(', ')"></td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
2026-07-30 09:33:03 +00:00
<div class="ei-side-section">
<h4>Cateraar entities</h4>
<div class="ei-table-wrap">
2026-07-30 09:33:03 +00:00
<table class="ei-table ei-table-compact">
<thead><tr>
<th class="ei-th-name">Naam</th><th class="ei-th-type">Type</th><th class="ei-th-country">Land</th><th class="ei-th-city">Stad</th><th class="ei-th-contact ei-col-hide-side">Contact</th><th class="ei-th-conf ei-col-hide-side">Conf.</th><th class="ei-th-open ei-col-hide-side"></th>
</tr></thead>
<tbody>
2026-07-30 09:33:03 +00:00
<template x-for="e in displayEntities()" :key="e.id">
<tr :data-entity-id="e.id" :class="{ 'ei-row-active': selected && selected.id === e.id && drawerOpen, 'ei-row-crm': e.client_id }" style="cursor:pointer" title="Klik voor profiel">
<td class="ei-td-name">
<div class="ei-name-cell">
<button type="button" class="ei-row-open-btn" x-text="e.name" :title="e.name"></button>
<span class="ei-crm-badge" x-show="e.client_id">CRM</span>
</div>
</td>
<td class="ei-td-type"><span class="ei-type-pill ei-type-pill-sm" :class="'ei-type-'+e.entity_type" x-text="typeLabel(e.entity_type)"></span></td>
<td class="ei-td-country" x-text="territoryName(e.country_iso2) || e.country_iso2"></td>
<td class="ei-td-city" x-text="e.city ? (e.city + (e.country_iso2 ? ' · ' + e.country_iso2 : '')) : (e.country_iso2 || '—')"></td>
<td class="ei-td-contact ei-contact-cell" x-text="contactPreview(e)"></td>
<td class="ei-td-conf ei-confidence" x-text="(e.confidence||0)+'%'"></td>
<td class="ei-td-open"><span class="ei-open-chevron">Profiel →</span></td>
</tr>
</template>
</tbody>
</table>
</div>
2026-07-30 09:33:03 +00:00
</div>
</aside>
</div>
</div>
</div>
</section>
<!-- Contacten -->
<section x-show="tab==='contacts'" x-cloak>
<div class="ei-tab-panel">
<div class="ei-tab-panel-head">
<div><h2>Contactregistry</h2><p>E-mail · telefoon · outreach-ready · direct naar CRM</p></div>
<div class="ei-tab-panel-actions">
<button type="button" class="btn btn-sm btn-pulse-green" :disabled="!selectedIds.length || crmPushBusy" @click="openCrmModal()">➕ Geselecteerde naar CRM</button>
<a class="btn btn-sm" :href="contactsExportUrl()" target="_blank">CSV export</a>
</div>
</div>
2026-07-30 09:33:03 +00:00
<div class="ei-tab-panel-body ei-workspace-body">
<div class="ei-split ei-split-side">
<div class="ei-map-wrap">
<div class="ei-map-loading" x-show="tabMapLoading" x-cloak><span class="ei-map-spinner"></span> Kaart laden…</div>
<div id="ei-map-contacts"></div>
</div>
<aside class="ei-side-panel">
<div class="ei-side-panel-head">
<h3>Contacten op kaart</h3>
<p>Klik een pin of tabelrij voor het volledige profiel.</p>
</div>
<div class="ei-side-panel-body">
<div class="ei-table-wrap">
2026-07-30 09:33:03 +00:00
<table class="ei-table ei-table-compact ei-table-contacts">
<thead>
<tr>
<th class="ei-th-check"><input type="checkbox" @change="toggleSelectAllVisible()" title="Alles selecteren" /></th>
2026-07-30 09:33:03 +00:00
<th class="ei-th-name">Organisatie</th>
<th class="ei-th-city">Stad</th>
<th class="ei-th-type">Type</th>
<th class="ei-th-country">Land</th>
<th class="ei-col-hide-side">Rol</th>
<th class="ei-col-hide-side ei-th-name">Naam</th>
<th class="ei-col-hide-side">E-mail</th>
<th class="ei-col-hide-side">Telefoon</th>
<th class="ei-col-hide-side">CRM</th>
<th class="ei-th-open ei-col-hide-side"></th>
<th></th>
</tr>
</thead>
<tbody>
<template x-for="c in contacts" :key="c.id">
2026-07-30 09:33:03 +00:00
<tr :data-entity-id="c.entity_id" :class="{ 'ei-row-selected': isSelected(c.entity_id), 'ei-row-crm': c.client_id, 'ei-row-active': selected && selected.id === c.entity_id && drawerOpen }" style="cursor:pointer" title="Klik voor profiel">
<td class="ei-td-check" @click.stop>
<input type="checkbox" :checked="isSelected(c.entity_id)" @change="toggleSelect(c.entity_id, $event)" />
</td>
2026-07-30 09:33:03 +00:00
<td class="ei-td-name">
<div class="ei-name-cell">
<button type="button" class="ei-row-open-btn" x-text="c.entity_name" :title="c.entity_name"></button>
<span class="ei-crm-badge" x-show="c.client_id">CRM</span>
</div>
</td>
2026-07-30 09:33:03 +00:00
<td class="ei-td-city" x-text="c.entity_city ? (c.entity_city + (c.country_iso2 ? ' · ' + c.country_iso2 : '')) : (c.country_iso2 || '—')"></td>
<td class="ei-td-type"><span class="ei-type-pill ei-type-pill-sm" :class="'ei-type-'+c.entity_type" x-text="typeLabel(c.entity_type)"></span></td>
<td class="ei-td-country" x-text="territoryName(c.country_iso2) || c.country_iso2"></td>
<td class="ei-col-hide-side" x-text="c.role || '—'"></td>
<td class="ei-col-hide-side" x-text="c.name||'—'"></td>
<td class="ei-col-hide-side" x-text="c.email||'—'"></td>
<td class="ei-col-hide-side" x-text="c.phone||c.mobile||'—'"></td>
<td class="ei-col-hide-side">
<span class="ei-crm-badge" x-show="c.client_id" :title="c.crm_client_name || 'In CRM'">CRM</span>
<span class="muted" x-show="!c.client_id">—</span>
</td>
2026-07-30 09:33:03 +00:00
<td class="ei-td-open"><span class="ei-open-chevron">Profiel →</span></td>
<td class="ei-td-actions" @click.stop>
<button type="button" class="btn btn-sm btn-pulse-green" x-show="!c.client_id" @click="pushEntityToCrm(c.entity_id)" :disabled="crmPushBusy">➕ CRM</button>
<a class="btn btn-sm" x-show="c.client_id" :href="'/clients'" title="Bekijk in CRM">Open</a>
</td>
</tr>
</template>
</tbody>
</table>
</div>
<div class="ei-load-more" x-show="contactsHasMore">
<button type="button" class="btn btn-sm" @click="loadMoreContacts()" :disabled="contactsLoadingMore || busy">
<span x-text="contactsLoadingMore ? 'Laden…' : ('Meer laden (' + contacts.length + ' / ' + resultCount + ')')"></span>
</button>
</div>
<div class="ei-empty" x-show="!contacts.length && !busy">
<p>Geen contacten voor deze filters — pas land, type of zoekterm aan.</p>
<button class="btn btn-sm btn-pulse-green" @click="syncKind('contacts')">Sync contacten</button>
</div>
2026-07-30 09:33:03 +00:00
</div>
</aside>
</div>
</div>
</div>
</section>
<!-- Tenders -->
<section x-show="tab==='tenders'" x-cloak>
<div class="ei-tab-panel">
<div class="ei-tab-panel-head">
<div><h2>Tenders</h2><p>TED API · food · catering · meat</p></div>
<button class="btn btn-sm btn-pulse-purple" @click="syncKind('tenders')" :disabled="busy">↻ TED sync</button>
</div>
2026-07-30 09:33:03 +00:00
<div class="ei-tab-panel-body">
<div class="ei-panel" style="border:none;background:transparent;padding:0">
<div class="ei-table-wrap">
2026-07-30 09:33:03 +00:00
<table class="ei-table ei-table-compact">
<thead><tr><th>Titel</th><th class="ei-th-country">Land</th><th>Status</th><th>Link</th></tr></thead>
<tbody>
<template x-for="t in tenders" :key="t.id">
<tr>
<td x-text="t.title"></td>
2026-07-30 09:33:03 +00:00
<td x-text="territoryName(t.country_iso2) || t.country_iso2||'—'"></td>
<td x-text="t.status"></td>
<td><a :href="t.url" target="_blank" rel="noopener">Open</a></td>
</tr>
</template>
</tbody>
</table>
</div>
<div class="ei-empty" x-show="!tenders.length">
<p>Geen tenders — klik TED sync.</p>
</div>
</div>
</div>
</div>
</section>
<!-- Overheid -->
<section x-show="tab==='gov'" x-cloak>
<div class="ei-tab-panel">
<div class="ei-tab-panel-head">
<div><h2>Overheid & data</h2><p>Statistiek · tender portals per land</p></div>
</div>
2026-07-30 09:33:03 +00:00
<div class="ei-tab-panel-body">
<div class="ei-panel" style="border:none;background:transparent;padding:0">
<div class="ei-table-wrap">
2026-07-30 09:33:03 +00:00
<table class="ei-table ei-table-compact">
<thead><tr><th class="ei-th-country">Land</th><th class="ei-th-name">Naam</th><th>Categorie</th><th>Link</th></tr></thead>
<tbody>
<template x-for="g in govSources" :key="g.id">
<tr>
2026-07-30 09:33:03 +00:00
<td x-text="territoryName(g.country_iso2) || g.country_iso2"></td>
<td x-text="g.name"></td>
<td x-text="g.category"></td>
<td><a :href="g.url" target="_blank" rel="noopener">Open</a></td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
</div>
</section>
<!-- Pipeline -->
<section x-show="tab==='pipeline'" x-cloak>
<div class="ei-tab-panel">
<div class="ei-tab-panel-head">
<div><h2>CRM Pipeline</h2><p>Van Wereldexport naar klanten & deals</p></div>
<div class="retail-actions">
<button type="button" class="btn btn-sm btn-pulse-green" :disabled="!selectedIds.length" @click="openCrmModal()">➕ Geselecteerde naar CRM</button>
<a class="btn btn-sm btn-pulse" href="/clients">Klanten</a>
<a class="btn btn-sm btn-pulse" href="/deals">Deals</a>
</div>
</div>
2026-07-30 09:33:03 +00:00
<div class="ei-tab-panel-body ei-workspace-body">
<div class="ei-split ei-split-side">
<div class="ei-map-wrap">
<div class="ei-map-loading" x-show="tabMapLoading" x-cloak><span class="ei-map-spinner"></span> Kaart laden…</div>
<div id="ei-map-pipeline"></div>
</div>
<aside class="ei-side-panel">
<div class="ei-side-panel-head">
<h3>CRM pipeline op kaart</h3>
<p>Klik een pin of tabelrij voor het volledige profiel.</p>
</div>
<div class="ei-side-panel-body">
<p class="ei-side-hint ei-pipeline-hint">Entities via <strong>Naar CRM</strong> — koppeling naar klant + deal.</p>
<div class="ei-table-wrap" x-show="crmPipeline.length">
2026-07-30 09:33:03 +00:00
<table class="ei-table ei-table-compact ei-table-pipeline">
<thead>
<tr>
2026-07-30 09:33:03 +00:00
<th class="ei-th-name">Organisatie</th>
<th class="ei-th-type">Type</th>
<th class="ei-th-country">Land</th>
<th class="ei-th-pipe-meta">CRM klant</th>
<th class="ei-th-pipe-meta">Deal</th>
<th class="ei-th-pipe-meta">Datum</th>
<th class="ei-th-open ei-col-hide-side"></th>
</tr>
</thead>
<tbody>
2026-07-30 09:33:03 +00:00
<template x-for="p in displayPipeline()" :key="p.id">
<tr :data-entity-id="p.id" :class="{ 'ei-row-active': selected && selected.id === p.id && drawerOpen, 'ei-row-crm': p.client_id }" style="cursor:pointer" title="Klik voor profiel">
<td class="ei-td-name">
<div class="ei-name-cell">
<button type="button" class="ei-row-open-btn" x-text="p.name" :title="p.name"></button>
<span class="ei-crm-badge" x-show="p.client_id">CRM</span>
</div>
</td>
<td class="ei-td-type"><span class="ei-type-pill ei-type-pill-sm" :class="'ei-type-'+p.entity_type" x-text="typeLabel(p.entity_type)"></span></td>
<td class="ei-td-country" x-text="territoryName(p.country_iso2) || p.country_iso2"></td>
<td class="ei-td-pipe-meta">
<a x-show="p.client_id" :href="'/clients'" @click.stop x-text="p.crm_client_name || ('#'+p.client_id)"></a>
</td>
2026-07-30 09:33:03 +00:00
<td class="ei-td-pipe-meta" x-text="p.crm_deal_title || '—'"></td>
<td class="ei-td-pipe-meta" x-text="p.crm_pushed_at ? new Date(p.crm_pushed_at).toLocaleDateString('nl-NL') : '—'"></td>
<td class="ei-td-open"><span class="ei-open-chevron">Profiel →</span></td>
</tr>
</template>
</tbody>
</table>
</div>
<div class="ei-empty" x-show="!crmPipeline.length">
<p>Nog geen entities in CRM vanuit Wereldexport.</p>
<p>Selecteer leads op de kaart en klik <strong>➕ Naar CRM</strong>.</p>
2026-07-30 09:33:03 +00:00
</div>
</aside>
</div>
</div>
</div>
</section>
</div>
</div>
2026-07-30 09:33:03 +00:00
<!-- Profiel popup (modal) — content rendered by ExportIntelProfile JS -->
<div id="ei-profile-modal" class="ei-profile-modal-root" aria-hidden="true">
<div class="ei-profile-backdrop" data-ei-action="close"></div>
<div class="ei-profile-panel" role="dialog" aria-modal="true" aria-labelledby="ei-profile-title">
<div id="ei-profile-mount"></div>
</div>
</div>
<!-- CRM bevestiging -->
<div class="ei-modal-root" x-show="crmModalOpen" x-cloak>
<div class="ei-drawer-backdrop" @click="crmModalOpen=false"></div>
<div class="ei-modal-card">
<h3>Toevoegen aan CRM</h3>
<p><strong x-text="selectedIds.length"></strong> organisatie(s) worden klant in CRM.</p>
<label class="ei-filter-check">
<input type="checkbox" x-model="crmCreateDeals" />
<span>Maak ook een deal per klant (aanbevolen)</span>
</label>
<p class="ei-modal-note">Bestaande klanten met hetzelfde e-mailadres worden gekoppeld i.p.v. dubbel aangemaakt.</p>
<div class="ei-modal-actions">
<button type="button" class="btn btn-sm" @click="crmModalOpen=false">Annuleren</button>
<button type="button" class="btn btn-sm btn-pulse-green" @click="pushToCrm()" :disabled="crmPushBusy">
<span x-text="crmPushBusy ? 'Bezig…' : 'Bevestig → CRM'"></span>
</button>
</div>
</div>
</div>
</div>
{% endblock %}
2026-07-30 09:33:03 +00:00
{% block scripts %}{% endblock %}