Files
foodlinkk-command-center/cockpit/static/js/export-intel.js
T
Aissa 8d2766efb6 Fix map layout jump, CRM/edit distributeurs, progress overlay.
Stable map grid, no fitBounds on world view, entity PATCH edit form, CRM buttons on distributors, and loading animation for sync/wait states.
2026-07-19 18:47:12 +00:00

751 lines
27 KiB
JavaScript

function exportIntelApp() {
return {
tab: 'map',
tabIndicatorTop: 0,
tabIndicatorH: 40,
busy: false,
syncMsg: '',
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,
mapTruncated: false,
halalTopMarkets: [],
halalTopEntities: [],
listFocusId: null,
listFocusDetail: null,
listFocusLoading: false,
drawerLoading: false,
filterHasEmail: '',
filterFavoritesOnly: false,
filterCrmStatus: '',
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' },
],
mapTypeChips: [
{ v: 'distributor', l: 'Distributeur', icon: '📦' },
{ v: 'wholesaler', l: 'Groothandel', icon: '🏪' },
{ v: 'importer', l: 'Importeur', icon: '🚢' },
{ v: 'logistics', l: 'Logistiek', icon: '🚚' },
{ v: 'restaurant', l: 'Restaurant', 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;
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.$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());
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.selectedIds = [];
this.applyMapFilters();
},
showEntityTypeFilter() {
return ['distributors', 'customers', 'caterers', 'contacts'].indexOf(this.tab) >= 0;
},
showMapTypeFilter() {
return this.tab === 'map';
},
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();
},
mapTypesParam() {
if (this.entityTypeFilter) return { entity_type: this.entityTypeFilter };
if (this.mapTypesSelected.length) {
return { entity_types: this.mapTypesSelected.join(',') };
}
return {};
},
applyMapFilters() {
if (this.tab !== 'map') {
this.loadTabData();
return;
}
this.loadTabData().then(() => this.loadMap());
},
showEmailFilter() {
return this.tab === 'contacts';
},
showCrmFilter() {
return this.tab === 'map' || this.tab === 'contacts';
},
showSelectionBar() {
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() {
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 === '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' },
];
},
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 = 'distributor,wholesaler,importer,logistics';
var et = this.entityTypeFilter ? '&entity_type=' + encodeURIComponent(this.entityTypeFilter) : '';
var r = await fetch(base + '/entities?entity_types=' + types + '&limit=500' + fq.replace(/&?entity_type=[^&]*/g, '') + et).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 = 'restaurant,doner_shoarma,butcher,foodservice';
var et2 = this.entityTypeFilter ? '&entity_type=' + encodeURIComponent(this.entityTypeFilter) : '';
var r2 = await fetch(base + '/entities?entity_types=' + types2 + '&limit=500' + fq.replace(/&?entity_type=[^&]*/g, '') + et2).then((x) => x.json());
this.entities = r2.items || [];
this.resultCount = r2.total != null ? r2.total : this.entities.length;
} else if (this.tab === 'caterers') {
var et3 = this.entityTypeFilter || 'contract_caterer';
var r3 = await fetch(base + '/entities?entity_type=' + et3 + '&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));
}
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;
}
},
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); });
window.ExportIntelMap.setHalalMode(this.mapHalalMode);
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);
}
}
this.mapLoading = false;
},
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());
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;
},
async openEntity(id) {
if (!id) return;
this.drawerLoading = true;
this.drawerOpen = true;
try {
if (this.listFocusDetail && this.listFocusDetail.id === id) {
this.selected = this.listFocusDetail;
} else {
this.selected = await this.fetchEntityDetail(id);
this.listFocusId = id;
this.listFocusDetail = this.selected;
}
if (window.ExportIntelMap && this.selected.lat && this.selected.lon) {
window.ExportIntelMap.flyTo(this.selected.lat, this.selected.lon, 14);
window.ExportIntelMap.highlightEntity(this.selected.id);
}
if (window.ExportIntelMap && window.ExportIntelMap.closePopup) {
window.ExportIntelMap.closePopup();
}
} catch (e) {
this.syncMsg = 'Profiel laden mislukt: ' + e.message;
this.drawerOpen = false;
this.selected = null;
}
this.drawerLoading = false;
},
closeDrawer() {
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: Europa, Midden-Oosten, Afrika en Amerika. OSM + contacten — kan lang duren. Doorgaan?')) return;
}
if (kind === 'region' && !this.region) {
this.syncMsg = 'Selecteer eerst een regio in de filterbalk';
return;
}
this.busy = true;
this.syncMsg = kind === 'world' ? 'Wereld-sync gestart…' : 'Sync gestart…';
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: 2 };
var endpoint = kind === 'region' ? 'region' : kind;
var r = await fetch('/api/export-intel/sync/' + endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
}).then((x) => x.json());
if (r.error) this.syncMsg = 'Fout: ' + r.error;
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';
await this.refresh();
} catch (e) {
this.syncMsg = 'Sync mislukt: ' + e.message;
}
this.busy = false;
},
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) {
return (t || '').replace(/_/g, ' ');
},
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.mapHalalMode && this.halalTopEntities.length ? this.halalTopEntities : this.entities;
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();
},
};
}