a6056f69ca
Co-authored-by: Copilot <copilot@github.com>
389 lines
16 KiB
HTML
389 lines
16 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||
<meta http-equiv="Pragma" content="no-cache">
|
||
<meta http-equiv="Expires" content="0">
|
||
<title>Advanced Search - Product Finder</title>
|
||
<link rel="stylesheet" href="{{ url_for('products.serve_css', filename='styles.css') }}">
|
||
<style>
|
||
.search-container {
|
||
padding: 30px 40px;
|
||
}
|
||
|
||
.search-header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.search-input-container {
|
||
max-width: 800px;
|
||
margin: 0 auto;
|
||
text-align: center;
|
||
}
|
||
|
||
.products-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||
gap: 20px;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.product-card {
|
||
background: white;
|
||
border: 2px solid #e0e0e0;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.product-card:hover {
|
||
border-color: #6a4c93;
|
||
box-shadow: 0 4px 12px rgba(106, 76, 147, 0.15);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.product-card h3 {
|
||
color: #333;
|
||
font-size: 1.1em;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.no-results {
|
||
text-align: center;
|
||
padding: 40px;
|
||
color: #666;
|
||
}
|
||
|
||
.search-stats {
|
||
text-align: center;
|
||
padding: 15px;
|
||
background: #f5f5f5;
|
||
border-radius: 8px;
|
||
margin-bottom: 20px;
|
||
color: #666;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h1>Advanced Search</h1>
|
||
</div>
|
||
|
||
<div class="user-info-bar" id="userInfoBar">
|
||
<span id="userLocationInfo"></span>
|
||
<a href="{{ url_for('auth.logout') }}" class="logout-btn" title="Logout"
|
||
onclick="return confirm('Are you sure you want to log out?');"
|
||
style="background: none; border: none; cursor: pointer; font-size: 16px; text-decoration: none; display: inline-block; padding: 8px 12px;">🚪</a>
|
||
</div>
|
||
|
||
<div class="breadcrumb">
|
||
Start › Advanced Search
|
||
</div>
|
||
|
||
<div id="content">
|
||
<div class="search-container">
|
||
<div class="search-header">
|
||
<h2 style="margin-bottom: 15px;">🔍 Search Products</h2>
|
||
<p style="color: #666; margin-bottom: 20px;">
|
||
Search by product code, description, color, material, or any combination.
|
||
Try: "c1800", "storm door white", "bronze aluminum"
|
||
</p>
|
||
</div>
|
||
|
||
<div class="search-input-container">
|
||
<input
|
||
type="text"
|
||
id="searchInput"
|
||
placeholder="Enter search terms..."
|
||
style="width: 100%; padding: 15px 20px; font-size: 16px; border: 2px solid #ddd; border-radius: 8px; box-sizing: border-box;"
|
||
onkeyup="handleSearch(event)"
|
||
>
|
||
<button
|
||
onclick="performSearch()"
|
||
style="margin-top: 15px; padding: 15px 30px; font-size: 16px; background-color: #6a4c93; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 600;"
|
||
>
|
||
Search
|
||
</button>
|
||
<button
|
||
onclick="clearSearch()"
|
||
style="margin-top: 15px; margin-left: 10px; padding: 15px 30px; font-size: 16px; background-color: #ccc; color: #333; border: none; border-radius: 8px; cursor: pointer; font-weight: 600;"
|
||
>
|
||
Clear
|
||
</button>
|
||
</div>
|
||
|
||
<div id="searchResults" style="margin-top: 30px;"></div>
|
||
|
||
<div class="result-actions" style="margin-top: 30px;">
|
||
<button class="back-button" onclick="goToFinder()">🏠 Back to Product Finder</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Product data
|
||
let productData = [];
|
||
|
||
// Load product data on page load
|
||
async function loadProductData() {
|
||
try {
|
||
const response = await fetch('/quiz/data/products.json');
|
||
productData = await response.json();
|
||
console.log('Loaded', productData.length, 'products');
|
||
} catch (error) {
|
||
console.error('Error loading product data:', error);
|
||
document.getElementById('searchResults').innerHTML = `
|
||
<div class="no-results">
|
||
<p style="color: #d32f2f;">⚠️ Error loading product data. Please refresh the page.</p>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
// Normalize text for matching (remove spaces, hyphens, special chars, lowercase)
|
||
function normalizeText(text) {
|
||
if (!text) return '';
|
||
return text.toString().toLowerCase()
|
||
.replace(/[-\s_]/g, '') // Remove hyphens, spaces, underscores
|
||
.replace(/[^a-z0-9]/g, ''); // Remove other special characters
|
||
}
|
||
|
||
// Check if a value matches a search term (partial match)
|
||
function matchesTerm(value, term) {
|
||
if (!value) return false;
|
||
|
||
// For arrays (like colors, materials)
|
||
if (Array.isArray(value)) {
|
||
return value.some(item => normalizeText(item).includes(term));
|
||
}
|
||
|
||
// For objects (like subType)
|
||
if (typeof value === 'object' && value !== null) {
|
||
return Object.values(value).some(v =>
|
||
v && normalizeText(v).includes(term)
|
||
);
|
||
}
|
||
|
||
// For strings and numbers
|
||
return normalizeText(value).includes(term);
|
||
}
|
||
|
||
// Search products with partial matching
|
||
function searchProducts(searchQuery) {
|
||
if (!searchQuery || searchQuery.trim() === '') {
|
||
return [];
|
||
}
|
||
|
||
// Split search query into individual terms
|
||
const searchTerms = searchQuery.toLowerCase()
|
||
.split(/\s+/)
|
||
.filter(term => term.length > 0)
|
||
.map(term => normalizeText(term));
|
||
|
||
console.log('Search terms:', searchTerms);
|
||
|
||
// Filter products that match ALL search terms
|
||
const results = productData.filter(product => {
|
||
// Each product must match ALL search terms
|
||
return searchTerms.every(term => {
|
||
// Check if the term matches any of these fields
|
||
return (
|
||
matchesTerm(product.productCode, term) ||
|
||
matchesTerm(product.id, term) ||
|
||
matchesTerm(product.description, term) ||
|
||
matchesTerm(product.category, term) ||
|
||
matchesTerm(product.baseType, term) ||
|
||
matchesTerm(product.subType, term) ||
|
||
matchesTerm(product.materials, term) ||
|
||
matchesTerm(product.colors, term) ||
|
||
matchesTerm(product.location, term)
|
||
);
|
||
});
|
||
});
|
||
|
||
return results;
|
||
}
|
||
|
||
// Display search results
|
||
function displayResults(results, searchQuery) {
|
||
const resultsDiv = document.getElementById('searchResults');
|
||
|
||
if (results.length === 0) {
|
||
resultsDiv.innerHTML = `
|
||
<div class="no-results">
|
||
<h3 style="color: #333; margin-bottom: 10px;">No products found</h3>
|
||
<p>No products match "${searchQuery}"</p>
|
||
<p style="margin-top: 15px; font-size: 0.9em;">Try:</p>
|
||
<ul style="text-align: left; max-width: 400px; margin: 10px auto;">
|
||
<li>Fewer or more general terms</li>
|
||
<li>Different spellings (c1800, c-1800, c 1800)</li>
|
||
<li>Product categories (storm door, window, slider)</li>
|
||
<li>Materials (aluminum, vinyl) or colors (white, bronze)</li>
|
||
</ul>
|
||
</div>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
let html = `
|
||
<div class="search-stats">
|
||
Found <strong>${results.length}</strong> product${results.length !== 1 ? 's' : ''} matching "<strong>${searchQuery}</strong>"
|
||
</div>
|
||
<div class="products-grid">
|
||
`;
|
||
|
||
results.forEach(product => {
|
||
const prodCode = product.productCode || product.id;
|
||
const desc = product.description || 'No description';
|
||
const materials = product.materials && product.materials.length > 0
|
||
? product.materials.join(', ')
|
||
: 'N/A';
|
||
const colors = product.colors && product.colors.length > 0
|
||
? product.colors.join(', ')
|
||
: 'N/A';
|
||
const location = product.location || 'N/A';
|
||
const discontinued = product.discontinued ? ' <span style="color: #d32f2f;">(Discontinued)</span>' : '';
|
||
|
||
// Get subtype string
|
||
let subtypeStr = '';
|
||
if (product.subType) {
|
||
const doorType = product.subType.door;
|
||
const windowType = product.subType.window;
|
||
if (doorType) subtypeStr = doorType;
|
||
else if (windowType) subtypeStr = windowType;
|
||
}
|
||
|
||
html += `
|
||
<div class="product-card" onclick="viewProductDetail('${prodCode}')">
|
||
<h3>${desc}${discontinued}</h3>
|
||
<p style="color: #999; font-size: 0.95em; margin-top: 5px;"><strong>${prodCode}</strong></p>
|
||
<div style="font-size: 0.9em; color: #666; margin-top: 10px;">
|
||
${product.baseType ? `<strong>Type:</strong> ${product.baseType}<br>` : ''}
|
||
${subtypeStr ? `<strong>Subtype:</strong> ${subtypeStr}<br>` : ''}
|
||
<strong>Materials:</strong> ${materials}<br>
|
||
<strong>Colors:</strong> ${colors}<br>
|
||
<strong>Location:</strong> ${location}
|
||
</div>
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
html += '</div>';
|
||
resultsDiv.innerHTML = html;
|
||
}
|
||
|
||
// Perform search
|
||
function performSearch() {
|
||
const searchInput = document.getElementById('searchInput');
|
||
const searchQuery = searchInput.value.trim();
|
||
|
||
if (searchQuery === '') {
|
||
document.getElementById('searchResults').innerHTML = `
|
||
<div class="no-results">
|
||
<p>Please enter a search term</p>
|
||
</div>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
const results = searchProducts(searchQuery);
|
||
displayResults(results, searchQuery);
|
||
}
|
||
|
||
// Handle Enter key in search input
|
||
function handleSearch(event) {
|
||
if (event.key === 'Enter') {
|
||
performSearch();
|
||
}
|
||
}
|
||
|
||
// Clear search
|
||
function clearSearch() {
|
||
document.getElementById('searchInput').value = '';
|
||
document.getElementById('searchResults').innerHTML = '';
|
||
document.getElementById('searchInput').focus();
|
||
}
|
||
|
||
// View product detail (redirect to product finder with product code)
|
||
function viewProductDetail(productCode) {
|
||
window.location.href = `/quiz/?p=${productCode}`;
|
||
}
|
||
|
||
// Load user session info
|
||
async function loadUserInfo() {
|
||
try {
|
||
const response = await fetch('/api/session');
|
||
const data = await response.json();
|
||
|
||
if (data.status === 'success') {
|
||
const locationNames = {
|
||
'LINDS': 'Lindsborg',
|
||
'IOLA': 'Iola',
|
||
'KC': 'Kansas City',
|
||
'BMD': 'BMD'
|
||
};
|
||
|
||
const currentLoc = locationNames[data.currentLocation] || data.currentLocation;
|
||
const canChangeLocation = data.accessibleLocations && data.accessibleLocations.length > 1;
|
||
|
||
let locationDisplay;
|
||
if (canChangeLocation) {
|
||
locationDisplay = `<a href="{{ url_for('auth.select_location_page') }}" style="color: #6a4c93; text-decoration: none; font-weight: 500; cursor: pointer;"
|
||
onmouseover="this.style.textDecoration='underline'"
|
||
onmouseout="this.style.textDecoration='none'"
|
||
title="Click to change location">📍 ${currentLoc}</a>`;
|
||
} else {
|
||
locationDisplay = `📍 ${currentLoc}`;
|
||
}
|
||
|
||
document.getElementById('userLocationInfo').innerHTML =
|
||
`👤 ${data.username} | ${locationDisplay}`;
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading user info:', error);
|
||
}
|
||
}
|
||
|
||
// Navigation functions
|
||
function goBack() {
|
||
window.history.back();
|
||
}
|
||
|
||
function goToFinder() {
|
||
window.location.href = '{{ url_for("products.quiz_index") }}';
|
||
}
|
||
|
||
// Initialize page
|
||
async function init() {
|
||
await loadUserInfo();
|
||
await loadProductData();
|
||
|
||
// Check if there's a search query in URL
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const queryParam = urlParams.get('q');
|
||
|
||
if (queryParam) {
|
||
// Populate search input with the query
|
||
const searchInput = document.getElementById('searchInput');
|
||
searchInput.value = queryParam;
|
||
|
||
// Automatically perform the search
|
||
performSearch();
|
||
}
|
||
|
||
// Focus on search input
|
||
document.getElementById('searchInput').focus();
|
||
}
|
||
|
||
// Load when page loads
|
||
init();
|
||
</script>
|
||
</body>
|
||
</html>
|