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;