383 lines
23 KiB
HTML
383 lines
23 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Cartelera de Hoy - Edu-Space Admin{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid px-0">
|
|
<!-- Header -->
|
|
<div class="row mb-3 align-items-center">
|
|
<div class="col-md-7">
|
|
<h1 class="h3 mb-1">
|
|
<i class="bi bi-calendar-day text-info me-2"></i>Cartelera del Día
|
|
<small class="text-muted ms-2 fs-6">{{ format_date(today, format='full')|capitalize }}</small>
|
|
</h1>
|
|
<p class="text-muted small mb-0">Cronograma de clases y asignación de aulas activas para el día de hoy</p>
|
|
</div>
|
|
<div class="col-md-5 text-md-end mt-3 mt-md-0">
|
|
{% if user.has_permission('reservations', 'read_write') %}
|
|
<a href="/schedule/add_reservation" class="btn btn-success me-2">
|
|
<i class="bi bi-plus-circle me-1"></i>Nueva Reserva
|
|
</a>
|
|
{% endif %}
|
|
<a href="/schedule/calendar_view" class="btn btn-outline-primary me-2">
|
|
<i class="bi bi-calendar3 me-1"></i>Ver Calendario
|
|
</a>
|
|
<a href="/schedule/list_reservations" class="btn btn-outline-secondary">
|
|
<i class="bi bi-list me-1"></i>Todas las Reservas
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Stats Cards with Shortcuts -->
|
|
<div class="row mb-4 g-3">
|
|
<div class="col-sm-6 col-xl-3">
|
|
<div class="card border-0 shadow-sm h-100">
|
|
<div class="card-body p-3 d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<span class="text-muted small fw-semibold text-uppercase">Total de Reservas</span>
|
|
<h2 class="mb-0 fw-bold mt-1 text-info">{{ stats.total_reservations }}</h2>
|
|
</div>
|
|
<div class="bg-info bg-opacity-10 rounded-circle p-3 text-info">
|
|
<i class="bi bi-calendar3 fs-3"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6 col-xl-3">
|
|
<div class="card border-0 shadow-sm h-100">
|
|
<div class="card-body p-3 d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<span class="text-muted small fw-semibold text-uppercase">Confirmadas</span>
|
|
<h2 class="mb-0 fw-bold mt-1 text-success">{{ stats.confirmed }}</h2>
|
|
</div>
|
|
<div class="bg-success bg-opacity-10 rounded-circle p-3 text-success">
|
|
<i class="bi bi-check-circle fs-3"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6 col-xl-3">
|
|
<div class="card border-0 shadow-sm h-100">
|
|
<div class="card-body p-3 d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<span class="text-muted small fw-semibold text-uppercase">Pendientes</span>
|
|
<h2 class="mb-0 fw-bold mt-1 text-warning">{{ stats.pending }}</h2>
|
|
</div>
|
|
<div class="bg-warning bg-opacity-10 rounded-circle p-3 text-warning">
|
|
<i class="bi bi-clock fs-3"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6 col-xl-3">
|
|
<div class="card border-0 shadow-sm h-100">
|
|
<div class="card-body p-3 d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<span class="text-muted small fw-semibold text-uppercase">Aulas Ocupadas</span>
|
|
<h2 class="mb-0 fw-bold mt-1 text-primary">{{ stats.occupied_classrooms }}/{{ stats.total_classrooms }}</h2>
|
|
</div>
|
|
<div class="bg-primary bg-opacity-10 rounded-circle p-3 text-primary">
|
|
<i class="bi bi-door-open fs-3"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters Toolbar (Piso, Turno y Ocultar Virtuales) -->
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-body p-3">
|
|
<form method="GET" action="/schedule/today_schedule" class="row g-2 align-items-center">
|
|
<!-- Filter by Floor -->
|
|
<div class="col-lg-3 col-md-4">
|
|
<label for="floorSelect" class="form-label small fw-bold text-muted mb-1">
|
|
<i class="bi bi-layers text-secondary me-1"></i>Piso / Planta
|
|
</label>
|
|
<select name="floor" id="floorSelect" class="form-select form-select-sm">
|
|
<option value="">Todos los pisos</option>
|
|
{% for f in available_floors %}
|
|
<option value="{{ f }}" {% if floor_filter == f|string %}selected{% endif %}>
|
|
{% if f == 0 %}Planta Baja (Piso 0){% else %}Piso {{ f }}{% endif %}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Filter by Turno -->
|
|
<div class="col-lg-3 col-md-4">
|
|
<label for="shiftSelect" class="form-label small fw-bold text-muted mb-1">
|
|
<i class="bi bi-brightness-high text-warning me-1"></i>Turno
|
|
</label>
|
|
<select name="shift" id="shiftSelect" class="form-select form-select-sm">
|
|
<option value="">Todos los turnos</option>
|
|
<option value="Mañana" {% if shift_filter == 'Mañana' %}selected{% endif %}>Turno Mañana</option>
|
|
<option value="Tarde" {% if shift_filter == 'Tarde' %}selected{% endif %}>Turno Tarde / Vespertino</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Hide Virtual Classrooms Toggle -->
|
|
<div class="col-lg-4 col-md-4 pt-md-3">
|
|
<div class="form-check form-switch pt-1">
|
|
<input class="form-check-input" type="checkbox" role="switch" name="hide_virtual" value="1" id="hideVirtualToday" {% if hide_virtual %}checked{% endif %} onchange="this.form.submit()">
|
|
<label class="form-check-label fw-semibold small text-dark" for="hideVirtualToday">
|
|
<i class="bi bi-laptop me-1 text-primary"></i>Ocultar Aulas Virtuales
|
|
<span class="badge bg-secondary-subtle text-secondary border ms-1" style="font-size: 0.72rem;">Capacidad ilimitada</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div class="col-lg-2 col-md-12 pt-md-3 text-lg-end d-flex gap-2">
|
|
<button type="submit" class="btn btn-primary btn-sm flex-fill">
|
|
<i class="bi bi-funnel me-1"></i>Filtrar
|
|
</button>
|
|
<a href="/schedule/today_schedule" class="btn btn-outline-secondary btn-sm" title="Limpiar Filtros">
|
|
<i class="bi bi-arrow-counterclockwise"></i>
|
|
</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Today's Schedule -->
|
|
{% if time_blocks %}
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-header border-0 py-3 bg-body-tertiary">
|
|
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2">
|
|
<h5 class="mb-0 h6 fw-bold">
|
|
<i class="bi bi-clock-history text-info me-2"></i>Vista Cronológica por Horario
|
|
</h5>
|
|
<div class="d-flex gap-2">
|
|
<button class="btn btn-sm btn-outline-primary" id="refresh-btn">
|
|
<i class="bi bi-arrow-clockwise me-1"></i>Actualizar
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-secondary" id="toggle-view-btn">
|
|
<i class="bi bi-grid me-1"></i>Vista en Tabla
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-3">
|
|
<!-- Timeline View -->
|
|
<div id="timeline-view">
|
|
{% for hour in range(7, 22) %}
|
|
<div class="row border-bottom py-3 align-items-start {% if time_blocks[hour] %}bg-light-subtle{% endif %}">
|
|
<div class="col-md-2 col-3">
|
|
<div class="text-center p-2 rounded bg-body-tertiary border">
|
|
<strong class="h5 text-primary mb-0 font-monospace">{% if hour < 10 %}0{% endif %}{{ hour }}:00</strong>
|
|
<div class="small text-muted font-monospace">{{ hour+1 }}:00 hs</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-10 col-9">
|
|
{% if time_blocks[hour] %}
|
|
<div class="row g-2">
|
|
{% for item in time_blocks[hour] %}
|
|
<div class="col-md-6">
|
|
<div class="card border-0 shadow-sm h-100 border-start border-4 border-primary">
|
|
<div class="card-body p-3">
|
|
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
<div>
|
|
<h6 class="card-title fw-bold mb-1 text-primary">
|
|
{{ item.reservation.subject_name }}
|
|
</h6>
|
|
<span class="badge bg-secondary-subtle text-secondary font-monospace border me-1">
|
|
{{ item.reservation.subject_code }}
|
|
</span>
|
|
<span class="badge bg-info-subtle text-info border">
|
|
{{ item.reservation.shift or 'Tarde' }}
|
|
</span>
|
|
</div>
|
|
<span class="badge bg-{% if item.reservation.status == 'CONFIRMED' or item.reservation.status.value == 'CONFIRMED' %}success-subtle text-success border border-success-subtle{% else %}warning-subtle text-warning border border-warning-subtle{% endif %}">
|
|
{% if item.reservation.status == 'CONFIRMED' or item.reservation.status.value == 'CONFIRMED' %}
|
|
Confirmada
|
|
{% else %}
|
|
Pendiente
|
|
{% endif %}
|
|
</span>
|
|
</div>
|
|
|
|
<div class="small text-muted mb-2">
|
|
<span class="fw-semibold text-dark me-2">
|
|
<i class="bi bi-door-open me-1 text-success"></i>{{ item.classroom.code }}
|
|
</span>
|
|
<span class="me-2">
|
|
<i class="bi bi-layers me-1"></i>{% if item.classroom.floor == 0 %}PB{% else %}Piso {{ item.classroom.floor }}{% endif %}
|
|
</span>
|
|
<span>
|
|
<i class="bi bi-clock me-1 text-warning"></i>{{ item.reservation.start_time }} - {{ item.reservation.end_time }} hs
|
|
</span>
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-between align-items-center pt-2 border-top">
|
|
<span class="small text-muted">
|
|
<i class="bi bi-people me-1"></i>{{ item.reservation.expected_attendees }} estudiantes
|
|
</span>
|
|
<div class="d-flex gap-1">
|
|
{% if item.reservation.effective_virtual_link %}
|
|
<a href="{{ item.reservation.effective_virtual_link }}" target="_blank" class="btn btn-sm btn-outline-success py-0 px-2 d-inline-flex align-items-center" title="Unirse a Videollamada (Zoom / Meet)">
|
|
<i class="bi bi-camera-video me-1"></i>Zoom/Meet
|
|
</a>
|
|
{% endif %}
|
|
{% if user.can_edit_reservation(item.reservation) %}
|
|
<a href="/schedule/edit_reservation?id={{ item.reservation.id }}" class="btn btn-sm btn-outline-warning py-0 px-2" title="Modificar Reserva">
|
|
<i class="bi bi-pencil"></i>
|
|
</a>
|
|
{% endif %}
|
|
<a href="/schedule/view_reservation?id={{ item.reservation.id }}" class="btn btn-sm btn-outline-secondary py-0 px-2" title="Ver Ficha Detallada">
|
|
<i class="bi bi-eye me-1"></i>Detalles
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-muted py-2 small ps-2">
|
|
<i class="bi bi-dash-circle me-1"></i>Sin clases programadas en este horario
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Table / Grid View (hidden by default) -->
|
|
<div id="grid-view" style="display: none;">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover align-middle mb-0">
|
|
<thead class="table-light small text-uppercase">
|
|
<tr>
|
|
<th class="ps-3">Horario</th>
|
|
<th>Aula / Piso</th>
|
|
<th>Asignatura / Código</th>
|
|
<th>Turno</th>
|
|
<th>Asistentes</th>
|
|
<th>Estado</th>
|
|
<th class="text-end pe-3">Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for reservation in reservations %}
|
|
<tr>
|
|
<td class="ps-3 font-monospace fw-bold">
|
|
{{ reservation.start_time }} - {{ reservation.end_time }} hs
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-secondary-subtle text-secondary font-monospace border">
|
|
{{ reservation.classroom.code if reservation.classroom else 'Sin Aula' }}
|
|
</span>
|
|
<small class="text-muted d-block">
|
|
{% if reservation.classroom and reservation.classroom.floor == 0 %}Planta Baja{% elif reservation.classroom %}Piso {{ reservation.classroom.floor }}{% endif %}
|
|
</small>
|
|
</td>
|
|
<td>
|
|
<div class="fw-semibold text-primary">{{ reservation.subject_name }}</div>
|
|
<small class="text-muted font-monospace">{{ reservation.subject_code }}</small>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-info-subtle text-info border">
|
|
{{ reservation.shift or 'Tarde' }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<span class="small">{{ reservation.expected_attendees }} estudiantes</span>
|
|
</td>
|
|
<td>
|
|
{% if reservation.status == 'CONFIRMED' or reservation.status.value == 'CONFIRMED' %}
|
|
<span class="badge bg-success-subtle text-success border border-success-subtle">
|
|
<i class="bi bi-check-circle me-1"></i>Confirmada
|
|
</span>
|
|
{% else %}
|
|
<span class="badge bg-warning-subtle text-warning border border-warning-subtle">
|
|
<i class="bi bi-clock me-1"></i>Pendiente
|
|
</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="text-end pe-3">
|
|
{% if reservation.effective_virtual_link %}
|
|
<a href="{{ reservation.effective_virtual_link }}" target="_blank"
|
|
class="btn btn-sm btn-outline-success py-0 px-2 me-1" title="Unirse a Clase Virtual (Zoom/Meet)">
|
|
<i class="bi bi-camera-video"></i>
|
|
</a>
|
|
{% endif %}
|
|
{% if user.can_edit_reservation(reservation) %}
|
|
<a href="/schedule/edit_reservation?id={{ reservation.id }}"
|
|
class="btn btn-sm btn-outline-warning py-0 px-2 me-1" title="Modificar Reserva">
|
|
<i class="bi bi-pencil"></i>
|
|
</a>
|
|
{% endif %}
|
|
<a href="/schedule/view_reservation?id={{ reservation.id }}"
|
|
class="btn btn-sm btn-outline-secondary py-0 px-2" title="Ver Ficha">
|
|
<i class="bi bi-eye"></i>
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="text-center py-5">
|
|
<i class="bi bi-calendar-x text-muted" style="font-size: 4rem;"></i>
|
|
<h4 class="mt-3 text-muted">No hay reservas programadas para hoy</h4>
|
|
<p class="text-muted">
|
|
No se encontraron clases para los filtros seleccionados.
|
|
</p>
|
|
<div class="mt-3">
|
|
{% if user.has_permission('reservations', 'read_write') %}
|
|
<a href="/schedule/add_reservation" class="btn btn-success">
|
|
<i class="bi bi-plus-circle me-1"></i>Crear Primera Reserva
|
|
</a>
|
|
{% endif %}
|
|
<a href="/schedule/calendar_view" class="btn btn-outline-primary ms-2">
|
|
<i class="bi bi-calendar-week me-1"></i>Ver Calendario Semanal
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
var toggleBtn = document.getElementById('toggle-view-btn');
|
|
var timelineView = document.getElementById('timeline-view');
|
|
var gridView = document.getElementById('grid-view');
|
|
|
|
if (toggleBtn && timelineView && gridView) {
|
|
toggleBtn.addEventListener('click', function() {
|
|
if (timelineView.style.display === 'none') {
|
|
timelineView.style.display = 'block';
|
|
gridView.style.display = 'none';
|
|
toggleBtn.innerHTML = '<i class="bi bi-grid me-1"></i>Vista en Tabla';
|
|
} else {
|
|
timelineView.style.display = 'none';
|
|
gridView.style.display = 'block';
|
|
toggleBtn.innerHTML = '<i class="bi bi-clock-history me-1"></i>Vista Cronológica';
|
|
}
|
|
});
|
|
}
|
|
|
|
var refreshBtn = document.getElementById('refresh-btn');
|
|
if (refreshBtn) {
|
|
refreshBtn.addEventListener('click', function() {
|
|
location.reload();
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %} |