Files
admin-edu-space/frontend/src/routes/dashboard.js
T

292 lines
14 KiB
JavaScript

const express = require('express');
const router = express.Router();
const { requireAuth } = require('../middlewares/authMiddleware');
router.use(requireAuth);
router.get('/', async (req, res) => {
const actual_role = (req.user?.role || 'admin').toLowerCase();
const roleParam = (req.query.role || req.query.view_as || '').toLowerCase().trim();
const role = (['admin', 'bedelia', 'docente', 'alumno'].includes(roleParam) ? roleParam : actual_role) || 'admin';
let apiData = {};
let backendStats = {};
let audit_logs = [];
let monthly_data = [];
try {
const response = await req.apiClient.get('/dashboard/stats');
apiData = response.data || {};
backendStats = apiData.stats || {};
audit_logs = apiData.audit_logs || [];
monthly_data = apiData.monthly_data || [];
} catch (error) {
console.warn('Dashboard stats API notice:', error.message);
}
if (!monthly_data || monthly_data.length === 0) {
monthly_data = [
{ month: 'Ene', reservations: 120 }, { month: 'Feb', reservations: 180 }, { month: 'Mar', reservations: 250 },
{ month: 'Abr', reservations: 220 }, { month: 'May', reservations: 300 }, { month: 'Jun', reservations: 310 },
{ month: 'Jul', reservations: 200 }, { month: 'Ago', reservations: 350 }, { month: 'Sep', reservations: 420 },
{ month: 'Oct', reservations: 480 }, { month: 'Nov', reservations: 520 }, { month: 'Dic', reservations: 210 }
];
}
const total_classrooms = backendStats.total_classrooms ?? 31;
const total_subjects = backendStats.active_subjects ?? backendStats.total_subjects ?? 12;
const total_users = backendStats.total_active_users ?? backendStats.total_users ?? 8;
const today_reservations = backendStats.today_reservations ?? 19;
const pending_count = backendStats.pending_count ?? 3;
const physical_classrooms = backendStats.physical_classrooms ?? Math.max(1, total_classrooms - 4);
const virtual_classrooms_count = backendStats.virtual_classrooms ?? 4;
const occupancy_rate = backendStats.occupancy_rate ?? 68;
const assigned_classrooms = backendStats.assigned_classrooms ?? 26;
const assigned_classrooms_pct = backendStats.assigned_classrooms_pct ?? 96.3;
const unassigned_classrooms = backendStats.unassigned_classrooms ?? 1;
const unassigned_classrooms_pct = backendStats.unassigned_classrooms_pct ?? 3.7;
const assigned_today_classrooms = backendStats.assigned_today_classrooms ?? 0;
const assigned_today_pct = backendStats.assigned_today_pct ?? 0;
const unassigned_today_classrooms = backendStats.unassigned_today_classrooms ?? physical_classrooms;
const unassigned_today_pct = backendStats.unassigned_today_pct ?? 100;
const apiPending = Array.isArray(apiData.pending_reservations) && apiData.pending_reservations.length > 0
? apiData.pending_reservations
: null;
const apiTodaySchedule = Array.isArray(apiData.today_schedule_list) && apiData.today_schedule_list.length > 0
? apiData.today_schedule_list
: null;
const apiUpcoming = Array.isArray(apiData.upcoming_reservations) && apiData.upcoming_reservations.length > 0
? apiData.upcoming_reservations
: null;
const apiMilestones = Array.isArray(apiData.milestones) && apiData.milestones.length > 0
? apiData.milestones
: null;
// Bedelía-specific data
const bedeliaData = apiData.bedelia || {};
// Consolidated and role-specific stats
const stats = {
total_classrooms,
total_subjects,
total_users,
today_reservations,
pending_count: apiPending ? apiPending.length : pending_count,
physical_classrooms,
virtual_classrooms: virtual_classrooms_count,
occupancy_rate,
assigned_classrooms,
assigned_classrooms_pct,
unassigned_classrooms,
unassigned_classrooms_pct,
assigned_today_classrooms,
assigned_today_pct,
unassigned_today_classrooms,
unassigned_today_pct,
my_today_count: apiTodaySchedule ? Math.min(3, apiTodaySchedule.length) : 2,
my_total_reservations: 8,
virtual_rooms_count: virtual_classrooms_count,
today_classes_count: apiTodaySchedule ? Math.min(4, apiTodaySchedule.length) : 2,
milestones_count: apiMilestones ? apiMilestones.length : 3
};
let role_data = {};
if (role === 'admin') {
role_data = {
recent_audits: audit_logs.length > 0 ? audit_logs : [
{ created_at: '2026-09-18 21:15', action_type: 'UPDATE_PERMISSION', user_email: 'admin@edu-space.com', ip_address: '127.0.0.1' },
{ created_at: '2026-09-18 20:30', action_type: 'CREATE_COMMISSION', user_email: 'admin@edu-space.com', ip_address: '127.0.0.1' },
{ created_at: '2026-09-18 19:45', action_type: 'LOGIN_SUCCESS', user_email: 'admin@edu-space.com', ip_address: '127.0.0.1' }
]
};
} else if (role === 'bedelia') {
role_data = {
pending_reservations: apiPending || [
{
id: 101,
start_time: '08:00',
end_time: '12:00',
classroom: { code: 'Aula 101' },
subject_name: 'Arquitectura de Computadoras',
user: { name: 'Prof. Gabriel Martínez' }
},
{
id: 102,
start_time: '14:00',
end_time: '18:00',
classroom: { code: 'Lab Informática 2' },
subject_name: 'Bases de Datos II',
user: { name: 'Dra. Laura Blanco' }
},
{
id: 103,
start_time: '18:30',
end_time: '22:00',
classroom: { code: 'Aula Magna' },
subject_name: 'Seminario de Ciberseguridad',
user: { name: 'Lic. Fernando Gómez' }
}
],
today_schedule_list: apiTodaySchedule || [
{ start_time: '08:00', end_time: '12:00', classroom: { code: 'Aula 102' }, subject_name: 'Algoritmos y Estructuras de Datos', user: { name: 'Prof. Ana Vega' } },
{ start_time: '08:30', end_time: '12:30', classroom: { code: 'Aula 204' }, subject_name: 'Matemática Discreta', user: { name: 'Prof. Carlos López' } },
{ start_time: '14:00', end_time: '18:00', classroom: { code: 'Lab Redes' }, subject_name: 'Redes y Telecomunicaciones', user: { name: 'Ing. Marcos Ruiz' } }
],
unassigned_commissions_week: bedeliaData.unassigned_commissions_week || [],
unassigned_commissions_month: bedeliaData.unassigned_commissions_month || [],
available_classrooms_week: bedeliaData.available_classrooms_week || [],
available_classrooms_month: bedeliaData.available_classrooms_month || []
};
} else if (role === 'docente') {
const myClasses = apiTodaySchedule ? apiTodaySchedule.slice(0, 3) : null;
role_data = {
my_today_classes: myClasses || [
{
id: 201,
start_time: '08:30',
end_time: '12:00',
subject_name: 'Algoritmos y Estructuras de Datos',
subject_code: 'AED-102',
commission: { code: 'COM-A' },
classroom: { is_virtual: false, location_display: 'Piso 1 · Aula 104' },
virtual_link: ''
},
{
id: 202,
start_time: '18:30',
end_time: '22:00',
subject_name: 'Programación Concurrente y Distribuida',
subject_code: 'PCD-301',
commission: { code: 'COM-D' },
classroom: { is_virtual: true, location_display: 'Campus Virtual' },
virtual_link: 'https://meet.google.com/edu-space-pcd'
}
],
virtual_classrooms: [
{ code: 'Sala Virtual 1', link: 'https://meet.google.com/edu-space-v1' },
{ code: 'Sala Virtual 2', link: 'https://meet.google.com/edu-space-v2' }
]
};
} else if (role === 'alumno') {
const studentClasses = apiTodaySchedule ? apiTodaySchedule.slice(0, 4) : null;
let upcomingMilestonesList = [];
try {
const mResp = await req.apiClient.get('/students/my-upcoming-milestones');
if (mResp.data && Array.isArray(mResp.data.upcoming_milestones) && mResp.data.upcoming_milestones.length > 0) {
upcomingMilestonesList = mResp.data.upcoming_milestones.map(m => {
const isFinal = (m.type || '').includes('Final');
const isParcial = (m.type || '').includes('Parcial');
const color = isFinal ? '#dc3545' : (isParcial ? '#fd7e14' : '#0d6efd');
return {
...m,
materia_codigo: m.commission_code || m.subject_name,
type_obj: { name: m.type, color },
milestone_type: { name: m.type, color }
};
});
}
} catch (e) {
console.warn('Student milestones fetch notice:', e.message);
}
if (upcomingMilestonesList.length === 0) {
upcomingMilestonesList = [
{
title: 'Primer Examen Parcial',
materia_codigo: 'BDR-201',
subject_name: 'Bases de Datos Relacionales',
date: '25/09/2026',
days_remaining: 2,
type_obj: { name: 'Examen Parcial', color: '#dc3545' },
milestone_type: { name: 'Examen Parcial', color: '#dc3545' },
notes: 'Traer DNI y calculadora',
classroom: 'Piso 2 · Aula 204'
},
{
title: 'Entrega TP Grupal Nº 2',
materia_codigo: 'AED-102',
subject_name: 'Algoritmos y Estructuras de Datos',
date: '30/09/2026',
days_remaining: 7,
type_obj: { name: 'Trabajo Práctico', color: '#198754' },
milestone_type: { name: 'Trabajo Práctico', color: '#198754' },
notes: 'Subir código al repositorio',
classroom: 'Campus Virtual',
link: 'https://campus.edu-space.com/tps'
},
{
title: 'Recuperatorio Parcial 1',
materia_codigo: 'RYT-302',
subject_name: 'Redes y Telecomunicaciones',
date: '08/10/2026',
days_remaining: 15,
type_obj: { name: 'Recuperatorio', color: '#fd7e14' },
milestone_type: { name: 'Recuperatorio', color: '#fd7e14' },
notes: 'Piso 2 · Aula 204',
classroom: 'Piso 2 · Aula 204'
}
];
}
role_data = {
today_classes: studentClasses || [
{
start_time: '08:00',
end_time: '12:00',
subject_name: 'Bases de Datos Relacionales',
subject_code: 'BDR-201',
commission: { code: 'COM-1' },
classroom: { is_virtual: false, location_display: 'Piso 2 · Laboratorio 3' },
virtual_link: ''
},
{
start_time: '18:30',
end_time: '22:30',
subject_name: 'Redes y Telecomunicaciones',
subject_code: 'RYT-302',
commission: { code: 'COM-2' },
classroom: { is_virtual: true, location_display: 'Campus Virtual (Google Meet)' },
virtual_link: 'https://meet.google.com/edu-space-demo'
}
],
upcoming_milestones: upcomingMilestonesList
};
}
const upcoming_reservations = apiUpcoming || [
{ start_time: '08:00', end_time: '12:00', date_display: '22/09', subject_name: 'Matemática Discreta', subject_code: 'MAT-101', shift: 'Mañana', commission: { code: 'COM-1' }, status: 'CONFIRMED', classroom: { code: 'Aula 101' }, user: { name: 'Prof. Carlos López' } },
{ start_time: '14:00', end_time: '18:00', date_display: '23/09', subject_name: 'Sistemas Operativos', subject_code: 'SO-202', shift: 'Tarde', commission: { code: 'COM-2' }, status: 'CONFIRMED', classroom: { code: 'Lab 2' }, user: { name: 'Dra. Ana Vega' } },
{ start_time: '18:30', end_time: '22:30', date_display: '24/09', subject_name: 'Ingeniería de Software', subject_code: 'IS-301', shift: 'Noche', commission: { code: 'COM-3' }, status: 'PENDING', classroom: { code: 'Aula Magna' }, user: { name: 'Ing. Marcos Ruiz' } }
];
let show_role_banner = true;
if (apiData && apiData.ui && apiData.ui.show_role_banner !== undefined) {
show_role_banner = Boolean(apiData.ui.show_role_banner);
}
res.render('dashboard', {
title: role === 'bedelia' ? 'Panel Operativo de Bedelía - Edu-Space Admin' :
role === 'docente' ? 'Panel Docente - Edu-Space Admin' :
role === 'alumno' ? 'Mi Portal de Cursada - Edu-Space Admin' :
'Panel de Administración - Edu-Space Admin',
user: req.user,
role,
actual_role,
show_role_banner,
stats,
role_data,
monthly_data,
audit_logs,
upcoming_reservations
});
});
module.exports = router;