Actualizar calendario.html
This commit is contained in:
162
calendario.html
162
calendario.html
@@ -317,13 +317,19 @@
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="label-modern">Día</label>
|
||||
<input type="date" id="nsDate" class="input-modern" required>
|
||||
<input type="date" id="nsDate" onchange="renderNewServiceAgenda()" class="input-modern" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label-modern">Hora</label>
|
||||
<input type="time" id="nsTime" class="input-modern" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="nsAgendaContainer" class="hidden mt-1 text-left bg-blue-50/50 border border-blue-100 rounded-xl p-3">
|
||||
<p class="text-[9px] font-black text-blue-500 uppercase tracking-widest mb-2 flex items-center gap-1.5"><i data-lucide="map" class="w-3 h-3"></i> Tu ruta para este día</p>
|
||||
<div id="nsAgendaList" class="flex flex-col gap-2"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="label-modern">Tiempo Estimado</label>
|
||||
<div class="relative">
|
||||
@@ -1343,12 +1349,67 @@
|
||||
document.getElementById('nsTime').value = "";
|
||||
document.getElementById('nsDate').value = selectedDateStr;
|
||||
|
||||
renderNewServiceAgenda(); // <-- Cargamos la ruta del día
|
||||
|
||||
const modal = document.getElementById('newServiceModal');
|
||||
modal.style.display = 'flex';
|
||||
setTimeout(() => modal.classList.remove('translate-y-full'), 10);
|
||||
safeLoadIcons();
|
||||
}
|
||||
|
||||
// Dibuja los huecos ocupados para que el operario sepa por dónde se mueve
|
||||
function renderNewServiceAgenda() {
|
||||
const dateInput = document.getElementById('nsDate').value;
|
||||
const agendaContainer = document.getElementById('nsAgendaContainer');
|
||||
const agendaList = document.getElementById('nsAgendaList');
|
||||
|
||||
if (!dateInput) {
|
||||
agendaContainer.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
const dayServices = localServices.filter(s => String(s.raw_data.scheduled_date || "").trim() === dateInput);
|
||||
|
||||
dayServices.sort((a, b) => {
|
||||
const tA = String(a.raw_data.scheduled_time || "23:59");
|
||||
const tB = String(b.raw_data.scheduled_time || "23:59");
|
||||
return tA.localeCompare(tB);
|
||||
});
|
||||
|
||||
if (dayServices.length === 0) {
|
||||
agendaList.innerHTML = `<span class="text-[10px] font-bold text-emerald-600 bg-emerald-50 px-3 py-1 rounded-lg border border-emerald-100 text-center">¡Día totalmente libre!</span>`;
|
||||
} else {
|
||||
agendaList.innerHTML = dayServices.map(s => {
|
||||
const time = s.raw_data.scheduled_time || "--:--";
|
||||
const dur = parseInt(s.raw_data.duration_minutes || 60);
|
||||
|
||||
let endTime = "--:--";
|
||||
if (time.includes(':')) {
|
||||
let [h, m] = time.split(':').map(Number);
|
||||
m += dur;
|
||||
h += Math.floor(m / 60);
|
||||
m = m % 60;
|
||||
endTime = `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
const city = s.raw_data["Población"] || "Sin Zona";
|
||||
|
||||
return `
|
||||
<div class="bg-white border border-blue-100 rounded-lg p-2 text-xs flex justify-between items-center shadow-sm">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="bg-blue-50 text-blue-600 font-black px-2 py-1 rounded-md text-[10px]">
|
||||
${time} - ${endTime}
|
||||
</div>
|
||||
<span class="font-bold text-slate-700 truncate max-w-[150px]">${city}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
agendaContainer.classList.remove('hidden');
|
||||
safeLoadIcons();
|
||||
}
|
||||
|
||||
function closeNewServiceModal() {
|
||||
const modal = document.getElementById('newServiceModal');
|
||||
modal.classList.add('translate-y-full');
|
||||
@@ -1378,8 +1439,101 @@
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
async function saveAppointment() {
|
||||
const id = document.getElementById('detId').value;
|
||||
const date = document.getElementById('dateInput').value;
|
||||
const time = document.getElementById('timeInput').value;
|
||||
const duration = parseInt(document.getElementById('durationInput').value) || 60;
|
||||
let statusMap = document.getElementById('detStatusMap').value;
|
||||
|
||||
const selectedSt = systemStatuses.find(st => String(st.id) === String(statusMap));
|
||||
if (selectedSt && !selectedSt.is_final && !date && !selectedSt.name.toLowerCase().includes('pausa') && !selectedSt.name.toLowerCase().includes('asignar')) {
|
||||
if(!confirm("No has asignado Fecha para este estado. ¿Deseas continuar?")) return;
|
||||
}
|
||||
|
||||
// 🛑 RADAR ANTI-SOLAPAMIENTO (PERMISIVO)
|
||||
if (date && time) {
|
||||
let [newH, newM] = time.split(':').map(Number);
|
||||
let newStartMin = newH * 60 + newM;
|
||||
let newEndMin = newStartMin + duration;
|
||||
|
||||
const solapamiento = localServices.find(s => {
|
||||
if (String(s.id) === String(id)) return false;
|
||||
const sDate = String(s.raw_data.scheduled_date || "").trim();
|
||||
const sTime = String(s.raw_data.scheduled_time || "").trim();
|
||||
if (sDate === date && sTime && sTime.includes(':')) {
|
||||
let [sH, sM] = sTime.split(':').map(Number);
|
||||
let sDur = parseInt(s.raw_data.duration_minutes || 60);
|
||||
let sStartMin = sH * 60 + sM;
|
||||
let sEndMin = sStartMin + sDur;
|
||||
return (newStartMin < sEndMin && newEndMin > sStartMin);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (solapamiento) {
|
||||
const horaC = solapamiento.raw_data.scheduled_time;
|
||||
const zonaC = solapamiento.raw_data["Población"] || "otra zona";
|
||||
// AHORA ES UN CONFIRM: Te avisa, pero te deja pasar si quieres
|
||||
const forzar = confirm(`⚠️ SOLAPAMIENTO DETECTADO\n\nYa tienes una cita a las ${horaC} en ${zonaC}.\n\n¿Deseas FORZAR el guardado y agendarla en el mismo hueco?`);
|
||||
if (!forzar) return; // Si cancela, abortamos
|
||||
}
|
||||
}
|
||||
|
||||
const btn = document.getElementById('btnSaveAppt');
|
||||
const originalContent = btn.innerHTML;
|
||||
btn.innerHTML = `<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Guardando...`;
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/services/set-appointment/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
|
||||
body: JSON.stringify({ date, time, duration_minutes: duration, status_operativo: statusMap })
|
||||
});
|
||||
if (res.ok) {
|
||||
showToast("Estado actualizado");
|
||||
closeModal();
|
||||
refreshData();
|
||||
} else { alert("Error al guardar"); }
|
||||
} catch (e) { alert("Error de conexión"); }
|
||||
finally { btn.innerHTML = originalContent; btn.disabled = false; safeLoadIcons(); }
|
||||
}
|
||||
|
||||
async function saveNewServiceApp(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const date = document.getElementById('nsDate').value;
|
||||
const time = document.getElementById('nsTime').value;
|
||||
const duration = parseInt(document.getElementById('nsDuration').value) || 60;
|
||||
|
||||
// 🛑 RADAR ANTI-SOLAPAMIENTO EN CREACIÓN (PERMISIVO)
|
||||
if (date && time) {
|
||||
let [newH, newM] = time.split(':').map(Number);
|
||||
let newStartMin = newH * 60 + newM;
|
||||
let newEndMin = newStartMin + duration;
|
||||
|
||||
const solapamiento = localServices.find(s => {
|
||||
const sDate = String(s.raw_data.scheduled_date || "").trim();
|
||||
const sTime = String(s.raw_data.scheduled_time || "").trim();
|
||||
if (sDate === date && sTime && sTime.includes(':')) {
|
||||
let [sH, sM] = sTime.split(':').map(Number);
|
||||
let sDur = parseInt(s.raw_data.duration_minutes || 60);
|
||||
let sStartMin = sH * 60 + sM;
|
||||
let sEndMin = sStartMin + sDur;
|
||||
return (newStartMin < sEndMin && newEndMin > sStartMin);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (solapamiento) {
|
||||
const horaC = solapamiento.raw_data.scheduled_time;
|
||||
const zonaC = solapamiento.raw_data["Población"] || "otra zona";
|
||||
const forzar = confirm(`⚠️ SOLAPAMIENTO DETECTADO\n\nYa tienes una cita a las ${horaC} en ${zonaC}.\n\n¿Deseas FORZAR la creación de este aviso en el mismo hueco?`);
|
||||
if (!forzar) return;
|
||||
}
|
||||
}
|
||||
|
||||
const btn = document.getElementById('btnSaveNewApp');
|
||||
const originalHTML = btn.innerHTML;
|
||||
btn.innerHTML = `<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i> Procesando...`;
|
||||
@@ -1397,7 +1551,7 @@
|
||||
description: document.getElementById('nsDesc').value,
|
||||
guild_id: null,
|
||||
assigned_to: myWorkerId,
|
||||
duration_minutes: document.getElementById('nsDuration').value,
|
||||
duration_minutes: duration,
|
||||
is_urgent: false,
|
||||
is_company: false,
|
||||
company_name: 'Particular',
|
||||
@@ -1417,10 +1571,6 @@
|
||||
const citadoSt = systemStatuses.find(st => st.name.toLowerCase().includes('citado'));
|
||||
const statusMapId = citadoSt ? String(citadoSt.id) : null;
|
||||
|
||||
const date = document.getElementById('nsDate').value;
|
||||
const time = document.getElementById('nsTime').value;
|
||||
const duration = document.getElementById('nsDuration').value;
|
||||
|
||||
await fetch(`${API_URL}/services/set-appointment/${dataCreate.id}`, {
|
||||
method: 'PUT',
|
||||
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
|
||||
|
||||
Reference in New Issue
Block a user