Add live CEO briefing, Wereldexport CRM, halal engine, and realtime cockpit.

Ship export intel with contact filters and CRM push, Herman live digest with Telegram briefing, halal recommendation engine, revenue cockpit, and dashboard polling/WebSocket fixes.
This commit is contained in:
Aissa
2026-07-19 18:25:01 +00:00
parent 544d9e611d
commit 4dd1b8265f
25 changed files with 2757 additions and 108 deletions
+74 -4
View File
@@ -12,6 +12,8 @@ function exportIntelApp() {
region: '',
entities: [],
contacts: [],
contactsHasMore: false,
contactsLoadingMore: false,
tenders: [],
catererPresence: [],
catererBrands: [],
@@ -139,7 +141,7 @@ function exportIntelApp() {
},
showEntityTypeFilter() {
return ['distributors', 'customers', 'caterers'].indexOf(this.tab) >= 0;
return ['distributors', 'customers', 'caterers', 'contacts'].indexOf(this.tab) >= 0;
},
showMapTypeFilter() {
@@ -183,6 +185,14 @@ function exportIntelApp() {
return this.tab === 'contacts';
},
showCrmFilter() {
return this.tab === 'map' || this.tab === 'contacts';
},
showSelectionBar() {
return this.tab === 'map' || this.tab === 'contacts';
},
entityTypeOptions() {
if (this.tab === 'distributors') {
return [
@@ -205,6 +215,18 @@ function exportIntelApp() {
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' },
@@ -214,7 +236,8 @@ function exportIntelApp() {
];
},
async loadTabData() {
async loadTabData(opts) {
opts = opts || {};
var base = '/api/export-intel';
var fq = this.filterQs();
if (this.tab === 'map') {
@@ -245,9 +268,17 @@ function exportIntelApp() {
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') {
var r4 = await fetch(base + '/contacts?limit=500' + fq).then((x) => x.json());
this.contacts = r4.items || [];
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 || [];
@@ -468,10 +499,26 @@ function exportIntelApp() {
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, ' ');
},
@@ -494,6 +541,25 @@ function exportIntelApp() {
},
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));
@@ -590,6 +656,10 @@ function exportIntelApp() {
},
onFilterCollectionChange() {
if (this.tab === 'contacts') {
this.loadTabData();
return;
}
this.applyMapFilters();
},
};