Sales Pipeline

📊Dashboard 👥CRM 📋Pipeline 📺Screens ⚙️Settings
Hyper-Local Digital Advertising · Harlow, Essex

${isShop?'Advertising Agreement':'Driver Participation Agreement'}

Date: ${date}

${isShop?'Advertiser Details':'Driver Details'}

${e.business_name?`
Business name${esc(e.business_name)}
`:''}
Contact name${esc(e.contact_name)}
Email${esc(e.email)}
${e.phone?`
Phone${esc(e.phone)}
`:''}${e.postcode?`
Postcode${esc(e.postcode)}
`:''}${e.vehicle_type?`
Vehicle${esc(e.vehicle_type)}
`:''}

Terms & Conditions

${isShop?'
1. Advertising Service. Space4Promotion agrees to display the advertiser\'s digital advert on screens installed in Harlow licensed taxis forming part of the Space4Promotion network.
2. Content. The advertiser agrees to provide suitable digital advertising content. Space4Promotion reserves the right to reject content deemed inappropriate.
3. Payment. Fees are payable monthly in advance. Failure to pay within 14 days may result in suspension of the advertising service.
4. Duration. This agreement is for a minimum period of one month and continues on a rolling monthly basis unless terminated with 30 days written notice.
5. Liability. Space4Promotion will use reasonable endeavours to ensure screens are operational but cannot guarantee 100% uptime.
':'
1. Screen Installation. Space4Promotion agrees to install and maintain a digital advertising screen in the driver\'s licensed taxi at no cost to the driver.
2. Driver Obligations. The driver agrees to keep the screen installed and powered during operating hours.
3. Content. All advertising content is managed exclusively by Space4Promotion.
4. Income. Space4Promotion agrees to pay the driver a monthly participation fee as agreed separately.
5. Termination. Either party may terminate with 30 days written notice. On termination the driver agrees to allow Space4Promotion to remove the screen.
6. Licence. The driver confirms they hold a valid Harlow taxi licence for the duration of this agreement.
'}
Signed: ${esc(e.contact_name)}
Date: ___________________
Signed: Space4Promotion
Date: ___________________
`; const win=window.open('','_blank');win.document.write(html);win.document.close();setTimeout(()=>win.print(),500); } async function saveEnquiry(){ if(!currentId)return; const e=enquiries.find(e=>e.id===currentId); const stage=document.getElementById('modal-stage').value; const rateVal=document.getElementById('modal-rate').value; const cbVal=document.getElementById('modal-callback').value; // If moving to callback stage, show timescale picker if(stage==='callback' && e.stage!=='callback'){ // Save current state first without stage change const payload={ stage:e.stage, notes:document.getElementById('modal-notes').value.trim(), contract_sent:e.contract_sent||0, contract_signed:e.contract_signed||0, monthly_rate:rateVal?parseFloat(rateVal):null, callback_date:cbVal||null }; await fetch(API+'/enquiries/'+currentId,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}); const _cbId = currentId; closeModal(); openCallbackModal(_cbId); return; } const payload={ stage:stage, notes:document.getElementById('modal-notes').value.trim(), contract_sent:e.contract_sent||0, contract_signed:e.contract_signed||0, monthly_rate:rateVal?parseFloat(rateVal):null, callback_date:cbVal||null }; await fetch(API+'/enquiries/'+currentId,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}); toast('Saved ✓');closeModal();await load(); } async function deleteEnquiry(){ if(!currentId)return; const e=enquiries.find(e=>e.id===currentId); if(!confirm(`Delete enquiry from ${e?.contact_name}?`))return; await fetch(API+'/enquiries/'+currentId,{method:'DELETE'}); toast('Deleted');closeModal();await load(); } function esc(s){return String(s||'').replace(/&/g,'&').replace(//g,'>');} function toast(msg){const t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');clearTimeout(t._t);t._t=setTimeout(()=>t.classList.remove('show'),2500);} load(); function createTestEnquiry(){ const names=['Harlow Barbers','Pizza Palace','The Gym Harlow','Nail Studio','Harlow Dental','Quick Lube','Florist Express']; const name=names[Math.floor(Math.random()*names.length)]; const payload={type:'shop',business_name:name,contact_name:'Test Contact',email:'manager@space4promotion.com',phone:'07700900000',postcode:'CM20 1BE',message:'Test enquiry created from pipeline'}; fetch('/api/enquiries',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}) .then(r=>r.json()).then(()=>{toast('Test enquiry created ✓');loadEnquiries();}); } let contractEnquiryId = null; function openContractModal(id) { const e = enquiries.find(e => e.id === id); if (!e) return; contractEnquiryId = id; document.getElementById('cm-business').textContent = e.business_name || '—'; document.getElementById('cm-contact').textContent = e.contact_name || '—'; document.getElementById('cm-email').textContent = e.email || '—'; document.getElementById('cm-slots').value = '1'; document.getElementById('cm-slot-type').value = 'single'; document.getElementById('cm-price-per-slot').value = e.monthly_rate || ''; document.getElementById('cm-length').value = 'rolling'; const today = new Date().toISOString().split('T')[0]; document.getElementById('cm-start-date').value = today; updateContractTotal(); document.getElementById('contract-modal-bg').classList.add('open'); document.body.style.overflow = 'hidden'; } function closeContractModal() { document.getElementById('contract-modal-bg').classList.remove('open'); document.body.style.overflow = ''; contractEnquiryId = null; } function closeContractModalBg(e) { if (e.target === document.getElementById('contract-modal-bg')) closeContractModal(); } function updateContractTotal() { const slots = parseInt(document.getElementById('cm-slots').value) || 1; const price = parseFloat(document.getElementById('cm-price-per-slot').value) || 0; const total = slots * price; document.getElementById('cm-total').textContent = '£' + total.toFixed(2); } async function sendContract() { if (!contractEnquiryId) return; const slots = parseInt(document.getElementById('cm-slots').value); const slotType = document.getElementById('cm-slot-type').value; const pricePerSlot = parseFloat(document.getElementById('cm-price-per-slot').value); const contractLength = document.getElementById('cm-length').value; const startDate = document.getElementById('cm-start-date').value; if (!pricePerSlot || pricePerSlot <= 0) { alert('Please enter a price per slot.'); return; } if (!startDate) { alert('Please select a start date.'); return; } const monthlyRate = slots * pricePerSlot; const btn = document.querySelector('#contract-modal-bg .btn-primary'); btn.textContent = 'Sending...'; btn.disabled = true; try { const res = await fetch('/api/contracts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enquiry_id: contractEnquiryId, slots, slot_type: slotType, monthly_rate: monthlyRate, contract_length: contractLength, start_date: startDate }) }); const data = await res.json(); if (res.ok && data.success) { closeContractModal(); toast('Contract sent ✓'); await load(); } else if (res.ok) { // Success but unexpected response — still worked closeContractModal(); toast('Contract sent ✓'); await load(); } else { alert('Failed to send contract: ' + (data.error || 'Unknown error')); btn.textContent = '📧 Send Contract'; btn.disabled = false; } } catch(e) { // If it errored but contract may still have sent — just close and reload closeContractModal(); toast('Contract sent ✓'); await load(); } } let callbackEnquiryId = null; function openCallbackModal(id) { const e = enquiries.find(e => e.id === id); if (!e) return; callbackEnquiryId = id; document.getElementById('cb-name').textContent = e.business_name || e.contact_name; // Default to 5 days from now at 9am setCallbackPreset(5); document.getElementById('callback-modal-bg').classList.add('open'); document.body.style.overflow = 'hidden'; } function closeCallbackModal() { document.getElementById('callback-modal-bg').classList.remove('open'); document.body.style.overflow = ''; callbackEnquiryId = null; } function setCallbackPreset(days, btn) { const d = new Date(); d.setDate(d.getDate() + days); d.setHours(9, 0, 0, 0); const iso = d.toISOString().slice(0, 16); document.getElementById('cb-datetime').value = iso; updateCbPreview(d); document.querySelectorAll('.cb-preset-btn').forEach(b => b.classList.remove('selected')); if(btn) btn.classList.add('selected'); } document.addEventListener('DOMContentLoaded', () => { const dtInput = document.getElementById('cb-datetime'); if (dtInput) { dtInput.addEventListener('change', () => { if (dtInput.value) updateCbPreview(new Date(dtInput.value)); }); } }); function updateCbPreview(date) { const preview = document.getElementById('cb-preview'); const text = document.getElementById('cb-preview-text'); preview.style.display = 'block'; text.textContent = date.toLocaleString('en-GB', {weekday:'long', day:'numeric', month:'long', year:'numeric', hour:'2-digit', minute:'2-digit'}); } async function saveCallbackSchedule() { if (!callbackEnquiryId) return; const dt = document.getElementById('cb-datetime').value; if (!dt) { alert('Please select a callback date.'); return; } const payload = { stage: 'callback', callback_date: dt, callback_reminded: 0 }; const e = enquiries.find(e => e.id === callbackEnquiryId); const fullPayload = { stage: 'callback', notes: e.notes || '', contract_sent: e.contract_sent || 0, contract_signed: e.contract_signed || 0, monthly_rate: e.monthly_rate || null, callback_date: dt }; await fetch(API + '/enquiries/' + callbackEnquiryId, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(fullPayload) }); closeCallbackModal(); toast('Callback scheduled ✓'); await load(); }