PropX | Weyland AI
SubX SightX TakeoffX PropX QText
No session loaded $799/mo
1 Project Information
2 Scope of Work
3 Hardware Schedule
Set # Component Qty Unit Price Extended
No hardware data. Load from TakeoffX or add manually.
4 Door Schedule
Mark HW Group Size Type Material Fire Rating
No door data. Load from TakeoffX or add manually.
5 Pricing Summary
Hardware Subtotal $0.00 Doors Subtotal $0.00 Frames Subtotal $0.00 Services Subtotal $0.00
Taxable Amount $0.00 Tax (8.25%) $0.00
Grand Total $0.00
6 ADA Compliance
7 Warranty Terms
8 Insurance & Bonding
9 Payment Schedule
10 Exclusions
11 Acceptance Block
📜
Live Proposal Preview
Fill in the editor fields to see a real-time preview of your proposal. Select a template from the sidebar to change styling.
' + previewContent + ' '; var blob = new Blob([wordHtml], { type: 'application/msword' }); var a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = ($('projName').value || 'Proposal').replace(/[^a-zA-Z0-9]/g, '_') + '.doc'; a.click(); URL.revokeObjectURL(a.href); toast('Word document exported', 'success'); } // ── Send via QText ───────────────────────────────────────────── function sendViaQText() { if (!state.currentSessionId) { toast('Save the proposal first', 'warn'); return; } // Generate hypercompressed SMS version var totals = calcTotals(); var projName = $('projName').value || 'Proposal'; var projClient = $('projClient').value || 'Client'; var smsText = 'PROPOSAL: ' + projName + '\n' + 'For: ' + projClient + '\n' + 'HW: ' + fmt(totals.hwTotal) + '\n'; if (totals.svcTotal > 0) smsText += 'SVC: ' + fmt(totals.svcTotal) + '\n'; smsText += 'Tax: ' + fmt(totals.taxAmt) + '\n' + 'TOTAL: ' + fmt(totals.grandTotal) + '\n' + 'Valid ' + ($('projValidDays').value || '30') + ' days\n' + 'Ref: ' + ($('projRef').value || 'N/A'); // Navigate to QText with the compressed message var url = '/qtext?session=' + state.currentSessionId + '&msg=' + encodeURIComponent(smsText); window.location.href = url; } // ── Split Pane Resize ────────────────────────────────────────── (function setupSplitResize() { var handle = null; var editorPane = null; var previewPane = null; var startX = 0; var startEditorWidth = 0; document.addEventListener('DOMContentLoaded', function() { handle = $('splitHandle'); editorPane = $('editorPane'); previewPane = $('previewPane'); handle.addEventListener('mousedown', function(e) { e.preventDefault(); startX = e.clientX; startEditorWidth = editorPane.offsetWidth; handle.classList.add('dragging'); document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }); }); function onMouseMove(e) { var delta = e.clientX - startX; var newWidth = startEditorWidth + delta; var container = $('contentArea'); var minW = 300; var maxW = container.offsetWidth - 300; newWidth = Math.max(minW, Math.min(maxW, newWidth)); editorPane.style.flex = 'none'; editorPane.style.width = newWidth + 'px'; previewPane.style.flex = '1'; } function onMouseUp() { handle.classList.remove('dragging'); document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } })(); // ── Keyboard Shortcuts ───────────────────────────────────────── document.addEventListener('keydown', function(e) { // Ctrl/Cmd + S = save draft if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); saveDraft(); } // Ctrl/Cmd + P = generate PDF if ((e.ctrlKey || e.metaKey) && e.key === 'p') { e.preventDefault(); generatePDF(); } }); // ── Init ─────────────────────────────────────────────────────── (function init() { // Render default state renderMilestones(); renderExclusions(); renderRecentProposals(); recalcTotals(); // Set today as default bid date var today = new Date(); var yyyy = today.getFullYear(); var mm = String(today.getMonth() + 1).padStart(2, '0'); var dd = String(today.getDate()).padStart(2, '0'); $('projBidDate').value = yyyy + '-' + mm + '-' + dd; // Generate default ref number $('projRef').value = 'WA-' + yyyy + '-' + mm + dd; // Load sessions loadSessions(); // Check for session param from pipeline var sessionParam = getUrlParam('session'); if (sessionParam) { state.currentSessionId = sessionParam; $('btnLoadTakeoff').disabled = false; $('btnSendQText').disabled = false; // Try to load saved draft first var hadDraft = loadDraft(sessionParam); // Wait for sessions to load then select + auto-load takeoff var checkInterval = setInterval(function() { if (state.sessions.length > 0 || document.querySelector('.session-item')) { clearInterval(checkInterval); selectSession(sessionParam); if (!hadDraft) { // Auto-load takeoff data if no draft exists setTimeout(function() { loadFromTakeoff(); }, 500); } } }, 300); setTimeout(function() { clearInterval(checkInterval); if (!state.currentSession) { selectSession(sessionParam); if (!hadDraft) loadFromTakeoff(); } }, 2000); } })();