Files
CGW-Quote-Builder/app/templates/product_manager.html
T
Jason 29154bd651 Editor Support
Co-authored-by: Copilot <copilot@github.com>
2026-05-05 15:42:30 -05:00

763 lines
28 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Product Manager - CGW</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
background: white;
border-radius: 10px;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
padding: 40px;
}
h1 {
color: #333;
margin-bottom: 10px;
font-size: 28px;
}
.subtitle {
color: #666;
margin-bottom: 30px;
font-size: 14px;
}
.form-section {
margin-bottom: 30px;
padding-bottom: 30px;
border-bottom: 1px solid #e0e0e0;
}
.form-section:last-child {
border-bottom: none;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 15px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
color: #555;
font-weight: 500;
font-size: 14px;
}
label .optional {
color: #999;
font-weight: normal;
font-size: 12px;
}
input[type="text"],
select,
textarea {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
font-family: inherit;
transition: border-color 0.3s;
}
input[type="text"]:focus,
select:focus,
textarea:focus {
outline: none;
border-color: #667eea;
}
textarea {
resize: vertical;
min-height: 80px;
}
.note {
background: #fff9e6;
border-left: 4px solid #ffd700;
padding: 10px 15px;
margin-bottom: 15px;
font-size: 13px;
color: #666;
}
.product-codes-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 15px;
margin-top: 15px;
}
.type-group {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.radio-group {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-top: 10px;
}
.radio-option {
display: flex;
align-items: center;
gap: 5px;
}
.radio-option input[type="radio"] {
width: auto;
cursor: pointer;
}
.radio-option label {
margin: 0;
cursor: pointer;
font-weight: normal;
}
.dynamic-section {
background: #f9f9f9;
padding: 15px;
border-radius: 5px;
margin-top: 10px;
}
.dynamic-section.hidden {
display: none;
}
.button-group {
display: flex;
gap: 10px;
margin-top: 30px;
}
button {
padding: 12px 30px;
border: none;
border-radius: 5px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5568d3;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
}
.btn-secondary {
background: #e0e0e0;
color: #333;
}
.btn-secondary:hover {
background: #d0d0d0;
}
.btn-danger {
background: #ff4757;
color: white;
}
.btn-danger:hover {
background: #e84052;
}
.nav-links {
margin-bottom: 20px;
}
.nav-links a {
color: #667eea;
text-decoration: none;
margin-right: 15px;
font-size: 14px;
}
.nav-links a:hover {
text-decoration: underline;
}
.status-indicator {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 5px;
}
.status-default {
background: #4caf50;
}
.status-discontinued {
background: #ff4757;
}
.status-hidden {
background: #ffa502;
}
@media (max-width: 768px) {
.container {
padding: 20px;
}
.type-group {
grid-template-columns: 1fr;
}
.product-codes-grid {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 480px) {
.product-codes-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<div class="nav-links">
<a href="/">← Back to Home</a>
<a href="/quiz/">Product Finder</a>
</div>
<h1>Product Manager</h1>
<p class="subtitle">Add or edit product information</p>
<form id="productForm">
<!-- Product Location & Description -->
<div class="form-section">
<div class="form-group">
<label for="location">Product Location:</label>
<select id="location" name="location" required>
<option value="">Select location...</option>
</select>
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea id="description" name="description" placeholder="Enter product description"></textarea>
</div>
<div class="form-group">
<label for="status">Status</label>
<select id="status" name="status">
<option value="default">Default</option>
<option value="discontinued">Discontinued</option>
<option value="hidden">Hide</option>
</select>
</div>
</div>
<!-- Product Codes -->
<div class="form-section">
<div class="section-title">Product Codes</div>
<div class="form-group">
<label for="standardCode">Standardized Code <span class="optional">(auto-generated from selections below)</span></label>
<input type="text" id="standardCode" name="standardCode" placeholder="Select type and modifiers to generate" readonly style="background-color: #f5f5f5;">
</div>
<div class="form-group">
<label for="baseCode">Base Code <span class="optional">(optional - e.g., 2000, 3000)</span></label>
<input type="text" id="baseCode" name="baseCode" placeholder="Enter base product code">
</div>
<div class="product-codes-grid" id="locationCodesContainer">
<!-- Dynamic location code fields will be inserted here based on user's accessible locations -->
</div>
</div>
<!-- Product Type -->
<div class="form-section">
<div class="section-title">Product Type</div>
<div class="type-group">
<div class="form-group">
<label for="productType">Type</label>
<select id="productType" name="productType">
<option value="">Select type...</option>
</select>
</div>
<div class="form-group">
<label for="productGroup">Group [Category]</label>
<input type="text" id="productGroup" name="productGroup" placeholder="" readonly>
</div>
</div>
</div>
<!-- Code Prefixes -->
<div class="form-section dynamic-section hidden" id="prefixesSection">
<div class="section-title">Code Prefixes</div>
<div class="radio-group" id="prefixesContainer">
<!-- Dynamic prefix options will be inserted here -->
</div>
</div>
<!-- Code Suffixes -->
<div class="form-section dynamic-section hidden" id="suffixesSection">
<div class="section-title">Code Suffixes</div>
<div style="margin-bottom: 10px; font-size: 13px; color: #666;">Select all that apply (multiple selections allowed)</div>
<div class="radio-group" id="suffixesContainer">
<!-- Dynamic suffix options will be inserted here -->
</div>
</div>
<!-- Materials -->
<div class="form-section dynamic-section hidden" id="materialsSection">
<div class="section-title">Materials</div>
<div class="radio-group" id="materialsContainer">
<!-- Dynamic material options will be inserted here -->
</div>
</div>
<!-- Colors -->
<div class="form-section dynamic-section hidden" id="colorsSection">
<div class="section-title">Colors</div>
<div class="radio-group" id="colorsContainer">
<!-- Dynamic color options will be inserted here -->
</div>
</div>
<!-- Additional Options -->
<div class="form-section dynamic-section hidden" id="additionalOptionsSection">
<div class="section-title">Additional Options</div>
<div class="radio-group" id="additionalOptionsContainer">
<!-- Dynamic additional options will be inserted here -->
</div>
</div>
<!-- Action Buttons -->
<div class="button-group">
<button type="submit" class="btn-primary">Save Product</button>
<button type="button" class="btn-secondary" onclick="resetForm()">Clear Form</button>
<button type="button" class="btn-danger" onclick="deleteProduct()">Delete Product</button>
</div>
</form>
</div>
<script>
let productAttributes = null;
let userAccessibleLocations = [];
// Load product attributes and user locations
async function loadProductAttributes() {
try {
const response = await fetch('/quiz/data/product_attributes.json');
productAttributes = await response.json();
// Get user's accessible locations from session
await fetchUserLocations();
initializeForm();
} catch (error) {
console.error('Error loading product attributes:', error);
alert('Failed to load product attributes');
}
}
// Fetch user's accessible locations from session
async function fetchUserLocations() {
try {
const response = await fetch('/quiz/api/user-session');
const userData = await response.json();
userAccessibleLocations = userData.accessibleLocations || [];
// If user has no accessible locations, default to their current location
if (userAccessibleLocations.length === 0 && userData.currentLocation) {
userAccessibleLocations = [userData.currentLocation];
}
} catch (error) {
console.error('Error fetching user locations:', error);
// Fallback to all location codes if API fails
userAccessibleLocations = productAttributes.locations.map(loc => loc.code);
}
}
// Initialize form with data
function initializeForm() {
// Populate locations based on user's access
// Match user's accessible location codes with full location details
const locationSelect = document.getElementById('location');
productAttributes.locations.forEach(loc => {
// Check if user has access to this location code
if (userAccessibleLocations.includes(loc.code)) {
const option = document.createElement('option');
option.value = loc.code;
option.textContent = loc.name;
locationSelect.appendChild(option);
}
});
// Initialize location code fields with user's accessible locations
updateLocationCodes();
// Populate product types
const typeSelect = document.getElementById('productType');
productAttributes.productTypes.forEach(type => {
const option = document.createElement('option');
option.value = type.id;
option.textContent = type.type;
option.dataset.category = type.category;
option.dataset.basetype = type.baseType;
option.dataset.materials = JSON.stringify(type.availableMaterials);
option.dataset.colors = JSON.stringify(type.availableColors);
option.dataset.options = JSON.stringify(type.additionalOptions);
typeSelect.appendChild(option);
});
// Setup event listeners
document.getElementById('productType').addEventListener('change', updateDynamicSections);
document.getElementById('baseCode').addEventListener('input', updateStandardizedCode);
}
// Update location code fields based on user's accessible locations
function updateLocationCodes() {
const container = document.getElementById('locationCodesContainer');
container.innerHTML = '';
// Get full location details for each accessible code
userAccessibleLocations.forEach(locCode => {
const locationDetails = productAttributes.locations.find(loc => loc.code === locCode);
if (locationDetails) {
const div = document.createElement('div');
div.className = 'form-group';
div.innerHTML = `
<label for="code_${locCode}">${locationDetails.name} code</label>
<input type="text" id="code_${locCode}" name="code_${locCode}" placeholder="">
`;
container.appendChild(div);
}
});
}
// Update dynamic sections based on product type
function updateDynamicSections() {
const typeSelect = document.getElementById('productType');
const selectedOption = typeSelect.options[typeSelect.selectedIndex];
if (!selectedOption.value) {
hideAllDynamicSections();
return;
}
// Update category
document.getElementById('productGroup').value = selectedOption.dataset.category || '';
// Get the baseType from the selected option
const baseType = selectedOption.dataset.basetype || '';
// Update code prefixes
if (baseType && productAttributes.codeModifiers && productAttributes.codeModifiers.prefixes) {
const applicablePrefixes = productAttributes.codeModifiers.prefixes.filter(
p => p.appliesTo.includes(baseType)
);
if (applicablePrefixes.length > 0) {
showPrefixesSection(applicablePrefixes);
} else {
hidePrefixesSection();
}
} else {
hidePrefixesSection();
}
// Update code suffixes
if (baseType && productAttributes.codeModifiers && productAttributes.codeModifiers.suffixes) {
const applicableSuffixes = productAttributes.codeModifiers.suffixes.filter(
s => s.appliesTo.includes(baseType)
);
if (applicableSuffixes.length > 0) {
showSuffixesSection(applicableSuffixes);
} else {
hideSuffixesSection();
}
} else {
hideSuffixesSection();
}
// Update materials
const materials = JSON.parse(selectedOption.dataset.materials || '[]');
if (materials.length > 0) {
showMaterialsSection(materials);
} else {
hideMaterialsSection();
}
// Update colors
const colors = JSON.parse(selectedOption.dataset.colors || '[]');
if (colors.length > 0) {
showColorsSection(colors);
} else {
hideColorsSection();
}
// Update additional options
const options = JSON.parse(selectedOption.dataset.options || '[]');
if (options.length > 0) {
showAdditionalOptionsSection(options);
} else {
hideAdditionalOptionsSection();
}
}
// Show/hide dynamic sections
function showMaterialsSection(materials) {
const section = document.getElementById('materialsSection');
const container = document.getElementById('materialsContainer');
container.innerHTML = '';
materials.forEach((material, index) => {
const div = document.createElement('div');
div.className = 'radio-option';
div.innerHTML = `
<input type="radio" id="material_${index}" name="material" value="${material}" ${index === 0 ? 'checked' : ''}>
<label for="material_${index}">${material}</label>
`;
container.appendChild(div);
});
section.classList.remove('hidden');
}
function hideMaterialsSection() {
document.getElementById('materialsSection').classList.add('hidden');
}
function showColorsSection(colors) {
const section = document.getElementById('colorsSection');
const container = document.getElementById('colorsContainer');
container.innerHTML = '';
colors.forEach((color, index) => {
const div = document.createElement('div');
div.className = 'radio-option';
div.innerHTML = `
<input type="radio" id="color_${index}" name="color" value="${color}" ${index === 0 ? 'checked' : ''}>
<label for="color_${index}">${color}</label>
`;
container.appendChild(div);
});
section.classList.remove('hidden');
}
function hideColorsSection() {
document.getElementById('colorsSection').classList.add('hidden');
}
function showAdditionalOptionsSection(options) {
const section = document.getElementById('additionalOptionsSection');
const container = document.getElementById('additionalOptionsContainer');
container.innerHTML = '';
options.forEach((option, index) => {
const div = document.createElement('div');
div.className = 'radio-option';
div.innerHTML = `
<input type="radio" id="option_${index}" name="additionalOption" value="${option}" ${index === 0 ? 'checked' : ''}>
<label for="option_${index}">${option}</label>
`;
container.appendChild(div);
});
section.classList.remove('hidden');
}
function hideAdditionalOptionsSection() {
document.getElementById('additionalOptionsSection').classList.add('hidden');
}
function showPrefixesSection(prefixes) {
const section = document.getElementById('prefixesSection');
const container = document.getElementById('prefixesContainer');
container.innerHTML = '';
// Add "None" option first
const noneDiv = document.createElement('div');
noneDiv.className = 'radio-option';
noneDiv.innerHTML = `
<input type="radio" id="prefix_none" name="codePrefix" value="" checked onchange="updateStandardizedCode()">
<label for="prefix_none">None</label>
`;
container.appendChild(noneDiv);
prefixes.forEach((prefix, index) => {
const div = document.createElement('div');
div.className = 'radio-option';
div.innerHTML = `
<input type="radio" id="prefix_${index}" name="codePrefix" value="${prefix.value}" onchange="updateStandardizedCode()" title="${prefix.description}">
<label for="prefix_${index}">${prefix.label}</label>
`;
container.appendChild(div);
});
section.classList.remove('hidden');
}
function hidePrefixesSection() {
document.getElementById('prefixesSection').classList.add('hidden');
}
function showSuffixesSection(suffixes) {
const section = document.getElementById('suffixesSection');
const container = document.getElementById('suffixesContainer');
container.innerHTML = '';
suffixes.forEach((suffix, index) => {
const div = document.createElement('div');
div.className = 'radio-option';
div.innerHTML = `
<input type="checkbox" id="suffix_${index}" name="codeSuffix" value="${suffix.value}" onchange="updateStandardizedCode()" title="${suffix.description}">
<label for="suffix_${index}">${suffix.label}</label>
`;
container.appendChild(div);
});
section.classList.remove('hidden');
}
function hideSuffixesSection() {
document.getElementById('suffixesSection').classList.add('hidden');
}
function updateStandardizedCode() {
const baseCode = document.getElementById('baseCode').value.trim();
// Get selected prefix
const prefixRadio = document.querySelector('input[name="codePrefix"]:checked');
const prefix = prefixRadio ? prefixRadio.value : '';
// Get selected suffixes (checkboxes)
const suffixCheckboxes = document.querySelectorAll('input[name="codeSuffix"]:checked');
const suffixes = Array.from(suffixCheckboxes).map(cb => cb.value);
// Build the standardized code
let standardizedCode = '';
if (baseCode) {
standardizedCode = prefix + baseCode;
if (suffixes.length > 0) {
standardizedCode += suffixes.join('');
}
}
document.getElementById('standardCode').value = standardizedCode;
}
function hideAllDynamicSections() {
document.getElementById('prefixesSection').classList.add('hidden');
document.getElementById('suffixesSection').classList.add('hidden');
document.getElementById('materialsSection').classList.add('hidden');
document.getElementById('colorsSection').classList.add('hidden');
document.getElementById('additionalOptionsSection').classList.add('hidden');
document.getElementById('additionalOptionsSection').classList.add('hidden');
document.getElementById('productGroup').value = '';
}
// Form submission
document.getElementById('productForm').addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(e.target);
// Get selected prefix
const selectedPrefix = document.querySelector('input[name="codePrefix"]:checked');
const codePrefix = selectedPrefix ? selectedPrefix.value : '';
// Get selected suffixes
const selectedSuffixes = Array.from(document.querySelectorAll('input[name="codeSuffix"]:checked'))
.map(cb => cb.value);
const productData = {
location: formData.get('location'),
description: formData.get('description'),
status: formData.get('status'),
baseCode: formData.get('baseCode'),
standardCode: formData.get('standardCode'),
codePrefix: codePrefix,
codeSuffixes: selectedSuffixes,
locationCodes: {},
productType: formData.get('productType'),
category: formData.get('productGroup'),
material: formData.get('material'),
color: formData.get('color'),
additionalOption: formData.get('additionalOption')
};
// Collect location codes for user's accessible locations
userAccessibleLocations.forEach(locCode => {
const code = formData.get(`code_${locCode}`);
if (code) {
productData.locationCodes[locCode] = code;
}
});
console.log('Product Data:', productData);
// Here you would send the data to your backend
// For now, just show an alert
alert('Product saved! (This is a demo - in production, this would save to the database)');
});
// Utility functions
function resetForm() {
document.getElementById('productForm').reset();
hideAllDynamicSections();
}
function deleteProduct() {
if (confirm('Are you sure you want to delete this product?')) {
alert('Product deleted! (This is a demo)');
resetForm();
}
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', loadProductAttributes);
</script>
</body>
</html>