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

268 lines
9.4 KiB
JavaScript

const express = require('express');
const router = express.Router();
const { requireAuth } = require('../middlewares/authMiddleware');
const { createFormMock } = require('../utils/formHelper');
router.use(requireAuth);
// Fallback data si la API estuviera inaccesible (con capacidad física real)
const defaultBuildings = [
{
id: 1,
code: 'CENTRAL',
name: 'Edificio Central',
is_active: true,
is_virtual: false,
address: 'Tte. Gral. Juan Domingo Perón 802, CABA',
description: 'Sede central institucional de UniCABA. Aulas de grado, auditorio y laboratorios.',
floors: 4,
classrooms_count: 29,
physical_capacity: 1250,
total_capacity: 1250,
capacity_display: '1250'
},
{
id: 2,
code: 'VIRTUAL',
name: 'Campus Virtual',
is_active: true,
is_virtual: true,
address: 'campus.unicaba.edu.ar / MS Teams & Moodle',
description: 'Entorno de aprendizaje digital, aulas sincrónicas y videoconferencias.',
floors: 0,
classrooms_count: 2,
physical_capacity: 0,
total_capacity: 0,
capacity_display: 'Ilimitada (Virtual)'
}
];
// 1. List Buildings
const handleListBuildings = async (req, res) => {
try {
const { search = '', status = '' } = req.query;
let buildings = [];
let stats = {
total_buildings: 2,
total_classrooms: 31,
physical_classrooms_count: 29,
total_physical_capacity: 1250,
total_floors: 4
};
try {
const apiRes = await req.apiClient.get('/buildings', { params: { search, status } });
if (apiRes.data && apiRes.data.buildings) {
buildings = apiRes.data.buildings;
if (apiRes.data.stats) {
stats = apiRes.data.stats;
}
}
} catch (apiErr) {
console.warn('API /buildings notice, using fallback:', apiErr.message);
buildings = [...defaultBuildings];
}
// Si por alguna razón la lista vino vacía de la API y no había filtros de búsqueda, usar default
if (buildings.length === 0 && !search && !status) {
buildings = [...defaultBuildings];
}
// Asegurar campos calculados consistentes para cada edificio
buildings = buildings.map(b => {
const is_virtual = Boolean(b.is_virtual || (b.code && b.code.toUpperCase() === 'VIRTUAL') || (b.name && b.name.toUpperCase().includes('VIRTUAL')));
return {
...b,
is_virtual,
capacity_display: is_virtual ? 'Ilimitada (Virtual)' : String(b.physical_capacity !== undefined ? b.physical_capacity : (b.total_capacity || 0)),
total_capacity: is_virtual ? 0 : (b.physical_capacity !== undefined ? b.physical_capacity : (b.total_capacity || 0)),
floors_display: is_virtual ? 'Digital' : String(b.floors ?? 1)
};
});
// Capacidad física total: suma exclusiva de sedes presenciales (nunca suma 9999)
const total_physical_capacity = stats.total_physical_capacity || buildings
.filter(b => !b.is_virtual)
.reduce((acc, b) => acc + (b.physical_capacity || b.total_capacity || 0), 0);
res.render('buildings/list', {
title: 'Gestión de Edificios - Edu-Space Admin',
buildings,
total_classrooms: stats.total_classrooms || buildings.reduce((a, b) => a + (b.classrooms_count || 0), 0),
total_capacity: total_physical_capacity,
total_physical_capacity,
search,
status_filter: status
});
} catch (error) {
console.error('Error rendering buildings list:', error.message);
res.render('buildings/list', {
title: 'Gestión de Edificios - Edu-Space Admin',
buildings: defaultBuildings,
total_classrooms: 31,
total_capacity: 1250,
total_physical_capacity: 1250,
search: '',
status_filter: ''
});
}
};
router.get(['/', '/list', '/list_buildings'], handleListBuildings);
// 2. Add Building (GET & POST)
router.get(['/add', '/add_building'], (req, res) => {
const form = createFormMock({
name: '',
code: '',
address: '',
floors: 1,
description: '',
is_active: true
});
res.render('buildings/form', {
title: 'Nuevo Edificio - Edu-Space Admin',
building: null,
form
});
});
router.post(['/add', '/add_building'], async (req, res) => {
try {
const { name, code, address, floors, description, is_active } = req.body;
const payload = {
name: (name || '').trim(),
code: (code || '').trim().toUpperCase() || null,
address: (address || '').trim() || null,
floors: parseInt(floors, 10) || 1,
description: (description || '').trim() || null,
is_active: is_active === 'true' || is_active === 'on' || is_active === true || is_active === '1'
};
await req.apiClient.post('/buildings', payload);
res.redirect('/buildings/list_buildings?created=1');
} catch (err) {
console.warn('Error creating building:', err.response?.data || err.message);
res.redirect('/buildings/list_buildings?error=create');
}
});
// 3. View Building
router.get(['/view/:id', '/view_building', '/:id'], async (req, res) => {
const id = parseInt(req.params.id || req.query.id, 10) || 1;
let building = null;
let classrooms = [];
try {
const apiRes = await req.apiClient.get(`/buildings/${id}`);
if (apiRes.data && apiRes.data.building) {
building = apiRes.data.building;
classrooms = apiRes.data.classrooms || [];
}
} catch (e) {
building = defaultBuildings.find(b => b.id === id) || defaultBuildings[0];
}
if (!building) {
return res.redirect('/buildings/list_buildings');
}
const is_virtual = Boolean(building.is_virtual || (building.code && building.code.toUpperCase() === 'VIRTUAL') || (building.name && building.name.toUpperCase().includes('VIRTUAL')));
building = {
...building,
is_virtual,
capacity_display: is_virtual ? 'Ilimitada (Virtual)' : String(building.physical_capacity || building.total_capacity || 0),
floors_display: is_virtual ? 'Digital' : String(building.floors ?? 1)
};
res.render('buildings/view', {
title: `Edificio ${building.name} - Edu-Space Admin`,
building,
classrooms
});
});
// 4. Edit Building (GET)
const handleEditBuildingGet = async (req, res) => {
const id = parseInt(req.params.id || req.query.id, 10) || 1;
let building = null;
try {
const apiRes = await req.apiClient.get(`/buildings/${id}`);
if (apiRes.data && apiRes.data.building) {
building = apiRes.data.building;
}
} catch (e) {
building = defaultBuildings.find(b => b.id === id) || defaultBuildings[0];
}
if (!building) {
return res.redirect('/buildings/list_buildings');
}
const form = createFormMock({
name: building.name,
code: building.code,
address: building.address,
floors: building.floors,
description: building.description,
is_active: building.is_active
});
res.render('buildings/form', {
title: `Editar Edificio - ${building.name}`,
building,
form
});
};
router.get(['/edit/:id', '/:id/edit', '/edit_building'], handleEditBuildingGet);
// 5. Edit Building (POST)
const handleEditBuildingPost = async (req, res) => {
const id = parseInt(req.params.id || req.query.id || req.body.id, 10) || 1;
const { name, code, address, floors, description, is_active } = req.body;
try {
const payload = {
name: (name || '').trim(),
code: (code || '').trim().toUpperCase() || null,
address: (address || '').trim() || null,
floors: parseInt(floors, 10) || 0,
description: (description || '').trim() || null,
is_active: is_active === 'true' || is_active === 'on' || is_active === true || is_active === '1'
};
await req.apiClient.put(`/buildings/${id}`, payload);
res.redirect('/buildings/list_buildings?updated=1');
} catch (err) {
console.warn('Error updating building:', err.response?.data || err.message);
res.redirect('/buildings/list_buildings?error=update');
}
};
router.post(['/edit/:id', '/:id/edit', '/edit_building'], handleEditBuildingPost);
// 6. Toggle Building
router.post(['/toggle/:id', '/:id/toggle', '/toggle_building'], async (req, res) => {
const id = parseInt(req.params.id || req.query.id || req.body.id, 10) || 1;
try {
await req.apiClient.post(`/buildings/${id}/toggle`);
} catch (e) {
console.warn('Error toggling building:', e.message);
}
res.redirect('/buildings/list_buildings');
});
// 7. Delete Building
router.post(['/delete/:id', '/:id/delete', '/delete_building'], async (req, res) => {
const id = parseInt(req.params.id || req.query.id || req.body.id, 10);
try {
await req.apiClient.delete(`/buildings/${id}`);
} catch (e) {
console.warn('Error deleting building:', e.message);
}
res.redirect('/buildings/list_buildings');
});
module.exports = router;