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:
153
app/modules/prospecting/static/admin/js/capture.js
Normal file
153
app/modules/prospecting/static/admin/js/capture.js
Normal file
@@ -0,0 +1,153 @@
|
||||
// noqa: js-006 - async init pattern is safe, no data loading
|
||||
// static/admin/js/capture.js
|
||||
|
||||
const captureLog = window.LogConfig.createLogger('prospecting-capture');
|
||||
|
||||
function quickCapture() {
|
||||
return {
|
||||
...data(),
|
||||
|
||||
currentPage: 'capture',
|
||||
|
||||
form: {
|
||||
business_name: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
address: '',
|
||||
city: '',
|
||||
postal_code: '',
|
||||
source: 'street',
|
||||
notes: '',
|
||||
tags: [],
|
||||
location_lat: null,
|
||||
location_lng: null,
|
||||
},
|
||||
|
||||
sources: [
|
||||
{ value: 'street', label: 'Street' },
|
||||
{ value: 'networking_event', label: 'Networking' },
|
||||
{ value: 'referral', label: 'Referral' },
|
||||
{ value: 'other', label: 'Other' },
|
||||
],
|
||||
|
||||
availableTags: [
|
||||
'no-website', 'gmail', 'restaurant', 'retail', 'services',
|
||||
'professional', 'construction', 'beauty', 'food',
|
||||
],
|
||||
|
||||
submitting: false,
|
||||
saved: false,
|
||||
lastSaved: '',
|
||||
gettingLocation: false,
|
||||
recentCaptures: [],
|
||||
|
||||
async init() {
|
||||
await I18n.loadModule('prospecting');
|
||||
|
||||
if (window._captureInit) return;
|
||||
window._captureInit = true;
|
||||
|
||||
captureLog.info('Quick capture initializing');
|
||||
},
|
||||
|
||||
toggleTag(tag) {
|
||||
const idx = this.form.tags.indexOf(tag);
|
||||
if (idx >= 0) {
|
||||
this.form.tags.splice(idx, 1);
|
||||
} else {
|
||||
this.form.tags.push(tag);
|
||||
}
|
||||
},
|
||||
|
||||
getLocation() {
|
||||
if (!navigator.geolocation) {
|
||||
Utils.showToast('Geolocation not supported', 'error');
|
||||
return;
|
||||
}
|
||||
this.gettingLocation = true;
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(pos) => {
|
||||
this.form.location_lat = pos.coords.latitude;
|
||||
this.form.location_lng = pos.coords.longitude;
|
||||
this.gettingLocation = false;
|
||||
captureLog.info('Location acquired', pos.coords);
|
||||
},
|
||||
(err) => {
|
||||
this.gettingLocation = false;
|
||||
Utils.showToast('Location error: ' + err.message, 'error');
|
||||
captureLog.error('Geolocation error', err);
|
||||
},
|
||||
{ enableHighAccuracy: true, timeout: 10000 },
|
||||
);
|
||||
},
|
||||
|
||||
async submitCapture() {
|
||||
if (!this.form.business_name) {
|
||||
Utils.showToast('Business name is required', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitting = true;
|
||||
try {
|
||||
const payload = {
|
||||
channel: 'offline',
|
||||
business_name: this.form.business_name,
|
||||
source: this.form.source,
|
||||
notes: this.form.notes,
|
||||
tags: this.form.tags.length > 0 ? this.form.tags : null,
|
||||
address: this.form.address || null,
|
||||
city: this.form.city || null,
|
||||
postal_code: this.form.postal_code || null,
|
||||
location_lat: this.form.location_lat,
|
||||
location_lng: this.form.location_lng,
|
||||
contacts: [],
|
||||
};
|
||||
|
||||
if (this.form.phone) {
|
||||
payload.contacts.push({ contact_type: 'phone', value: this.form.phone });
|
||||
}
|
||||
if (this.form.email) {
|
||||
payload.contacts.push({ contact_type: 'email', value: this.form.email });
|
||||
}
|
||||
|
||||
const result = await apiClient.post('/admin/prospecting/prospects', payload);
|
||||
|
||||
this.lastSaved = this.form.business_name;
|
||||
this.recentCaptures.unshift({
|
||||
id: result.id,
|
||||
business_name: this.form.business_name,
|
||||
city: this.form.city,
|
||||
source: this.form.source,
|
||||
});
|
||||
|
||||
this.saved = true;
|
||||
setTimeout(() => { this.saved = false; }, 3000);
|
||||
|
||||
this.resetForm();
|
||||
Utils.showToast('Prospect saved!', 'success');
|
||||
captureLog.info('Capture saved', result.id);
|
||||
} catch (err) {
|
||||
Utils.showToast('Failed: ' + err.message, 'error');
|
||||
captureLog.error('Capture failed', err);
|
||||
} finally {
|
||||
this.submitting = false;
|
||||
}
|
||||
},
|
||||
|
||||
resetForm() {
|
||||
this.form = {
|
||||
business_name: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
address: '',
|
||||
city: this.form.city, // Keep city for rapid captures in same area
|
||||
postal_code: this.form.postal_code,
|
||||
source: this.form.source,
|
||||
notes: '',
|
||||
tags: [],
|
||||
location_lat: this.form.location_lat, // Keep GPS
|
||||
location_lng: this.form.location_lng,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user