c5ea8cbb63
Make A2A togglable, keep reply context while reading, route agent data to //10.4.7.11/share/Agents, and polish themes plus export-intel sync cancel.
1164 lines
41 KiB
JavaScript
1164 lines
41 KiB
JavaScript
function exportIntelApp() {
|
|
return {
|
|
tab: 'map',
|
|
tabIndicatorTop: 0,
|
|
tabIndicatorH: 40,
|
|
busy: false,
|
|
overlayDismissed: false,
|
|
syncAbort: null,
|
|
syncMsg: '',
|
|
syncMonitor: null,
|
|
syncMonitorOpen: true,
|
|
syncMonitorPoll: null,
|
|
googleMapsKey: '',
|
|
googleMapsKeySet: false,
|
|
googleKeySaving: false,
|
|
stats: {},
|
|
regions: [],
|
|
territories: [],
|
|
country: '',
|
|
region: '',
|
|
entities: [],
|
|
contacts: [],
|
|
contactsHasMore: false,
|
|
contactsLoadingMore: false,
|
|
tenders: [],
|
|
catererPresence: [],
|
|
catererBrands: [],
|
|
govSources: [],
|
|
selected: null,
|
|
drawerOpen: false,
|
|
q: '',
|
|
entityTypeFilter: '',
|
|
mapTypesSelected: [],
|
|
mapHalalMode: false,
|
|
mapHalalMin: 55,
|
|
mapError: '',
|
|
mapLoading: false,
|
|
tabMapLoading: false,
|
|
mapTruncated: false,
|
|
halalTopMarkets: [],
|
|
halalTopEntities: [],
|
|
listFocusId: null,
|
|
listFocusDetail: null,
|
|
listFocusLoading: false,
|
|
drawerLoading: false,
|
|
filterHasEmail: '',
|
|
filterFavoritesOnly: false,
|
|
filterCrmStatus: '',
|
|
filterConfidenceMin: '',
|
|
filterCity: '',
|
|
filterTenderStatus: '',
|
|
filterGovCategory: '',
|
|
_cityTimer: null,
|
|
selectedIds: [],
|
|
crmPipeline: [],
|
|
crmModalOpen: false,
|
|
crmCreateDeals: true,
|
|
crmPushBusy: false,
|
|
editMode: false,
|
|
editForm: {},
|
|
resultCount: 0,
|
|
_searchTimer: null,
|
|
|
|
tabs: [
|
|
{ id: 'map', icon: '🗺️', label: 'Kaart', color: '#0ea5e9', key: '1' },
|
|
{ id: 'distributors', icon: '📦', label: 'Distributeurs', color: '#fbbf24', key: '2' },
|
|
{ id: 'customers', icon: '🍽️', label: 'Eindklanten', color: '#34d399', key: '3' },
|
|
{ id: 'caterers', icon: '🏢', label: 'Cateraars', color: '#a78bfa', key: '4' },
|
|
{ id: 'contacts', icon: '📇', label: 'Contacten', color: '#38bdf8', key: '5' },
|
|
{ id: 'tenders', icon: '📋', label: 'Tenders', color: '#c084fc', key: '6' },
|
|
{ id: 'gov', icon: '🏛️', label: 'Overheid', color: '#94a3b8', key: '7' },
|
|
{ id: 'pipeline', icon: '🔗', label: 'Pipeline', color: '#fb923c', key: '8' },
|
|
],
|
|
|
|
|
|
TYPE_GROUPS: {
|
|
b2b: ['distributor', 'wholesaler', 'importer', 'logistics', 'cold_storage', 'port_agent'],
|
|
horeca: ['restaurant', 'doner_shoarma', 'butcher', 'foodservice'],
|
|
caterer: ['contract_caterer'],
|
|
},
|
|
TYPE_CHIP_EXPAND: {
|
|
distributor: 'b2b',
|
|
importer: 'b2b',
|
|
logistics: 'b2b',
|
|
restaurant: 'horeca',
|
|
doner_shoarma: 'horeca',
|
|
butcher: 'horeca',
|
|
foodservice: 'horeca',
|
|
},
|
|
|
|
expandEntityTypes(typeOrList) {
|
|
var self = this;
|
|
var raw = Array.isArray(typeOrList) ? typeOrList : [typeOrList];
|
|
var out = [];
|
|
raw.forEach(function (v) {
|
|
if (!v) return;
|
|
var key = String(v).trim();
|
|
var group = self.TYPE_CHIP_EXPAND[key];
|
|
if (group && self.TYPE_GROUPS[group]) {
|
|
out = out.concat(self.TYPE_GROUPS[group]);
|
|
} else {
|
|
out.push(key);
|
|
}
|
|
});
|
|
return out.filter(function (v, i, a) { return a.indexOf(v) === i; });
|
|
},
|
|
|
|
mapTypesParam() {
|
|
if (this.entityTypeFilter) {
|
|
var expanded = this.expandEntityTypes(this.entityTypeFilter);
|
|
if (expanded.length === 1) return { entity_type: expanded[0] };
|
|
if (expanded.length) return { entity_types: expanded.join(',') };
|
|
}
|
|
if (this.mapTypesSelected.length) {
|
|
var all = this.expandEntityTypes(this.mapTypesSelected);
|
|
if (all.length === 1) return { entity_type: all[0] };
|
|
if (all.length) return { entity_types: all.join(',') };
|
|
}
|
|
return {};
|
|
},
|
|
|
|
shouldAutoFitMap(featCount) {
|
|
return !!(this.country || this.region || this.q || this.entityTypeFilter ||
|
|
this.mapTypesSelected.length || this.mapHalalMode || this.filterFavoritesOnly ||
|
|
this.filterCrmStatus || (featCount > 0 && featCount <= 800));
|
|
},
|
|
|
|
mapTypeChips: [
|
|
{ v: 'distributor', l: 'B2B / distri', icon: '📦' },
|
|
{ v: 'wholesaler', l: 'Groothandel', icon: '🏪' },
|
|
{ v: 'importer', l: 'Importeur', icon: '🚢' },
|
|
{ v: 'logistics', l: 'Logistiek', icon: '🚚' },
|
|
{ v: 'restaurant', l: 'Horeca', icon: '🍽️' },
|
|
{ v: 'doner_shoarma', l: 'Döner', icon: '🥙' },
|
|
{ v: 'butcher', l: 'Slager', icon: '🥩' },
|
|
{ v: 'contract_caterer', l: 'Cateraar', icon: '🏢' },
|
|
{ v: 'foodservice', l: 'Foodservice', icon: '🍴' },
|
|
],
|
|
|
|
async init() {
|
|
var params = new URLSearchParams(location.search);
|
|
var t = params.get('tab');
|
|
if (t) this.tab = t;
|
|
var self = this;
|
|
window.__exportIntelApp = self;
|
|
document.addEventListener('export-intel:open-entity', function (ev) {
|
|
if (ev.detail && ev.detail.id) self.openEntity(ev.detail.id);
|
|
});
|
|
await this.loadMeta();
|
|
await this.refresh();
|
|
this.startSyncMonitor(true);
|
|
this.$nextTick(() => this.updateTabIndicator());
|
|
window.addEventListener('keydown', (e) => {
|
|
if (e.target.matches('input, textarea, select')) return;
|
|
var n = parseInt(e.key, 10);
|
|
if (n >= 1 && n <= 8) {
|
|
var btn = this.$refs.vtabsNav?.querySelector('[data-tab="' + this.tabs[n - 1].id + '"]');
|
|
this.setTab(this.tabs[n - 1].id, btn);
|
|
}
|
|
});
|
|
},
|
|
|
|
async loadMeta() {
|
|
try {
|
|
this.regions = await fetch('/api/export-intel/regions').then((r) => r.json());
|
|
await this.loadGoogleSettings();
|
|
this.territories = await fetch('/api/export-intel/territories').then((r) => r.json());
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
},
|
|
|
|
async refresh() {
|
|
this.busy = true;
|
|
try {
|
|
var cp = this.country ? '?country=' + encodeURIComponent(this.country) : '';
|
|
var rp = this.region ? (cp ? '&' : '?') + 'region=' + encodeURIComponent(this.region) : '';
|
|
this.stats = await fetch('/api/export-intel/stats' + cp + rp).then((r) => r.json());
|
|
await this.loadTabData();
|
|
if (this.tab === 'map') this.loadMap();
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
this.busy = false;
|
|
},
|
|
|
|
filterQs() {
|
|
var parts = [];
|
|
if (this.country) parts.push('country=' + encodeURIComponent(this.country));
|
|
if (this.region) parts.push('region=' + encodeURIComponent(this.region));
|
|
if (this.q && this.q.trim()) parts.push('q=' + encodeURIComponent(this.q.trim()));
|
|
if (this.entityTypeFilter) parts.push('entity_type=' + encodeURIComponent(this.entityTypeFilter));
|
|
if (this.filterHasEmail === 'yes') parts.push('has_email=true');
|
|
if (this.filterHasEmail === 'no') parts.push('has_email=false');
|
|
if (this.filterFavoritesOnly) parts.push('favorite_only=true');
|
|
if (this.filterCrmStatus === 'linked') parts.push('crm_linked=true');
|
|
if (this.filterCrmStatus === 'not_linked') parts.push('crm_linked=false');
|
|
return parts.length ? '&' + parts.join('&') : '';
|
|
},
|
|
|
|
onSearchInput() {
|
|
clearTimeout(this._searchTimer);
|
|
this._searchTimer = setTimeout(() => this.applyMapFilters(), 350);
|
|
},
|
|
|
|
clearFilters() {
|
|
this.q = '';
|
|
this.entityTypeFilter = '';
|
|
this.mapTypesSelected = [];
|
|
this.mapHalalMode = false;
|
|
this.filterHasEmail = '';
|
|
this.filterFavoritesOnly = false;
|
|
this.filterCrmStatus = '';
|
|
this.filterConfidenceMin = '';
|
|
this.filterCity = '';
|
|
this.filterTenderStatus = '';
|
|
this.filterGovCategory = '';
|
|
this.selectedIds = [];
|
|
this.applyMapFilters();
|
|
},
|
|
|
|
showEntityTypeFilter() {
|
|
return ['distributors', 'customers', 'caterers', 'contacts'].indexOf(this.tab) >= 0;
|
|
},
|
|
|
|
showMapTypeFilter() {
|
|
return this.tab === 'map';
|
|
},
|
|
|
|
mapTypeCount(v) {
|
|
var geo = (this.stats && this.stats.entity_types_geo) || {};
|
|
var expanded = this.expandEntityTypes(v);
|
|
var n = 0;
|
|
expanded.forEach(function (t) { n += Number(geo[t] || 0); });
|
|
return n;
|
|
},
|
|
|
|
isMapTypeActive(v) {
|
|
return this.mapTypesSelected.indexOf(v) >= 0;
|
|
},
|
|
|
|
toggleMapType(v) {
|
|
var i = this.mapTypesSelected.indexOf(v);
|
|
if (i >= 0) this.mapTypesSelected.splice(i, 1);
|
|
else this.mapTypesSelected.push(v);
|
|
this.entityTypeFilter = '';
|
|
this.applyMapFilters();
|
|
},
|
|
|
|
clearMapTypes() {
|
|
this.mapTypesSelected = [];
|
|
this.applyMapFilters();
|
|
},
|
|
|
|
applyMapFilters() {
|
|
if (this.tab !== 'map') {
|
|
this.loadTabData();
|
|
return;
|
|
}
|
|
this.loadTabData().then(() => this.loadMap());
|
|
},
|
|
|
|
showEmailFilter() {
|
|
return ['map', 'distributors', 'customers', 'caterers', 'contacts'].indexOf(this.tab) >= 0;
|
|
},
|
|
|
|
showCrmFilter() {
|
|
return ['map', 'distributors', 'customers', 'caterers', 'contacts', 'pipeline'].indexOf(this.tab) >= 0;
|
|
},
|
|
|
|
showCollectionFilters() {
|
|
return this.showCrmFilter() || this.showFavoritesFilter();
|
|
},
|
|
|
|
showFavoritesFilter() {
|
|
return ['map', 'distributors', 'customers', 'caterers', 'contacts'].indexOf(this.tab) >= 0;
|
|
},
|
|
|
|
showConfidenceFilter() {
|
|
return this.tab === 'distributors';
|
|
},
|
|
|
|
showCityFilter() {
|
|
return ['distributors', 'customers', 'caterers', 'contacts'].indexOf(this.tab) >= 0;
|
|
},
|
|
|
|
showContextFilters() {
|
|
return this.showEntityTypeFilter() || this.showEmailFilter() || this.showConfidenceFilter() || this.showCityFilter() || this.tab === 'tenders' || this.tab === 'gov';
|
|
},
|
|
|
|
showSelectionBar() {
|
|
return ['map', 'contacts', 'distributors', 'customers', 'caterers'].indexOf(this.tab) >= 0;
|
|
},
|
|
|
|
isWorking() {
|
|
if (this.overlayDismissed) return false;
|
|
return this.busy || this.crmPushBusy || this.mapLoading || this.drawerLoading;
|
|
},
|
|
|
|
dismissOverlay() {
|
|
this.overlayDismissed = true;
|
|
},
|
|
|
|
async cancelSync() {
|
|
try {
|
|
if (this.syncAbort) {
|
|
try { this.syncAbort.abort(); } catch (e) {}
|
|
}
|
|
await fetch('/api/export-intel/sync/cancel', { method: 'POST' });
|
|
this.syncMsg = 'Sync wordt geannuleerd…';
|
|
this.busy = false;
|
|
this.overlayDismissed = true;
|
|
if (window.Cockpit) Cockpit.toast('Sync annuleren…', 'info');
|
|
await this.pollSyncStatus();
|
|
} catch (e) {
|
|
this.syncMsg = 'Annuleren mislukt: ' + e.message;
|
|
}
|
|
},
|
|
|
|
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…';
|
|
},
|
|
|
|
|
|
displayEntities() {
|
|
var list = this.entities || [];
|
|
if (this.filterHasEmail === "yes") {
|
|
list = list.filter(function (e) {
|
|
return e.email || e.primary_email || e.phone || e.primary_phone || e.has_contact;
|
|
});
|
|
} else if (this.filterHasEmail === "no") {
|
|
list = list.filter(function (e) {
|
|
return !e.email && !e.primary_email && !e.phone && !e.primary_phone && !e.has_contact;
|
|
});
|
|
}
|
|
if (this.filterConfidenceMin) {
|
|
var min = Number(this.filterConfidenceMin);
|
|
list = list.filter(function (e) { return (e.confidence || 0) >= min; });
|
|
}
|
|
if (this.filterCity && this.filterCity.trim()) {
|
|
var c = this.filterCity.trim().toLowerCase();
|
|
list = list.filter(function (e) { return (e.city || "").toLowerCase().includes(c); });
|
|
}
|
|
return list;
|
|
},
|
|
|
|
displayCount() {
|
|
if (this.tab === "contacts") return (this.contacts || []).length;
|
|
return this.displayEntities().length;
|
|
},
|
|
|
|
displayPipeline() {
|
|
var list = this.crmPipeline || [];
|
|
if (this.q && this.q.trim()) {
|
|
var ql = this.q.trim().toLowerCase();
|
|
list = list.filter(function (p) { return (p.name || "").toLowerCase().includes(ql); });
|
|
}
|
|
return list;
|
|
},
|
|
|
|
hasActiveFilters() {
|
|
return !!(this.q || this.entityTypeFilter || this.mapTypesSelected.length || this.mapHalalMode ||
|
|
this.filterHasEmail || this.filterFavoritesOnly || this.filterCrmStatus ||
|
|
this.filterConfidenceMin || this.filterCity || this.filterTenderStatus || this.filterGovCategory);
|
|
},
|
|
|
|
applyEntityFilters() {
|
|
if (this.tab === "map") this.applyMapFilters();
|
|
else {
|
|
this.loadTabData();
|
|
}
|
|
},
|
|
|
|
onCityFilterInput() {
|
|
clearTimeout(this._cityTimer);
|
|
},
|
|
|
|
onPipelineFilter() {
|
|
clearTimeout(this._searchTimer);
|
|
},
|
|
|
|
applyGovFilters() {
|
|
this.loadTabData();
|
|
},
|
|
|
|
visibleEntities() {
|
|
if (this.tab === "map" && this.mapHalalMode && this.halalTopEntities.length) {
|
|
return this.halalTopEntities;
|
|
}
|
|
return this.displayEntities();
|
|
},
|
|
|
|
entityTypeOptions() {
|
|
if (this.tab === 'distributors') {
|
|
return [
|
|
{ v: '', l: 'Alle types' },
|
|
{ v: 'distributor', l: 'Distributeur' },
|
|
{ v: 'wholesaler', l: 'Groothandel' },
|
|
{ v: 'importer', l: 'Importeur' },
|
|
{ v: 'logistics', l: 'Logistiek' },
|
|
];
|
|
}
|
|
if (this.tab === 'customers') {
|
|
return [
|
|
{ v: '', l: 'Alle types' },
|
|
{ v: 'restaurant', l: 'Restaurant' },
|
|
{ v: 'doner_shoarma', l: 'Döner / shoarma' },
|
|
{ v: 'butcher', l: 'Slager' },
|
|
{ v: 'foodservice', l: 'Foodservice' },
|
|
];
|
|
}
|
|
if (this.tab === 'caterers') {
|
|
return [{ v: '', l: 'Alle types' }, { v: 'contract_caterer', l: 'Contract cateraar' }];
|
|
}
|
|
if (this.tab === 'pipeline') {
|
|
return [
|
|
{ v: '', l: 'Alle types' },
|
|
{ v: 'distributor', l: 'Distributeur' },
|
|
{ v: 'wholesaler', l: 'Groothandel' },
|
|
{ v: 'contract_caterer', l: 'Cateraar' },
|
|
{ v: 'restaurant', l: 'Restaurant' },
|
|
];
|
|
}
|
|
if (this.tab === 'contacts') {
|
|
return [
|
|
{ v: '', l: 'Alle types' },
|
|
{ v: 'distributor', l: 'Distributeur' },
|
|
{ v: 'wholesaler', l: 'Groothandel' },
|
|
{ v: 'importer', l: 'Importeur' },
|
|
{ v: 'logistics', l: 'Logistiek' },
|
|
{ v: 'contract_caterer', l: 'Cateraar' },
|
|
{ v: 'restaurant', l: 'Restaurant' },
|
|
{ v: 'foodservice', l: 'Foodservice' },
|
|
];
|
|
}
|
|
return [
|
|
{ v: '', l: 'Alle types' },
|
|
{ v: 'distributor', l: 'Distributeur' },
|
|
{ v: 'wholesaler', l: 'Groothandel' },
|
|
{ v: 'contract_caterer', l: 'Cateraar' },
|
|
{ v: 'restaurant', l: 'Restaurant' },
|
|
];
|
|
},
|
|
|
|
|
|
tabMapId() {
|
|
var tabs = ['distributors', 'customers', 'caterers', 'contacts', 'pipeline'];
|
|
if (tabs.indexOf(this.tab) < 0) return null;
|
|
return 'ei-map-' + this.tab;
|
|
},
|
|
|
|
entitiesToGeojson(list) {
|
|
var features = [];
|
|
(list || []).forEach(function (e) {
|
|
if (e.lat == null || e.lon == null) return;
|
|
features.push({
|
|
type: 'Feature',
|
|
geometry: { type: 'Point', coordinates: [Number(e.lon), Number(e.lat)] },
|
|
properties: {
|
|
id: e.id,
|
|
name: e.name,
|
|
entity_type: e.entity_type,
|
|
country_iso2: e.country_iso2,
|
|
city: e.city,
|
|
address_line: e.address_line,
|
|
email: e.email || e.primary_email,
|
|
phone: e.phone || e.primary_phone,
|
|
website: e.website,
|
|
confidence: e.confidence,
|
|
halal_score: e.halal_score,
|
|
halal_tier: e.halal_tier,
|
|
contact_count: e.contact_count,
|
|
pipeline_stage: e.pipeline_stage,
|
|
volume_band: e.volume_band,
|
|
},
|
|
});
|
|
});
|
|
return { type: 'FeatureCollection', features: features };
|
|
},
|
|
|
|
async fetchTabMapBundle() {
|
|
var url = '/api/export-intel/map/bundle';
|
|
var qs = [];
|
|
if (this.country) qs.push('country=' + encodeURIComponent(this.country));
|
|
if (this.region) qs.push('region=' + encodeURIComponent(this.region));
|
|
if (this.q && this.q.trim()) qs.push('q=' + encodeURIComponent(this.q.trim()));
|
|
if (this.filterFavoritesOnly) qs.push('favorite_only=true');
|
|
if (this.filterCrmStatus === 'linked') qs.push('crm_linked=true');
|
|
if (this.filterCrmStatus === 'not_linked') qs.push('crm_linked=false');
|
|
var tabTypes = null;
|
|
if (this.tab === 'distributors') tabTypes = this.TYPE_GROUPS.b2b;
|
|
else if (this.tab === 'customers') tabTypes = this.TYPE_GROUPS.horeca;
|
|
else if (this.tab === 'caterers') tabTypes = this.TYPE_GROUPS.caterer;
|
|
if (this.entityTypeFilter) {
|
|
var ef = this.expandEntityTypes(this.entityTypeFilter);
|
|
tabTypes = ef;
|
|
}
|
|
if (tabTypes) {
|
|
if (tabTypes.length === 1) qs.push('entity_type=' + encodeURIComponent(tabTypes[0]));
|
|
else qs.push('entity_types=' + encodeURIComponent(tabTypes.join(',')));
|
|
}
|
|
if (this.tab === 'pipeline') qs.push('crm_linked=true');
|
|
if (qs.length) url += '?' + qs.join('&');
|
|
var bundle = await fetch(url).then(function (r) { return r.json(); });
|
|
this._lastMapBundleMeta = bundle.meta || {};
|
|
return bundle.entities || { type: 'FeatureCollection', features: [] };
|
|
},
|
|
|
|
async loadTabMap() {
|
|
var cid = this.tabMapId();
|
|
if (!cid || !document.getElementById(cid)) return;
|
|
if (!window.L || !window.ExportIntelMap) return;
|
|
this.tabMapLoading = true;
|
|
try {
|
|
var geo = await this.fetchTabMapBundle();
|
|
var featN = (geo.features || []).length;
|
|
var self = this;
|
|
await this.$nextTick();
|
|
window.ExportIntelMap.init(cid);
|
|
window.ExportIntelMap.setOnEntityClick(function (id) { self.openEntity(id); }, cid);
|
|
window.ExportIntelMap.setHalalMode(false, cid);
|
|
window.ExportIntelMap.setFeatures(geo, { autoFit: this.shouldAutoFitMap(featN) }, cid);
|
|
if (window.ExportIntelMap.invalidateAll) window.ExportIntelMap.invalidateAll();
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
this.tabMapLoading = false;
|
|
},
|
|
|
|
async loadTabData(opts) {
|
|
opts = opts || {};
|
|
var base = '/api/export-intel';
|
|
var fq = this.filterQs();
|
|
if (this.tab === 'map') {
|
|
return;
|
|
} else if (this.tab === 'distributors') {
|
|
var types = this.expandEntityTypes(this.entityTypeFilter || 'b2b').join(',');
|
|
var r = await fetch(base + '/entities?entity_types=' + encodeURIComponent(types) + '&limit=500' + fq.replace(/&?entity_type=[^&]*/g, '')).then((x) => x.json());
|
|
this.entities = r.items || [];
|
|
this.resultCount = r.total != null ? r.total : this.entities.length;
|
|
} else if (this.tab === 'customers') {
|
|
var types2 = this.expandEntityTypes(this.entityTypeFilter || 'horeca').join(',');
|
|
var r2 = await fetch(base + '/entities?entity_types=' + encodeURIComponent(types2) + '&limit=500' + fq.replace(/&?entity_type=[^&]*/g, '')).then((x) => x.json());
|
|
this.entities = r2.items || [];
|
|
this.resultCount = r2.total != null ? r2.total : this.entities.length;
|
|
} else if (this.tab === 'caterers') {
|
|
var types3 = this.expandEntityTypes(this.entityTypeFilter || 'contract_caterer').join(',');
|
|
var r3 = await fetch(base + '/entities?entity_types=' + encodeURIComponent(types3) + '&limit=500' + fq.replace(/&?entity_type=[^&]*/g, '')).then((x) => x.json());
|
|
this.entities = r3.items || [];
|
|
this.resultCount = r3.total != null ? r3.total : this.entities.length;
|
|
this.catererPresence = await fetch(base + '/caterers/presence' + (this.country ? '?country=' + this.country : '')).then((x) => x.json());
|
|
this.catererBrands = await fetch(base + '/caterers/brands').then((x) => x.json());
|
|
} else if (this.tab === 'contacts') {
|
|
if (!opts.append) {
|
|
this.contacts = [];
|
|
this.contactsHasMore = false;
|
|
}
|
|
var offset = opts.append ? (this.contacts || []).length : 0;
|
|
var r4 = await fetch(base + '/contacts?limit=200&offset=' + offset + fq).then((x) => x.json());
|
|
var items = r4.items || [];
|
|
if (opts.append) this.contacts = (this.contacts || []).concat(items);
|
|
else this.contacts = items;
|
|
this.resultCount = r4.total != null ? r4.total : this.contacts.length;
|
|
this.contactsHasMore = this.contacts.length < this.resultCount;
|
|
} else if (this.tab === 'tenders') {
|
|
var tenders = await fetch(base + '/tenders' + (this.country ? '?country=' + this.country : '')).then((x) => x.json());
|
|
var items = tenders.items || [];
|
|
if (this.q && this.q.trim()) {
|
|
var ql = this.q.trim().toLowerCase();
|
|
items = items.filter((t) => (t.title || '').toLowerCase().includes(ql));
|
|
}
|
|
if (this.filterTenderStatus) {
|
|
items = items.filter((t) => (t.status || '').toLowerCase() === this.filterTenderStatus);
|
|
}
|
|
this.tenders = items;
|
|
this.resultCount = items.length;
|
|
} else if (this.tab === 'gov') {
|
|
var gov = await fetch(base + '/gov-sources' + (this.country ? '?country=' + this.country : '')).then((x) => x.json());
|
|
if (this.q && this.q.trim()) {
|
|
var ql2 = this.q.trim().toLowerCase();
|
|
gov = gov.filter((g) => (g.name || '').toLowerCase().includes(ql2) || (g.category || '').toLowerCase().includes(ql2));
|
|
}
|
|
this.govSources = gov;
|
|
this.resultCount = gov.length;
|
|
} else if (this.tab === 'pipeline') {
|
|
var pipeQs = this.filterQs();
|
|
var pipe = await fetch(base + '/crm/pipeline?limit=200' + pipeQs).then((x) => x.json());
|
|
this.crmPipeline = pipe.items || [];
|
|
this.resultCount = this.crmPipeline.length;
|
|
}
|
|
if (['distributors','customers','caterers','contacts','pipeline'].indexOf(this.tab) >= 0) {
|
|
this.$nextTick(() => this.loadTabMap());
|
|
}
|
|
},
|
|
|
|
async loadMap() {
|
|
this.clearListFocus();
|
|
this.mapLoading = true;
|
|
this.mapError = '';
|
|
var url = '/api/export-intel/map/bundle';
|
|
var qs = [];
|
|
if (this.country) qs.push('country=' + encodeURIComponent(this.country));
|
|
if (this.region) qs.push('region=' + encodeURIComponent(this.region));
|
|
var mt = this.mapTypesParam();
|
|
if (mt.entity_type) qs.push('entity_type=' + encodeURIComponent(mt.entity_type));
|
|
else if (mt.entity_types) qs.push('entity_types=' + encodeURIComponent(mt.entity_types));
|
|
if (this.q && this.q.trim()) qs.push('q=' + encodeURIComponent(this.q.trim()));
|
|
if (this.mapHalalMode) qs.push('halal_min=' + encodeURIComponent(this.mapHalalMin));
|
|
if (this.filterFavoritesOnly) qs.push('favorite_only=true');
|
|
if (this.filterCrmStatus === 'linked') qs.push('crm_linked=true');
|
|
if (this.filterCrmStatus === 'not_linked') qs.push('crm_linked=false');
|
|
if (qs.length) url += '?' + qs.join('&');
|
|
var bundle;
|
|
try {
|
|
bundle = await fetch(url).then((r) => r.json());
|
|
} catch (e) {
|
|
this.mapLoading = false;
|
|
this.mapError = 'Kaart laden mislukt. Probeer opnieuw.';
|
|
return;
|
|
}
|
|
this.halalTopMarkets = (bundle.meta && bundle.meta.top_markets) || [];
|
|
this.halalTopEntities = (bundle.meta && bundle.meta.top_halal_entities) || [];
|
|
this.entities = (bundle.meta && bundle.meta.sidebar_entities) || this.halalTopEntities || [];
|
|
this.resultCount = (bundle.meta && bundle.meta.entity_count) || this.entities.length;
|
|
this.mapTruncated = !!(bundle.meta && bundle.meta.truncated);
|
|
if (!window.L || !window.ExportIntelMap) {
|
|
this.mapError = 'Kaart kon niet laden. Vernieuw de pagina (Ctrl+F5).';
|
|
this.mapLoading = false;
|
|
return;
|
|
}
|
|
this.mapError = '';
|
|
if (window.ExportIntelMap) {
|
|
if (!document.getElementById('ei-map')) { this.mapLoading = false; return; }
|
|
var self = this;
|
|
window.ExportIntelMap.init('ei-map');
|
|
window.ExportIntelMap.setOnEntityClick(function (id) { self.openEntity(id); }, 'ei-map');
|
|
window.ExportIntelMap.setHalalMode(this.mapHalalMode, 'ei-map');
|
|
var feats = (bundle.entities && bundle.entities.features) || [];
|
|
var autoFit = this.shouldAutoFitMap(feats.length);
|
|
window.ExportIntelMap.setFeatures(bundle.entities || { features: [] }, { autoFit: autoFit }, 'ei-map');
|
|
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, 'ei-map');
|
|
}
|
|
}
|
|
this.mapLoading = false;
|
|
var self2 = this;
|
|
this.$nextTick(function () {
|
|
requestAnimationFrame(function () {
|
|
if (window.ExportIntelMap && window.ExportIntelMap.invalidateAll) {
|
|
window.ExportIntelMap.invalidateAll();
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
setTab(id, el) {
|
|
this.tab = id;
|
|
this.entityTypeFilter = '';
|
|
this.mapTypesSelected = [];
|
|
this.mapHalalMode = false;
|
|
this.filterHasEmail = '';
|
|
var u = new URL(location.href);
|
|
u.searchParams.set('tab', id);
|
|
history.replaceState(null, '', u.pathname + u.search);
|
|
this.loadTabData();
|
|
if (id === 'map') this.$nextTick(() => this.loadMap());
|
|
else if (['distributors','customers','caterers','contacts','pipeline'].indexOf(id) >= 0) {
|
|
this.$nextTick(() => this.loadTabMap());
|
|
}
|
|
this.$nextTick(() => this.updateTabIndicator(el));
|
|
},
|
|
|
|
updateTabIndicator(el) {
|
|
var nav = this.$refs.vtabsNav;
|
|
if (!nav) return;
|
|
var btn = el || nav.querySelector('.vtab-btn.active');
|
|
if (!btn) return;
|
|
var navRect = nav.getBoundingClientRect();
|
|
var btnRect = btn.getBoundingClientRect();
|
|
this.tabIndicatorTop = btnRect.top - navRect.top + nav.scrollTop;
|
|
this.tabIndicatorH = btnRect.height;
|
|
},
|
|
|
|
async onCountryChange() {
|
|
await this.refresh();
|
|
},
|
|
|
|
contactPreview(e) {
|
|
if (!e) return '—';
|
|
if (e.email || e.primary_email) return e.email || e.primary_email;
|
|
if (e.phone || e.primary_phone) return e.phone || e.primary_phone;
|
|
if (e.website) return e.website.replace(/^https?:\/\//, '').slice(0, 28);
|
|
if (e.has_contact) return 'Contact in registry';
|
|
return 'Geen contact';
|
|
},
|
|
|
|
async fetchEntityDetail(id) {
|
|
var r = await fetch('/api/export-intel/entities/' + id);
|
|
if (!r.ok) {
|
|
var err = await r.json().catch(function () { return {}; });
|
|
throw new Error((err && err.error) || ('HTTP ' + r.status));
|
|
}
|
|
var data = await r.json();
|
|
if (data && data.error) throw new Error(data.error);
|
|
return data;
|
|
},
|
|
|
|
async selectListEntity(id) {
|
|
if (!id) return;
|
|
this.listFocusId = id;
|
|
this.listFocusLoading = true;
|
|
this.listFocusDetail = null;
|
|
try {
|
|
var detail = await this.fetchEntityDetail(id);
|
|
this.listFocusDetail = detail;
|
|
if (window.ExportIntelMap && detail.lat && detail.lon) {
|
|
window.ExportIntelMap.flyTo(detail.lat, detail.lon, 14);
|
|
window.ExportIntelMap.highlightEntity(detail.id);
|
|
}
|
|
} catch (e) {
|
|
this.syncMsg = 'Profiel laden mislukt: ' + e.message;
|
|
this.listFocusId = null;
|
|
}
|
|
this.listFocusLoading = false;
|
|
},
|
|
|
|
clearListFocus() {
|
|
this.listFocusId = null;
|
|
this.listFocusDetail = null;
|
|
},
|
|
|
|
openEntity(id) {
|
|
if (!id) return;
|
|
if (window.ExportIntelProfile && typeof window.ExportIntelProfile.open === 'function') {
|
|
window.ExportIntelProfile.open(id);
|
|
return;
|
|
}
|
|
this.syncMsg = 'Profiel-module niet geladen — ververs de pagina';
|
|
},
|
|
|
|
closeDrawer() {
|
|
if (window.ExportIntelProfile && typeof window.ExportIntelProfile.close === 'function') {
|
|
window.ExportIntelProfile.close();
|
|
return;
|
|
}
|
|
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) {
|
|
if (kind === 'world') {
|
|
if (!confirm('Wereld-sync: alle regio\'s (Europa, MENA, Afrika, Amerika, Asia, Oceanië). OSM + horeca + contacten — kan uren duren. Doorgaan?')) return;
|
|
}
|
|
if (kind === 'region' && !this.region) {
|
|
this.syncMsg = 'Selecteer eerst een regio in de filterbalk';
|
|
return;
|
|
}
|
|
this.busy = true;
|
|
this.overlayDismissed = false;
|
|
this.syncMsg = kind === 'world' ? 'Wereld-sync gestart…' : 'Sync gestart…';
|
|
this.syncAbort = (typeof AbortController !== 'undefined') ? new AbortController() : null;
|
|
try {
|
|
var body = {
|
|
country_iso2: this.country || null,
|
|
region_code: kind === 'region' ? this.region : (this.region || null),
|
|
max_priority: 2,
|
|
};
|
|
if (kind === 'world') body = { max_priority: 3 };
|
|
if (kind === 'places' && !this.country && !this.region) {
|
|
this.syncMsg = 'Selecteer een land of regio voor Google Places';
|
|
this.busy = false;
|
|
return;
|
|
}
|
|
var endpoint = kind === 'region' ? 'region' : kind;
|
|
var fetchOpts = {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body),
|
|
};
|
|
if (this.syncAbort) fetchOpts.signal = this.syncAbort.signal;
|
|
var r = await fetch('/api/export-intel/sync/' + endpoint, fetchOpts).then((x) => x.json());
|
|
if (r.error) this.syncMsg = 'Fout: ' + r.error;
|
|
else if (r.cancelled || (r.result && r.result.cancelled)) this.syncMsg = 'Sync geannuleerd';
|
|
else if (kind === 'world') this.syncMsg = 'Wereld-sync voltooid — kaart wordt ververst';
|
|
else if (kind === 'all' || kind === 'region') this.syncMsg = 'Regio-sync voltooid';
|
|
else this.syncMsg = 'Sync voltooid';
|
|
if (!(r.cancelled || (r.result && r.result.cancelled))) await this.refresh();
|
|
} catch (e) {
|
|
if (e && e.name === 'AbortError') this.syncMsg = 'Sync geannuleerd';
|
|
else this.syncMsg = 'Sync mislukt: ' + e.message;
|
|
}
|
|
this.busy = false;
|
|
this.syncAbort = null;
|
|
},
|
|
|
|
tabCount(id) {
|
|
if (id === 'distributors') return this.stats.distributors || 0;
|
|
if (id === 'caterers') return this.stats.caterers || 0;
|
|
if (id === 'contacts') return this.stats.contacts || 0;
|
|
if (id === 'tenders') return this.stats.tenders_open || 0;
|
|
if (id === 'pipeline') return this.stats.crm_linked || 0;
|
|
if (id === 'map') return this.stats.entities || 0;
|
|
return 0;
|
|
},
|
|
|
|
tabSub(id) {
|
|
var n = this.tabCount(id);
|
|
if (id === 'map') return n + ' op kaart';
|
|
if (id === 'gov') return 'Bronnen per land';
|
|
if (id === 'pipeline') return 'CRM koppeling';
|
|
if (n > 0) return n + ' records';
|
|
return '';
|
|
},
|
|
|
|
contactsExportUrl() {
|
|
var u = '/api/export-intel/contacts/export.csv?';
|
|
var parts = [];
|
|
if (this.country) parts.push('country=' + encodeURIComponent(this.country));
|
|
if (this.region) parts.push('region=' + encodeURIComponent(this.region));
|
|
if (this.entityTypeFilter) parts.push('entity_type=' + encodeURIComponent(this.entityTypeFilter));
|
|
if (this.filterHasEmail === 'yes') parts.push('has_email=true');
|
|
if (this.filterHasEmail === 'no') parts.push('has_email=false');
|
|
if (this.filterCrmStatus === 'linked') parts.push('crm_linked=true');
|
|
if (this.filterCrmStatus === 'not_linked') parts.push('crm_linked=false');
|
|
if (this.q && this.q.trim()) parts.push('q=' + encodeURIComponent(this.q.trim()));
|
|
return u + parts.join('&');
|
|
},
|
|
|
|
async loadMoreContacts() {
|
|
if (!this.contactsHasMore || this.contactsLoadingMore) return;
|
|
this.contactsLoadingMore = true;
|
|
try {
|
|
await this.loadTabData({ append: true });
|
|
} finally {
|
|
this.contactsLoadingMore = false;
|
|
}
|
|
},
|
|
|
|
typeLabel(t) {
|
|
var labels = {
|
|
distributor: 'Distrib.',
|
|
wholesaler: 'Grooth.',
|
|
contract_caterer: 'Cateraar',
|
|
end_customer: 'Eindkl.',
|
|
hotel: 'Hotel',
|
|
restaurant: 'Rest.',
|
|
retailer: 'Retail',
|
|
importer: 'Import',
|
|
};
|
|
return labels[t] || (t || '').replace(/_/g, ' ');
|
|
},
|
|
|
|
|
|
filteredTerritories() {
|
|
var list = (this.territories || []).filter(function (t) {
|
|
return !this.region || t.region_code === this.region;
|
|
}, this);
|
|
var seen = {};
|
|
var out = [];
|
|
list.forEach(function (t) {
|
|
var iso = t.country_iso2;
|
|
if (!iso || seen[iso]) return;
|
|
seen[iso] = true;
|
|
out.push(t);
|
|
});
|
|
out.sort(function (a, b) {
|
|
return (a.name_nl || a.country_iso2 || '').localeCompare(b.name_nl || b.country_iso2 || '', 'nl');
|
|
});
|
|
return out;
|
|
},
|
|
|
|
countryEntityCount(iso2) {
|
|
if (!iso2) return 0;
|
|
var cc = (this.stats && this.stats.country_entity_counts) || {};
|
|
return Number(cc[iso2] || 0);
|
|
},
|
|
|
|
countryOptionLabel(t) {
|
|
if (!t) return '';
|
|
var n = this.countryEntityCount(t.country_iso2);
|
|
return n > 0 ? (t.name_nl + ' (' + n + ')') : t.name_nl;
|
|
},
|
|
|
|
|
|
|
|
async loadGoogleSettings() {
|
|
try {
|
|
var s = await fetch('/api/export-intel/settings').then(function (r) { return r.json(); });
|
|
this.googleMapsKeySet = !!(s && s.google_maps_api_key_set);
|
|
this.googleMapsKey = '';
|
|
} catch (e) { console.error(e); }
|
|
},
|
|
|
|
async saveGoogleMapsKey() {
|
|
if (!this.googleMapsKey || !this.googleMapsKey.trim()) return;
|
|
this.googleKeySaving = true;
|
|
try {
|
|
await fetch('/api/export-intel/settings', {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ google_maps_api_key: this.googleMapsKey.trim() }),
|
|
});
|
|
this.googleMapsKey = '';
|
|
this.googleMapsKeySet = true;
|
|
this.syncMsg = 'Google Places API-key opgeslagen';
|
|
} catch (e) {
|
|
this.syncMsg = 'API-key opslaan mislukt';
|
|
}
|
|
this.googleKeySaving = false;
|
|
},
|
|
|
|
startSyncMonitor(silent) {
|
|
var self = this;
|
|
if (this.syncMonitorPoll) return;
|
|
this.pollSyncStatus();
|
|
this.syncMonitorPoll = setInterval(function () { self.pollSyncStatus(); }, 3000);
|
|
},
|
|
|
|
stopSyncMonitor() {
|
|
if (this.syncMonitorPoll) {
|
|
clearInterval(this.syncMonitorPoll);
|
|
this.syncMonitorPoll = null;
|
|
}
|
|
},
|
|
|
|
async pollSyncStatus() {
|
|
try {
|
|
var s = await fetch('/api/export-intel/sync/status').then(function (r) { return r.json(); });
|
|
this.syncMonitor = s;
|
|
if (s.any_running && s.active_jobs && s.active_jobs.length) {
|
|
var j = s.active_jobs[0];
|
|
this.syncMsg = (j.message || j.label || 'Sync bezig') + (j.percent ? ' · ' + j.percent + '%' : '');
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
},
|
|
|
|
toggleSyncMonitor() {
|
|
this.syncMonitorOpen = !this.syncMonitorOpen;
|
|
},
|
|
|
|
syncMonitorPercent() {
|
|
var jobs = (this.syncMonitor && this.syncMonitor.active_jobs) || [];
|
|
if (!jobs.length) return 0;
|
|
return jobs[0].percent || 0;
|
|
},
|
|
|
|
formatSyncTime(ts) {
|
|
if (!ts) return '';
|
|
var d = new Date(ts * 1000);
|
|
return d.toLocaleTimeString('nl-NL', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
},
|
|
|
|
syncEventLabel(ev) {
|
|
if (!ev) return '';
|
|
var parts = [ev.event || ''];
|
|
if (ev.message) parts.push(ev.message);
|
|
else if (ev.country) parts.push(ev.country);
|
|
if (ev.step) parts.push('(' + ev.step + ')');
|
|
return parts.filter(Boolean).join(' · ');
|
|
},
|
|
|
|
territoryName(iso2) {
|
|
if (!iso2) return '';
|
|
var t = this.territories.find(function (x) { return x.country_iso2 === iso2; });
|
|
return t ? t.name_nl : iso2;
|
|
},
|
|
|
|
isSelected(id) {
|
|
return this.selectedIds.indexOf(id) >= 0;
|
|
},
|
|
|
|
toggleSelect(id, ev) {
|
|
if (ev) ev.stopPropagation();
|
|
var i = this.selectedIds.indexOf(id);
|
|
if (i >= 0) this.selectedIds.splice(i, 1);
|
|
else this.selectedIds.push(id);
|
|
},
|
|
|
|
toggleSelectAllVisible() {
|
|
if (this.tab === 'contacts') {
|
|
var entityIds = [];
|
|
(this.contacts || []).forEach(function (c) {
|
|
if (c.entity_id && entityIds.indexOf(c.entity_id) < 0) entityIds.push(c.entity_id);
|
|
});
|
|
if (!entityIds.length) return;
|
|
var allSelected = entityIds.every((id) => this.isSelected(id));
|
|
if (allSelected) {
|
|
entityIds.forEach((id) => {
|
|
var i = this.selectedIds.indexOf(id);
|
|
if (i >= 0) this.selectedIds.splice(i, 1);
|
|
});
|
|
} else {
|
|
entityIds.forEach((id) => {
|
|
if (!this.isSelected(id)) this.selectedIds.push(id);
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
var list = this.visibleEntities();
|
|
if (!list.length) return;
|
|
var allSelected = list.every((e) => this.isSelected(e.id));
|
|
if (allSelected) {
|
|
list.forEach((e) => {
|
|
var i = this.selectedIds.indexOf(e.id);
|
|
if (i >= 0) this.selectedIds.splice(i, 1);
|
|
});
|
|
} else {
|
|
list.forEach((e) => {
|
|
if (!this.isSelected(e.id)) this.selectedIds.push(e.id);
|
|
});
|
|
}
|
|
},
|
|
|
|
clearSelection() {
|
|
this.selectedIds = [];
|
|
},
|
|
|
|
async setFavorites(ids, favorite) {
|
|
if (!ids.length) return;
|
|
this.busy = true;
|
|
try {
|
|
await fetch('/api/export-intel/entities/favorites', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ entity_ids: ids, favorite: favorite }),
|
|
});
|
|
this.syncMsg = favorite
|
|
? ids.length + ' favoriet' + (ids.length > 1 ? 'en' : '') + ' opgeslagen'
|
|
: 'Favoriet verwijderd';
|
|
await this.refresh();
|
|
} catch (e) {
|
|
this.syncMsg = 'Favoriet mislukt: ' + e.message;
|
|
}
|
|
this.busy = false;
|
|
},
|
|
|
|
async toggleFavorite(id, current, ev) {
|
|
if (ev) ev.stopPropagation();
|
|
await this.setFavorites([id], !current);
|
|
},
|
|
|
|
async favoriteSelection() {
|
|
await this.setFavorites(this.selectedIds.slice(), true);
|
|
},
|
|
|
|
async unfavoriteSelection() {
|
|
await this.setFavorites(this.selectedIds.slice(), false);
|
|
},
|
|
|
|
openCrmModal() {
|
|
if (!this.selectedIds.length) {
|
|
this.syncMsg = 'Selecteer eerst één of meer rijen (checkbox)';
|
|
return;
|
|
}
|
|
this.crmModalOpen = true;
|
|
},
|
|
|
|
async pushToCrm(ids) {
|
|
var entityIds = (ids && ids.length) ? ids : this.selectedIds.slice();
|
|
if (!entityIds.length) return;
|
|
this.crmPushBusy = true;
|
|
this.syncMsg = 'CRM import gestart…';
|
|
try {
|
|
var r = await fetch('/api/export-intel/crm/push', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
entity_ids: entityIds,
|
|
create_deals: this.crmCreateDeals,
|
|
}),
|
|
}).then((x) => x.json());
|
|
if (r.error) throw new Error(r.error);
|
|
var created = r.created || 0;
|
|
var linked = r.linked_existing || 0;
|
|
this.syncMsg = 'CRM: ' + created + ' nieuw, ' + linked + ' bestaand gekoppeld';
|
|
this.crmModalOpen = false;
|
|
this.selectedIds = [];
|
|
await this.refresh();
|
|
if (this.listFocusDetail && entityIds.indexOf(this.listFocusDetail.id) >= 0) {
|
|
this.listFocusDetail = await this.fetchEntityDetail(this.listFocusDetail.id);
|
|
}
|
|
if (this.tab === 'pipeline') await this.loadTabData();
|
|
} catch (e) {
|
|
this.syncMsg = 'CRM import mislukt: ' + e.message;
|
|
}
|
|
this.crmPushBusy = false;
|
|
},
|
|
|
|
async pushEntityToCrm(id) {
|
|
this.crmCreateDeals = true;
|
|
await this.pushToCrm([id]);
|
|
},
|
|
|
|
onFilterCollectionChange() {
|
|
if (this.tab === 'contacts') {
|
|
this.loadTabData();
|
|
return;
|
|
}
|
|
this.applyMapFilters();
|
|
},
|
|
};
|
|
}
|