Deals under contract · projects · key dates
", inject+"\n"); const blob=new Blob([html],{type:"text/html"}); const a=document.createElement("a"); a.href=URL.createObjectURL(blob); a.download="index.html"; a.click(); URL.revokeObjectURL(a.href); } /* ---------- helpers ---------- */ function esc(s){ return String(s??"").replace(/[&<>"']/g,c=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[c])); } function renderDashboard(){ renderKPIs(); renderUpcoming(); renderMain(); } function closeAll(){ document.querySelectorAll(".overlay").forEach(o=>o.classList.remove("open")); } function closeProj(){ document.getElementById("projOverlay").classList.remove("open"); } /* ---------- wiring ---------- */ document.getElementById("btnNew").onclick=()=>openEdit(null); document.querySelectorAll(".vtab").forEach(t=>t.onclick=()=>{ view=t.dataset.view; renderMain(); }); document.getElementById("btnContractor").onclick=exportContractorPage; document.getElementById("dealForm").elements["type"].onchange=toggleType; document.getElementById("addTaskBtn").onclick=()=>{ const t=document.getElementById("newTaskText"),du=document.getElementById("newTaskDue"); if(!t.value.trim())return; editTasks.push({text:t.value.trim(),due:du.value||"",done:false}); t.value="";du.value=""; renderEditTasks(); }; document.getElementById("newTaskText").addEventListener("keydown",e=>{if(e.key==="Enter"){e.preventDefault();document.getElementById("addTaskBtn").click();}}); document.getElementById("saveBtn").onclick=()=>{ const form=document.getElementById("dealForm"); if(!form.elements["address"].value.trim()){form.elements["address"].focus();return;} const d=collect(); const idx=deals.findIndex(x=>x.id===d.id); if(idx>=0)deals[idx]=d; else deals.push(d); save(); closeAll(); renderDashboard(); bgSync(d); }; document.getElementById("deleteBtn").onclick=async ()=>{ if(!editingId)return; if(confirm("Delete this deal? This cannot be undone.")){ const target=deals.find(x=>x.id===editingId); await cleanupDeal(target); deals=deals.filter(x=>x.id!==editingId); save(); closeAll(); renderDashboard(); } }; document.getElementById("editFromDetail").onclick=()=>{ document.getElementById("detailOverlay").classList.remove("open"); openEdit(editingId); }; document.getElementById("syncCalBtn").onclick=async ()=>{ const b=document.getElementById("syncCalBtn"); if(!calAvailable()){ alert("Calendar isn't available in this view."); return; } const o=b.textContent; b.textContent="Syncing…"; b.disabled=true; const d=deals.find(x=>x.id===editingId); const r=await autoSyncDeal(d,true); b.textContent=o; b.disabled=false; renderDashboard(); alert(r&&!r.skipped?`Synced. ${r.created} added, ${r.updated} updated, ${r.deleted} removed on your Google Calendar.`:"Nothing to sync."); }; document.getElementById("autoSyncChk").onchange=e=>{ localStorage.setItem(AUTOSYNC_KEY, e.target.checked?"on":"off"); if(e.target.checked) autoSyncAll(true); else setCalStatus(); }; document.getElementById("projSaveBtn").onclick=saveProject; document.getElementById("addDocBtn").onclick=()=>{ const l=document.getElementById("newDocLabel"),u=document.getElementById("newDocUrl"); if(!u.value.trim())return; editDocs.push({label:l.value.trim()||"link",url:u.value.trim()}); l.value="";u.value=""; renderDocs(); }; document.getElementById("projDeleteBtn").onclick=()=>{ if(!projEditId)return; const d=deals.find(x=>x.id===projDealId); if(confirm("Delete this project?")){ d.projects=d.projects.filter(x=>x.id!==projEditId); save(); closeProj(); openDetail(projDealId); renderDashboard(); bgSync(d); } }; document.querySelectorAll("[data-close]").forEach(b=>b.onclick=closeAll); document.querySelectorAll("[data-pclose]").forEach(b=>b.onclick=closeProj); document.querySelectorAll(".overlay").forEach(o=>o.addEventListener("click",e=>{ if(e.target===o){ if(o.id==="projOverlay")closeProj(); else closeAll(); } })); document.addEventListener("keydown",e=>{ if(e.key==="Escape"){ if(document.getElementById("projOverlay").classList.contains("open"))closeProj(); else closeAll(); } }); document.getElementById("btnExport").onclick=()=>{ const blob=new Blob([JSON.stringify(deals,null,2)],{type:"application/json"}); const a=document.createElement("a"); a.href=URL.createObjectURL(blob); a.download="deal-pipeline-backup-"+todayISO()+".json"; a.click(); URL.revokeObjectURL(a.href); }; document.getElementById("btnImport").onclick=()=>document.getElementById("importFile").click(); document.getElementById("importFile").onchange=e=>{ const f=e.target.files[0]; if(!f)return; const r=new FileReader(); r.onload=()=>{ try{ const arr=JSON.parse(r.result); if(Array.isArray(arr)){ if(confirm("Replace current deals with "+arr.length+" imported deal(s)?")){ deals=arr.map(migrate); save(); renderDashboard(); autoSyncAll(false); } } else alert("Not a valid backup."); }catch(err){ alert("Could not read file."); } }; r.readAsText(f); e.target.value=""; }; /* ---------- boot ---------- */ function boot(){ if(window.__READONLY__){ RO=true; document.body.classList.add("readonly"); document.getElementById("roBanner").classList.remove("hide"); document.getElementById("subtitle").textContent="Contractor view — read-only snapshot"; deals=(window.__DEALS__||[]).map(migrate); } else { deals=load().map(migrate); applyPresets(); deals=deals.map(migrate); save(); seedPriOrder(); const chk=document.getElementById("autoSyncChk"); if(chk) chk.checked=calEnabled(); setCalStatus(); renderDashboard(); autoSyncAll(false); return; } renderDashboard(); } if(document.readyState==="loading") document.addEventListener("DOMContentLoaded",boot); else boot(); })();