Actualizar calendario.html

This commit is contained in:
2026-02-22 17:34:31 +00:00
parent 446c5a05e6
commit e9e43d7272

View File

@@ -2,7 +2,7 @@
<html lang="es"> <html lang="es">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Calendario - IntegraRepara</title> <title>Calendario - IntegraRepara</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script> <script src="https://unpkg.com/lucide@latest"></script>
@@ -12,7 +12,10 @@
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
.no-scrollbar::-webkit-scrollbar { display: none; } .no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; } .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
.bottom-nav-safe { padding-bottom: env(safe-area-inset-bottom); }
/* Ajustes IOS Notch Scroll */
.safe-bottom-nav { padding-bottom: calc(env(safe-area-inset-bottom) + 12px); }
.main-content { padding-bottom: calc(env(safe-area-inset-bottom) + 90px); }
.day-card { transition: all 0.2s; } .day-card { transition: all 0.2s; }
.day-active { background-color: #2563eb; color: white; border-color: #2563eb; transform: scale(1.05); box-shadow: 0 10px 15px -3px rgba(37,99,235,0.4); } .day-active { background-color: #2563eb; color: white; border-color: #2563eb; transform: scale(1.05); box-shadow: 0 10px 15px -3px rgba(37,99,235,0.4); }
@@ -21,7 +24,7 @@
</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">
<header class="bg-white px-5 pt-8 pb-4 shadow-sm z-20 shrink-0 border-b border-slate-100"> <header class="bg-white px-5 pt-safe mt-6 pb-4 shadow-sm z-20 shrink-0 border-b border-slate-100">
<div class="flex items-center gap-3 mb-4"> <div class="flex items-center gap-3 mb-4">
<a href="menu.html" class="w-10 h-10 shrink-0 bg-slate-50 rounded-full flex items-center justify-center text-slate-600 border border-slate-200 active:bg-slate-100"> <a href="menu.html" class="w-10 h-10 shrink-0 bg-slate-50 rounded-full flex items-center justify-center text-slate-600 border border-slate-200 active:bg-slate-100">
<i data-lucide="arrow-left" class="w-5 h-5"></i> <i data-lucide="arrow-left" class="w-5 h-5"></i>
@@ -41,27 +44,27 @@
<div class="overflow-x-auto no-scrollbar py-2 -mx-5 px-5 flex gap-3" id="weekStrip"></div> <div class="overflow-x-auto no-scrollbar py-2 -mx-5 px-5 flex gap-3" id="weekStrip"></div>
</header> </header>
<main class="flex-1 overflow-y-auto no-scrollbar p-5 relative z-10" id="mainArea"> <main class="flex-1 overflow-y-auto no-scrollbar p-5 main-content relative z-10" id="mainArea">
<div id="loader" class="text-center py-10 opacity-50"> <div id="loader" class="text-center py-10 opacity-50">
<i data-lucide="loader-2" class="w-8 h-8 animate-spin mx-auto text-blue-500 mb-2"></i> <i data-lucide="loader-2" class="w-8 h-8 animate-spin mx-auto text-blue-500 mb-2"></i>
<p class="text-xs font-bold uppercase tracking-widest">Cargando agenda...</p> <p class="text-xs font-bold uppercase tracking-widest">Cargando agenda...</p>
</div> </div>
<div id="dayTitle" class="font-black text-slate-400 uppercase tracking-widest text-[10px] mb-4 hidden">Servicios para hoy</div> <div id="dayTitle" class="font-black text-slate-400 uppercase tracking-widest text-[10px] mb-4 hidden">Servicios para hoy</div>
<div id="servicesList" class="space-y-4 pb-24 hidden fade-in"></div> <div id="servicesList" class="space-y-4 hidden fade-in"></div>
</main> </main>
<nav class="bg-white border-t border-slate-200 shrink-0 pb-safe z-20 absolute bottom-0 w-full shadow-[0_-10px_30px_rgba(0,0,0,0.05)] rounded-t-3xl"> <nav class="bg-white/90 backdrop-blur-md border-t border-slate-200 z-20 absolute bottom-0 left-0 w-full shadow-[0_-10px_30px_rgba(0,0,0,0.05)] rounded-t-[2rem]">
<div class="flex justify-around items-center p-3 bottom-nav-safe"> <div class="flex justify-around items-center px-3 pt-3 safe-bottom-nav">
<a href="menu.html" class="flex flex-col items-center p-2 text-slate-400 hover:text-blue-600 transition-transform active:scale-95"> <a href="menu.html" class="flex flex-col items-center p-2 text-slate-400 hover:text-blue-600 transition-transform active:scale-95 w-20">
<i data-lucide="layout-grid" class="w-6 h-6 mb-1"></i> <i data-lucide="layout-grid" class="w-6 h-6 mb-1"></i>
<span class="text-[9px] font-black uppercase tracking-widest">Inicio</span> <span class="text-[9px] font-black uppercase tracking-widest">Inicio</span>
</a> </a>
<a href="calendario.html" class="flex flex-col items-center p-2 text-blue-600 transition-transform active:scale-95"> <a href="calendario.html" class="flex flex-col items-center p-2 text-blue-600 transition-transform active:scale-95 w-20">
<i data-lucide="calendar-days" class="w-6 h-6 mb-1"></i> <i data-lucide="calendar-days" class="w-6 h-6 mb-1"></i>
<span class="text-[9px] font-black uppercase tracking-widest">Agenda</span> <span class="text-[9px] font-black uppercase tracking-widest">Agenda</span>
</a> </a>
<button onclick="logout()" class="flex flex-col items-center p-2 text-slate-400 hover:text-red-500 transition-transform active:scale-95"> <button onclick="logout()" class="flex flex-col items-center p-2 text-slate-400 hover:text-red-500 transition-transform active:scale-95 w-20">
<i data-lucide="log-out" class="w-6 h-6 mb-1"></i> <i data-lucide="log-out" class="w-6 h-6 mb-1"></i>
<span class="text-[9px] font-bold uppercase tracking-widest">Salir</span> <span class="text-[9px] font-bold uppercase tracking-widest">Salir</span>
</button> </button>
@@ -69,13 +72,13 @@
</nav> </nav>
<div id="serviceModal" class="fixed inset-0 bg-slate-50 z-[100] hidden flex-col translate-y-full transition-transform duration-300"> <div id="serviceModal" class="fixed inset-0 bg-slate-50 z-[100] hidden flex-col translate-y-full transition-transform duration-300">
<div class="p-5 border-b border-slate-200 flex justify-between items-center bg-white shrink-0 shadow-sm relative z-20"> <div class="p-5 pt-safe border-b border-slate-200 flex justify-between items-center bg-white shrink-0 shadow-sm relative z-20">
<button onclick="closeModal()" class="bg-slate-50 p-2.5 rounded-xl border border-slate-200 text-slate-600 active:bg-slate-100"><i data-lucide="chevron-down" class="w-6 h-6"></i></button> <button onclick="closeModal()" class="bg-slate-50 p-2.5 rounded-xl border border-slate-200 text-slate-600 active:bg-slate-100"><i data-lucide="chevron-down" class="w-6 h-6"></i></button>
<h3 class="font-black text-sm uppercase tracking-widest text-slate-800" id="modTime">10:00 - 11:00</h3> <h3 class="font-black text-sm uppercase tracking-widest text-slate-800" id="modTime">10:00 - 11:00</h3>
<div class="w-10"></div> <div class="w-10"></div>
</div> </div>
<div class="flex-1 overflow-y-auto p-5 space-y-5 no-scrollbar pb-10"> <div class="flex-1 overflow-y-auto p-5 space-y-5 no-scrollbar pb-24">
<input type="hidden" id="detId"> <input type="hidden" id="detId">
<div class="bg-white p-5 rounded-3xl shadow-sm border border-slate-100"> <div class="bg-white p-5 rounded-3xl shadow-sm border border-slate-100">
@@ -153,7 +156,7 @@
function safeLoadIcons() { function safeLoadIcons() {
try { if (typeof lucide !== 'undefined') lucide.createIcons(); } try { if (typeof lucide !== 'undefined') lucide.createIcons(); }
catch(e) { console.warn("Iconos no cargados aún"); } catch(e) { console.warn("Iconos no cargados"); }
} }
function toISODate(dateObj) { function toISODate(dateObj) {
@@ -177,7 +180,6 @@
if (!localStorage.getItem("token") || localStorage.getItem("role") !== 'operario') { if (!localStorage.getItem("token") || localStorage.getItem("role") !== 'operario') {
window.location.href = "index.html"; return; window.location.href = "index.html"; return;
} }
safeLoadIcons(); safeLoadIcons();
try { try {
@@ -186,16 +188,12 @@
selectedDateStr = toISODate(today); selectedDateStr = toISODate(today);
buildWeekCalendar(); buildWeekCalendar();
// Cargar datos paralelos
loadStatuses(); loadStatuses();
await loadGuilds(); await loadGuilds();
refreshData(); refreshData();
} catch(error) { } catch(error) { alert("Error iniciando calendario"); }
alert("Error iniciando calendario: " + error.message);
}
}); });
// Cargar gremios para saber los nombres
async function loadGuilds() { async function loadGuilds() {
try { try {
const res = await fetch(`${API_URL}/guilds`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } }); const res = await fetch(`${API_URL}/guilds`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
@@ -235,7 +233,7 @@
} }
if(localServices.length > 0) updateBadges(); if(localServices.length > 0) updateBadges();
safeLoadIcons(); safeLoadIcons();
} catch(e) { console.error(e); } } catch(e) {}
} }
function changeWeek(offsetWeeks) { function changeWeek(offsetWeeks) {
@@ -256,7 +254,7 @@
const res = await fetch(`${API_URL}/statuses`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } }); const res = await fetch(`${API_URL}/statuses`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json(); const data = await res.json();
if (data.ok) systemStatuses = data.statuses; if (data.ok) systemStatuses = data.statuses;
} catch (e) { console.error("Error estados:", e); } } catch (e) {}
} }
async function refreshData() { async function refreshData() {
@@ -275,12 +273,10 @@
const dateStr = String(raw.scheduled_date || "").trim(); const dateStr = String(raw.scheduled_date || "").trim();
return dateStr !== ""; return dateStr !== "";
}); });
buildWeekCalendar(); buildWeekCalendar();
renderServices(); renderServices();
} }
} catch (e) { } catch (e) {
console.error(e);
} finally { } finally {
document.getElementById('loader').classList.add('hidden'); document.getElementById('loader').classList.add('hidden');
document.getElementById('dayTitle').classList.remove('hidden'); document.getElementById('dayTitle').classList.remove('hidden');
@@ -290,13 +286,11 @@
function updateBadges() { function updateBadges() {
document.querySelectorAll('[id^="badge-"]').forEach(el => el.classList.add('hidden')); document.querySelectorAll('[id^="badge-"]').forEach(el => el.classList.add('hidden'));
const counts = {}; const counts = {};
localServices.forEach(s => { localServices.forEach(s => {
const date = String(s.raw_data.scheduled_date || "").trim(); const date = String(s.raw_data.scheduled_date || "").trim();
if(date) counts[date] = (counts[date] || 0) + 1; if(date) counts[date] = (counts[date] || 0) + 1;
}); });
for (const [date, qty] of Object.entries(counts)) { for (const [date, qty] of Object.entries(counts)) {
const badge = document.getElementById(`badge-${date}`); const badge = document.getElementById(`badge-${date}`);
if (badge) badge.classList.remove('hidden'); if (badge) badge.classList.remove('hidden');
@@ -335,7 +329,6 @@
const raw = s.raw_data || {}; const raw = s.raw_data || {};
const time = raw.scheduled_time || "A convenir"; const time = raw.scheduled_time || "A convenir";
// BLOQUEOS
if (s.provider === 'SYSTEM_BLOCK') { if (s.provider === 'SYSTEM_BLOCK') {
const desc = raw["Descripción"] || "Operario no disponible"; const desc = raw["Descripción"] || "Operario no disponible";
return ` return `
@@ -351,13 +344,11 @@
</div>`; </div>`;
} }
// SERVICIOS NORMALES
const name = raw["Nombre Cliente"] || raw["CLIENTE"] || "Asegurado"; const name = raw["Nombre Cliente"] || raw["CLIENTE"] || "Asegurado";
const addr = raw["Dirección"] || "Sin dirección"; const addr = raw["Dirección"] || "Sin dirección";
const pop = raw["Población"] || ""; const pop = raw["Población"] || "";
const isUrgent = s.is_urgent; const isUrgent = s.is_urgent;
// Extracción Compañía y Gremio
let compRaw = raw["Compañía"] || raw["COMPAÑIA"] || raw["Procedencia"] || "Particular"; let compRaw = raw["Compañía"] || raw["COMPAÑIA"] || raw["Procedencia"] || "Particular";
let compShort = compRaw.split('-')[0].trim().substring(0, 15); let compShort = compRaw.split('-')[0].trim().substring(0, 15);
if(compRaw.includes("MULTI")) compShort = "MULTIASISTENCIA"; if(compRaw.includes("MULTI")) compShort = "MULTIASISTENCIA";
@@ -395,9 +386,6 @@
} catch(e) { console.error("Render error:", e); } } catch(e) { console.error("Render error:", e); }
} }
// ==========================================
// LÓGICA DEL MODAL Y GPS
// ==========================================
let currentServiceId = null; let currentServiceId = null;
function openService(id) { function openService(id) {
@@ -413,18 +401,16 @@
const fullAddress = `${raw["Dirección"] || ""}, ${raw["Código Postal"] || ""} ${raw["Población"] || ""}`; const fullAddress = `${raw["Dirección"] || ""}, ${raw["Código Postal"] || ""} ${raw["Población"] || ""}`;
document.getElementById('detAddress').innerText = fullAddress; document.getElementById('detAddress').innerText = fullAddress;
// VOLCAR TODOS LOS DATOS EXTRA
const detailsContainer = document.getElementById('detExtraInfo'); const detailsContainer = document.getElementById('detExtraInfo');
let detailsHtml = ''; let detailsHtml = '';
// Ignoramos los campos que ya están enseñados arriba por defecto const skipKeys = ["Nombre Cliente", "CLIENTE", "Dirección", "DOMICILIO", "Población", "POBLACION-PROVINCIA", "scheduled_date", "scheduled_time", "status_operativo", "assigned_to", "guild_id", "Código Postal", "assigned_to_name"];
const skipKeys = ["Nombre Cliente", "CLIENTE", "Dirección", "DOMICILIO", "Población", "POBLACION-PROVINCIA", "scheduled_date", "scheduled_time", "status_operativo", "assigned_to", "guild_id", "Código Postal"];
for(let key in raw) { for(let key in raw) {
if(skipKeys.includes(key)) continue; if(skipKeys.includes(key)) continue;
let val = raw[key]; let val = raw[key];
if(typeof val === 'object') val = JSON.stringify(val); // Por si es un JSON anidado if(typeof val === 'object') val = JSON.stringify(val);
if(!val || val.trim() === "") continue; if(!val || val.trim() === "") continue;
detailsHtml += ` detailsHtml += `
@@ -438,12 +424,10 @@
if(detailsHtml === '') detailsHtml = '<p class="text-xs text-slate-400 font-medium">No hay más datos proporcionados.</p>'; if(detailsHtml === '') detailsHtml = '<p class="text-xs text-slate-400 font-medium">No hay más datos proporcionados.</p>';
detailsContainer.innerHTML = detailsHtml; detailsContainer.innerHTML = detailsHtml;
// Mostrar modal
const modal = document.getElementById('serviceModal'); const modal = document.getElementById('serviceModal');
modal.classList.remove('hidden'); modal.classList.remove('hidden');
setTimeout(() => modal.classList.remove('translate-y-full'), 10); setTimeout(() => modal.classList.remove('translate-y-full'), 10);
// Iniciar GPS automático
calculateDistance(fullAddress); calculateDistance(fullAddress);
} }
@@ -457,14 +441,12 @@
async function calculateDistance(destAddress) { async function calculateDistance(destAddress) {
if(!navigator.geolocation) { showGpsError("GPS no soportado"); return; } if(!navigator.geolocation) { showGpsError("GPS no soportado"); return; }
navigator.geolocation.getCurrentPosition(async (position) => { navigator.geolocation.getCurrentPosition(async (position) => {
const userLat = position.coords.latitude; const userLat = position.coords.latitude;
const userLon = position.coords.longitude; const userLon = position.coords.longitude;
try { try {
const res = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(destAddress + ', España')}`); const res = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(destAddress + ', España')}`);
const data = await res.json(); const data = await res.json();
if (data && data.length > 0) { if (data && data.length > 0) {
const destLat = parseFloat(data[0].lat); const destLat = parseFloat(data[0].lat);
const destLon = parseFloat(data[0].lon); const destLon = parseFloat(data[0].lon);