Docs consolidation + audit-log timezone fix + PTO-Exhausted sliding scale
Build and Push Docker Image / build (push) Successful in 49s
Build and Push Docker Image / build (push) Successful in 49s
Documentation: - Consolidate 6 doc surfaces to 3. Merge Unraid guide into README and fold durable mobile rules into AGENTS.md; remove README_UNRAID_INSTALL.md, MOBILE_RESPONSIVE.md, and the misplaced "CPAS Violation Tracker.md" vault note. - Fix drift across README/AGENTS/in-app guide: clean-cycle roll-off model (not per-violation 90-day window), auth documented as shipped, dropped active_cpas_scores view, correct table count (8 tables/0 views), auth endpoints + ADMIN_PASSWORD in run commands, roadmap updated. Fixes: - Audit log / amendment timestamps: SQLite CURRENT_TIMESTAMP is UTC with no zone marker; new Date() parsed the space-separated form as local time, shifting entries +5h in Chicago/CDT. Pin the bare form to UTC before formatting in AuditLog.jsx and AmendViolationModal.jsx. - Absence - PTO Exhausted: fixed 5 pts -> sliding scale 1-5. Rebuild client bundle to include the above. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+33
-27
File diff suppressed because one or more lines are too long
Vendored
+13
-13
@@ -1,18 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>CPAS Violation Tracker</title>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>CPAS Violation Tracker</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
html, body { margin: 0; padding: 0; height: 100%; }
|
||||
#root { height: 100%; }
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/index-C1XYfYmx.js"></script>
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/index-DhWLw3RT.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BoQf6yV_.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
</body>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -73,9 +73,13 @@ const s = {
|
||||
},
|
||||
};
|
||||
|
||||
// SQLite CURRENT_TIMESTAMP is UTC as "YYYY-MM-DD HH:MM:SS" with no zone marker;
|
||||
// JS Date() would parse that space-separated form as local time and shift it by
|
||||
// the viewer's UTC offset. Pin the bare SQLite form to UTC before formatting.
|
||||
function fmtDt(iso) {
|
||||
if (!iso) return '—';
|
||||
return new Date(iso).toLocaleString('en-US', { timeZone: 'America/Chicago', dateStyle: 'medium', timeStyle: 'short' });
|
||||
const utc = /^\d{4}-\d\d-\d\d \d\d:\d\d:\d\d$/.test(iso) ? iso.replace(' ', 'T') + 'Z' : iso;
|
||||
return new Date(utc).toLocaleString('en-US', { timeZone: 'America/Chicago', dateStyle: 'medium', timeStyle: 'short' });
|
||||
}
|
||||
|
||||
export default function AmendViolationModal({ violation, onClose, onSaved }) {
|
||||
|
||||
@@ -85,9 +85,14 @@ const s = {
|
||||
},
|
||||
};
|
||||
|
||||
// SQLite CURRENT_TIMESTAMP returns UTC as "YYYY-MM-DD HH:MM:SS" with no zone
|
||||
// marker. JS Date() parses that space-separated form as LOCAL time, shifting
|
||||
// every entry by the viewer's UTC offset (the ~5h drift seen from Chicago/CDT).
|
||||
// Detect the bare SQLite form and pin it to UTC before formatting.
|
||||
function fmtDt(iso) {
|
||||
if (!iso) return '—';
|
||||
return new Date(iso).toLocaleString('en-US', {
|
||||
const utc = /^\d{4}-\d\d-\d\d \d\d:\d\d:\d\d$/.test(iso) ? iso.replace(' ', 'T') + 'Z' : iso;
|
||||
return new Date(utc).toLocaleString('en-US', {
|
||||
timeZone: 'America/Chicago', dateStyle: 'medium', timeStyle: 'short',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -112,7 +112,13 @@ Roles: **Admin** can manage users plus everything a User can do; **User** can vi
|
||||
|
||||
## How Scoring Works
|
||||
|
||||
Every violation carries a **point value** set at the time of submission. Points count toward an employee's score only within a **rolling 90-day window** — once a violation is older than 90 days it automatically drops off and the score recalculates.
|
||||
Every violation carries a **point value** set at the time of submission. Points do **not** each expire on their own 90-day timer. Instead, CPAS uses a **clean-cycle roll-off**:
|
||||
|
||||
- An employee's points start retiring only after they go a full **90 consecutive days with no new violation**.
|
||||
- **Any new violation resets that 90-day clock** — the whole balance stays put and the countdown starts over.
|
||||
- Each completed 90-day clean stretch removes **5 points, oldest violation first**. Dropping another 5 requires another full 90 clean days.
|
||||
|
||||
In short: staying clean is what burns points down, and a single new incident pushes everyone's roll-off back to square one.
|
||||
|
||||
Negated (voided) violations are excluded from scoring immediately. Hard-deleted violations are removed from the record entirely.
|
||||
|
||||
@@ -172,13 +178,13 @@ Shows current tier badge, active points, and 90-day violation count.
|
||||
#### Notes & Flags
|
||||
Free-text field for HR context (e.g. "On PIP", "Union member", "Pending investigation", "FMLA"). Quick-add tag buttons pre-fill common statuses. Notes are visible to anyone who opens the profile but **do not affect CPAS scoring**. Edit inline; saves on blur.
|
||||
|
||||
#### Point Expiration Timeline
|
||||
Visible when the employee has active points. Shows each active violation as a progress bar indicating how far through its 90-day window it is, days remaining until roll-off, and a **tier-drop indicator** for violations whose expiration would move the employee down a tier.
|
||||
#### Point Roll-Off Timeline
|
||||
Visible when the employee has active points. Lists each future roll-off event (Roll-off #1, #2, …) — how many points retire, a progress bar and days-remaining countdown to that event, the projected date, and a **tier-drop indicator** where a roll-off would move the employee down a tier. It reflects the clean-cycle model: the countdown is anchored to the last violation date, and logging a new violation resets it.
|
||||
|
||||
#### Violation History
|
||||
Full record of all submissions — active, negated, and resolved.
|
||||
|
||||
- **Amend** — edit non-scoring fields (location, details, witness, submitted-by, incident time, acknowledged-by, acknowledged-date) on any active violation. Every change is logged as a field-level diff (old → new) with timestamp. Points, type, and incident date are immutable.
|
||||
- **Amend** — edit non-scoring fields (location, details, witness, submitted-by, incident time, acknowledged-by, acknowledged-date, amount) on any active violation. Every change is logged as a field-level diff (old → new) with timestamp. Points, type, and incident date are immutable.
|
||||
- **Negate** — soft-delete a violation with a resolution type and notes. The record is preserved in history; the points are immediately removed from the score. Fully reversible via **Restore**.
|
||||
- **Hard delete** — permanent removal. Use only for genuine data entry errors.
|
||||
- **PDF** — download the formal violation document for any historical record. If the violation has an employee acknowledgment on record, the PDF shows the filled-in name and date instead of blank signature lines.
|
||||
@@ -234,7 +240,7 @@ The audit log is the authoritative record for compliance review. Nothing in it c
|
||||
|
||||
Amendments allow corrections to a violation's non-scoring fields without deleting and re-submitting, which would disrupt the audit trail and the prior-points snapshot.
|
||||
|
||||
**Amendable fields:** incident time, location, details, submitted-by, witness name, acknowledged-by, acknowledged-date.
|
||||
**Amendable fields:** incident time, location, details, submitted-by, witness name, acknowledged-by, acknowledged-date, amount (dollar figure on financial violations).
|
||||
|
||||
**Immutable fields:** violation type, incident date, point value.
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ export const violationData = {
|
||||
},
|
||||
pto_exhausted: {
|
||||
name: 'Absence - PTO Exhausted', category: 'Attendance & Punctuality',
|
||||
minPoints: 5, maxPoints: 5, chapter: 'Chapter 4, Section 5',
|
||||
minPoints: 1, maxPoints: 5, chapter: 'Chapter 4, Section 5',
|
||||
fields: ['description'],
|
||||
description: 'Any absence after PTO bank reaches zero'
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user