const BuilderState = {
severity: '*',
component: '*',
primary: '*',
sequence: '*'
};
function initQueryBuilder() {
if (!DOM.queryBuilderToggle || !DOM.queryBuilder) return;
DOM.queryBuilderToggle.addEventListener('click', () => {
DOM.queryBuilder.classList.toggle('open');
DOM.queryBuilderToggle.classList.toggle('active');
if (DOM.queryBuilder.classList.contains('open')) {
buildAllColumns();
}
});
if (DOM.builderCloseBtn) {
DOM.builderCloseBtn.addEventListener('click', () => {
DOM.queryBuilder.classList.remove('open');
DOM.queryBuilderToggle.classList.remove('active');
});
}
if (DOM.builderClearBtn) {
DOM.builderClearBtn.addEventListener('click', clearQueryBuilder);
}
if (DOM.builderSearchBtn) {
DOM.builderSearchBtn.addEventListener('click', applyQueryBuilder);
}
buildAllColumns();
document.querySelectorAll('.builder-tab').forEach(tab => {
tab.addEventListener('click', () => {
const col = tab.dataset.col;
document.querySelectorAll('.builder-tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
document.querySelectorAll('.builder-column').forEach(c => {
c.classList.toggle('active', c.dataset.col === col);
});
});
});
}
function getFilteredErrorsForBuilder(excludeColumn) {
return AppState.errors.filter(error => {
if (!isErrorVisible(error)) return false;
const parts = (error.code || '').split('.');
const errSev = parts[0] || '';
const errComp = parts[1] || '';
const errPrim = parts[2] || '';
const errSeq = parts[3] || '';
if (excludeColumn !== 'severity' && BuilderState.severity !== '*') {
if (errSev.toUpperCase() !== BuilderState.severity.toUpperCase()) return false;
}
if (excludeColumn !== 'component' && BuilderState.component !== '*') {
if (errComp.toLowerCase() !== BuilderState.component.toLowerCase()) return false;
}
if (excludeColumn !== 'primary' && BuilderState.primary !== '*') {
if (errPrim.toLowerCase() !== BuilderState.primary.toLowerCase()) return false;
}
if (excludeColumn !== 'sequence' && BuilderState.sequence !== '*') {
const seqNum = Number.parseInt(errSeq, 10);
const stateSeqNum = Number.parseInt(BuilderState.sequence, 10);
if (Number.isFinite(seqNum) && Number.isFinite(stateSeqNum)) {
if (seqNum !== stateSeqNum) return false;
}
}
return true;
});
}
function countErrorsForOption(column, value, errors) {
return errors.filter(error => {
const parts = (error.code || '').split('.');
switch (column) {
case 'severity':
return parts[0]?.toUpperCase() === value.toUpperCase();
case 'component':
return parts[1]?.toLowerCase() === value.toLowerCase();
case 'primary':
return parts[2]?.toLowerCase() === value.toLowerCase();
case 'sequence':
const errSeq = parseInt(parts[3], 10);
const optSeq = parseInt(value, 10);
return errSeq === optSeq;
default:
return false;
}
}).length;
}
function getUniqueValuesFromErrors(column, errors) {
const values = new Map();
errors.forEach(error => {
const parts = (error.code || '').split('.');
let value;
switch (column) {
case 'severity':
value = parts[0]?.toUpperCase();
break;
case 'component':
value = parts[1];
break;
case 'primary':
value = parts[2];
break;
case 'sequence':
value = parts[3];
break;
}
if (value) {
values.set(value, (values.get(value) || 0) + 1);
}
});
return values;
}
function buildAllColumns() {
buildSeverityColumn();
buildComponentColumn();
buildPrimaryColumn();
buildSequenceColumn();
updateBuilderPreview();
}
function buildSeverityColumn() {
const sevCol = document.getElementById('sevCol');
if (!sevCol) return;
const allErrors = AppState.errors.filter(e => isErrorVisible(e));
const sevCounts = getUniqueValuesFromErrors('severity', allErrors);
const totalCount = allErrors.length;
let html = `
<div class="column-option ${BuilderState.severity === '*' ? 'selected' : ''}" data-value="*">
<span class="option-radio"></span>
<span class="option-label">All (*)</span>
<span class="option-count">${totalCount}</span>
</div>
`;
const sevOrder = ['E', 'B', 'C', 'W', 'H', 'S', 'K', 'I', 'T'];
sevOrder.forEach(char => {
const count = sevCounts.get(char) || 0;
if (count === 0) return;
const sevData = AppState.severities?.[char] || {};
const name = sevData.name || char;
const emoji = sevData.emoji || '';
const isSelected = BuilderState.severity === char;
html += `
<div class="column-option ${isSelected ? 'selected' : ''}" data-value="${char}">
<span class="option-radio"></span>
<span class="option-emoji">${emoji}</span>
<span class="option-label">${char} (${name})</span>
<span class="option-count">${count}</span>
</div>
`;
});
sevCol.innerHTML = html;
setupColumnClickHandlers(sevCol, 'severity', 'sevCurrent');
}
function buildComponentColumn() {
const compCol = document.getElementById('compCol');
if (!compCol) return;
const filteredErrors = getFilteredErrorsForBuilder('component');
const compCounts = getUniqueValuesFromErrors('component', filteredErrors);
const totalCount = filteredErrors.length;
let html = `
<div class="column-option ${BuilderState.component === '*' ? 'selected' : ''}" data-value="*">
<span class="option-radio"></span>
<span class="option-label">All (*)</span>
<span class="option-count">${totalCount}</span>
</div>
`;
const sortedComps = Array.from(compCounts.entries()).sort((a, b) => a[0].localeCompare(b[0]));
sortedComps.forEach(([comp, count]) => {
const isSelected = BuilderState.component.toLowerCase() === comp.toLowerCase();
const compData = AppState.components?.[comp] || {};
html += `
<div class="column-option ${isSelected ? 'selected' : ''}" data-value="${comp}">
<span class="option-radio"></span>
<span class="option-label">${compData.name || comp}</span>
<span class="option-count">${count}</span>
</div>
`;
});
compCol.innerHTML = html;
setupColumnClickHandlers(compCol, 'component', 'compCurrent');
if (BuilderState.component !== '*' && !compCounts.has(BuilderState.component)) {
BuilderState.component = '*';
updateCurrentDisplay('compCurrent', '*');
}
}
function buildPrimaryColumn() {
const primCol = document.getElementById('primCol');
if (!primCol) return;
const filteredErrors = getFilteredErrorsForBuilder('primary');
const primCounts = getUniqueValuesFromErrors('primary', filteredErrors);
const totalCount = filteredErrors.length;
let html = `
<div class="column-option ${BuilderState.primary === '*' ? 'selected' : ''}" data-value="*">
<span class="option-radio"></span>
<span class="option-label">All (*)</span>
<span class="option-count">${totalCount}</span>
</div>
`;
const sortedPrims = Array.from(primCounts.entries()).sort((a, b) => a[0].localeCompare(b[0]));
sortedPrims.forEach(([prim, count]) => {
const isSelected = BuilderState.primary.toLowerCase() === prim.toLowerCase();
const primData = AppState.primaries?.[prim] || {};
html += `
<div class="column-option ${isSelected ? 'selected' : ''}" data-value="${prim}">
<span class="option-radio"></span>
<span class="option-label">${primData.name || prim}</span>
<span class="option-count">${count}</span>
</div>
`;
});
primCol.innerHTML = html;
setupColumnClickHandlers(primCol, 'primary', 'primCurrent');
if (BuilderState.primary !== '*' && !primCounts.has(BuilderState.primary)) {
BuilderState.primary = '*';
updateCurrentDisplay('primCurrent', '*');
}
}
function buildSequenceColumn() {
const seqCol = document.getElementById('seqCol');
if (!seqCol) return;
const filteredErrors = getFilteredErrorsForBuilder('sequence');
const seqCounts = getUniqueValuesFromErrors('sequence', filteredErrors);
const totalCount = filteredErrors.length;
let html = `
<div class="column-option ${BuilderState.sequence === '*' ? 'selected' : ''}" data-value="*">
<span class="option-radio"></span>
<span class="option-label">All (*)</span>
<span class="option-count">${totalCount}</span>
</div>
`;
const sortedSeqs = Array.from(seqCounts.entries()).sort((a, b) => {
const numA = parseInt(a[0], 10) || 0;
const numB = parseInt(b[0], 10) || 0;
return numA - numB;
});
sortedSeqs.forEach(([seq, count]) => {
const seqNum = parseInt(seq, 10);
const seqPadded = String(seqNum).padStart(3, '0');
const isSelected = BuilderState.sequence === seq || BuilderState.sequence === seqPadded;
const seqData = AppState.sequences?.[seqNum] || AppState.sequences?.[seq] || {};
const seqName = seqData.name || '';
html += `
<div class="column-option ${isSelected ? 'selected' : ''}" data-value="${seq}">
<span class="option-radio"></span>
<span class="option-label">${seqPadded}${seqName ? ` (${seqName})` : ''}</span>
<span class="option-count">${count}</span>
</div>
`;
});
seqCol.innerHTML = html;
setupColumnClickHandlers(seqCol, 'sequence', 'seqCurrent');
const seqKeys = Array.from(seqCounts.keys());
const currentSeqNum = parseInt(BuilderState.sequence, 10);
const isValidSeq = BuilderState.sequence === '*' || seqKeys.some(k => parseInt(k, 10) === currentSeqNum);
if (!isValidSeq) {
BuilderState.sequence = '*';
updateCurrentDisplay('seqCurrent', '*');
}
}
function setupColumnClickHandlers(column, columnName, currentId) {
column.querySelectorAll('.column-option').forEach(opt => {
opt.addEventListener('click', () => {
const value = opt.dataset.value;
column.querySelectorAll('.column-option').forEach(o => o.classList.remove('selected'));
opt.classList.add('selected');
BuilderState[columnName] = value;
updateCurrentDisplay(currentId, value);
if (columnName === 'severity') {
buildComponentColumn();
buildPrimaryColumn();
buildSequenceColumn();
} else if (columnName === 'component') {
buildSeverityColumn();
buildPrimaryColumn();
buildSequenceColumn();
} else if (columnName === 'primary') {
buildSeverityColumn();
buildComponentColumn();
buildSequenceColumn();
} else if (columnName === 'sequence') {
buildSeverityColumn();
buildComponentColumn();
buildPrimaryColumn();
}
updateBuilderPreview();
});
});
}
function updateCurrentDisplay(currentId, value) {
const el = document.getElementById(currentId);
if (el) {
el.textContent = `[${value}]`;
}
}
function updateBuilderPreview() {
if (!DOM.builderPreview) return;
const { severity, component, primary, sequence } = BuilderState;
DOM.builderPreview.textContent = `${severity}.${component}.${primary}.${sequence}`;
}
function applyQueryBuilder() {
const pattern = DOM.builderPreview?.textContent || '*.*.*.*';
if (DOM.searchInput) {
DOM.searchInput.value = pattern;
}
AppState.searchQuery = pattern;
DOM.queryBuilder.classList.remove('open');
DOM.queryBuilderToggle.classList.remove('active');
filterErrors();
renderResults();
updateSidebar();
}
function clearQueryBuilder() {
BuilderState.severity = '*';
BuilderState.component = '*';
BuilderState.primary = '*';
BuilderState.sequence = '*';
buildAllColumns();
['sevCurrent', 'compCurrent', 'primCurrent', 'seqCurrent'].forEach(id => {
updateCurrentDisplay(id, '*');
});
updateBuilderPreview();
}