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">
<head>
<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>
<script src="https://cdn.tailwindcss.com"></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); } }
.no-scrollbar::-webkit-scrollbar { display: 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-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>
<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">
<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>
@@ -41,27 +44,27 @@
<div class="overflow-x-auto no-scrollbar py-2 -mx-5 px-5 flex gap-3" id="weekStrip"></div>
</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">
<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>
</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>
<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">
<div class="flex justify-around items-center p-3 bottom-nav-safe">
<a href="menu.html" class="flex flex-col items-center p-2 text-slate-400 hover:text-blue-600 transition-transform active:scale-95">
<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 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 w-20">
<i data-lucide="layout-grid" class="w-6 h-6 mb-1"></i>
<span class="text-[9px] font-black uppercase tracking-widest">Inicio</span>
</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>
<span class="text-[9px] font-black uppercase tracking-widest">Agenda</span>
</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>
<span class="text-[9px] font-bold uppercase tracking-widest">Salir</span>
</button>
@@ -69,13 +72,13 @@
</nav>
<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>
<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>
<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">
<div class="bg-white p-5 rounded-3xl shadow-sm border border-slate-100">
@@ -153,7 +156,7 @@
function safeLoadIcons() {
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) {
@@ -177,7 +180,6 @@
if (!localStorage.getItem("token") || localStorage.getItem("role") !== 'operario') {
window.location.href = "index.html"; return;
}
safeLoadIcons();
try {
@@ -186,16 +188,12 @@
selectedDateStr = toISODate(today);
buildWeekCalendar();
// Cargar datos paralelos
loadStatuses();
await loadGuilds();
refreshData();
} catch(error) {
alert("Error iniciando calendario: " + error.message);
}
} catch(error) { alert("Error iniciando calendario"); }
});
// Cargar gremios para saber los nombres
async function loadGuilds() {
try {
const res = await fetch(`${API_URL}/guilds`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
@@ -235,7 +233,7 @@
}
if(localServices.length > 0) updateBadges();
safeLoadIcons();
} catch(e) { console.error(e); }
} catch(e) {}
}
function changeWeek(offsetWeeks) {
@@ -256,7 +254,7 @@
const res = await fetch(`${API_URL}/statuses`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json();
if (data.ok) systemStatuses = data.statuses;
} catch (e) { console.error("Error estados:", e); }
} catch (e) {}
}
async function refreshData() {
@@ -275,12 +273,10 @@
const dateStr = String(raw.scheduled_date || "").trim();
return dateStr !== "";
});
buildWeekCalendar();
renderServices();
}
} catch (e) {
console.error(e);
} finally {
document.getElementById('loader').classList.add('hidden');
document.getElementById('dayTitle').classList.remove('hidden');
@@ -290,13 +286,11 @@
function updateBadges() {
document.querySelectorAll('[id^="badge-"]').forEach(el => el.classList.add('hidden'));
const counts = {};
localServices.forEach(s => {
const date = String(s.raw_data.scheduled_date || "").trim();
if(date) counts[date] = (counts[date] || 0) + 1;
});
for (const [date, qty] of Object.entries(counts)) {
const badge = document.getElementById(`badge-${date}`);
if (badge) badge.classList.remove('hidden');
@@ -335,7 +329,6 @@
const raw = s.raw_data || {};
const time = raw.scheduled_time || "A convenir";
// BLOQUEOS
if (s.provider === 'SYSTEM_BLOCK') {
const desc = raw["Descripción"] || "Operario no disponible";
return `
@@ -351,13 +344,11 @@
</div>`;
}
// SERVICIOS NORMALES
const name = raw["Nombre Cliente"] || raw["CLIENTE"] || "Asegurado";
const addr = raw["Dirección"] || "Sin dirección";
const pop = raw["Población"] || "";
const isUrgent = s.is_urgent;
// Extracción Compañía y Gremio
let compRaw = raw["Compañía"] || raw["COMPAÑIA"] || raw["Procedencia"] || "Particular";
let compShort = compRaw.split('-')[0].trim().substring(0, 15);
if(compRaw.includes("MULTI")) compShort = "MULTIASISTENCIA";
@@ -395,9 +386,6 @@
} catch(e) { console.error("Render error:", e); }
}
// ==========================================
// LÓGICA DEL MODAL Y GPS
// ==========================================
let currentServiceId = null;
function openService(id) {
@@ -413,18 +401,16 @@
const fullAddress = `${raw["Dirección"] || ""}, ${raw["Código Postal"] || ""} ${raw["Población"] || ""}`;
document.getElementById('detAddress').innerText = fullAddress;
// VOLCAR TODOS LOS DATOS EXTRA
const detailsContainer = document.getElementById('detExtraInfo');
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"];
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"];
for(let key in raw) {
if(skipKeys.includes(key)) continue;
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;
detailsHtml += `
@@ -438,12 +424,10 @@
if(detailsHtml === '') detailsHtml = '<p class="text-xs text-slate-400 font-medium">No hay más datos proporcionados.</p>';
detailsContainer.innerHTML = detailsHtml;
// Mostrar modal
const modal = document.getElementById('serviceModal');
modal.classList.remove('hidden');
setTimeout(() => modal.classList.remove('translate-y-full'), 10);
// Iniciar GPS automático
calculateDistance(fullAddress);
}
@@ -457,14 +441,12 @@
async function calculateDistance(destAddress) {
if(!navigator.geolocation) { showGpsError("GPS no soportado"); return; }
navigator.geolocation.getCurrentPosition(async (position) => {
const userLat = position.coords.latitude;
const userLon = position.coords.longitude;
try {
const res = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(destAddress + ', España')}`);
const data = await res.json();
if (data && data.length > 0) {
const destLat = parseFloat(data[0].lat);
const destLon = parseFloat(data[0].lon);