138 lines
5.6 KiB
JavaScript
Executable File
138 lines
5.6 KiB
JavaScript
Executable File
const express = require('express');
|
|
const cors = require('cors');
|
|
const path = require('path');
|
|
const db = require('./db/database');
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3001;
|
|
|
|
app.use(cors());
|
|
app.use(express.json());
|
|
app.use(express.static(path.join(__dirname, 'client', 'dist')));
|
|
|
|
// ── Health ─────────────────────────────────────────────────────────────────
|
|
app.get('/api/health', (req, res) => {
|
|
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
});
|
|
|
|
// ── Employees ──────────────────────────────────────────────────────────────
|
|
app.get('/api/employees', (req, res) => {
|
|
const rows = db.prepare(
|
|
'SELECT id, name, department, supervisor FROM employees ORDER BY name ASC'
|
|
).all();
|
|
res.json(rows);
|
|
});
|
|
|
|
app.post('/api/employees', (req, res) => {
|
|
const { name, department, supervisor } = req.body;
|
|
if (!name) return res.status(400).json({ error: 'name is required' });
|
|
|
|
const existing = db.prepare(
|
|
'SELECT * FROM employees WHERE LOWER(name) = LOWER(?)'
|
|
).get(name);
|
|
|
|
if (existing) {
|
|
if (department || supervisor) {
|
|
db.prepare(
|
|
'UPDATE employees SET department = COALESCE(?, department), supervisor = COALESCE(?, supervisor) WHERE id = ?'
|
|
).run(department || null, supervisor || null, existing.id);
|
|
}
|
|
return res.json({ ...existing, department, supervisor });
|
|
}
|
|
|
|
const result = db.prepare(
|
|
'INSERT INTO employees (name, department, supervisor) VALUES (?, ?, ?)'
|
|
).run(name, department || null, supervisor || null);
|
|
|
|
res.status(201).json({ id: result.lastInsertRowid, name, department, supervisor });
|
|
});
|
|
|
|
// ── Employee CPAS Score (rolling 90-day) ───────────────────────────────────
|
|
app.get('/api/employees/:employeeId/score', (req, res) => {
|
|
const row = db.prepare(
|
|
'SELECT * FROM active_cpas_scores WHERE employee_id = ?'
|
|
).get(req.params.employeeId);
|
|
res.json(row || { employee_id: req.params.employeeId, active_points: 0, violation_count: 0 });
|
|
});
|
|
|
|
// ── Violation type usage counts for an employee (90-day window) ────────────
|
|
// Returns { violation_type: count } so the frontend can badge the dropdown
|
|
app.get('/api/employees/:employeeId/violation-counts', (req, res) => {
|
|
const rows = db.prepare(`
|
|
SELECT violation_type, COUNT(*) as count
|
|
FROM violations
|
|
WHERE employee_id = ?
|
|
AND incident_date >= DATE('now', '-90 days')
|
|
GROUP BY violation_type
|
|
`).all(req.params.employeeId);
|
|
|
|
const map = {};
|
|
rows.forEach(r => { map[r.violation_type] = r.count; });
|
|
res.json(map);
|
|
});
|
|
|
|
// ── All-time violation type counts for recidivist point suggestion ─────────
|
|
app.get('/api/employees/:employeeId/violation-counts/alltime', (req, res) => {
|
|
const rows = db.prepare(`
|
|
SELECT violation_type, COUNT(*) as count, MAX(points) as max_points_used
|
|
FROM violations
|
|
WHERE employee_id = ?
|
|
GROUP BY violation_type
|
|
`).all(req.params.employeeId);
|
|
|
|
const map = {};
|
|
rows.forEach(r => { map[r.violation_type] = { count: r.count, max_points_used: r.max_points_used }; });
|
|
res.json(map);
|
|
});
|
|
|
|
// ── Violation history for an employee ─────────────────────────────────────
|
|
app.get('/api/violations/employee/:employeeId', (req, res) => {
|
|
const limit = parseInt(req.query.limit) || 50;
|
|
const rows = db.prepare(`
|
|
SELECT * FROM violations
|
|
WHERE employee_id = ?
|
|
ORDER BY incident_date DESC, created_at DESC
|
|
LIMIT ?
|
|
`).all(req.params.employeeId, limit);
|
|
res.json(rows);
|
|
});
|
|
|
|
// ── POST new violation ─────────────────────────────────────────────────────
|
|
app.post('/api/violations', (req, res) => {
|
|
const {
|
|
employee_id, violation_type, violation_name, category,
|
|
points, incident_date, incident_time, location,
|
|
details, submitted_by, witness_name
|
|
} = req.body;
|
|
|
|
if (!employee_id || !violation_type || !points || !incident_date) {
|
|
return res.status(400).json({
|
|
error: 'Missing required fields: employee_id, violation_type, points, incident_date'
|
|
});
|
|
}
|
|
|
|
const result = db.prepare(`
|
|
INSERT INTO violations (
|
|
employee_id, violation_type, violation_name, category,
|
|
points, incident_date, incident_time, location,
|
|
details, submitted_by, witness_name
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`).run(
|
|
employee_id, violation_type, violation_name || violation_type,
|
|
category || 'General', points, incident_date,
|
|
incident_time || null, location || null,
|
|
details || null, submitted_by || null, witness_name || null
|
|
);
|
|
|
|
res.status(201).json({ id: result.lastInsertRowid });
|
|
});
|
|
|
|
// ── SPA fallback ───────────────────────────────────────────────────────────
|
|
app.get('*', (req, res) => {
|
|
res.sendFile(path.join(__dirname, 'client', 'dist', 'index.html'));
|
|
});
|
|
|
|
app.listen(PORT, '0.0.0.0', () => {
|
|
console.log(`[CPAS] Server running on port ${PORT}`);
|
|
});
|