Actualizar asignados.html
This commit is contained in:
208
asignados.html
208
asignados.html
@@ -28,7 +28,7 @@
|
||||
|
||||
/* Carrusel de fechas */
|
||||
.day-chip { transition: all 0.2s; border: 2px solid transparent; }
|
||||
.day-chip.active { background-color: var(--primary) !important; color: white !important; border-color: var(--primary) !important; transform: scale(1.05); box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); }
|
||||
.day-chip.active { background-color: var(--primary) !important; color: white !important; border-color: var(--primary) !important; transform: scale(1.05); shadow: 0 4px 6px -1px rgba(0,0,0,0.1); }
|
||||
.day-chip.inactive { background-color: #f8fafc; color: #64748b; border-color: #e2e8f0; }
|
||||
|
||||
/* Chips de Horas */
|
||||
@@ -182,8 +182,9 @@
|
||||
<button onclick="closeModal('approveModal', 'approveModalContent')" class="w-8 h-8 bg-slate-100 rounded-full flex items-center justify-center text-slate-500 hover:text-red-500"><i data-lucide="x" class="w-4 h-4"></i></button>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-50 border border-slate-200 p-5 rounded-2xl mb-6 shadow-inner text-center">
|
||||
<p class="text-[10px] font-black text-emerald-600 uppercase tracking-widest mb-1">Fecha Solicitada por el Cliente</p>
|
||||
<div class="bg-slate-50 border-2 border-primary-dynamic p-5 rounded-2xl mb-6 shadow-inner text-center relative overflow-hidden">
|
||||
<div class="absolute left-0 top-0 bottom-0 w-2 bg-primary-dynamic"></div>
|
||||
<p class="text-[10px] font-black text-primary-dynamic uppercase tracking-widest mb-1">Fecha Solicitada por el Cliente</p>
|
||||
<h4 class="text-2xl font-black text-slate-800" id="appDate">--/--/----</h4>
|
||||
<p class="text-sm font-bold text-slate-600 mt-1" id="appTime">--:--</p>
|
||||
</div>
|
||||
@@ -193,7 +194,7 @@
|
||||
<div class="mb-8">
|
||||
<p class="text-[10px] font-black text-slate-500 uppercase tracking-widest mb-2 ml-1">¿Cuánto tiempo vas a tardar?</p>
|
||||
<div class="relative">
|
||||
<select id="appDurationInput" class="w-full bg-slate-50 border border-slate-200 p-4 rounded-xl text-sm font-black text-slate-700 outline-none focus:ring-2 focus:ring-emerald-500 appearance-none pr-10">
|
||||
<select id="appDurationInput" class="w-full bg-slate-50 border border-slate-200 p-4 rounded-xl text-sm font-black text-slate-700 outline-none focus:ring-2 focus:ring-primary-dynamic appearance-none pr-10">
|
||||
<option value="15">15 Minutos (Muy rápido)</option>
|
||||
<option value="30">30 Minutos</option>
|
||||
<option value="45">45 Minutos</option>
|
||||
@@ -211,7 +212,7 @@
|
||||
<button onclick="rejectRequest()" id="btnReject" class="bg-white border-2 border-rose-200 text-rose-600 font-black py-4 rounded-xl hover:bg-rose-50 flex items-center justify-center gap-2 transition-all text-xs uppercase tracking-widest">
|
||||
<i data-lucide="x" class="w-4 h-4"></i> Rechazar
|
||||
</button>
|
||||
<button onclick="approveRequest()" id="btnApprove" class="bg-emerald-500 text-white font-black py-4 rounded-xl shadow-lg shadow-emerald-500/30 hover:bg-emerald-600 flex items-center justify-center gap-2 transition-all text-xs uppercase tracking-widest">
|
||||
<button onclick="approveRequest()" id="btnApprove" class="bg-primary-dynamic text-white font-black py-4 rounded-xl shadow-lg hover:opacity-90 flex items-center justify-center gap-2 transition-all text-xs uppercase tracking-widest">
|
||||
<i data-lucide="check" class="w-4 h-4"></i> Aceptar
|
||||
</button>
|
||||
</div>
|
||||
@@ -229,9 +230,9 @@
|
||||
? 'http://localhost:3000'
|
||||
: 'https://integrarepara-api.integrarepara.es';
|
||||
|
||||
let localServices = []; // Solo los "Sin Cita"
|
||||
let globalAllServices = []; // TODOS para solapamientos
|
||||
let pendingRequests = []; // Solicitudes del portal
|
||||
let localServices = [];
|
||||
let globalAllServices = [];
|
||||
let pendingRequests = [];
|
||||
let systemStatuses = [];
|
||||
let bizHours = { m_start: '09:00', m_end: '14:00', a_start: '16:00', a_end: '19:00' };
|
||||
|
||||
@@ -239,7 +240,6 @@
|
||||
let pickerSelectedDate = "";
|
||||
let pickerSelectedTime = "";
|
||||
|
||||
// --- SISTEMA DE TEMA DINÁMICO ---
|
||||
async function applyTheme() {
|
||||
try {
|
||||
let theme = JSON.parse(localStorage.getItem('app_theme'));
|
||||
@@ -261,7 +261,7 @@
|
||||
document.documentElement.style.setProperty('--secondary', theme.secondary);
|
||||
document.documentElement.style.setProperty('--app-bg', theme.bg);
|
||||
}
|
||||
} catch (e) { console.warn("Usando tema y horario por defecto"); }
|
||||
} catch (e) { console.warn("Usando tema por defecto"); }
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
@@ -279,7 +279,7 @@
|
||||
const res = await fetch(`${API_URL}/statuses`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
|
||||
const data = await res.json();
|
||||
if (data.ok) systemStatuses = data.statuses;
|
||||
} catch (e) { console.error("Error estados:", e); }
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
async function refreshData() {
|
||||
@@ -289,11 +289,9 @@
|
||||
try {
|
||||
const headers = { "Authorization": `Bearer ${localStorage.getItem("token")}` };
|
||||
|
||||
// 1. Cargar servicios activos del operario
|
||||
const resActivos = await fetch(`${API_URL}/services/active`, { headers });
|
||||
const dataActivos = await resActivos.json();
|
||||
|
||||
// 2. Cargar peticiones de cita pendientes de este operario
|
||||
const resReqs = await fetch(`${API_URL}/agenda/requests`, { headers });
|
||||
const dataReqs = await resReqs.json();
|
||||
|
||||
@@ -301,11 +299,9 @@
|
||||
globalAllServices = dataActivos.services;
|
||||
pendingRequests = dataReqs.requests;
|
||||
|
||||
// Filtrar la lista normal de "Sin Cita"
|
||||
localServices = globalAllServices.filter(s => {
|
||||
if (s.provider === 'SYSTEM_BLOCK') return false;
|
||||
const raw = s.raw_data || {};
|
||||
// Si ya tiene fecha o si tiene una solicitud pendiente, no lo mostramos en el listado base
|
||||
if (raw.scheduled_date && raw.scheduled_date.trim() !== "") return false;
|
||||
if (raw.appointment_status === 'pending') return false;
|
||||
|
||||
@@ -348,21 +344,19 @@
|
||||
const rDate = formatDate(raw.requested_date);
|
||||
const rTime = addOneHour(raw.requested_time);
|
||||
|
||||
// Extraer descripción y teléfono
|
||||
const desc = raw["Descripción"] || raw["DESCRIPCION"] || "Sin notas de avería.";
|
||||
const rawPhone = raw["Teléfono"] || raw["TELEFONOS"] || raw["TELEFONO"] || "";
|
||||
const matchPhone = rawPhone.toString().match(/[6789]\d{8}/);
|
||||
const safePhone = matchPhone ? matchPhone[0] : "";
|
||||
|
||||
// DISEÑO ACTUALIZADO CON ACENTOS VERDES
|
||||
reqList.innerHTML += `
|
||||
<div class="bg-white border-2 border-emerald-400 p-5 rounded-[2rem] flex flex-col shadow-md relative overflow-hidden gap-3">
|
||||
<div class="absolute right-0 top-0 w-24 h-24 bg-emerald-50 rounded-bl-full opacity-60 z-0"></div>
|
||||
<div class="bg-white border-2 border-primary-dynamic p-5 rounded-[2rem] flex flex-col shadow-md relative overflow-hidden gap-3">
|
||||
<div class="absolute right-0 top-0 w-24 h-24 bg-primary-dynamic rounded-bl-full opacity-10 z-0"></div>
|
||||
|
||||
<div class="relative z-10">
|
||||
<p class="text-[9px] font-black text-emerald-600 uppercase tracking-widest mb-0.5">Cita Solicitada</p>
|
||||
<p class="text-[9px] font-black text-primary-dynamic uppercase tracking-widest mb-0.5">Cita Solicitada</p>
|
||||
<h3 class="font-black text-slate-800 text-base leading-tight truncate pr-8">${name}</h3>
|
||||
<p class="text-[10px] font-bold text-slate-600 mt-1 flex items-center gap-1"><i data-lucide="calendar" class="w-3 h-3 text-emerald-500"></i> ${rDate} | ${raw.requested_time} - ${rTime}</p>
|
||||
<p class="text-[10px] font-bold text-slate-600 mt-1 flex items-center gap-1"><i data-lucide="calendar" class="w-3 h-3 text-primary-dynamic"></i> ${rDate} | ${raw.requested_time} - ${rTime}</p>
|
||||
</div>
|
||||
|
||||
<div class="relative z-10 bg-slate-50 p-3 rounded-2xl border border-slate-100 mt-1">
|
||||
@@ -370,10 +364,10 @@
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 relative z-10 mt-1">
|
||||
<button onclick="quickCallInline('${safePhone}')" class="flex-1 bg-white border border-slate-200 text-slate-600 hover:bg-slate-50 font-black py-3 rounded-xl flex items-center justify-center gap-1.5 active:scale-95 transition-transform shadow-sm text-[10px] uppercase tracking-widest">
|
||||
<button onclick="quickCallInline('${safePhone}')" class="flex-1 bg-white border border-primary-dynamic text-primary-dynamic hover:bg-slate-50 font-black py-3 rounded-xl flex items-center justify-center gap-1.5 active:scale-95 transition-transform shadow-sm text-[10px] uppercase tracking-widest">
|
||||
<i data-lucide="phone" class="w-4 h-4"></i> Llamar
|
||||
</button>
|
||||
<button onclick="openApproveModal(${req.id})" class="flex-1 bg-emerald-500 text-white hover:bg-emerald-600 font-black py-3 rounded-xl flex items-center justify-center gap-1.5 shadow-md active:scale-95 transition-transform text-[10px] uppercase tracking-widest">
|
||||
<button onclick="openApproveModal(${req.id})" class="flex-1 bg-primary-dynamic text-white hover:opacity-90 font-black py-3 rounded-xl flex items-center justify-center gap-1.5 shadow-md active:scale-95 transition-transform text-[10px] uppercase tracking-widest">
|
||||
Revisar <i data-lucide="chevron-right" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -383,10 +377,9 @@
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
// Helper rápido para el botón de llamada
|
||||
function quickCallInline(phone) {
|
||||
if (phone) window.location.href = `tel:+34${phone}`;
|
||||
else alert("No hay un teléfono válido guardado para este cliente.");
|
||||
else alert("No hay un teléfono válido guardado.");
|
||||
}
|
||||
|
||||
function openApproveModal(id) {
|
||||
@@ -400,8 +393,6 @@
|
||||
|
||||
document.getElementById('appDate').innerText = formatDate(raw.requested_date);
|
||||
document.getElementById('appTime').innerText = `Llegada aprox: ${raw.requested_time} - ${addOneHour(raw.requested_time)}`;
|
||||
|
||||
// Reseteamos el selector de duración a 1 hora por defecto
|
||||
document.getElementById('appDurationInput').value = "60";
|
||||
|
||||
const modal = document.getElementById('approveModal');
|
||||
@@ -415,7 +406,6 @@
|
||||
async function approveRequest() {
|
||||
const id = document.getElementById('appId').value;
|
||||
const duration = document.getElementById('appDurationInput').value;
|
||||
|
||||
const btn = document.getElementById('btnApprove');
|
||||
const originalContent = btn.innerHTML;
|
||||
btn.innerHTML = `<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i>`;
|
||||
@@ -427,26 +417,17 @@
|
||||
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
|
||||
body: JSON.stringify({ duration: duration })
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
showToast("Cita Aceptada");
|
||||
closeModal('approveModal', 'approveModalContent');
|
||||
refreshData();
|
||||
} else {
|
||||
alert("Error al confirmar.");
|
||||
}
|
||||
} catch (e) {
|
||||
alert("Error de conexión");
|
||||
} finally {
|
||||
btn.innerHTML = originalContent;
|
||||
btn.disabled = false;
|
||||
lucide.createIcons();
|
||||
}
|
||||
} else { alert("Error al confirmar."); }
|
||||
} catch (e) { alert("Error de conexión"); }
|
||||
finally { btn.innerHTML = originalContent; btn.disabled = false; lucide.createIcons(); }
|
||||
}
|
||||
|
||||
async function rejectRequest() {
|
||||
if(!confirm("¿Seguro que quieres rechazar este horario? El cliente recibirá un mensaje para elegir otro.")) return;
|
||||
|
||||
const id = document.getElementById('appId').value;
|
||||
const btn = document.getElementById('btnReject');
|
||||
const originalContent = btn.innerHTML;
|
||||
@@ -458,26 +439,27 @@
|
||||
method: 'POST',
|
||||
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
showToast("Cita Rechazada");
|
||||
closeModal('approveModal', 'approveModalContent');
|
||||
refreshData();
|
||||
} else {
|
||||
alert("Error al rechazar.");
|
||||
}
|
||||
} catch (e) {
|
||||
alert("Error de conexión");
|
||||
} finally {
|
||||
btn.innerHTML = originalContent;
|
||||
btn.disabled = false;
|
||||
lucide.createIcons();
|
||||
}
|
||||
} else { alert("Error al rechazar."); }
|
||||
} catch (e) { alert("Error de conexión"); }
|
||||
finally { btn.innerHTML = originalContent; btn.disabled = false; lucide.createIcons(); }
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// RENDERIZADO DE LISTADO NORMAL (SIN CITA)
|
||||
// ==========================================
|
||||
function calculateDelayDays(createdAtStr) {
|
||||
if (!createdAtStr) return 0;
|
||||
const createdDate = new Date(createdAtStr);
|
||||
const now = new Date();
|
||||
const diffTime = Math.abs(now - createdDate);
|
||||
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
||||
return diffDays;
|
||||
}
|
||||
|
||||
function renderServices() {
|
||||
const container = document.getElementById('servicesList');
|
||||
const noDateSec = document.getElementById('noDateSection');
|
||||
@@ -492,12 +474,8 @@
|
||||
<p class="text-xs text-slate-400 font-medium mt-1">No tienes avisos pendientes de agendar.</p>
|
||||
</div>`;
|
||||
lucide.createIcons();
|
||||
|
||||
if (pendingRequests.length === 0) {
|
||||
noDateSec.querySelector('h2').classList.add('hidden');
|
||||
} else {
|
||||
noDateSec.classList.add('hidden');
|
||||
}
|
||||
if (pendingRequests.length === 0) noDateSec.querySelector('h2').classList.add('hidden');
|
||||
else noDateSec.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -511,23 +489,39 @@
|
||||
const pop = raw["Población"] || "";
|
||||
const isUrgent = s.is_urgent;
|
||||
const company = raw["Compañía"] || raw["Procedencia"] || "Particular";
|
||||
|
||||
const calledTimes = parseInt(raw.called_times || 0);
|
||||
const delayDays = calculateDelayDays(s.created_at);
|
||||
|
||||
let delayHtml = '';
|
||||
if (delayDays > 1) {
|
||||
delayHtml = `<span class="bg-rose-100 text-rose-600 text-[8px] font-black px-2 py-0.5 rounded uppercase tracking-widest ml-2">Retraso: ${delayDays} días</span>`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div onclick="openActionModal(${s.id})" class="bg-white p-5 rounded-3xl border border-slate-200 shadow-sm active:scale-95 transition-transform flex gap-4 items-center relative overflow-hidden">
|
||||
<div class="bg-white p-5 rounded-3xl border border-slate-200 shadow-sm relative overflow-hidden mb-4">
|
||||
${isUrgent ? '<div class="absolute top-0 right-0 bg-red-500 text-white text-[8px] font-black px-3 py-1 rounded-bl-xl uppercase tracking-widest shadow-sm z-10">Urgente</div>' : ''}
|
||||
|
||||
<div class="w-12 h-12 bg-primary-dynamic/10 rounded-2xl flex flex-col items-center justify-center text-primary-dynamic shrink-0 border border-primary-dynamic/20">
|
||||
<i data-lucide="clock" class="w-5 h-5"></i>
|
||||
<div onclick="openActionModal(${s.id})" class="flex gap-4 items-center active:scale-95 transition-transform cursor-pointer">
|
||||
<div class="w-12 h-12 bg-primary-dynamic/10 rounded-2xl flex flex-col items-center justify-center text-primary-dynamic shrink-0 border border-primary-dynamic/20">
|
||||
<i data-lucide="clock" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-[9px] font-black text-blue-500 uppercase tracking-widest mb-0.5 truncate">${company} ${delayHtml}</p>
|
||||
<h3 class="font-black text-slate-800 text-base uppercase leading-tight truncate">${name}</h3>
|
||||
<p class="text-[10px] font-bold text-slate-500 mt-1 truncate uppercase flex items-center gap-1">
|
||||
<i data-lucide="map-pin" class="w-3 h-3"></i> ${addr}, ${pop}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-slate-300 shrink-0 pr-2">
|
||||
<i data-lucide="chevron-right"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-[9px] font-black text-blue-500 uppercase tracking-widest mb-0.5 truncate">${company}</p>
|
||||
<h3 class="font-black text-slate-800 text-base uppercase leading-tight truncate">${name}</h3>
|
||||
<p class="text-[10px] font-bold text-slate-500 mt-1 truncate uppercase flex items-center gap-1">
|
||||
<i data-lucide="map-pin" class="w-3 h-3"></i> ${addr}, ${pop}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-slate-300 shrink-0 pr-2">
|
||||
<i data-lucide="chevron-right"></i>
|
||||
|
||||
<div class="mt-4 pt-4 border-t border-slate-100">
|
||||
<button onclick="markClientNotFound(${s.id}, ${calledTimes})" class="w-full bg-orange-50 border border-orange-100 hover:bg-orange-100 text-orange-600 font-black py-2.5 rounded-xl flex items-center justify-center gap-2 transition-colors text-[10px] uppercase tracking-widest">
|
||||
<i data-lucide="bell-ring" class="w-4 h-4"></i> No Localizado (${calledTimes})
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -536,6 +530,34 @@
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
async function markClientNotFound(serviceId, currentTimes) {
|
||||
const btn = event.currentTarget;
|
||||
const originalHtml = btn.innerHTML;
|
||||
btn.innerHTML = `<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Enviando...`;
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/services/not-found/${serviceId}`, {
|
||||
method: 'POST',
|
||||
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
if (data.ok) {
|
||||
showToast(`Aviso enviado. Intentos: ${data.called_times}`);
|
||||
refreshData(); // Recargamos para actualizar el numerito
|
||||
} else {
|
||||
alert("Error al registrar la llamada.");
|
||||
btn.innerHTML = originalHtml;
|
||||
btn.disabled = false;
|
||||
}
|
||||
} catch (e) {
|
||||
alert("Error de conexión");
|
||||
btn.innerHTML = originalHtml;
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// LÓGICA DEL MOTOR DE AGENDAMIENTO INTELIGENTE
|
||||
// ==========================================
|
||||
@@ -546,17 +568,8 @@
|
||||
return `${y}-${m}-${d}`;
|
||||
}
|
||||
|
||||
function timeToMins(t) {
|
||||
let [h, m] = t.split(':').map(Number);
|
||||
return h * 60 + m;
|
||||
}
|
||||
|
||||
function minsToTime(m) {
|
||||
let h = Math.floor(m / 60).toString().padStart(2, '0');
|
||||
let min = (m % 60).toString().padStart(2, '0');
|
||||
return `${h}:${min}`;
|
||||
}
|
||||
|
||||
function timeToMins(t) { let [h, m] = t.split(':').map(Number); return h * 60 + m; }
|
||||
function minsToTime(m) { let h = Math.floor(m / 60).toString().padStart(2, '0'); let min = (m % 60).toString().padStart(2, '0'); return `${h}:${min}`; }
|
||||
function addOneHour(timeStr) {
|
||||
if(!timeStr) return "";
|
||||
let [h, m] = timeStr.split(':').map(Number);
|
||||
@@ -565,7 +578,6 @@
|
||||
let newM = totalMins % 60;
|
||||
return `${String(newH).padStart(2,'0')}:${String(newM).padStart(2,'0')}`;
|
||||
}
|
||||
|
||||
function formatDate(dateStr) {
|
||||
if (!dateStr) return "";
|
||||
try {
|
||||
@@ -582,19 +594,16 @@
|
||||
container.innerHTML = "";
|
||||
let today = new Date();
|
||||
|
||||
for (let i = 0; i < 14; i++) { // 14 días a futuro
|
||||
for (let i = 0; i < 14; i++) {
|
||||
let d = new Date(today);
|
||||
d.setDate(today.getDate() + i);
|
||||
|
||||
if (d.getDay() === 0) continue; // Saltar domingos
|
||||
if (d.getDay() === 0) continue;
|
||||
|
||||
const isoDate = toISODate(d);
|
||||
const dayName = d.toLocaleDateString('es-ES', { weekday: 'short' }).replace('.', '').substring(0,3);
|
||||
const dayNum = d.getDate();
|
||||
|
||||
// Seleccionar hoy por defecto
|
||||
if (!pickerSelectedDate) pickerSelectedDate = isoDate;
|
||||
|
||||
const isSelected = isoDate === pickerSelectedDate;
|
||||
|
||||
container.innerHTML += `
|
||||
@@ -608,15 +617,15 @@
|
||||
|
||||
function selectPickerDate(isoDate) {
|
||||
pickerSelectedDate = isoDate;
|
||||
pickerSelectedTime = ""; // Resetear hora al cambiar de día
|
||||
buildDayCarousel(); // Repintar para actualizar estilos
|
||||
pickerSelectedTime = "";
|
||||
buildDayCarousel();
|
||||
renderTimeSlots();
|
||||
checkSaveButton();
|
||||
}
|
||||
|
||||
function selectPickerTime(timeStr) {
|
||||
pickerSelectedTime = timeStr;
|
||||
renderTimeSlots(); // Repintar para estilos
|
||||
renderTimeSlots();
|
||||
checkSaveButton();
|
||||
}
|
||||
|
||||
@@ -648,7 +657,6 @@
|
||||
|
||||
function isOverlapping(startMins, endMins, occupiedRanges) {
|
||||
for(let r of occupiedRanges) {
|
||||
// Hay solapamiento si mi inicio es antes de que él acabe, Y mi fin es después de que él empiece
|
||||
if(startMins < r.end && endMins > r.start) return true;
|
||||
}
|
||||
return false;
|
||||
@@ -663,12 +671,10 @@
|
||||
grid.innerHTML = "";
|
||||
let slotsGenerated = 0;
|
||||
|
||||
// Función interna para recorrer tramos
|
||||
const genSlotsForPeriod = (startStr, endStr) => {
|
||||
let startMins = timeToMins(startStr);
|
||||
let endMins = timeToMins(endStr);
|
||||
|
||||
// Saltos de 30 minutos (igual que en buscar.html)
|
||||
for (let m = startMins; m + duration <= endMins; m += 30) {
|
||||
if (!isOverlapping(m, m + duration, occupied)) {
|
||||
const timeStr = minsToTime(m);
|
||||
@@ -683,20 +689,18 @@
|
||||
}
|
||||
};
|
||||
|
||||
// Generar Mañana y Tarde
|
||||
genSlotsForPeriod(bizHours.m_start, bizHours.m_end);
|
||||
genSlotsForPeriod(bizHours.a_start, bizHours.a_end);
|
||||
|
||||
if (slotsGenerated === 0) {
|
||||
msg.classList.remove('hidden');
|
||||
grid.classList.add('hidden');
|
||||
pickerSelectedTime = ""; // Reset forzado
|
||||
pickerSelectedTime = "";
|
||||
} else {
|
||||
msg.classList.add('hidden');
|
||||
grid.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Si la hora que estaba seleccionada ya no sale, la borramos
|
||||
if (pickerSelectedTime && !grid.innerHTML.includes(`'${pickerSelectedTime}'`)) {
|
||||
pickerSelectedTime = "";
|
||||
}
|
||||
@@ -704,9 +708,6 @@
|
||||
checkSaveButton();
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// APERTURA DE MODAL Y GUARDADO (MANUAL)
|
||||
// ==========================================
|
||||
function openActionModal(id) {
|
||||
const s = localServices.find(x => x.id === id);
|
||||
if (!s) return;
|
||||
@@ -721,17 +722,16 @@
|
||||
const matchPhone = rawPhone.toString().match(/[6789]\d{8}/);
|
||||
document.getElementById('detPhoneRaw').value = matchPhone ? matchPhone[0] : "";
|
||||
|
||||
// INICIALIZAR EL AGENDADOR
|
||||
document.getElementById('durationInput').value = "60";
|
||||
pickerSelectedDate = ""; // Reset
|
||||
pickerSelectedTime = ""; // Reset
|
||||
buildDayCarousel(); // Esto selecciona 'hoy' automáticamente y pinta los huecos
|
||||
pickerSelectedDate = "";
|
||||
pickerSelectedTime = "";
|
||||
buildDayCarousel();
|
||||
checkSaveButton();
|
||||
|
||||
const modal = document.getElementById('actionModal');
|
||||
const content = document.getElementById('modalContent');
|
||||
modal.classList.remove('hidden');
|
||||
void modal.offsetWidth; // Reflow
|
||||
void modal.offsetWidth;
|
||||
modal.classList.remove('opacity-0');
|
||||
content.classList.remove('translate-y-full');
|
||||
}
|
||||
@@ -739,10 +739,8 @@
|
||||
function closeModal(modalId, contentId) {
|
||||
const modal = document.getElementById(modalId);
|
||||
const content = document.getElementById(contentId);
|
||||
|
||||
modal.classList.add('opacity-0');
|
||||
content.classList.add('translate-y-full');
|
||||
|
||||
setTimeout(() => { modal.classList.add('hidden'); }, 300);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user