feat: add Audit Log button to Dashboard toolbar
This commit is contained in:
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useCallback } from 'react';
|
||||
import axios from 'axios';
|
||||
import CpasBadge, { getTier } from './CpasBadge';
|
||||
import EmployeeModal from './EmployeeModal';
|
||||
import AuditLog from './AuditLog';
|
||||
|
||||
const AT_RISK_THRESHOLD = 2;
|
||||
|
||||
@@ -28,30 +29,33 @@ function isAtRisk(points) {
|
||||
}
|
||||
|
||||
const s = {
|
||||
wrap: { padding: '32px 40px', color: '#f8f9fa' },
|
||||
header: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px', flexWrap: 'wrap', gap: '12px' },
|
||||
title: { fontSize: '24px', fontWeight: 700, color: '#f8f9fa' },
|
||||
subtitle: { fontSize: '13px', color: '#b5b5c0', marginTop: '3px' },
|
||||
statsRow: { display: 'flex', gap: '16px', flexWrap: 'wrap', marginBottom: '28px' },
|
||||
statCard: { flex: '1', minWidth: '140px', background: '#181924', border: '1px solid #30313f', borderRadius: '8px', padding: '16px', textAlign: 'center' },
|
||||
statNum: { fontSize: '28px', fontWeight: 800, color: '#f8f9fa' },
|
||||
statLbl: { fontSize: '11px', color: '#b5b5c0', marginTop: '4px' },
|
||||
search: { padding: '10px 14px', border: '1px solid #333544', borderRadius: '6px', fontSize: '14px', width: '260px', background: '#050608', color: '#f8f9fa' },
|
||||
table: { width: '100%', borderCollapse: 'collapse', background: '#111217', borderRadius: '8px', overflow: 'hidden', boxShadow: '0 1px 8px rgba(0,0,0,0.6)', border: '1px solid #222' },
|
||||
th: { background: '#000000', color: '#f8f9fa', padding: '10px 14px', textAlign: 'left', fontSize: '12px', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.5px' },
|
||||
td: { padding: '11px 14px', borderBottom: '1px solid #1c1d29', fontSize: '13px', verticalAlign: 'middle', color: '#f8f9fa' },
|
||||
nameBtn: { background: 'none', border: 'none', cursor: 'pointer', fontWeight: 600, color: '#d4af37', fontSize: '14px', padding: 0, textDecoration: 'underline dotted' },
|
||||
atRiskBadge: { display: 'inline-block', marginLeft: '8px', padding: '2px 8px', borderRadius: '10px', fontSize: '10px', fontWeight: 700, background: '#3b2e00', color: '#ffd666', border: '1px solid #d4af37', verticalAlign: 'middle' },
|
||||
zeroRow: { color: '#77798a', fontStyle: 'italic', fontSize: '12px' },
|
||||
refreshBtn: { padding: '9px 18px', background: '#d4af37', color: '#000', border: 'none', borderRadius: '6px', cursor: 'pointer', fontWeight: 600, fontSize: '13px' },
|
||||
wrap: { padding: '32px 40px', color: '#f8f9fa' },
|
||||
header: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px', flexWrap: 'wrap', gap: '12px' },
|
||||
title: { fontSize: '24px', fontWeight: 700, color: '#f8f9fa' },
|
||||
subtitle: { fontSize: '13px', color: '#b5b5c0', marginTop: '3px' },
|
||||
statsRow: { display: 'flex', gap: '16px', flexWrap: 'wrap', marginBottom: '28px' },
|
||||
statCard: { flex: '1', minWidth: '140px', background: '#181924', border: '1px solid #30313f', borderRadius: '8px', padding: '16px', textAlign: 'center' },
|
||||
statNum: { fontSize: '28px', fontWeight: 800, color: '#f8f9fa' },
|
||||
statLbl: { fontSize: '11px', color: '#b5b5c0', marginTop: '4px' },
|
||||
search: { padding: '10px 14px', border: '1px solid #333544', borderRadius: '6px', fontSize: '14px', width: '260px', background: '#050608', color: '#f8f9fa' },
|
||||
table: { width: '100%', borderCollapse: 'collapse', background: '#111217', borderRadius: '8px', overflow: 'hidden', boxShadow: '0 1px 8px rgba(0,0,0,0.6)', border: '1px solid #222' },
|
||||
th: { background: '#000000', color: '#f8f9fa', padding: '10px 14px', textAlign: 'left', fontSize: '12px', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.5px' },
|
||||
td: { padding: '11px 14px', borderBottom: '1px solid #1c1d29', fontSize: '13px', verticalAlign: 'middle', color: '#f8f9fa' },
|
||||
nameBtn: { background: 'none', border: 'none', cursor: 'pointer', fontWeight: 600, color: '#d4af37', fontSize: '14px', padding: 0, textDecoration: 'underline dotted' },
|
||||
atRiskBadge: { display: 'inline-block', marginLeft: '8px', padding: '2px 8px', borderRadius: '10px', fontSize: '10px', fontWeight: 700, background: '#3b2e00', color: '#ffd666', border: '1px solid #d4af37', verticalAlign: 'middle' },
|
||||
zeroRow: { color: '#77798a', fontStyle: 'italic', fontSize: '12px' },
|
||||
toolbarRight: { display: 'flex', gap: '10px', alignItems: 'center' },
|
||||
refreshBtn: { padding: '9px 18px', background: '#d4af37', color: '#000', border: 'none', borderRadius: '6px', cursor: 'pointer', fontWeight: 600, fontSize: '13px' },
|
||||
auditBtn: { padding: '9px 18px', background: 'none', color: '#9ca0b8', border: '1px solid #2a2b3a', borderRadius: '6px', cursor: 'pointer', fontWeight: 600, fontSize: '13px' },
|
||||
};
|
||||
|
||||
export default function Dashboard() {
|
||||
const [employees, setEmployees] = useState([]);
|
||||
const [filtered, setFiltered] = useState([]);
|
||||
const [search, setSearch] = useState('');
|
||||
const [selectedId, setSelectedId] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [employees, setEmployees] = useState([]);
|
||||
const [filtered, setFiltered] = useState([]);
|
||||
const [search, setSearch] = useState('');
|
||||
const [selectedId, setSelectedId] = useState(null);
|
||||
const [showAudit, setShowAudit] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const load = useCallback(() => {
|
||||
setLoading(true);
|
||||
@@ -77,9 +81,6 @@ export default function Dashboard() {
|
||||
const maxPoints = employees.reduce((m, e) => Math.max(m, e.active_points), 0);
|
||||
|
||||
return (
|
||||
// FIX: Fragment wraps both s.wrap AND EmployeeModal so the modal is
|
||||
// outside the s.wrap div — React synthetic events will no longer bubble
|
||||
// from inside the modal up through the Dashboard's DOM tree.
|
||||
<>
|
||||
<div style={s.wrap}>
|
||||
<div style={s.header}>
|
||||
@@ -87,13 +88,14 @@ export default function Dashboard() {
|
||||
<div style={s.title}>Company Dashboard</div>
|
||||
<div style={s.subtitle}>Click any employee name to view their full profile</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
|
||||
<div style={s.toolbarRight}>
|
||||
<input
|
||||
style={s.search}
|
||||
placeholder="Search name, dept, supervisor…"
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
/>
|
||||
<button style={s.auditBtn} onClick={() => setShowAudit(true)}>📋 Audit Log</button>
|
||||
<button style={s.refreshBtn} onClick={load}>↻ Refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -179,15 +181,13 @@ export default function Dashboard() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* FIX: EmployeeModal is now OUTSIDE <div style={s.wrap}>.
|
||||
React synthetic events no longer bubble from modal buttons
|
||||
up through Dashboard's component tree. */}
|
||||
{selectedId && (
|
||||
<EmployeeModal
|
||||
employeeId={selectedId}
|
||||
onClose={() => { setSelectedId(null); load(); }}
|
||||
/>
|
||||
)}
|
||||
{showAudit && <AuditLog onClose={() => setShowAudit(false)} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user