// 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, auditRunning: false, activeTab: 'overview', tabs: [ { id: 'overview', label: 'Overview' }, { id: 'security', label: 'Security' }, { 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'); } }, openSecurityReport() { window.open('/api/v1/admin/prospecting/enrichment/security-audit/report/' + this.prospectId, '_blank'); }, async runSecurityAudit() { this.auditRunning = true; try { await apiClient.post('/admin/prospecting/enrichment/security-audit/' + this.prospectId); Utils.showToast('Security audit complete', 'success'); await this.loadProspect(); } catch (err) { Utils.showToast('Audit failed: ' + err.message, 'error'); } finally { this.auditRunning = false; } }, gradeColor(grade) { if (!grade) return 'text-gray-400'; if (grade === 'A+' || grade === 'A') return 'text-green-600 dark:text-green-400'; if (grade === 'B') return 'text-blue-600 dark:text-blue-400'; if (grade === 'C') return 'text-yellow-600 dark:text-yellow-400'; if (grade === 'D') return 'text-orange-600 dark:text-orange-400'; return 'text-red-600 dark:text-red-400'; }, severityBadge(severity) { var classes = { critical: 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300', high: 'bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300', medium: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300', low: 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300', info: 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400', }; return classes[severity] || classes.info; }, 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'; }, scoreCategories() { var s = this.prospect?.score; if (!s) return []; var bd = s.score_breakdown || {}; var positive = ['has_website', 'has_contacts', 'has_email', 'has_phone', 'has_ssl', 'modern_cms', 'fast_site', 'mobile_friendly', 'has_ecommerce', 'short_domain', 'met_in_person']; return [ { key: 'technical_health', label: 'Technical Health', score: s.technical_health_score, max: 40, flags: Object.entries(bd.technical_health || {}), positive: positive }, { key: 'modernity', label: 'Modernity', score: s.modernity_score, max: 25, flags: Object.entries(bd.modernity || {}), positive: positive }, { key: 'business_value', label: 'Business Value', score: s.business_value_score, max: 25, flags: Object.entries(bd.business_value || {}), positive: positive }, { key: 'engagement', label: 'Engagement', score: s.engagement_score, max: 10, flags: Object.entries(bd.engagement || {}), positive: positive }, ]; }, 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; }, }; }