Initial commit apps directory with .gitignore
This commit is contained in:
312
Sistem-Pelanggaran-Siswa/routes/students.js
Normal file
312
Sistem-Pelanggaran-Siswa/routes/students.js
Normal file
@@ -0,0 +1,312 @@
|
||||
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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user