Initial
This commit is contained in:
@@ -0,0 +1,791 @@
|
||||
# Required Code Changes for Conditional Navigation
|
||||
|
||||
## Overview
|
||||
To support conditional material/color questions and dynamic navigation, the following changes are needed in your application code.
|
||||
|
||||
---
|
||||
|
||||
## 1. JavaScript Changes (`js/script.js`)
|
||||
|
||||
### Current State
|
||||
- Loads `questions.json` statically
|
||||
- Has hardcoded conditional for `q-material-conditional`
|
||||
- No product data loading
|
||||
|
||||
### Required Changes
|
||||
|
||||
#### A. Load Multiple Data Files
|
||||
```javascript
|
||||
// At the top of script.js - modify loadQuestionData() to load both files
|
||||
let questionData = {};
|
||||
let productData = [];
|
||||
let accessoryData = [];
|
||||
let bitwiseData = {};
|
||||
let accumulatedBitValue = 0;
|
||||
|
||||
function init() {
|
||||
// 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
|
||||
Reference in New Issue
Block a user