Files
cpas/README.md

402 lines
20 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CPAS Violation Tracker
Single-container Dockerized web app for CPAS violation documentation and workforce standing management.
Built with **React + Vite** (frontend), **Node.js + Express** (backend), **SQLite** (database), and **Puppeteer** (PDF generation).
> © Jason Stedwell · [git.alwisp.com/jason/cpas](https://git.alwisp.com/jason/cpas)
---
## The only requirement on your machine: Docker Desktop
Everything else — Node.js, npm, React build, Chromium for PDF — happens inside Docker.
---
## Quickstart (Local)
```bash
# 1. Build the image (installs all deps + compiles React inside Docker)
docker build -t cpas .
# 2. Run it
docker run -d --name cpas \
-p 3001:3001 \
-v cpas-data:/data \
cpas
# 3. Open
# http://localhost:3001
```
## Update After Code Changes
```bash
docker build -t cpas .
docker stop cpas && docker rm cpas
docker run -d --name cpas -p 3001:3001 -v cpas-data:/data cpas
```
---
## Deploying on Unraid
### Step 1 — Build and export the image on your dev machine
```bash
docker build -t cpas:latest .
docker save cpas:latest | gzip > cpas-latest.tar.gz
```
### Step 2 — Load the image on Unraid
Transfer `cpas-latest.tar.gz` to your Unraid server, then load it via the Unraid terminal:
```bash
docker load < /path/to/cpas-latest.tar.gz
```
Confirm the image is present:
```bash
docker images | grep cpas
```
### Step 3 — Create the appdata directory
```bash
mkdir -p /mnt/user/appdata/cpas/db
```
### Step 4 — Run the container
This is the verified working `docker run` command for Unraid (bridge networking with static IP):
```bash
docker run \
-d \
--name='cpas' \
--net='br0' \
--ip='10.2.0.14' \
--pids-limit 2048 \
-e TZ="America/Chicago" \
-e HOST_OS="Unraid" \
-e HOST_HOSTNAME="ALPHA" \
-e HOST_CONTAINERNAME="cpas" \
-e 'PORT'='3001' \
-e 'DB_PATH'='/data/cpas.db' \
-l net.unraid.docker.managed=dockerman \
-l net.unraid.docker.webui='http://[IP]:[PORT:3001]' \
-v '/mnt/user/appdata/cpas/db':'/data':'rw' \
cpas:latest
```
Access the app at `http://10.2.0.14:3001` (or whatever static IP you assigned).
### Key settings explained
| Setting | Value | Notes |
|---------|-------|-------|
| `--net` | `br0` | Unraid custom bridge network — gives the container its own LAN IP |
| `--ip` | `10.2.0.14` | Static IP on your LAN — adjust to match your subnet |
| `--pids-limit` | `2048` | Required — Puppeteer/Chromium spawns many processes for PDF generation; default Unraid limit is too low and will cause PDF failures |
| `PORT` | `3001` | Express listen port inside the container |
| `DB_PATH` | `/data/cpas.db` | SQLite database path inside the container |
| Volume | `/mnt/user/appdata/cpas/db``/data` | Persists the database across container restarts and rebuilds |
### Updating on Unraid
1. Build and export the new image on your dev machine (Step 1 above)
2. Load it on Unraid: `docker load < cpas-latest.tar.gz`
3. Stop and remove the old container: `docker stop cpas && docker rm cpas`
4. Re-run the `docker run` command from Step 4 — the volume mount preserves all data
> **Note:** The `--pids-limit 2048` flag is critical. Without it, Chromium hits Unraid's default PID limit and PDF generation silently fails or crashes the container.
---
## Stakeholder Demo
A standalone demo page with synthetic data is available at `/demo` (e.g. `http://localhost:3001/demo`).
It is served as a static route before the SPA catch-all and requires no authentication.
Useful for showing the app to stakeholders without exposing live employee data.
---
## Features
### Company Dashboard
- Live table of all employees sorted by active CPAS points (highest risk first)
- Summary stat cards: total employees, elite standing (0 pts), with active points, at-risk count, highest active score
- **At-risk badge**: flags employees within 2 points of the next tier escalation
- Search/filter by name, department, or supervisor
- **Department filter**: pre-loaded dropdown of all departments for quick scoped views
- Click any employee name to open their full profile modal
- **📋 Audit Log** button — filterable, paginated view of all system write actions
### Violation Form
- Select existing employee or enter new employee by name
- **Employee intelligence**: shows current CPAS standing badge and 90-day violation count before submitting
- Violation type dropdown grouped by category; shows prior 90-day counts inline
- **Recidivist auto-escalation**: if an employee has prior violations of the same type, points slider auto-sets to maximum per policy
- Repeat offense badge with prior count displayed
- Context-sensitive fields (time, minutes late, amount, location, description) shown only when relevant to violation type
- **Tier crossing warning** (TierWarning component): previews what tier the new points would push the employee into before submission
- Point slider for discretionary adjustments within the violation's min/max range
- **Employee Acknowledgment section**: optional "received by employee" name and date fields; when filled, the PDF signature block shows the recorded acknowledgment instead of a blank signature line
- One-click PDF download immediately after submission
- **Toast notifications**: success/error/warning feedback for form submissions, validation, and PDF downloads
### Employee Profile Modal
- Full violation history with resolution status and **amendment count badge** per record
- **✎ Edit Employee** button — update name, department, supervisor, or notes inline
- **Merge Duplicate** tab — reassign all violations from a duplicate record and delete it
- **Amend** button per active violation — edit non-scoring fields (location, notes, witness, acknowledgment, etc.) with a full field-level diff history
- Negate / restore individual violations (soft delete with resolution type + notes)
- Hard delete option for data entry errors
- PDF download for any historical violation record
- **Notes & Flags** — free-text notes (e.g. "on PIP", "union member") with quick-add tag buttons; visible in the profile modal without affecting scoring
- **Point Expiration Timeline** — shows when each active violation rolls off the 90-day window, with a progress bar, days-remaining countdown, and projected tier-drop indicators
- **Toast notifications** for all actions: negate, restore, delete, amend, PDF download, employee edit
### Audit Log
- Append-only log of every write action: employee created/edited/merged, violation logged/amended/negated/restored/deleted
- Filterable by entity type (employee / violation) and action
- Paginated with load-more; accessible from the Dashboard toolbar
### Violation Amendment
- Edit submitted violations' non-scoring fields without delete-and-resubmit
- Point values, violation type, and incident date are immutable
- Every change is stored as a field-level diff (old → new value) with timestamp and actor
### In-App Documentation
- **? Docs** button in the navbar opens a slide-in admin reference panel
- Covers feature map, CPAS tier system, workflow guidance, and roadmap
- No external link required; always reflects current deployed version
### Toast Notification System
- Global toast notifications for all user actions across the application
- Four variants: success (green), error (red), warning (gold), info (blue)
- Auto-dismiss with configurable duration and visual progress bar countdown
- Slide-in animation; stacks up to 5 notifications simultaneously
- Consistent dark theme styling matching the rest of the UI
### App Footer
- **© Jason Stedwell** copyright with auto-advancing year
- **Live dev ticker**: real-time elapsed counter since first commit (`2026-03-06`), ticking every second in `Xd HHh MMm SSs` format with a pulsing green dot
- **Gitea repo link** with icon — links directly to `git.alwisp.com/jason/cpas`
### CPAS Tier System
| Points | Tier | Label |
|--------|------|-------|
| 04 | 01 | Elite Standing |
| 59 | 1 | Realignment |
| 1014 | 2 | Administrative Lockdown |
| 1519 | 3 | Verification |
| 2024 | 4 | Risk Mitigation |
| 2529 | 5 | Final Decision |
| 30+ | 6 | Separation |
Scores are computed over a **rolling 90-day window** (negated violations excluded).
### PDF Generation
- Puppeteer + system Chromium (bundled in Docker image)
- Logo loaded from disk at startup (no hardcoded base64); falls back gracefully if not found
- Generated on-demand per violation via `GET /api/violations/:id/pdf`
- Filename: `CPAS_<EmployeeName>_<IncidentDate>.pdf`
- PDF captures prior active points **at the time of the incident** (snapshot stored on insert)
- **Acknowledgment rendering**: if the violation has an `acknowledged_by` value, the employee signature block on the PDF shows the recorded name and date with an "Acknowledged" badge; otherwise, blank signature lines are rendered for wet-ink signing
---
## API Reference
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/health` | Health check |
| GET | `/api/employees` | List all employees (includes `notes`) |
| POST | `/api/employees` | Create or upsert employee |
| PATCH | `/api/employees/:id` | Edit name, department, supervisor, or notes |
| POST | `/api/employees/:id/merge` | Merge duplicate employee; reassigns all violations |
| GET | `/api/employees/:id/score` | Get active CPAS score for employee |
| GET | `/api/employees/:id/expiration` | Active violation roll-off timeline with days remaining |
| PATCH | `/api/employees/:id/notes` | Save employee notes only (shorthand) |
| GET | `/api/dashboard` | All employees with active points + violation counts |
| POST | `/api/violations` | Log a new violation (accepts `acknowledged_by`, `acknowledged_date`) |
| GET | `/api/violations/employee/:id` | Violation history with resolutions + amendment counts |
| PATCH | `/api/violations/:id/negated` | Negate a violation (soft delete + resolution record) |
| PATCH | `/api/violations/:id/restore` | Restore a negated violation |
| PATCH | `/api/violations/:id/amend` | Amend non-scoring fields with field-level diff logging |
| GET | `/api/violations/:id/amendments` | Get amendment history for a violation |
| DELETE | `/api/violations/:id` | Hard delete a violation |
| GET | `/api/violations/:id/pdf` | Download violation PDF |
| GET | `/api/audit` | Paginated audit log (filterable by `entity_type`, `entity_id`) |
---
## Project Structure
```
cpas/
├── Dockerfile # Multi-stage: builds React + runs Express w/ Chromium
├── .dockerignore
├── package.json # Backend (Express) deps
├── server.js # API + static file server
├── db/
│ ├── schema.sql # Tables + 90-day active score view
│ └── database.js # SQLite connection (better-sqlite3) + auto-migrations
├── pdf/
│ ├── generator.js # Puppeteer PDF generation
│ └── template.js # HTML template (loads logo from disk, ack signature rendering)
├── demo/ # Static stakeholder demo page (served at /demo)
└── client/ # React frontend (Vite)
├── package.json
├── vite.config.js
├── index.html
└── src/
├── main.jsx
├── App.jsx # Root app + AppFooter (copyright, dev ticker, Gitea link)
├── data/
│ └── violations.js # All CPAS violation definitions + groups
├── hooks/
│ └── useEmployeeIntelligence.js # Score + history hook
└── components/
├── CpasBadge.jsx # Tier badge + color logic
├── TierWarning.jsx # Pre-submit tier crossing alert
├── Dashboard.jsx # Company-wide leaderboard + audit log trigger
├── ViolationForm.jsx # Violation entry form + ack signature fields
├── EmployeeModal.jsx # Employee profile + history modal
├── EditEmployeeModal.jsx # Employee edit + merge duplicate
├── AmendViolationModal.jsx # Non-scoring field amendment + diff history
├── AuditLog.jsx # Filterable audit log panel
├── NegateModal.jsx # Negate/resolve violation dialog
├── ViolationHistory.jsx # Violation list component
├── ExpirationTimeline.jsx # Per-violation 90-day roll-off countdown
├── EmployeeNotes.jsx # Inline notes editor with quick-add HR tags
├── ToastProvider.jsx # Global toast notification system + useToast hook
└── ReadmeModal.jsx # In-app admin documentation panel
```
---
## Database Schema
Six tables + one view:
- **`employees`** — id, name, department, supervisor, **notes**
- **`violations`** — full incident record including `prior_active_points` snapshot at time of logging, `acknowledged_by` and `acknowledged_date` for employee acknowledgment
- **`violation_resolutions`** — resolution type, details, resolved_by (linked to violations)
- **`violation_amendments`** — field-level diff log for violation edits; one row per changed field per amendment
- **`audit_log`** — append-only record of every write action (action, entity_type, entity_id, performed_by, details, timestamp)
- **`active_cpas_scores`** (view) — sum of points for non-negated violations in rolling 90 days, grouped by employee
---
## Amendable Fields
Point values, violation type, and incident date are **immutable** after submission. The following fields can be amended:
| Field | Notes |
|-------|-------|
| `incident_time` | Time of day the incident occurred |
| `location` | Where the incident took place |
| `details` | Narrative description |
| `submitted_by` | Supervisor who submitted |
| `witness_name` | Witness on record |
| `acknowledged_by` | Employee who acknowledged receipt |
| `acknowledged_date` | Date of employee acknowledgment |
---
## Roadmap
### ✅ Completed
| Phase | Feature | Description |
|-------|---------|-------------|
| 1 | Container scaffold | Docker multi-stage build, Express server, SQLite schema |
| 1 | Base violation form | Employee fields, violation type, incident date, point submission |
| 2 | Employee intelligence | Live CPAS standing badge and 90-day count shown before submitting |
| 2 | Prior violation highlighting | Violation dropdown annotates types with 90-day recurrence counts |
| 2 | Recidivist auto-escalation | Points slider auto-maximizes on repeat same-type violations |
| 2 | Violation history | Per-employee history list with resolution status |
| 3 | PDF generation | Puppeteer/Chromium PDF per violation, downloadable immediately post-submit |
| 3 | Prior-points snapshot | `prior_active_points` captured at insert time for accurate historical PDFs |
| 4 | Company dashboard | Sortable employee table with live tier badges and at-risk flags |
| 4 | Stat cards | Summary counts: total, clean, active, at-risk, highest score |
| 4 | Tier crossing warning | Pre-submit alert when new points push employee to next tier |
| 4 | Employee profile modal | Full history, negate/restore, hard delete, per-record PDF download |
| 4 | Negate & restore | Soft-delete violations with resolution type + notes, fully reversible |
| 5 | Employee edit / merge | Update employee name/dept/supervisor; merge duplicate records without losing history |
| 5 | Violation amendment | Edit non-scoring fields with field-level audit trail |
| 5 | Audit log | Append-only log of all system writes; filterable panel in the dashboard |
| 6 | Employee notes / flags | Free-text notes on employee record with quick-add HR tags; does not affect scoring |
| 6 | Point expiration timeline | Per-violation roll-off countdown with tier-drop projections |
| 6 | In-app documentation | Admin usage guide and feature map accessible from the navbar |
| 7 | Acknowledgment signature field | "Received by employee" name + date on the violation form; renders on the PDF replacing blank signature lines with recorded acknowledgment |
| 7 | Toast notification system | Global success/error/warning/info notifications for all user actions; auto-dismiss with progress bar; consistent dark theme |
| 7 | Department dropdown | Pre-loaded select on the violation form replacing free-text department input; shared `DEPARTMENTS` constant |
| 8 | Stakeholder demo page | Standalone `/demo` route with synthetic data; static HTML served before SPA catch-all; useful for non-live presentations |
| 8 | App footer | Copyright (© Jason Stedwell), live dev ticker since first commit, Gitea repo icon+link |
---
### 📋 Proposed
Effort ratings: 🟢 Low · 🟡 Medium · 🔴 High
#### Quick Wins (High value, low effort)
| Feature | Effort | Description |
|---------|--------|-------------|
| Column sort on dashboard | 🟢 | Click `Tier`, `Active Points`, or `Department` headers to sort in-place; one `useState` + comparator, no API changes |
| Department filter on dashboard | 🟢 | Multi-select dropdown to scope the employee table by department; `DEPARTMENTS` constant already exists |
| Keyboard shortcut: New Violation | 🟢 | `N` key triggers tab switch to the violation form; ~5 lines of code |
| CSV export of dashboard | 🟢 | Client-side Blob download of the current filtered employee view; no backend changes needed |
#### Reporting & Analytics
| Feature | Effort | Description |
|---------|--------|-------------|
| Violation trend chart | 🟡 | Line/bar chart of violations per day/week/month, filterable by department or supervisor; useful for identifying systemic patterns |
| Department heat map | 🟡 | Grid view showing violation density and average CPAS score by department; helps supervisors identify team-level risk |
| Violation sparklines per employee | 🟡 | Tiny inline bar chart of points over the last 6 months in the employee modal |
| CSV / Excel bulk export | 🟡 | Full export of violations or dashboard data for external reporting or payroll integration |
#### Employee Management
| Feature | Effort | Description |
|---------|--------|-------------|
| Supervisor scoped view | 🟡 | Dashboard filtered to a supervisor's direct reports, accessible via URL param (`?supervisor=Name`); no schema changes required |
| Employee photo / avatar | 🟢 | Optional avatar upload stored alongside the employee record; shown in the profile modal and dashboard row |
#### Violation Workflow
| Feature | Effort | Description |
|---------|--------|-------------|
| Draft / pending violations | 🟡 | Save a violation as draft before finalizing; useful when incidents need review before being officially logged |
| Bulk violation import | 🔴 | CSV import for migrating historical records from paper logs or a prior system |
| Violation templates | 🟢 | Pre-fill the form with a saved violation type + common details for frequently logged incidents |
#### Notifications & Escalation
| Feature | Effort | Description |
|---------|--------|-------------|
| Scheduled expiration digest | 🟡 | Weekly or daily email listing violations rolling off in the next 7 days; `nodemailer` + cron on the Node server |
| Tier escalation alerts | 🟡 | Email or in-app notification when an employee crosses into Tier 2+ so the relevant supervisor is automatically informed |
| At-risk threshold config | 🟢 | Make the "at-risk" warning threshold (currently hardcoded at 2 pts) configurable per deployment via an env var |
| version.json / build badge | 🟢 | Inject git SHA + build timestamp into a static file during `docker build`; surfaced in the footer and `/api/health` |
#### Infrastructure & Ops
| Feature | Effort | Description |
|---------|--------|-------------|
| Multi-user auth | 🔴 | Simple login with role-based access (admin, supervisor, read-only); currently the app runs on a trusted internal network with no auth |
| Automated DB backup | 🟡 | Cron job or Docker health hook to snapshot `/data/cpas.db` to a mounted backup volume or remote location on a schedule |
| Dark/light theme toggle | 🟡 | The UI is currently dark-only; a toggle would improve usability in bright environments |
---
*Proposed features are suggestions based on common HR documentation workflows. Priority and implementation order should be driven by actual operational needs.*