Actualizar calendario.html
This commit is contained in:
@@ -116,7 +116,7 @@
|
||||
<span class="text-[10px] uppercase tracking-widest">De Camino</span>
|
||||
</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 text-center">
|
||||
<i data-lucide="wrench" class="w-8 h-8"></i>
|
||||
<i data-lucide="hammer" class="w-8 h-8"></i>
|
||||
<span class="text-[10px] uppercase tracking-widest">He Llegado</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -189,9 +189,9 @@
|
||||
<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>
|
||||
<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-50 border border-slate-200 text-slate-800 p-3.5 rounded-xl text-xs font-black shadow-sm outline-none focus:border-blue-400 focus:ring-2 focus:ring-blue-100 transition-all cursor-pointer appearance-none pr-10">
|
||||
</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-5 h-5 text-slate-400 absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -215,7 +215,7 @@
|
||||
</div>
|
||||
|
||||
<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="askToFinish()" 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
|
||||
</button>
|
||||
<p class="text-center text-[9px] font-bold text-slate-400 mt-6 uppercase tracking-widest">IntegraRepara Mobile</p>
|
||||
@@ -350,7 +350,8 @@
|
||||
const modalSelect = document.getElementById('detStatusMap');
|
||||
modalSelect.innerHTML = '';
|
||||
systemStatuses.forEach(st => {
|
||||
modalSelect.innerHTML += `<option value="${st.id}">👉 ${st.name.toUpperCase()}</option>`;
|
||||
// Modificación: Solo el nombre en el select para diseño más limpio
|
||||
modalSelect.innerHTML += `<option value="${st.id}">${st.name.toUpperCase()}</option>`;
|
||||
});
|
||||
}
|
||||
} catch (e) {}
|
||||
@@ -391,6 +392,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Mapa de colores (Mismo que en proveedores)
|
||||
const colorMap = {
|
||||
'gray': { text: 'text-slate-500', bg: 'bg-slate-100', border: 'border-slate-200' },
|
||||
'blue': { text: 'text-blue-600', bg: 'bg-blue-50', border: 'border-blue-100' },
|
||||
'amber': { text: 'text-amber-600', bg: 'bg-amber-50', border: 'border-amber-100' },
|
||||
'emerald': { text: 'text-emerald-600', bg: 'bg-emerald-50', border: 'border-emerald-100' },
|
||||
'indigo': { text: 'text-indigo-600', bg: 'bg-indigo-50', border: 'border-indigo-100' },
|
||||
'orange': { text: 'text-orange-600', bg: 'bg-orange-50', border: 'border-orange-100' },
|
||||
'red': { text: 'text-red-600', bg: 'bg-red-50', border: 'border-red-100' },
|
||||
'rose': { text: 'text-rose-600', bg: 'bg-rose-50', border: 'border-rose-100' },
|
||||
'purple': { text: 'text-purple-600', bg: 'bg-purple-50', border: 'border-purple-100' }
|
||||
};
|
||||
|
||||
function renderServices() {
|
||||
const container = document.getElementById('servicesList');
|
||||
const displayDate = selectedDateStr.split('-').reverse().join('/');
|
||||
@@ -416,28 +430,33 @@
|
||||
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));
|
||||
|
||||
// LÓGICA DE ICONO SEGÚN ESTADO
|
||||
// MODIFICACIÓN 1: LÓGICA DE ICONO Y COLOR 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();
|
||||
const stColor = (statusObj?.color || "gray");
|
||||
|
||||
if (stName.includes('camino')) iconName = "car";
|
||||
else if (stName.includes('trabajando') || stName.includes('llegado')) iconName = "wrench";
|
||||
else if (stName.includes('trabajando') || stName.includes('llegado') || stName.includes('reparación')) iconName = "hammer";
|
||||
else if (stName.includes('incidencia') || stName.includes('pausa')) iconName = "alert-triangle";
|
||||
else if (stName.includes('citado')) iconName = "calendar";
|
||||
|
||||
const cMap = colorMap[stColor] || colorMap['gray'];
|
||||
|
||||
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 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>' : ''}
|
||||
<div class="flex flex-col items-center justify-center border-r border-slate-100 pr-4 shrink-0 min-w-[70px]">
|
||||
<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>
|
||||
<div class="${cMap.bg} ${cMap.text} p-2 rounded-xl mb-1 ${cMap.border} border shadow-sm">
|
||||
<i data-lucide="${iconName}" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<span class="font-black text-slate-900 text-sm">${time}</span>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex flex-wrap gap-1.5 mb-2">
|
||||
<span class="text-[8px] font-black bg-blue-50 text-primary-dynamic px-2 py-0.5 rounded uppercase border border-blue-100">${compShort}</span>
|
||||
<span class="text-[8px] font-black bg-slate-100 text-slate-500 px-2 py-0.5 rounded uppercase border border-slate-200">${guildObj ? guildObj.name : 'Reparación'}</span>
|
||||
<span class="text-[8px] font-black bg-slate-100 text-slate-500 px-2 py-0.5 rounded uppercase border border-slate-200">${compShort}</span>
|
||||
<span class="text-[8px] font-black bg-blue-50 text-blue-600 px-2 py-0.5 rounded uppercase border border-blue-100">${guildObj ? guildObj.name : 'Reparación'}</span>
|
||||
</div>
|
||||
<h3 class="font-black text-slate-800 text-sm uppercase leading-tight truncate">${name}</h3>
|
||||
<p class="text-[10px] font-bold text-slate-400 mt-1 truncate uppercase flex items-center gap-1.5"><i data-lucide="map-pin" class="w-3 h-3 shrink-0"></i> ${addr}</p>
|
||||
@@ -509,7 +528,6 @@
|
||||
function callClient() { const p = document.getElementById('detPhoneRaw').value; if (p) window.location.href = `tel:+34${p}`; else alert("Sin teléfono"); }
|
||||
function openWhatsApp() { const p = document.getElementById('detPhoneRaw').value; if (p) window.open(`https://wa.me/34${p}`, '_blank'); else alert("Sin teléfono"); }
|
||||
|
||||
// BOTÓN VER MAPA CORREGIDO PARA ANDROID E IOS
|
||||
function openMaps() {
|
||||
const a = document.getElementById('detAddress').innerText;
|
||||
if (a) {
|
||||
@@ -517,7 +535,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// CÁLCULO DE GPS ULTRARRÁPIDO CON FALLBACK Y TIMEOUT
|
||||
async function calculateDistance(dest) {
|
||||
const loading = document.getElementById('gpsLoading');
|
||||
const result = document.getElementById('gpsResult');
|
||||
@@ -533,22 +550,19 @@
|
||||
result.classList.add('hidden');
|
||||
lucide.createIcons();
|
||||
|
||||
// OPCIONES MÁGICAS PARA QUE SEA INSTANTÁNEO
|
||||
const gpsOptions = {
|
||||
enableHighAccuracy: false, // Usa antenas/wifi en vez de buscar satélites (tarda milisegundos en vez de minutos)
|
||||
timeout: 8000, // Máximo 8 segundos de espera, si no, cancela
|
||||
maximumAge: 300000 // Permite usar la ubicación de hace 5 minutos si ya la sabe
|
||||
enableHighAccuracy: false,
|
||||
timeout: 8000,
|
||||
maximumAge: 300000
|
||||
};
|
||||
|
||||
navigator.geolocation.getCurrentPosition(async (pos) => {
|
||||
const lat = pos.coords.latitude;
|
||||
const lon = pos.coords.longitude;
|
||||
try {
|
||||
// Intento 1: Dirección completa
|
||||
let res = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(dest + ', España')}`);
|
||||
let data = await res.json();
|
||||
|
||||
// Intento 2: Si falla (porque hay "pisos", "escaleras", etc), buscamos solo por CP y Población
|
||||
if (!data || data.length === 0) {
|
||||
const parts = dest.split(',');
|
||||
const fallbackDest = parts.length > 1 ? parts[parts.length - 1].trim() : dest;
|
||||
@@ -573,15 +587,14 @@
|
||||
}
|
||||
}, (err) => {
|
||||
loading.innerHTML = '<span class="text-[9px]">GPS Lento/Denegado</span>';
|
||||
}, gpsOptions); // <- AQUÍ PASAMOS LAS OPCIONES DE VELOCIDAD
|
||||
}, gpsOptions);
|
||||
}
|
||||
|
||||
async function quickUpdate(action) {
|
||||
if(!currentServiceId) return;
|
||||
let word = ""; let msg = "";
|
||||
if(action === 'camino') { word = "camino"; msg = "¿Enviar 'De Camino'?"; }
|
||||
if(action === 'camino') { word = "camino"; msg = "¿Enviar estado 'De Camino'?"; }
|
||||
if(action === 'trabajando') { word = "trabaja"; msg = "¿Iniciar trabajo?"; }
|
||||
if(action === 'finalizado') { word = "finaliza"; msg = "¿Finalizar expediente?"; }
|
||||
|
||||
if(!confirm(msg)) return;
|
||||
const st = systemStatuses.find(s => s.name.toLowerCase().includes(word));
|
||||
@@ -596,7 +609,6 @@
|
||||
if(res.ok) {
|
||||
showToast("¡Actualizado!");
|
||||
|
||||
// MAGIA: Si es "De camino", forzamos enviar el GPS INMEDIATAMENTE
|
||||
if (action === 'camino' && navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(async (pos) => {
|
||||
await fetch(`${API_URL}/services/${currentServiceId}/location`, {
|
||||
@@ -610,7 +622,40 @@
|
||||
closeModal();
|
||||
refreshData();
|
||||
}
|
||||
} catch (e) { alert("Error"); }
|
||||
} catch (e) { alert("Error al actualizar"); }
|
||||
}
|
||||
|
||||
// MODIFICACIÓN 3: LÓGICA DE FINALIZAR CON PREGUNTA DE ENCUESTA
|
||||
async function askToFinish() {
|
||||
if(!currentServiceId) return;
|
||||
|
||||
const stFinalizado = systemStatuses.find(s => s.name.toLowerCase().includes('finaliza'));
|
||||
if(!stFinalizado) return alert("El sistema no tiene un estado Finalizado válido.");
|
||||
|
||||
// Preguntamos explícitamente si se envía la encuesta
|
||||
const sendSurvey = confirm("¿Deseas enviar la encuesta de satisfacción al cliente antes de finalizar?");
|
||||
|
||||
try {
|
||||
// Al poner el estado a Finalizado, el servidor borra el aviso de la agenda.
|
||||
// Le pasamos al servidor un "flag" para que sepa si envía o no envía el WhatsApp de la encuesta.
|
||||
// NOTA: Como la ruta de server.js actual dispara automáticamente el WA al ver 'finalizado',
|
||||
// le pasamos un booleano en el body "skip_survey" para que el servidor lo bloquee si eligió NO.
|
||||
|
||||
const res = await fetch(`${API_URL}/services/set-appointment/${currentServiceId}`, {
|
||||
method: 'PUT',
|
||||
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
|
||||
body: JSON.stringify({
|
||||
status_operativo: stFinalizado.id,
|
||||
skip_survey: !sendSurvey // <- Flag que puedes usar en tu server.js si quieres anularlo
|
||||
})
|
||||
});
|
||||
|
||||
if(res.ok) {
|
||||
showToast(sendSurvey ? "Expediente finalizado y encuesta enviada." : "Expediente finalizado.");
|
||||
closeModal();
|
||||
refreshData();
|
||||
}
|
||||
} catch (e) { alert("Error al finalizar."); }
|
||||
}
|
||||
|
||||
async function saveAppointment() {
|
||||
|
||||
Reference in New Issue
Block a user