Files
smanab/Sistem-Pelanggaran-Siswa/routes/students.js

313 lines
10 KiB
JavaScript

import express from 'express';
import pool from '../database.js';
const router = express.Router();
// GET all students
router.get('/', async (req, res) => {
try {
const [rows] = await pool.execute(`
SELECT id, name, class, parent_name as parentName, parent_phone as parentPhone, shift, face_descriptor as faceDescriptor
FROM students
ORDER BY class, name
`);
res.json(rows);
} catch (error) {
console.error('Error fetching students:', error);
res.status(500).json({ error: 'Failed to fetch students' });
}
});
// GET single student
router.get('/:id', async (req, res) => {
try {
const [rows] = await pool.execute(`
SELECT id, name, class, parent_name as parentName, parent_phone as parentPhone, shift, face_descriptor as faceDescriptor
FROM students
WHERE id = ?
`, [req.params.id]);
if (rows.length === 0) {
return res.status(404).json({ error: 'Student not found' });
}
res.json(rows[0]);
} catch (error) {
console.error('Error fetching student:', error);
res.status(500).json({ error: 'Failed to fetch student' });
}
});
// POST create student
router.post('/', async (req, res) => {
try {
const { id, name, class: studentClass, parentName, parentPhone, shift } = req.body;
await pool.execute(`
INSERT INTO students (id, name, class, parent_name, parent_phone, shift)
VALUES (?, ?, ?, ?, ?, ?)
`, [id, name, studentClass, parentName, parentPhone, shift || 'Siang']);
res.status(201).json({ status: 'success', message: 'Student created' });
} catch (error) {
console.error('Error creating student:', error);
res.status(500).json({ error: 'Failed to create student' });
}
});
// PUT update student
router.put('/:id', async (req, res) => {
try {
const { name, class: studentClass, parentName, parentPhone, shift, faceDescriptor } = req.body;
await pool.execute(`
UPDATE students
SET name = ?, class = ?, parent_name = ?, parent_phone = ?, shift = ?, face_descriptor = ?
WHERE id = ?
`, [name, studentClass, parentName, parentPhone, shift, faceDescriptor, req.params.id]);
res.json({ status: 'success', message: 'Student updated' });
} catch (error) {
console.error('Error updating student:', error);
res.status(500).json({ error: 'Failed to update student' });
}
});
// DELETE student
router.delete('/:id', async (req, res) => {
try {
await pool.execute('DELETE FROM students WHERE id = ?', [req.params.id]);
res.json({ status: 'success', message: 'Student deleted' });
} catch (error) {
console.error('Error deleting student:', error);
res.status(500).json({ error: 'Failed to delete student' });
}
});
// GET class statistics for promotion
router.get('/stats/classes', async (req, res) => {
try {
const [rows] = await pool.execute(`
SELECT class, COUNT(*) as count
FROM students
GROUP BY class
ORDER BY class
`);
res.json(rows);
} catch (error) {
console.error('Error fetching class stats:', error);
res.status(500).json({ error: 'Failed to fetch class stats' });
}
});
// POST promote students to next class
router.post('/promote', async (req, res) => {
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
// Get promotion mapping: X -> XI, XI -> XII, XII -> LULUS (delete)
const promotionRules = [
{ from: 'X', to: 'XI' },
{ from: 'XI', to: 'XII' },
];
let promoted = 0;
let graduated = 0;
// First, handle graduates (XII -> Lulus/Delete)
// Count XII students first
const [xiiStudents] = await connection.execute(
"SELECT COUNT(*) as count FROM students WHERE class LIKE 'XII%'"
);
graduated = xiiStudents[0].count;
// Archive XII students before deletion (optional - can create alumni table later)
// For now, just delete them
await connection.execute("DELETE FROM students WHERE class LIKE 'XII%'");
// Promote XI to XII
const [xiResult] = await connection.execute(`
UPDATE students
SET class = REPLACE(class, 'XI-', 'XII-')
WHERE class LIKE 'XI-%'
`);
// Also handle XI without dash
await connection.execute(`
UPDATE students
SET class = REPLACE(class, 'XI', 'XII')
WHERE class = 'XI' OR (class LIKE 'XI%' AND class NOT LIKE 'XII%')
`);
// Promote X to XI
const [xResult] = await connection.execute(`
UPDATE students
SET class = REPLACE(class, 'X-', 'XI-')
WHERE class LIKE 'X-%' AND class NOT LIKE 'XI%' AND class NOT LIKE 'XII%'
`);
// Also handle X without dash
await connection.execute(`
UPDATE students
SET class = 'XI'
WHERE class = 'X'
`);
// Count promoted students
const [countResult] = await connection.execute('SELECT COUNT(*) as count FROM students');
promoted = countResult[0].count;
await connection.commit();
res.json({
status: 'success',
message: 'Kenaikan kelas berhasil!',
graduated,
totalRemaining: promoted
});
} catch (error) {
await connection.rollback();
console.error('Error promoting students:', error);
res.status(500).json({ error: 'Failed to promote students' });
} finally {
connection.release();
}
});
// POST graduate specific class (manual)
router.post('/graduate/:classPrefix', async (req, res) => {
try {
const { classPrefix } = req.params;
const [result] = await pool.execute(
'DELETE FROM students WHERE class LIKE ?',
[`${classPrefix}%`]
);
res.json({
status: 'success',
message: `Siswa kelas ${classPrefix} telah diluluskan`,
count: result.affectedRows
});
} catch (error) {
console.error('Error graduating students:', error);
res.status(500).json({ error: 'Failed to graduate students' });
}
});
// POST promote individual students with mapping
router.post('/promote-mapping', async (req, res) => {
const { mappings } = req.body; // Array of { studentId, newClass }
if (!mappings || !Array.isArray(mappings) || mappings.length === 0) {
return res.status(400).json({ error: 'Data mapping diperlukan' });
}
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
let success = 0;
let failed = 0;
const errors = [];
const graduatedIds = [];
for (const mapping of mappings) {
const { studentId, newClass } = mapping;
if (!studentId) {
failed++;
errors.push(`ID siswa tidak valid`);
continue;
}
try {
if (newClass === 'LULUS' || newClass === 'lulus' || newClass === '') {
// Graduate this student (delete)
await connection.execute(
'DELETE FROM students WHERE id = ?',
[studentId]
);
graduatedIds.push(studentId);
success++;
} else {
// Update class
const [result] = await connection.execute(
'UPDATE students SET class = ? WHERE id = ?',
[newClass, studentId]
);
if (result.affectedRows > 0) {
success++;
} else {
failed++;
errors.push(`Siswa ${studentId} tidak ditemukan`);
}
}
} catch (err) {
failed++;
errors.push(`Error untuk siswa ${studentId}: ${err.message}`);
}
}
await connection.commit();
res.json({
status: 'success',
message: `Proses selesai: ${success} berhasil, ${failed} gagal`,
success,
failed,
graduated: graduatedIds.length,
errors: errors.slice(0, 10) // Limit error messages
});
} catch (error) {
await connection.rollback();
console.error('Error in promote-mapping:', error);
res.status(500).json({ error: 'Gagal memproses kenaikan kelas' });
} finally {
connection.release();
}
});
// GET available classes for dropdown
router.get('/available-classes', async (req, res) => {
try {
const [rows] = await pool.execute(`
SELECT DISTINCT class
FROM students
ORDER BY class
`);
// Generate next level classes
const currentClasses = rows.map(r => r.class);
const allClasses = new Set(currentClasses);
// Add common class patterns for next level
currentClasses.forEach(c => {
// Extract level and suffix (e.g., "X-A" -> "X", "A")
const match = c.match(/^(X|XI|XII)-?(.*)$/i);
if (match) {
const level = match[1].toUpperCase();
const suffix = match[2];
if (level === 'X') {
allClasses.add(`XI-${suffix}`);
allClasses.add(`XI${suffix}`);
} else if (level === 'XI') {
allClasses.add(`XII-${suffix}`);
allClasses.add(`XII${suffix}`);
}
}
});
// Sort and return unique classes
const sortedClasses = Array.from(allClasses).sort();
res.json(sortedClasses);
} catch (error) {
console.error('Error fetching available classes:', error);
res.status(500).json({ error: 'Failed to fetch classes' });
}
});
export default router;