feat: acople de schedule.view_reservation al BFF y creacion/asociacion de comisiones a materias
This commit is contained in:
@@ -623,20 +623,39 @@ const handleCommissionsList = async (req, res) => {
|
||||
const subject_id = req.query.subject_id || '';
|
||||
const shift = req.query.shift || '';
|
||||
const active = req.query.active || '';
|
||||
const created = req.query.created === '1';
|
||||
|
||||
let commissions = [];
|
||||
let subjects = [];
|
||||
let teachers = [];
|
||||
|
||||
try {
|
||||
const cRes = await req.apiClient.get('/commissions', { params: { search, subject_id, shift, active } });
|
||||
if (cRes.data) {
|
||||
commissions = (cRes.data.commissions || []).map(c => ({
|
||||
const [cRes, uRes] = await Promise.allSettled([
|
||||
req.apiClient.get('/commissions', { params: { search, subject_id, shift, active } }),
|
||||
req.apiClient.get('/users')
|
||||
]);
|
||||
|
||||
if (cRes.status === 'fulfilled' && cRes.value.data) {
|
||||
commissions = (cRes.value.data.commissions || []).map(c => ({
|
||||
...c,
|
||||
get_full_code: () => c.code,
|
||||
subject: { name: c.subject_name, code: c.subject_code },
|
||||
get_full_code: () => c.full_code || c.code,
|
||||
subject: {
|
||||
id: c.subject_id,
|
||||
name: c.subject_name,
|
||||
code: c.subject_code,
|
||||
career_name: c.career_name || 'Carrera General'
|
||||
},
|
||||
teacher: c.teacher_name ? { name: c.teacher_name } : null
|
||||
}));
|
||||
subjects = cRes.data.subjects || [];
|
||||
subjects = cRes.value.data.subjects || [];
|
||||
}
|
||||
|
||||
if (uRes.status === 'fulfilled' && uRes.value.data) {
|
||||
const users = uRes.value.data.users || [];
|
||||
teachers = users.filter(u => {
|
||||
const r = (u.role || '').toLowerCase();
|
||||
return r.includes('docent') || r.includes('prof') || r.includes('admin') || r.includes('bedel');
|
||||
});
|
||||
}
|
||||
} catch (apiErr) {
|
||||
console.warn('Commissions list API notice:', apiErr.message);
|
||||
@@ -646,10 +665,12 @@ const handleCommissionsList = async (req, res) => {
|
||||
title: 'Gestión de Comisiones — Edu-Space',
|
||||
commissions,
|
||||
subjects,
|
||||
teachers,
|
||||
search,
|
||||
subject_filter: subject_id ? parseInt(subject_id, 10) : '',
|
||||
shift_filter: shift,
|
||||
active_filter: active,
|
||||
created,
|
||||
pagination: { pages: 1, page: 1, total: commissions.length, has_prev: false, has_next: false }
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -658,6 +679,7 @@ const handleCommissionsList = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleCommissionDetail = async (req, res) => {
|
||||
const id = req.params.id || req.query.id || 1;
|
||||
try {
|
||||
@@ -725,11 +747,25 @@ const handleCommissionDetail = async (req, res) => {
|
||||
|
||||
const handleCommissionAdd = async (req, res) => {
|
||||
try {
|
||||
await req.apiClient.post('/commissions', req.body);
|
||||
const { subject_id, code, semester, year, shift, teacher_id, max_students, schedule, virtual_link } = req.body;
|
||||
const payload = {
|
||||
subject_id: parseInt(subject_id, 10),
|
||||
code: (code || '').trim(),
|
||||
semester: semester || '1C',
|
||||
year: year ? parseInt(year, 10) : 2026,
|
||||
shift: shift || 'Mañana',
|
||||
teacher_id: teacher_id ? parseInt(teacher_id, 10) : null,
|
||||
max_students: max_students ? parseInt(max_students, 10) : 40,
|
||||
schedule: (schedule || '').trim(),
|
||||
virtual_link: (virtual_link || '').trim(),
|
||||
active: true
|
||||
};
|
||||
await req.apiClient.post('/commissions', payload);
|
||||
res.redirect('/admin/commissions_list?created=1');
|
||||
} catch (e) {
|
||||
console.warn('Commission add API notice:', e.response?.data || e.message);
|
||||
res.redirect('/admin/commissions_list');
|
||||
}
|
||||
res.redirect('/admin/commissions_list');
|
||||
};
|
||||
|
||||
const handleCommissionEdit = async (req, res) => {
|
||||
|
||||
@@ -376,5 +376,211 @@ router.get('/add_reservation', handleAddReservation);
|
||||
router.post('/add', handlePostReservation);
|
||||
router.post('/add_reservation', handlePostReservation);
|
||||
|
||||
// 6. Vista Detallada de Reserva (Ficha de Reserva)
|
||||
const handleViewReservation = async (req, res) => {
|
||||
try {
|
||||
let id = req.params.id || req.query.id;
|
||||
if (!id) {
|
||||
try {
|
||||
const listRes = await req.apiClient.get('/reservations');
|
||||
const items = (listRes.data && listRes.data.reservations) || [];
|
||||
if (items.length > 0) {
|
||||
return res.redirect(`/schedule/view_reservation?id=${items[0].id}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Could not fetch default reservation:', e.message);
|
||||
}
|
||||
return res.redirect('/schedule/today_schedule');
|
||||
}
|
||||
|
||||
const response = await req.apiClient.get(`/reservations/${id}`);
|
||||
const r = response.data;
|
||||
|
||||
const resObj = {
|
||||
...r,
|
||||
status: {
|
||||
value: (r.status || 'CONFIRMED').toUpperCase(),
|
||||
toString: () => (r.status || 'CONFIRMED').toUpperCase()
|
||||
},
|
||||
duration: r.duration || 120,
|
||||
classroom: r.classroom || {
|
||||
code: r.classroom_name || 'Aula Asignada',
|
||||
building_name: 'Edificio Central',
|
||||
floor_display: 'PB',
|
||||
capacity: 35,
|
||||
capacity_display: '35',
|
||||
is_virtual: Boolean(r.is_virtual),
|
||||
description: ''
|
||||
},
|
||||
commission: r.commission ? {
|
||||
...r.commission,
|
||||
get_full_code: () => r.commission.full_code || r.commission.code || `COM-${r.commission.id}`,
|
||||
subject: r.commission.subject || { name: r.purpose || 'Materia General' }
|
||||
} : {
|
||||
get_full_code: () => 'COM-GRAL',
|
||||
subject: { name: r.purpose || 'Materia General' }
|
||||
},
|
||||
user: r.user || {
|
||||
first_name: (r.teacher_name || 'Profesor').split(' ')[0],
|
||||
last_name: (r.teacher_name || '').split(' ').slice(1).join(' ') || 'Docente',
|
||||
email: 'docente@edu-space.com'
|
||||
},
|
||||
effective_virtual_link: r.effective_virtual_link || r.virtual_link || '',
|
||||
can_be_cancelled: () => (typeof r.can_be_cancelled === 'boolean' ? r.can_be_cancelled : true)
|
||||
};
|
||||
|
||||
res.render('schedule/view', {
|
||||
title: `Reservation Details #${r.id} - Edu-Space Admin`,
|
||||
reservation: resObj
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in view_reservation:', error.message);
|
||||
res.redirect('/schedule/today_schedule');
|
||||
}
|
||||
};
|
||||
|
||||
// 7. Modificar Reserva
|
||||
const handleEditReservation = async (req, res) => {
|
||||
const id = req.params.id || req.query.id;
|
||||
if (!id) return res.redirect('/schedule/today_schedule');
|
||||
|
||||
try {
|
||||
let reservation = null;
|
||||
try {
|
||||
const rRes = await req.apiClient.get(`/reservations/${id}`);
|
||||
reservation = rRes.data;
|
||||
} catch (err) {
|
||||
console.error('Error fetching reservation for edit:', err.message);
|
||||
return res.redirect('/schedule/today_schedule');
|
||||
}
|
||||
|
||||
let allClassrooms = [];
|
||||
try {
|
||||
const cRes = await req.apiClient.get('/classrooms');
|
||||
allClassrooms = (cRes.data && cRes.data.classrooms) || [];
|
||||
} catch (e) {}
|
||||
|
||||
const buildingsMap = new Map();
|
||||
buildingsMap.set('1', { id: 1, name: 'Edificio Central', code: 'CENTRAL' });
|
||||
buildingsMap.set('2', { id: 2, name: 'Campus Virtual', code: 'VIRTUAL' });
|
||||
|
||||
const classroomList = allClassrooms.map(c => {
|
||||
const bId = c.building_id || (c.is_virtual ? 2 : 1);
|
||||
const bName = c.building || (c.is_virtual ? 'Campus Virtual' : 'Edificio Central');
|
||||
if (!buildingsMap.has(String(bId))) {
|
||||
buildingsMap.set(String(bId), { id: bId, name: bName, code: c.is_virtual ? 'VIRTUAL' : 'CENTRAL' });
|
||||
}
|
||||
const flDisplay = c.floor_display || (c.floor === '0' || c.floor === 'PB' ? 'Planta Baja' : (c.floor ? `Piso ${c.floor}` : 'Campus Virtual'));
|
||||
const cap = c.capacity || (c.is_virtual ? 0 : 35);
|
||||
return {
|
||||
id: c.id,
|
||||
building_id: bId,
|
||||
building_name: bName,
|
||||
floor: c.floor || '',
|
||||
floor_display: flDisplay,
|
||||
code: c.code,
|
||||
code_display: c.code,
|
||||
location_display: c.location_display || `${bName} - ${flDisplay}`,
|
||||
capacity: cap === 0 ? '∞' : String(cap),
|
||||
capacity_display: cap === 0 ? '∞' : String(cap),
|
||||
capacity_num: cap,
|
||||
is_virtual: Boolean(c.is_virtual || (c.code && c.code.includes('VIRTUAL')))
|
||||
};
|
||||
});
|
||||
|
||||
const commissionList = [
|
||||
{ id: 1, code: 'COM-ARQ-01', name: 'Arquitectura de Software - Comisión A', virtual_link: 'https://meet.google.com/edu-arq-01', max_students: 35 },
|
||||
{ id: 2, code: 'COM-BD-01', name: 'Bases de Datos II - Comisión Única', virtual_link: 'https://meet.google.com/edu-bd-01', max_students: 40 },
|
||||
{ id: 3, code: 'COM-PRG-01', name: 'Programación Avanzada - Comisión B', virtual_link: 'https://meet.google.com/edu-prg-01', max_students: 30 },
|
||||
{ id: 4, code: 'COM-RED-01', name: 'Redes y Telecomunicaciones - Comisión A', virtual_link: '', max_students: 28 },
|
||||
{ id: 5, code: 'COM-IA-01', name: 'Inteligencia Artificial - Comisión Tarde', virtual_link: 'https://meet.google.com/edu-ia-01', max_students: 32 }
|
||||
];
|
||||
|
||||
const classroomOptions = classroomList.map(c => ({ value: c.id, text: `${c.code} · ${c.floor_display} (Cap: ${c.capacity_display})` }));
|
||||
const commissionOptions = commissionList.map(c => ({ value: c.id, text: `${c.code} · ${c.name} (${c.max_students} alum.)` }));
|
||||
|
||||
const st = reservation.start_time ? reservation.start_time.substring(0, 16) : '';
|
||||
const et = reservation.end_time ? reservation.end_time.substring(0, 16) : '';
|
||||
|
||||
const form = createFormMock({
|
||||
classroom_id: reservation.classroom_id || '',
|
||||
commission_id: reservation.commission_id || '',
|
||||
start_time: st,
|
||||
end_time: et,
|
||||
expected_attendees: reservation.expected_attendees || 30,
|
||||
purpose: reservation.purpose || 'Clase Regular',
|
||||
notes: reservation.notes || '',
|
||||
virtual_link: reservation.virtual_link || ''
|
||||
}, {
|
||||
classroom_id: classroomOptions,
|
||||
commission_id: commissionOptions
|
||||
});
|
||||
|
||||
res.render('schedule/edit', {
|
||||
title: `Modificar Reserva #${id} - Edu-Space Admin`,
|
||||
reservation,
|
||||
form,
|
||||
classroom_list: classroomList,
|
||||
commission_list: commissionList,
|
||||
buildings: Array.from(buildingsMap.values())
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error rendering edit reservation:', error.message);
|
||||
res.redirect(`/schedule/view_reservation?id=${id}`);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePostEditReservation = async (req, res) => {
|
||||
const id = req.params.id || req.query.id || req.body.id;
|
||||
try {
|
||||
const { classroom_id, commission_id, start_time, end_time, purpose, expected_attendees, notes, virtual_link } = req.body;
|
||||
const payload = {};
|
||||
if (classroom_id) payload.classroom_id = parseInt(classroom_id, 10);
|
||||
if (commission_id) payload.commission_id = parseInt(commission_id, 10);
|
||||
if (start_time) payload.start_time = new Date(start_time).toISOString();
|
||||
if (end_time) payload.end_time = new Date(end_time).toISOString();
|
||||
if (purpose) payload.purpose = purpose;
|
||||
if (expected_attendees) payload.expected_attendees = parseInt(expected_attendees, 10);
|
||||
if (notes !== undefined) payload.notes = notes;
|
||||
if (virtual_link !== undefined) payload.virtual_link = virtual_link;
|
||||
|
||||
await req.apiClient.put(`/reservations/${id}`, payload);
|
||||
res.redirect(`/schedule/view_reservation?id=${id}`);
|
||||
} catch (e) {
|
||||
console.warn('Update reservation notice:', e.response?.data || e.message);
|
||||
res.redirect(`/schedule/view_reservation?id=${id}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 8. Confirmar y Cancelar Acciones
|
||||
const handleConfirmReservation = async (req, res) => {
|
||||
const id = req.params.id || req.body.id || req.query.id;
|
||||
try {
|
||||
await req.apiClient.post(`/reservations/${id}/confirm`);
|
||||
} catch (e) {
|
||||
console.warn('Confirm reservation API notice:', e.response?.data || e.message);
|
||||
}
|
||||
res.redirect(`/schedule/view_reservation?id=${id}`);
|
||||
};
|
||||
|
||||
const handleCancelReservation = async (req, res) => {
|
||||
const id = req.params.id || req.body.id || req.query.id;
|
||||
try {
|
||||
await req.apiClient.post(`/reservations/${id}/cancel`);
|
||||
} catch (e) {
|
||||
console.warn('Cancel reservation API notice:', e.response?.data || e.message);
|
||||
}
|
||||
res.redirect(`/schedule/view_reservation?id=${id}`);
|
||||
};
|
||||
|
||||
router.get(['/view_reservation', '/view_reservation/:id', '/view/:id', '/reservation/:id/view'], handleViewReservation);
|
||||
|
||||
router.get(['/edit_reservation', '/edit_reservation/:id', '/reservation/:id/edit'], handleEditReservation);
|
||||
router.post(['/edit_reservation', '/edit_reservation/:id', '/reservation/:id/edit'], handlePostEditReservation);
|
||||
|
||||
router.post(['/confirm_reservation', '/confirm_reservation/:id', '/reservation/:id/confirm'], handleConfirmReservation);
|
||||
router.post(['/cancel_reservation', '/cancel_reservation/:id', '/reservation/:id/cancel'], handleCancelReservation);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user