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:
@@ -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();
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user