From 2383e3cc9453a021c182f63f8b64a4ef5ebe06a1 Mon Sep 17 00:00:00 2001 From: jason Date: Fri, 6 Mar 2026 14:11:46 -0600 Subject: [PATCH] Upload files to "client/src/components" --- client/src/components/Dashboard.jsx | 290 ++++++++++++++-------------- 1 file changed, 143 insertions(+), 147 deletions(-) diff --git a/client/src/components/Dashboard.jsx b/client/src/components/Dashboard.jsx index 33f6b81..6257990 100755 --- a/client/src/components/Dashboard.jsx +++ b/client/src/components/Dashboard.jsx @@ -3,171 +3,167 @@ import axios from 'axios'; import CpasBadge, { getTier } from './CpasBadge'; import EmployeeModal from './EmployeeModal'; -const AT_RISK_THRESHOLD = 2; // points within next tier boundary +const AT_RISK_THRESHOLD = 2; const TIERS = [ - { min: 0, max: 4 }, - { min: 5, max: 9 }, - { min: 10, max: 14 }, - { min: 15, max: 19 }, - { min: 20, max: 24 }, - { min: 25, max: 29 }, - { min: 30, max: 999}, + { min: 0, max: 4 }, + { min: 5, max: 9 }, + { min: 10, max: 14 }, + { min: 15, max: 19 }, + { min: 20, max: 24 }, + { min: 25, max: 29 }, + { min: 30, max: 999}, ]; function nextTierBoundary(points) { - for (const t of TIERS) { - if (points >= t.min && points <= t.max && t.max < 999) - return t.max + 1; - } - return null; + for (const t of TIERS) { + if (points >= t.min && points <= t.max && t.max < 999) return t.max + 1; + } + return null; } function isAtRisk(points) { - const boundary = nextTierBoundary(points); - return boundary !== null && (boundary - points) <= AT_RISK_THRESHOLD; + const boundary = nextTierBoundary(points); + return boundary !== null && (boundary - points) <= AT_RISK_THRESHOLD; } const s = { - wrap: { padding: '40px' }, - header: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px', flexWrap: 'wrap', gap: '12px' }, - title: { fontSize: '24px', fontWeight: 700, color: '#2c3e50' }, - subtitle: { fontSize: '13px', color: '#888', marginTop: '3px' }, - statsRow: { display: 'flex', gap: '16px', flexWrap: 'wrap', marginBottom: '28px' }, - statCard: { flex: '1', minWidth: '140px', background: '#f8f9fa', border: '1px solid #dee2e6', borderRadius: '8px', padding: '16px', textAlign: 'center' }, - statNum: { fontSize: '28px', fontWeight: 800, color: '#2c3e50' }, - statLbl: { fontSize: '11px', color: '#888', marginTop: '4px' }, - search: { padding: '10px 14px', border: '1px solid #ddd', borderRadius: '6px', fontSize: '14px', width: '260px' }, - table: { width: '100%', borderCollapse: 'collapse', background: 'white', borderRadius: '8px', overflow: 'hidden', boxShadow: '0 1px 4px rgba(0,0,0,0.08)' }, - th: { background: '#34495e', color: 'white', padding: '10px 14px', textAlign: 'left', fontSize: '12px', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.5px' }, - td: { padding: '11px 14px', borderBottom: '1px solid #f0f0f0', fontSize: '13px', verticalAlign: 'middle' }, - nameBtn: { background: 'none', border: 'none', cursor: 'pointer', fontWeight: 600, color: '#667eea', fontSize: '14px', padding: 0, textDecoration: 'underline dotted' }, - atRiskBadge: { display: 'inline-block', marginLeft: '8px', padding: '2px 8px', borderRadius: '10px', fontSize: '10px', fontWeight: 700, background: '#fff3cd', color: '#856404', border: '1px solid #ffc107', verticalAlign: 'middle' }, - zeroRow: { color: '#aaa', fontStyle: 'italic', fontSize: '12px' }, - refreshBtn:{ padding: '9px 18px', background: '#667eea', color: 'white', 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' }, + refreshBtn:{ padding: '9px 18px', background: '#d4af37', color: '#000', border: 'none', 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 [loading, setLoading] = useState(true); - const load = useCallback(() => { - setLoading(true); - axios.get('/api/dashboard') - .then(r => { setEmployees(r.data); setFiltered(r.data); }) - .finally(() => setLoading(false)); - }, []); + const load = useCallback(() => { + setLoading(true); + axios.get('/api/dashboard') + .then(r => { setEmployees(r.data); setFiltered(r.data); }) + .finally(() => setLoading(false)); + }, []); - useEffect(() => { load(); }, [load]); + useEffect(() => { load(); }, [load]); - useEffect(() => { - const q = search.toLowerCase(); - setFiltered(employees.filter(e => - e.name.toLowerCase().includes(q) || - (e.department || '').toLowerCase().includes(q) || - (e.supervisor || '').toLowerCase().includes(q) - )); - }, [search, employees]); + useEffect(() => { + const q = search.toLowerCase(); + setFiltered(employees.filter(e => + e.name.toLowerCase().includes(q) || + (e.department || '').toLowerCase().includes(q) || + (e.supervisor || '').toLowerCase().includes(q) + )); + }, [search, employees]); - const atRiskCount = employees.filter(e => isAtRisk(e.active_points)).length; - const activeCount = employees.filter(e => e.active_points > 0).length; - const cleanCount = employees.filter(e => e.active_points === 0).length; - const maxPoints = employees.reduce((m, e) => Math.max(m, e.active_points), 0); + const atRiskCount = employees.filter(e => isAtRisk(e.active_points)).length; + const activeCount = employees.filter(e => e.active_points > 0).length; + const cleanCount = employees.filter(e => e.active_points === 0).length; + const maxPoints = employees.reduce((m, e) => Math.max(m, e.active_points), 0); - return ( -
-
-
-
Company Dashboard
-
Click any employee name to view their full profile
-
-
- setSearch(e.target.value)} /> - -
-
- - {/* ── Stat cards ───────────────────────────────────────── */} -
-
-
{employees.length}
-
Total Employees
-
-
-
{cleanCount}
-
Elite Standing (0 pts)
-
-
-
{activeCount}
-
With Active Points
-
-
-
{atRiskCount}
-
At Risk (≤{AT_RISK_THRESHOLD} pts to next tier)
-
-
-
{maxPoints}
-
Highest Active Score
-
-
- - {/* ── Scoreboard table ─────────────────────────────────── */} - {loading ? ( -

Loading…

- ) : ( - - - - - - - - - - - - - - {filtered.length === 0 && ( - - )} - {filtered.map((emp, i) => { - const risk = isAtRisk(emp.active_points); - const tier = getTier(emp.active_points); - const boundary = nextTierBoundary(emp.active_points); - return ( - - - - - - - - - - ); - })} - -
#EmployeeDepartmentSupervisorTier / StandingActive Points90-Day Violations
No employees found.
{i + 1} - - {risk && ( - - ⚠ {boundary - emp.active_points} pt{boundary - emp.active_points > 1 ? 's' : ''} to {getTier(boundary).label.split('—')[0].trim()} - - )} - {emp.department || '—'}{emp.supervisor || '—'}{emp.active_points}{emp.violation_count}
- )} - - {/* ── Employee profile modal ───────────────────────────── */} - {selectedId && ( - { setSelectedId(null); load(); }} - /> - )} + return ( +
+
+
+
Company Dashboard
+
Click any employee name to view their full profile
- ); +
+ setSearch(e.target.value)} /> + +
+
+ +
+
+
{employees.length}
+
Total Employees
+
+
+
{cleanCount}
+
Elite Standing (0 pts)
+
+
+
{activeCount}
+
With Active Points
+
+
+
{atRiskCount}
+
At Risk (≤{AT_RISK_THRESHOLD} pts to next tier)
+
+
+
{maxPoints}
+
Highest Active Score
+
+
+ + {loading ? ( +

Loading…

+ ) : ( + + + + + + + + + + + + + + {filtered.length === 0 && ( + + )} + {filtered.map((emp, i) => { + const risk = isAtRisk(emp.active_points); + const tier = getTier(emp.active_points); + const boundary = nextTierBoundary(emp.active_points); + return ( + + + + + + + + + + ); + })} + +
#EmployeeDepartmentSupervisorTier / StandingActive Points90-Day Violations
No employees found.
{i + 1} + + {risk && ( + + ⚠ {boundary - emp.active_points} pt{boundary - emp.active_points > 1 ? 's' : ''} to {getTier(boundary).label.split('—')[0].trim()} + + )} + {emp.department || '—'}{emp.supervisor || '—'}{emp.active_points}{emp.violation_count}
+ )} + + {selectedId && ( + { setSelectedId(null); load(); }} + /> + )} +
+ ); }