691 lines
37 KiB
HTML
691 lines
37 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{% trans %}Aulas y Espacios{% endtrans %} - Edu-Space Admin{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.custom-space-pills .nav-link {
|
|
font-weight: 600;
|
|
font-size: 0.9rem;
|
|
padding: 0.5rem 1.1rem;
|
|
border-radius: 0.5rem;
|
|
color: var(--bs-body-color);
|
|
background-color: var(--bs-tertiary-bg);
|
|
border: 1px solid var(--bs-border-color);
|
|
transition: all 0.15s ease;
|
|
}
|
|
.custom-space-pills .nav-link.active {
|
|
background-color: var(--bs-primary);
|
|
color: #fff !important;
|
|
border-color: var(--bs-primary);
|
|
box-shadow: 0 4px 12px rgba(180, 62, 142, 0.25);
|
|
}
|
|
.custom-space-pills .nav-link:hover:not(.active) {
|
|
background-color: var(--bs-secondary-bg);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.classroom-card, .virtual-room-card {
|
|
transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
|
|
border-radius: 0.65rem;
|
|
}
|
|
.classroom-card:hover, .virtual-room-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.08) !important;
|
|
}
|
|
|
|
.virtual-banner {
|
|
background: linear-gradient(135deg, rgba(99, 102, 241, 0.08) 0%, rgba(180, 62, 142, 0.08) 100%);
|
|
border: 1px solid rgba(99, 102, 241, 0.25) !important;
|
|
border-radius: 0.75rem;
|
|
}
|
|
[data-bs-theme="dark"] .virtual-banner {
|
|
background: linear-gradient(135deg, rgba(99, 102, 241, 0.16) 0%, rgba(180, 62, 142, 0.16) 100%);
|
|
border: 1px solid rgba(99, 102, 241, 0.35) !important;
|
|
}
|
|
|
|
.virtual-stat-box {
|
|
background-color: var(--bs-body-bg);
|
|
border: 1px solid var(--bs-border-color);
|
|
border-radius: 0.5rem;
|
|
padding: 0.6rem 0.85rem;
|
|
transition: transform 0.15s ease, border-color 0.15s ease;
|
|
text-decoration: none !important;
|
|
display: block;
|
|
}
|
|
.virtual-stat-box:hover {
|
|
transform: translateY(-2px);
|
|
border-color: var(--bs-primary) !important;
|
|
}
|
|
|
|
.metric-pill {
|
|
padding: 0.35rem 0.2rem;
|
|
border-radius: 0.45rem;
|
|
transition: all 0.15s ease;
|
|
cursor: pointer;
|
|
line-height: 1.2;
|
|
}
|
|
.metric-pill:hover {
|
|
transform: translateY(-1px);
|
|
filter: brightness(1.06);
|
|
}
|
|
.metric-label {
|
|
font-size: 0.65rem;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.3px;
|
|
margin-bottom: 2px;
|
|
}
|
|
.metric-val {
|
|
font-size: 0.85rem;
|
|
font-weight: 700;
|
|
font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
}
|
|
|
|
/* Semantics light */
|
|
.metric-high {
|
|
background-color: #FEE2E2;
|
|
border-color: #FCA5A5 !important;
|
|
color: #991B1B !important;
|
|
}
|
|
.metric-medium {
|
|
background-color: #FEF3C7;
|
|
border-color: #FCD34D !important;
|
|
color: #92400E !important;
|
|
}
|
|
.metric-low {
|
|
background-color: #D1FAE5;
|
|
border-color: #6EE7B7 !important;
|
|
color: #065F46 !important;
|
|
}
|
|
.metric-zero {
|
|
background-color: rgba(148, 163, 184, 0.1);
|
|
border-color: rgba(148, 163, 184, 0.25) !important;
|
|
color: #64748B !important;
|
|
}
|
|
|
|
/* Semantics dark */
|
|
[data-bs-theme="dark"] .metric-high {
|
|
background-color: rgba(239, 68, 68, 0.18);
|
|
border-color: rgba(239, 68, 68, 0.4) !important;
|
|
color: #FCA5A5 !important;
|
|
}
|
|
[data-bs-theme="dark"] .metric-medium {
|
|
background-color: rgba(245, 158, 11, 0.18);
|
|
border-color: rgba(245, 158, 11, 0.4) !important;
|
|
color: #FCD34D !important;
|
|
}
|
|
[data-bs-theme="dark"] .metric-low {
|
|
background-color: rgba(16, 185, 129, 0.18);
|
|
border-color: rgba(16, 185, 129, 0.4) !important;
|
|
color: #6EE7B7 !important;
|
|
}
|
|
[data-bs-theme="dark"] .metric-zero {
|
|
background-color: rgba(148, 163, 184, 0.08);
|
|
border-color: rgba(148, 163, 184, 0.18) !important;
|
|
color: #94A3B8 !important;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid pt-5 mt-4">
|
|
|
|
<!-- 1. Encabezado y Selector de Entorno (Presencial vs Virtual) -->
|
|
<div class="row mb-3">
|
|
<div class="col-12">
|
|
<div class="d-flex justify-content-between align-items-center flex-wrap gap-3">
|
|
<div>
|
|
<h1 class="h3 mb-1">
|
|
<i class="bi bi-door-open text-primary me-2"></i>{% trans %}Aulas y Espacios de Aprendizaje{% endtrans %}
|
|
</h1>
|
|
<p class="text-muted small mb-0">{% trans %}Administre las aulas físicas de sedes presenciales y las aulas virtuales a demanda del Campus Virtual.{% endtrans %}</p>
|
|
</div>
|
|
<div>
|
|
{% if view_mode == 'virtual' %}
|
|
<a href="{{ url_for('schedule.add_reservation') }}?is_virtual=1" class="btn btn-success shadow-sm">
|
|
<i class="bi bi-camera-video me-1"></i>{% trans %}Nueva Clase Virtual a Demanda{% endtrans %}
|
|
</a>
|
|
{% else %}
|
|
<a href="{{ url_for('classrooms.add_classroom') }}" class="btn btn-primary shadow-sm">
|
|
<i class="bi bi-plus-circle me-1"></i>{% trans %}Agregar Aula Física{% endtrans %}
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pestañas de Selección de Modalidad -->
|
|
<div class="mt-3">
|
|
<ul class="nav nav-pills custom-space-pills gap-2">
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if view_mode == 'physical' %}active{% endif %}"
|
|
href="{{ url_for('classrooms.list_classrooms', view_mode='physical', active_only='true' if active_only else 'false') }}">
|
|
<i class="bi bi-buildings me-2"></i>{% trans %}Aulas Presenciales{% endtrans %}
|
|
<span class="badge rounded-pill ms-2 {% if view_mode == 'physical' %}bg-light text-primary{% else %}bg-secondary-subtle text-secondary border{% endif %}">
|
|
{{ physical_count }} {% trans %}aulas{% endtrans %}
|
|
</span>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if view_mode == 'virtual' %}active{% endif %}"
|
|
href="{{ url_for('classrooms.list_classrooms', view_mode='virtual') }}">
|
|
<i class="bi bi-globe2 me-2 text-info"></i>{% trans %}Campus Virtual & Aulas a Demanda{% endtrans %}
|
|
<span class="badge rounded-pill ms-2 {% if view_mode == 'virtual' %}bg-light text-primary{% else %}bg-primary-subtle text-primary border{% endif %}">
|
|
<i class="bi bi-infinity me-1"></i>{{ virtual_count }} {% trans %}salas activas{% endtrans %}
|
|
</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ========================================================================= -->
|
|
<!-- VISTA 1: AULAS FÍSICAS (PRESENCIALES) -->
|
|
<!-- ========================================================================= -->
|
|
{% if view_mode == 'physical' %}
|
|
|
|
<!-- Filtros de Aulas Físicas -->
|
|
<div class="row mb-3">
|
|
<div class="col-12">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body py-2">
|
|
<form method="GET" class="row g-2 align-items-center">
|
|
<input type="hidden" name="view_mode" value="physical">
|
|
<div class="col-md-3">
|
|
<div class="input-group input-group-sm">
|
|
<span class="input-group-text bg-body-tertiary"><i class="bi bi-search"></i></span>
|
|
<input type="text" name="search" class="form-control"
|
|
placeholder="{% trans %}Buscar aula, descripción...{% endtrans %}"
|
|
value="{{ search }}">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filtro por Edificio Físico -->
|
|
<div class="col-md-3">
|
|
<div class="input-group input-group-sm">
|
|
<span class="input-group-text bg-body-tertiary"><i class="bi bi-buildings text-primary"></i></span>
|
|
<select name="building_id" class="form-select form-select-sm" onchange="this.form.submit()">
|
|
<option value="">{% trans %}Todos los edificios presenciales{% endtrans %}</option>
|
|
{% for b in buildings %}
|
|
<option value="{{ b.id }}" {% if building_filter == (b.id|string) %}selected{% endif %}>{{ b.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filtro por Piso Real -->
|
|
<div class="col-md-2">
|
|
<div class="input-group input-group-sm">
|
|
<span class="input-group-text bg-body-tertiary"><i class="bi bi-layers text-secondary"></i></span>
|
|
<select name="floor" class="form-select form-select-sm" onchange="this.form.submit()">
|
|
<option value="">{% trans %}Todos los pisos{% endtrans %}</option>
|
|
{% for f in floors %}
|
|
<option value="{{ f }}" {% if floor_filter == (f|string) %}selected{% endif %}>
|
|
{% if f == '0' or f == 'PB' %}{% trans %}Planta Baja{% endtrans %}{% else %}{% trans %}Piso{% endtrans %} {{ f }}{% endif %}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-2">
|
|
<div class="form-check pt-1">
|
|
<input class="form-check-input" type="checkbox" name="active_only" value="true"
|
|
id="activeOnly" {% if active_only %}checked{% endif %} onchange="this.form.submit()">
|
|
<label class="form-check-label small" for="activeOnly">
|
|
{% trans %}Solo activas{% endtrans %}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-2 text-md-end">
|
|
<button type="submit" class="btn btn-sm btn-primary me-1">
|
|
<i class="bi bi-filter"></i> {% trans %}Filtrar{% endtrans %}
|
|
</button>
|
|
<a href="{{ url_for('classrooms.list_classrooms', view_mode='physical') }}" class="btn btn-sm btn-outline-secondary">
|
|
<i class="bi bi-x-circle"></i> {% trans %}Limpiar{% endtrans %}
|
|
</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Grilla de Aulas Físicas -->
|
|
{% if classrooms and classrooms.items %}
|
|
<div class="row g-3 mt-1">
|
|
{% for classroom in classrooms.items %}
|
|
<div class="col-xxl-3 col-xl-3 col-lg-4 col-md-6 col-sm-12 mb-2">
|
|
<div class="card classroom-card border shadow-sm h-100">
|
|
<div class="card-body p-3 d-flex flex-column justify-content-between">
|
|
<div>
|
|
<!-- Encabezado con Código y Estado -->
|
|
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
<div class="text-truncate me-2">
|
|
<h5 class="card-title mb-0 fw-bold text-primary text-truncate fs-6" title="{{ classroom.code }}">
|
|
<i class="bi bi-door-open me-1"></i>{{ classroom.code }}
|
|
</h5>
|
|
</div>
|
|
<span class="badge rounded-pill {% if classroom.is_active %}bg-success-subtle text-success border border-success-subtle{% else %}bg-secondary-subtle text-secondary border{% endif %}" style="font-size: 0.68rem;">
|
|
{% if classroom.is_active %}{% trans %}Activa{% endtrans %}{% else %}{% trans %}Inactiva{% endtrans %}{% endif %}
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Ubicación: Edificio y Piso -->
|
|
<div class="d-flex flex-wrap gap-1 mb-2">
|
|
<span class="badge bg-primary-subtle text-primary border" style="font-size: 0.7rem;">
|
|
<i class="bi bi-buildings me-1"></i>{{ classroom.building_name }}
|
|
</span>
|
|
<span class="badge bg-secondary-subtle text-secondary border" style="font-size: 0.7rem;">
|
|
<i class="bi bi-layers me-1"></i>{{ classroom.floor_display }}
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Capacidad y Recursos -->
|
|
<div class="d-flex justify-content-between align-items-center small py-1 px-2 bg-body-tertiary rounded mb-2" style="font-size: 0.75rem;">
|
|
<div>
|
|
<span class="text-muted">{% trans %}Capacidad:{% endtrans %}</span>
|
|
<strong class="text-body ms-1">{{ classroom.capacity }} {% trans %}asientos{% endtrans %}</strong>
|
|
</div>
|
|
<div>
|
|
<span class="text-muted"><i class="bi bi-tools me-1"></i>{{ classroom.resources|length if classroom.resources else 0 }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Barra de Métricas de Ocupación FÍSICA (%) -->
|
|
{% set metrics = occupancy_metrics.get(classroom.id, {'day_pct': 0, 'day_count': 0, 'week_pct': 0, 'week_count': 0, 'month_pct': 0, 'month_count': 0}) %}
|
|
<div class="classroom-occupancy-bar p-2 rounded bg-body-tertiary border mb-2">
|
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
<span class="small fw-semibold text-muted" style="font-size: 0.72rem;">
|
|
<i class="bi bi-graph-up me-1 text-primary"></i>{% trans %}Ocupación{% endtrans %}
|
|
</span>
|
|
<span class="text-muted" style="font-size: 0.65rem;" title="{% trans %}Haz clic en cualquier métrica para ver las reservas de ese período{% endtrans %}">
|
|
<i class="bi bi-info-circle me-1"></i>{% trans %}Ver reservas{% endtrans %}
|
|
</span>
|
|
</div>
|
|
<div class="row g-1 text-center">
|
|
<!-- Día -->
|
|
<div class="col-4">
|
|
<a href="{{ url_for('schedule.list_reservations', classroom_id=classroom.id, date_from=date_filters.today, date_to=date_filters.today) }}"
|
|
class="d-block p-1 text-decoration-none border metric-pill metric-{% if metrics.day_pct > 80 %}high{% elif metrics.day_pct > 40 %}medium{% elif metrics.day_pct > 0 %}low{% else %}zero{% endif %}"
|
|
title="{% trans %}Ocupación Hoy{% endtrans %}: {{ metrics.day_count }} {% trans %}clases registradas{% endtrans %}">
|
|
<div class="metric-label">{% trans %}Hoy{% endtrans %}</div>
|
|
<div class="metric-val">{{ metrics.day_pct }}%</div>
|
|
</a>
|
|
</div>
|
|
<!-- Semana -->
|
|
<div class="col-4">
|
|
<a href="{{ url_for('schedule.list_reservations', classroom_id=classroom.id, date_from=date_filters.week_start, date_to=date_filters.week_end) }}"
|
|
class="d-block p-1 text-decoration-none border metric-pill metric-{% if metrics.week_pct > 80 %}high{% elif metrics.week_pct > 40 %}medium{% elif metrics.week_pct > 0 %}low{% else %}zero{% endif %}"
|
|
title="{% trans %}Ocupación Semana{% endtrans %}: {{ metrics.week_count }} {% trans %}clases registradas{% endtrans %}">
|
|
<div class="metric-label">{% trans %}Semana{% endtrans %}</div>
|
|
<div class="metric-val">{{ metrics.week_pct }}%</div>
|
|
</a>
|
|
</div>
|
|
<!-- Mes -->
|
|
<div class="col-4">
|
|
<a href="{{ url_for('schedule.list_reservations', classroom_id=classroom.id, date_from=date_filters.month_start, date_to=date_filters.month_end) }}"
|
|
class="d-block p-1 text-decoration-none border metric-pill metric-{% if metrics.month_pct > 80 %}high{% elif metrics.month_pct > 40 %}medium{% elif metrics.month_pct > 0 %}low{% else %}zero{% endif %}"
|
|
title="{% trans %}Ocupación Mes{% endtrans %}: {{ metrics.month_count }} {% trans %}clases registradas{% endtrans %}">
|
|
<div class="metric-label">{% trans %}Mes{% endtrans %}</div>
|
|
<div class="metric-val">{{ metrics.month_pct }}%</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer con Botones Compactos -->
|
|
<div class="pt-2 border-top d-flex justify-content-between align-items-center">
|
|
<div class="btn-group btn-group-sm">
|
|
<a href="{{ url_for('classrooms.view_classroom', id=classroom.id) }}"
|
|
class="btn btn-outline-primary py-0 px-2" title="{% trans %}Ver Ficha{% endtrans %}" style="font-size: 0.75rem;">
|
|
<i class="bi bi-eye me-1"></i>{% trans %}Ficha{% endtrans %}
|
|
</a>
|
|
<a href="{{ url_for('classrooms.edit_classroom', id=classroom.id) }}"
|
|
class="btn btn-outline-secondary py-0 px-2" title="{% trans %}Editar Aula{% endtrans %}" style="font-size: 0.75rem;">
|
|
<i class="bi bi-pencil"></i>
|
|
</a>
|
|
</div>
|
|
<form method="POST" action="{{ url_for('classrooms.toggle_classroom', id=classroom.id) }}" class="d-inline mb-0">
|
|
<button type="submit" class="btn btn-sm py-0 px-2 {% if classroom.is_active %}btn-outline-warning{% else %}btn-outline-success{% endif %}"
|
|
title="{% if classroom.is_active %}{% trans %}Desactivar{% endtrans %}{% else %}{% trans %}Activar{% endtrans %}{% endif %}" style="font-size: 0.75rem;">
|
|
{% if classroom.is_active %}
|
|
<i class="bi bi-pause"></i>
|
|
{% else %}
|
|
<i class="bi bi-play"></i>
|
|
{% endif %}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Paginación Aulas Físicas -->
|
|
{% if classrooms.pages > 1 %}
|
|
<nav aria-label="Classroom pagination" class="mt-4">
|
|
<ul class="pagination pagination-sm justify-content-center">
|
|
{% if classrooms.has_prev %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="{{ url_for('classrooms.list_classrooms', view_mode='physical', page=classrooms.prev_num, search=search, active_only='true' if active_only else 'false', building_id=building_filter, floor=floor_filter) }}">
|
|
{% trans %}Anterior{% endtrans %}
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
{% for page_num in classrooms.iter_pages() %}
|
|
{% if page_num %}
|
|
{% if page_num != classrooms.page %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="{{ url_for('classrooms.list_classrooms', view_mode='physical', page=page_num, search=search, active_only='true' if active_only else 'false', building_id=building_filter, floor=floor_filter) }}">
|
|
{{ page_num }}
|
|
</a>
|
|
</li>
|
|
{% else %}
|
|
<li class="page-item active">
|
|
<span class="page-link">{{ page_num }}</span>
|
|
</li>
|
|
{% endif %}
|
|
{% else %}
|
|
<li class="page-item disabled">
|
|
<span class="page-link">…</span>
|
|
</li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if classrooms.has_next %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="{{ url_for('classrooms.list_classrooms', view_mode='physical', page=classrooms.next_num, search=search, active_only='true' if active_only else 'false', building_id=building_filter, floor=floor_filter) }}">
|
|
{% trans %}Siguiente{% endtrans %}
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
{% endif %}
|
|
|
|
{% else %}
|
|
<div class="row mt-4">
|
|
<div class="col-12">
|
|
<div class="text-center py-5">
|
|
<i class="bi bi-door-open text-muted" style="font-size: 4rem;"></i>
|
|
<h4 class="mt-3 text-muted">{% trans %}No se encontraron aulas físicas{% endtrans %}</h4>
|
|
<p class="text-muted">
|
|
{% if search %}
|
|
{% trans %}Ningún aula coincide con los criterios de búsqueda.{% endtrans %}
|
|
{% else %}
|
|
{% trans %}No hay aulas físicas registradas en esta sede.{% endtrans %}
|
|
{% endif %}
|
|
</p>
|
|
<a href="{{ url_for('classrooms.add_classroom') }}" class="btn btn-primary">
|
|
<i class="bi bi-plus-circle"></i> {% trans %}Agregar Primera Aula{% endtrans %}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- ========================================================================= -->
|
|
<!-- VISTA 2: CAMPUS VIRTUAL (AULAS A DEMANDA · INFINITAS) -->
|
|
<!-- ========================================================================= -->
|
|
{% else %}
|
|
|
|
<!-- Banner de Infraestructura Cloud y Concurrencia Ilimitada -->
|
|
<div class="card virtual-banner p-3 mb-4 shadow-sm">
|
|
<div class="d-flex justify-content-between align-items-start flex-wrap gap-2 mb-3">
|
|
<div>
|
|
<div class="d-flex align-items-center gap-2">
|
|
<span class="badge bg-primary text-white px-2 py-1"><i class="bi bi-cloud-check-fill me-1"></i>Cloud Infrastructure</span>
|
|
<h5 class="mb-0 fw-bold text-primary">
|
|
{% trans %}Campus Virtual UniCABA · Aulas a Demanda{% endtrans %}
|
|
</h5>
|
|
</div>
|
|
<p class="small text-muted mb-0 mt-1">
|
|
{% trans %}A diferencia de los espacios físicos, las aulas virtuales se crean y escalan de manera <strong>infinita y concurrente</strong> según la necesidad pedagógica de cada comisión.{% endtrans %}
|
|
</p>
|
|
</div>
|
|
<div class="d-flex gap-2 align-items-center">
|
|
<span class="badge bg-success-subtle text-success border border-success-subtle px-3 py-2 fs-6">
|
|
<i class="bi bi-infinity me-1"></i>{% trans %}Capacidad Ilimitada{% endtrans %}
|
|
</span>
|
|
<a href="{{ url_for('schedule.add_reservation') }}?is_virtual=1" class="btn btn-sm btn-primary">
|
|
<i class="bi bi-plus-lg me-1"></i>{% trans %}Programar Clase Virtual{% endtrans %}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Indicadores de Demanda Global -->
|
|
<div class="row g-2">
|
|
<div class="col-lg-3 col-sm-6">
|
|
<a href="{{ url_for('schedule.today_schedule') }}" class="virtual-stat-box">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<span class="text-muted small text-uppercase fw-semibold" style="font-size: 0.7rem;">{% trans %}Sesiones Hoy{% endtrans %}</span>
|
|
<h4 class="mb-0 fw-bold text-primary">{{ global_virtual_metrics.today_count }}</h4>
|
|
</div>
|
|
<div class="text-primary fs-4"><i class="bi bi-calendar-event"></i></div>
|
|
</div>
|
|
<small class="text-muted d-block mt-1" style="font-size: 0.72rem;">{% trans %}Clases virtuales programadas hoy{% endtrans %}</small>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="col-lg-3 col-sm-6">
|
|
<a href="{{ url_for('schedule.list_reservations', classroom_id=virtual_classroom_id, date_from=date_filters.week_start, date_to=date_filters.week_end) }}" class="virtual-stat-box">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<span class="text-muted small text-uppercase fw-semibold" style="font-size: 0.7rem;">{% trans %}Sesiones Semana{% endtrans %}</span>
|
|
<h4 class="mb-0 fw-bold text-info">{{ global_virtual_metrics.week_count }}</h4>
|
|
</div>
|
|
<div class="text-info fs-4"><i class="bi bi-calendar-week"></i></div>
|
|
</div>
|
|
<small class="text-muted d-block mt-1" style="font-size: 0.72rem;">{% trans %}Clases virtuales esta semana{% endtrans %}</small>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="col-lg-3 col-sm-6">
|
|
<a href="{{ url_for('schedule.list_reservations', classroom_id=virtual_classroom_id, date_from=date_filters.month_start, date_to=date_filters.month_end) }}" class="virtual-stat-box">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<span class="text-muted small text-uppercase fw-semibold" style="font-size: 0.7rem;">{% trans %}Sesiones del Mes{% endtrans %}</span>
|
|
<h4 class="mb-0 fw-bold text-success">{{ global_virtual_metrics.month_count }}</h4>
|
|
</div>
|
|
<div class="text-success fs-4"><i class="bi bi-calendar3"></i></div>
|
|
</div>
|
|
<small class="text-muted d-block mt-1" style="font-size: 0.72rem;">{% trans %}Total clases remotas en el mes{% endtrans %}</small>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="col-lg-3 col-sm-6">
|
|
<div class="virtual-stat-box">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<span class="text-muted small text-uppercase fw-semibold" style="font-size: 0.7rem;">{% trans %}Salas Activas{% endtrans %}</span>
|
|
<h4 class="mb-0 fw-bold text-warning">{{ global_virtual_metrics.commissions_count }}</h4>
|
|
</div>
|
|
<div class="text-warning fs-4"><i class="bi bi-diagram-3"></i></div>
|
|
</div>
|
|
<small class="text-muted d-block mt-1" style="font-size: 0.72rem;">{% trans %}Comisiones con entorno virtual{% endtrans %}</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Barra de Búsqueda de Salas Virtuales -->
|
|
<div class="row mb-3">
|
|
<div class="col-12">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body py-2">
|
|
<form method="GET" class="row g-2 align-items-center">
|
|
<input type="hidden" name="view_mode" value="virtual">
|
|
<div class="col-md-6">
|
|
<div class="input-group input-group-sm">
|
|
<span class="input-group-text bg-body-tertiary"><i class="bi bi-search"></i></span>
|
|
<input type="text" name="search" class="form-control"
|
|
placeholder="{% trans %}Buscar sala virtual por asignatura, comisión o carrera...{% endtrans %}"
|
|
value="{{ search }}">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6 text-md-end">
|
|
<button type="submit" class="btn btn-sm btn-primary me-1">
|
|
<i class="bi bi-funnel me-1"></i>{% trans %}Buscar Salas{% endtrans %}
|
|
</button>
|
|
<a href="{{ url_for('classrooms.list_classrooms', view_mode='virtual') }}" class="btn btn-sm btn-outline-secondary">
|
|
<i class="bi bi-arrow-counterclockwise me-1"></i>{% trans %}Limpiar{% endtrans %}
|
|
</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Catálogo de Aulas Virtuales por Comisión -->
|
|
{% if virtual_rooms %}
|
|
<div class="row g-3">
|
|
{% for room in virtual_rooms %}
|
|
<div class="col-xxl-3 col-xl-3 col-lg-4 col-md-6 col-sm-12 mb-2">
|
|
<div class="card virtual-room-card border shadow-sm h-100">
|
|
<div class="card-body p-3 d-flex flex-column justify-content-between">
|
|
<div>
|
|
<!-- Encabezado de la Sala Virtual -->
|
|
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
<div class="text-truncate me-2">
|
|
<h5 class="card-title mb-0 fw-bold text-primary text-truncate fs-6" title="{{ room.subject_name }}">
|
|
<i class="bi bi-laptop me-1 text-info"></i>{{ room.subject_name }}
|
|
</h5>
|
|
</div>
|
|
<span class="badge rounded-pill bg-info-subtle text-info border border-info-subtle" style="font-size: 0.68rem;">
|
|
<i class="bi bi-infinity me-1"></i>A Demanda
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Badges: Comisión, Carrera y Turno -->
|
|
<div class="d-flex flex-wrap gap-1 mb-2">
|
|
<span class="badge bg-primary-subtle text-primary border" style="font-size: 0.7rem;">
|
|
<i class="bi bi-diagram-3 me-1"></i>Com. {{ room.commission_code }}
|
|
</span>
|
|
{% if room.subject_code %}
|
|
<span class="badge bg-secondary-subtle text-secondary border" style="font-size: 0.7rem;">
|
|
{{ room.subject_code }}
|
|
</span>
|
|
{% endif %}
|
|
<span class="badge bg-light text-dark border" style="font-size: 0.7rem;">
|
|
{{ room.shift }}
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Cupo e Información Pedagógica -->
|
|
<div class="d-flex justify-content-between align-items-center small py-1 px-2 bg-body-tertiary rounded mb-2" style="font-size: 0.75rem;">
|
|
<div>
|
|
<span class="text-muted">{% trans %}Alumnos:{% endtrans %}</span>
|
|
<strong class="text-body ms-1">{{ room.max_students }}</strong>
|
|
</div>
|
|
<div>
|
|
<span class="text-muted">{% trans %}Modalidad:{% endtrans %}</span>
|
|
<strong class="text-info ms-1">Remoto ∞</strong>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Botón de Videollamada (Zoom / Meet / Teams) -->
|
|
<div class="mb-2">
|
|
{% if room.virtual_link %}
|
|
<a href="{{ room.virtual_link }}" target="_blank"
|
|
class="btn btn-sm btn-outline-success w-100 py-1 d-flex align-items-center justify-content-center"
|
|
style="font-size: 0.75rem;" title="{% trans %}Abrir enlace de videollamada{% endtrans %}">
|
|
<i class="bi bi-camera-video-fill me-1"></i>{% trans %}Unirse a Videollamada{% endtrans %}
|
|
</a>
|
|
{% else %}
|
|
<div class="p-1 rounded bg-body-tertiary border text-center small text-muted" style="font-size: 0.72rem;">
|
|
<i class="bi bi-link-45deg me-1"></i>{% trans %}Enlace a demanda por clase{% endtrans %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Barra de Demanda de la Sala Virtual (Hoy, Semana, Mes) -->
|
|
<div class="p-2 rounded bg-body-tertiary border mb-2">
|
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
<span class="small fw-semibold text-muted" style="font-size: 0.72rem;">
|
|
<i class="bi bi-calendar3 me-1 text-primary"></i>{% trans %}Clases a Demanda{% endtrans %}
|
|
</span>
|
|
<span class="text-muted" style="font-size: 0.65rem;">
|
|
{{ room.total_count }} {% trans %}totales{% endtrans %}
|
|
</span>
|
|
</div>
|
|
<div class="row g-1 text-center">
|
|
<!-- Hoy -->
|
|
<div class="col-4">
|
|
<a href="{{ url_for('schedule.list_reservations', classroom_id=virtual_classroom_id, search=room.commission_code, date_from=date_filters.today, date_to=date_filters.today) }}"
|
|
class="d-block p-1 text-decoration-none border metric-pill {% if room.day_count > 0 %}metric-low{% else %}metric-zero{% endif %}"
|
|
title="{% trans %}Ver clases de hoy para esta comisión{% endtrans %}">
|
|
<div class="metric-label">{% trans %}Hoy{% endtrans %}</div>
|
|
<div class="metric-val">{{ room.day_count }} cl.</div>
|
|
</a>
|
|
</div>
|
|
<!-- Semana -->
|
|
<div class="col-4">
|
|
<a href="{{ url_for('schedule.list_reservations', classroom_id=virtual_classroom_id, search=room.commission_code, date_from=date_filters.week_start, date_to=date_filters.week_end) }}"
|
|
class="d-block p-1 text-decoration-none border metric-pill {% if room.week_count > 0 %}metric-medium{% else %}metric-zero{% endif %}"
|
|
title="{% trans %}Ver clases de la semana para esta comisión{% endtrans %}">
|
|
<div class="metric-label">{% trans %}Semana{% endtrans %}</div>
|
|
<div class="metric-val">{{ room.week_count }} cl.</div>
|
|
</a>
|
|
</div>
|
|
<!-- Mes -->
|
|
<div class="col-4">
|
|
<a href="{{ url_for('schedule.list_reservations', classroom_id=virtual_classroom_id, search=room.commission_code, date_from=date_filters.month_start, date_to=date_filters.month_end) }}"
|
|
class="d-block p-1 text-decoration-none border metric-pill {% if room.month_count > 0 %}metric-high{% else %}metric-zero{% endif %}"
|
|
title="{% trans %}Ver clases del mes para esta comisión{% endtrans %}">
|
|
<div class="metric-label">{% trans %}Mes{% endtrans %}</div>
|
|
<div class="metric-val">{{ room.month_count }} cl.</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer con Botones -->
|
|
<div class="pt-2 border-top d-flex justify-content-between align-items-center">
|
|
<a href="{{ url_for('schedule.list_reservations', classroom_id=virtual_classroom_id, search=room.commission_code) }}"
|
|
class="btn btn-sm btn-outline-primary py-0 px-2" style="font-size: 0.75rem;">
|
|
<i class="bi bi-calendar-check me-1"></i>{% trans %}Ver Reservas{% endtrans %}
|
|
</a>
|
|
<a href="{{ url_for('schedule.add_reservation') }}?commission_id={{ room.commission_id }}&is_virtual=1"
|
|
class="btn btn-sm btn-outline-success py-0 px-2" style="font-size: 0.75rem;" title="{% trans %}Programar clase en esta sala virtual{% endtrans %}">
|
|
<i class="bi bi-plus-lg me-1"></i>{% trans %}Programar{% endtrans %}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="row mt-4">
|
|
<div class="col-12">
|
|
<div class="text-center py-5">
|
|
<i class="bi bi-globe text-muted" style="font-size: 4rem;"></i>
|
|
<h4 class="mt-3 text-muted">{% trans %}No se encontraron salas virtuales{% endtrans %}</h4>
|
|
<p class="text-muted">
|
|
{% if search %}
|
|
{% trans %}Ninguna sala virtual coincide con los términos de búsqueda.{% endtrans %}
|
|
{% else %}
|
|
{% trans %}No hay clases virtuales programadas en el Campus Virtual.{% endtrans %}
|
|
{% endif %}
|
|
</p>
|
|
<a href="{{ url_for('schedule.add_reservation') }}?is_virtual=1" class="btn btn-primary">
|
|
<i class="bi bi-camera-video me-1"></i>{% trans %}Crear Primera Reserva Virtual{% endtrans %}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% endif %}
|
|
|
|
</div>
|
|
{% endblock %} |