// Variables globales para mantener el auto-refresh let filtrosActuales = {}; let intervaloActualizacion = null; // Datos de las canchas (similar a Reservar_Turno.js) let datosCanchas = { futbol: null, padel: null, quincho: null }; // Cargar datos de las canchas async function cargarDatosCanchastabla() { try { const response = await fetch('../php/Obtener_Canchas.php'); const data = await response.json(); const canchas = Array.isArray(data) ? data : (data && Array.isArray(data.canchas) ? data.canchas : []); if (!canchas.length) return; canchas.forEach(cancha => { const rawTipo = String(cancha.Tipo ?? cancha.tipo ?? '').trim().toUpperCase(); const tipo = rawTipo === 'F' || rawTipo === 'FUTBOL' ? 'F' : rawTipo === 'P' || rawTipo === 'PADEL' ? 'P' : rawTipo === 'Q' || rawTipo === 'QUINCHO' ? 'Q' : null; if (!tipo) return; const duracion = parseInt(cancha.Duracion ?? cancha.duracion, 10) || 0; const apertura = cancha.Hora_Apertura ?? cancha.apertura ?? null; const cierre = cancha.Hora_Cierre ?? cancha.cierre ?? null; if (tipo === 'F') { datosCanchas.futbol = { duracion, apertura, cierre }; } else if (tipo === 'P') { datosCanchas.padel = { duracion, apertura, cierre }; } }); } catch (error) { console.error('Error al cargar datos de canchas:', error); } } document.addEventListener("DOMContentLoaded", async () => { // Cargar datos de canchas await cargarDatosCanchastabla(); // Establecer la fecha de hoy por defecto const hoy = new Date().toISOString().split('T')[0]; const inputFecha = document.querySelector(".filtro-formulario input[name='fecha']"); if (inputFecha) { inputFecha.value = hoy; } cargarReservas({ fecha: hoy }); // Cargar reservas del día actual iniciarActualizacionAutomatica(); // Inicia el auto-refresh const formFiltro = document.querySelector(".filtro-formulario"); const btnReestablecer = document.getElementById("btn-reestablecer-filtros"); // Asegurar estilos para reprogramar (inyección en head si el css no está presente) ensureReprogramStyles(); // Agregar event listeners para búsqueda dinámica if (formFiltro) { const inputs = formFiltro.querySelectorAll("input, select"); inputs.forEach(input => { input.addEventListener("change", () => { aplicarFiltrosDinamicos(); }); input.addEventListener("input", () => { aplicarFiltrosDinamicos(); }); }); } // Event listener para botón reestablecer if (btnReestablecer) { btnReestablecer.addEventListener("click", (e) => { e.preventDefault(); reestablecerFiltros(); }); } }); function aplicarFiltrosDinamicos() { const formFiltro = document.querySelector(".filtro-formulario"); const formData = new FormData(formFiltro); const filtros = { fecha: formData.get("fecha"), nombre: formData.get("nombre"), dni: formData.get("dni"), tipo: formData.get("tipo_cancha") }; cargarReservas(filtros); } function reestablecerFiltros() { const formFiltro = document.querySelector(".filtro-formulario"); // Limpiar todos los inputs const inputs = formFiltro.querySelectorAll("input, select"); inputs.forEach(input => { if (input.name === "fecha") { // Establecer fecha de hoy input.value = new Date().toISOString().split('T')[0]; } else { input.value = ""; } }); // Cargar reservas del día actual const hoy = new Date().toISOString().split('T')[0]; cargarReservas({ fecha: hoy }); } function cargarReservas(filtros = {}) { // Guardar los filtros actuales para el auto-refresh (solo los que tienen valor) const filtrosConValor = {}; for (const [key, value] of Object.entries(filtros)) { if (value) { filtrosConValor[key] = value; } } if (Object.keys(filtrosConValor).length > 0) { filtrosActuales = filtrosConValor; } let url = "../php/Obtener_Reservas.php?"; const params = new URLSearchParams(); // Usar los filtros guardados si no se proporcionan nuevos const filtrosAUsar = Object.keys(filtrosConValor).length > 0 ? filtrosConValor : filtrosActuales; // Solo agregamos parámetros si tienen valor if (filtrosAUsar.fecha) params.append("fecha", filtrosAUsar.fecha); if (filtrosAUsar.nombre) params.append("nombre", filtrosAUsar.nombre); if (filtrosAUsar.dni) params.append("dni", filtrosAUsar.dni); if (filtrosAUsar.tipo) params.append("tipo", filtrosAUsar.tipo); fetch(url + params.toString()) .then(res => res.json()) .then(reservas => { const tablaBody = document.querySelector("#tabla-reservas tbody"); tablaBody.innerHTML = ""; if (reservas.length === 0) { tablaBody.innerHTML = "No se encontraron resultados."; return; } reservas.forEach(r => { const tipoTexto = r.Tipo === 'F' ? 'Fútbol' : (r.Tipo === 'P' ? 'Pádel' : (r.Tipo === 'Q' ? 'Quincho' : 'Otro')); // Convertir estado numérico a texto let estadoTexto = ''; let estadoValor = parseInt(r.Estado); if (estadoValor === 0) { estadoTexto = 'No Pagado'; } else if (estadoValor === 1) { estadoTexto = 'Pagado'; } else if (estadoValor === 2) { estadoTexto = 'Cancelado'; } // Verificar si la reserva es del pasado const fechaReserva = new Date(r.Fecha_Hora); const hoy = new Date(); hoy.setHours(0, 0, 0, 0); fechaReserva.setHours(0, 0, 0, 0); const esDelPasado = fechaReserva < hoy; const disabledAttr = esDelPasado ? 'disabled' : ''; const disabledClass = esDelPasado ? 'disabled' : ''; const fila = document.createElement("tr"); fila.setAttribute("data-id-reserva", r.ID_Reserva); fila.setAttribute("data-tipo-cancha", r.Tipo); fila.setAttribute("data-numero-cancha", r.Numero); fila.setAttribute("data-fecha-reserva", r.Fecha_Hora); fila.innerHTML = ` ${r.Nombre} ${r.DNI} ${r.Telefono} ${new Date(r.Fecha_Hora).toLocaleString()} ${tipoTexto} ${r.Numero} ${parseFloat(r.Descuento_Cupon) > 0 ? (parseFloat(r.Descuento_Cupon) * 100).toFixed(0) + '%' : '0%'} $${parseFloat(r.Monto).toFixed(2)}
`; tablaBody.appendChild(fila); }); // Agregar event listeners al dropdown agregarListenersDropdown(); agregarListenersReprogramar(); }) .catch(err => console.error("Error en fetch:", err)); } function agregarListenersDropdown() { const botonesEstado = document.querySelectorAll(".estado-btn"); botonesEstado.forEach(btn => { // Marcar la opción activa al inicio const estadoActual = btn.getAttribute("data-estado"); const dropdown = btn.closest(".estado-dropdown"); const opciones = dropdown.querySelectorAll(".estado-opcion"); opciones.forEach(op => { if (op.getAttribute("data-valor") === estadoActual) { op.classList.add("activo"); } else { op.classList.remove("activo"); } }); btn.addEventListener("click", (e) => { e.stopPropagation(); // Si el botón está deshabilitado, no hacer nada if (btn.hasAttribute("disabled") || btn.classList.contains("disabled")) { return; } const opcionesDiv = dropdown.querySelector(".estado-opciones"); // Cerrar otros dropdowns abiertos document.querySelectorAll(".estado-opciones").forEach(op => { if (op !== opcionesDiv) op.style.display = "none"; }); // Toggle del dropdown actual const estaAbierto = opcionesDiv.style.display === "block"; opcionesDiv.style.display = estaAbierto ? "none" : "block"; btn.setAttribute("aria-expanded", !estaAbierto); }); }); // Click en opciones const opcionesEstado = document.querySelectorAll(".estado-opcion"); opcionesEstado.forEach(opcion => { opcion.addEventListener("click", (e) => { e.stopPropagation(); const dropdown = opcion.closest(".estado-dropdown"); const btn = dropdown.querySelector(".estado-btn"); const textoSpan = btn.querySelector(".estado-texto"); const opcionesDiv = dropdown.querySelector(".estado-opciones"); const nuevoValor = opcion.getAttribute("data-valor"); const textoOpcion = opcion.textContent; // Remover clase activo de todas las opciones dropdown.querySelectorAll(".estado-opcion").forEach(op => { op.classList.remove("activo"); }); // Agregar clase activo a la opción seleccionada opcion.classList.add("activo"); // Actualizar el botón textoSpan.textContent = textoOpcion; btn.setAttribute("data-estado", nuevoValor); // Ocultar dropdown opcionesDiv.style.display = "none"; btn.setAttribute("aria-expanded", false); // Guardar cambio en BD const fila = opcion.closest("tr"); const idReserva = fila.getAttribute("data-id-reserva"); actualizarEstadoReserva(idReserva, parseInt(nuevoValor)); }); }); // Cerrar dropdown si se hace clic fuera document.addEventListener("click", (e) => { if (!e.target.closest(".estado-dropdown")) { document.querySelectorAll(".estado-opciones").forEach(op => { op.style.display = "none"; }); document.querySelectorAll(".estado-btn").forEach(btn => { btn.setAttribute("aria-expanded", false); }); } }); } function actualizarEstadoReserva(idReserva, nuevoEstado) { fetch("../php/Actualizar_Estado_Reserva.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id_reserva: idReserva, estado: nuevoEstado }) }) .then(res => res.json()) .then(data => { if (data.success) { console.log("Estado actualizado correctamente"); } else { alert("Error al actualizar: " + data.error); // Recargar para mostrar el estado anterior cargarReservas(); } }) .catch(err => { console.error("Error:", err); alert("Error al actualizar el estado"); cargarReservas(); }); } // Función para iniciar la actualización automática function iniciarActualizacionAutomatica() { // Actualizar cada 5 segundos (5000 milisegundos) intervaloActualizacion = setInterval(() => { cargarReservas(); }, 5000); } // Función para detener la actualización automática (opcional, si es necesario) function detenerActualizacionAutomatica() { if (intervaloActualizacion) { clearInterval(intervaloActualizacion); intervaloActualizacion = null; } } // Listeners y modal para reprogramar function agregarListenersReprogramar() { const botones = document.querySelectorAll('.reprogramar-btn'); botones.forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); if (btn.hasAttribute('disabled') || btn.classList.contains('disabled')) return; const fila = btn.closest('tr'); const idReserva = fila.getAttribute('data-id-reserva'); crearModalReprogramar(idReserva, fila); }); }); } function crearModalReprogramar(idReserva, fila) { // Obtener datos de la reserva const tipoCancha = fila.getAttribute('data-tipo-cancha'); // 'F' o 'P' const fechaActualReserva = fila.getAttribute('data-fecha-reserva'); // Crear overlay const overlay = document.createElement('div'); overlay.className = 'reprogramar-overlay'; const modal = document.createElement('div'); modal.className = 'reprogramar-modal'; modal.innerHTML = `
`; overlay.appendChild(modal); document.body.appendChild(overlay); // Generar los próximos 7 días const diasGrid = modal.querySelector('.dias-grid'); const hoy = new Date(); for (let i = 0; i < 7; i++) { const dia = new Date(hoy); dia.setDate(hoy.getDate() + i); const btn = document.createElement('button'); btn.className = 'dia-btn'; const options = { weekday: 'short', day: 'numeric', month: 'numeric' }; btn.textContent = `${dia.toLocaleDateString(undefined, { weekday: 'short' })}\n${dia.getDate()}/${dia.getMonth()+1}`; btn.dataset.iso = dia.toISOString().split('T')[0]; diasGrid.appendChild(btn); } let diaSeleccionado = null; let horaSeleccionada = null; let canchaSeleccionada = null; // Handlers selección de día modal.querySelectorAll('.dia-btn').forEach(d => { d.addEventListener('click', async () => { modal.querySelectorAll('.dia-btn').forEach(x => x.classList.remove('selected')); d.classList.add('selected'); diaSeleccionado = d.dataset.iso; // Generar horarios cuando se selecciona un día await generarHorariosReprogramar(modal, tipoCancha, diaSeleccionado); // Limpiar selección de horario y cancha horaSeleccionada = null; canchaSeleccionada = null; actualizarEstadoBotonConfirm(modal, diaSeleccionado, horaSeleccionada, canchaSeleccionada); }); }); // Delegation para horarios (se generan dinámicamente) modal.addEventListener('click', async (e) => { if (e.target.classList.contains('horario-btn') && !e.target.disabled) { modal.querySelectorAll('.horario-btn').forEach(x => x.classList.remove('selected')); e.target.classList.add('selected'); horaSeleccionada = e.target.dataset.hora; canchaSeleccionada = null; // Generar selección de canchas await generarSeleccionCanchasReprogramar(modal, tipoCancha, diaSeleccionado, horaSeleccionada); actualizarEstadoBotonConfirm(modal, diaSeleccionado, horaSeleccionada, canchaSeleccionada); } }); // Delegation para canchas modal.addEventListener('click', (e) => { if (e.target.classList.contains('cancha-btn') && !e.target.disabled) { modal.querySelectorAll('.cancha-btn').forEach(x => x.classList.remove('selected')); e.target.classList.add('selected'); canchaSeleccionada = e.target.dataset.numero; actualizarEstadoBotonConfirm(modal, diaSeleccionado, horaSeleccionada, canchaSeleccionada); } }); // Cancelar modal.querySelector('.btn-cancelar').addEventListener('click', () => { closeModalReprogramar(overlay); }); // Confirmar modal.querySelector('.btn-confirmar').addEventListener('click', () => { if (!diaSeleccionado || !horaSeleccionada || !canchaSeleccionada) return; const fechaHora = diaSeleccionado + ' ' + horaSeleccionada; reprogramarReserva(idReserva, fechaHora, overlay); }); // Cerrar al hacer click fuera del modal overlay.addEventListener('click', (e) => { if (e.target === overlay) closeModalReprogramar(overlay); }); } // Generar horarios para reprogramación async function generarHorariosReprogramar(modal, tipoCancha, fechaSeleccionada) { const horariosGrid = modal.querySelector('.horarios-grid'); horariosGrid.innerHTML = ''; const tipoCode = tipoCancha === 'F' ? 'futbol' : 'padel'; const datos = datosCanchas[tipoCode]; if (!datos || !datos.duracion || !datos.apertura || !datos.cierre) { horariosGrid.innerHTML = '

Error: No se pudieron cargar los datos de las canchas

'; return; } const duracion = datos.duracion; const [aperturaH, aperturaM] = datos.apertura.split(':').map(Number); const [cierreH, cierreM] = datos.cierre.split(':').map(Number); const aperturaTotalMin = aperturaH * 60 + aperturaM; const cierreTotalMin = cierreH * 60 + cierreM; const ahora = new Date(); const [selYear, selMonth, selDay] = fechaSeleccionada.split('-').map(Number); const fechaSelDate = new Date(selYear, selMonth - 1, selDay); const hoy = new Date(ahora.getFullYear(), ahora.getMonth(), ahora.getDate()); const esHoy = fechaSelDate.getTime() === hoy.getTime(); const minutoActual = ahora.getHours() * 60 + ahora.getMinutes(); let horaActualMin = aperturaTotalMin; while (horaActualMin + duracion <= cierreTotalMin) { const horas = Math.floor(horaActualMin / 60); const minutos = horaActualMin % 60; const horaFin = Math.floor((horaActualMin + duracion) / 60); const minutosFin = (horaActualMin + duracion) % 60; const inicio = `${String(horas).padStart(2, '0')}:${String(minutos).padStart(2, '0')}`; const fin = `${String(horaFin).padStart(2, '0')}:${String(minutosFin).padStart(2, '0')}`; const inicioMin = horas * 60 + minutos; const esPasado = fechaSelDate < hoy || (esHoy && inicioMin < minutoActual); const btn = document.createElement('button'); btn.className = 'horario-btn'; btn.textContent = `${inicio}\n-\n${fin}`; btn.dataset.hora = `${String(horas).padStart(2, '0')}:${String(minutos).padStart(2, '0')}:00`; // Verificar disponibilidad let disponibles = 0; if (!esPasado) { disponibles = await obtenerDisponibilidadCanchaReprogramar( fechaSeleccionada, inicio, duracion, tipoCancha ); } // Desactivar si es pasado o no hay canchas disponibles if (esPasado || disponibles === 0) { btn.disabled = true; btn.classList.add('no-disponible'); btn.style.opacity = '0.5'; btn.style.cursor = 'not-allowed'; } horariosGrid.appendChild(btn); horaActualMin += duracion; } // Limpiar grid de canchas modal.querySelector('.canchas-grid').innerHTML = ''; } // Obtener disponibilidad de cancha para reprogramación async function obtenerDisponibilidadCanchaReprogramar(fecha, horario, duracion, tipo) { try { const response = await fetch( `../php/Obtener_Disponibilidad_Cancha.php?fecha=${fecha}&horario=${horario}&duracion=${duracion}&tipo=${tipo}` ); const data = await response.json(); return data.success ? data.disponibles : 0; } catch (error) { console.error('Error obteniendo disponibilidad:', error); return 0; } } // Generar selección de canchas disponibles async function generarSeleccionCanchasReprogramar(modal, tipoCancha, fecha, horario) { const canchasGrid = modal.querySelector('.canchas-grid'); canchasGrid.innerHTML = ''; const tipoCode = tipoCancha === 'F' ? 'futbol' : 'padel'; const datos = datosCanchas[tipoCode]; if (!datos || !datos.duracion) { canchasGrid.innerHTML = '

Error: No se pudieron cargar los datos

'; return; } try { const response = await fetch( `../php/Obtener_Canchas_Numeros.php?fecha=${fecha}&horario=${horario}&duracion=${datos.duracion}&tipo=${tipoCancha}` ); const data = await response.json(); if (data.success && data.canchas) { data.canchas.forEach(cancha => { const btn = document.createElement('button'); btn.className = 'cancha-btn'; btn.textContent = `Cancha ${cancha.numero}`; btn.dataset.numero = cancha.numero; if (!cancha.disponible) { btn.disabled = true; btn.classList.add('no-disponible'); btn.style.opacity = '0.5'; btn.style.cursor = 'not-allowed'; } canchasGrid.appendChild(btn); }); } } catch (error) { console.error('Error generando selección de canchas:', error); canchasGrid.innerHTML = '

Error al cargar canchas disponibles

'; } } function actualizarEstadoBotonConfirm(modal, dia, hora, cancha) { const btn = modal.querySelector('.btn-confirmar'); if (dia && hora && cancha) { btn.disabled = false; } else { btn.disabled = true; } } function closeModalReprogramar(overlay) { if (overlay && overlay.parentNode) overlay.parentNode.removeChild(overlay); } function reprogramarReserva(idReserva, fechaHora, overlay) { fetch('../php/Actualizar_Horario_Reserva.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id_reserva: idReserva, fecha_hora: fechaHora }) }) .then(async res => { if (!res.ok) throw new Error('HTTP ' + res.status); const text = await res.text(); try { return JSON.parse(text); } catch (error) { throw new Error('Respuesta no JSON: ' + text.trim().replace(/\s+/g, ' ')); } }) .then(data => { if (data.success) { closeModalReprogramar(overlay); cargarReservas(); alert('Reserva reprogramada correctamente'); } else { alert('Error al reprogramar: ' + (data.error || 'Error desconocido')); } }) .catch(err => { console.error('Error reprogramando:', err); alert('Error al reprogramar la reserva: ' + err.message); }); } // Inyecta estilos mínimos si no están presentes en el CSS cargado function ensureReprogramStyles() { if (document.getElementById('reprogramar-styles')) return; const css = ` .reprogramar-btn{margin-left:8px;padding:6px 8px;border-radius:6px;background:rgba(30,125,30,0.12);border:1px solid rgba(30,125,30,0.2);color:#d9f2d9;cursor:pointer;font-weight:600} .reprogramar-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:9999;padding:20px} .reprogramar-modal{width:min(980px,96%);max-height:90vh;overflow:auto;background:#111;border-radius:12px;padding:18px;border:1px solid rgba(30,125,30,0.18)} .dias-grid{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:14px}.dia-btn{width:110px;height:70px;border-radius:10px;padding:8px;background:rgba(255,255,255,0.03);border:2px solid rgba(255,255,255,0.08);color:#fff;font-weight:700;cursor:pointer;white-space:pre-line;display:flex;flex-direction:column;align-items:center;justify-content:center} .dia-btn.selected{background:linear-gradient(135deg,#1e7d1e,#145214);border-color:#1e7d1e} .horarios-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:12px;margin-bottom:14px}.horario-btn{padding:12px;border-radius:10px;background:rgba(255,255,255,0.03);border:2px solid rgba(255,255,255,0.08);color:#fff;font-weight:700;cursor:pointer;white-space:pre-line} .horario-btn.selected{background:linear-gradient(135deg,#1e7d1e,#145214);border-color:#1e7d1e} .horario-btn.no-disponible{opacity:0.4 !important;cursor:not-allowed !important} .canchas-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:12px;margin-bottom:14px}.cancha-btn{padding:12px;border-radius:10px;background:rgba(255,255,255,0.03);border:2px solid rgba(255,255,255,0.08);color:#fff;font-weight:700;cursor:pointer} .cancha-btn.selected{background:linear-gradient(135deg,#1e7d1e,#145214);border-color:#1e7d1e} .cancha-btn.no-disponible{opacity:0.4 !important;cursor:not-allowed !important} .reprogramar-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}.reprogramar-actions .btn-cancelar,.reprogramar-actions .btn-confirmar{padding:10px 14px;border-radius:8px;font-weight:700;border:2px solid rgba(255,255,255,0.08);background:rgba(255,255,255,0.03);color:#fff} .reprogramar-actions .btn-confirmar:disabled{opacity:0.5;cursor:not-allowed} `; const style = document.createElement('style'); style.id = 'reprogramar-styles'; style.appendChild(document.createTextNode(css)); document.head.appendChild(style); } // Delegación: por si los listeners directos no se añadieron (tabla re-renderizada) document.addEventListener('click', (e) => { const btn = e.target.closest && e.target.closest('.reprogramar-btn'); if (!btn) return; e.stopPropagation(); if (btn.hasAttribute('disabled') || btn.classList.contains('disabled')) return; // evitar crear múltiples overlays if (document.querySelector('.reprogramar-overlay')) return; const fila = btn.closest('tr'); const idReserva = fila ? fila.getAttribute('data-id-reserva') : null; if (idReserva) crearModalReprogramar(idReserva, fila); });