// ========================================== // 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 `
🔍
No Review Data
No review findings in .review/
`; } // Convert dimensions object to flat findings array const findings = []; let findingIndex = 0; Object.entries(review.dimensions).forEach(([dim, rawFindings]) => { let dimFindings = []; if (Array.isArray(rawFindings)) { dimFindings = rawFindings; } else if (rawFindings && typeof rawFindings === 'object') { if (Array.isArray(rawFindings.findings)) { dimFindings = rawFindings.findings; } } dimFindings.forEach(f => { findings.push({ id: f.id || `finding-${findingIndex++}`, title: f.title || 'Finding', description: f.description || '', severity: (f.severity || 'medium').toLowerCase(), dimension: dim, category: f.category || '', file: f.file || '', line: f.line || '', code_context: f.code_context || f.snippet || '', recommendations: f.recommendations || (f.recommendation ? [f.recommendation] : []), root_cause: f.root_cause || '', impact: f.impact || '', references: f.references || [], metadata: f.metadata || {} }); }); }); if (findings.length === 0) { return `
🔍
No Findings
No review findings found.
`; } // Store findings in state reviewTabState.allFindings = findings; reviewTabState.filteredFindings = [...findings]; reviewTabState.selectedFindings.clear(); reviewTabState.previewFinding = null; // Get dimensions for tabs const dimensions = [...new Set(findings.map(f => f.dimension))]; // Count by severity const severityCounts = { critical: findings.filter(f => f.severity === 'critical').length, high: findings.filter(f => f.severity === 'high').length, medium: findings.filter(f => f.severity === 'medium').length, low: findings.filter(f => f.severity === 'low').length }; return `
🔴 ${severityCounts.critical} 🟠 ${severityCounts.high} 🟡 ${severityCounts.medium} đŸŸĸ ${severityCounts.low}
0 selected
Severity:
Sort:
${dimensions.map(dim => ` `).join('')}
${findings.length} findings
${renderReviewFindingsList(findings)}
👆
Click on a finding to preview details
`; } // ========================================== // Findings List Rendering // ========================================== function renderReviewFindingsList(findings) { if (findings.length === 0) { return `
✨ No findings match your filters
`; } return findings.map(finding => `
${finding.severity} ${escapeHtml(finding.dimension)}
${escapeHtml(finding.title)}
${finding.file ? `
📄 ${escapeHtml(finding.file)}${finding.line ? ':' + finding.line : ''}
` : ''}
`).join(''); } // ========================================== // Preview Panel Rendering // ========================================== function previewReviewFinding(findingId) { const finding = reviewTabState.allFindings.find(f => f.id === findingId); if (!finding) return; reviewTabState.previewFinding = finding; // Update active state in list document.querySelectorAll('.review-finding-item').forEach(item => { item.classList.toggle('previewing', item.dataset.findingId === findingId); }); const previewPanel = document.getElementById('reviewPreviewPanel'); if (!previewPanel) return; previewPanel.innerHTML = `
${finding.severity} ${escapeHtml(finding.dimension)} ${finding.category ? `${escapeHtml(finding.category)}` : ''}

${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.root_cause ? `
🔍 Root Cause
${escapeHtml(finding.root_cause)}
` : ''} ${finding.impact ? `
âš ī¸ Impact
${escapeHtml(finding.impact)}
` : ''} ${finding.references && finding.references.length > 0 ? `
🔗 References
` : ''} ${finding.metadata && Object.keys(finding.metadata).length > 0 ? `
â„šī¸ Metadata
` : ''}
`; } // ========================================== // 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'); }