From b656c970f091850564994549d4efd0f5977e8ea5 Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 11 Mar 2026 00:11:42 -0500 Subject: [PATCH] feat: stat card badges act as filters; Elite Standing = 0-4 pts --- client/src/components/Dashboard.jsx | 120 ++++++++++++++++++++++++---- 1 file changed, 104 insertions(+), 16 deletions(-) diff --git a/client/src/components/Dashboard.jsx b/client/src/components/Dashboard.jsx index a27bb73..4706e08 100755 --- a/client/src/components/Dashboard.jsx +++ b/client/src/components/Dashboard.jsx @@ -42,15 +42,24 @@ function useMediaQuery(query) { return matches; } +// Filter keys +const FILTER_NONE = null; +const FILTER_TOTAL = 'total'; +const FILTER_ELITE = 'elite'; +const FILTER_ACTIVE = 'active'; +const FILTER_AT_RISK = 'at_risk'; + 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 #303136', borderRadius: '8px', padding: '16px', textAlign: 'center' }, + statCard: { flex: '1', minWidth: '140px', background: '#181924', border: '1px solid #303136', borderRadius: '8px', padding: '16px', textAlign: 'center', cursor: 'pointer', transition: 'border-color 0.15s, box-shadow 0.15s' }, + statCardActive: { boxShadow: '0 0 0 2px #d4af37', border: '1px solid #d4af37' }, statNum: { fontSize: '28px', fontWeight: 800, color: '#f8f9fa' }, statLbl: { fontSize: '11px', color: '#b5b5c0', marginTop: '4px' }, + filterBadge: { fontSize: '10px', color: '#d4af37', marginTop: '4px', fontWeight: 600 }, 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' }, @@ -119,6 +128,7 @@ export default function Dashboard() { const [selectedId, setSelectedId] = useState(null); const [showAudit, setShowAudit] = useState(false); const [loading, setLoading] = useState(true); + const [activeFilter, setActiveFilter] = useState(FILTER_NONE); const isMobile = useMediaQuery('(max-width: 768px)'); const load = useCallback(() => { @@ -130,20 +140,50 @@ export default function Dashboard() { useEffect(() => { load(); }, [load]); + // Apply search + badge filter together 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]); + let base = employees; + + if (activeFilter === FILTER_ELITE) { + base = base.filter(e => e.active_points >= 0 && e.active_points <= 4); + } else if (activeFilter === FILTER_ACTIVE) { + base = base.filter(e => e.active_points > 0); + } else if (activeFilter === FILTER_AT_RISK) { + base = base.filter(e => isAtRisk(e.active_points)); + } + // FILTER_TOTAL and FILTER_NONE show all + + if (q) { + base = base.filter(e => + e.name.toLowerCase().includes(q) || + (e.department || '').toLowerCase().includes(q) || + (e.supervisor || '').toLowerCase().includes(q) + ); + } + + setFiltered(base); + }, [search, employees, activeFilter]); 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; + // Elite Standing: 0–4 pts (Tier 0-1) + const eliteCount = employees.filter(e => e.active_points >= 0 && e.active_points <= 4).length; const maxPoints = employees.reduce((m, e) => Math.max(m, e.active_points), 0); + function handleBadgeClick(filterKey) { + setActiveFilter(prev => prev === filterKey ? FILTER_NONE : filterKey); + } + + function cardStyle(filterKey, extra = {}) { + const isActive = activeFilter === filterKey; + return { + ...s.statCard, + ...(isActive ? s.statCardActive : {}), + ...extra, + }; + } + return ( <> @@ -151,7 +191,19 @@ export default function Dashboard() {
Company Dashboard
-
Click any employee name to view their full profile
+
+ Click any employee name to view their full profile + {activeFilter && activeFilter !== FILTER_NONE && ( + + · Filtered: {activeFilter === FILTER_ELITE ? 'Elite Standing (0–4 pts)' : activeFilter === FILTER_ACTIVE ? 'With Active Points' : activeFilter === FILTER_AT_RISK ? 'At Risk' : 'All'} + + + )} +
-
+ {/* Total Employees — clicking shows all */} +
handleBadgeClick(FILTER_TOTAL)} + title="Click to show all employees" + >
{employees.length}
Total Employees
+ {activeFilter === FILTER_TOTAL &&
▼ Showing All
}
-
-
{cleanCount}
-
Elite Standing (0 pts)
+ + {/* Elite Standing: 0–4 pts */} +
handleBadgeClick(FILTER_ELITE)} + title="Click to filter: Elite Standing (0–4 pts)" + > +
{eliteCount}
+
Elite Standing (0–4 pts)
+ {activeFilter === FILTER_ELITE &&
▼ Filtered
}
-
+ + {/* With Active Points */} +
handleBadgeClick(FILTER_ACTIVE)} + title="Click to filter: employees with active points" + >
{activeCount}
With Active Points
+ {activeFilter === FILTER_ACTIVE &&
▼ Filtered
}
-
+ + {/* At Risk */} +
handleBadgeClick(FILTER_AT_RISK)} + title={`Click to filter: at risk (≤${AT_RISK_THRESHOLD} pts to next tier)`} + >
{atRiskCount}
At Risk (≤{AT_RISK_THRESHOLD} pts to next tier)
+ {activeFilter === FILTER_AT_RISK &&
▼ Filtered
}
-
+ + {/* Highest Score — display only, no filter */} +
{maxPoints}
Highest Active Score
-- 2.49.1