// 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 = `
';
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);
});