// Form validation and submission handler const form = document.getElementById('contactForm'); const emailInput = document.getElementById('email'); const successMessage = document.getElementById('successMessage'); form.addEventListener('submit', function(e) { e.preventDefault(); // Reset error states clearErrors(); // Validate inputs let isValid = true; if (!emailInput.value.trim() || !isValidEmail(emailInput.value)) { showError('email', 'Please enter a valid email address'); isValid = false; } if (isValid) { // Collect form data const formData = { email: emailInput.value.trim() }; // Include BoM if checkbox is checked const includeBomCheckbox = document.getElementById('includeBom'); if (includeBomCheckbox.checked && bom.size > 0) { formData.bom = Array.from(bom.values()); } // Call the backend function (stubbed) sendEmailToBackend(formData); } }); function showError(fieldId, message) { const input = document.getElementById(fieldId); const errorDiv = document.getElementById(fieldId + 'Error'); input.classList.add('error'); errorDiv.textContent = message; errorDiv.style.display = 'block'; } function clearErrors() { emailInput.classList.remove('error'); const errorMessages = document.querySelectorAll('.error-message'); errorMessages.forEach(msg => { msg.style.display = 'none'; }); successMessage.style.display = 'none'; } function isValidEmail(email) { // Basic email validation const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } function sendEmailToBackend(formData) { try { var emailField = document.querySelector('input[name="form_fields[email]"]'); var messageField = document.querySelector('textarea[name="form_fields[message]"]'); var submitBtn = document.querySelector('.elementor-form button[type="submit"]'); if (!emailField || !messageField || !submitBtn) { alert('Email form not found. Please contact info@radiationteam.com directly.'); return; } var message = ''; if (formData.bom && formData.bom.length > 0) { message += 'BILL OF MATERIALS (' + formData.bom.length + ' items)\n'; message += '----------------------------------------\n'; for (var i = 0; i < formData.bom.length; i++) { var item = formData.bom[i]; var line = ' ' + (i + 1) + '. ' + item.part_number; while (line.length < 30) line += ' '; line += (item.manufacturer || ''); if (item.test_data) { while (line.length < 50) line += ' '; line += 'Tests: ' + item.test_data; } message += line + '\n'; } message += '\n'; } if (searchHistory.length > 0) { message += 'SEARCH HISTORY\n'; message += '----------------------------------------\n'; for (var j = 0; j < searchHistory.length; j++) { var rec = searchHistory[j]; var entry = ' ' + (j + 1) + '. '; if (rec.query) entry += '"' + rec.query + '"'; else entry += '(browse)'; if (rec.category) { entry += ' | ' + rec.category; if (rec.subcategory) entry += ' > ' + rec.subcategory; } message += entry + '\n'; } } if (!message) message = '(No BoM or search history included)'; emailField.value = formData.email; messageField.value = message; submitBtn.click(); showSuccess(); } catch (err) { alert('Unable to send. Please contact info@radiationteam.com directly.'); } } // STUB: This function should be replaced with your actual backend call //function sendEmailToBackend(formData) { // console.log('Sending email with data:', formData); // if (formData.bom && formData.bom.length > 0) { // console.log(`BoM included with ${formData.bom.length} items:`, formData.bom); // } // Simulate API call // Replace this with your actual backend call, e.g.: // fetch('/api/send-email', { // method: 'POST', // headers: { 'Content-Type': 'application/json' }, // body: JSON.stringify(formData) // }) // .then(response => response.json()) // .then(data => { // showSuccess(); // }) // .catch(error => { // console.error('Error:', error); // alert('An error occurred. Please try again.'); // }); // For now, just show success message // showSuccess(); //} function showSuccess() { successMessage.style.display = 'block'; form.reset(); // Hide success message after 5 seconds setTimeout(() => { successMessage.style.display = 'none'; }, 5000); } // Global state let allParts = []; let filteredParts = []; let currentPage = 1; const itemsPerPage = 25; let selectedCategory = null; let selectedSubcategory = null; let activeFilters = {}; let searchQuery = ''; let bom = new Map(); // Map of part_number -> part object // Local search history let searchHistory = []; // Parse hierarchy column into main_category and subcategory // Takes last 2 levels of pipe-delimited hierarchy function parseHierarchy(hierarchy) { if (!hierarchy || hierarchy.trim() === '') { return { main_category: 'Uncategorized', subcategory: '' }; } const levels = hierarchy.split('|').map(level => level.trim()); if (levels.length === 1) { return { main_category: levels[0], subcategory: '' }; } else if (levels.length === 2) { return { main_category: levels[0], subcategory: levels[1] }; } else { // Take last 2 levels return { main_category: levels[levels.length - 2], subcategory: levels[levels.length - 1] }; } } // BoM Management Functions function loadBomFromStorage() { try { const bomData = localStorage.getItem('partsBrowserBom'); if (bomData) { const bomArray = JSON.parse(bomData); bom = new Map(bomArray.map(part => [part.part_number, part])); } } catch (error) { console.error('Error loading BoM from storage:', error); } } function saveBomToStorage() { try { const bomArray = Array.from(bom.values()); localStorage.setItem('partsBrowserBom', JSON.stringify(bomArray)); } catch (error) { console.error('Error saving BoM to storage:', error); } } function addToBoM(part) { const key = `${part.part_number}|${part.manufacturer}`; bom.set(key, { part_number: part.part_number, manufacturer: part.manufacturer, subcategory: part.subcategory, main_category: part.main_category }); saveBomToStorage(); updateBomDisplay(); renderResults(); // Re-render to update checkboxes } function removeFromBoM(partNumber, manufacturer) { const key = `${partNumber}|${manufacturer}`; bom.delete(key); saveBomToStorage(); updateBomDisplay(); renderResults(); // Re-render to update checkboxes } function clearBoM() { if (bom.size === 0) return; if (confirm('Clear all items from your Parts List?')) { bom.clear(); saveBomToStorage(); updateBomDisplay(); renderResults(); } } function updateBomDisplay() { const bomToggleBtn = document.getElementById('bomToggleBtn'); const bomItems = document.getElementById('bomItems'); const bomPanel = document.getElementById('bomPanel'); // Update button text if (bom.size == 1) { bomToggleBtn.textContent = `🔽 Parts List (${bom.size} item)`; } else { bomToggleBtn.textContent = `🔽 Parts List (${bom.size} items)`; } // Render Parts List items if (bom.size === 0) { bomItems.innerHTML = '
No parts in Parts List. Check boxes in the results to add parts.
'; } else { bomItems.innerHTML = ''; Array.from(bom.values()).forEach(part => { const item = document.createElement('div'); item.className = 'bom-item'; item.innerHTML = `
${part.part_number}
${part.manufacturer} • ${part.subcategory}
`; bomItems.appendChild(item); }); } } function toggleBomPanel() { const bomPanel = document.getElementById('bomPanel'); const bomToggleBtn = document.getElementById('bomToggleBtn'); bomPanel.classList.toggle('show'); bomToggleBtn.classList.toggle('active'); } function togglePartInBom(partNumber, manufacturer) { const part = allParts.find(p => p.part_number === partNumber && p.manufacturer === manufacturer); if (!part) return; const key = `${partNumber}|${manufacturer}`; if (bom.has(key)) { removeFromBoM(partNumber, manufacturer); } else { addToBoM(part); } } // Initialize async function init() { try { loadBomFromStorage(); await loadCSV(); renderCategories(); renderResults(); setupEventListeners(); setupCollapsibleHeaders(); updateBomDisplay(); } catch (error) { document.getElementById('resultsContainer').innerHTML = '
Error loading data. Make sure asset URL is correct.
'; console.error('Error:', error); } } // Setup collapsible headers function setupCollapsibleHeaders() { const howToHeader = document.getElementById('howToHeader'); const howToContent = document.getElementById('howToContent'); const contactHeader = document.getElementById('contactHeader'); const contactContent = document.getElementById('contactContent'); howToHeader.onclick = () => { howToHeader.classList.toggle('active'); howToContent.classList.toggle('show'); }; contactHeader.onclick = () => { contactHeader.classList.toggle('active'); contactContent.classList.toggle('show'); }; // Sidebar collapsible sections const categoriesHeader = document.getElementById('categoriesHeader'); const categoriesContent = document.getElementById('categoriesContent'); categoriesHeader.onclick = () => { categoriesHeader.classList.toggle('collapsed'); categoriesContent.classList.toggle('collapsed'); }; const filtersHeader = document.getElementById('filtersHeader'); const filtersContent = document.getElementById('filtersContent'); filtersHeader.onclick = () => { filtersHeader.classList.toggle('collapsed'); filtersContent.classList.toggle('collapsed'); }; } // Load and parse CSV from external file async function loadCSV() { const response = await fetch("https://gisyhavx.elementor.cloud/wp-content/uploads/2026/02/parts-data.csv"); const text = await response.text(); // Handle both tab and comma delimiters const firstLine = text.split('\n')[0]; const delimiter = firstLine.includes('\t') ? '\t' : ','; console.log('Detected delimiter:', delimiter === '\t' ? 'TAB' : 'COMMA'); const lines = text.trim().split('\n'); const headers = lines[0].split(delimiter).map(h => h.trim()); console.log('Headers found:', headers); console.log('Total lines:', lines.length); allParts = lines.slice(1).filter(line => line.trim().length > 0).map(line => { const values = parseCSVLine(line, delimiter); const part = {}; headers.forEach((header, index) => { part[header] = values[index] || ''; }); // Parse hierarchy into main_category and subcategory const hierarchyData = parseHierarchy(part.hierarchy || ''); part.main_category = hierarchyData.main_category; part.subcategory = hierarchyData.subcategory; // Parse specifications into key-value pairs part.specsMap = {}; if (part.specifications) { part.specifications.split('|').forEach(spec => { const [key, value] = spec.split(':').map(s => s.trim()); if (key && value) { part.specsMap[key] = value; } }); } return part; }); filteredParts = [...allParts]; console.log(`Loaded ${allParts.length} parts`); console.log('Sample part:', allParts[0]); console.log('Sample hierarchy:', allParts[0].hierarchy); console.log('Sample parsed category:', allParts[0].main_category, '/', allParts[0].subcategory); } // Parse CSV line handling quoted values and different delimiters function parseCSVLine(line, delimiter = ',') { const result = []; let current = ''; let inQuotes = false; for (let i = 0; i < line.length; i++) { const char = line[i]; if (char === '"') { inQuotes = !inQuotes; } else if (char === delimiter && !inQuotes) { result.push(current.trim()); current = ''; } else { current += char; } } result.push(current.trim()); return result; } // Setup event listeners function setupEventListeners() { // Search button document.getElementById('searchButton').onclick = () => { executeSearch(); }; // Allow Enter key to trigger search document.getElementById('searchInput').onkeypress = (e) => { if (e.key === 'Enter') { executeSearch(); } }; // Clear categories button document.getElementById('clearCategoriesBtn').onclick = () => { clearCategories(); }; // Clear filters button (desktop) document.getElementById('clearFilters').onclick = () => { clearAllFilters(); }; // BoM toggle button document.getElementById('bomToggleBtn').onclick = () => { toggleBomPanel(); }; // Clear BoM button document.getElementById('clearBomBtn').onclick = () => { clearBoM(); }; // Pagination document.getElementById('prevPage').onclick = () => { if (currentPage > 1) { currentPage--; renderResults(); window.scrollTo({ top: 0, behavior: 'smooth' }); } }; document.getElementById('nextPage').onclick = () => { const totalPages = Math.ceil(filteredParts.length / itemsPerPage); if (currentPage < totalPages) { currentPage++; renderResults(); window.scrollTo({ top: 0, behavior: 'smooth' }); } }; } // Clear categories selection function clearCategories() { selectedCategory = null; selectedSubcategory = null; activeFilters = {}; currentPage = 1; // Update UI document.querySelectorAll('.category-item').forEach(el => el.classList.remove('active')); document.querySelectorAll('.subcategory-item').forEach(el => el.classList.remove('active')); document.querySelectorAll('.subcategory-list').forEach(el => el.classList.remove('show')); // Hide filters sidebar const sidebarFilters = document.getElementById('sidebarFilters'); sidebarFilters.classList.remove('show'); applyFilters(); } // Render active filters display function renderActiveFilters() { const activeFiltersSection = document.getElementById('activeFiltersSection'); const filterTags = document.getElementById('filterTags'); // Check if there are any active filters const hasSearchQuery = searchQuery.length > 0; const hasCategory = selectedCategory !== null; const hasSpecFilters = Object.keys(activeFilters).length > 0; if (!hasSearchQuery && !hasCategory && !hasSpecFilters) { activeFiltersSection.classList.remove('show'); return; } activeFiltersSection.classList.add('show'); filterTags.innerHTML = ''; // Search query tag if (hasSearchQuery) { const tag = document.createElement('div'); tag.className = 'filter-tag'; tag.innerHTML = ` Search: "${document.getElementById('searchInput').value}" `; filterTags.appendChild(tag); } // Category tag if (hasCategory) { const tag = document.createElement('div'); tag.className = 'filter-tag'; tag.innerHTML = ` Category: ${selectedSubcategory || selectedCategory} `; filterTags.appendChild(tag); } // Specification filter tags Object.entries(activeFilters).forEach(([specName, value]) => { const tag = document.createElement('div'); tag.className = 'filter-tag'; tag.innerHTML = ` ${specName}: ${value} `; filterTags.appendChild(tag); }); // Clear all button (only if multiple filters) if ((hasSearchQuery ? 1 : 0) + (hasCategory ? 1 : 0) + Object.keys(activeFilters).length > 1) { const clearAllBtn = document.createElement('button'); clearAllBtn.className = 'clear-all-filters-btn'; clearAllBtn.textContent = 'Clear All'; clearAllBtn.onclick = clearAllActiveFilters; filterTags.appendChild(clearAllBtn); } } // Clear search query function clearSearchQuery() { document.getElementById('searchInput').value = ''; searchQuery = ''; currentPage = 1; applyFilters(); } // Clear specific spec filter function clearSpecFilter(specName) { delete activeFilters[specName]; // Reset dropdown const select = document.getElementById(`filter-${specName}`); if (select) select.value = ''; currentPage = 1; applyFilters(); } // Clear all active filters function clearAllActiveFilters() { document.getElementById('searchInput').value = ''; searchQuery = ''; clearCategories(); } // Execute search and log locally function executeSearch() { const query = document.getElementById('searchInput').value.trim(); searchQuery = query.toLowerCase(); currentPage = 1; // Log search locally if (query || Object.keys(activeFilters).length > 0) { logSearchLocally(query); } applyFilters(); } // Log search query and filters locally function logSearchLocally(query) { const searchRecord = { timestamp: new Date().toISOString(), query: query, category: selectedCategory, subcategory: selectedSubcategory, filters: {...activeFilters}, resultsCount: null // Will be filled after filtering }; searchHistory.push(searchRecord); console.log('Search logged locally:', searchRecord); console.log('Total searches:', searchHistory.length); } // Get unique categories and subcategories function getCategories() { const categoryMap = {}; allParts.forEach(part => { const category = part.main_category; const subcategory = part.subcategory; if (!categoryMap[category]) { categoryMap[category] = new Set(); } if (subcategory) { // Only add non-empty subcategories categoryMap[category].add(subcategory); } }); return categoryMap; } // Render category sidebar function renderCategories() { const categories = getCategories(); const categoryList = document.getElementById('categoryList'); categoryList.innerHTML = ''; Object.keys(categories).sort().forEach(category => { const categoryDiv = document.createElement('div'); categoryDiv.className = 'category-item'; categoryDiv.textContent = category; categoryDiv.onclick = () => toggleCategory(category); const subcategoryList = document.createElement('div'); subcategoryList.className = 'subcategory-list'; const subcategories = Array.from(categories[category]).sort(); // Only create subcategory list if there are subcategories if (subcategories.length > 0) { subcategories.forEach(subcategory => { const subcategoryDiv = document.createElement('div'); subcategoryDiv.className = 'subcategory-item'; subcategoryDiv.textContent = subcategory; subcategoryDiv.onclick = (e) => { e.stopPropagation(); selectSubcategory(category, subcategory); }; subcategoryList.appendChild(subcategoryDiv); }); categoryDiv.appendChild(subcategoryList); } categoryList.appendChild(categoryDiv); }); } // Toggle category expansion and select category function toggleCategory(category) { // If clicking on an already selected category, deselect it if (selectedCategory === category && selectedSubcategory === null) { clearCategories(); return; } // Set category selection (no subcategory) selectedCategory = category; selectedSubcategory = null; activeFilters = {}; currentPage = 1; // Update UI - clear all active states first document.querySelectorAll('.category-item').forEach(el => el.classList.remove('active')); document.querySelectorAll('.subcategory-item').forEach(el => el.classList.remove('active')); // Find and activate this category const categoryItems = document.querySelectorAll('.category-item'); categoryItems.forEach(item => { if (item.textContent.startsWith(category)) { item.classList.add('active'); const subcategoryList = item.querySelector('.subcategory-list'); if (subcategoryList) { subcategoryList.classList.add('show'); } } }); // Hide filters sidebar when selecting top-level category const sidebarFilters = document.getElementById('sidebarFilters'); sidebarFilters.classList.remove('show'); applyFilters(); } // Select subcategory and update filters function selectSubcategory(category, subcategory) { selectedCategory = category; selectedSubcategory = subcategory; activeFilters = {}; currentPage = 1; // Update UI - clear all active states first document.querySelectorAll('.category-item').forEach(el => el.classList.remove('active')); document.querySelectorAll('.subcategory-item').forEach(el => el.classList.remove('active')); // Activate the selected subcategory and its parent category document.querySelectorAll('.subcategory-item').forEach(el => { if (el.textContent === subcategory) { el.classList.add('active'); const parentCategory = el.closest('.category-item'); if (parentCategory) { parentCategory.classList.add('active'); } } }); // Show filters in sidebar const sidebarFilters = document.getElementById('sidebarFilters'); sidebarFilters.classList.add('show'); renderFilters(); applyFilters(); } // Render specification filters as dropdowns function renderFilters() { const filterGroups = document.getElementById('filterGroups'); if (!selectedSubcategory) { return; } filterGroups.innerHTML = ''; // Get all unique specifications for this subcategory const specOptions = {}; allParts.forEach(part => { if (part.main_category === selectedCategory && part.subcategory === selectedSubcategory) { Object.entries(part.specsMap).forEach(([key, value]) => { if (!specOptions[key]) { specOptions[key] = new Set(); } specOptions[key].add(value); }); } }); // Create dropdown for each specification Object.entries(specOptions).forEach(([specName, values]) => { const filterGroup = createFilterGroup(specName, values); filterGroups.appendChild(filterGroup); }); } // Create a filter group function createFilterGroup(specName, values) { const filterGroup = document.createElement('div'); filterGroup.className = 'filter-group'; const label = document.createElement('label'); label.textContent = specName; filterGroup.appendChild(label); const select = document.createElement('select'); select.id = `filter-${specName}`; // Add "Any" option const anyOption = document.createElement('option'); anyOption.value = ''; anyOption.textContent = 'Any'; select.appendChild(anyOption); // Add all possible values Array.from(values).sort().forEach(value => { const option = document.createElement('option'); option.value = value; option.textContent = value; select.appendChild(option); }); select.onchange = () => { updateFilter(specName, select.value); }; filterGroup.appendChild(select); return filterGroup; } // Update filter state function updateFilter(specName, value) { if (value === '') { delete activeFilters[specName]; } else { activeFilters[specName] = value; } currentPage = 1; applyFilters(); } // Clear all filters function clearAllFilters() { activeFilters = {}; // Reset all dropdowns to "Any" document.querySelectorAll('.filter-group select').forEach(select => { select.value = ''; }); currentPage = 1; applyFilters(); } // Apply all filters (category, subcategory, specs, search) function applyFilters() { filteredParts = allParts.filter(part => { // Category filter - if category is selected, match it // If subcategory is also selected, match both // If only category is selected, match category regardless of subcategory if (selectedCategory) { if (part.main_category !== selectedCategory) { return false; } // If a subcategory is selected, filter by it too if (selectedSubcategory && part.subcategory !== selectedSubcategory) { return false; } } // Specification filters for (const [specName, value] of Object.entries(activeFilters)) { if (part.specsMap[specName] !== value) { return false; } } // Search filter (tokenized multi-field) if (searchQuery) { const tokens = searchQuery.split(/\s+/).filter(t => t.length > 0); const searchableText = [ part.part_number, part.manufacturer, part.main_category, part.subcategory, part.specifications, Object.values(part.specsMap).join(' ') ].join(' ').toLowerCase(); const allTokensMatch = tokens.every(token => searchableText.includes(token) ); if (!allTokensMatch) { return false; } } return true; }); // Update the results count in the last search record if (searchHistory.length > 0) { searchHistory[searchHistory.length - 1].resultsCount = filteredParts.length; } renderActiveFilters(); renderResults(); } // Render results table function renderResults() { const resultsContainer = document.getElementById('resultsContainer'); const resultsCount = document.getElementById('resultsCount'); const pagination = document.getElementById('pagination'); const totalResults = filteredParts.length; const totalPages = Math.ceil(totalResults / itemsPerPage); const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = Math.min(startIndex + itemsPerPage, totalResults); const currentParts = filteredParts.slice(startIndex, endIndex); // Update count if (totalResults === 0) { resultsCount.textContent = 'No results found'; } else { resultsCount.textContent = `Showing ${startIndex + 1}-${endIndex} of ${totalResults} parts`; } // Render table if (currentParts.length === 0) { resultsContainer.innerHTML = '
No parts match your criteria. Try adjusting your filters.
'; pagination.style.display = 'none'; return; } let tableHTML = ` `; currentParts.forEach(part => { const testDataBadges = part.test_data .split('|') .map(test => `${test.trim()}`) .join(''); const key = `${part.part_number}|${part.manufacturer}`; const isInBom = bom.has(key); const rowClass = isInBom ? 'in-bom' : ''; tableHTML += ` `; }); tableHTML += '
Add to Parts List Part Number Manufacturer Category Test Data Available
${part.part_number} ${part.manufacturer} ${part.subcategory || part.main_category} ${testDataBadges}
'; resultsContainer.innerHTML = tableHTML; // Update pagination if (totalPages > 1) { pagination.style.display = 'flex'; document.getElementById('pageInfo').textContent = `Page ${currentPage} of ${totalPages}`; document.getElementById('prevPage').disabled = currentPage === 1; document.getElementById('nextPage').disabled = currentPage === totalPages; } else { pagination.style.display = 'none'; } } window.addEventListener('load', function() { setTimeout(init, 500); });