feat: fecha y horario en aula, fix division by zero, soporte enlace virtual y mejora contrastes comisiones
This commit is contained in:
@@ -21,6 +21,10 @@ class ReservationForm(FlaskForm):
|
||||
Optional(),
|
||||
Length(max=1000)
|
||||
])
|
||||
virtual_link = StringField(_l('Virtual Classroom Link'), validators=[
|
||||
Optional(),
|
||||
Length(max=500)
|
||||
])
|
||||
submit = SubmitField(_l('Make Reservation'))
|
||||
|
||||
def validate_end_time(self, field):
|
||||
|
||||
@@ -47,6 +47,7 @@ class Reservation(db.Model):
|
||||
status = db.Column(StatusType, default='PENDING', nullable=False) # Maps to VARCHAR(20) with StatusString
|
||||
expected_attendees = db.Column(db.Integer, nullable=False)
|
||||
shift = db.Column(db.String(30), nullable=True) # Mañana, Tarde, Vespertino, Noche
|
||||
virtual_link = db.Column(db.String(500), nullable=True) # Zoom, Meet, Teams, etc.
|
||||
notes = db.Column(db.Text)
|
||||
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
@@ -81,6 +82,24 @@ class Reservation(db.Model):
|
||||
if self.classroom:
|
||||
return self.classroom.code
|
||||
return ''
|
||||
|
||||
@property
|
||||
def effective_virtual_link(self):
|
||||
"""Devuelve el enlace explícito de la reserva o hereda el de la comisión"""
|
||||
if self.virtual_link and self.virtual_link.strip():
|
||||
return self.virtual_link.strip()
|
||||
if self.commission and self.commission.virtual_link:
|
||||
return self.commission.virtual_link.strip()
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_virtual(self):
|
||||
"""Verifica si la reserva es virtual por aula o por contar con enlace virtual"""
|
||||
if self.classroom and self.classroom.is_virtual:
|
||||
return True
|
||||
if self.effective_virtual_link:
|
||||
return True
|
||||
return False
|
||||
|
||||
# Relationships
|
||||
classroom = db.relationship('Classroom', backref=db.backref('reservation_list', lazy=True, cascade='all, delete-orphan'))
|
||||
@@ -100,6 +119,8 @@ class Reservation(db.Model):
|
||||
'status': self.status, # Already a string in DB
|
||||
'expected_attendees': self.expected_attendees,
|
||||
'notes': self.notes,
|
||||
'virtual_link': self.effective_virtual_link,
|
||||
'is_virtual': self.is_virtual,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
|
||||
'classroom': self.classroom.to_dict() if self.classroom else None,
|
||||
|
||||
@@ -446,6 +446,7 @@ def add_reservation():
|
||||
end_time=form.end_time.data,
|
||||
purpose=form.purpose.data,
|
||||
notes=form.notes.data,
|
||||
virtual_link=form.virtual_link.data.strip() if form.virtual_link.data else None,
|
||||
status=ReservationStatus.PENDING.value,
|
||||
expected_attendees=form.expected_attendees.data
|
||||
)
|
||||
@@ -462,6 +463,50 @@ def add_reservation():
|
||||
|
||||
return render_template('schedule/add.html', form=form, title='New Reservation', classroom_list=classrooms, commission_list=commissions, buildings=buildings)
|
||||
|
||||
@schedule_bp.route('/reservation/add', methods=['POST'])
|
||||
@login_required
|
||||
def quick_reservation_add():
|
||||
"""Endpoint AJAX para crear reserva rápida desde el modal de comisiones"""
|
||||
try:
|
||||
commission_id = request.form.get('commission_id', type=int)
|
||||
classroom_id = request.form.get('classroom_id', type=int)
|
||||
purpose = request.form.get('purpose', '').strip() or 'Clase de cursada'
|
||||
start_str = request.form.get('start_time', '').strip()
|
||||
end_str = request.form.get('end_time', '').strip()
|
||||
virtual_link = request.form.get('virtual_link', '').strip() or None
|
||||
|
||||
if not commission_id or not classroom_id or not start_str or not end_str:
|
||||
return jsonify({'error': 'Faltan campos obligatorios'}), 400
|
||||
|
||||
start_time = datetime.strptime(start_str, '%Y-%m-%d %H:%M')
|
||||
end_time = datetime.strptime(end_str, '%Y-%m-%d %H:%M')
|
||||
|
||||
from app.models.subject import Commission
|
||||
from app.models.classroom import Classroom
|
||||
comm = Commission.query.get_or_404(commission_id)
|
||||
|
||||
if not virtual_link and comm.virtual_link:
|
||||
virtual_link = comm.virtual_link
|
||||
|
||||
new_res = Reservation(
|
||||
classroom_id=classroom_id,
|
||||
commission_id=commission_id,
|
||||
user_id=comm.teacher_id or current_user.id,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
purpose=purpose,
|
||||
expected_attendees=comm.max_students or 30,
|
||||
shift=comm.shift or 'Tarde',
|
||||
virtual_link=virtual_link,
|
||||
status=ReservationStatus.CONFIRMED.value if current_user.is_admin() else ReservationStatus.PENDING.value
|
||||
)
|
||||
db.session.add(new_res)
|
||||
db.session.commit()
|
||||
return jsonify({'success': True, 'id': new_res.id})
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@schedule_bp.route('/list')
|
||||
@login_required
|
||||
def list_reservations():
|
||||
@@ -777,6 +822,7 @@ def calendar_data():
|
||||
'attendees': reservation.expected_attendees or 0,
|
||||
'notes': reservation.notes or '',
|
||||
'teacher': reservation.user.name if reservation.user else '',
|
||||
'virtual_link': reservation.effective_virtual_link,
|
||||
'cancel_url': url_for('schedule.cancel_reservation', id=reservation.id)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="table-light small text-uppercase">
|
||||
<tr>
|
||||
<th class="ps-3">{% trans %}Horario{% endtrans %}</th>
|
||||
<th class="ps-3">{% trans %}Fecha y Horario{% endtrans %}</th>
|
||||
<th>{% trans %}Materia / Motivo{% endtrans %}</th>
|
||||
<th>{% trans %}Comisión{% endtrans %}</th>
|
||||
<th>{% trans %}Asistentes{% endtrans %}</th>
|
||||
@@ -119,7 +119,10 @@
|
||||
{% for res in upcoming_reservations %}
|
||||
<tr>
|
||||
<td class="ps-3">
|
||||
<div class="fw-bold font-monospace text-primary fs-6">
|
||||
<div class="fw-bold font-monospace text-primary fs-6 mb-1">
|
||||
<i class="bi bi-calendar-event text-primary me-1"></i>{{ res.start_time.strftime('%d/%m/%Y') }}
|
||||
</div>
|
||||
<div class="small font-monospace text-body-secondary">
|
||||
<i class="bi bi-clock text-warning me-1"></i>{{ res.start_time.strftime('%H:%M') }} - {{ res.end_time.strftime('%H:%M') }} hs
|
||||
</div>
|
||||
</td>
|
||||
@@ -136,6 +139,11 @@
|
||||
<span class="small">{{ res.expected_attendees }} / {{ classroom.capacity }}</span>
|
||||
</td>
|
||||
<td class="text-end pe-3">
|
||||
{% if res.effective_virtual_link %}
|
||||
<a href="{{ res.effective_virtual_link }}" target="_blank" class="btn btn-outline-success btn-sm py-0 px-2 me-1" title="{% trans %}Unirse a Clase Virtual (Zoom / Meet){% endtrans %}">
|
||||
<i class="bi bi-camera-video"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('schedule.view_reservation', id=res.id) }}" class="btn btn-outline-secondary btn-sm py-0 px-2" title="{% trans %}Ver Ficha{% endtrans %}">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 5. Notas -->
|
||||
<div class="mb-4">
|
||||
<div class="mb-3">
|
||||
{{ form.notes.label(class="form-label fw-semibold") }}
|
||||
{{ form.notes(class="form-control", rows="2", placeholder=_("Cualquier nota adicional o requerimientos especiales...")) }}
|
||||
{% if form.notes.errors %}
|
||||
@@ -161,8 +161,24 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- 6. Enlace de Aula Virtual (Zoom, Meet, Teams, Moodle) -->
|
||||
<div class="mb-4" id="virtual-link-container">
|
||||
<label for="virtual_link" class="form-label fw-semibold">
|
||||
<i class="bi bi-camera-video text-primary me-1"></i>{% trans %}Enlace de Aula Virtual (Zoom, Google Meet, Teams, Moodle){% endtrans %}
|
||||
</label>
|
||||
{{ form.virtual_link(class="form-control", id="virtual_link", placeholder="https://zoom.us/j/... o https://meet.google.com/...") }}
|
||||
{% if form.virtual_link.errors %}
|
||||
<div class="text-danger small mt-1">
|
||||
{{ form.virtual_link.errors[0] }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-text small text-muted">
|
||||
<i class="bi bi-info-circle me-1"></i>{% trans %}Obligatorio para Campus Virtual o clases remotas/híbridas. Se autocompleta con el enlace de la comisión seleccionada.{% endtrans %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 6. TARJETA DE RESUMEN DESGLOSADA: Edificio, Piso, Aula -->
|
||||
<!-- 7. TARJETA DE RESUMEN DESGLOSADA: Edificio, Piso, Aula, Modalidad -->
|
||||
<div class="card bg-body-tertiary border-0 shadow-xs mb-4" id="reservation-summary">
|
||||
<div class="card-body p-3">
|
||||
<h6 class="card-subtitle mb-2 text-muted small fw-bold">
|
||||
@@ -191,6 +207,21 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fila de Enlace Virtual -->
|
||||
<div class="row g-2 mb-3" id="summary-virtual-row">
|
||||
<div class="col-12">
|
||||
<div class="p-2 rounded bg-body border d-flex align-items-center justify-content-between">
|
||||
<div class="text-truncate me-2">
|
||||
<small class="text-muted d-block">🎥 {% trans %}Enlace Virtual (Videoconferencia){% endtrans %}</small>
|
||||
<span id="summary-virtual-link" class="small font-monospace text-primary text-break">{% trans %}Sin enlace configurado{% endtrans %}</span>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success border" id="summary-virtual-badge" style="display:none;">
|
||||
<i class="bi bi-camera-video me-1"></i>Virtual
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Duración y Comisión -->
|
||||
<div class="row g-2 small">
|
||||
<div class="col-md-4">
|
||||
@@ -243,6 +274,19 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="application/json" id="commission-data">
|
||||
{
|
||||
{% for c in commission_list %}
|
||||
"{{ c.id }}": {
|
||||
"id": {{ c.id }},
|
||||
"code": {{ c.code|tojson }},
|
||||
"virtual_link": {{ (c.virtual_link or '')|tojson }},
|
||||
"max_students": {{ c.max_students }}
|
||||
}{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="application/json" id="i18n-strings">
|
||||
{
|
||||
"not_selected": "{% trans %}No seleccionada{% endtrans %}",
|
||||
@@ -253,7 +297,8 @@
|
||||
"minute": "{% trans %}minuto{% endtrans %}",
|
||||
"minutes": "{% trans %}minutos{% endtrans %}",
|
||||
"capacity": "{% trans %}Capacidad{% endtrans %}",
|
||||
"capacity_exceeded": "{% trans %}Los asistentes esperados superan la capacidad del aula{% endtrans %}"
|
||||
"capacity_exceeded": "{% trans %}Los asistentes esperados superan la capacidad del aula{% endtrans %}",
|
||||
"no_virtual_link": "{% trans %}Sin enlace configurado (Clase presencial){% endtrans %}"
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -267,6 +312,14 @@ $(document).ready(function() {
|
||||
console.error('Error parsing classroomData:', err);
|
||||
}
|
||||
|
||||
let commissionData = {};
|
||||
try {
|
||||
const rawComm = document.getElementById('commission-data')?.textContent;
|
||||
if (rawComm) commissionData = JSON.parse(rawComm);
|
||||
} catch (err) {
|
||||
console.error('Error parsing commissionData:', err);
|
||||
}
|
||||
|
||||
let i18n = {};
|
||||
try {
|
||||
const rawI18n = document.getElementById('i18n-strings')?.textContent;
|
||||
@@ -457,13 +510,28 @@ $(document).ready(function() {
|
||||
$('#summary-classroom').text(i18n.not_selected || 'No seleccionada');
|
||||
}
|
||||
|
||||
// Update commission summary
|
||||
// Update commission summary and autofill virtual link if available
|
||||
if (commissionId) {
|
||||
const commissionText = $('#commission-select option:selected').text();
|
||||
$('#summary-commission').text(commissionText);
|
||||
} else {
|
||||
$('#summary-commission').text(i18n.not_selected || 'No seleccionada');
|
||||
}
|
||||
|
||||
// Update virtual link in summary
|
||||
const vLink = ($('#virtual_link').val() || '').trim();
|
||||
const isVirtual = (classroomId && classroomData[classroomId] && classroomData[classroomId].is_virtual);
|
||||
|
||||
if (vLink) {
|
||||
$('#summary-virtual-link').text(vLink);
|
||||
$('#summary-virtual-badge').show();
|
||||
} else if (isVirtual) {
|
||||
$('#summary-virtual-link').html('<span class="text-warning fw-semibold"><i class="bi bi-exclamation-triangle me-1"></i>Campus Virtual (Recomendado agregar enlace Zoom/Meet)</span>');
|
||||
$('#summary-virtual-badge').hide();
|
||||
} else {
|
||||
$('#summary-virtual-link').text(i18n.no_virtual_link || 'Sin enlace configurado (Clase presencial)');
|
||||
$('#summary-virtual-badge').hide();
|
||||
}
|
||||
|
||||
// Calculate duration
|
||||
if (startTime && endTime) {
|
||||
@@ -526,7 +594,19 @@ $(document).ready(function() {
|
||||
}
|
||||
}
|
||||
|
||||
$('#commission-select, #start-time, #end-time').on('change', updateSummary);
|
||||
$('#commission-select').on('change', function() {
|
||||
const cId = $(this).val();
|
||||
if (cId && commissionData[cId]) {
|
||||
const comm = commissionData[cId];
|
||||
if (comm.virtual_link && !$('#virtual_link').val().trim()) {
|
||||
$('#virtual_link').val(comm.virtual_link);
|
||||
}
|
||||
}
|
||||
updateSummary();
|
||||
});
|
||||
|
||||
$('#virtual_link').on('input change', updateSummary);
|
||||
$('#start-time, #end-time').on('change', updateSummary);
|
||||
$('#expected_attendees').on('input', function() {
|
||||
checkCapacity();
|
||||
updateSummary();
|
||||
|
||||
@@ -237,6 +237,11 @@
|
||||
<i class="bi bi-camera-video me-1"></i>{% trans %}Campus Virtual UniCABA (Teams / Moodle){% endtrans %}
|
||||
</span>
|
||||
</div>
|
||||
<div id="previewVirtualLinkWrapper" class="d-none mt-2">
|
||||
<a id="previewVirtualButton" href="#" target="_blank" class="btn btn-sm btn-success d-inline-flex align-items-center">
|
||||
<i class="bi bi-camera-video me-1"></i>{% trans %}Unirse a Videollamada (Zoom / Meet){% endtrans %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -963,6 +968,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (virtualWrap) virtualWrap.classList.add('d-none');
|
||||
}
|
||||
|
||||
// Enlace de Aula Virtual / Videollamada
|
||||
var vLink = ext.virtual_link;
|
||||
var vBtnWrap = document.getElementById('previewVirtualLinkWrapper');
|
||||
var vBtn = document.getElementById('previewVirtualButton');
|
||||
if (vLink && vBtnWrap && vBtn) {
|
||||
vBtn.href = vLink;
|
||||
vBtnWrap.classList.remove('d-none');
|
||||
} else if (vBtnWrap) {
|
||||
vBtnWrap.classList.add('d-none');
|
||||
}
|
||||
|
||||
// Carrera, Comisión, Docente y Asistentes
|
||||
document.getElementById('previewCareer').textContent = career;
|
||||
document.getElementById('previewCommission').textContent = commission;
|
||||
|
||||
@@ -2,6 +2,515 @@
|
||||
|
||||
{% block title %}{% trans %}Comisiones y Cursadas{% endtrans %} - Edu-Space{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
/* ==========================================================================
|
||||
GESTIÓN DE COMISIONES - SISTEMA DE COLOR Y CONTRASTE (CLARO & OSCURO)
|
||||
========================================================================== */
|
||||
|
||||
/* --- 1. MODO CLARO (Default / Light) --- */
|
||||
.comm-stat-card {
|
||||
border: 1px solid rgba(0, 0, 0, 0.06) !important;
|
||||
background-color: #FFFFFF !important;
|
||||
border-radius: 12px;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
.comm-stat-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(180, 62, 142, 0.08) !important;
|
||||
}
|
||||
.stat-icon-magenta {
|
||||
background: rgba(180, 62, 142, 0.12) !important;
|
||||
color: #B43E8E !important;
|
||||
}
|
||||
.stat-icon-purple {
|
||||
background: rgba(104, 44, 173, 0.12) !important;
|
||||
color: #682CAD !important;
|
||||
}
|
||||
.stat-icon-teal {
|
||||
background: rgba(16, 185, 129, 0.12) !important;
|
||||
color: #059669 !important;
|
||||
}
|
||||
.stat-icon-amber {
|
||||
background: rgba(245, 158, 11, 0.14) !important;
|
||||
color: #D97706 !important;
|
||||
}
|
||||
|
||||
.comm-stat-val {
|
||||
color: #0F172A;
|
||||
font-weight: 800;
|
||||
}
|
||||
.comm-stat-lbl {
|
||||
color: #64748B;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Barra de Búsqueda */
|
||||
.comm-search-card {
|
||||
background-color: #FFFFFF !important;
|
||||
border: 1px solid rgba(0, 0, 0, 0.07) !important;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.comm-search-input {
|
||||
background-color: #F8FAFC !important;
|
||||
color: #0F172A !important;
|
||||
border: 1px solid #E2E8F0 !important;
|
||||
}
|
||||
.comm-search-input:focus {
|
||||
border-color: #B43E8E !important;
|
||||
box-shadow: 0 0 0 0.2rem rgba(180, 62, 142, 0.2) !important;
|
||||
}
|
||||
.comm-search-icon {
|
||||
background-color: #F8FAFC !important;
|
||||
color: #B43E8E !important;
|
||||
border: 1px solid #E2E8F0 !important;
|
||||
border-right: none !important;
|
||||
}
|
||||
.comm-info-hint {
|
||||
color: #475569 !important;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
/* Tarjeta de Asignatura */
|
||||
.subject-card .card {
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.07) !important;
|
||||
background-color: #FFFFFF !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
.comm-subject-header {
|
||||
background: linear-gradient(90deg, rgba(180, 62, 142, 0.06) 0%, rgba(104, 44, 173, 0.03) 50%, #FFFFFF 100%) !important;
|
||||
border-bottom: 1px solid rgba(180, 62, 142, 0.12) !important;
|
||||
}
|
||||
.comm-subject-badge {
|
||||
background: linear-gradient(135deg, #B43E8E 0%, #9E367D 100%) !important;
|
||||
color: #FFFFFF !important;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 2px 6px rgba(180, 62, 142, 0.25);
|
||||
}
|
||||
.comm-subject-title {
|
||||
color: #0F172A !important;
|
||||
font-weight: 700;
|
||||
}
|
||||
.comm-subject-meta {
|
||||
color: #64748B !important;
|
||||
}
|
||||
.comm-count-badge {
|
||||
background-color: rgba(180, 62, 142, 0.08) !important;
|
||||
color: #9E367D !important;
|
||||
border: 1px solid rgba(180, 62, 142, 0.2) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
.comm-btn-subject-add {
|
||||
color: #B43E8E !important;
|
||||
border-color: rgba(180, 62, 142, 0.35) !important;
|
||||
background: transparent !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
.comm-btn-subject-add:hover {
|
||||
background-color: #B43E8E !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
/* Encabezados de Tabla */
|
||||
.commissions-table-head th {
|
||||
background-color: #F8FAFC !important;
|
||||
color: #334155 !important;
|
||||
font-size: 0.76rem !important;
|
||||
font-weight: 700 !important;
|
||||
text-transform: uppercase !important;
|
||||
letter-spacing: 0.04em !important;
|
||||
border-bottom: 2px solid #E2E8F0 !important;
|
||||
border-top: none !important;
|
||||
padding-top: 11px !important;
|
||||
padding-bottom: 11px !important;
|
||||
}
|
||||
|
||||
/* Filas de la Tabla */
|
||||
.commission-row {
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
.commission-row:hover {
|
||||
background-color: rgba(180, 62, 142, 0.035) !important;
|
||||
}
|
||||
.comm-code {
|
||||
color: #B43E8E !important;
|
||||
font-weight: 800;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.comm-shift-badge {
|
||||
background-color: rgba(104, 44, 173, 0.10) !important;
|
||||
color: #682CAD !important;
|
||||
border: 1px solid rgba(104, 44, 173, 0.22) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
.comm-schedule-badge {
|
||||
background-color: #F1F5F9 !important;
|
||||
color: #334155 !important;
|
||||
border: 1px solid #CBD5E1 !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
.comm-semester-badge {
|
||||
background-color: rgba(180, 62, 142, 0.08) !important;
|
||||
color: #9E367D !important;
|
||||
border: 1px solid rgba(180, 62, 142, 0.22) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
.comm-teacher-name {
|
||||
color: #0F172A !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
.comm-teacher-icon {
|
||||
color: #682CAD !important;
|
||||
}
|
||||
.comm-teacher-email {
|
||||
color: #64748B !important;
|
||||
}
|
||||
.comm-unassigned-badge {
|
||||
background-color: rgba(245, 158, 11, 0.12) !important;
|
||||
color: #B45309 !important;
|
||||
border: 1px solid rgba(245, 158, 11, 0.3) !important;
|
||||
}
|
||||
|
||||
/* Aula Virtual */
|
||||
.comm-btn-virtual {
|
||||
background-color: rgba(16, 185, 129, 0.12) !important;
|
||||
color: #059669 !important;
|
||||
border: 1px solid rgba(16, 185, 129, 0.35) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
.comm-btn-virtual:hover {
|
||||
background-color: #10B981 !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
.comm-btn-action-sm {
|
||||
color: #64748B !important;
|
||||
border-color: #CBD5E1 !important;
|
||||
}
|
||||
.comm-btn-action-sm:hover {
|
||||
background-color: #F1F5F9 !important;
|
||||
color: #0F172A !important;
|
||||
}
|
||||
.comm-no-link {
|
||||
color: #94A3B8 !important;
|
||||
}
|
||||
|
||||
/* Hitos Académicos */
|
||||
.comm-milestone-chip {
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.12);
|
||||
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
.comm-no-milestone {
|
||||
color: #94A3B8 !important;
|
||||
}
|
||||
.comm-btn-add-milestone {
|
||||
background-color: rgba(245, 158, 11, 0.10) !important;
|
||||
color: #B45309 !important;
|
||||
border: 1px solid rgba(245, 158, 11, 0.32) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
.comm-btn-add-milestone:hover {
|
||||
background-color: #F59E0B !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
/* Botones de Acción */
|
||||
.comm-actions-group .btn {
|
||||
border-color: #E2E8F0;
|
||||
}
|
||||
.comm-btn-reserve {
|
||||
color: #B43E8E !important;
|
||||
}
|
||||
.comm-btn-reserve:hover {
|
||||
background-color: #B43E8E !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
.comm-btn-edit {
|
||||
color: #682CAD !important;
|
||||
}
|
||||
.comm-btn-edit:hover {
|
||||
background-color: #682CAD !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
.comm-btn-delete {
|
||||
color: #DC2626 !important;
|
||||
}
|
||||
.comm-btn-delete:hover {
|
||||
background-color: #DC2626 !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
2. MODO OSCURO ([data-bs-theme="dark"])
|
||||
========================================================================== */
|
||||
[data-bs-theme="dark"] .comm-stat-card {
|
||||
background-color: #1A2035 !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08) !important;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.35) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .stat-icon-magenta {
|
||||
background: rgba(180, 62, 142, 0.24) !important;
|
||||
color: #FF8AB6 !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .stat-icon-purple {
|
||||
background: rgba(174, 127, 255, 0.20) !important;
|
||||
color: #AE7FFF !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .stat-icon-teal {
|
||||
background: rgba(52, 211, 153, 0.20) !important;
|
||||
color: #34D399 !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .stat-icon-amber {
|
||||
background: rgba(251, 191, 36, 0.20) !important;
|
||||
color: #FBBF24 !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-stat-val {
|
||||
color: #F8FAFC !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-stat-lbl {
|
||||
color: #94A3B8 !important;
|
||||
}
|
||||
|
||||
/* Barra de Búsqueda Oscuro */
|
||||
[data-bs-theme="dark"] .comm-search-card {
|
||||
background-color: #161B30 !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-search-input {
|
||||
background-color: #101424 !important;
|
||||
color: #F8FAFC !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-search-input::placeholder {
|
||||
color: #64748B !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-search-input:focus {
|
||||
border-color: #FF8AB6 !important;
|
||||
box-shadow: 0 0 0 0.2rem rgba(180, 62, 142, 0.35) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-search-icon {
|
||||
background-color: #101424 !important;
|
||||
color: #FF8AB6 !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12) !important;
|
||||
border-right: none !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-info-hint {
|
||||
color: #94A3B8 !important;
|
||||
}
|
||||
|
||||
/* Tarjeta de Asignatura Oscuro */
|
||||
[data-bs-theme="dark"] .subject-card .card {
|
||||
background-color: #161B30 !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08) !important;
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-subject-header {
|
||||
background: linear-gradient(90deg, rgba(180, 62, 142, 0.18) 0%, rgba(104, 44, 173, 0.10) 50%, #161B30 100%) !important;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-subject-badge {
|
||||
background: rgba(180, 62, 142, 0.30) !important;
|
||||
color: #FF8AB6 !important;
|
||||
border: 1px solid rgba(180, 62, 142, 0.55) !important;
|
||||
box-shadow: 0 0 10px rgba(180, 62, 142, 0.2);
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-subject-title {
|
||||
color: #F8FAFC !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-subject-meta {
|
||||
color: #94A3B8 !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-count-badge {
|
||||
background-color: rgba(255, 255, 255, 0.07) !important;
|
||||
color: #CBD5E1 !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-btn-subject-add {
|
||||
color: #FF8AB6 !important;
|
||||
border-color: rgba(180, 62, 142, 0.5) !important;
|
||||
background: rgba(180, 62, 142, 0.12) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-btn-subject-add:hover {
|
||||
background-color: #B43E8E !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
/* Encabezados de Tabla Oscuro (NO MÁS FRANJA BLANCA) */
|
||||
[data-bs-theme="dark"] .commissions-table-head th {
|
||||
background-color: #121526 !important;
|
||||
color: #CBD5E1 !important;
|
||||
border-bottom: 2px solid rgba(255, 255, 255, 0.10) !important;
|
||||
border-top: none !important;
|
||||
}
|
||||
|
||||
/* Filas de la Tabla Oscuro */
|
||||
[data-bs-theme="dark"] .table {
|
||||
--bs-table-bg: transparent;
|
||||
--bs-table-color: #E2E8F0;
|
||||
--bs-table-hover-bg: rgba(180, 62, 142, 0.08);
|
||||
}
|
||||
[data-bs-theme="dark"] .commission-row td {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.06) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-code {
|
||||
color: #FF8AB6 !important; /* Alto contraste neon pink */
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-shift-badge {
|
||||
background-color: rgba(174, 127, 255, 0.20) !important;
|
||||
color: #D8B4FE !important;
|
||||
border: 1px solid rgba(174, 127, 255, 0.40) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-schedule-badge {
|
||||
background-color: rgba(255, 255, 255, 0.08) !important;
|
||||
color: #E2E8F0 !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.15) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-semester-badge {
|
||||
background-color: rgba(180, 62, 142, 0.24) !important;
|
||||
color: #FF96B4 !important;
|
||||
border: 1px solid rgba(180, 62, 142, 0.45) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-teacher-name {
|
||||
color: #F1F5F9 !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-teacher-icon {
|
||||
color: #AE7FFF !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-teacher-email {
|
||||
color: #94A3B8 !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-unassigned-badge {
|
||||
background-color: rgba(245, 158, 11, 0.18) !important;
|
||||
color: #FCD34D !important;
|
||||
border: 1px solid rgba(245, 158, 11, 0.38) !important;
|
||||
}
|
||||
|
||||
/* Aula Virtual Oscuro */
|
||||
[data-bs-theme="dark"] .comm-btn-virtual {
|
||||
background-color: rgba(16, 185, 129, 0.20) !important;
|
||||
color: #34D399 !important;
|
||||
border: 1px solid rgba(16, 185, 129, 0.45) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-btn-virtual:hover {
|
||||
background-color: #059669 !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-btn-action-sm {
|
||||
background-color: rgba(255, 255, 255, 0.06) !important;
|
||||
color: #CBD5E1 !important;
|
||||
border-color: rgba(255, 255, 255, 0.14) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-btn-action-sm:hover {
|
||||
background-color: rgba(255, 255, 255, 0.12) !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-no-link {
|
||||
color: #64748B !important;
|
||||
}
|
||||
|
||||
/* Hitos Académicos Oscuro */
|
||||
[data-bs-theme="dark"] .comm-milestone-chip {
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.4);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-no-milestone {
|
||||
color: #64748B !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-btn-add-milestone {
|
||||
background-color: rgba(245, 158, 11, 0.18) !important;
|
||||
color: #FCD34D !important;
|
||||
border: 1px solid rgba(245, 158, 11, 0.45) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-btn-add-milestone:hover {
|
||||
background-color: #D97706 !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
/* Botones de Acción Oscuro */
|
||||
[data-bs-theme="dark"] .comm-actions-group .btn {
|
||||
border-color: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-btn-reserve {
|
||||
background-color: rgba(180, 62, 142, 0.16) !important;
|
||||
color: #FF8AB6 !important;
|
||||
border-color: rgba(180, 62, 142, 0.4) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-btn-reserve:hover {
|
||||
background-color: #B43E8E !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-btn-edit {
|
||||
background-color: rgba(104, 44, 173, 0.16) !important;
|
||||
color: #C4B5FD !important;
|
||||
border-color: rgba(104, 44, 173, 0.38) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-btn-edit:hover {
|
||||
background-color: #682CAD !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-btn-delete {
|
||||
background-color: rgba(239, 68, 68, 0.16) !important;
|
||||
color: #FCA5A5 !important;
|
||||
border-color: rgba(239, 68, 68, 0.38) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-btn-delete:hover {
|
||||
background-color: #DC2626 !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
/* Modales en Modo Oscuro */
|
||||
[data-bs-theme="dark"] .modal-content {
|
||||
background-color: #161B30 !important;
|
||||
border: 1px solid rgba(180, 62, 142, 0.35) !important;
|
||||
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.6) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .modal-header {
|
||||
background: linear-gradient(135deg, rgba(180, 62, 142, 0.18) 0%, rgba(22, 27, 48, 0.95) 100%) !important;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .modal-footer {
|
||||
background-color: #121526 !important;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.08) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .modal-title {
|
||||
color: #F8FAFC !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .form-label {
|
||||
color: #E2E8F0 !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .form-control,
|
||||
[data-bs-theme="dark"] .form-select {
|
||||
background-color: #101424 !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.14) !important;
|
||||
color: #F8FAFC !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .form-control:focus,
|
||||
[data-bs-theme="dark"] .form-select:focus {
|
||||
border-color: #FF8AB6 !important;
|
||||
box-shadow: 0 0 0 0.2rem rgba(180, 62, 142, 0.35) !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .input-group-text {
|
||||
background-color: #101424 !important;
|
||||
border-color: rgba(255, 255, 255, 0.14) !important;
|
||||
color: #FF8AB6 !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .comm-modal-alert {
|
||||
background-color: rgba(104, 44, 173, 0.22) !important;
|
||||
border: 1px solid rgba(174, 127, 255, 0.35) !important;
|
||||
color: #E2E8F0 !important;
|
||||
}
|
||||
[data-bs-theme="dark"] .alert-light {
|
||||
background-color: rgba(255, 255, 255, 0.05) !important;
|
||||
border-color: rgba(255, 255, 255, 0.1) !important;
|
||||
color: #CBD5E1 !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid pt-5 mt-4">
|
||||
<!-- Header -->
|
||||
@@ -38,48 +547,48 @@
|
||||
<!-- Statistics Cards -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card comm-stat-card shadow-sm h-100">
|
||||
<div class="card-body d-flex align-items-center">
|
||||
<div class="badge bg-primary-subtle text-primary p-3 rounded-3 me-3 fs-4">
|
||||
<div class="badge stat-icon-magenta p-3 rounded-3 me-3 fs-4">
|
||||
<i class="bi bi-journal-bookmark"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="h3 fw-bold mb-0">{{ subjects|length }}</div>
|
||||
<div class="text-muted small">{% trans %}Materias Activas{% endtrans %}</div>
|
||||
<div class="h3 comm-stat-val mb-0">{{ subjects|length }}</div>
|
||||
<div class="comm-stat-lbl">{% trans %}Materias Activas{% endtrans %}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card comm-stat-card shadow-sm h-100">
|
||||
<div class="card-body d-flex align-items-center">
|
||||
<div class="badge bg-info-subtle text-info p-3 rounded-3 me-3 fs-4">
|
||||
<div class="badge stat-icon-purple p-3 rounded-3 me-3 fs-4">
|
||||
<i class="bi bi-diagram-3"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="h3 fw-bold mb-0">{{ total_commissions }}</div>
|
||||
<div class="text-muted small">{% trans %}Total Comisiones{% endtrans %}</div>
|
||||
<div class="h3 comm-stat-val mb-0">{{ total_commissions }}</div>
|
||||
<div class="comm-stat-lbl">{% trans %}Total Comisiones{% endtrans %}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card comm-stat-card shadow-sm h-100">
|
||||
<div class="card-body d-flex align-items-center">
|
||||
<div class="badge bg-success-subtle text-success p-3 rounded-3 me-3 fs-4">
|
||||
<div class="badge stat-icon-teal p-3 rounded-3 me-3 fs-4">
|
||||
<i class="bi bi-people"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="h3 fw-bold mb-0">{{ total_capacity }}</div>
|
||||
<div class="text-muted small">{% trans %}Capacidad Total{% endtrans %}</div>
|
||||
<div class="h3 comm-stat-val mb-0">{{ total_capacity }}</div>
|
||||
<div class="comm-stat-lbl">{% trans %}Capacidad Total{% endtrans %}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card comm-stat-card shadow-sm h-100">
|
||||
<div class="card-body d-flex align-items-center">
|
||||
<div class="badge bg-warning-subtle text-warning p-3 rounded-3 me-3 fs-4">
|
||||
<div class="badge stat-icon-amber p-3 rounded-3 me-3 fs-4">
|
||||
<i class="bi bi-camera-video"></i>
|
||||
</div>
|
||||
<div>
|
||||
@@ -89,8 +598,8 @@
|
||||
{% if c.virtual_link %}{% set ns.virtual_count = ns.virtual_count + 1 %}{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
<div class="h3 fw-bold mb-0">{{ ns.virtual_count }}</div>
|
||||
<div class="text-muted small">{% trans %}Aulas Virtuales Config.{% endtrans %}</div>
|
||||
<div class="h3 comm-stat-val mb-0">{{ ns.virtual_count }}</div>
|
||||
<div class="comm-stat-lbl">{% trans %}Aulas Virtuales Config.{% endtrans %}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -98,16 +607,16 @@
|
||||
</div>
|
||||
|
||||
<!-- Search / Filter Bar -->
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card comm-search-card shadow-sm mb-4">
|
||||
<div class="card-body py-2">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-md-5">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-body-tertiary border-0"><i class="bi bi-search"></i></span>
|
||||
<input type="text" id="filterInput" class="form-control border-0 bg-body-tertiary" placeholder="{% trans %}Filtrar por asignatura, código de comisión o docente...{% endtrans %}">
|
||||
<span class="input-group-text comm-search-icon"><i class="bi bi-search"></i></span>
|
||||
<input type="text" id="filterInput" class="form-control comm-search-input" placeholder="{% trans %}Filtrar por asignatura, código de comisión o docente...{% endtrans %}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-7 text-md-end text-muted small">
|
||||
<div class="col-md-7 text-md-end comm-info-hint">
|
||||
<i class="bi bi-info-circle me-1"></i>{% trans %}Profesores, Bedelía y Administradores pueden actualizar el enlace de videollamada y agendar hitos de examen.{% endtrans %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -118,25 +627,25 @@
|
||||
<div class="row g-4" id="commissionsListContainer">
|
||||
{% for subject in subjects %}
|
||||
<div class="col-12 subject-card" data-subject-name="{{ subject.name|lower }}" data-subject-code="{{ subject.code|lower }}">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-body-tertiary border-0 py-3">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header comm-subject-header border-0 py-3">
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-2">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="badge bg-primary fs-6 me-2 font-monospace">{{ subject.code }}</span>
|
||||
<span class="badge comm-subject-badge fs-6 me-2 font-monospace">{{ subject.code }}</span>
|
||||
<div>
|
||||
<h5 class="fw-bold mb-0 text-dark-emphasis">{{ subject.name }}</h5>
|
||||
<small class="text-muted">
|
||||
<h5 class="mb-0 comm-subject-title">{{ subject.name }}</h5>
|
||||
<small class="comm-subject-meta">
|
||||
<i class="bi bi-building me-1"></i>{{ subject.department or 'UniCABA' }}
|
||||
{% if subject.career %} • <i class="bi bi-mortarboard me-1"></i>{{ subject.career }}{% endif %}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="badge bg-body-secondary text-secondary border">
|
||||
<span class="badge comm-count-badge">
|
||||
{{ subject.commissions|length }} {% trans %}comisiones{% endtrans %}
|
||||
</span>
|
||||
{% if can_edit_commissions %}
|
||||
<button type="button" class="btn btn-sm btn-outline-primary btn-add-comm-subject"
|
||||
<button type="button" class="btn btn-sm comm-btn-subject-add btn-add-comm-subject"
|
||||
data-subject-id="{{ subject.id }}"
|
||||
data-subject-name="{{ subject.name }} ({{ subject.code }})"
|
||||
title="{% trans %}Agregar comisión a esta materia{% endtrans %}">
|
||||
@@ -151,7 +660,7 @@
|
||||
{% if subject.commissions %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-body-tertiary table-light">
|
||||
<thead class="commissions-table-head">
|
||||
<tr>
|
||||
<th class="ps-4" style="min-width: 140px;">{% trans %}Comisión / Turno{% endtrans %}</th>
|
||||
<th style="min-width: 130px;">{% trans %}Ciclo Lectivo{% endtrans %}</th>
|
||||
@@ -166,31 +675,31 @@
|
||||
{% for commission in subject.commissions %}
|
||||
<tr class="commission-row" data-search-text="{{ commission.code|lower }} {{ commission.teacher_name|lower }} {{ subject.name|lower }}">
|
||||
<td class="ps-4">
|
||||
<div class="fw-bold fs-6 text-primary">{{ commission.code }}</div>
|
||||
<div class="comm-code">{{ commission.code }}</div>
|
||||
<div class="d-flex gap-1 mt-1">
|
||||
{% if commission.shift %}
|
||||
<span class="badge bg-secondary-subtle text-secondary border" style="font-size: 0.70rem;">
|
||||
<span class="badge comm-shift-badge" style="font-size: 0.70rem;">
|
||||
<i class="bi bi-clock me-1"></i>{{ commission.shift }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if commission.schedule %}
|
||||
<span class="badge bg-body-tertiary text-dark border" style="font-size: 0.70rem;" title="{{ commission.schedule }}">
|
||||
<span class="badge comm-schedule-badge" style="font-size: 0.70rem;" title="{{ commission.schedule }}">
|
||||
{{ commission.schedule|truncate(15) }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-light text-dark border">
|
||||
<span class="badge comm-semester-badge">
|
||||
{{ commission.semester }} {{ commission.year }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if commission.teacher %}
|
||||
<div class="fw-semibold small"><i class="bi bi-person me-1 text-secondary"></i>{{ commission.teacher.name }}</div>
|
||||
<div class="text-muted small font-monospace" style="font-size: 0.75rem;">{{ commission.teacher.email }}</div>
|
||||
<div class="fw-semibold small comm-teacher-name"><i class="bi bi-person me-1 comm-teacher-icon"></i>{{ commission.teacher.name }}</div>
|
||||
<div class="comm-teacher-email small font-monospace" style="font-size: 0.75rem;">{{ commission.teacher.email }}</div>
|
||||
{% else %}
|
||||
<span class="badge bg-warning-subtle text-warning border border-warning-subtle small">
|
||||
<span class="badge comm-unassigned-badge small">
|
||||
<i class="bi bi-question-circle me-1"></i>{% trans %}Sin asignar{% endtrans %}
|
||||
</span>
|
||||
{% endif %}
|
||||
@@ -199,15 +708,15 @@
|
||||
<!-- Enlace a Aula Virtual (Zoom, Teams, Meet) -->
|
||||
<div class="d-flex align-items-center gap-1">
|
||||
{% if commission.virtual_link %}
|
||||
<a href="{{ commission.virtual_link }}" target="_blank" class="btn btn-sm btn-outline-success text-truncate d-inline-flex align-items-center" style="max-width: 150px;" title="{{ commission.virtual_link }}">
|
||||
<a href="{{ commission.virtual_link }}" target="_blank" class="btn btn-sm comm-btn-virtual text-truncate d-inline-flex align-items-center" style="max-width: 150px;" title="{{ commission.virtual_link }}">
|
||||
<i class="bi bi-camera-video me-1"></i>{% trans %}Unirse{% endtrans %}
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="text-muted small fst-italic me-1">{% trans %}Sin enlace{% endtrans %}</span>
|
||||
<span class="comm-no-link small fst-italic me-1">{% trans %}Sin enlace{% endtrans %}</span>
|
||||
{% endif %}
|
||||
|
||||
{% if can_manage_milestones %}
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary btn-virtual-link"
|
||||
<button type="button" class="btn btn-sm comm-btn-action-sm btn-virtual-link"
|
||||
data-id="{{ commission.id }}"
|
||||
data-name="{{ commission.get_full_code() }}"
|
||||
data-link="{{ commission.virtual_link or '' }}"
|
||||
@@ -221,7 +730,7 @@
|
||||
<!-- Hitos Académicos (Parciales, TPs) -->
|
||||
<div class="d-flex flex-wrap align-items-center gap-1">
|
||||
{% for m in commission.academic_milestones %}
|
||||
<span class="badge shadow-xs text-white d-inline-flex align-items-center py-1 px-2"
|
||||
<span class="badge comm-milestone-chip text-white d-inline-flex align-items-center py-1 px-2"
|
||||
style="background-color: {{ m.color }}; font-size: 0.72rem;"
|
||||
title="{{ m.title }} - {{ m.date.strftime('%d/%m/%Y %H:%M') if m.date else '' }} ({{ m.classroom or 'Campus Virtual' }})">
|
||||
<i class="bi bi-flag-fill me-1" style="font-size: 0.65rem;"></i>
|
||||
@@ -234,11 +743,11 @@
|
||||
{% endif %}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-muted small fst-italic">{% trans %}Sin hitos{% endtrans %}</span>
|
||||
<span class="comm-no-milestone small fst-italic">{% trans %}Sin hitos{% endtrans %}</span>
|
||||
{% endfor %}
|
||||
|
||||
{% if can_manage_milestones %}
|
||||
<button type="button" class="btn btn-sm btn-outline-warning py-0 px-2 btn-add-milestone"
|
||||
<button type="button" class="btn btn-sm comm-btn-add-milestone py-0 px-2 btn-add-milestone"
|
||||
data-id="{{ commission.id }}"
|
||||
data-name="{{ commission.get_full_code() }}"
|
||||
data-subject-code="{{ subject.code }}"
|
||||
@@ -251,7 +760,7 @@
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div class="small fw-semibold">{{ commission.current_students }}/{{ commission.max_students }}</div>
|
||||
<div class="small fw-semibold comm-stat-val">{{ commission.current_students }}/{{ commission.max_students }}</div>
|
||||
{% set fill_pct = (commission.current_students / commission.max_students * 100) if commission.max_students > 0 else 0 %}
|
||||
<div class="progress mt-1 mx-auto" style="height: 4px; width: 50px;">
|
||||
<div class="progress-bar {% if fill_pct > 90 %}bg-danger{% elif fill_pct > 70 %}bg-warning{% else %}bg-success{% endif %}"
|
||||
@@ -259,8 +768,8 @@
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-end pe-4">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button type="button" class="btn btn-outline-primary quick-reserve-btn"
|
||||
<div class="btn-group btn-group-sm comm-actions-group">
|
||||
<button type="button" class="btn comm-btn-reserve quick-reserve-btn"
|
||||
data-commission-id="{{ commission.id }}"
|
||||
data-max-students="{{ commission.max_students }}"
|
||||
data-commission-name="{{ commission.get_full_code() }}"
|
||||
@@ -269,7 +778,7 @@
|
||||
<i class="bi bi-calendar-plus"></i>
|
||||
</button>
|
||||
{% if can_edit_commissions %}
|
||||
<button type="button" class="btn btn-outline-secondary btn-edit-comm"
|
||||
<button type="button" class="btn comm-btn-edit btn-edit-comm"
|
||||
data-id="{{ commission.id }}"
|
||||
data-code="{{ commission.code }}"
|
||||
data-semester="{{ commission.semester }}"
|
||||
@@ -283,7 +792,7 @@
|
||||
title="{% trans %}Editar Comisión{% endtrans %}">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger btn-delete-comm"
|
||||
<button type="button" class="btn comm-btn-delete btn-delete-comm"
|
||||
data-id="{{ commission.id }}"
|
||||
data-name="{{ commission.get_full_code() }}"
|
||||
data-action-url="{{ url_for('schedule.commission_delete', id=commission.id) }}"
|
||||
@@ -500,7 +1009,7 @@
|
||||
<form id="formVirtualLink" method="POST">
|
||||
<div class="modal-body p-4">
|
||||
<p class="small text-muted mb-3">
|
||||
{% trans %}Asigna o actualiza el enlace de acceso directo al aula virtual para la comisión{% endtrans %} <strong id="virtualLinkCommName" class="text-primary"></strong>:
|
||||
{% trans %}Asigna o actualiza el enlace de acceso directo al aula virtual para la comisión{% endtrans %} <strong id="virtualLinkCommName" class="comm-code"></strong>:
|
||||
</p>
|
||||
<div class="mb-3">
|
||||
<label for="inputVirtualLink" class="form-label fw-semibold">URL de la reunión / sala</label>
|
||||
@@ -537,8 +1046,8 @@
|
||||
</div>
|
||||
<form id="formAddMilestone" method="POST">
|
||||
<div class="modal-body p-4">
|
||||
<div class="alert alert-info border-0 py-2 small mb-3">
|
||||
{% trans %}Agendando evento de evaluación para la comisión{% endtrans %} <strong id="milestoneCommName" class="text-primary"></strong>
|
||||
<div class="alert comm-modal-alert border-0 py-2 small mb-3">
|
||||
{% trans %}Agendando evento de evaluación para la comisión{% endtrans %} <strong id="milestoneCommName" class="comm-code"></strong>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
@@ -612,7 +1121,7 @@
|
||||
<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 permanentemente la comisión{% endtrans %} <strong id="deleteCommName" class="text-primary"></strong>?</p>
|
||||
<p class="mb-2">{% trans %}¿Está seguro de que desea eliminar permanentemente la comisión{% endtrans %} <strong id="deleteCommName" class="comm-code"></strong>?</p>
|
||||
<p class="text-muted small mb-0"><i class="bi bi-info-circle me-1"></i>{% trans %}Esta acción eliminará sus reservas e hitos académicos asociados.{% endtrans %}</p>
|
||||
</div>
|
||||
<div class="modal-footer border-0 pt-0">
|
||||
@@ -639,7 +1148,7 @@
|
||||
<form id="quick-reservation-form">
|
||||
<input type="hidden" id="quick-commission-id" name="commission_id">
|
||||
|
||||
<div class="alert alert-info py-2 mb-3" id="commission-details"></div>
|
||||
<div class="alert comm-modal-alert py-2 mb-3" id="commission-details"></div>
|
||||
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-6">
|
||||
|
||||
@@ -179,6 +179,12 @@
|
||||
{% 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="{% trans %}Unirse a Clase Virtual (Zoom/Meet){% endtrans %}">
|
||||
<i class="bi bi-camera-video"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('schedule.view_reservation', id=reservation.id) }}"
|
||||
class="btn btn-sm btn-outline-secondary py-0 px-2" title="{% trans %}Ver Ficha{% endtrans %}">
|
||||
<i class="bi bi-eye"></i>
|
||||
|
||||
@@ -214,9 +214,16 @@
|
||||
<span class="small text-muted">
|
||||
<i class="bi bi-people me-1"></i>{{ item.reservation.expected_attendees }} {% trans %}estudiantes{% endtrans %}
|
||||
</span>
|
||||
<a href="{{ url_for('schedule.view_reservation', id=item.reservation.id) }}" class="btn btn-sm btn-outline-secondary py-0 px-2">
|
||||
<i class="bi bi-eye me-1"></i>{% trans %}Detalles{% endtrans %}
|
||||
</a>
|
||||
<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="{% trans %}Unirse a Videollamada (Zoom / Meet){% endtrans %}">
|
||||
<i class="bi bi-camera-video me-1"></i>Zoom/Meet
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('schedule.view_reservation', id=item.reservation.id) }}" class="btn btn-sm btn-outline-secondary py-0 px-2" title="{% trans %}Ver Ficha Detallada{% endtrans %}">
|
||||
<i class="bi bi-eye me-1"></i>{% trans %}Detalles{% endtrans %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -286,6 +293,12 @@
|
||||
{% 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="{% trans %}Unirse a Clase Virtual (Zoom/Meet){% endtrans %}">
|
||||
<i class="bi bi-camera-video"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('schedule.view_reservation', id=reservation.id) }}"
|
||||
class="btn btn-sm btn-outline-secondary py-0 px-2" title="{% trans %}Ver Ficha{% endtrans %}">
|
||||
<i class="bi bi-eye"></i>
|
||||
|
||||
@@ -127,6 +127,27 @@
|
||||
<p class="mb-0 small">{{ reservation.classroom.description }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Banner de Aula Virtual (Zoom, Meet, Teams) -->
|
||||
{% if reservation.effective_virtual_link or (reservation.classroom and reservation.classroom.is_virtual) %}
|
||||
<div class="mt-3 p-3 rounded bg-primary bg-opacity-10 border border-primary border-opacity-25 d-flex flex-column flex-sm-row justify-content-between align-items-sm-center gap-3">
|
||||
<div>
|
||||
<div class="fw-bold text-primary mb-1">
|
||||
<i class="bi bi-camera-video me-1"></i>{% trans %}Aula Virtual / Videoconferencia{% endtrans %}
|
||||
</div>
|
||||
{% if reservation.effective_virtual_link %}
|
||||
<small class="text-secondary text-break font-monospace d-block">{{ reservation.effective_virtual_link }}</small>
|
||||
{% else %}
|
||||
<small class="text-muted fst-italic">{% trans %}Modalidad remota sin enlace específico registrado{% endtrans %}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if reservation.effective_virtual_link %}
|
||||
<a href="{{ reservation.effective_virtual_link }}" target="_blank" class="btn btn-success btn-sm px-3 text-nowrap">
|
||||
<i class="bi bi-box-arrow-up-right me-1"></i>{% trans %}Unirse a Clase (Zoom / Meet){% endtrans %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<p class="text-muted mb-0">{% trans %}Classroom information not available{% endtrans %}</p>
|
||||
{% endif %}
|
||||
@@ -173,15 +194,23 @@
|
||||
<h3 class="mb-1">{{ reservation.expected_attendees }}</h3>
|
||||
<p class="text-muted small">Expected Attendees</p>
|
||||
{% if reservation.classroom %}
|
||||
<div class="progress mt-3" style="height: 8px;">
|
||||
{% set percentage = (reservation.expected_attendees / reservation.classroom.capacity * 100) %}
|
||||
<div class="progress-bar bg-{% if percentage > 90 %}danger{% elif percentage > 75 %}warning{% else %}success{% endif %}"
|
||||
role="progressbar"
|
||||
style="{{ 'width: ' ~ (percentage|round(1)|string) ~ '%;' }}"></div>
|
||||
</div>
|
||||
<small class="text-muted">
|
||||
{{ reservation.expected_attendees }} / {{ reservation.classroom.capacity }} capacity
|
||||
</small>
|
||||
{% if reservation.classroom.capacity and reservation.classroom.capacity > 0 %}
|
||||
<div class="progress mt-3" style="height: 8px;">
|
||||
{% set percentage = (reservation.expected_attendees / reservation.classroom.capacity * 100) %}
|
||||
<div class="progress-bar bg-{% if percentage > 90 %}danger{% elif percentage > 75 %}warning{% else %}success{% endif %}"
|
||||
role="progressbar"
|
||||
style="{{ 'width: ' ~ ([percentage|round(1), 100]|min|string) ~ '%;' }}"></div>
|
||||
</div>
|
||||
<small class="text-muted">
|
||||
{{ reservation.expected_attendees }} / {{ reservation.classroom.capacity }} {% trans %}capacidad{% endtrans %}
|
||||
</small>
|
||||
{% else %}
|
||||
<div class="mt-2">
|
||||
<span class="badge bg-primary-subtle text-primary border">
|
||||
{% if reservation.classroom.is_virtual %}🌐 Campus Virtual (Sin límite físico){% else %}{% trans %}Capacidad no definida{% endtrans %}{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user