feat: rediseno de calendario estilo Google Calendar y correccion de modal de roles
This commit is contained in:
@@ -262,6 +262,29 @@
|
|||||||
* Se expuso el endpoint `GET /api/milestone-types` para consumo desacoplado.
|
* Se expuso el endpoint `GET /api/milestone-types` para consumo desacoplado.
|
||||||
* Se actualizó el modal de eventos académicos (`evento-modal.component.html` y `.ts`) desacoplando las opciones estáticas y cargando dinámicamente el catálogo de tipificaciones activas con fallback automático para funcionamiento offline.
|
* Se actualizó el modal de eventos académicos (`evento-modal.component.html` y `.ts`) desacoplando las opciones estáticas y cargando dinámicamente el catálogo de tipificaciones activas con fallback automático para funcionamiento offline.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 17. Rediseño de Calendario Estilo Google Calendar y Corrección Visual en Eliminación de Roles
|
||||||
|
* **Manejo de Eventos Simultáneos y Densidad Visual ([app/templates/schedule/calendar.html](file:///c:/Workspace/admin-edu-space/app/templates/schedule/calendar.html)):**
|
||||||
|
* Se configuró FullCalendar con `eventMaxStack: 3`, evitando que decenas de reservas concurrentes reduzcan las columnas a franjas milimétricas del 3%.
|
||||||
|
* Se habilitó `dayMaxEvents: 3`, `dayMaxEventRows: true` y `moreLinkClick: 'popover'` nativo de Google Calendar con enlaces "+X más".
|
||||||
|
* Se fijaron `slotMinTime: "07:00:00"`, `slotMaxTime: "23:00:00"`, `slotDuration: "00:30:00"`, `slotLabelInterval: "01:00:00"` y `nowIndicator: true` con la línea roja indicadora de hora actual.
|
||||||
|
* **Diseño de Tarjetas de Evento Píldora (`.gc-event-pill` / `.gc-month-pill`):**
|
||||||
|
* Estilo Google Calendar con barra lateral de color (magenta UniCABA `#B43E8E`, ámbar `#F59E0B` para pendientes, índigo `#6366F1` para aulas virtuales).
|
||||||
|
* Encabezado compacto con hora de inicio y badge de aula (`bi bi-door-open` o `bi bi-camera-video` con fondo suave para `VIRTUAL`).
|
||||||
|
* Tipografía en `0.78rem` con elipsis para evitar desbordes y sombras sutiles.
|
||||||
|
* Compatibilidad completa con modo oscuro (`[data-bs-theme="dark"]`).
|
||||||
|
* **Modal Flotante Interactivo al Clic (`#eventPreviewModal`):**
|
||||||
|
* Reemplazo de la navegación directa por un popover flotante estilizado como Google Calendar.
|
||||||
|
* Banner artístico superior con accesos rápidos: editar (✏️), eliminar/cancelar (🗑️) y cerrar (✕).
|
||||||
|
* Fila principal con indicador cuadrado de color, título completo, código de materia y badge de estado/turno.
|
||||||
|
* Lista con íconos: 🕒 horario y fecha completa, 📍 aula/piso/sede (o enlace directo al Campus Virtual), 👤 carrera/comisión/docente, 📝 hito o notas.
|
||||||
|
* Botones de acción inferior: "Ver Ficha Completa" y "Cerrar".
|
||||||
|
* **Corrección de Modal de Eliminación de Roles ([app/templates/admin/roles/list.html](file:///c:/Workspace/admin-edu-space/app/templates/admin/roles/list.html)):**
|
||||||
|
* Se trasladó el modal de confirmación de eliminación `#deleteRoleModal` fuera de la celda `<td>` y de la tabla `<table>`, situándolo a nivel raíz de la plantilla.
|
||||||
|
* Se corrigió el error visual donde el cuadro emergente temblaba, saltaba o se desplazaba por el recálculo de padding/scrollbar de Bootstrap en la celda de la tabla.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -527,7 +527,9 @@ def calendar_data():
|
|||||||
'status': status_val,
|
'status': status_val,
|
||||||
'purpose': reservation.purpose or '',
|
'purpose': reservation.purpose or '',
|
||||||
'attendees': reservation.expected_attendees or 0,
|
'attendees': reservation.expected_attendees or 0,
|
||||||
'notes': reservation.notes or ''
|
'notes': reservation.notes or '',
|
||||||
|
'teacher': reservation.user.name if reservation.user else '',
|
||||||
|
'cancel_url': url_for('schedule.cancel_reservation', id=reservation.id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -82,37 +82,16 @@
|
|||||||
<i class="bi bi-pencil-square me-1"></i>{% trans %}Permisos{% endtrans %}
|
<i class="bi bi-pencil-square me-1"></i>{% trans %}Permisos{% endtrans %}
|
||||||
</a>
|
</a>
|
||||||
{% if not role.is_system %}
|
{% if not role.is_system %}
|
||||||
<button type="button" class="btn btn-outline-danger" data-bs-toggle="modal" data-bs-target="#deleteModal{{ role.id }}" title="{% trans %}Eliminar Rol{% endtrans %}" {% if role.users|length > 0 %}disabled{% endif %}>
|
<button type="button" class="btn btn-outline-danger btn-delete-role"
|
||||||
|
data-role-id="{{ role.id }}"
|
||||||
|
data-role-name="{{ role.name }}"
|
||||||
|
data-action-url="{{ url_for('admin.role_delete', id=role.id) }}"
|
||||||
|
title="{% trans %}Eliminar Rol{% endtrans %}"
|
||||||
|
{% if role.users|length > 0 %}disabled{% endif %}>
|
||||||
<i class="bi bi-trash"></i>
|
<i class="bi bi-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if not role.is_system %}
|
|
||||||
<!-- Delete Confirmation Modal -->
|
|
||||||
<div class="modal fade" id="deleteModal{{ role.id }}" tabindex="-1" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
|
||||||
<div class="modal-content border-0 shadow">
|
|
||||||
<div class="modal-header border-0">
|
|
||||||
<h5 class="modal-title text-danger">
|
|
||||||
<i class="bi bi-exclamation-triangle-fill me-2"></i>{% trans %}Confirmar Eliminación de Rol{% endtrans %}
|
|
||||||
</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body text-start">
|
|
||||||
<p>{% trans %}¿Está seguro de que desea eliminar el rol{% endtrans %} <strong>{{ role.name }}</strong>?</p>
|
|
||||||
<p class="text-muted small mb-0">{% trans %}Esta acción no se puede deshacer.{% endtrans %}</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer border-0">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">{% trans %}Cancelar{% endtrans %}</button>
|
|
||||||
<form action="{{ url_for('admin.role_delete', id=role.id) }}" method="POST" class="d-inline">
|
|
||||||
<button type="submit" class="btn btn-danger">{% trans %}Eliminar Rol{% endtrans %}</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -121,4 +100,54 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Global de Eliminación fuera de la tabla (evita desplazamientos visuales) -->
|
||||||
|
<div class="modal fade" id="deleteRoleModal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content border-0 shadow-lg" style="border-radius: 14px;">
|
||||||
|
<div class="modal-header border-0 pb-0">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="badge bg-danger-subtle text-danger p-2 rounded-circle me-2">
|
||||||
|
<i class="bi bi-exclamation-triangle-fill fs-5"></i>
|
||||||
|
</div>
|
||||||
|
<h5 class="modal-title fw-bold text-danger mb-0">{% trans %}Confirmar Eliminación de Rol{% endtrans %}</h5>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body text-start py-3">
|
||||||
|
<p class="mb-2">{% trans %}¿Está seguro de que desea eliminar el rol{% endtrans %} <strong id="deleteRoleName" class="text-primary"></strong>?</p>
|
||||||
|
<p class="text-muted small mb-0"><i class="bi bi-info-circle me-1"></i>{% trans %}Esta acción no se puede deshacer.{% endtrans %}</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0 pt-0">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">{% trans %}Cancelar{% endtrans %}</button>
|
||||||
|
<form id="deleteRoleForm" method="POST" class="d-inline">
|
||||||
|
<button type="submit" class="btn btn-danger px-3">
|
||||||
|
<i class="bi bi-trash me-1"></i>{% trans %}Eliminar Rol{% endtrans %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
var deleteModalEl = document.getElementById('deleteRoleModal');
|
||||||
|
var deleteModal = deleteModalEl ? new bootstrap.Modal(deleteModalEl) : null;
|
||||||
|
var deleteRoleName = document.getElementById('deleteRoleName');
|
||||||
|
var deleteRoleForm = document.getElementById('deleteRoleForm');
|
||||||
|
|
||||||
|
document.querySelectorAll('.btn-delete-role').forEach(function(btn) {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
var roleName = this.getAttribute('data-role-name');
|
||||||
|
var actionUrl = this.getAttribute('data-action-url');
|
||||||
|
if (deleteRoleName) deleteRoleName.textContent = roleName;
|
||||||
|
if (deleteRoleForm) deleteRoleForm.action = actionUrl;
|
||||||
|
if (deleteModal) deleteModal.show();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -158,68 +158,111 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Reservation Details Modal -->
|
<!-- Modal Interactivo al Clic (Estilo Google Calendar Preview & Edición Rápida) -->
|
||||||
<div class="modal fade" id="reservationModal" tabindex="-1" aria-hidden="true">
|
<div class="modal fade" id="eventPreviewModal" tabindex="-1" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
<div class="modal-dialog modal-dialog-centered" style="max-width: 490px;">
|
||||||
<div class="modal-content border-0 shadow-lg">
|
<div class="modal-content border-0 shadow-lg gc-modal-card">
|
||||||
<div class="modal-header border-0 pb-0">
|
<!-- Banner Artístico Superior con Botones Rápidos (Estilo Google Calendar) -->
|
||||||
|
<div class="gc-modal-banner" id="gcModalBanner">
|
||||||
|
<div class="d-flex justify-content-end align-items-center gap-2 p-2 gc-banner-actions">
|
||||||
|
<a id="btnPreviewEdit" href="#" class="btn btn-sm btn-icon text-white" title="{% trans %}Editar / Ver Ficha{% endtrans %}">
|
||||||
|
<i class="bi bi-pencil fs-6"></i>
|
||||||
|
</a>
|
||||||
|
{% if current_user.has_permission('reservations', 'read_write') %}
|
||||||
|
<form id="formPreviewCancel" method="POST" class="d-inline" onsubmit="return confirm('{% trans %}¿Confirma que desea cancelar esta reserva?{% endtrans %}');">
|
||||||
|
<button type="submit" class="btn btn-sm btn-icon text-white" title="{% trans %}Cancelar Reserva{% endtrans %}">
|
||||||
|
<i class="bi bi-trash fs-6"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
<button type="button" class="btn btn-sm btn-icon text-white" data-bs-dismiss="modal" aria-label="Close">
|
||||||
|
<i class="bi bi-x-lg fs-6"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Contenido de la Tarjeta Google Calendar -->
|
||||||
|
<div class="modal-body p-4 pt-3">
|
||||||
|
<!-- Fila Principal con Cuadrado de Color y Título en Grande -->
|
||||||
|
<div class="d-flex align-items-start gap-3 mb-3">
|
||||||
|
<div class="gc-color-box rounded-2 mt-1 shadow-sm" id="gcPreviewColorBox" style="width: 22px; height: 22px; background-color: #B43E8E; flex-shrink: 0;"></div>
|
||||||
|
<div class="flex-grow-1 min-w-0">
|
||||||
|
<h4 class="h5 fw-bold mb-1 text-break text-body" id="previewSubjectName">Nombre de Asignatura</h4>
|
||||||
|
<div class="d-flex align-items-center flex-wrap gap-2">
|
||||||
|
<span class="badge font-monospace bg-secondary-subtle text-secondary border" id="previewSubjectCode">ASIG001</span>
|
||||||
|
<span class="badge" id="previewStatusBadge">Confirmada</span>
|
||||||
|
<span class="badge bg-secondary-subtle text-secondary border d-none" id="previewShiftBadge">Turno Tarde</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lista de Atributos con Iconografía Limpia -->
|
||||||
|
<div class="d-flex flex-column gap-3 ps-2 gc-info-list">
|
||||||
|
<!-- 🕒 Horario y Fecha -->
|
||||||
|
<div class="d-flex align-items-center gap-3">
|
||||||
|
<div class="gc-icon-circle text-muted">
|
||||||
|
<i class="bi bi-clock fs-5"></i>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span id="modalCategoryBadge" class="badge badge-brand mb-1">Clase Académica</span>
|
<div class="fw-semibold text-body" id="previewTimeRange">Lunes, 1 de septiembre · 17:30 a 21:30 hs</div>
|
||||||
<h4 class="modal-title fw-bold text-primary" id="modalSubjectTitle">Detalle de la Clase</h4>
|
<small class="text-muted" id="previewDateFull">Horario regular de cursada</small>
|
||||||
</div>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body pt-3" id="reservationDetails">
|
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="p-3 rounded bg-body-tertiary h-100">
|
|
||||||
<h6 class="text-muted small text-uppercase fw-bold mb-2">
|
|
||||||
<i class="bi bi-book text-primary me-1"></i>{% trans %}Materia y Código{% endtrans %}
|
|
||||||
</h6>
|
|
||||||
<p class="h6 fw-bold mb-1" id="modalSubjectName">-</p>
|
|
||||||
<p class="mb-2">
|
|
||||||
<span class="badge bg-dark bg-opacity-75 font-monospace text-white" id="modalSubjectCode">-</span>
|
|
||||||
</p>
|
|
||||||
<p class="small text-muted mb-0" id="modalCareerWrap">
|
|
||||||
<i class="bi bi-mortarboard me-1"></i><span id="modalCareer">-</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="p-3 rounded bg-body-tertiary h-100">
|
<!-- 📍 Espacio / Aula / Virtual -->
|
||||||
<h6 class="text-muted small text-uppercase fw-bold mb-2">
|
<div class="d-flex align-items-center gap-3">
|
||||||
<i class="bi bi-door-open text-success me-1"></i>{% trans %}Espacio Asignado{% endtrans %}
|
<div class="gc-icon-circle text-danger" id="previewLocationIcon">
|
||||||
</h6>
|
<i class="bi bi-geo-alt fs-5"></i>
|
||||||
<p class="h5 fw-bold text-success mb-1" id="modalClassroom">-</p>
|
</div>
|
||||||
<p class="small text-muted mb-0" id="modalBuilding">-</p>
|
<div class="flex-grow-1">
|
||||||
|
<div class="fw-semibold text-body" id="previewClassroom">Aula 304</div>
|
||||||
|
<small class="text-muted" id="previewBuilding">Piso 3 · Edificio Central (Perón 802)</small>
|
||||||
|
<div id="previewVirtualWrapper" class="d-none mt-1">
|
||||||
|
<span class="badge bg-primary-subtle text-primary border">
|
||||||
|
<i class="bi bi-camera-video me-1"></i>{% trans %}Campus Virtual UniCABA (Teams / Moodle){% endtrans %}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
</div>
|
||||||
<div class="p-3 rounded bg-body-tertiary h-100">
|
|
||||||
<h6 class="text-muted small text-uppercase fw-bold mb-2">
|
<!-- 👤 Docente, Carrera y Comisión -->
|
||||||
<i class="bi bi-clock text-warning me-1"></i>{% trans %}Horario Programado{% endtrans %}
|
<div class="d-flex align-items-center gap-3">
|
||||||
</h6>
|
<div class="gc-icon-circle text-primary">
|
||||||
<p class="h6 fw-bold mb-0" id="modalTimeRange">-</p>
|
<i class="bi bi-person fs-5"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="fw-semibold text-body" id="previewCareer">Licenciatura en Sistemas</div>
|
||||||
|
<small class="text-muted">
|
||||||
|
<span id="previewCommission">Comisión 1</span>
|
||||||
|
<span class="mx-1">·</span>
|
||||||
|
<span id="previewTeacher">Docente Asignado</span>
|
||||||
|
<span class="mx-1">·</span>
|
||||||
|
<span id="previewAttendees">35 estudiantes</span>
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="p-3 rounded bg-body-tertiary h-100">
|
<!-- 📝 Hito Académico / Detalle -->
|
||||||
<h6 class="text-muted small text-uppercase fw-bold mb-2">
|
<div class="d-flex align-items-start gap-3" id="previewNotesSection">
|
||||||
<i class="bi bi-shield-check text-info me-1"></i>{% trans %}Estado y Capacidad{% endtrans %}
|
<div class="gc-icon-circle text-warning mt-1">
|
||||||
</h6>
|
<i class="bi bi-flag fs-5"></i>
|
||||||
<p class="mb-1">
|
</div>
|
||||||
<span class="badge bg-success-subtle text-success border border-success-subtle px-2 py-1" id="modalStatus">-</span>
|
<div>
|
||||||
</p>
|
<div class="fw-semibold text-body" id="previewPurpose">Clase Regular</div>
|
||||||
<small class="text-muted" id="modalAttendees">-</small>
|
<small class="text-muted text-break" id="previewNotes">Sin observaciones adicionales</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer border-0 pt-0">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">{% trans %}Close{% endtrans %}</button>
|
<!-- Footer de Acciones Rápidas -->
|
||||||
<a href="#" id="modalFullLink" class="btn btn-primary">
|
<div class="modal-footer border-0 p-3 pt-0 d-flex justify-content-between">
|
||||||
|
<a id="previewFullLink" href="#" class="btn btn-outline-primary btn-sm px-3">
|
||||||
<i class="bi bi-box-arrow-up-right me-1"></i>{% trans %}Ver Ficha Completa{% endtrans %}
|
<i class="bi bi-box-arrow-up-right me-1"></i>{% trans %}Ver Ficha Completa{% endtrans %}
|
||||||
</a>
|
</a>
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">
|
||||||
|
{% trans %}Cerrar{% endtrans %}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -230,16 +273,19 @@
|
|||||||
{% block extra_css %}
|
{% block extra_css %}
|
||||||
<link href="https://cdn.jsdelivr.net/npm/fullcalendar@5.11.0/main.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/fullcalendar@5.11.0/main.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
/* FullCalendar Modern Overrides */
|
/* ==========================================================================
|
||||||
|
Google Calendar Aesthetic & FullCalendar Overrides (Light & Dark)
|
||||||
|
========================================================================== */
|
||||||
#calendar {
|
#calendar {
|
||||||
font-family: inherit;
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
min-height: 700px;
|
min-height: 750px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc .fc-toolbar-title {
|
.fc .fc-toolbar-title {
|
||||||
font-size: 1.25rem !important;
|
font-size: 1.35rem !important;
|
||||||
font-weight: 700 !important;
|
font-weight: 700 !important;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
|
letter-spacing: -0.2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc .fc-button-primary {
|
.fc .fc-button-primary {
|
||||||
@@ -262,116 +308,366 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fc-theme-standard td, .fc-theme-standard th {
|
.fc-theme-standard td, .fc-theme-standard th {
|
||||||
border-color: var(--card-border, #E2E8F0) !important;
|
border-color: rgba(0, 0, 0, 0.08) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .fc-theme-standard td,
|
||||||
|
[data-bs-theme="dark"] .fc-theme-standard th {
|
||||||
|
border-color: rgba(255, 255, 255, 0.08) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Column Headers (Google Calendar Style) */
|
||||||
|
.fc .fc-col-header-cell {
|
||||||
|
padding: 6px 0 !important;
|
||||||
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc .fc-col-header-cell-cushion {
|
.fc .fc-col-header-cell-cushion {
|
||||||
color: var(--text-primary);
|
color: var(--bs-body-color, #3c4043);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 8px 4px !important;
|
font-size: 0.82rem;
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-day-today .fc-col-header-cell-cushion {
|
||||||
|
color: #B43E8E !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slot Lines & Label Height */
|
||||||
|
.fc .fc-timegrid-slot {
|
||||||
|
height: 28px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc .fc-timegrid-slot-label-cushion {
|
.fc .fc-timegrid-slot-label-cushion {
|
||||||
color: var(--text-secondary, #64748B);
|
color: var(--text-secondary, #64748B);
|
||||||
font-size: 0.78rem;
|
font-size: 0.72rem;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Event Card Visual Hierarchy */
|
/* Red Now Indicator Line */
|
||||||
.fc-event {
|
.fc .fc-timegrid-now-indicator-line {
|
||||||
cursor: pointer;
|
border-color: #ea4335 !important;
|
||||||
|
border-width: 2px !important;
|
||||||
|
z-index: 12 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc .fc-timegrid-now-indicator-arrow {
|
||||||
|
border-color: #ea4335 !important;
|
||||||
|
border-top-color: transparent !important;
|
||||||
|
border-bottom-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Base Event Wrapper Overrides */
|
||||||
|
.fc-timegrid-event-harness {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-timegrid-event, .fc-daygrid-event {
|
||||||
|
background: transparent !important;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
border-radius: 8px !important;
|
box-shadow: none !important;
|
||||||
overflow: hidden !important;
|
padding: 0 !important;
|
||||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12) !important;
|
|
||||||
transition: transform 0.15s ease, box-shadow 0.15s ease !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-event:hover {
|
.fc-event-main {
|
||||||
transform: translateY(-2px) scale(1.01);
|
padding: 0 !important;
|
||||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.22) !important;
|
height: 100% !important;
|
||||||
z-index: 10 !important;
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-event-confirmed {
|
/* -------------------------------------------------------------
|
||||||
background: linear-gradient(135deg, #B43E8E 0%, #832765 100%) !important;
|
Google Calendar Event Pill (.gc-event-pill)
|
||||||
color: #FFFFFF !important;
|
------------------------------------------------------------- */
|
||||||
}
|
.gc-event-pill {
|
||||||
|
position: relative;
|
||||||
.fc-event-pending {
|
display: flex;
|
||||||
background: linear-gradient(135deg, #F59E0B 0%, #D97706 100%) !important;
|
|
||||||
color: #FFFFFF !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fc-event-custom-container {
|
|
||||||
padding: 5px 7px;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 26px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(180, 62, 142, 0.12);
|
||||||
|
border: 1px solid rgba(180, 62, 142, 0.28);
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.12s ease, box-shadow 0.12s ease, filter 0.12s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gc-event-pill:hover {
|
||||||
|
transform: translateY(-1px) scale(1.01);
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.18);
|
||||||
|
z-index: 20 !important;
|
||||||
|
filter: brightness(0.97);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .gc-event-pill {
|
||||||
|
background: rgba(180, 62, 142, 0.22);
|
||||||
|
border-color: rgba(180, 62, 142, 0.45);
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .gc-event-pill:hover {
|
||||||
|
filter: brightness(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Left Accent Stripe */
|
||||||
|
.gc-event-stripe {
|
||||||
|
width: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: 100%;
|
||||||
|
border-top-left-radius: 5px;
|
||||||
|
border-bottom-left-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Event Content Container */
|
||||||
|
.gc-event-content {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 2px 5px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-event-time-pill {
|
/* Event Header: Time + Room Badge */
|
||||||
font-size: 0.72rem;
|
.gc-event-header {
|
||||||
font-weight: 500;
|
|
||||||
color: rgba(255, 255, 255, 0.85);
|
|
||||||
margin-bottom: 2px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 3px;
|
||||||
|
line-height: 1.1;
|
||||||
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* High Visual Hierarchy for Subject Name */
|
.gc-event-time {
|
||||||
.fc-event-subject-title {
|
font-size: 0.68rem;
|
||||||
font-size: 0.88rem !important;
|
font-weight: 700;
|
||||||
font-weight: 700 !important;
|
color: #7d2662;
|
||||||
line-height: 1.22 !important;
|
white-space: nowrap;
|
||||||
color: #FFFFFF !important;
|
}
|
||||||
letter-spacing: -0.15px;
|
|
||||||
display: -webkit-box;
|
[data-bs-theme="dark"] .gc-event-time {
|
||||||
-webkit-line-clamp: 2;
|
color: #ffb1d9;
|
||||||
-webkit-box-orient: vertical;
|
}
|
||||||
|
|
||||||
|
.gc-event-room {
|
||||||
|
font-size: 0.64rem !important;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 1px 4px !important;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: rgba(180, 62, 142, 0.20) !important;
|
||||||
|
color: #63174c !important;
|
||||||
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
word-break: break-word;
|
max-width: 70%;
|
||||||
margin-bottom: 4px;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Secondary Badges */
|
[data-bs-theme="dark"] .gc-event-room {
|
||||||
.fc-event-badges {
|
background-color: rgba(255, 255, 255, 0.16) !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gc-event-room.gc-badge-virtual {
|
||||||
|
background-color: rgba(99, 102, 241, 0.20) !important;
|
||||||
|
color: #4338ca !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .gc-event-room.gc-badge-virtual {
|
||||||
|
background-color: rgba(99, 102, 241, 0.35) !important;
|
||||||
|
color: #c7d2fe !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Subject Title (Compact with Ellipsis) */
|
||||||
|
.gc-event-title {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.18;
|
||||||
|
color: #202124;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
letter-spacing: -0.1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .gc-event-title {
|
||||||
|
color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status Variants */
|
||||||
|
.gc-event-pill.status-pending {
|
||||||
|
background: rgba(245, 158, 11, 0.12);
|
||||||
|
border-color: rgba(245, 158, 11, 0.32);
|
||||||
|
}
|
||||||
|
.gc-event-pill.status-pending .gc-event-time {
|
||||||
|
color: #b45309;
|
||||||
|
}
|
||||||
|
[data-bs-theme="dark"] .gc-event-pill.status-pending {
|
||||||
|
background: rgba(245, 158, 11, 0.22);
|
||||||
|
}
|
||||||
|
[data-bs-theme="dark"] .gc-event-pill.status-pending .gc-event-time {
|
||||||
|
color: #fcd34d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gc-event-pill.status-virtual {
|
||||||
|
background: rgba(99, 102, 241, 0.12);
|
||||||
|
border-color: rgba(99, 102, 241, 0.30);
|
||||||
|
}
|
||||||
|
[data-bs-theme="dark"] .gc-event-pill.status-virtual {
|
||||||
|
background: rgba(99, 102, 241, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------
|
||||||
|
Month View Pill (.gc-month-pill)
|
||||||
|
------------------------------------------------------------- */
|
||||||
|
.gc-month-pill {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
gap: 4px;
|
||||||
gap: 3px;
|
padding: 1px 6px;
|
||||||
margin-top: auto;
|
margin: 1px 2px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(180, 62, 142, 0.12);
|
||||||
|
color: #202124;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
cursor: pointer;
|
||||||
|
border-left: 3px solid var(--event-color, #B43E8E);
|
||||||
|
transition: filter 0.12s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-badge-code {
|
.gc-month-pill:hover {
|
||||||
background-color: rgba(0, 0, 0, 0.3) !important;
|
filter: brightness(0.95);
|
||||||
color: #FFFFFF !important;
|
|
||||||
font-size: 0.70rem !important;
|
|
||||||
font-weight: 600 !important;
|
|
||||||
padding: 2px 5px !important;
|
|
||||||
border-radius: 4px !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-badge-room {
|
[data-bs-theme="dark"] .gc-month-pill {
|
||||||
background-color: rgba(255, 255, 255, 0.28) !important;
|
background: rgba(180, 62, 142, 0.25);
|
||||||
color: #FFFFFF !important;
|
color: #f1f3f4;
|
||||||
font-size: 0.70rem !important;
|
}
|
||||||
|
|
||||||
|
.gc-month-dot {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gc-month-time {
|
||||||
|
font-size: 0.68rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #7d2662;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .gc-month-time {
|
||||||
|
color: #ffb1d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gc-month-title {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gc-month-room {
|
||||||
|
font-size: 0.64rem;
|
||||||
|
color: #5f6368;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .gc-month-room {
|
||||||
|
color: #9aa0a6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FullCalendar "+X más" Popover estilo Google Calendar */
|
||||||
|
.fc-popover {
|
||||||
|
border-radius: 12px !important;
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.22) !important;
|
||||||
|
background: var(--bs-body-bg, #ffffff) !important;
|
||||||
|
z-index: 1055 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-popover-header {
|
||||||
|
background: var(--bs-tertiary-bg, #f8f9fa) !important;
|
||||||
|
padding: 8px 12px !important;
|
||||||
|
border-top-left-radius: 12px !important;
|
||||||
|
border-top-right-radius: 12px !important;
|
||||||
font-weight: 700 !important;
|
font-weight: 700 !important;
|
||||||
padding: 2px 5px !important;
|
font-size: 0.85rem !important;
|
||||||
border-radius: 4px !important;
|
}
|
||||||
|
|
||||||
|
.fc-more-link {
|
||||||
|
font-size: 0.72rem !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
color: #B43E8E !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
padding: 1px 4px !important;
|
||||||
|
border-radius: 3px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-more-link:hover {
|
||||||
|
background-color: rgba(180, 62, 142, 0.12) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------
|
||||||
|
Google Calendar Modal Card (.gc-modal-card)
|
||||||
|
------------------------------------------------------------- */
|
||||||
|
.gc-modal-card {
|
||||||
|
border-radius: 16px !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gc-modal-banner {
|
||||||
|
height: 80px;
|
||||||
|
background: linear-gradient(135deg, #B43E8E 0%, #832765 60%, #4338CA 100%);
|
||||||
|
position: relative;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gc-modal-banner.banner-virtual {
|
||||||
|
background: linear-gradient(135deg, #4338CA 0%, #2563EB 60%, #0284C7 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gc-modal-banner.banner-pending {
|
||||||
|
background: linear-gradient(135deg, #D97706 0%, #F59E0B 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gc-banner-actions .btn-icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(0, 0, 0, 0.28);
|
||||||
|
border: none;
|
||||||
|
transition: background 0.15s ease, transform 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Month View Optimization */
|
.gc-banner-actions .btn-icon:hover {
|
||||||
.fc-daygrid-event .fc-event-custom-container {
|
background: rgba(0, 0, 0, 0.50);
|
||||||
padding: 2px 4px;
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
.fc-daygrid-event .fc-event-subject-title {
|
|
||||||
font-size: 0.78rem !important;
|
.gc-icon-circle {
|
||||||
-webkit-line-clamp: 1;
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: var(--bs-tertiary-bg, #f1f3f4);
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -394,6 +690,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
var activeBadge = document.getElementById('activeFilterBadge');
|
var activeBadge = document.getElementById('activeFilterBadge');
|
||||||
var countEl = document.getElementById('eventCountNumber');
|
var countEl = document.getElementById('eventCountNumber');
|
||||||
|
|
||||||
|
var days = ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'];
|
||||||
|
var months = ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'];
|
||||||
|
|
||||||
function updateFilterSummaryBadge() {
|
function updateFilterSummaryBadge() {
|
||||||
var parts = [];
|
var parts = [];
|
||||||
if (filterRoom.value && filterRoom.selectedOptions[0]) {
|
if (filterRoom.value && filterRoom.selectedOptions[0]) {
|
||||||
@@ -439,10 +738,21 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
minute: '2-digit',
|
minute: '2-digit',
|
||||||
hour12: false
|
hour12: false
|
||||||
},
|
},
|
||||||
slotMinTime: '07:30:00',
|
slotMinTime: '07:00:00',
|
||||||
slotMaxTime: '22:30:00',
|
slotMaxTime: '23:00:00',
|
||||||
|
slotDuration: '00:30:00',
|
||||||
|
slotLabelInterval: '01:00:00',
|
||||||
|
nowIndicator: true,
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
|
|
||||||
|
/* Manejo de Eventos Simultáneos estilo Google Calendar */
|
||||||
|
dayMaxEvents: 3,
|
||||||
|
dayMaxEventRows: true,
|
||||||
|
eventMaxStack: 3,
|
||||||
|
moreLinkClick: 'popover',
|
||||||
slotEventOverlap: true,
|
slotEventOverlap: true,
|
||||||
|
expandRows: true,
|
||||||
|
stickyHeaderDates: true,
|
||||||
|
|
||||||
/* Dynamic Server-Side Filtered Events */
|
/* Dynamic Server-Side Filtered Events */
|
||||||
events: function(info, successCallback, failureCallback) {
|
events: function(info, successCallback, failureCallback) {
|
||||||
@@ -476,64 +786,182 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Custom Card Render with Visual Hierarchy */
|
/* Diseño de Tarjetas de Evento Estilo Google Calendar */
|
||||||
eventContent: function(arg) {
|
eventContent: function(arg) {
|
||||||
var ext = arg.event.extendedProps || {};
|
var ext = arg.event.extendedProps || {};
|
||||||
var subjectName = ext.subject_name || arg.event.title || '';
|
var subjectName = ext.subject_name || arg.event.title || '';
|
||||||
var subjectCode = ext.subject_code || '';
|
var subjectCode = ext.subject_code || '';
|
||||||
var classroom = ext.classroom || '';
|
var classroom = ext.classroom || '';
|
||||||
|
var isVirtual = ext.is_virtual || false;
|
||||||
var timeText = arg.timeText || '';
|
var timeText = arg.timeText || '';
|
||||||
|
var status = (ext.status || 'CONFIRMED').toUpperCase();
|
||||||
|
var isMonthView = arg.view.type === 'dayGridMonth';
|
||||||
|
|
||||||
var container = document.createElement('div');
|
var eventColor = '#B43E8E';
|
||||||
container.className = 'fc-event-custom-container';
|
if (status === 'PENDING') {
|
||||||
|
eventColor = '#F59E0B';
|
||||||
|
} else if (isVirtual) {
|
||||||
|
eventColor = '#6366F1';
|
||||||
|
}
|
||||||
|
|
||||||
var timeHtml = timeText ? `<div class="fc-event-time-pill"><i class="bi bi-clock me-1"></i>${timeText}</div>` : '';
|
// Vista de Mes: Píldora Google Calendar Compacta
|
||||||
var titleHtml = `<div class="fc-event-subject-title" title="${subjectName}">${subjectName}</div>`;
|
if (isMonthView) {
|
||||||
var badgesHtml = `
|
var monthPill = document.createElement('div');
|
||||||
<div class="fc-event-badges">
|
monthPill.className = 'gc-month-pill ' + (isVirtual ? 'gc-month-virtual' : '');
|
||||||
${subjectCode ? `<span class="fc-badge-code font-monospace">${subjectCode}</span>` : ''}
|
monthPill.style.setProperty('--event-color', eventColor);
|
||||||
${classroom ? `<span class="fc-badge-room"><i class="bi bi-door-open me-1"></i>${classroom}</span>` : ''}
|
monthPill.title = subjectName + (classroom ? ' · ' + classroom : '');
|
||||||
|
|
||||||
|
var startHour = timeText ? timeText.split(' - ')[0] : '';
|
||||||
|
monthPill.innerHTML = `
|
||||||
|
<span class="gc-month-dot" style="background-color: ${eventColor};"></span>
|
||||||
|
${startHour ? `<span class="gc-month-time">${startHour}</span>` : ''}
|
||||||
|
<span class="gc-month-title">${subjectName}</span>
|
||||||
|
<span class="gc-month-room">${isVirtual ? '<i class="bi bi-camera-video"></i>' : classroom}</span>
|
||||||
|
`;
|
||||||
|
return { domNodes: [monthPill] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vista Semanal / Diaria: Tarjeta Píldora con Franja Lateral y Acento
|
||||||
|
var pill = document.createElement('div');
|
||||||
|
pill.className = 'gc-event-pill ' + (status === 'PENDING' ? 'status-pending' : '') + (isVirtual ? ' status-virtual' : '');
|
||||||
|
pill.style.setProperty('--event-color', eventColor);
|
||||||
|
pill.title = subjectName + (classroom ? ' - ' + classroom : '');
|
||||||
|
|
||||||
|
var stripe = `<div class="gc-event-stripe" style="background-color: ${eventColor};"></div>`;
|
||||||
|
var startHourOnly = timeText ? timeText.split(' - ')[0] : '';
|
||||||
|
var roomBadge = classroom ? `
|
||||||
|
<span class="gc-event-room badge ${isVirtual ? 'gc-badge-virtual' : ''}">
|
||||||
|
<i class="bi ${isVirtual ? 'bi-camera-video' : 'bi-door-open'} me-1"></i>${classroom}
|
||||||
|
</span>` : '';
|
||||||
|
|
||||||
|
var header = `
|
||||||
|
<div class="gc-event-header">
|
||||||
|
<span class="gc-event-time">${startHourOnly}</span>
|
||||||
|
${roomBadge}
|
||||||
</div>`;
|
</div>`;
|
||||||
|
var title = `<div class="gc-event-title" title="${subjectName}">${subjectName}</div>`;
|
||||||
|
|
||||||
container.innerHTML = timeHtml + titleHtml + badgesHtml;
|
pill.innerHTML = stripe + `<div class="gc-event-content">${header}${title}</div>`;
|
||||||
return { domNodes: [container] };
|
return { domNodes: [pill] };
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Interactive Instant Modal */
|
/* Intercepción de Clic: Popover / Modal Flotante de Vista Previa Google Calendar */
|
||||||
eventClick: function(info) {
|
eventClick: function(info) {
|
||||||
info.jsEvent.preventDefault();
|
info.jsEvent.preventDefault();
|
||||||
var ext = info.event.extendedProps || {};
|
var ext = info.event.extendedProps || {};
|
||||||
var subjectName = ext.subject_name || info.event.title || '';
|
var subjectName = ext.subject_name || info.event.title || 'Asignatura';
|
||||||
var subjectCode = ext.subject_code || '-';
|
var subjectCode = ext.subject_code || '-';
|
||||||
var classroom = ext.classroom || 'Sin Aula';
|
var classroom = ext.classroom || 'Sin Aula Asignada';
|
||||||
var building = ext.building || 'Edificio Central';
|
var building = ext.building || 'Edificio Central';
|
||||||
var career = ext.career || 'General';
|
var career = ext.career || 'Tecnicatura / Licenciatura UniCABA';
|
||||||
var status = ext.status || 'CONFIRMED';
|
var status = (ext.status || 'CONFIRMED').toUpperCase();
|
||||||
|
var isVirtual = ext.is_virtual || false;
|
||||||
|
var teacher = ext.teacher || 'Docente Responsable';
|
||||||
|
var commission = ext.commission ? `Comisión ${ext.commission}` : 'Comisión Regular';
|
||||||
var attendees = ext.attendees || 0;
|
var attendees = ext.attendees || 0;
|
||||||
|
var purpose = ext.purpose || 'Clase Regular';
|
||||||
|
var notes = ext.notes || 'Sin observaciones registradas';
|
||||||
|
var shift = ext.shift || '';
|
||||||
|
|
||||||
var startStr = info.event.start ? info.event.start.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit', hour12: false}) : '';
|
var eventColor = '#B43E8E';
|
||||||
var endStr = info.event.end ? info.event.end.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit', hour12: false}) : '';
|
var bannerClass = 'gc-modal-banner';
|
||||||
var dateStr = info.event.start ? info.event.start.toLocaleDateString([], {weekday: 'long', day: 'numeric', month: 'long'}) : '';
|
if (status === 'PENDING') {
|
||||||
|
eventColor = '#F59E0B';
|
||||||
document.getElementById('modalSubjectTitle').textContent = subjectName;
|
bannerClass += ' banner-pending';
|
||||||
document.getElementById('modalSubjectName').textContent = subjectName;
|
} else if (isVirtual) {
|
||||||
document.getElementById('modalSubjectCode').textContent = subjectCode;
|
eventColor = '#6366F1';
|
||||||
document.getElementById('modalCareer').textContent = career;
|
bannerClass += ' banner-virtual';
|
||||||
document.getElementById('modalClassroom').textContent = classroom;
|
|
||||||
document.getElementById('modalBuilding').textContent = building;
|
|
||||||
document.getElementById('modalTimeRange').textContent = `${dateStr} (${startStr} a ${endStr} hs)`;
|
|
||||||
document.getElementById('modalStatus').textContent = status;
|
|
||||||
document.getElementById('modalAttendees').textContent = `${attendees} estudiantes esperados`;
|
|
||||||
|
|
||||||
if (info.event.url) {
|
|
||||||
var btn = document.getElementById('modalFullLink');
|
|
||||||
btn.href = info.event.url;
|
|
||||||
btn.classList.remove('d-none');
|
|
||||||
} else {
|
|
||||||
document.getElementById('modalFullLink').classList.add('d-none');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var modal = new bootstrap.Modal(document.getElementById('reservationModal'));
|
// Actualizar Banner y Color Box
|
||||||
|
var bannerEl = document.getElementById('gcModalBanner');
|
||||||
|
if (bannerEl) bannerEl.className = bannerClass;
|
||||||
|
|
||||||
|
var colorBox = document.getElementById('gcPreviewColorBox');
|
||||||
|
if (colorBox) colorBox.style.backgroundColor = eventColor;
|
||||||
|
|
||||||
|
// Textos Principales
|
||||||
|
document.getElementById('previewSubjectName').textContent = subjectName;
|
||||||
|
document.getElementById('previewSubjectCode').textContent = subjectCode;
|
||||||
|
|
||||||
|
// Badges
|
||||||
|
var statusBadge = document.getElementById('previewStatusBadge');
|
||||||
|
if (statusBadge) {
|
||||||
|
if (status === 'CONFIRMED') {
|
||||||
|
statusBadge.className = 'badge bg-success-subtle text-success border border-success-subtle';
|
||||||
|
statusBadge.textContent = 'Confirmada';
|
||||||
|
} else if (status === 'PENDING') {
|
||||||
|
statusBadge.className = 'badge bg-warning-subtle text-warning border border-warning-subtle';
|
||||||
|
statusBadge.textContent = 'Pendiente';
|
||||||
|
} else {
|
||||||
|
statusBadge.className = 'badge bg-secondary-subtle text-secondary border';
|
||||||
|
statusBadge.textContent = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var shiftBadge = document.getElementById('previewShiftBadge');
|
||||||
|
if (shiftBadge) {
|
||||||
|
if (shift) {
|
||||||
|
shiftBadge.textContent = 'Turno ' + shift;
|
||||||
|
shiftBadge.classList.remove('d-none');
|
||||||
|
} else {
|
||||||
|
shiftBadge.classList.add('d-none');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horario Formateado
|
||||||
|
if (info.event.start) {
|
||||||
|
var start = info.event.start;
|
||||||
|
var end = info.event.end;
|
||||||
|
var dayName = days[start.getDay()];
|
||||||
|
var dayNum = start.getDate();
|
||||||
|
var monthName = months[start.getMonth()];
|
||||||
|
var startTime = start.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit', hour12: false});
|
||||||
|
var endTime = end ? end.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit', hour12: false}) : '';
|
||||||
|
document.getElementById('previewTimeRange').textContent = `${dayName}, ${dayNum} de ${monthName} · ${startTime}${endTime ? ' a ' + endTime + ' hs' : ''}`;
|
||||||
|
document.getElementById('previewDateFull').textContent = `Horario académico registrado`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Espacio Asignado
|
||||||
|
var locIcon = document.getElementById('previewLocationIcon');
|
||||||
|
var virtualWrap = document.getElementById('previewVirtualWrapper');
|
||||||
|
if (isVirtual) {
|
||||||
|
if (locIcon) locIcon.innerHTML = '<i class="bi bi-camera-video fs-5 text-primary"></i>';
|
||||||
|
document.getElementById('previewClassroom').textContent = 'Campus Virtual';
|
||||||
|
document.getElementById('previewBuilding').textContent = 'Entorno Digital UniCABA';
|
||||||
|
if (virtualWrap) virtualWrap.classList.remove('d-none');
|
||||||
|
} else {
|
||||||
|
if (locIcon) locIcon.innerHTML = '<i class="bi bi-geo-alt fs-5 text-danger"></i>';
|
||||||
|
document.getElementById('previewClassroom').textContent = classroom;
|
||||||
|
document.getElementById('previewBuilding').textContent = `${ext.floor !== null && ext.floor !== undefined ? (ext.floor === 0 ? 'Planta Baja' : 'Piso ' + ext.floor) + ' · ' : ''}${building}`;
|
||||||
|
if (virtualWrap) virtualWrap.classList.add('d-none');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carrera, Comisión, Docente y Asistentes
|
||||||
|
document.getElementById('previewCareer').textContent = career;
|
||||||
|
document.getElementById('previewCommission').textContent = commission;
|
||||||
|
document.getElementById('previewTeacher').textContent = teacher;
|
||||||
|
document.getElementById('previewAttendees').textContent = `${attendees} estudiantes esperados`;
|
||||||
|
|
||||||
|
// Hito / Notas
|
||||||
|
document.getElementById('previewPurpose').textContent = purpose;
|
||||||
|
document.getElementById('previewNotes').textContent = notes;
|
||||||
|
|
||||||
|
// Enlaces y Acciones
|
||||||
|
var detailUrl = info.event.url || ('/schedule/reservation/' + info.event.id + '/view');
|
||||||
|
var fullLink = document.getElementById('previewFullLink');
|
||||||
|
if (fullLink) fullLink.href = detailUrl;
|
||||||
|
|
||||||
|
var editBtn = document.getElementById('btnPreviewEdit');
|
||||||
|
if (editBtn) editBtn.href = detailUrl;
|
||||||
|
|
||||||
|
var cancelForm = document.getElementById('formPreviewCancel');
|
||||||
|
if (cancelForm) {
|
||||||
|
cancelForm.action = ext.cancel_url || ('/schedule/cancel/' + info.event.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mostrar Modal Google Calendar
|
||||||
|
var modal = new bootstrap.Modal(document.getElementById('eventPreviewModal'));
|
||||||
modal.show();
|
modal.show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user