feat(prospecting): add complete prospecting module for lead discovery and scoring
Some checks failed
Some checks failed
Migrates scanning pipeline from marketing-.lu-domains app into Orion module. Supports digital (domain scan) and offline (manual capture) lead channels with enrichment, scoring, campaign management, and interaction tracking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
141
app/modules/prospecting/static/admin/js/prospect-detail.js
Normal file
141
app/modules/prospecting/static/admin/js/prospect-detail.js
Normal file
@@ -0,0 +1,141 @@
|
||||
// static/admin/js/prospect-detail.js
|
||||
|
||||
const detailLog = window.LogConfig.createLogger('prospecting-detail');
|
||||
|
||||
function prospectDetail(prospectId) {
|
||||
return {
|
||||
...data(),
|
||||
|
||||
currentPage: 'prospects',
|
||||
prospectId: prospectId,
|
||||
prospect: null,
|
||||
interactions: [],
|
||||
campaignSends: [],
|
||||
loading: true,
|
||||
error: null,
|
||||
|
||||
activeTab: 'overview',
|
||||
tabs: [
|
||||
{ id: 'overview', label: 'Overview' },
|
||||
{ id: 'interactions', label: 'Interactions' },
|
||||
{ id: 'campaigns', label: 'Campaigns' },
|
||||
],
|
||||
|
||||
// Interaction modal
|
||||
showInteractionModal: false,
|
||||
newInteraction: {
|
||||
interaction_type: 'note',
|
||||
subject: '',
|
||||
notes: '',
|
||||
outcome: '',
|
||||
next_action: '',
|
||||
},
|
||||
|
||||
// Campaign modal
|
||||
showSendCampaignModal: false,
|
||||
|
||||
async init() {
|
||||
await I18n.loadModule('prospecting');
|
||||
|
||||
if (window._prospectDetailInit) return;
|
||||
window._prospectDetailInit = true;
|
||||
|
||||
detailLog.info('Prospect detail initializing for ID:', this.prospectId);
|
||||
await this.loadProspect();
|
||||
},
|
||||
|
||||
async loadProspect() {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
try {
|
||||
this.prospect = await apiClient.get('/admin/prospecting/prospects/' + this.prospectId);
|
||||
await Promise.all([
|
||||
this.loadInteractions(),
|
||||
this.loadCampaignSends(),
|
||||
]);
|
||||
} catch (err) {
|
||||
this.error = err.message;
|
||||
detailLog.error('Failed to load prospect', err);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async loadInteractions() {
|
||||
try {
|
||||
const resp = await apiClient.get('/admin/prospecting/prospects/' + this.prospectId + '/interactions');
|
||||
this.interactions = resp.items || resp || [];
|
||||
} catch (err) {
|
||||
detailLog.warn('Failed to load interactions', err);
|
||||
}
|
||||
},
|
||||
|
||||
async loadCampaignSends() {
|
||||
try {
|
||||
const resp = await apiClient.get('/admin/prospecting/campaigns/sends?prospect_id=' + this.prospectId);
|
||||
this.campaignSends = resp.items || resp || [];
|
||||
} catch (err) {
|
||||
detailLog.warn('Failed to load campaign sends', err);
|
||||
}
|
||||
},
|
||||
|
||||
async updateStatus() {
|
||||
try {
|
||||
await apiClient.put('/admin/prospecting/prospects/' + this.prospectId, {
|
||||
status: this.prospect.status,
|
||||
});
|
||||
Utils.showToast('Status updated', 'success');
|
||||
} catch (err) {
|
||||
Utils.showToast('Failed: ' + err.message, 'error');
|
||||
}
|
||||
},
|
||||
|
||||
async runEnrichment() {
|
||||
try {
|
||||
await apiClient.post('/admin/prospecting/enrichment/full/' + this.prospectId);
|
||||
Utils.showToast('Enrichment scan started', 'success');
|
||||
setTimeout(() => this.loadProspect(), 5000);
|
||||
} catch (err) {
|
||||
Utils.showToast('Failed: ' + err.message, 'error');
|
||||
}
|
||||
},
|
||||
|
||||
async createInteraction() {
|
||||
try {
|
||||
await apiClient.post(
|
||||
'/admin/prospecting/prospects/' + this.prospectId + '/interactions',
|
||||
this.newInteraction,
|
||||
);
|
||||
Utils.showToast('Interaction logged', 'success');
|
||||
this.showInteractionModal = false;
|
||||
this.newInteraction = { interaction_type: 'note', subject: '', notes: '', outcome: '', next_action: '' };
|
||||
await this.loadInteractions();
|
||||
} catch (err) {
|
||||
Utils.showToast('Failed: ' + err.message, 'error');
|
||||
}
|
||||
},
|
||||
|
||||
scoreColor(score) {
|
||||
if (score == null) return 'text-gray-400';
|
||||
if (score >= 70) return 'text-red-600';
|
||||
if (score >= 50) return 'text-orange-600';
|
||||
if (score >= 30) return 'text-blue-600';
|
||||
return 'text-gray-600';
|
||||
},
|
||||
|
||||
techProfileEntries() {
|
||||
const tp = this.prospect?.tech_profile;
|
||||
if (!tp) return [];
|
||||
const entries = [];
|
||||
if (tp.cms) entries.push(['CMS', tp.cms + (tp.cms_version ? ' ' + tp.cms_version : '')]);
|
||||
if (tp.server) entries.push(['Server', tp.server]);
|
||||
if (tp.js_framework) entries.push(['JS Framework', tp.js_framework]);
|
||||
if (tp.analytics) entries.push(['Analytics', tp.analytics]);
|
||||
if (tp.ecommerce_platform) entries.push(['E-commerce', tp.ecommerce_platform]);
|
||||
if (tp.hosting_provider) entries.push(['Hosting', tp.hosting_provider]);
|
||||
if (tp.cdn) entries.push(['CDN', tp.cdn]);
|
||||
entries.push(['SSL Valid', tp.has_valid_cert ? 'Yes' : 'No']);
|
||||
return entries;
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user