Room Makeover Planner Generator

Room Makeover Planner Generator

Project Details

Describe the desired look, feel, and function of the finished room.

What specific problems must this makeover solve? (e.g., more storage, better lighting)

Room Dimensions (Feet/Inches)

Outline the initial demolition, repair, and cleaning work needed before decorating starts.

Note where major furniture pieces (bed, desk, sofa) will be placed relative to windows/doors.

Shopping List Builder

Current Shopping List (Editable Dashboard)

Item Description Unit Cost ($) Qty Line Total ($) Actions

Review your complete Room Makeover Plan.

Click "Next" or "Previous" to refresh this preview.

Layout Notes: ${escapeHTML(data.layout)}

3. Projected Budget

Shopping List Subtotal: ${formatCurrency(data.totals.subtotal)}
Tax/Contingency Estimate (8.0%): ${formatCurrency(data.totals.taxEstimate)}
PROJECTED GRAND TOTAL: ${formatCurrency(data.totals.grandTotal)}

4. Shopping List

${itemRowsHTML.length > 0 ? itemRowsHTML : ''}
Item Description Unit Cost ($) Qty Line Total ($)
No items logged.
`; }; const downloadTxt = () => { const data = getPlanData(); const totals = data.totals; let content = `ROOM MAKEOVER PLAN: ${data.roomName.toUpperCase()}\n`; content += `Style: ${data.style}\n`; content += "========================================================\n\n"; content += "1. PROJECT VISION & GOALS\n"; content += `Vision: ${data.vision}\n\n`; content += `Key Goals:\n${data.goals.split('\n').filter(l => l.trim()).map(l => ` - ${l}`).join('\n')}\n\n`; content += "2. DIMENSIONS & PREP\n"; content += `Dimensions (L x W x H): ${data.length} ft x ${data.width} ft x ${data.height} ft\n`; content += `Preparation Notes: ${data.prep}\n`; content += `Layout Notes: ${data.layout}\n\n`; content += "3. PROJECTED BUDGET\n"; content += `Subtotal (Items):\t${formatCurrency(totals.subtotal)}\n`; content += `Tax/Contingency (8.0%):\t${formatCurrency(totals.taxEstimate)}\n`; content += `GRAND TOTAL:\t\t${formatCurrency(totals.grandTotal)}\n\n`; content += "4. SHOPPING LIST\n"; content += "--------------------------------------------------------\n"; content += "Item Description\tUnit Cost\tQty\tLine Total\n"; content += "--------------------------------------------------------\n"; data.items.forEach(item => { const lineTotal = item.cost * item.quantity; content += `${item.description.padEnd(25).substring(0, 25)}\t$${item.cost.toFixed(2)}\t\t${item.quantity}\t$${lineTotal.toFixed(2)}\n`; }); content += "--------------------------------------------------------\n"; const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `plan_${data.roomName.replace(/ /g, '_')}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(a.href); }; const downloadPDF = () => { if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('Error: jsPDF library not loaded.'); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF('p', 'mm', 'a4'); const data = getPlanData(); const totals = data.totals; const margin = 15; let yPos = 20; const pageWidth = doc.internal.pageSize.getWidth(); const addTitle = (text, size, color, style = 'bold') => { if (yPos > 280) { doc.addPage(); yPos = 20; } doc.setFontSize(size); doc.setFont(undefined, style); doc.setTextColor(color[0], color[1], color[2]); doc.text(text, margin, yPos); yPos += size / 2 + 3; }; const addText = (text, size = 10, style = 'normal', indent = 0) => { doc.setFontSize(size); doc.setFont(undefined, style); doc.setTextColor(52, 73, 94); const lines = doc.splitTextToSize(text, pageWidth - margin * 2 - indent); if (lines.length * 5 + yPos > 280) { doc.addPage(); yPos = 20; } doc.text(lines, margin + indent, yPos); yPos += (lines.length * 5) + 3; }; // 1. Header doc.setFontSize(22); doc.setFont(undefined, 'bold'); doc.setTextColor(44, 62, 80); doc.text(`Room Makeover Plan`, pageWidth / 2, yPos, { align: 'center' }); yPos += 8; addTitle(data.roomName, 14, [217, 119, 6], 'bold'); yPos -= 5; addText(`Style: ${data.style}`, 10, 'italic', 0); yPos += 3; // 2. Project Vision addTitle("1. Project Vision & Goals", 12, [217, 119, 6]); addText("Vision:", 10, 'bold'); addText(data.vision, 10, 'normal', 5); addText("Key Goals:", 10, 'bold'); addText(data.goals, 10, 'normal', 5); yPos += 3; // 3. Dimensions & Prep addTitle("2. Dimensions & Preparation", 12, [217, 119, 6]); addText(`Dimensions (L x W x H): ${data.length} ft x ${data.width} ft x ${data.height} ft`, 10); addText("Preparation Notes:", 10, 'bold'); addText(data.prep, 10, 'normal', 5); addText("Layout Notes:", 10, 'bold'); addText(data.layout, 10, 'normal', 5); yPos += 3; // 4. Budget Summary addTitle("3. Projected Budget", 12, [217, 119, 6]); const totalsY = yPos; const totalsX = pageWidth - margin - 50; const addTotalLine = (label, value, isGrand = false) => { doc.setFontSize(isGrand ? 12 : 10); doc.setFont(undefined, isGrand ? 'bold' : 'normal'); doc.setTextColor(isGrand ? 217 : 52, isGrand ? 119 : 73, isGrand ? 6 : 94); if (isGrand) { doc.setDrawColor(44, 62, 80); doc.setLineWidth(0.5); doc.line(totalsX - 10, totalsY - 1, pageWidth - margin, totalsY - 1); yPos += 2; } doc.text(label, totalsX, totalsY, { align: 'left' }); doc.text(formatCurrency(value), pageWidth - margin, totalsY, { align: 'right' }); yPos += 5; }; addTotalLine("Shopping List Subtotal:", totals.subtotal); addTotalLine(`Tax/Contingency (8.0%):`, totals.taxEstimate); addTotalLine("PROJECTED GRAND TOTAL:", totals.grandTotal, true); yPos = Math.max(yPos, totalsY) + 5; // Advance Y position past the totals block // 5. Shopping List Table addTitle("4. Shopping List", 12, [217, 119, 6]); const itemHead = [['Item Description', 'Unit Cost ($)', 'Qty', 'Line Total ($)']]; const itemBody = data.items.map(item => [ item.description, item.cost.toFixed(2), item.quantity.toString(), (item.cost * item.quantity).toFixed(2) ]); doc.autoTable({ startY: yPos, head: itemHead, body: itemBody, theme: 'grid', styles: { fontSize: 9, cellPadding: 2, textColor: [52, 73, 94] }, headStyles: { fillColor: [243, 203, 137], textColor: [44, 62, 80], fontStyle: 'bold' }, columnStyles: { 0: { cellWidth: 70, fontStyle: 'normal' }, 1: { halign: 'right' }, 2: { halign: 'center', cellWidth: 15 }, 3: { halign: 'right', fontStyle: 'bold' } }, margin: { left: margin, right: margin } }); doc.save(`plan_${data.roomName.replace(/ /g, '_')}.pdf`); }; // --- Event Listeners --- // Tab Buttons tabButtons.forEach((btn, index) => { btn.addEventListener('click', () => showTab(index + 1)); }); // Next/Prev Navigation nextBtn.addEventListener('click', () => showTab(currentTab + 1)); prevBtn.addEventListener('click', () => showTab(currentTab - 1)); // Tab 3 Actions (Add, Remove, Edit) itemInputs.addBtn.addEventListener('click', addItem); itemInputs.tbody.addEventListener('click', (e) => { if (e.target.dataset.removeId) { removeItem(parseInt(e.target.dataset.removeId)); } }); itemInputs.tbody.addEventListener('blur', (e) => { if (e.target.tagName === 'TD' && e.target.isContentEditable) { const id = parseInt(e.target.dataset.id); const field = e.target.dataset.field; updateItem(id, field, e.target.textContent); } }, true); // Tab 4 Actions downloadPdfBtn.addEventListener('click', downloadPDF); downloadTxtBtn.addEventListener('click', downloadTxt); // --- Initialization --- loadSampleData(); showTab(1); // Set initial state });
Scroll to Top