Upload files to "client/src/components" #18
@@ -77,6 +77,10 @@ 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}>
|
||||
<div>
|
||||
@@ -84,7 +88,12 @@ export default function Dashboard() {
|
||||
<div style={s.subtitle}>Click any employee name to view their full profile</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
|
||||
<input style={s.search} placeholder="Search name, dept, supervisor…" value={search} onChange={e => setSearch(e.target.value)} />
|
||||
<input
|
||||
style={s.search}
|
||||
placeholder="Search name, dept, supervisor…"
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
/>
|
||||
<button style={s.refreshBtn} onClick={load}>↻ Refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -129,17 +138,26 @@ export default function Dashboard() {
|
||||
</thead>
|
||||
<tbody>
|
||||
{filtered.length === 0 && (
|
||||
<tr><td colSpan={7} style={{ ...s.td, textAlign: 'center', ...s.zeroRow }}>No employees found.</td></tr>
|
||||
<tr>
|
||||
<td colSpan={7} style={{ ...s.td, textAlign: 'center', ...s.zeroRow }}>
|
||||
No employees found.
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{filtered.map((emp, i) => {
|
||||
const risk = isAtRisk(emp.active_points);
|
||||
const tier = getTier(emp.active_points);
|
||||
const boundary = nextTierBoundary(emp.active_points);
|
||||
return (
|
||||
<tr key={emp.id} style={{ background: risk ? '#181200' : i % 2 === 0 ? '#111217' : '#151622' }}>
|
||||
<tr
|
||||
key={emp.id}
|
||||
style={{ background: risk ? '#181200' : i % 2 === 0 ? '#111217' : '#151622' }}
|
||||
>
|
||||
<td style={{ ...s.td, color: '#77798a', fontSize: '12px' }}>{i + 1}</td>
|
||||
<td style={s.td}>
|
||||
<button style={s.nameBtn} onClick={() => setSelectedId(emp.id)}>{emp.name}</button>
|
||||
<button style={s.nameBtn} onClick={() => setSelectedId(emp.id)}>
|
||||
{emp.name}
|
||||
</button>
|
||||
{risk && (
|
||||
<span style={s.atRiskBadge}>
|
||||
⚠ {boundary - emp.active_points} pt{boundary - emp.active_points > 1 ? 's' : ''} to {getTier(boundary).label.split('—')[0].trim()}
|
||||
@@ -149,7 +167,9 @@ export default function Dashboard() {
|
||||
<td style={{ ...s.td, color: '#c0c2d6' }}>{emp.department || '—'}</td>
|
||||
<td style={{ ...s.td, color: '#c0c2d6' }}>{emp.supervisor || '—'}</td>
|
||||
<td style={s.td}><CpasBadge points={emp.active_points} /></td>
|
||||
<td style={{ ...s.td, fontWeight: 700, color: tier.color, fontSize: '16px' }}>{emp.active_points}</td>
|
||||
<td style={{ ...s.td, fontWeight: 700, color: tier.color, fontSize: '16px' }}>
|
||||
{emp.active_points}
|
||||
</td>
|
||||
<td style={{ ...s.td, color: '#c0c2d6' }}>{emp.violation_count}</td>
|
||||
</tr>
|
||||
);
|
||||
@@ -157,13 +177,17 @@ export default function Dashboard() {
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</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(); }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user