772 lines
24 KiB
Markdown
772 lines
24 KiB
Markdown
# Conditional Navigation and Filtering Status
|
|
|
|
## Purpose
|
|
|
|
This document records what was originally proposed for conditional question flow,
|
|
what is already implemented, and what still remains.
|
|
// Load all data files
|
|
Promise.all([
|
|
fetch('data/navigation.json').then(r => r.json()),
|
|
fetch('data/products.json').then(r => r.json()),
|
|
fetch('data/accessories.json').then(r => r.json()),
|
|
fetch('data/product_bitwise.json').then(r => r.json())
|
|
])
|
|
.then(([questions, products, accessories, bitwise]) => {
|
|
questionData = questions;
|
|
productData = products;
|
|
accessoryData = accessories;
|
|
|
|
// Index bitwise data by product code
|
|
bitwiseData = {};
|
|
bitwise.forEach(item => {
|
|
bitwiseData[item.PROD_CODE] = item.BIT_VALUE;
|
|
});
|
|
|
|
// Check if there's state in URL
|
|
initFromURL();
|
|
|
|
// If no URL state, start normally
|
|
if (!window.location.search) {
|
|
loadContent('start');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading data:', error);
|
|
// Show error message
|
|
});
|
|
}
|
|
```
|
|
|
|
#### B. Add Dynamic Next Resolution Function
|
|
```javascript
|
|
// Add this new function to handle conditional navigation
|
|
function resolveNextQuestion(currentKey, selectedAnswer, answerObject) {
|
|
// Get the current accumulated filters
|
|
const currentFilters = buildFilterFromAnswers();
|
|
|
|
// Add the new filter from this answer
|
|
if (answerObject.filter) {
|
|
Object.assign(currentFilters, answerObject.filter);
|
|
}
|
|
|
|
// Check if next question in chain should be shown
|
|
const nextKey = answerObject.next;
|
|
const nextQuestion = questionData[nextKey];
|
|
|
|
// If next question has conditional flag, evaluate it
|
|
if (nextQuestion && nextQuestion.conditional) {
|
|
return evaluateConditional(nextQuestion, currentFilters);
|
|
}
|
|
|
|
return nextKey;
|
|
}
|
|
|
|
// Evaluate if a conditional question should be shown
|
|
function evaluateConditional(question, filters) {
|
|
if (question.conditional.type === 'material') {
|
|
// Check if products matching current filters have multiple materials
|
|
const matchingProducts = filterProducts(filters);
|
|
const materials = getAvailableMaterials(matchingProducts);
|
|
|
|
if (materials.length <= 1) {
|
|
// Skip this question, auto-apply material if exists
|
|
if (materials.length === 1) {
|
|
userAnswers[question.id + '.material'] = materials[0];
|
|
}
|
|
// Return the fallback next
|
|
return question.conditional.fallbackNext || question.next;
|
|
}
|
|
}
|
|
|
|
if (question.conditional.type === 'color') {
|
|
// Similar logic for colors
|
|
const matchingProducts = filterProducts(filters);
|
|
const colors = getAvailableColors(matchingProducts);
|
|
|
|
if (colors.length <= 1) {
|
|
if (colors.length === 1) {
|
|
userAnswers[question.id + '.color'] = colors[0];
|
|
}
|
|
return question.conditional.fallbackNext || question.next;
|
|
}
|
|
}
|
|
|
|
// Show the question
|
|
return question.id;
|
|
}
|
|
|
|
// Build current filter object from user answers
|
|
function buildFilterFromAnswers() {
|
|
const filters = {};
|
|
|
|
// Extract filter info from answers
|
|
for (const [key, value] of Object.entries(userAnswers)) {
|
|
if (key === 'start') {
|
|
filters.baseType = value;
|
|
} else if (key.includes('type')) {
|
|
filters.subType = value;
|
|
} else if (key.includes('material')) {
|
|
filters.material = value;
|
|
} else if (key.includes('color')) {
|
|
filters.color = value;
|
|
}
|
|
}
|
|
|
|
return filters;
|
|
}
|
|
|
|
// Filter products based on criteria
|
|
function filterProducts(filters) {
|
|
return productData.filter(product => {
|
|
if (filters.baseType && product.baseType !== filters.baseType) {
|
|
return false;
|
|
}
|
|
if (filters.subType) {
|
|
const subType = product.subType.door || product.subType.window;
|
|
if (subType !== filters.subType) {
|
|
return false;
|
|
}
|
|
}
|
|
if (filters.material && !product.materials.includes(filters.material)) {
|
|
return false;
|
|
}
|
|
if (filters.color && !product.colors.includes(filters.color)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
// Get available materials from product set
|
|
function getAvailableMaterials(products) {
|
|
const materials = new Set();
|
|
products.forEach(p => {
|
|
p.materials.forEach(m => materials.add(m));
|
|
});
|
|
return Array.from(materials);
|
|
}
|
|
|
|
// Get available colors from product set
|
|
function getAvailableColors(products) {
|
|
const colors = new Set();
|
|
products.forEach(p => {
|
|
p.colors.forEach(c => colors.add(c));
|
|
});
|
|
return Array.from(colors);
|
|
}
|
|
```
|
|
|
|
#### C. Update handleAnswer Function
|
|
```javascript
|
|
// Replace the existing handleAnswer function
|
|
function handleAnswer(currentKey, answerValue, answerIndex) {
|
|
const currentQuestion = questionData[currentKey];
|
|
const answerObject = currentQuestion.answers[answerIndex];
|
|
|
|
// Store the answer
|
|
userAnswers[currentKey] = answerValue;
|
|
|
|
// Update bit value based on selection
|
|
updateBitValue(answerObject);
|
|
|
|
// Resolve the next question (handles conditionals)
|
|
const nextKey = resolveNextQuestion(currentKey, answerValue, answerObject);
|
|
|
|
history.push(nextKey);
|
|
loadContent(nextKey);
|
|
}
|
|
```
|
|
|
|
#### D. Update renderQuestion to Pass Answer Index
|
|
```javascript
|
|
// In renderQuestion function, change the button onclick
|
|
// FROM:
|
|
// onclick="handleAnswer('${currentKey}', '${answer.caption}', '${answer.next}')"
|
|
|
|
// TO:
|
|
data.answers.forEach((answer, index) => {
|
|
html += `
|
|
<button class="answer-button" onclick="handleAnswer('${currentKey}', '${answer.caption}', ${index})">
|
|
<div class="answer-image">${answer.image}</div>
|
|
<div class="answer-caption">${answer.caption}</div>
|
|
</button>
|
|
`;
|
|
});
|
|
```
|
|
|
|
#### D2. Add URL State Management
|
|
```javascript
|
|
// Track accumulated bit value from user selections
|
|
let accumulatedBitValue = 0;
|
|
|
|
// Load bitwise helper data
|
|
let bitwiseData = {};
|
|
|
|
function loadBitwiseData() {
|
|
return fetch('data/product_bitwise.json')
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
bitwiseData = {};
|
|
data.forEach(item => {
|
|
bitwiseData[item.PROD_CODE] = item.BIT_VALUE;
|
|
});
|
|
return bitwiseData;
|
|
});
|
|
}
|
|
|
|
// Initialize from URL on page load
|
|
function initFromURL() {
|
|
const params = new URLSearchParams(window.location.search);
|
|
|
|
// Check for bit value in URL
|
|
const bitValue = params.get('b');
|
|
if (bitValue) {
|
|
accumulatedBitValue = parseInt(bitValue, 10);
|
|
// Restore state based on bit value
|
|
restoreStateFromBitValue(accumulatedBitValue);
|
|
}
|
|
|
|
// Check for product code in URL
|
|
const productCode = params.get('p');
|
|
if (productCode) {
|
|
showProductByCode(productCode);
|
|
}
|
|
}
|
|
|
|
// Update URL with current state
|
|
function updateURL() {
|
|
const params = new URLSearchParams();
|
|
|
|
if (accumulatedBitValue > 0) {
|
|
params.set('b', accumulatedBitValue);
|
|
}
|
|
|
|
// Get current question
|
|
if (history.length > 0) {
|
|
const currentKey = history[history.length - 1];
|
|
params.set('q', currentKey);
|
|
}
|
|
|
|
// Update URL without reloading page
|
|
const newURL = window.location.pathname + '?' + params.toString();
|
|
window.history.replaceState({ bitValue: accumulatedBitValue }, '', newURL);
|
|
}
|
|
|
|
// Update bit value based on user selection
|
|
function updateBitValue(answerObject) {
|
|
if (answerObject.filter) {
|
|
// Add bits based on filter
|
|
if (answerObject.filter.baseType === 'Door') {
|
|
accumulatedBitValue |= 1; // Bit 0
|
|
} else if (answerObject.filter.baseType === 'Window') {
|
|
accumulatedBitValue |= 2; // Bit 1
|
|
}
|
|
|
|
if (answerObject.filter.material === 'Aluminum') {
|
|
accumulatedBitValue |= 16; // Bit 4
|
|
} else if (answerObject.filter.material === 'Vinyl') {
|
|
accumulatedBitValue |= 32; // Bit 5
|
|
}
|
|
|
|
// Add color bits
|
|
const colorBits = {
|
|
'Black': 64,
|
|
'White': 128,
|
|
'Bronze': 256,
|
|
'Tan': 512,
|
|
'Mill': 1024,
|
|
'Sandstone': 2048
|
|
};
|
|
|
|
if (answerObject.filter.color && colorBits[answerObject.filter.color]) {
|
|
accumulatedBitValue |= colorBits[answerObject.filter.color];
|
|
}
|
|
|
|
// Add subtype bits (based on bitwise_legend.json)
|
|
const subtypeBits = {
|
|
'Patio Door': 4096,
|
|
'Primary Window': 8192,
|
|
'Storm Door': 16384,
|
|
'Storm Window': 32768
|
|
};
|
|
|
|
if (answerObject.filter.subType && subtypeBits[answerObject.filter.subType]) {
|
|
accumulatedBitValue |= subtypeBits[answerObject.filter.subType];
|
|
}
|
|
}
|
|
|
|
updateURL();
|
|
}
|
|
|
|
// Restore state from bit value
|
|
function restoreStateFromBitValue(bitValue) {
|
|
// Decode bit value back to user selections
|
|
const selections = {};
|
|
|
|
if (bitValue & 1) selections.baseType = 'Door';
|
|
if (bitValue & 2) selections.baseType = 'Window';
|
|
|
|
if (bitValue & 16) selections.material = 'Aluminum';
|
|
if (bitValue & 32) selections.material = 'Vinyl';
|
|
|
|
const colors = ['Black', 'White', 'Bronze', 'Tan', 'Mill', 'Sandstone'];
|
|
const colorBits = [64, 128, 256, 512, 1024, 2048];
|
|
|
|
colors.forEach((color, idx) => {
|
|
if (bitValue & colorBits[idx]) {
|
|
selections.color = color;
|
|
}
|
|
});
|
|
|
|
// Store in userAnswers
|
|
if (selections.baseType) userAnswers['start'] = selections.baseType;
|
|
if (selections.material) userAnswers['q-material.material'] = selections.material;
|
|
if (selections.color) userAnswers['q-color.color'] = selections.color;
|
|
|
|
// Navigate to appropriate question from URL param
|
|
const params = new URLSearchParams(window.location.search);
|
|
const questionKey = params.get('q') || 'start';
|
|
loadContent(questionKey);
|
|
}
|
|
|
|
// Share current state
|
|
function shareCurrentState() {
|
|
const url = window.location.href;
|
|
|
|
// Copy to clipboard
|
|
if (navigator.clipboard) {
|
|
navigator.clipboard.writeText(url).then(() => {
|
|
alert('Link copied to clipboard! Share this link to return to this exact state.');
|
|
});
|
|
} else {
|
|
// Fallback: show URL
|
|
prompt('Copy this URL to share:', url);
|
|
}
|
|
}
|
|
|
|
// Add share button to breadcrumb
|
|
function updateBreadcrumbWithShare() {
|
|
const breadcrumbDiv = document.getElementById('breadcrumb');
|
|
const shareButton = `
|
|
<button onclick="shareCurrentState()"
|
|
style="float: right; padding: 5px 10px; cursor: pointer;">
|
|
🔗 Share
|
|
</button>
|
|
`;
|
|
breadcrumbDiv.innerHTML += shareButton;
|
|
}
|
|
```
|
|
|
|
#### E. Add Product Results Page
|
|
```javascript
|
|
// Add new function to show filtered products
|
|
function showProductResults(filters) {
|
|
const matchingProducts = filterProducts(filters);
|
|
|
|
const contentDiv = document.getElementById('content');
|
|
|
|
if (matchingProducts.length === 0) {
|
|
contentDiv.innerHTML = `
|
|
<div class="result-container">
|
|
<div class="result-title">No Products Found</div>
|
|
<div class="result-details">
|
|
No products match your specifications. Please try different options.
|
|
</div>
|
|
<button class="back-button" onclick="goBack()">← Go Back</button>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
let html = `
|
|
<div class="result-container">
|
|
<div class="result-title">Found ${matchingProducts.length} Product(s)</div>
|
|
<div class="products-grid">
|
|
`;
|
|
|
|
matchingProducts.forEach(product => {
|
|
// Get compatible accessories
|
|
const compatibleAccessories = accessoryData.filter(acc =>
|
|
product.compatibleAccessories.includes(acc.id)
|
|
);
|
|
|
|
html += `
|
|
<div class="product-card" onclick="showProductDetail('${product.productCode}')">
|
|
<h3>${product.description}</h3>
|
|
<p><strong>Code:</strong> ${product.productCode}</p>
|
|
<p><strong>Materials:</strong> ${product.materials.join(', ')}</p>
|
|
<p><strong>Colors:</strong> ${product.colors.join(', ')}</p>
|
|
${compatibleAccessories.length > 0 ? `
|
|
<p><strong>Available Options:</strong></p>
|
|
<ul>
|
|
${compatibleAccessories.map(acc =>
|
|
`<li>${acc.description}</li>`
|
|
).join('')}
|
|
</ul>
|
|
` : ''}
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
html += `
|
|
</div>
|
|
<div class="result-actions">
|
|
<button class="back-button" onclick="goBack()">← Go Back</button>
|
|
<button class="back-button" onclick="startOver()">Start Over</button>
|
|
<button class="back-button" onclick="shareCurrentState()">🔗 Share Results</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
contentDiv.innerHTML = html;
|
|
}
|
|
|
|
// Show individual product detail with URL update
|
|
function showProductDetail(productCode) {
|
|
const product = productData.find(p => p.productCode === productCode);
|
|
if (!product) return;
|
|
|
|
// Update URL with product code
|
|
const params = new URLSearchParams();
|
|
params.set('p', productCode);
|
|
if (accumulatedBitValue > 0) {
|
|
params.set('b', accumulatedBitValue);
|
|
}
|
|
window.history.pushState({ productCode }, '', '?' + params.toString());
|
|
|
|
// Get compatible accessories
|
|
const compatibleAccessories = accessoryData.filter(acc =>
|
|
product.compatibleAccessories.includes(acc.id)
|
|
);
|
|
|
|
const contentDiv = document.getElementById('content');
|
|
let html = `
|
|
<div class="result-container">
|
|
<div class="result-title">${product.description}</div>
|
|
<div class="result-content">
|
|
<div class="result-details">
|
|
<p><strong>Product Code:</strong> ${product.productCode}</p>
|
|
<p><strong>Category:</strong> ${product.category}</p>
|
|
<p><strong>Base Type:</strong> ${product.baseType || 'N/A'}</p>
|
|
<p><strong>Materials:</strong> ${product.materials.join(', ') || 'N/A'}</p>
|
|
<p><strong>Colors:</strong> ${product.colors.join(', ') || 'N/A'}</p>
|
|
|
|
${compatibleAccessories.length > 0 ? `
|
|
<h3 style="margin-top: 20px;">Compatible Accessories</h3>
|
|
<div class="accessories-list">
|
|
${compatibleAccessories.map(acc => `
|
|
<div class="accessory-item">
|
|
<strong>${acc.description}</strong><br>
|
|
<small>Code: ${acc.accessoryCode}</small><br>
|
|
<small>Materials: ${acc.materials.join(', ')}</small>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
</div>
|
|
<div class="result-actions">
|
|
<button class="back-button" onclick="goBack()">← Back to Results</button>
|
|
<button class="back-button" onclick="startOver()">Start Over</button>
|
|
<button class="back-button" onclick="shareCurrentState()">🔗 Share Product</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
contentDiv.innerHTML = html;
|
|
}
|
|
|
|
// Show product by code (from URL)
|
|
function showProductByCode(productCode) {
|
|
const product = productData.find(p => p.productCode === productCode);
|
|
if (product) {
|
|
showProductDetail(productCode);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 2. JSON Structure Changes (`data/navigation.json`)
|
|
|
|
### Add Conditional Metadata to Questions
|
|
|
|
Questions that might be skipped need a `conditional` property:
|
|
|
|
```json
|
|
{
|
|
"q-material-storm-door": {
|
|
"type": "question",
|
|
"inputType": "button",
|
|
"title": "Select Material",
|
|
"subtitle": "Choose your preferred material",
|
|
"conditional": {
|
|
"type": "material",
|
|
"fallbackNext": "q-dimensions",
|
|
"autoApply": true
|
|
},
|
|
"answers": [
|
|
{
|
|
"caption": "Aluminum",
|
|
"next": "q-color",
|
|
"filter": { "material": "Aluminum" }
|
|
},
|
|
{
|
|
"caption": "Vinyl",
|
|
"next": "q-color",
|
|
"filter": { "material": "Vinyl" }
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
### Add Filter Properties to Answers
|
|
|
|
Each answer should include filter criteria:
|
|
|
|
```json
|
|
{
|
|
"caption": "Storm Door",
|
|
"image": "🚪",
|
|
"next": "q-material-storm-door",
|
|
"filter": {
|
|
"baseType": "Door",
|
|
"subType": "Storm Door"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 3. CSS Changes (`css/styles.css`)
|
|
|
|
Add styles for product grid:
|
|
|
|
```css
|
|
.products-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
gap: 20px;
|
|
margin: 20px 0;
|
|
padding: 20px;
|
|
}
|
|
|
|
.product-card {
|
|
background: white;
|
|
border: 1px solid #ddd;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.product-card h3 {
|
|
margin-top: 0;
|
|
color: #333;
|
|
font-size: 1.1em;
|
|
}
|
|
|
|
.product-card p {
|
|
margin: 10px 0;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.product-card ul {
|
|
list-style-position: inside;
|
|
padding-left: 0;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Python Changes (OPTIONAL)
|
|
|
|
If you want server-side filtering:
|
|
|
|
```python
|
|
# Add to app.py
|
|
import json
|
|
import csv
|
|
|
|
@app.route('/api/filter-products', methods=['POST'])
|
|
def filter_products():
|
|
"""Filter products based on criteria"""
|
|
filters = request.json
|
|
|
|
# Load products
|
|
with open('data/products.json', 'r') as f:
|
|
products = json.load(f)
|
|
|
|
# Apply filters
|
|
filtered = []
|
|
for product in products:
|
|
if filters.get('baseType') and product['baseType'] != filters['baseType']:
|
|
continue
|
|
if filters.get('subType'):
|
|
sub = product['subType'].get('door') or product['subType'].get('window')
|
|
if sub != filters['subType']:
|
|
continue
|
|
if filters.get('material') and filters['material'] not in product['materials']:
|
|
continue
|
|
if filters.get('color') and filters['color'] not in product['colors']:
|
|
continue
|
|
|
|
filtered.append(product)
|
|
|
|
return jsonify({
|
|
'status': 'success',
|
|
'count': len(filtered),
|
|
'products': filtered
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## 5. CSV Parsing Script Changes
|
|
|
|
Create a Python script to generate the JSON files:
|
|
|
|
```python
|
|
# create_json_files.py
|
|
import csv
|
|
import json
|
|
from collections import defaultdict
|
|
|
|
def parse_products_csv(csv_path):
|
|
"""Parse products.csv and generate three JSON files"""
|
|
|
|
products = []
|
|
accessories = []
|
|
nav_data = {
|
|
"start": {
|
|
"type": "question",
|
|
"inputType": "button",
|
|
"title": "What are you looking for?",
|
|
"subtitle": "Select the product category",
|
|
"answers": []
|
|
}
|
|
}
|
|
|
|
# Track unique values for navigation
|
|
base_types = set()
|
|
door_subtypes = defaultdict(int) # Track material diversity per subtype
|
|
window_subtypes = defaultdict(int)
|
|
|
|
with open(csv_path, 'r', encoding='utf-8') as f:
|
|
reader = csv.DictReader(f, skipinitialspace=True)
|
|
|
|
for row in reader:
|
|
# Skip discontinued
|
|
if row['DISCONT'].upper() == 'TRUE':
|
|
continue
|
|
|
|
# Parse materials
|
|
materials = []
|
|
if row['Aluminum'].upper() == 'TRUE':
|
|
materials.append('Aluminum')
|
|
if row['Vinyl'].upper() == 'TRUE':
|
|
materials.append('Vinyl')
|
|
|
|
# Parse colors
|
|
colors = []
|
|
for color in ['Black', 'White', 'Bronze', 'Tan', 'Mill', 'Sandstone']:
|
|
if row.get(color, '').upper() == 'TRUE':
|
|
colors.append(color)
|
|
|
|
# Determine if accessory
|
|
is_accessory = row['Accessory\\nYes'].upper() == 'TRUE'
|
|
|
|
# Build sub-type object
|
|
sub_type = {
|
|
"door": row['Sub-type\\nDoor'].strip() if row['Sub-type\\nDoor'] else None,
|
|
"window": row['Sub-type\\nWindow'].strip() if row['Sub-type\\nWindow'] else None
|
|
}
|
|
|
|
# Common data
|
|
item_data = {
|
|
"id": row['PROD_CODE'],
|
|
"productCode": row['PROD_CODE'],
|
|
"category": row['CATEGORY'],
|
|
"description": row['DESCRIPTION'],
|
|
"discontinued": False,
|
|
"location": row['LOC_CODE'],
|
|
"baseType": row['Base\\nType'].strip() if row['Base\\nType'] else None,
|
|
"subType": sub_type,
|
|
"materials": materials,
|
|
"colors": colors
|
|
}
|
|
|
|
if is_accessory:
|
|
# Add to accessories
|
|
accessories.append({
|
|
**item_data,
|
|
"compatibilityRules": {
|
|
"type": "subType",
|
|
"subTypeDoor": sub_type['door'],
|
|
"subTypeWindow": sub_type['window'],
|
|
# ... more rules
|
|
}
|
|
})
|
|
else:
|
|
# Add to products
|
|
products.append({
|
|
**item_data,
|
|
"isAccessory": False,
|
|
"compatibleAccessories": []
|
|
})
|
|
|
|
# Track for navigation
|
|
if item_data['baseType']:
|
|
base_types.add(item_data['baseType'])
|
|
|
|
# Build navigation structure
|
|
# ... (logic to create navigation.json based on analysis)
|
|
|
|
# Write JSON files
|
|
with open('data/products.json', 'w') as f:
|
|
json.dump(products, f, indent=2)
|
|
|
|
with open('data/accessories.json', 'w') as f:
|
|
json.dump(accessories, f, indent=2)
|
|
|
|
with open('data/navigation.json', 'w') as f:
|
|
json.dump(nav_data, f, indent=2)
|
|
|
|
if __name__ == '__main__':
|
|
parse_products_csv('data/products.csv')
|
|
print("JSON files generated successfully!")
|
|
```
|
|
|
|
---
|
|
|
|
## Implementation Checklist
|
|
|
|
- [ ] Update `js/script.js` with conditional navigation logic
|
|
- [ ] Create parsing script to generate JSON files from CSV
|
|
- [ ] Run parsing script to create `navigation.json`, `products.json`, `accessories.json`
|
|
- [ ] Update CSS for product display
|
|
- [ ] Test navigation flow with different product types
|
|
- [ ] Verify material questions are skipped when appropriate
|
|
- [ ] Verify color questions are skipped when appropriate
|
|
- [ ] Test product filtering and results display
|
|
- [ ] Add accessory display on product pages
|
|
|
|
---
|
|
|
|
## Testing Scenarios
|
|
|
|
1. **Storm Door Selection**: Should skip material question (all Aluminum)
|
|
2. **Window Selection**: Should show material question (mixed materials)
|
|
3. **Single Color Products**: Should skip color question
|
|
4. **Multi-Color Products**: Should show color question
|
|
5. **Product Results**: Should show filtered products with compatible accessories
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
**CSV**: No changes needed (unless adding bit values)
|
|
**JavaScript**: Major refactoring for conditional logic
|
|
**JSON**: New structure with filter objects and conditional flags
|
|
**Python**: Optional parsing script to generate JSON from CSV
|