diff --git a/calendario.html b/calendario.html index 7ce09f3..24cf651 100644 --- a/calendario.html +++ b/calendario.html @@ -180,16 +180,28 @@ let calendarEvents = []; let calendarInstance = null; - // Diccionario de colores. AÑADIMOS EL BLOQUEO (Rojo vivo) + // DICCIONARIO DE COLORES E ICONOS const statusConfig = { - 'citado': { bg: '#3b82f6', border: '#2563eb', label: 'Citado / Agendado' }, // Azul - 'de_camino': { bg: '#8b5cf6', border: '#7c3aed', label: 'De Camino' }, // Morado - 'trabajando': { bg: '#f59e0b', border: '#d97706', label: 'Trabajando en lugar' }, // Naranja - 'incidencia': { bg: '#ef4444', border: '#dc2626', label: 'Pausado / Incidencia' }, // Rojo - 'terminado': { bg: '#10b981', border: '#059669', label: 'Trabajo Terminado' }, // Verde - 'SYSTEM_BLOCK': { bg: '#f43f5e', border: '#e11d48', label: 'BLOQUEO DE AGENDA' } // Rosa fuerte + 'citado': { bg: '#3b82f6', border: '#2563eb', label: 'Citado / Agendado', icon: 'calendar' }, + 'de_camino': { bg: '#8b5cf6', border: '#7c3aed', label: 'Técnico de Camino', icon: 'truck' }, // COCHE/CAMIÓN + 'trabajando': { bg: '#f59e0b', border: '#d97706', label: 'En Reparación', icon: 'hammer' }, // MARTILLO + 'incidencia': { bg: '#ef4444', border: '#dc2626', label: 'Pausado / Incidencia', icon: 'alert-triangle' }, + 'terminado': { bg: '#10b981', border: '#059669', label: 'Trabajo Terminado', icon: 'check-circle' }, + 'SYSTEM_BLOCK': { bg: '#f43f5e', border: '#e11d48', label: 'BLOQUEO DE AGENDA', icon: 'shield-alert' } }; + // Función para detectar la clave correcta aunque venga el ID o un texto variable + function getStatusKey(status) { + if (!status) return 'citado'; + const s = String(status).toLowerCase(); + if (s.includes('camino')) return 'de_camino'; + if (s.includes('trabajando') || s.includes('reparacion')) return 'trabajando'; + if (s.includes('incidencia') || s.includes('pausado')) return 'incidencia'; + if (s.includes('terminado') || s.includes('finalizado')) return 'terminado'; + if (s === 'system_block') return 'SYSTEM_BLOCK'; + return 'citado'; + } + document.addEventListener("DOMContentLoaded", async () => { if (!localStorage.getItem("token")) window.location.href = "index.html"; lucide.createIcons(); @@ -210,17 +222,16 @@ async function loadServices() { try { - // Traemos los servicios activos const res = await fetch(`${API_URL}/services/active`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } }); const data = await res.json(); if (data.ok) { - // Filtramos solo los que tienen fecha de inicio programada rawServices = data.services.filter(s => s.raw_data && s.raw_data.scheduled_date && s.raw_data.scheduled_date !== ""); - // Extraemos compañías (ignorando los bloqueos) const compSelect = document.getElementById('compFilter'); const uniqueComps = [...new Set(rawServices.filter(s => s.provider !== 'SYSTEM_BLOCK').map(s => (s.raw_data['Compañía'] || s.raw_data['COMPAÑIA'] || "Particular").toString().toUpperCase()))].sort(); + + compSelect.innerHTML = ''; uniqueComps.forEach(c => compSelect.innerHTML += ``); processEventsAndRender(); @@ -235,24 +246,19 @@ const name = r['Nombre Cliente'] || r['CLIENTE'] || "Sin Nombre"; const pop = r['Población'] || r['POBLACION-PROVINCIA'] || ""; - const title = isBlock ? `BLOQUEADO: ${r['Descripción'] || 'Motivo no especificado'}` : `${name} - ${pop}`; + const title = isBlock ? `BLOQUEO: ${r['Descripción'] || 'Motivo'}` : `${name} - ${pop}`; - // Formato ISO: YYYY-MM-DDTHH:mm:00 const dateIso = r.scheduled_date; const timeIso = r.scheduled_time ? r.scheduled_time + ':00' : '09:00:00'; const startStr = `${dateIso}T${timeIso}`; - // Calcular la fecha/hora de fin sumando los minutos const startObj = new Date(startStr); const duration = parseInt(r.duration_minutes || 60); const endObj = new Date(startObj.getTime() + duration * 60000); - - // Ajuste de offset horario local para la ISO String final const endStr = new Date(endObj.getTime() - (endObj.getTimezoneOffset() * 60000)).toISOString().slice(0, 19); - // Color según estado (o si es bloqueo) - const status = isBlock ? 'SYSTEM_BLOCK' : (r.status_operativo || 'citado'); - const sConf = statusConfig[status] || statusConfig['citado']; + const statusKey = isBlock ? 'SYSTEM_BLOCK' : getStatusKey(r.status_operativo); + const sConf = statusConfig[statusKey] || statusConfig['citado']; return { id: svc.id, @@ -271,7 +277,12 @@ }; }); - initCalendar(); + if (calendarInstance) { + calendarInstance.removeAllEventSources(); + calendarInstance.addEventSource(calendarEvents); + } else { + initCalendar(); + } } function initCalendar() { @@ -279,22 +290,16 @@ calendarInstance = new FullCalendar.Calendar(calendarEl, { initialView: 'timeGridWeek', - locale: 'es', // FORZAMOS IDIOMA ESPAÑOL PARA LA VENTANITA + locale: 'es', firstDay: 1, slotMinTime: '08:00:00', slotMaxTime: '22:00:00', expandRows: true, allDaySlot: false, nowIndicator: true, - dayMaxEvents: 3, // Cuántas caben antes de mostrar "X más" - moreLinkText: "más", // TRADUCCIÓN MANUAL POR SI ACASO - buttonText: { - today: 'Hoy', - month: 'Mes', - week: 'Semana', - day: 'Día', - list: 'Agenda' - }, + dayMaxEvents: 3, + moreLinkText: "más", + buttonText: { today: 'Hoy', month: 'Mes', week: 'Semana', day: 'Día', list: 'Agenda' }, headerToolbar: { left: 'prev,next today', center: 'title', @@ -302,9 +307,7 @@ }, events: calendarEvents, - // INYECCIÓN DE HTML Y COLOR DENTRO DEL EVENTO eventContent: function(arg) { - // Extraemos la hora para la tarjeta let timeText = arg.timeText; if (!timeText && arg.event.start) { timeText = arg.event.start.toLocaleTimeString('es-ES', {hour: '2-digit', minute:'2-digit'}); @@ -313,29 +316,35 @@ const title = arg.event.title; const op = arg.event.extendedProps.operatorName; const isBlock = arg.event.extendedProps.isBlock; - const bgColor = arg.event.backgroundColor; // Recuperamos el color oficial + const conf = arg.event.extendedProps.statusConf; + const bgColor = arg.event.backgroundColor; return { html: ` -
+
${timeText} -

