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 class="grid grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label class="label-modern">Día</label>
|
<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>
|
||||||
<div>
|
<div>
|
||||||
<label class="label-modern">Hora</label>
|
<label class="label-modern">Hora</label>
|
||||||
<input type="time" id="nsTime" class="input-modern" required>
|
<input type="time" id="nsTime" class="input-modern" required>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<div>
|
||||||
<label class="label-modern">Tiempo Estimado</label>
|
<label class="label-modern">Tiempo Estimado</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
@@ -1343,12 +1349,67 @@
|
|||||||
document.getElementById('nsTime').value = "";
|
document.getElementById('nsTime').value = "";
|
||||||
document.getElementById('nsDate').value = selectedDateStr;
|
document.getElementById('nsDate').value = selectedDateStr;
|
||||||
|
|
||||||
|
renderNewServiceAgenda(); // <-- Cargamos la ruta del día
|
||||||
|
|
||||||
const modal = document.getElementById('newServiceModal');
|
const modal = document.getElementById('newServiceModal');
|
||||||
modal.style.display = 'flex';
|
modal.style.display = 'flex';
|
||||||
setTimeout(() => modal.classList.remove('translate-y-full'), 10);
|
setTimeout(() => modal.classList.remove('translate-y-full'), 10);
|
||||||
safeLoadIcons();
|
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() {
|
function closeNewServiceModal() {
|
||||||
const modal = document.getElementById('newServiceModal');
|
const modal = document.getElementById('newServiceModal');
|
||||||
modal.classList.add('translate-y-full');
|
modal.classList.add('translate-y-full');
|
||||||
@@ -1378,8 +1439,101 @@
|
|||||||
} catch (e) {}
|
} 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) {
|
async function saveNewServiceApp(e) {
|
||||||
e.preventDefault();
|
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 btn = document.getElementById('btnSaveNewApp');
|
||||||
const originalHTML = btn.innerHTML;
|
const originalHTML = btn.innerHTML;
|
||||||
btn.innerHTML = `<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i> Procesando...`;
|
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,
|
description: document.getElementById('nsDesc').value,
|
||||||
guild_id: null,
|
guild_id: null,
|
||||||
assigned_to: myWorkerId,
|
assigned_to: myWorkerId,
|
||||||
duration_minutes: document.getElementById('nsDuration').value,
|
duration_minutes: duration,
|
||||||
is_urgent: false,
|
is_urgent: false,
|
||||||
is_company: false,
|
is_company: false,
|
||||||
company_name: 'Particular',
|
company_name: 'Particular',
|
||||||
@@ -1417,10 +1571,6 @@
|
|||||||
const citadoSt = systemStatuses.find(st => st.name.toLowerCase().includes('citado'));
|
const citadoSt = systemStatuses.find(st => st.name.toLowerCase().includes('citado'));
|
||||||
const statusMapId = citadoSt ? String(citadoSt.id) : null;
|
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}`, {
|
await fetch(`${API_URL}/services/set-appointment/${dataCreate.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
|
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
|
||||||
|
|||||||
Reference in New Issue
Block a user