KatieColihan's picture
when i click on "calculate materials", it freezes.
59f4fcd verified
Raw
History Blame Contribute Delete
15.7 kB
document.addEventListener('DOMContentLoaded', function() {
const questions = [
{
question: "What is the width of the deck (left to right)?",
type: "number",
key: "width"
},
{
question: "What is the depth of the deck (distance from the house outward)?",
type: "number",
key: "depth"
},
{
question: "Is the deck a simple rectangle, or does it have angles/irregularities?",
type: "select",
options: ["Simple rectangle", "Angles/irregularities"],
key: "shape"
},
{
question: "Is the deck tying into the house with a ledger?",
type: "yesno",
key: "hasLedger"
},
{
question: "Will deck boards run parallel or perpendicular to the house?",
type: "select",
options: ["Parallel", "Perpendicular"],
key: "boardOrientation"
},
{
question: "Are we picture framing the deck?",
type: "yesno",
key: "hasPictureFrame",
followups: [
{
question: "What color is the picture frame?",
type: "select",
options: ["Black", "White", "Gray", "Brown", "Custom"],
key: "frameColor"
},
{
question: "What color is the field decking?",
type: "select",
options: ["Black", "White", "Gray", "Brown", "Custom"],
key: "fieldColor"
}
]
},
{
question: "Are there steps?",
type: "yesno",
key: "hasSteps",
followups: [
{
question: "How many sets of steps?",
type: "number",
key: "stepSets"
},
{
question: "What is the width of each step run?",
type: "number",
key: "stepWidth"
},
{
question: "What is the deck height off the ground?",
type: "number",
key: "deckHeight"
},
{
question: "Is there a landing?",
type: "yesno",
key: "hasLanding",
followups: [
{
question: "What size is the landing?",
type: "text",
key: "landingSize"
}
]
}
]
},
{
question: "Are we installing railing on the deck?",
type: "yesno",
key: "hasRailing",
followups: [
{
question: "Which sides of the deck will have railing? (Select all that apply)",
type: "multiselect",
options: ["Front", "Left", "Right", "Back"],
key: "railingSides"
}
]
},
{
question: "Do we need ADA handrail?",
type: "yesno",
key: "hasAdaRail",
followups: [
{
question: "Choose ADA handrail length",
type: "select",
options: ["8'8\"", "16'"],
key: "adaRailLength"
}
]
},
{
question: "Will the deck be skirted?",
type: "yesno",
key: "hasSkirting"
},
{
question: "Are we installing fascia?",
type: "yesno",
key: "hasFascia",
followups: [
{
question: "What color is the fascia?",
type: "select",
options: ["Black", "White", "Gray", "Brown", "Custom"],
key: "fasciaColor"
}
]
},
{
question: "Any customer preferences, township requirements, or special framing considerations?",
type: "textarea",
key: "notes"
}
];
let currentQuestion = 0;
let answers = {};
const questionContainer = document.getElementById('current-question');
const answerInput = document.getElementById('answer-input');
const nextBtn = document.getElementById('next-btn');
function renderQuestion(index) {
const q = questions[index];
questionContainer.textContent = q.question;
answerInput.innerHTML = '';
switch(q.type) {
case 'number':
answerInput.innerHTML = `
<input type="number" class="w-full px-4 py-2 border border-gray-300 rounded-lg"
id="answer" placeholder="Enter measurement in feet">
`;
break;
case 'yesno':
answerInput.innerHTML = `
<div class="flex space-x-4">
<button type="button" class="yesno-btn px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-100" data-value="yes">Yes</button>
<button type="button" class="yesno-btn px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-100" data-value="no">No</button>
</div>
`;
// Add click handlers for yes/no buttons
document.querySelectorAll('.yesno-btn').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.yesno-btn').forEach(b => b.classList.remove('bg-blue-500', 'text-white', 'border-blue-500'));
this.classList.add('bg-blue-500', 'text-white', 'border-blue-500');
});
});
break;
case 'select':
answerInput.innerHTML = `
<select class="w-full px-4 py-2 border border-gray-300 rounded-lg" id="answer">
${q.options.map(opt => `<option value="${opt}">${opt}</option>`).join('')}
</select>
`;
break;
case 'multiselect':
answerInput.innerHTML = `
<div class="space-y-2">
${q.options.map(opt => `
<label class="flex items-center">
<input type="checkbox" class="mr-2" value="${opt}">
${opt}
</label>
`).join('')}
</div>
`;
break;
case 'textarea':
answerInput.innerHTML = `
<textarea class="w-full px-4 py-2 border border-gray-300 rounded-lg"
id="answer" rows="3"></textarea>
`;
break;
}
}
function collectAnswer() {
const q = questions[currentQuestion];
let answer;
switch(q.type) {
case 'number':
answer = parseFloat(document.getElementById('answer').value) || 0;
break;
case 'select':
case 'textarea':
answer = document.getElementById('answer').value;
break;
case 'yesno':
const selectedBtn = document.querySelector('.yesno-btn.bg-blue-500');
answer = selectedBtn ? selectedBtn.dataset.value : null;
break;
case 'multiselect':
answer = Array.from(document.querySelectorAll('input[type="checkbox"]:checked'))
.map(cb => cb.value);
break;
}
// Skip validation for notes question (it's optional)
if (q.key === 'notes') {
answers[q.key] = answer || '';
return true;
}
// Validate other questions
if (answer === null || answer === undefined || (Array.isArray(answer) && answer.length === 0)) {
return false;
}
// Store answer
answers[q.key] = answer;
return true;
}
nextBtn.addEventListener('click', function() {
if (!collectAnswer()) {
alert('Please provide an answer before continuing');
return;
}
currentQuestion++;
if (currentQuestion < questions.length) {
renderQuestion(currentQuestion);
} else {
// All questions answered - show material list section
document.getElementById('question-container').classList.add('hidden');
document.getElementById('results-section').classList.remove('hidden');
}
});
// Initialize first question
renderQuestion(0);
// Show stairs details if checkbox is checked on load
if (document.getElementById('stairs').checked) {
document.getElementById('stairs-details').classList.remove('hidden');
}
// Toggle stairs details
document.getElementById('stairs').addEventListener('change', function() {
document.getElementById('stairs-details').classList.toggle('hidden', !this.checked);
});
// Toggle railing details
document.getElementById('railing').addEventListener('change', function() {
// Add any railing-specific toggle logic here
});
// Handle calculate button click
document.getElementById('calculate-btn').addEventListener('click', function() {
// First make sure all questions are answered
while (currentQuestion < questions.length) {
if (!collectAnswer()) {
alert('Please answer all questions before calculating materials');
document.getElementById('question-container').classList.remove('hidden');
document.getElementById('results-section').classList.add('hidden');
renderQuestion(currentQuestion);
return;
}
currentQuestion++;
}
// Generate and show results
generateMaterialList();
document.getElementById('question-container').classList.add('hidden');
document.getElementById('results-section').classList.remove('hidden');
document.getElementById('results-section').scrollIntoView({ behavior: 'smooth' });
// Reset for next calculation
currentQuestion = 0;
answers = {};
renderQuestion(0);
// Log answers for debugging
console.log('User answers:', answers);
});
function generateMaterialList() {
// Get basic deck dimensions
const width = answers.width || 10; // Default to 10ft if not provided
const depth = answers.depth || 10; // Default to 10ft if not provided
const deckArea = width * depth;
// Get special features
const boardType = document.getElementById('board-type').value;
const hasStairs = document.getElementById('stairs').checked;
const stepCount = parseInt(document.getElementById('step-count').value) || 0;
const hasRailing = document.getElementById('railing').checked;
const hasRoof = document.getElementById('roof').checked;
// Calculate deck boards based on board type and dimensions
const boardWidth = 5.5; // inches
const boardSpacing = 0.25; // inches
const effectiveWidth = boardWidth + boardSpacing;
// Calculate number of boards needed along depth
const boardsAlongDepth = Math.ceil((depth * 12) / effectiveWidth);
// Calculate board lengths needed based on width
let deckBoards;
if (boardType === 'trex-grooved') {
// Trex grooved comes in 12', 16', 20' lengths - optimize for least waste
deckBoards = calculateOptimalBoards(width, boardsAlongDepth, [12, 16, 20]);
} else if (boardType === 'trex-square') {
// Trex square only in 20'
deckBoards = calculateOptimalBoards(width, boardsAlongDepth, [20]);
} else {
// Wood/other composite - assume 12' boards
deckBoards = calculateOptimalBoards(width, boardsAlongDepth, [12]);
}
// Calculate framing materials
const joistSpacing = 16; // inches
const joists = Math.ceil((width * 12) / joistSpacing) + 1; // +1 for rim joist
const beams = Math.ceil(width / 8);
const posts = Math.ceil(width / 6) * 2; // Posts on both sides
const concrete = posts * 3; // 3 bags per post
// Calculate fasteners
const screwSpacing = 12; // inches along board length
const screwsPerBoard = Math.ceil((depth * 12) / screwSpacing) * 2; // 2 screws per joist
const totalScrews = screwsPerBoard * boardsAlongDepth;
const fastenerBoxes = Math.ceil(totalScrews / 250); // 250 screws per box
// Update results display
document.getElementById('deck-boards').textContent = `${deckBoards.total} ${boardType.includes('trex') ? 'Trex' : boardType === 'wood' ? 'Wood' : 'Composite'} boards`;
document.getElementById('joists').textContent = `${joists} (2×8 @ 16" OC)`;
document.getElementById('beams').textContent = `${beams} (4×6)`;
document.getElementById('posts').textContent = `${posts} (6×6)`;
document.getElementById('concrete').textContent = `${concrete} bags`;
document.getElementById('fasteners').textContent = `${fastenerBoxes} boxes`;
// Add special features if checked
if (hasStairs) {
document.getElementById('deck-boards').textContent += ` + ${stepCount} stair treads`;
document.getElementById('joists').textContent += ` + ${stepCount} stair stringers`;
}
if (hasRailing) {
const railingLength = width * 2; // Assuming 2 sides
const railingPosts = Math.ceil(railingLength / 6) * 2; // 6' max spacing, 2 per section
document.getElementById('deck-boards').textContent += ` + ${Math.ceil(railingLength / 6)} railing sections`;
document.getElementById('posts').textContent += ` + ${railingPosts} railing posts`;
}
if (hasRoof) {
document.getElementById('joists').textContent += ` + ${Math.ceil(width / 2)} roof rafters`;
document.getElementById('posts').textContent += ` + ${Math.ceil(width / 8) * 2} roof support posts`;
}
}
function calculateOptimalBoards(width, count, availableLengths) {
availableLengths.sort((a, b) => b - a); // Sort descending
const result = {
total: 0,
lengths: {}
};
for (let i = 0; i < count; i++) {
let remaining = width;
let currentCut = {};
while (remaining > 0) {
for (const len of availableLengths) {
if (len <= remaining) {
currentCut[len] = (currentCut[len] || 0) + 1;
remaining -= len;
break;
}
}
}
// Aggregate the lengths
for (const [len, qty] of Object.entries(currentCut)) {
result.lengths[len] = (result.lengths[len] || 0) + qty;
result.total += qty;
}
}
return result;
}
});