/** * ReportStep — Screen 9 "Report + next step" (spec §11, §13). * Shows the report summary, lets the customer email/print/download/share, books * the inspection, and fires the CRM + nurture handoff (once) with the immutable * compliance record (spec §18 G1). */ import React, { useEffect, useState } from 'react'; import { Download, Printer, Mail, Share2, CalendarCheck, FileText, Loader2, Check } from 'lucide-react'; import { useEstimateWizard } from '../../EstimateWizardContext'; import { buildReportModel, downloadReportPdf, getReportBlobUrl } from '../../utils/wizardReportExport'; import { submitToCrm } from '../../api/crmHandoff'; import { formatUSD } from '../../engine/pricing'; const PKG_LABEL = { GOOD: 'Good', BETTER: 'Better', BEST: 'Best' }; const ActionButton = ({ icon: Icon, label, onClick, busy, primary }) => ( ); const ReportStep = () => { const { session, tenantConfig, patchSession, logEvent } = useEstimateWizard(); const model = buildReportModel(session, tenantConfig); const [busy, setBusy] = useState(null); const [booked, setBooked] = useState(!!session.report?.appointmentBooked); // Fire CRM + nurture handoff once (spec §13 / §17 E2). useEffect(() => { if (session.report) return; const reportMeta = { reportId: model.reportId, version: 1, generatedAt: model.generatedAt, deliveryStatus: 'created' }; patchSession({ report: reportMeta }); submitToCrm(session, tenantConfig, reportMeta); logEvent('report_generated', { reportId: model.reportId }); logEvent('crm_handoff', { trigger: 'wizard_completed' }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleDownload = async () => { setBusy('download'); try { await downloadReportPdf(session, tenantConfig); logEvent('report_downloaded', { reportId: model.reportId }); } finally { setBusy(null); } }; const handlePrint = async () => { setBusy('print'); try { const { url } = await getReportBlobUrl(session, tenantConfig); const w = window.open(url, '_blank'); if (w) w.addEventListener('load', () => w.print()); patchSession({ report: { ...session.report, printedAt: new Date().toISOString() } }); logEvent('report_printed', { reportId: model.reportId }); } finally { setBusy(null); } }; const handleEmail = () => { const subject = encodeURIComponent(`Your Roof Options Report (${model.reportId})`); const body = encodeURIComponent( `Hi ${model.contact.name || ''},\n\nHere is a summary of your roof options report.\n` + `Recommended: ${PKG_LABEL[model.recommended]}\nEstimated total: ~${formatUSD(model.totals.total)}\n\n` + `${tenantConfig.branding.company} · ${tenantConfig.branding.phone}\n\n` + `Note: ${tenantConfig.disclaimer}` ); window.location.href = `mailto:${model.contact.email || ''}?subject=${subject}&body=${body}`; patchSession({ report: { ...session.report, emailedAt: new Date().toISOString(), deliveryStatus: 'emailed' } }); logEvent('report_emailed', { reportId: model.reportId }); }; const handleShare = async () => { const summary = `My roof options report (${PKG_LABEL[model.recommended]} recommended) from ${tenantConfig.branding.company}.`; try { if (navigator.share) await navigator.share({ title: 'Roof Options Report', text: summary }); else { await navigator.clipboard?.writeText(summary); } logEvent('report_shared', { reportId: model.reportId }); } catch { /* user cancelled */ } }; const handleBook = () => { setBooked(true); patchSession({ report: { ...session.report, appointmentBooked: true, bookedAt: new Date().toISOString() } }); logEvent('appointment_booked', { reportId: model.reportId }); }; return (
{tenantConfig.disclaimer}