|
684 | 684 | border-left: 4px solid var(--warning); |
685 | 685 | } |
686 | 686 |
|
| 687 | + /* Batch Action Bar */ |
| 688 | + .batch-action-bar { |
| 689 | + position: fixed; |
| 690 | + bottom: 24px; |
| 691 | + left: 50%; |
| 692 | + transform: translateX(-50%); |
| 693 | + background: var(--bg-card); |
| 694 | + border: 1px solid var(--primary); |
| 695 | + border-radius: 12px; |
| 696 | + padding: 16px 24px; |
| 697 | + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); |
| 698 | + display: none; |
| 699 | + align-items: center; |
| 700 | + gap: 16px; |
| 701 | + z-index: 1000; |
| 702 | + animation: slideUp 0.3s ease; |
| 703 | + } |
| 704 | + |
| 705 | + @keyframes slideUp { |
| 706 | + from { |
| 707 | + opacity: 0; |
| 708 | + transform: translateX(-50%) translateY(20px); |
| 709 | + } |
| 710 | + to { |
| 711 | + opacity: 1; |
| 712 | + transform: translateX(-50%) translateY(0); |
| 713 | + } |
| 714 | + } |
| 715 | + |
| 716 | + .batch-action-bar.show { |
| 717 | + display: flex; |
| 718 | + } |
| 719 | + |
| 720 | + .batch-info { |
| 721 | + color: var(--text-primary); |
| 722 | + font-weight: 600; |
| 723 | + font-size: 14px; |
| 724 | + } |
| 725 | + |
| 726 | + .batch-actions { |
| 727 | + display: flex; |
| 728 | + gap: 8px; |
| 729 | + } |
| 730 | + |
| 731 | + .batch-btn { |
| 732 | + padding: 8px 16px; |
| 733 | + border: none; |
| 734 | + border-radius: 6px; |
| 735 | + font-size: 13px; |
| 736 | + font-weight: 500; |
| 737 | + cursor: pointer; |
| 738 | + transition: all 0.2s; |
| 739 | + } |
| 740 | + |
| 741 | + .batch-btn.primary { |
| 742 | + background: var(--success); |
| 743 | + color: white; |
| 744 | + } |
| 745 | + |
| 746 | + .batch-btn.primary:hover { |
| 747 | + background: #047857; |
| 748 | + } |
| 749 | + |
| 750 | + .batch-btn.secondary { |
| 751 | + background: var(--primary); |
| 752 | + color: white; |
| 753 | + } |
| 754 | + |
| 755 | + .batch-btn.secondary:hover { |
| 756 | + background: #0e7490; |
| 757 | + } |
| 758 | + |
| 759 | + .batch-btn.clear { |
| 760 | + background: rgba(255, 255, 255, 0.1); |
| 761 | + color: var(--text-primary); |
| 762 | + } |
| 763 | + |
| 764 | + .batch-btn.clear:hover { |
| 765 | + background: rgba(255, 255, 255, 0.15); |
| 766 | + } |
| 767 | + |
| 768 | + /* Escalation Card Selection */ |
| 769 | + .escalation-card.selected { |
| 770 | + background: rgba(8, 145, 178, 0.15); |
| 771 | + border-color: var(--primary); |
| 772 | + } |
| 773 | + |
| 774 | + .escalation-checkbox { |
| 775 | + display: flex; |
| 776 | + align-items: center; |
| 777 | + gap: 12px; |
| 778 | + } |
| 779 | + |
| 780 | + .escalation-checkbox input[type="checkbox"] { |
| 781 | + width: 20px; |
| 782 | + height: 20px; |
| 783 | + cursor: pointer; |
| 784 | + accent-color: var(--primary); |
| 785 | + } |
| 786 | + |
687 | 787 | /* Scrollbar Styling */ |
688 | 788 | ::-webkit-scrollbar { |
689 | 789 | width: 8px; |
@@ -833,6 +933,24 @@ <h2 class="section-title">📦 Evidence Artifacts</h2> |
833 | 933 | </div> |
834 | 934 | </div> |
835 | 935 |
|
| 936 | + <!-- Batch Action Bar --> |
| 937 | + <div id="batch-action-bar" class="batch-action-bar"> |
| 938 | + <div class="batch-info"> |
| 939 | + <span id="batch-count">0</span> escalations selected |
| 940 | + </div> |
| 941 | + <div class="batch-actions"> |
| 942 | + <button class="batch-btn primary" onclick="batchApproveEscalations()"> |
| 943 | + ✓ Approve All |
| 944 | + </button> |
| 945 | + <button class="batch-btn secondary" onclick="batchReviewEscalations()"> |
| 946 | + 👁️ Mark for Review |
| 947 | + </button> |
| 948 | + <button class="batch-btn clear" onclick="clearBatchSelection()"> |
| 949 | + Clear |
| 950 | + </button> |
| 951 | + </div> |
| 952 | + </div> |
| 953 | + |
836 | 954 | <!-- Risk Assessment Modal --> |
837 | 955 | <div id="risk-modal" class="modal"> |
838 | 956 | <div class="modal-content"> |
@@ -955,16 +1073,102 @@ <h3 class="modal-title">Assess Risk for Decision</h3> |
955 | 1073 | } |
956 | 1074 | } |
957 | 1075 |
|
| 1076 | + // Track selected escalations |
| 1077 | + const selectedEscalations = new Set(); |
| 1078 | + |
958 | 1079 | async function loadEscalations() { |
959 | 1080 | const container = document.getElementById('escalation-list'); |
960 | 1081 | try { |
961 | | - container.innerHTML = ` |
962 | | - <div class="empty-state"> |
963 | | - <div class="empty-state-icon">🚨</div> |
964 | | - <div>No escalations found</div> |
965 | | - <div class="form-help">Click "+ Create Escalation" to create one</div> |
| 1082 | + // Mock escalation data - in production, fetch from API |
| 1083 | + const escalations = [ |
| 1084 | + { |
| 1085 | + id: 'esc_001', |
| 1086 | + priority: 'critical', |
| 1087 | + status: 'pending_review', |
| 1088 | + title: 'High-Risk Decision Approval Required', |
| 1089 | + description: 'Decision dec_user_123 requires senior approval due to high security risk score (87)', |
| 1090 | + decision_id: 'dec_user_123', |
| 1091 | + created_at: '2026-01-10T10:30:00Z', |
| 1092 | + sla_violation: true |
| 1093 | + }, |
| 1094 | + { |
| 1095 | + id: 'esc_002', |
| 1096 | + priority: 'high', |
| 1097 | + status: 'pending_review', |
| 1098 | + title: 'Privacy Compliance Review Needed', |
| 1099 | + description: 'Multiple privacy risk dimensions exceed threshold for decision dec_data_456', |
| 1100 | + decision_id: 'dec_data_456', |
| 1101 | + created_at: '2026-01-10T11:00:00Z', |
| 1102 | + sla_violation: false |
| 1103 | + }, |
| 1104 | + { |
| 1105 | + id: 'esc_003', |
| 1106 | + priority: 'high', |
| 1107 | + status: 'pending_review', |
| 1108 | + title: 'Financial Impact Assessment', |
| 1109 | + description: 'Decision dec_contract_789 has high financial risk requiring CFO review', |
| 1110 | + decision_id: 'dec_contract_789', |
| 1111 | + created_at: '2026-01-10T11:15:00Z', |
| 1112 | + sla_violation: false |
| 1113 | + } |
| 1114 | + ]; |
| 1115 | + |
| 1116 | + if (escalations.length === 0) { |
| 1117 | + container.innerHTML = ` |
| 1118 | + <div class="empty-state"> |
| 1119 | + <div class="empty-state-icon">🚨</div> |
| 1120 | + <div>No escalations found</div> |
| 1121 | + <div class="form-help">Click "+ Create Escalation" to create one</div> |
| 1122 | + </div> |
| 1123 | + `; |
| 1124 | + return; |
| 1125 | + } |
| 1126 | + |
| 1127 | + container.innerHTML = escalations.map(esc => ` |
| 1128 | + <div class="escalation-card ${esc.sla_violation ? 'sla-violation' : ''} ${selectedEscalations.has(esc.id) ? 'selected' : ''}" |
| 1129 | + data-escalation-id="${esc.id}"> |
| 1130 | + <div class="escalation-header"> |
| 1131 | + <div class="escalation-checkbox"> |
| 1132 | + <input type="checkbox" |
| 1133 | + id="esc-check-${esc.id}" |
| 1134 | + ${selectedEscalations.has(esc.id) ? 'checked' : ''} |
| 1135 | + onchange="toggleEscalationSelection('${esc.id}')"> |
| 1136 | + <label for="esc-check-${esc.id}" style="cursor: pointer;"> |
| 1137 | + <strong>${esc.title}</strong> |
| 1138 | + </label> |
| 1139 | + </div> |
| 1140 | + <div style="display: flex; gap: 8px; align-items: center;"> |
| 1141 | + <span class="escalation-priority ${esc.priority}">${esc.priority}</span> |
| 1142 | + ${esc.sla_violation ? '<span style="color: var(--danger); font-size: 0.875rem;">⚠️ SLA</span>' : ''} |
| 1143 | + </div> |
| 1144 | + </div> |
| 1145 | + <div style="margin: 8px 0; color: var(--text-secondary); font-size: 0.875rem;"> |
| 1146 | + ${esc.description} |
| 1147 | + </div> |
| 1148 | + <div class="escalation-meta"> |
| 1149 | + <div class="escalation-meta-item"> |
| 1150 | + <span>🆔</span> |
| 1151 | + <span>${esc.decision_id}</span> |
| 1152 | + </div> |
| 1153 | + <div class="escalation-meta-item"> |
| 1154 | + <span>📋</span> |
| 1155 | + <span>${esc.status.replace(/_/g, ' ')}</span> |
| 1156 | + </div> |
| 1157 | + <div class="escalation-meta-item"> |
| 1158 | + <span>🕐</span> |
| 1159 | + <span>${new Date(esc.created_at).toLocaleTimeString()}</span> |
| 1160 | + </div> |
| 1161 | + </div> |
966 | 1162 | </div> |
967 | | - `; |
| 1163 | + `).join(''); |
| 1164 | + |
| 1165 | + // Update stats |
| 1166 | + document.getElementById('pending-escalations').textContent = escalations.length; |
| 1167 | + const slaViolations = escalations.filter(e => e.sla_violation).length; |
| 1168 | + document.getElementById('escalation-trend').innerHTML = slaViolations > 0 |
| 1169 | + ? `<span class="trend-down">⚠️ ${slaViolations} SLA violations</span>` |
| 1170 | + : `<span class="trend-up">✓ All within SLA</span>`; |
| 1171 | + |
968 | 1172 | } catch (error) { |
969 | 1173 | console.error('Error loading escalations:', error); |
970 | 1174 | container.innerHTML = `<div class="empty-state">Error loading escalations</div>`; |
@@ -1100,6 +1304,88 @@ <h3 class="modal-title">Assess Risk for Decision</h3> |
1100 | 1304 | } |
1101 | 1305 | }); |
1102 | 1306 | }); |
| 1307 | + |
| 1308 | + // Batch Selection Functions |
| 1309 | + function toggleEscalationSelection(escalationId) { |
| 1310 | + if (selectedEscalations.has(escalationId)) { |
| 1311 | + selectedEscalations.delete(escalationId); |
| 1312 | + } else { |
| 1313 | + selectedEscalations.add(escalationId); |
| 1314 | + } |
| 1315 | + updateBatchActionBar(); |
| 1316 | + updateEscalationCardStyles(); |
| 1317 | + } |
| 1318 | + |
| 1319 | + function updateBatchActionBar() { |
| 1320 | + const batchBar = document.getElementById('batch-action-bar'); |
| 1321 | + const batchCount = document.getElementById('batch-count'); |
| 1322 | + const count = selectedEscalations.size; |
| 1323 | + |
| 1324 | + batchCount.textContent = count; |
| 1325 | + |
| 1326 | + if (count > 0) { |
| 1327 | + batchBar.classList.add('show'); |
| 1328 | + } else { |
| 1329 | + batchBar.classList.remove('show'); |
| 1330 | + } |
| 1331 | + } |
| 1332 | + |
| 1333 | + function updateEscalationCardStyles() { |
| 1334 | + document.querySelectorAll('.escalation-card').forEach(card => { |
| 1335 | + const id = card.getAttribute('data-escalation-id'); |
| 1336 | + if (selectedEscalations.has(id)) { |
| 1337 | + card.classList.add('selected'); |
| 1338 | + } else { |
| 1339 | + card.classList.remove('selected'); |
| 1340 | + } |
| 1341 | + }); |
| 1342 | + } |
| 1343 | + |
| 1344 | + async function batchApproveEscalations() { |
| 1345 | + if (selectedEscalations.size === 0) return; |
| 1346 | + |
| 1347 | + const count = selectedEscalations.size; |
| 1348 | + const ids = Array.from(selectedEscalations); |
| 1349 | + |
| 1350 | + // In production, send to API: |
| 1351 | + // await fetch(`${API_BASE}/api/governance/escalations/batch-approve`, { |
| 1352 | + // method: 'POST', |
| 1353 | + // headers: { 'Content-Type': 'application/json' }, |
| 1354 | + // body: JSON.stringify({ escalation_ids: ids }) |
| 1355 | + // }); |
| 1356 | + |
| 1357 | + showToast(`${count} escalation${count > 1 ? 's' : ''} approved successfully`, 'success'); |
| 1358 | + clearBatchSelection(); |
| 1359 | + await loadEscalations(); |
| 1360 | + } |
| 1361 | + |
| 1362 | + async function batchReviewEscalations() { |
| 1363 | + if (selectedEscalations.size === 0) return; |
| 1364 | + |
| 1365 | + const count = selectedEscalations.size; |
| 1366 | + const ids = Array.from(selectedEscalations); |
| 1367 | + |
| 1368 | + // In production, send to API: |
| 1369 | + // await fetch(`${API_BASE}/api/governance/escalations/batch-review`, { |
| 1370 | + // method: 'POST', |
| 1371 | + // headers: { 'Content-Type': 'application/json' }, |
| 1372 | + // body: JSON.stringify({ escalation_ids: ids }) |
| 1373 | + // }); |
| 1374 | + |
| 1375 | + showToast(`${count} escalation${count > 1 ? 's' : ''} marked for review`, 'success'); |
| 1376 | + clearBatchSelection(); |
| 1377 | + await loadEscalations(); |
| 1378 | + } |
| 1379 | + |
| 1380 | + function clearBatchSelection() { |
| 1381 | + selectedEscalations.clear(); |
| 1382 | + updateBatchActionBar(); |
| 1383 | + // Uncheck all checkboxes |
| 1384 | + document.querySelectorAll('.escalation-card input[type="checkbox"]').forEach(cb => { |
| 1385 | + cb.checked = false; |
| 1386 | + }); |
| 1387 | + updateEscalationCardStyles(); |
| 1388 | + } |
1103 | 1389 | </script> |
1104 | 1390 | </body> |
1105 | 1391 | </html> |
0 commit comments