This commit is contained in:
@@ -86,6 +86,11 @@ const s = {
|
||||
fontSize: '9px', fontWeight: 700, background: '#0e2a2a', color: '#4db6ac',
|
||||
border: '1px solid #1a4a4a', verticalAlign: 'middle',
|
||||
},
|
||||
backfillBtn: {
|
||||
background: 'none', border: '1px solid #d4af37', color: '#ffd666',
|
||||
borderRadius: '4px', padding: '4px 10px', fontSize: '11px',
|
||||
cursor: 'pointer', fontWeight: 600,
|
||||
},
|
||||
};
|
||||
|
||||
export default function EmployeeModal({ employeeId, onClose }) {
|
||||
@@ -156,6 +161,26 @@ export default function EmployeeModal({ employeeId, onClose }) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleRecomputeSnapshots = async () => {
|
||||
if (!window.confirm(
|
||||
'Rebuild the "Prior Active Points" snapshot on every violation for this employee?\n\n' +
|
||||
'Use this after back-dating a violation if older PDFs no longer reflect the correct prior-points total. ' +
|
||||
'Existing PDFs will regenerate with up-to-date numbers.'
|
||||
)) return;
|
||||
try {
|
||||
const r = await axios.post(`/api/employees/${employeeId}/recompute-snapshots`);
|
||||
const { scanned, updated } = r.data;
|
||||
if (updated === 0) {
|
||||
toast.success(`Snapshots already up to date (${scanned} checked).`);
|
||||
} else {
|
||||
toast.success(`Updated ${updated} of ${scanned} snapshot${updated === 1 ? '' : 's'}.`);
|
||||
}
|
||||
load();
|
||||
} catch (err) {
|
||||
toast.error('Backfill failed: ' + (err.response?.data?.error || err.message));
|
||||
}
|
||||
};
|
||||
|
||||
const handleNegate = async ({ resolution_type, details, resolved_by }) => {
|
||||
try {
|
||||
await axios.patch(`/api/violations/${negating.id}/negate`, { resolution_type, details, resolved_by });
|
||||
@@ -251,7 +276,18 @@ export default function EmployeeModal({ employeeId, onClose }) {
|
||||
)}
|
||||
|
||||
{/* ── Active Violations ── */}
|
||||
<div style={s.sectionHd}>Active Violations</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: '24px', marginBottom: '10px' }}>
|
||||
<div style={{ ...s.sectionHd, marginTop: 0, marginBottom: 0 }}>Active Violations</div>
|
||||
{violations.length > 0 && (
|
||||
<button
|
||||
style={s.backfillBtn}
|
||||
onClick={handleRecomputeSnapshots}
|
||||
title="Rebuild prior-points snapshot on each violation. Use after a back-dated insert if older PDFs show the wrong Prior Active Points."
|
||||
>
|
||||
↻ Backfill Snapshots
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{active.length === 0 ? (
|
||||
<div style={{ color: '#777990', fontStyle: 'italic', fontSize: '12px' }}>
|
||||
No active violations on record.
|
||||
|
||||
@@ -164,6 +164,31 @@ Update name, department, or supervisor. Changes are logged to the audit trail.
|
||||
#### Merge Duplicate
|
||||
If the same employee exists under two names, use Merge to reassign all violations from the duplicate to the canonical record. The duplicate is then deleted. This cannot be undone.
|
||||
|
||||
#### Backfill Snapshots (repair tool)
|
||||
|
||||
Each violation stores a **prior-points snapshot** at submission time so its PDF always shows the score *as it was on the incident date*. Normally you never touch this — it's set on insert, refreshed automatically when a back-dated violation lands inside another violation's 90-day window, and otherwise locked. PDFs stay stable through negate/restore by design.
|
||||
|
||||
The **↻ Backfill Snapshots** button sits next to the **Active Violations** header in the profile modal. It rebuilds the snapshot on every violation for that employee using current data.
|
||||
|
||||
**When to use it:**
|
||||
|
||||
- After back-dating a violation, an older PDF still shows "Prior Active Points: 0" even though an earlier violation now clearly exists in the timeline.
|
||||
- More generally: any time a regenerated PDF disagrees with what you see in the Point Expiration Timeline (the timeline is computed live; PDFs use the snapshot).
|
||||
|
||||
**When *not* to use it:**
|
||||
|
||||
- After a negate, restore, amend, or hard delete in normal workflow. The system intentionally keeps existing PDFs stable through those operations.
|
||||
- As a routine maintenance step. If you keep needing it after ordinary back-dated inserts, that's a bug worth reporting — the auto-refresh should already be covering you.
|
||||
|
||||
**What clicking it does:**
|
||||
|
||||
1. Iterates every violation belonging to the employee (active and negated).
|
||||
2. Recomputes each row's prior-points snapshot from the current set of non-negated violations in the 90 days before its incident date.
|
||||
3. Writes only the rows that actually changed.
|
||||
4. Records one entry in the audit log (action: \`violation_snapshots_recomputed\`, reason: \`manual_backfill\`) with the per-row before/after values.
|
||||
|
||||
A toast confirms the outcome — either *"Updated X of Y snapshots"* or *"Snapshots already up to date"*. Re-download any affected PDFs after running it; the new totals will appear immediately.
|
||||
|
||||
---
|
||||
|
||||
### Audit Log
|
||||
@@ -217,6 +242,7 @@ Toasts auto-dismiss after a few seconds (errors persist longer). Each toast has
|
||||
| Edit employee name / dept / supervisor | Yes | Logged to audit trail |
|
||||
| Merge duplicate employees | Yes | Irreversible |
|
||||
| Add / edit employee notes | Yes | Does not affect score |
|
||||
| Recompute prior-points snapshot | Yes | Two paths only: auto (back-dated insert) or **↻ Backfill Snapshots** button. Never touched by negate, restore, amend, or hard delete |
|
||||
|
||||
---
|
||||
|
||||
@@ -237,6 +263,7 @@ Toasts auto-dismiss after a few seconds (errors persist longer). Each toast has
|
||||
- In-app admin guide (this panel)
|
||||
- Acknowledgment signature field — employee name + date on form and PDF
|
||||
- Toast notification system — global feedback for all user actions
|
||||
- Backfill Snapshots — per-employee repair tool that rebuilds the prior-points snapshot on every violation when older PDFs drift from current data
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user