// ==========================================
// Enhanced Review Tab with Multi-Select & Preview
// ==========================================
// Review tab state
let reviewTabState = {
allFindings: [],
filteredFindings: [],
selectedFindings: new Set(),
currentFilters: {
dimension: 'all',
severities: new Set(),
search: ''
},
sortConfig: {
field: 'severity',
order: 'desc'
},
previewFinding: null,
sessionPath: null,
sessionId: null
};
// ==========================================
// Main Review Tab Render
// ==========================================
function renderReviewContent(review) {
if (!review || !review.dimensions) {
return `
${escapeHtml(finding.title)}
${finding.file ? `
đ Location
${escapeHtml(finding.file)}${finding.line ? ':' + finding.line : ''}
` : ''}
đ Description
${escapeHtml(finding.description)}
${finding.code_context ? `
đģ Code Context
${escapeHtml(finding.code_context)}
` : ''}
${finding.recommendations && finding.recommendations.length > 0 ? `
â
Recommendations
${finding.recommendations.map(r => `- ${escapeHtml(r)}
`).join('')}
` : ''}
${finding.root_cause ? `
đ Root Cause
${escapeHtml(finding.root_cause)}
` : ''}
${finding.impact ? `
â ī¸ Impact
${escapeHtml(finding.impact)}
` : ''}
${finding.references && finding.references.length > 0 ? `
đ References
${finding.references.map(ref => {
const isUrl = ref.startsWith('http');
return `- ${isUrl ? `${ref}` : ref}
`;
}).join('')}
` : ''}
${finding.metadata && Object.keys(finding.metadata).length > 0 ? `
` : ''}
`;
}
// ==========================================
// Selection Management
// ==========================================
function toggleReviewFindingSelection(findingId, event) {
if (event) {
event.stopPropagation();
}
if (reviewTabState.selectedFindings.has(findingId)) {
reviewTabState.selectedFindings.delete(findingId);
} else {
reviewTabState.selectedFindings.add(findingId);
}
updateReviewSelectionUI();
// Update preview panel button if this finding is being previewed
if (reviewTabState.previewFinding && reviewTabState.previewFinding.id === findingId) {
previewReviewFinding(findingId);
}
}
function selectAllReviewFindings() {
reviewTabState.allFindings.forEach(f => reviewTabState.selectedFindings.add(f.id));
updateReviewSelectionUI();
}
function selectVisibleReviewFindings() {
reviewTabState.filteredFindings.forEach(f => reviewTabState.selectedFindings.add(f.id));
updateReviewSelectionUI();
}
function selectReviewBySeverity(severity) {
reviewTabState.allFindings
.filter(f => f.severity === severity)
.forEach(f => reviewTabState.selectedFindings.add(f.id));
updateReviewSelectionUI();
}
function clearReviewSelection() {
reviewTabState.selectedFindings.clear();
updateReviewSelectionUI();
}
function updateReviewSelectionUI() {
// Update counter
const counter = document.getElementById('reviewSelectionCounter');
if (counter) {
counter.textContent = `${reviewTabState.selectedFindings.size} selected`;
}
// Update export button
const exportBtn = document.getElementById('exportFixBtn');
if (exportBtn) {
exportBtn.disabled = reviewTabState.selectedFindings.size === 0;
}
// Update checkbox states in list
document.querySelectorAll('.review-finding-item').forEach(item => {
const findingId = item.dataset.findingId;
const isSelected = reviewTabState.selectedFindings.has(findingId);
item.classList.toggle('selected', isSelected);
const checkbox = item.querySelector('.finding-checkbox');
if (checkbox) {
checkbox.checked = isSelected;
}
});
}
// ==========================================
// Filtering & Sorting
// ==========================================
function filterReviewByDimension(dimension) {
reviewTabState.currentFilters.dimension = dimension;
// Update tab active state
document.querySelectorAll('.dim-tab').forEach(tab => {
tab.classList.toggle('active', tab.dataset.dimension === dimension);
});
applyReviewFilters();
}
function filterReviewBySeverity(severity) {
// Toggle the severity filter
if (reviewTabState.currentFilters.severities.has(severity)) {
reviewTabState.currentFilters.severities.delete(severity);
} else {
reviewTabState.currentFilters.severities.add(severity);
}
// Update filter chip UI
const filterChip = document.getElementById(`filter-${severity}`);
if (filterChip) {
filterChip.classList.toggle('active', reviewTabState.currentFilters.severities.has(severity));
const checkbox = filterChip.querySelector('input[type="checkbox"]');
if (checkbox) {
checkbox.checked = reviewTabState.currentFilters.severities.has(severity);
}
}
applyReviewFilters();
}
function toggleReviewSeverityFilter(severity) {
filterReviewBySeverity(severity);
}
function onReviewSearch(searchText) {
reviewTabState.currentFilters.search = searchText.toLowerCase();
applyReviewFilters();
}
function applyReviewFilters() {
reviewTabState.filteredFindings = reviewTabState.allFindings.filter(finding => {
// Dimension filter
if (reviewTabState.currentFilters.dimension !== 'all') {
if (finding.dimension !== reviewTabState.currentFilters.dimension) {
return false;
}
}
// Severity filter (multi-select)
if (reviewTabState.currentFilters.severities.size > 0) {
if (!reviewTabState.currentFilters.severities.has(finding.severity)) {
return false;
}
}
// Search filter
if (reviewTabState.currentFilters.search) {
const searchText = `${finding.title} ${finding.description} ${finding.file} ${finding.category}`.toLowerCase();
if (!searchText.includes(reviewTabState.currentFilters.search)) {
return false;
}
}
return true;
});
sortReviewFindings();
}
function sortReviewFindings() {
const sortBy = document.getElementById('reviewSortSelect')?.value || 'severity';
reviewTabState.sortConfig.field = sortBy;
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
reviewTabState.filteredFindings.sort((a, b) => {
let comparison = 0;
if (sortBy === 'severity') {
comparison = severityOrder[a.severity] - severityOrder[b.severity];
} else if (sortBy === 'dimension') {
comparison = a.dimension.localeCompare(b.dimension);
} else if (sortBy === 'file') {
comparison = (a.file || '').localeCompare(b.file || '');
}
return reviewTabState.sortConfig.order === 'asc' ? comparison : -comparison;
});
renderFilteredReviewFindings();
}
function toggleReviewSortOrder() {
reviewTabState.sortConfig.order = reviewTabState.sortConfig.order === 'asc' ? 'desc' : 'asc';
const icon = document.getElementById('reviewSortOrderIcon');
if (icon) {
icon.textContent = reviewTabState.sortConfig.order === 'asc' ? 'â' : 'â';
}
sortReviewFindings();
}
function resetReviewFilters() {
// Reset state
reviewTabState.currentFilters.dimension = 'all';
reviewTabState.currentFilters.severities.clear();
reviewTabState.currentFilters.search = '';
reviewTabState.sortConfig.field = 'severity';
reviewTabState.sortConfig.order = 'desc';
// Reset UI
document.querySelectorAll('.dim-tab').forEach(tab => {
tab.classList.toggle('active', tab.dataset.dimension === 'all');
});
document.querySelectorAll('.filter-chip').forEach(chip => {
chip.classList.remove('active');
const checkbox = chip.querySelector('input[type="checkbox"]');
if (checkbox) checkbox.checked = false;
});
const searchInput = document.getElementById('reviewSearchInput');
if (searchInput) searchInput.value = '';
const sortSelect = document.getElementById('reviewSortSelect');
if (sortSelect) sortSelect.value = 'severity';
const sortIcon = document.getElementById('reviewSortOrderIcon');
if (sortIcon) sortIcon.textContent = 'â';
// Re-apply filters
reviewTabState.filteredFindings = [...reviewTabState.allFindings];
sortReviewFindings();
}
function renderFilteredReviewFindings() {
const listContainer = document.getElementById('reviewFindingsList');
const countEl = document.getElementById('reviewFindingsCount');
if (listContainer) {
listContainer.innerHTML = renderReviewFindingsList(reviewTabState.filteredFindings);
}
if (countEl) {
countEl.textContent = `${reviewTabState.filteredFindings.length} findings`;
}
}
// ==========================================
// Export Fix JSON
// ==========================================
function exportReviewFixJson() {
if (reviewTabState.selectedFindings.size === 0) {
showToast('Please select at least one finding to export', 'error');
return;
}
const selectedFindingsData = reviewTabState.allFindings.filter(f =>
reviewTabState.selectedFindings.has(f.id)
);
const session = sessionDataStore[currentSessionDetailKey];
const sessionId = session?.session_id || 'unknown';
const exportId = `fix-${Date.now()}`;
const exportData = {
export_id: exportId,
export_timestamp: new Date().toISOString(),
review_id: `review-${sessionId}`,
session_id: sessionId,
findings_count: selectedFindingsData.length,
findings: selectedFindingsData.map(f => ({
id: f.id,
title: f.title,
description: f.description,
severity: f.severity,
dimension: f.dimension,
category: f.category || 'uncategorized',
file: f.file,
line: f.line,
code_context: f.code_context || null,
recommendations: f.recommendations || [],
root_cause: f.root_cause || null
}))
};
// Convert to JSON and download
const jsonStr = JSON.stringify(exportData, null, 2);
const blob = new Blob([jsonStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
const filename = `fix-export-${exportId}.json`;
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
// Show success notification
const severityCounts = {
critical: selectedFindingsData.filter(f => f.severity === 'critical').length,
high: selectedFindingsData.filter(f => f.severity === 'high').length,
medium: selectedFindingsData.filter(f => f.severity === 'medium').length,
low: selectedFindingsData.filter(f => f.severity === 'low').length
};
showToast(`Exported ${selectedFindingsData.length} findings for fixing (Critical: ${severityCounts.critical}, High: ${severityCounts.high}, Medium: ${severityCounts.medium}, Low: ${severityCounts.low})`, 'success');
}