- ${op} -

+

${title}

+
+ + ${op} +
` }; }, + eventDidMount: function() { + lucide.createIcons(); + }, + eventClick: function(info) { openEventDetails(info.event.extendedProps); } }); calendarInstance.render(); - setTimeout(() => lucide.createIcons(), 100); } function applyFilters() { @@ -345,13 +354,9 @@ const filteredEvents = calendarEvents.filter(ev => { const props = ev.extendedProps; - - // Los bloqueos se muestran siempre si cumplen el filtro de operario - if (props.isBlock) { - return (opFilter === 'ALL' || props.operatorName === opFilter); - } + if (props.isBlock) return (opFilter === 'ALL' || props.operatorName === opFilter); - const matchText = ev.title.toUpperCase().includes(textFilter) || props.originalData.service_ref.toUpperCase().includes(textFilter); + const matchText = ev.title.toUpperCase().includes(textFilter) || String(props.originalData.service_ref).toUpperCase().includes(textFilter); const matchOp = opFilter === 'ALL' || props.operatorName === opFilter; const matchComp = compFilter === 'ALL' || props.companyName === compFilter; @@ -360,6 +365,7 @@ calendarInstance.removeAllEventSources(); calendarInstance.addEventSource(filteredEvents); + setTimeout(() => lucide.createIcons(), 50); } function openEventDetails(props) { @@ -372,7 +378,6 @@ header.style.backgroundColor = conf.bg; document.getElementById('emStatusLabel').innerText = conf.label; document.getElementById('emRef').innerText = isBlock ? 'BLOQUEO TÉCNICO' : `EXP. #${ref}`; - document.getElementById('emOperator').innerText = props.operatorName; if (isBlock) { @@ -385,25 +390,20 @@ } else { document.getElementById('emClientLabel').innerText = "Asegurado / Cliente"; document.getElementById('emName').innerText = raw['Nombre Cliente'] || raw['CLIENTE'] || "Desconocido"; - const phone = (raw['Teléfono'] || raw['TELEFONOS'] || raw['TELEFONO'] || "").match(/[6789]\d{8}/)?.[0] || "Sin Teléfono"; document.getElementById('emPhone').innerText = phone; document.getElementById('emPhoneLink').classList.remove('hidden'); document.getElementById('emPhoneLink').href = phone !== "Sin Teléfono" ? `tel:+34${phone}` : '#'; - document.getElementById('boxCompany').classList.remove('hidden'); document.getElementById('emCompany').innerText = props.companyName; - document.getElementById('boxAddress').classList.remove('hidden'); const addr = raw['Dirección'] || raw['DOMICILIO'] || ""; const pop = raw['Población'] || raw['POBLACION-PROVINCIA'] || ""; document.getElementById('emAddress').innerText = `${addr}, ${pop} (CP: ${raw['Código Postal'] || '---'})`; - document.getElementById('emDescLabel').innerText = "Descripción de la Avería"; } document.getElementById('emDesc').innerHTML = (raw['Descripción'] || raw['DESCRIPCION'] || "Sin información.").replace(/\n/g, '
'); - document.getElementById('eventModal').classList.remove('hidden'); lucide.createIcons(); }