{% extends 'admin/base_admin.html.twig' %}
{% block content %}
<div class="row mb-4">
<div class="col-12">
<!-- Header con estadísticas -->
<div class="row g-3 mb-4">
<div class="col-md-3">
<div class="card bg-primary text-white">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<h4 class="mb-0">{{ appointments.getTotalItemCount }}</h4>
<small class="opacity-75">Total Citas</small>
</div>
<div class="flex-shrink-0">
<i class="fas fa-calendar-check fa-2x opacity-50"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-success text-white">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<h4 class="mb-0">{{ appointments|filter(a => a.status == 'confirmada')|length }}</h4>
<small class="opacity-75">Confirmadas</small>
</div>
<div class="flex-shrink-0">
<i class="fas fa-check-circle fa-2x opacity-50"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-info text-white">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<h4 class="mb-0">{{ appointments|filter(a => a.status == 'completada')|length }}</h4>
<small class="opacity-75">Completadas</small>
</div>
<div class="flex-shrink-0">
<i class="fas fa-check-double fa-2x opacity-50"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-danger text-white">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<h4 class="mb-0">{{ appointments|filter(a => a.status starts with 'cancelada')|length }}</h4>
<small class="opacity-75">Canceladas</small>
</div>
<div class="flex-shrink-0">
<i class="fas fa-times-circle fa-2x opacity-50"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Card principal -->
<div class="card border-0 shadow-lg">
<div class="card-header bg-white border-bottom-0 py-4">
<div class="row align-items-center">
<div class="col-md-6">
<h4 class="mb-0 text-dark fw-bold">
<i class="fas fa-calendar-check me-2 text-primary"></i>
Gestión de Citas
</h4>
<p class="text-muted mb-0 mt-1">Administra las citas médicas del sistema</p>
</div>
<div class="col-md-6 text-md-end">
<div class="d-flex flex-column flex-md-row gap-2 justify-content-md-end">
<a href="{{ path('admin_appointment_calendar') }}" class="btn btn-outline-primary btn-lg px-4">
<i class="fas fa-calendar-alt me-2"></i>Vista Calendario
</a>
<a href="{{ path('admin_appointment_new') }}" class="btn btn-primary btn-lg px-4">
<i class="fas fa-plus-circle me-2"></i>Nueva Cita
</a>
</div>
</div>
</div>
</div>
<div class="card-body p-0">
<!-- Barra de búsqueda y filtros -->
<div class="bg-light border-bottom p-4">
<form method="get" class="row g-3 align-items-end">
<!-- Primera fila -->
<div class="col-12 col-md-8 col-lg-6">
<label class="form-label fw-semibold text-dark">Búsqueda</label>
<div class="input-group">
<span class="input-group-text bg-white border-end-0">
<i class="fas fa-search text-muted"></i>
</span>
<input type="text" name="q" value="{{ q|default('') }}"
class="form-control border-start-0"
placeholder="Buscar por paciente, método de pago, tipo estudio, notas...">
</div>
</div>
<!-- Segunda fila - Filtros -->
<div class="col-12 col-sm-6 col-md-4 col-lg-3">
<label class="form-label fw-semibold text-dark">Proveedor</label>
<select name="provider" class="form-select">
<option value="">Todos los proveedores</option>
{% for provider in providers %}
<option value="{{ provider.id }}"
{{ (selectedProvider|default('') == provider.id) ? 'selected' : '' }}>
{{ provider.name }} {{ provider.lastName }}
</option>
{% endfor %}
</select>
</div>
{# Filtro de estado - solo mostrar cuando no hay filtro del submenú #}
{% if active_filter == 'all' %}
<div class="col-12 col-sm-6 col-md-4 col-lg-3">
<label class="form-label fw-semibold text-dark">Estado</label>
<select name="status" class="form-select">
<option value="">Todos los estados</option>
{% for status in statuses %}
<option value="{{ status }}"
{{ (selectedStatus|default('') == status) ? 'selected' : '' }}>
{{ status }}
</option>
{% endfor %}
</select>
</div>
{% endif %}
<!-- Tercera fila - Botones -->
<div class="col-12 col-sm-6 col-md-4 col-lg-3">
<label class="form-label fw-semibold text-dark"> </label>
<div class="d-grid gap-2 d-md-flex">
<button type="submit" class="btn btn-primary btn-lg flex-fill">
<i class="fas fa-search me-2"></i>Buscar
</button>
<a href="{{ path('admin_appointment') }}" class="btn btn-outline-secondary btn-lg flex-fill">
<i class="fas fa-times me-2"></i>Limpiar
</a>
</div>
</div>
</form>
{# Información del filtro activo del submenú #}
{% if active_filter != 'all' %}
<div class="row mt-3">
<div class="col-12">
<div class="alert alert-info py-2 mb-0">
<small class="d-flex align-items-center">
<i class="fas fa-filter me-2"></i>
<span>
Mostrando
<strong class="text-dark">
{% if active_filter == 'confirmada' %}
citas confirmadas
{% elseif active_filter == 'completada' %}
citas finalizadas (incluye completadas y ausentes)
{% elseif active_filter == 'cancelada' %}
citas canceladas (incluye canceladas por paciente y clínica)
{% endif %}
</strong>
.
<a href="{{ path('admin_appointment') }}" class="alert-link ms-1">
Ver todas las citas
</a>
</span>
</small>
</div>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Leyenda de estados -->
<div class="p-4 border-bottom bg-white">
<h6 class="text-muted mb-3 fw-semibold">
<i class="fas fa-info-circle me-2 text-primary"></i>
Leyenda de Estados
</h6>
<div class="d-flex flex-wrap gap-2">
<span class="badge bg-success bg-opacity-10 text-success border border-success border-opacity-25 px-3 py-2 rounded-pill">
<i class="fas fa-check-circle me-1"></i>Confirmada
</span>
<span class="badge bg-info bg-opacity-10 text-info border border-info border-opacity-25 px-3 py-2 rounded-pill">
<i class="fas fa-check-double me-1"></i>Completada
</span>
<span class="badge bg-danger bg-opacity-10 text-danger border border-danger border-opacity-25 px-3 py-2 rounded-pill">
<i class="fas fa-user-times me-1"></i>Cancelada Paciente
</span>
<span class="badge bg-dark bg-opacity-10 text-dark border border-dark border-opacity-25 px-3 py-2 rounded-pill">
<i class="fas fa-clinic-medical me-1"></i>Cancelada Clínica
</span>
<span class="badge bg-warning bg-opacity-10 text-warning border border-warning border-opacity-25 px-3 py-2 rounded-pill">
<i class="fas fa-user-slash me-1"></i>Ausente
</span>
<span class="badge bg-primary bg-opacity-10 text-primary border border-primary border-opacity-25 px-3 py-2 rounded-pill">
<i class="fas fa-calendar-alt me-1"></i>Reprogramada
</span>
</div>
</div>
<!-- Tabla de citas ACTUALIZADA con paciente y MÉTODO DE PAGO -->
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th class="ps-4 py-3 fw-semibold text-dark border-bottom-2">
<i class="fas fa-hashtag me-2 text-primary"></i>ID
</th>
<th class="py-3 fw-semibold text-dark border-bottom-2">
<i class="fas fa-user-injured me-2 text-primary"></i>Paciente
</th>
<th class="py-3 fw-semibold text-dark border-bottom-2">
<i class="fas fa-credit-card me-2 text-primary"></i>Método de Pago
</th>
<th class="py-3 fw-semibold text-dark border-bottom-2">
<i class="fas fa-clock me-2 text-primary"></i>Fecha/Hora
</th>
<th class="py-3 fw-semibold text-dark border-bottom-2">
<i class="fas fa-tag me-2 text-primary"></i>Estado
</th>
<th class="py-3 fw-semibold text-dark border-bottom-2">
<i class="fas fa-stethoscope me-2 text-primary"></i>Tipo Estudio
</th>
<th class="py-3 fw-semibold text-dark border-bottom-2">
<i class="fas fa-file-alt me-2 text-primary"></i>Notas
</th>
<th class="text-end pe-4 py-3 fw-semibold text-dark border-bottom-2">
<i class="fas fa-cogs me-2 text-primary"></i>Acciones
</th>
</tr>
</thead>
<tbody>
{% for appointment in appointments %}
<tr class="border-bottom">
<td class="ps-4 py-3">
<div class="d-flex align-items-center">
<div class="bg-primary bg-opacity-10 rounded p-2 me-3">
<i class="fas fa-calendar text-primary"></i>
</div>
<div>
<h6 class="mb-0 fw-semibold text-dark">#{{ appointment.id }}</h6>
<small class="text-muted">Cita</small>
</div>
</div>
</td>
<!-- Columna PACIENTE -->
<td class="py-3">
<div class="d-flex align-items-center">
<div class="bg-success bg-opacity-10 rounded p-2 me-2">
<i class="fas fa-user-injured text-success"></i>
</div>
<div>
<h6 class="mb-0 fw-semibold text-dark">{{ appointment.patient.name }} {{ appointment.patient.lastName }}</h6>
<small class="text-muted">tel: {{ appointment.patient.phone ?? 'Sin teléfono' }}</small>
<small class="text-success d-block">
<i class="fas fa-user-md me-1"></i>
Proveedor: {{ appointment.provider.name }}
</small>
<div>
<small class="text-primary">
<i class="fas fa-envelope me-1"></i>{{ appointment.provider.email }}
</small>
</div>
</div>
</div>
</td>
<!-- Columna MÉTODO DE PAGO (REEMPLAZA PROVEEDOR) -->
<td class="py-3">
<div class="d-flex align-items-center">
<div class="bg-info bg-opacity-10 rounded p-2 me-2">
<i class="fas fa-credit-card text-info"></i>
</div>
<div>
{% if appointment.paymentMethod == 'imaging_pro' %}
<h6 class="mb-0 fw-semibold text-dark">
Imaging Pro
{% if appointment.montoImagingPro %}
<span class="text-success fw-bold ms-1">
${{ appointment.montoImagingPro|number_format(2, '.', ',') }}
</span>
{% endif %}
</h6>
<small class="text-muted">Pago en Imaging Pro</small>
{% elseif appointment.paymentMethod == 'clinica' %}
<h6 class="mb-0 fw-semibold text-dark">Clínica</h6>
<small class="text-muted">Pago directo</small>
{% else %}
<h6 class="mb-0 fw-semibold text-dark">No especificado</h6>
<small class="text-muted">-</small>
{% endif %}
</div>
</div>
</td>
<td class="py-3">
<span class="fw-semibold text-dark">{{ appointment.horario.fecha|date("d/m/Y") }}</span>
<small class="text-muted d-block">{{ appointment.horario.hora|date("H:i") }}</small>
</td>
<td class="py-3">
{% if appointment.status == 'confirmada' %}
<span class="badge bg-success bg-opacity-10 text-success border border-success border-opacity-25 px-3 py-2 rounded-pill">
<i class="fas fa-check-circle me-1"></i>Confirmada
</span>
{% elseif appointment.status starts with 'cancelada_paciente' %}
<span class="badge bg-danger bg-opacity-10 text-danger border border-danger border-opacity-25 px-3 py-2 rounded-pill">
<i class="fas fa-user-times me-1"></i>Cancelada Paciente
</span>
{% elseif appointment.status starts with 'cancelada_clinica' %}
<span class="badge bg-dark bg-opacity-10 text-dark border border-dark border-opacity-25 px-3 py-2 rounded-pill">
<i class="fas fa-clinic-medical me-1"></i>Cancelada Clínica
</span>
{% elseif appointment.status == 'completada' %}
<span class="badge bg-info bg-opacity-10 text-info border border-info border-opacity-25 px-3 py-2 rounded-pill">
<i class="fas fa-check-double me-1"></i>Completada
</span>
{% elseif appointment.status == 'ausente' %}
<span class="badge bg-warning bg-opacity-10 text-warning border border-warning border-opacity-25 px-3 py-2 rounded-pill">
<i class="fas fa-user-slash me-1"></i>Ausente
</span>
{% elseif appointment.status == 'reprogramada' %}
<span class="badge bg-primary bg-opacity-10 text-primary border border-primary border-opacity-25 px-3 py-2 rounded-pill">
<i class="fas fa-calendar-alt me-1"></i>Reprogramada
</span>
{% else %}
<span class="badge bg-secondary bg-opacity-10 text-secondary border border-secondary border-opacity-25 px-3 py-2 rounded-pill">
<i class="fas fa-question-circle me-1"></i>{{ appointment.status|title }}
</span>
{% endif %}
</td>
<!-- Columna TIPO ESTUDIO -->
<td class="py-3">
{% if appointment.tipoEstudio %}
<span class="badge bg-primary bg-opacity-10 text-primary border border-primary border-opacity-25 px-3 py-2 rounded-pill">
<i class="fas fa-stethoscope me-1"></i>{{ appointment.tipoEstudio }}
</span>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
<td class="py-3">
{% if appointment.notes %}
<div style="white-space: pre-line; max-width: 200px; font-size: 0.9rem; line-height: 1.4;"
data-bs-toggle="tooltip"
title="{{ appointment.notes|replace({'\n': ' / '}) }}">
{{ appointment.notes|length > 50 ? appointment.notes|slice(0, 50) ~ '...' : appointment.notes }}
</div>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
<td class="text-end pe-4 py-3">
<div class="d-flex justify-content-end gap-2">
{% if appointment.status not in ['cancelada_clinica', 'cancelada_paciente', 'completada', 'ausente'] %}
<button class="btn btn-outline-danger btn-sm px-3 rounded-pill btn-cancelar"
data-bs-toggle="modal"
data-bs-target="#cancelModal"
data-appointment-id="{{ appointment.id }}"
data-appointment-info="{{ appointment.horario.fecha|date('d/m/Y') }} {{ appointment.horario.hora|date('H:i') }} - {{ appointment.patient.name }}"
data-csrf-token="{{ csrf_token('cancel_' ~ appointment.id) }}">
<i class="fas fa-times-circle me-1"></i>Cancelar
</button>
<button class="btn btn-outline-success btn-sm px-3 rounded-pill btn-finalizar"
data-bs-toggle="modal"
data-bs-target="#finalizeModal"
data-appointment-id="{{ appointment.id }}"
data-appointment-info="Cita #{{ appointment.id }} - {{ appointment.horario.fecha|date('d/m/Y') }} {{ appointment.horario.hora|date('H:i') }} - {{ appointment.patient.name }}"
data-csrf-token="{{ csrf_token('status_' ~ appointment.id) }}">
<i class="fas fa-flag-checkered me-1"></i>Finalizar
</button>
{% endif %}
<a href="{{ path('admin_appointment_reschedule', {id: appointment.id}) }}"
class="btn btn-outline-primary btn-sm px-3 rounded-pill"
data-bs-toggle="tooltip"
title="Reprogramar cita">
<i class="fas fa-calendar-alt me-1"></i>Reprogramar
</a>
<button class="btn btn-outline-danger btn-sm px-3 rounded-pill"
data-bs-toggle="modal"
data-bs-target="#deleteModal"
data-appointment-id="{{ appointment.id }}"
data-appointment-info="Cita #{{ appointment.id }} - {{ appointment.horario.fecha|date('d/m/Y') }} {{ appointment.horario.hora|date('H:i') }} - {{ appointment.patient.name }}"
data-csrf-token="{{ csrf_token('delete' ~ appointment.id) }}">
<i class="fas fa-trash me-1"></i>Eliminar
</button>
</div>
</td>
</tr>
{% else %}
<tr>
<td colspan="8" class="text-center py-5">
<div class="py-5">
<i class="fas fa-calendar-times fa-3x text-muted mb-3"></i>
<h5 class="text-muted">No se encontraron citas</h5>
<p class="text-muted mb-4">No hay citas registradas en el sistema</p>
<a href="{{ path('admin_appointment_new') }}" class="btn btn-primary">
<i class="fas fa-plus me-2"></i>Crear primera cita
</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Paginación (mantener igual) -->
{% if appointments.getTotalItemCount > 0 %}
<div class="border-top bg-light">
<div class="row align-items-center p-3 p-md-4">
<div class="col-12 col-md-6 mb-3 mb-md-0">
{% set total = appointments.getTotalItemCount %}
{% set perPage = appointments.getItemNumberPerPage %}
{% set page = appointments.getCurrentPageNumber %}
{% set first = total > 0 ? ((page - 1) * perPage + 1) : 0 %}
{% set last = min(first + appointments|length - 1, total) %}
<p class="mb-0 text-muted fw-semibold text-center text-md-start">
Mostrando <span class="text-dark">{{ first }}–{{ last }}</span> de
<span class="text-dark">{{ total }}</span> citas
</p>
</div>
<div class="col-12 col-md-6">
<div class="d-flex justify-content-center justify-content-md-end align-items-center">
<!-- Paginación responsive -->
<nav aria-label="Paginación de citas">
<ul class="pagination pagination-sm mb-0 flex-wrap justify-content-center">
<!-- Primera página -->
<li class="page-item {% if page == 1 %}disabled{% endif %}">
<a class="page-link" href="{{ path('admin_appointment', app.request.query.all|merge({page: 1})) }}"
aria-label="Primera página" data-bs-toggle="tooltip" title="Primera página">
<i class="fas fa-angle-double-left d-none d-sm-inline"></i>
<span class="d-inline d-sm-none">Primera</span>
</a>
</li>
<!-- Página anterior -->
<li class="page-item {% if page == 1 %}disabled{% endif %}">
<a class="page-link" href="{{ path('admin_appointment', app.request.query.all|merge({page: page - 1})) }}"
aria-label="Página anterior" data-bs-toggle="tooltip" title="Página anterior">
<i class="fas fa-angle-left"></i>
</a>
</li>
<!-- Indicador de página actual en móvil -->
<li class="page-item d-block d-md-none disabled">
<span class="page-link fw-bold text-primary">
{{ page }} de {{ (total / perPage)|round(0, 'ceil') }}
</span>
</li>
<!-- Páginas numeradas (ocultas en móvil) -->
{% set totalPages = (total / perPage)|round(0, 'ceil') %}
{% set startPage = max(1, page - 2) %}
{% set endPage = min(totalPages, startPage + 4) %}
{% if startPage > 1 %}
<li class="page-item d-none d-md-block disabled">
<span class="page-link">...</span>
</li>
{% endif %}
{% for i in startPage..endPage %}
<li class="page-item d-none d-md-block {% if i == page %}active{% endif %}">
<a class="page-link" href="{{ path('admin_appointment', app.request.query.all|merge({page: i})) }}">
{{ i }}
</a>
</li>
{% endfor %}
{% if endPage < totalPages %}
<li class="page-item d-none d-md-block disabled">
<span class="page-link">...</span>
</li>
{% endif %}
<!-- Página siguiente -->
<li class="page-item {% if page == totalPages %}disabled{% endif %}">
<a class="page-link" href="{{ path('admin_appointment', app.request.query.all|merge({page: page + 1})) }}"
aria-label="Página siguiente" data-bs-toggle="tooltip" title="Página siguiente">
<i class="fas fa-angle-right"></i>
</a>
</li>
<!-- Última página -->
<li class="page-item {% if page == totalPages %}disabled{% endif %}">
<a class="page-link" href="{{ path('admin_appointment', app.request.query.all|merge({page: totalPages})) }}"
aria-label="Última página" data-bs-toggle="tooltip" title="Última página">
<i class="fas fa-angle-double-right d-none d-sm-inline"></i>
<span class="d-inline d-sm-none">Última</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Modal de Cancelación - Manteniendo el mismo estilo -->
<div class="modal fade" id="cancelModal" tabindex="-1" aria-labelledby="cancelModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title" id="cancelModalLabel">
<i class="fas fa-exclamation-triangle me-2"></i>Confirmar Cancelación
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>¿Estás seguro de que deseas cancelar la siguiente cita?</p>
<div class="alert alert-warning">
<i class="fas fa-info-circle me-2"></i>
<strong id="appointmentInfo"></strong>
</div>
<div class="mb-3">
<label for="cancelReason" class="form-label">Motivo de cancelación (opcional):</label>
<textarea class="form-control" id="cancelReason" rows="3" placeholder="Ingresa el motivo de la cancelación..."></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="fas fa-times me-2"></i>Cancelar
</button>
<button type="button" class="btn btn-danger" id="confirmCancel">
<i class="fas fa-times-circle me-2"></i>Confirmar Cancelación
</button>
</div>
</div>
</div>
</div>
<!-- Modal de Finalización -->
<div class="modal fade" id="finalizeModal" tabindex="-1" aria-labelledby="finalizeModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-success text-white">
<h5 class="modal-title" id="finalizeModalLabel">
<i class="fas fa-flag-checkered me-2"></i>Finalizar Cita
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Selecciona el estado final para la siguiente cita:</p>
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
<strong id="finalizeAppointmentInfo"></strong>
</div>
<div class="row g-3">
<div class="col-md-6">
<div class="card border-success h-100 text-center finalize-option" data-status="completada">
<div class="card-body">
<i class="fas fa-check-double fa-2x text-success mb-3"></i>
<h6 class="card-title">Completada</h6>
<p class="card-text small text-muted">El paciente asistió y la cita se completó exitosamente</p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card border-warning h-100 text-center finalize-option" data-status="ausente">
<div class="card-body">
<i class="fas fa-user-slash fa-2x text-warning mb-3"></i>
<h6 class="card-title">Ausente</h6>
<p class="card-text small text-muted">El paciente no se presentó a la cita programada</p>
</div>
</div>
</div>
</div>
<div class="mt-3">
<label for="finalizeNotes" class="form-label">Observaciones (opcional):</label>
<textarea class="form-control" id="finalizeNotes" rows="2" placeholder="Agregar observaciones sobre el estado final..."></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="fas fa-times me-2"></i>Cancelar
</button>
<button type="button" class="btn btn-success" id="confirmFinalize" disabled>
<i class="fas fa-check me-2"></i>Confirmar
</button>
</div>
</div>
</div>
</div>
<!-- Modal de Eliminación - VERSIÓN CORREGIDA -->
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title" id="deleteModalLabel">
<i class="fas fa-exclamation-triangle me-2"></i>Confirmar Eliminación
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>¿Estás seguro de que deseas eliminar la siguiente cita?</p>
<div class="alert alert-danger">
<i class="fas fa-exclamation-circle me-2"></i>
<strong>¡Esta acción no se puede deshacer!</strong>
</div>
<div class="card border-danger">
<div class="card-body">
<h6 id="deleteAppointmentInfo" class="text-danger mb-2"></h6>
<p class="mb-0 text-muted small">
<i class="fas fa-info-circle me-1"></i>
Se eliminará permanentemente toda la información de esta cita.
</p>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="fas fa-times me-2"></i>Cancelar
</button>
<button type="button" class="btn btn-danger" id="confirmDelete">
<i class="fas fa-trash me-2"></i>Eliminar Permanentemente
</button>
</div>
</div>
</div>
</div>
<style>
.finalize-option {
cursor: pointer;
transition: all 0.3s ease;
}
.finalize-option:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
</style>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Tooltips
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
});
// Modal de cancelación
const cancelModal = document.getElementById('cancelModal');
const cancelButtons = document.querySelectorAll('.btn-cancelar');
const confirmCancelBtn = document.getElementById('confirmCancel');
const appointmentInfo = document.getElementById('appointmentInfo');
let currentAppointmentId = null;
let currentCsrfToken = null;
cancelButtons.forEach(button => {
button.addEventListener('click', function() {
currentAppointmentId = this.getAttribute('data-appointment-id');
const info = this.getAttribute('data-appointment-info');
const csrfToken = this.getAttribute('data-csrf-token');
appointmentInfo.textContent = `Cita #${currentAppointmentId} - ${info}`;
currentCsrfToken = csrfToken;
});
});
confirmCancelBtn.addEventListener('click', function() {
if (currentAppointmentId) {
const reason = document.getElementById('cancelReason').value;
// Mostrar loading
confirmCancelBtn.disabled = true;
confirmCancelBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Cancelando...';
// Endpoint real para cancelar la cita
fetch(`/admin/appointment/${currentAppointmentId}/status`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest'
},
body: new URLSearchParams({
'status': 'cancelada_clinica',
'reason': reason || 'Sin motivo especificado',
'_token': currentCsrfToken
})
})
.then(response => {
if (!response.ok) {
throw new Error('Error en la respuesta del servidor');
}
return response.json();
})
.then(data => {
if (data.success) {
// Mostrar toast de éxito para cancelación
showToast('success', 'Cita cancelada correctamente');
// Cerrar modal
const modal = bootstrap.Modal.getInstance(cancelModal);
modal.hide();
// Recargar la página después de un breve delay
setTimeout(() => {
location.reload();
}, 1500);
} else {
throw new Error(data.message || 'Error al cancelar la cita');
}
})
.catch(error => {
console.error('Error:', error);
showToast('error', 'Error al cancelar la cita: ' + error.message);
// Restaurar botón
confirmCancelBtn.disabled = false;
confirmCancelBtn.innerHTML = '<i class="fas fa-times-circle me-2"></i>Confirmar Cancelación';
});
}
});
// Reset modal cuando se cierra
cancelModal.addEventListener('hidden.bs.modal', function () {
document.getElementById('cancelReason').value = '';
currentAppointmentId = null;
currentCsrfToken = null;
confirmCancelBtn.disabled = false;
confirmCancelBtn.innerHTML = '<i class="fas fa-times-circle me-2"></i>Confirmar Cancelación';
});
// Modal de Finalización
const finalizeModal = document.getElementById('finalizeModal');
const finalizeButtons = document.querySelectorAll('.btn-finalizar');
const finalizeAppointmentInfo = document.getElementById('finalizeAppointmentInfo');
const confirmFinalizeBtn = document.getElementById('confirmFinalize');
const finalizeOptions = document.querySelectorAll('.finalize-option');
let currentFinalizeAppointmentId = null;
let currentFinalizeCsrfToken = null;
let selectedStatus = null;
finalizeButtons.forEach(button => {
button.addEventListener('click', function() {
currentFinalizeAppointmentId = this.getAttribute('data-appointment-id');
const appointmentInfo = this.getAttribute('data-appointment-info');
currentFinalizeCsrfToken = this.getAttribute('data-csrf-token');
finalizeAppointmentInfo.textContent = appointmentInfo;
// Resetear selección
selectedStatus = null;
confirmFinalizeBtn.disabled = true;
finalizeOptions.forEach(option => {
option.classList.remove('bg-success', 'bg-warning', 'text-white');
option.classList.add('bg-light');
});
});
});
// Manejar selección de opción
finalizeOptions.forEach(option => {
option.addEventListener('click', function() {
const status = this.getAttribute('data-status');
// Remover selección anterior
finalizeOptions.forEach(opt => {
opt.classList.remove('bg-success', 'bg-warning', 'text-white');
opt.classList.add('bg-light');
});
// Aplicar selección actual
this.classList.remove('bg-light');
if (status === 'completada') {
this.classList.add('bg-success', 'text-white');
} else {
this.classList.add('bg-warning', 'text-white');
}
selectedStatus = status;
confirmFinalizeBtn.disabled = false;
});
});
// Confirmar finalización
confirmFinalizeBtn.addEventListener('click', function() {
if (currentFinalizeAppointmentId && selectedStatus) {
const notes = document.getElementById('finalizeNotes').value;
const statusText = selectedStatus === 'completada' ? 'Completada' : 'Ausente';
// Mostrar loading
confirmFinalizeBtn.disabled = true;
confirmFinalizeBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Procesando...';
// Cambiar estado de la cita
fetch(`/admin/appointment/${currentFinalizeAppointmentId}/status`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest'
},
body: new URLSearchParams({
'status': selectedStatus,
'reason': notes || 'Sin observaciones',
'_token': currentFinalizeCsrfToken
})
})
.then(response => {
if (!response.ok) {
throw new Error('Error en la respuesta del servidor');
}
return response.json();
})
.then(data => {
if (data.success) {
showToast('success', `Cita marcada como ${statusText} correctamente`);
// Cerrar modal
const modal = bootstrap.Modal.getInstance(finalizeModal);
modal.hide();
// Recargar después de un breve delay
setTimeout(() => {
location.reload();
}, 1500);
} else {
throw new Error(data.message || `Error al marcar como ${statusText}`);
}
})
.catch(error => {
console.error('Error:', error);
showToast('error', error.message);
// Restaurar botón
confirmFinalizeBtn.disabled = false;
confirmFinalizeBtn.innerHTML = '<i class="fas fa-check me-2"></i>Confirmar';
});
}
});
// Reset modal cuando se cierra
finalizeModal.addEventListener('hidden.bs.modal', function () {
document.getElementById('finalizeNotes').value = '';
currentFinalizeAppointmentId = null;
currentFinalizeCsrfToken = null;
selectedStatus = null;
confirmFinalizeBtn.disabled = true;
confirmFinalizeBtn.innerHTML = '<i class="fas fa-check me-2"></i>Confirmar';
// Resetear opciones
finalizeOptions.forEach(option => {
option.classList.remove('bg-success', 'bg-warning', 'text-white');
option.classList.add('bg-light');
});
});
// ========== MODAL DE ELIMINACIÓN - VERSIÓN CORREGIDA ==========
const deleteModal = document.getElementById('deleteModal');
const deleteButtons = document.querySelectorAll('[data-bs-target="#deleteModal"]');
const deleteAppointmentInfo = document.getElementById('deleteAppointmentInfo');
const confirmDeleteBtn = document.getElementById('confirmDelete');
let currentDeleteAppointmentId = null;
let currentDeleteCsrfToken = null;
deleteButtons.forEach(button => {
button.addEventListener('click', function() {
currentDeleteAppointmentId = this.getAttribute('data-appointment-id');
const appointmentInfo = this.getAttribute('data-appointment-info');
currentDeleteCsrfToken = this.getAttribute('data-csrf-token');
deleteAppointmentInfo.textContent = appointmentInfo;
});
});
// Manejar el clic en el botón "Eliminar Permanentemente"
confirmDeleteBtn.addEventListener('click', function() {
if (!currentDeleteAppointmentId || !currentDeleteCsrfToken) {
showToast('error', 'Error: No se pudo obtener la información de la cita');
return;
}
const url = "{{ path('admin_appointment_delete', {id: 'PLACEHOLDER'}) }}".replace('PLACEHOLDER', currentDeleteAppointmentId);
// Mostrar loading en el botón y deshabilitar
confirmDeleteBtn.disabled = true;
confirmDeleteBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Eliminando...';
// Hacer la petición fetch UNA sola vez
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest'
},
body: new URLSearchParams({
'_token': currentDeleteCsrfToken
})
})
.then(response => {
if (!response.ok) {
throw new Error('Error en la respuesta del servidor');
}
return response.json();
})
.then(data => {
if (data.success) {
showToast('success', data.message || 'Cita eliminada correctamente');
// Cerrar modal
const modal = bootstrap.Modal.getInstance(deleteModal);
modal.hide();
// Recargar la página después de un breve delay
setTimeout(() => {
location.reload();
}, 1500);
} else {
throw new Error(data.message || 'Error al eliminar la cita');
}
})
.catch(error => {
console.error('Error:', error);
showToast('error', error.message || 'Error al eliminar la cita');
// Restaurar botón en caso de error
confirmDeleteBtn.disabled = false;
confirmDeleteBtn.innerHTML = '<i class="fas fa-trash me-2"></i>Eliminar Permanentemente';
});
});
// Reset modal cuando se cierra
deleteModal.addEventListener('hidden.bs.modal', function () {
currentDeleteAppointmentId = null;
currentDeleteCsrfToken = null;
confirmDeleteBtn.disabled = false;
confirmDeleteBtn.innerHTML = '<i class="fas fa-trash me-2"></i>Eliminar Permanentemente';
});
// ========== FIN MODAL DE ELIMINACIÓN ==========
// Función para mostrar toast (compartida)
function showToast(type, message) {
// Crear contenedor de toasts si no existe
let toastContainer = document.getElementById('toast-container');
if (!toastContainer) {
toastContainer = document.createElement('div');
toastContainer.id = 'toast-container';
toastContainer.className = 'toast-container position-fixed top-0 end-0 p-3';
toastContainer.style.zIndex = '1060';
document.body.appendChild(toastContainer);
}
// Crear toast
const toastId = 'toast-' + Date.now();
const toastHtml = `
<div id="${toastId}" class="toast align-items-center text-bg-${type} border-0" role="alert">
<div class="d-flex">
<div class="toast-body">
<i class="fas ${type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle'} me-2"></i>
${message}
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
</div>
</div>
`;
toastContainer.insertAdjacentHTML('beforeend', toastHtml);
// Mostrar toast
const toastElement = document.getElementById(toastId);
const toast = new bootstrap.Toast(toastElement, {
autohide: true,
delay: 5000
});
toast.show();
// Remover del DOM cuando se oculte
toastElement.addEventListener('hidden.bs.toast', function() {
this.remove();
});
}
});
</script>
{% endblock %}