Upload files to "client/src/components"
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
\
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import axios from 'axios';
|
||||
import { violationData, violationGroups } from '../data/violations';
|
||||
@@ -7,27 +8,27 @@ import TierWarning from './TierWarning';
|
||||
import ViolationHistory from './ViolationHistory';
|
||||
|
||||
const s = {
|
||||
content: { padding: '40px' },
|
||||
section: { background: '#f8f9fa', borderLeft: '4px solid #667eea', padding: '20px', marginBottom: '30px', borderRadius: '4px' },
|
||||
sectionTitle: { color: '#2c3e50', fontSize: '20px', marginBottom: '15px' },
|
||||
content: { padding: '32px 40px', background: '#111217', borderRadius: '10px', color: '#f8f9fa' },
|
||||
section: { background: '#181924', borderLeft: '4px solid #d4af37', padding: '20px', marginBottom: '30px', borderRadius: '4px', border: '1px solid #2a2b3a' },
|
||||
sectionTitle: { color: '#f8f9fa', fontSize: '20px', marginBottom: '15px', fontWeight: 700 },
|
||||
grid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: '15px', marginTop: '15px' },
|
||||
item: { display: 'flex', flexDirection: 'column' },
|
||||
label: { fontWeight: 600, color: '#555', marginBottom: '5px', fontSize: '13px' },
|
||||
input: { padding: '10px', border: '1px solid #ddd', borderRadius: '4px', fontSize: '14px', fontFamily: 'inherit' },
|
||||
label: { fontWeight: 600, color: '#e5e7f1', marginBottom: '5px', fontSize: '13px' },
|
||||
input: { padding: '10px', border: '1px solid #333544', borderRadius: '4px', fontSize: '14px', fontFamily: 'inherit', background: '#050608', color: '#f8f9fa' },
|
||||
fullCol: { gridColumn: '1 / -1' },
|
||||
contextBox: { background: '#f1f3f5', border: '1px solid #ced4da', borderRadius: '4px', padding: '10px', fontSize: '12px', color: '#444', marginTop: '4px' },
|
||||
repeatBadge: { display: 'inline-block', marginLeft: '8px', padding: '1px 7px', borderRadius: '10px', fontSize: '11px', fontWeight: 700, background: '#fff3cd', color: '#856404', border: '1px solid #ffc107' },
|
||||
repeatWarn: { background: '#fff3cd', border: '1px solid #ffc107', borderRadius: '4px', padding: '8px 12px', marginTop: '6px', fontSize: '12px', color: '#856404' },
|
||||
pointBox: { background: '#fff3cd', border: '2px solid #ffc107', padding: '15px', borderRadius: '6px', marginTop: '15px', textAlign: 'center' },
|
||||
pointValue: { fontSize: '24px', fontWeight: 'bold', color: '#667eea', margin: '10px 0' },
|
||||
contextBox: { background: '#141623', border: '1px solid #333544', borderRadius: '4px', padding: '10px', fontSize: '12px', color: '#d1d3e0', marginTop: '4px' },
|
||||
repeatBadge: { display: 'inline-block', marginLeft: '8px', padding: '1px 7px', borderRadius: '10px', fontSize: '11px', fontWeight: 700, background: '#3b2e00', color: '#ffd666', border: '1px solid #d4af37' },
|
||||
repeatWarn: { background: '#3b2e00', border: '1px solid #d4af37', borderRadius: '4px', padding: '8px 12px', marginTop: '6px', fontSize: '12px', color: '#ffdf8a' },
|
||||
pointBox: { background: '#181200', border: '2px solid #d4af37', padding: '15px', borderRadius: '6px', marginTop: '15px', textAlign: 'center' },
|
||||
pointValue: { fontSize: '24px', fontWeight: 'bold', color: '#ffd666', margin: '10px 0' },
|
||||
scoreRow: { display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '14px', flexWrap: 'wrap' },
|
||||
btnRow: { display: 'flex', gap: '15px', justifyContent: 'center', marginTop: '30px', flexWrap: 'wrap' },
|
||||
btnPrimary: { padding: '15px 40px', fontSize: '16px', fontWeight: 600, border: 'none', borderRadius: '6px', cursor: 'pointer', background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', color: 'white', textTransform: 'uppercase' },
|
||||
btnPrimary: { padding: '15px 40px', fontSize: '16px', fontWeight: 600, border: 'none', borderRadius: '6px', cursor: 'pointer', background: 'linear-gradient(135deg, #d4af37 0%, #ffdf8a 100%)', color: '#000', textTransform: 'uppercase' },
|
||||
btnPdf: { padding: '15px 40px', fontSize: '16px', fontWeight: 600, border: 'none', borderRadius: '6px', cursor: 'pointer', background: 'linear-gradient(135deg, #e74c3c 0%, #c0392b 100%)', color: 'white', textTransform: 'uppercase' },
|
||||
btnSecondary: { padding: '15px 40px', fontSize: '16px', fontWeight: 600, border: 'none', borderRadius: '6px', cursor: 'pointer', background: '#6c757d', color: 'white', textTransform: 'uppercase' },
|
||||
note: { background: '#e7f3ff', borderLeft: '4px solid #2196F3', padding: '15px', margin: '20px 0', borderRadius: '4px' },
|
||||
statusOk: { marginTop: '15px', padding: '15px', borderRadius: '6px', textAlign: 'center', fontWeight: 600, background: '#d4edda', color: '#155724', border: '1px solid #c3e6cb' },
|
||||
statusErr: { marginTop: '15px', padding: '15px', borderRadius: '6px', textAlign: 'center', fontWeight: 600, background: '#f8d7da', color: '#721c24', border: '1px solid #f5c6cb' },
|
||||
btnSecondary: { padding: '15px 40px', fontSize: '16px', fontWeight: 600, border: '1px solid #333544', borderRadius: '6px', cursor: 'pointer', background: '#050608', color: '#f8f9fa', textTransform: 'uppercase' },
|
||||
note: { background: '#141623', borderLeft: '4px solid #2196F3', padding: '15px', margin: '20px 0', borderRadius: '4px', fontSize: '13px', color: '#d1d3e0' },
|
||||
statusOk: { marginTop: '15px', padding: '15px', borderRadius: '6px', textAlign: 'center', fontWeight: 600, background: '#053321', color: '#9ef7c1', border: '1px solid #0f5132' },
|
||||
statusErr: { marginTop: '15px', padding: '15px', borderRadius: '6px', textAlign: 'center', fontWeight: 600, background: '#3c1114', color: '#ffb3b8', border: '1px solid #f5c6cb' },
|
||||
};
|
||||
|
||||
const EMPTY_FORM = {
|
||||
@@ -41,7 +42,7 @@ export default function ViolationForm() {
|
||||
const [form, setForm] = useState(EMPTY_FORM);
|
||||
const [violation, setViolation] = useState(null);
|
||||
const [status, setStatus] = useState(null);
|
||||
const [lastViolId, setLastViolId] = useState(null); // ID of most recently saved violation
|
||||
const [lastViolId, setLastViolId] = useState(null);
|
||||
const [pdfLoading, setPdfLoading] = useState(false);
|
||||
|
||||
const intel = useEmployeeIntelligence(form.employeeId || null);
|
||||
@@ -113,9 +114,7 @@ export default function ViolationForm() {
|
||||
if (!lastViolId) return;
|
||||
setPdfLoading(true);
|
||||
try {
|
||||
const response = await axios.get(`/api/violations/${lastViolId}/pdf`, {
|
||||
responseType: 'blob',
|
||||
});
|
||||
const response = await axios.get(`/api/violations/${lastViolId}/pdf`, { responseType: 'blob' });
|
||||
const url = window.URL.createObjectURL(new Blob([response.data], { type: 'application/pdf' }));
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
@@ -138,15 +137,14 @@ export default function ViolationForm() {
|
||||
return (
|
||||
<div style={s.content}>
|
||||
|
||||
{/* ── Employee Information ─────────────────────────────── */}
|
||||
<div style={s.section}>
|
||||
<h2 style={s.sectionTitle}>Employee Information</h2>
|
||||
|
||||
{intel.score && form.employeeId && (
|
||||
<div style={s.scoreRow}>
|
||||
<span style={{ fontSize: '13px', color: '#555', fontWeight: 600 }}>Current Standing:</span>
|
||||
<span style={{ fontSize: '13px', color: '#d1d3e0', fontWeight: 600 }}>Current Standing:</span>
|
||||
<CpasBadge points={intel.score.active_points} />
|
||||
<span style={{ fontSize: '12px', color: '#888' }}>
|
||||
<span style={{ fontSize: '12px', color: '#9ca0b8' }}>
|
||||
{intel.score.violation_count} violation{intel.score.violation_count !== 1 ? 's' : ''} in last 90 days
|
||||
</span>
|
||||
</div>
|
||||
@@ -174,7 +172,6 @@ export default function ViolationForm() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Violation Details ────────────────────────────────── */}
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div style={s.section}>
|
||||
<h2 style={s.sectionTitle}>Violation Details</h2>
|
||||
@@ -207,7 +204,7 @@ export default function ViolationForm() {
|
||||
</span>
|
||||
)}
|
||||
<br />{violation.description}<br />
|
||||
<span style={{ fontSize: '11px', color: '#666' }}>{violation.chapter}</span>
|
||||
<span style={{ fontSize: '11px', color: '#a0a3ba' }}>{violation.chapter}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -264,7 +261,7 @@ export default function ViolationForm() {
|
||||
|
||||
{violation && (
|
||||
<div style={s.pointBox}>
|
||||
<h4 style={{ color: '#856404', marginBottom: '10px' }}>CPAS Point Assessment</h4>
|
||||
<h4 style={{ color: '#ffdf8a', marginBottom: '10px' }}>CPAS Point Assessment</h4>
|
||||
<p style={{ margin: 0 }}>
|
||||
{violation.name}: {violation.minPoints === violation.maxPoints
|
||||
? `${violation.minPoints} Points (Fixed)`
|
||||
@@ -274,7 +271,7 @@ export default function ViolationForm() {
|
||||
min={violation.minPoints} max={violation.maxPoints}
|
||||
value={form.points} onChange={handleChange} />
|
||||
<div style={s.pointValue}>{form.points} Points</div>
|
||||
<p style={{ fontSize: '12px', color: '#666' }}>Adjust to reflect severity and context</p>
|
||||
<p style={{ fontSize: '12px', color: '#d1d3e0' }}>Adjust to reflect severity and context</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -286,7 +283,6 @@ export default function ViolationForm() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* PDF download — appears after successful submission */}
|
||||
{lastViolId && status?.ok && (
|
||||
<div style={{ textAlign: 'center', marginTop: '16px' }}>
|
||||
<button
|
||||
@@ -297,7 +293,7 @@ export default function ViolationForm() {
|
||||
>
|
||||
{pdfLoading ? '⏳ Generating PDF...' : '⬇ Download PDF'}
|
||||
</button>
|
||||
<p style={{ fontSize: '11px', color: '#888', marginTop: '6px' }}>
|
||||
<p style={{ fontSize: '11px', color: '#9ca0b8', marginTop: '6px' }}>
|
||||
Violation #{lastViolId} — click to download the signed violation document
|
||||
</p>
|
||||
</div>
|
||||
@@ -306,7 +302,6 @@ export default function ViolationForm() {
|
||||
{status && <div style={status.ok ? s.statusOk : s.statusErr}>{status.msg}</div>}
|
||||
</form>
|
||||
|
||||
{/* ── Violation History Panel ──────────────────────────── */}
|
||||
{form.employeeId && (
|
||||
<div style={s.section}>
|
||||
<h2 style={s.sectionTitle}>Violation History</h2>
|
||||
|
||||
Reference in New Issue
Block a user