diff --git a/client/src/components/ExpirationTimeline.jsx b/client/src/components/ExpirationTimeline.jsx new file mode 100644 index 0000000..37ca0ef --- /dev/null +++ b/client/src/components/ExpirationTimeline.jsx @@ -0,0 +1,159 @@ +import React, { useEffect, useState } from 'react'; +import axios from 'axios'; + +// Tier thresholds used to compute what tier an employee would drop to +// after a given violation rolls off. +const TIER_THRESHOLDS = [ + { min: 30, label: 'Separation', color: '#ff1744' }, + { min: 25, label: 'Final Decision', color: '#ff6d00' }, + { min: 20, label: 'Risk Mitigation', color: '#ff9100' }, + { min: 15, label: 'Verification', color: '#ffc400' }, + { min: 10, label: 'Administrative Lockdown', color: '#ffea00' }, + { min: 5, label: 'Realignment', color: '#b2ff59' }, + { min: 0, label: 'Elite Standing', color: '#69f0ae' }, +]; + +function getTier(pts) { + return TIER_THRESHOLDS.find(t => pts >= t.min) || TIER_THRESHOLDS[TIER_THRESHOLDS.length - 1]; +} + +function urgencyColor(days) { + if (days <= 7) return '#ff4d4f'; + if (days <= 14) return '#ffa940'; + if (days <= 30) return '#fadb14'; + return '#52c41a'; +} + +const s = { + wrapper: { marginTop: '24px' }, + sectionHd: { + fontSize: '13px', fontWeight: 700, color: '#f8f9fa', textTransform: 'uppercase', + letterSpacing: '0.5px', marginBottom: '10px', + }, + empty: { color: '#777990', fontStyle: 'italic', fontSize: '12px' }, + row: { + display: 'flex', alignItems: 'center', gap: '12px', + padding: '10px 12px', background: '#181924', borderRadius: '6px', + border: '1px solid #2a2b3a', marginBottom: '6px', + }, + bar: (pct, color) => ({ + flex: 1, height: '6px', background: '#2a2b3a', borderRadius: '3px', overflow: 'hidden', + position: 'relative', + }), + barFill: (pct, color) => ({ + position: 'absolute', left: 0, top: 0, bottom: 0, + width: `${Math.min(100, Math.max(0, 100 - pct))}%`, + background: color, borderRadius: '3px', + transition: 'width 0.3s ease', + }), + pill: (color) => ({ + display: 'inline-block', padding: '2px 8px', borderRadius: '10px', + fontSize: '11px', fontWeight: 700, background: `${color}22`, + color, border: `1px solid ${color}55`, whiteSpace: 'nowrap', + }), + pts: { fontSize: '13px', fontWeight: 700, color: '#f8f9fa', minWidth: '28px', textAlign: 'right' }, + name: { fontSize: '12px', color: '#f8f9fa', fontWeight: 600, flex: '0 0 160px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, + date: { fontSize: '11px', color: '#9ca0b8', minWidth: '88px' }, + projBox: { + marginTop: '16px', padding: '12px 14px', background: '#0d1117', + border: '1px solid #2a2b3a', borderRadius: '6px', fontSize: '12px', color: '#b5b5c0', + }, + projRow: { display: 'flex', justifyContent: 'space-between', marginBottom: '4px' }, +}; + +export default function ExpirationTimeline({ employeeId, currentPoints }) { + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setLoading(true); + axios.get(`/api/employees/${employeeId}/expiration`) + .then(r => setItems(r.data)) + .finally(() => setLoading(false)); + }, [employeeId]); + + if (loading) return ( +