Actualizar calendario.html

This commit is contained in:
2026-02-24 23:19:23 +00:00
parent 972eddbfb1
commit 9bc2782292

View File

@@ -46,7 +46,7 @@
.desc-box { background: white; border: 1px solid #e2e8f0; border-radius: 1.5rem; padding: 1.25rem; } .desc-box { background: white; border: 1px solid #e2e8f0; border-radius: 1.5rem; padding: 1.25rem; }
</style> </style>
</head> </head>
<body class="text-slate-800 font-sans antialiased h-screen flex flex-col overflow-hidden relative"> <body class="text-slate-800 font-sans antialiased h-screen flex flex-col overflow-hidden relative text-left">
<header class="bg-white px-5 pt-safe mt-6 pb-4 shadow-sm z-20 shrink-0 border-b border-slate-200"> <header class="bg-white px-5 pt-safe mt-6 pb-4 shadow-sm z-20 shrink-0 border-b border-slate-200">
<div class="flex items-center gap-3 mb-4"> <div class="flex items-center gap-3 mb-4">
@@ -70,7 +70,7 @@
<i data-lucide="loader-2" class="w-8 h-8 animate-spin mx-auto text-primary-dynamic mb-2"></i> <i data-lucide="loader-2" class="w-8 h-8 animate-spin mx-auto text-primary-dynamic mb-2"></i>
<p class="text-xs font-bold uppercase tracking-widest text-slate-400">Consultando Base de Datos...</p> <p class="text-xs font-bold uppercase tracking-widest text-slate-400">Consultando Base de Datos...</p>
</div> </div>
<div id="dayTitle" class="font-black text-slate-400 uppercase tracking-widest text-[10px] mb-4 hidden">Servicios</div> <div id="dayTitle" class="font-black text-slate-400 uppercase tracking-widest text-[10px] mb-4 hidden text-left">Servicios</div>
<div id="servicesList" class="space-y-4 hidden fade-in"></div> <div id="servicesList" class="space-y-4 hidden fade-in"></div>
</main> </main>
@@ -111,17 +111,17 @@
</div> </div>
<div class="grid grid-cols-2 gap-3"> <div class="grid grid-cols-2 gap-3">
<button onclick="quickUpdate('camino')" class="bg-white border border-slate-200 text-primary-dynamic font-black p-5 rounded-3xl flex flex-col items-center gap-2 shadow-sm active:bg-slate-50 transition-all"> <button onclick="quickUpdate('camino')" class="bg-white border border-slate-200 text-primary-dynamic font-black p-5 rounded-3xl flex flex-col items-center gap-2 shadow-sm active:bg-slate-50 transition-all text-center">
<i data-lucide="car" class="w-8 h-8"></i> <i data-lucide="car" class="w-8 h-8"></i>
<span class="text-[10px] uppercase tracking-widest">De Camino</span> <span class="text-[10px] uppercase tracking-widest">De Camino</span>
</button> </button>
<button onclick="quickUpdate('trabajando')" class="bg-white border border-slate-200 text-primary-dynamic font-black p-5 rounded-3xl flex flex-col items-center gap-2 shadow-sm active:bg-slate-50 transition-all"> <button onclick="quickUpdate('trabajando')" class="bg-white border border-slate-200 text-primary-dynamic font-black p-5 rounded-3xl flex flex-col items-center gap-2 shadow-sm active:bg-slate-50 transition-all text-center">
<i data-lucide="wrench" class="w-8 h-8"></i> <i data-lucide="wrench" class="w-8 h-8"></i>
<span class="text-[10px] uppercase tracking-widest">He Llegado</span> <span class="text-[10px] uppercase tracking-widest">He Llegado</span>
</button> </button>
</div> </div>
<div class="bg-white p-6 rounded-[2.5rem] shadow-sm border border-slate-200"> <div class="bg-white p-6 rounded-[2.5rem] shadow-sm border border-slate-200 text-left">
<div class="flex justify-between items-start mb-1"> <div class="flex justify-between items-start mb-1">
<p class="text-[9px] font-black text-primary-dynamic uppercase tracking-widest" id="detCompany">Compañía</p> <p class="text-[9px] font-black text-primary-dynamic uppercase tracking-widest" id="detCompany">Compañía</p>
<span id="detRef" class="text-[9px] font-black text-slate-400 uppercase"></span> <span id="detRef" class="text-[9px] font-black text-slate-400 uppercase"></span>
@@ -131,12 +131,12 @@
<button onclick="callClient()" class="w-full bg-primary-dynamic text-white font-black py-4 rounded-2xl shadow-xl flex items-center justify-center gap-3 uppercase text-sm tracking-widest active:scale-95 transition-all"> <button onclick="callClient()" class="w-full bg-primary-dynamic text-white font-black py-4 rounded-2xl shadow-xl flex items-center justify-center gap-3 uppercase text-sm tracking-widest active:scale-95 transition-all">
<i data-lucide="phone" class="w-5 h-5 fill-current"></i> Llamar al Cliente <i data-lucide="phone" class="w-5 h-5 fill-current"></i> Llamar al Cliente
</button> </button>
<button onclick="openWhatsApp()" class="w-full mt-3 bg-slate-50 text-slate-700 font-black py-3 rounded-2xl border border-slate-200 flex items-center justify-center gap-2 text-xs uppercase tracking-widest active:scale-95 transition-all"> <button onclick="openWhatsApp()" class="w-full mt-3 bg-slate-50 text-slate-700 font-black py-3 rounded-2xl border border-slate-200 flex items-center justify-center gap-2 text-xs uppercase tracking-widest active:scale-95 transition-all text-center">
<i data-lucide="message-circle" class="w-4 h-4 text-emerald-500"></i> WhatsApp <i data-lucide="message-circle" class="w-4 h-4 text-emerald-500"></i> WhatsApp
</button> </button>
</div> </div>
<div class="bg-white p-6 rounded-[2.5rem] shadow-sm border border-slate-200 relative overflow-hidden"> <div class="bg-white p-6 rounded-[2.5rem] shadow-sm border border-slate-200 relative overflow-hidden text-left">
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-2">Ubicación</p> <p class="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-2">Ubicación</p>
<p id="detAddress" class="text-base font-bold text-slate-900 uppercase leading-tight mb-6"></p> <p id="detAddress" class="text-base font-bold text-slate-900 uppercase leading-tight mb-6"></p>
@@ -151,10 +151,10 @@
</div> </div>
</div> </div>
<div class="bg-white border border-slate-200 rounded-3xl p-5 shadow-sm space-y-4"> <div class="bg-white border border-slate-200 rounded-3xl p-5 shadow-sm space-y-4 text-left">
<p class="text-[10px] font-black text-blue-600 uppercase tracking-widest border-b border-slate-100 pb-2 flex items-center gap-1.5"><i data-lucide="calendar-clock" class="w-4 h-4"></i> Asignar Fechas y Tiempos</p> <p class="text-[10px] font-black text-blue-600 uppercase tracking-widest border-b border-slate-100 pb-2 flex items-center gap-1.5"><i data-lucide="calendar-clock" class="w-4 h-4"></i> Asignar Fechas y Tiempos</p>
<div class="grid grid-cols-2 gap-3"> <div class="grid grid-cols-2 gap-3 text-left">
<div> <div>
<label class="block text-[10px] font-black text-slate-500 uppercase tracking-widest mb-1 ml-1">Fecha</label> <label class="block text-[10px] font-black text-slate-500 uppercase tracking-widest mb-1 ml-1">Fecha</label>
<input type="date" id="dateInput" class="w-full bg-slate-50 border border-slate-200 p-3 rounded-xl text-xs font-bold text-slate-700 shadow-sm outline-none focus:border-blue-400 focus:ring-2 focus:ring-blue-100 transition-all"> <input type="date" id="dateInput" class="w-full bg-slate-50 border border-slate-200 p-3 rounded-xl text-xs font-bold text-slate-700 shadow-sm outline-none focus:border-blue-400 focus:ring-2 focus:ring-blue-100 transition-all">
@@ -166,8 +166,8 @@
</div> </div>
<div> <div>
<label class="block text-[10px] font-black text-slate-500 uppercase tracking-widest mb-1 ml-1">Duración Estimada</label> <label class="block text-[10px] font-black text-slate-500 uppercase tracking-widest mb-1 ml-1 text-left">Duración Estimada</label>
<div class="relative"> <div class="relative text-left">
<select id="durationInput" class="w-full bg-slate-50 border border-slate-200 p-3 rounded-xl text-xs font-bold text-slate-700 shadow-sm outline-none focus:border-blue-400 focus:ring-2 focus:ring-blue-100 transition-all appearance-none pr-8 cursor-pointer"> <select id="durationInput" class="w-full bg-slate-50 border border-slate-200 p-3 rounded-xl text-xs font-bold text-slate-700 shadow-sm outline-none focus:border-blue-400 focus:ring-2 focus:ring-blue-100 transition-all appearance-none pr-8 cursor-pointer">
<option value="15">15 min</option> <option value="15">15 min</option>
<option value="30">30 min</option> <option value="30">30 min</option>
@@ -186,35 +186,35 @@
</div> </div>
</div> </div>
<div class="pt-2"> <div class="pt-2 text-left">
<p class="text-[10px] font-black text-slate-800 uppercase ml-1 flex items-center gap-1.5 mb-1.5"><i data-lucide="arrow-right-left" class="w-4 h-4 text-blue-500"></i> Cambio de Estado</p> <p class="text-[10px] font-black text-slate-800 uppercase ml-1 flex items-center gap-1.5 mb-1.5"><i data-lucide="arrow-right-left" class="w-4 h-4 text-blue-500"></i> Cambio de Estado</p>
<div class="relative"> <div class="relative text-left">
<select id="detStatusMap" class="w-full bg-slate-800 text-white border-none p-4 rounded-xl text-xs font-bold shadow-lg outline-none cursor-pointer appearance-none pr-10"> <select id="detStatusMap" class="w-full bg-slate-800 text-white border-none p-4 rounded-xl text-xs font-bold shadow-lg outline-none cursor-pointer appearance-none pr-10">
</select> </select>
<i data-lucide="chevron-down" class="w-4 h-4 text-slate-400 absolute right-4 top-1/2 -translate-y-1/2 pointer-events-none"></i> <i data-lucide="chevron-down" class="w-4 h-4 text-slate-400 absolute right-4 top-1/2 -translate-y-1/2 pointer-events-none"></i>
</div> </div>
</div> </div>
<button id="btnSaveAppt" onclick="saveAppointment()" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-black py-3.5 rounded-xl shadow-lg transition-all uppercase tracking-widest text-xs flex items-center justify-center gap-2 mt-2"> <button id="btnSaveAppt" onclick="saveAppointment()" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-black py-3.5 rounded-xl shadow-lg transition-all uppercase tracking-widest text-xs flex items-center justify-center gap-2 mt-2 text-center">
<i data-lucide="save" class="w-4 h-4"></i> Guardar Cambios <i data-lucide="save" class="w-4 h-4"></i> Guardar Cambios
</button> </button>
</div> </div>
<div class="desc-box"> <div class="desc-box text-left">
<p class="text-[10px] font-black text-primary-dynamic uppercase tracking-widest mb-3 flex items-center gap-2"><i data-lucide="file-text" class="w-4 h-4"></i> Descripción Técnica</p> <p class="text-[10px] font-black text-primary-dynamic uppercase tracking-widest mb-3 flex items-center gap-2"><i data-lucide="file-text" class="w-4 h-4"></i> Descripción Técnica</p>
<div id="detDesc" class="text-sm font-bold text-slate-600 leading-relaxed max-h-60 overflow-y-auto no-scrollbar">--</div> <div id="detDesc" class="text-sm font-bold text-slate-600 leading-relaxed max-h-60 overflow-y-auto no-scrollbar">--</div>
</div> </div>
<div class="bg-white border border-slate-200 rounded-3xl overflow-hidden shadow-sm"> <div class="bg-white border border-slate-200 rounded-3xl overflow-hidden shadow-sm text-left">
<button onclick="document.getElementById('extraDataBox').classList.toggle('hidden')" class="w-full p-4 flex justify-between items-center text-[10px] font-black text-slate-500 uppercase tracking-widest bg-slate-50"> <button onclick="document.getElementById('extraDataBox').classList.toggle('hidden')" class="w-full p-4 flex justify-between items-center text-[10px] font-black text-slate-500 uppercase tracking-widest bg-slate-50">
<span>Ficha técnica completa</span> <span>Ficha técnica completa</span>
<i data-lucide="chevron-right" class="w-4 h-4 text-slate-400"></i> <i data-lucide="chevron-right" class="w-4 h-4 text-slate-400"></i>
</button> </button>
<div id="extraDataBox" class="hidden p-5 space-y-3 bg-white border-t border-slate-100" id="detExtraInfo"> <div id="extraDataBox" class="hidden p-5 space-y-3 bg-white border-t border-slate-100">
</div> </div>
</div> </div>
<div class="pt-4"> <div class="pt-4 text-center">
<button onclick="quickUpdate('finalizado')" class="w-full bg-emerald-600 text-white font-black py-5 rounded-[2rem] shadow-xl flex items-center justify-center gap-3 uppercase text-sm tracking-widest active:scale-95 transition-all"> <button onclick="quickUpdate('finalizado')" class="w-full bg-emerald-600 text-white font-black py-5 rounded-[2rem] shadow-xl flex items-center justify-center gap-3 uppercase text-sm tracking-widest active:scale-95 transition-all">
<i data-lucide="check-circle" class="w-6 h-6"></i> Finalizar Trabajo <i data-lucide="check-circle" class="w-6 h-6"></i> Finalizar Trabajo
</button> </button>
@@ -239,7 +239,6 @@
let currentWeekStart = new Date(); let currentWeekStart = new Date();
let selectedDateStr = ""; let selectedDateStr = "";
// --- SISTEMA DE TEMA DINÁMICO ---
async function applyTheme() { async function applyTheme() {
try { try {
let theme = JSON.parse(localStorage.getItem('app_theme')); let theme = JSON.parse(localStorage.getItem('app_theme'));
@@ -416,11 +415,22 @@
let compShort = (raw["Compañía"] || "Particular").split('-')[0].trim().substring(0, 15); let compShort = (raw["Compañía"] || "Particular").split('-')[0].trim().substring(0, 15);
const guildObj = systemGuilds.find(g => String(g.id) === String(s.guild_id || raw.guild_id)); const guildObj = systemGuilds.find(g => String(g.id) === String(s.guild_id || raw.guild_id));
// LÓGICA DE ICONO SEGÚN ESTADO
let iconName = "clock";
const dbStatId = raw.status_operativo;
const statusObj = systemStatuses.find(st => String(st.id) === String(dbStatId));
const stName = (statusObj?.name || "").toLowerCase();
if (stName.includes('camino')) iconName = "car";
else if (stName.includes('trabajando') || stName.includes('llegado')) iconName = "wrench";
else if (stName.includes('incidencia') || stName.includes('pausa')) iconName = "alert-triangle";
else if (stName.includes('citado')) iconName = "calendar";
return ` return `
<div onclick="openService(${s.id})" class="bg-white p-6 rounded-[2.5rem] border border-slate-200 shadow-sm active:scale-95 transition-transform flex gap-4 relative overflow-hidden"> <div onclick="openService(${s.id})" class="bg-white p-6 rounded-[2.5rem] border border-slate-200 shadow-sm active:scale-95 transition-transform flex gap-4 relative overflow-hidden text-left">
${s.is_urgent ? '<div class="absolute top-0 right-0 bg-primary-dynamic text-white text-[8px] font-black px-3 py-1.5 rounded-bl-xl uppercase tracking-widest z-10">Urgente</div>' : ''} ${s.is_urgent ? '<div class="absolute top-0 right-0 bg-primary-dynamic text-white text-[8px] font-black px-3 py-1.5 rounded-bl-xl uppercase tracking-widest z-10">Urgente</div>' : ''}
<div class="flex flex-col items-center justify-center border-r border-slate-100 pr-4 shrink-0 min-w-[70px]"> <div class="flex flex-col items-center justify-center border-r border-slate-100 pr-4 shrink-0 min-w-[70px]">
<i data-lucide="clock" class="w-6 h-6 text-primary-dynamic mb-1"></i> <i data-lucide="${iconName}" class="w-6 h-6 text-primary-dynamic mb-1"></i>
<span class="font-black text-slate-900 text-base">${time}</span> <span class="font-black text-slate-900 text-base">${time}</span>
</div> </div>
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
@@ -463,30 +473,30 @@
document.getElementById('detDesc').innerHTML = (raw["Descripción"] || raw["DESCRIPCION"] || "Sin notas.").replace(/\n/g, '<br>'); document.getElementById('detDesc').innerHTML = (raw["Descripción"] || raw["DESCRIPCION"] || "Sin notas.").replace(/\n/g, '<br>');
const extraContainer = document.getElementById('extraDataBox'); const extraContainer = document.getElementById('extraDataBox');
extraContainer.innerHTML = '';
let detailsHtml = ''; let detailsHtml = '';
const skipKeys = ["Nombre Cliente", "CLIENTE", "Dirección", "DOMICILIO", "Población", "POBLACION-PROVINCIA", "scheduled_date", "scheduled_time", "status_operativo", "duration_minutes", "assigned_to", "guild_id", "Código Postal", "assigned_to_name", "Descripción", "DESCRIPCION", "Compañía", "COMPAÑIA"]; const skipKeys = ["Nombre Cliente", "CLIENTE", "Dirección", "DOMICILIO", "Población", "POBLACION-PROVINCIA", "scheduled_date", "scheduled_time", "status_operativo", "duration_minutes", "assigned_to", "guild_id", "Código Postal", "assigned_to_name", "Descripción", "DESCRIPCION", "Compañía", "COMPAÑIA", "called_times", "appointment_status"];
for(let key in raw) { for(let key in raw) {
if(skipKeys.includes(key)) continue; if(skipKeys.includes(key)) continue;
let valStr = String(raw[key] || ""); let valStr = String(raw[key] || "");
if(valStr.trim() === "") continue; if(valStr.trim() === "" || valStr === "null") continue;
detailsHtml += `<div class="border-b border-slate-100 pb-2 mb-2 last:border-0 flex flex-col gap-0.5"><span class="text-[8px] font-black text-slate-400 uppercase tracking-widest">${key}</span><span class="text-[11px] font-bold text-slate-700 break-words">${valStr}</span></div>`; detailsHtml += `<div class="border-b border-slate-100 pb-2 mb-2 last:border-0 flex flex-col gap-0.5"><span class="text-[8px] font-black text-slate-400 uppercase tracking-widest">${key}</span><span class="text-[11px] font-bold text-slate-700 break-words">${valStr}</span></div>`;
} }
extraContainer.innerHTML = detailsHtml || '<p class="text-xs text-slate-400">Sin datos adicionales.</p>'; extraContainer.innerHTML = detailsHtml || '<p class="text-xs text-slate-400">Sin datos adicionales.</p>';
// CARGAMOS LA FECHA, LA HORA Y LA DURACIÓN EN EL MODAL
document.getElementById('dateInput').value = raw.scheduled_date || ""; document.getElementById('dateInput').value = raw.scheduled_date || "";
document.getElementById('timeInput').value = raw.scheduled_time || ""; document.getElementById('timeInput').value = raw.scheduled_time || "";
document.getElementById('durationInput').value = raw.duration_minutes || "60"; document.getElementById('durationInput').value = raw.duration_minutes || "60";
const dbStat = raw.status_operativo; const dbStat = raw.status_operativo;
const foundById = systemStatuses.find(st => String(st.id) === String(dbStat)); if(dbStat) document.getElementById('detStatusMap').value = dbStat;
if(foundById) document.getElementById('detStatusMap').value = foundById.id;
const modal = document.getElementById('serviceModal'); const modal = document.getElementById('serviceModal');
modal.style.display = 'flex'; modal.style.display = 'flex';
setTimeout(() => modal.classList.remove('translate-y-full'), 10); setTimeout(() => modal.classList.remove('translate-y-full'), 10);
calculateDistance(fullAddress); calculateDistance(fullAddress);
safeLoadIcons();
} }
function closeModal() { function closeModal() {
@@ -501,6 +511,9 @@
async function calculateDistance(dest) { async function calculateDistance(dest) {
if(!navigator.geolocation) return; if(!navigator.geolocation) return;
document.getElementById('gpsLoading').classList.remove('hidden');
document.getElementById('gpsResult').classList.add('hidden');
navigator.geolocation.getCurrentPosition(async (pos) => { navigator.geolocation.getCurrentPosition(async (pos) => {
const lat = pos.coords.latitude; const lon = pos.coords.longitude; const lat = pos.coords.latitude; const lon = pos.coords.longitude;
try { try {
@@ -540,7 +553,6 @@
} catch (e) { alert("Error"); } } catch (e) { alert("Error"); }
} }
// --- FUNCIÓN ACTUALIZADA PARA CAPTURAR LA DURACIÓN ---
async function saveAppointment() { async function saveAppointment() {
const id = document.getElementById('detId').value; const id = document.getElementById('detId').value;
const date = document.getElementById('dateInput').value; const date = document.getElementById('dateInput').value;
@@ -558,23 +570,30 @@
const originalContent = btn.innerHTML; const originalContent = btn.innerHTML;
btn.innerHTML = `<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Guardando...`; btn.innerHTML = `<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Guardando...`;
btn.disabled = true; btn.disabled = true;
safeLoadIcons();
try { try {
await fetch(`${API_URL}/services/set-appointment/${id}`, { const res = await fetch(`${API_URL}/services/set-appointment/${id}`, {
method: 'PUT', method: 'PUT',
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }, headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
body: JSON.stringify({ date, time, duration_minutes: duration, status_operativo: statusMap }) body: JSON.stringify({ date, time, duration_minutes: duration, status_operativo: statusMap })
}); });
closeDetailModal(); showToast("Estado actualizado"); refreshData(); if (res.ok) {
} catch (e) { alert("Error"); } showToast("Estado actualizado");
finally { btn.innerHTML = originalContent; btn.disabled = false; } closeModal();
refreshData();
} else {
alert("Error al guardar");
}
} catch (e) { alert("Error de conexión"); }
finally { btn.innerHTML = originalContent; btn.disabled = false; safeLoadIcons(); }
} }
function showToast(m) { function showToast(m) {
const t = document.getElementById('toast'); document.getElementById('toastMsg').innerText = m; const t = document.getElementById('toast'); document.getElementById('toastMsg').innerText = m;
t.classList.remove('opacity-0', '-translate-y-10'); t.classList.add('translate-y-0'); t.classList.remove('opacity-0', 'pointer-events-none', '-translate-y-10'); t.classList.add('translate-y-0');
setTimeout(() => { t.classList.add('opacity-0', '-translate-y-10'); t.classList.remove('translate-y-0'); }, 2000); setTimeout(() => { t.classList.add('opacity-0', 'pointer-events-none', '-translate-y-10'); t.classList.remove('translate-y-0'); }, 2000);
} }
function logout() { localStorage.clear(); window.location.href = "index.html"; } function logout() { localStorage.clear(); window.location.href = "index.html"; }