import React, { useEffect, useRef } from 'react'; // Minimal Markdown to HTML renderer (headings, bold, inline-code, tables, hr, ul, ol, paragraphs) function mdToHtml(md) { const lines = md.split('\n'); const out = []; let i = 0, inUl = false, inOl = false, inTable = false; const close = () => { if (inUl) { out.push(''); inUl = false; } if (inOl) { out.push(''); inOl = false; } if (inTable) { out.push(''); inTable = false; } }; const inline = s => s.replace(/&/g,'&').replace(//g,'>') .replace(/\*\*(.+?)\*\*/g,'$1') .replace(/`([^`]+)`/g,'$1'); while (i < lines.length) { const line = lines[i]; if (line.startsWith('```')) { close(); i++; while (i < lines.length && !lines[i].startsWith('```')) i++; i++; continue; } if (/^---+$/.test(line.trim())) { close(); out.push('
'); i++; continue; } const hm = line.match(/^(#{1,4})\s+(.+)/); if (hm) { close(); const lv=hm[1].length, id=hm[2].toLowerCase().replace(/[^a-z0-9]+/g,'-'); out.push(`${inline(hm[2])}`); i++; continue; } if (line.trim().startsWith('|')) { const cells = line.trim().replace(/^\||\|$/g,'').split('|').map(c=>c.trim()); if (!inTable) { close(); inTable=true; out.push(''); cells.forEach(c=>out.push(``)); out.push(''); i++; if (i < lines.length && /^[\|\s\-:]+$/.test(lines[i])) i++; continue; } else { out.push(''); cells.forEach(c=>out.push(``)); out.push(''); i++; continue; } } const ul = line.match(/^[-*]\s+(.*)/); if (ul) { if (inTable) close(); if (!inUl) { if (inOl){out.push('');inOl=false;} out.push('');inUl=false;} out.push('
    ');inOl=true; } out.push(`
  1. ${inline(ol[1])}
  2. `); i++; continue; } if (line.trim() === '') { close(); i++; continue; } close(); out.push(`

    ${inline(line)}

    `); i++; } close(); return out.join('\n'); } function buildToc(md) { return md.split('\n').reduce((acc, line) => { const m = line.match(/^(#{1,2})\s+(.+)/); if (m) acc.push({ level: m[1].length, text: m[2], id: m[2].toLowerCase().replace(/[^a-z0-9]+/g,'-') }); return acc; }, []); } // ─── Styles ─────────────────────────────────────────────────────────────────── const S = { overlay: { position:'fixed', inset:0, background:'rgba(0,0,0,0.75)', zIndex:2000, display:'flex', alignItems:'flex-start', justifyContent:'flex-end' }, panel: { background:'#111217', color:'#f8f9fa', width:'760px', maxWidth:'95vw', height:'100vh', overflowY:'auto', boxShadow:'-4px 0 32px rgba(0,0,0,0.85)', display:'flex', flexDirection:'column' }, header: { background:'linear-gradient(135deg,#000000,#151622)', color:'white', padding:'22px 28px', position:'sticky', top:0, zIndex:10, borderBottom:'1px solid #222', display:'flex', alignItems:'center', justifyContent:'space-between' }, closeBtn:{ background:'none', border:'none', color:'white', fontSize:'22px', cursor:'pointer', lineHeight:1 }, toc: { background:'#0d1117', borderBottom:'1px solid #1e1f2e', padding:'10px 32px', display:'flex', flexWrap:'wrap', gap:'4px 18px', fontSize:'11px' }, body: { padding:'28px 32px', flex:1, fontSize:'13px', lineHeight:'1.75' }, footer: { padding:'14px 32px', borderTop:'1px solid #1e1f2e', fontSize:'11px', color:'#555770', textAlign:'center' }, }; const CSS = ` .adm h1 { font-size:21px; font-weight:800; color:#f8f9fa; margin:28px 0 10px; border-bottom:1px solid #2a2b3a; padding-bottom:8px } .adm h2 { font-size:16px; font-weight:700; color:#d4af37; margin:28px 0 6px; letter-spacing:.2px } .adm h3 { font-size:12px; font-weight:700; color:#90caf9; margin:18px 0 4px; text-transform:uppercase; letter-spacing:.5px } .adm h4 { font-size:13px; font-weight:600; color:#b0b8d0; margin:14px 0 4px } .adm p { color:#c8ccd8; margin:5px 0 10px } .adm hr { border:none; border-top:1px solid #2a2b3a; margin:22px 0 } .adm strong { color:#f8f9fa } .adm code { background:#0d1117; color:#79c0ff; border:1px solid #2a2b3a; border-radius:4px; padding:1px 6px; font-family:'Consolas','Fira Code',monospace; font-size:12px } .adm ul { padding-left:20px; margin:5px 0 10px; color:#c8ccd8 } .adm ol { padding-left:20px; margin:5px 0 10px; color:#c8ccd8 } .adm li { margin:4px 0 } .adm table { width:100%; border-collapse:collapse; font-size:12px; background:#181924; border-radius:6px; overflow:hidden; border:1px solid #2a2b3a; margin:10px 0 16px } .adm th { background:#050608; padding:8px 12px; text-align:left; color:#f8f9fa; font-weight:600; font-size:11px; text-transform:uppercase; border-bottom:1px solid #2a2b3a } .adm td { padding:8px 12px; border-bottom:1px solid #202231; color:#c8ccd8 } .adm tr:last-child td { border-bottom:none } .adm tr:hover td { background:#1e1f2e } `; // ─── Admin guide content (no install / Docker content) ──────────────────────── const GUIDE_MD = `# CPAS Tracker — Admin Guide Internal tool for CPAS violation documentation, workforce standing management, and audit compliance. All data is stored locally in the Docker container volume — there is no external dependency. --- ## 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. Negated (voided) violations are excluded from scoring immediately. Hard-deleted violations are removed from the record entirely. ## Tier Reference | Points | Tier | Label | |--------|------|-------| | 0–4 | 0–1 | Elite Standing | | 5–9 | 1 | Realignment | | 10–14 | 2 | Administrative Lockdown | | 15–19 | 3 | Verification | | 20–24 | 4 | Risk Mitigation | | 25–29 | 5 | Final Decision | | 30+ | 6 | Separation | The **at-risk badge** on the dashboard flags anyone within 2 points of the next tier threshold so supervisors can act before escalation occurs. --- ## Feature Map ### Dashboard The main view. Employees are sorted by active CPAS points, highest first. - **Stat cards** — live counts: total employees, zero-point (elite), with active points, at-risk, highest score - **Search / filter** — by name, department, or supervisor; narrows the table in real time - **At-risk badge** — gold flag on rows where the employee is within 2 pts of the next tier - **Audit Log button** — opens the filterable, paginated write-action log (top right of the dashboard toolbar) - **Click any name** — opens that employee's full profile modal --- ### Logging a Violation Use the **+ New Violation** tab. 1. Select an existing employee from the dropdown, or type a new name to create a record on-the-fly. 2. The **employee intelligence panel** loads their current tier badge and 90-day violation count before you commit. 3. Choose a violation type. The dropdown is grouped by category and shows prior 90-day counts inline for each type. 4. If the employee has a prior violation of the same type, the **recidivist auto-escalation** rule triggers — the points slider jumps to the maximum allowed for that violation type. 5. The **tier crossing warning** previews what tier the submission would land the employee in. Review before submitting. 6. Adjust points using the slider if discretionary reduction is warranted (within the violation's allowed min/max range). 7. Submit. A **PDF download link** appears immediately — download it for the employee's file. --- ### Employee Profile Modal Click any name on the dashboard to open their profile. #### Overview section 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. #### Violation History Full record of all submissions — active, negated, and resolved. - **Amend** — edit non-scoring fields (location, details, witness, submitted-by, incident time) 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. #### Edit Employee 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. --- ### Audit Log Accessible from the dashboard toolbar (🔍 button). Append-only log of every write action in the system. - Filter by entity type: **employee** or **violation** - Filter by action: created, edited, merged, negated, restored, amended, deleted, notes updated - Paginated with load-more; most recent entries first The audit log is the authoritative record for compliance review. Nothing in it can be edited or deleted through the UI. --- ### Violation Amendment 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. **Immutable fields:** violation type, incident date, point value. Each amendment stores a before/after diff for every changed field. Amendment history is accessible from the violation card in the employee's history. --- ## Immutability Rules — Quick Reference | Action | Allowed? | Notes | |--------|----------|-------| | Edit violation type | No | Immutable after submission | | Edit incident date | No | Immutable after submission | | Edit point value | No | Immutable after submission | | Edit location / details / witness | Yes | Via Amend | | Negate (void) a violation | Yes | Soft delete; reversible | | Hard delete a violation | Yes | Permanent; use sparingly | | Edit employee name / dept / supervisor | Yes | Logged to audit trail | | Merge duplicate employees | Yes | Irreversible | | Add / edit employee notes | Yes | Does not affect score | --- ## Roadmap ### Shipped - Container scaffold, violation form, employee intelligence - Recidivist auto-escalation, tier crossing warning - PDF generation with prior-points snapshot - Company dashboard, stat cards, at-risk badges - Employee profile modal — full history, negate/restore, hard delete - Employee edit and duplicate merge - Violation amendment with field-level diff log - Audit log — filterable, paginated, append-only - Employee notes and flags with quick-add HR tags - Point expiration timeline with tier-drop projections - In-app admin guide (this panel) --- ### Near-term These are well-scoped additions that fit the current architecture without major changes. - **Acknowledgment signature field** — "received by employee" name + date on the violation form; prints on the PDF in place of the blank signature line. Addresses the most common field workflow gap. - **CSV export** — one endpoint returning violations or dashboard data as a downloadable CSV for payroll or external reporting. - **Supervisor-scoped view** — filter the dashboard to a single supervisor's team via URL param; useful in multi-supervisor environments without requiring full auth. --- ### Planned Larger features that require more design work or infrastructure. - **Violation trends chart** — line/bar chart of violations over time, filterable by department or supervisor. Useful for identifying systemic patterns vs. isolated incidents. Recharts is already available in the frontend bundle. - **Department heat map** — grid showing violation density and average CPAS score per department. Helps identify team-level risk early. - **Draft / pending violations** — save a violation as a draft before it's officially logged. Useful when incidents need supervisor review or HR sign-off before they count toward the score. - **At-risk threshold configuration** — make the 2-point at-risk warning threshold configurable per deployment rather than hardcoded. --- ### Future Considerations These require meaningful infrastructure additions and should be evaluated against actual operational need before committing. - **Multi-user auth** — role-based login (admin, supervisor, read-only). Currently the app assumes a trusted internal network with no authentication layer. - **Tier escalation alerts** — email or in-app notification when an employee crosses into Tier 2+, automatically routed to their supervisor. - **Scheduled digest** — weekly email summary to supervisors showing their employees' current standings and any approaching thresholds. - **Automated DB backup** — scheduled snapshot of the database to a mounted backup volume or remote destination. - **Bulk CSV import** — migrate historical violation records from paper logs or a prior system. - **Dark/light theme toggle** — UI is currently dark-only. `; // ─── Component ──────────────────────────────────────────────────────────────── export default function ReadmeModal({ onClose }) { const bodyRef = useRef(null); const html = mdToHtml(GUIDE_MD); const toc = buildToc(GUIDE_MD); useEffect(() => { const h = e => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', h); return () => window.removeEventListener('keydown', h); }, [onClose]); const scrollTo = id => { const el = bodyRef.current?.querySelector(`#${id}`); if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); }; return (
    { if (e.target === e.currentTarget) onClose(); }}>
    e.stopPropagation()}> {/* Header */}
    📋 CPAS Tracker — Admin Guide
    Feature map · workflows · roadmap · Esc or click outside to close
    {/* TOC strip */}
    {toc.map(h => ( ))}
    {/* Body */}
    {/* Footer */}
    CPAS Violation Tracker · internal admin use only
    ); }
${inline(c)}
${inline(c)}