Actualizar calendario.html

This commit is contained in:
2026-02-21 17:44:03 +00:00
parent 2018ab94f4
commit b8391e1f89

View File

@@ -8,13 +8,14 @@
<script src="https://unpkg.com/lucide@latest"></script> <script src="https://unpkg.com/lucide@latest"></script>
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.js'></script> <script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.js'></script>
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/locales/es.global.min.js'></script>
<style> <style>
.fade-in { animation: fadeIn 0.3s ease-in-out; } .fade-in { animation: fadeIn 0.3s ease-in-out; }
@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; }
/* CUSTOMIZACIÓN PREMIUM DEL CALENDARIO PARA QUE NO PAREZCA DE LOS AÑOS 90 */ /* CUSTOMIZACIÓN PREMIUM DEL CALENDARIO */
.fc { font-family: inherit; } .fc { font-family: inherit; }
.fc-toolbar-title { font-weight: 900 !important; font-size: 1.25rem !important; text-transform: uppercase; color: #1e293b; letter-spacing: -0.025em; } .fc-toolbar-title { font-weight: 900 !important; font-size: 1.25rem !important; text-transform: uppercase; color: #1e293b; letter-spacing: -0.025em; }
@@ -39,30 +40,32 @@
.fc-col-header-cell-cushion { padding: 12px 0 !important; font-size: 0.75rem; text-transform: uppercase; font-weight: 900; color: #64748b; } .fc-col-header-cell-cushion { padding: 12px 0 !important; font-size: 0.75rem; text-transform: uppercase; font-weight: 900; color: #64748b; }
.fc-timegrid-slot-label-cushion { font-size: 0.7rem; font-weight: 800; color: #94a3b8; } .fc-timegrid-slot-label-cushion { font-size: 0.7rem; font-weight: 800; color: #94a3b8; }
/* CORRECCIÓN: Forzamos fondo transparente en la caja del evento para que mande nuestro HTML interno */
.fc-event { .fc-event {
border: none !important; border: none !important;
border-radius: 0.75rem !important; border-radius: 0.5rem !important;
padding: 4px; background-color: transparent !important;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); box-shadow: none !important;
transition: transform 0.2s; transition: transform 0.2s;
cursor: pointer; cursor: pointer;
} }
.fc-event:hover { transform: scale(1.02); z-index: 50 !important; } .fc-event:hover { transform: scale(1.02); z-index: 50 !important; }
.fc-v-event .fc-event-main-frame { padding: 4px; } .fc-event-main { padding: 0 !important; border-radius: 0.5rem !important; }
.fc-v-event .fc-event-main-frame { padding: 0 !important; }
.fc-today-button { background-color: #0f172a !important; color: white !important; } .fc-today-button { background-color: #0f172a !important; color: white !important; }
.fc-timegrid-col.fc-day-today { background-color: #f8fafc !important; } .fc-timegrid-col.fc-day-today { background-color: #f8fafc !important; }
/* ESTILO PARA EL POPOVER (La ventanita gris pequeña al pulsar "+X más") */ /* ESTILO PARA EL POPOVER (La ventanita "+X más") */
.fc-popover { .fc-popover {
border: none !important; border: none !important;
border-radius: 1rem !important; border-radius: 1rem !important;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1) !important; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1) !important;
overflow: hidden; overflow: hidden;
z-index: 200 !important;
} }
.fc-popover-header { .fc-popover-header {
background-color: #3b82f6 !important; /* Azul corporativo */ background-color: #2563eb !important;
color: white !important; color: white !important;
font-weight: 900 !important; font-weight: 900 !important;
text-transform: uppercase; text-transform: uppercase;
@@ -70,17 +73,16 @@
font-size: 0.8rem !important; font-size: 0.8rem !important;
letter-spacing: 0.05em; letter-spacing: 0.05em;
} }
.fc-popover-body { .fc-popover-body {
padding: 0.75rem !important; padding: 0.75rem !important;
background-color: #f8fafc !important; /* Fondo gris muy claro */ background-color: #f8fafc !important;
}
.fc-popover-close-icon {
color: white !important;
opacity: 0.7;
} }
.fc-popover-close-icon { color: white !important; opacity: 0.7; }
.fc-popover-close-icon:hover { opacity: 1; } .fc-popover-close-icon:hover { opacity: 1; }
/* Ocultamos los puntos por defecto del popover para usar nuestras tarjetas completas */
.fc-daygrid-event-dot { display: none !important; }
.fc-popover .fc-event { margin-bottom: 0.5rem !important; }
</style> </style>
</head> </head>
<body class="bg-gray-50 text-gray-800 font-sans antialiased overflow-hidden text-left"> <body class="bg-gray-50 text-gray-800 font-sans antialiased overflow-hidden text-left">
@@ -240,11 +242,13 @@
const timeIso = r.scheduled_time ? r.scheduled_time + ':00' : '09:00:00'; const timeIso = r.scheduled_time ? r.scheduled_time + ':00' : '09:00:00';
const startStr = `${dateIso}T${timeIso}`; const startStr = `${dateIso}T${timeIso}`;
// Calcular la fecha/hora de fin sumando los minutos (si existen, por defecto 60 min) // Calcular la fecha/hora de fin sumando los minutos
const startObj = new Date(startStr); const startObj = new Date(startStr);
const duration = parseInt(r.duration_minutes || 60); const duration = parseInt(r.duration_minutes || 60);
const endObj = new Date(startObj.getTime() + duration * 60000); const endObj = new Date(startObj.getTime() + duration * 60000);
const endStr = endObj.toISOString().slice(0, 19); // YYYY-MM-DDTHH:mm:ss
// 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) // Color según estado (o si es bloqueo)
const status = isBlock ? 'SYSTEM_BLOCK' : (r.status_operativo || 'citado'); const status = isBlock ? 'SYSTEM_BLOCK' : (r.status_operativo || 'citado');
@@ -275,14 +279,15 @@
calendarInstance = new FullCalendar.Calendar(calendarEl, { calendarInstance = new FullCalendar.Calendar(calendarEl, {
initialView: 'timeGridWeek', initialView: 'timeGridWeek',
locale: 'es', locale: 'es', // FORZAMOS IDIOMA ESPAÑOL PARA LA VENTANITA
firstDay: 1, firstDay: 1,
slotMinTime: '08:00:00', slotMinTime: '08:00:00',
slotMaxTime: '22:00:00', slotMaxTime: '22:00:00',
expandRows: true, expandRows: true,
allDaySlot: false, allDaySlot: false,
nowIndicator: true, nowIndicator: true,
dayMaxEvents: true, dayMaxEvents: 3, // Cuántas caben antes de mostrar "X más"
moreLinkText: "más", // TRADUCCIÓN MANUAL POR SI ACASO
buttonText: { buttonText: {
today: 'Hoy', today: 'Hoy',
month: 'Mes', month: 'Mes',
@@ -297,22 +302,29 @@
}, },
events: calendarEvents, events: calendarEvents,
// INYECCIÓN DE HTML Y COLOR DENTRO DEL EVENTO
eventContent: function(arg) { eventContent: function(arg) {
const timeText = arg.timeText; // 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'});
}
const title = arg.event.title; const title = arg.event.title;
const op = arg.event.extendedProps.operatorName; const op = arg.event.extendedProps.operatorName;
const isBlock = arg.event.extendedProps.isBlock; const isBlock = arg.event.extendedProps.isBlock;
const bgColor = arg.event.backgroundColor; // Recuperamos el color oficial
return { return {
html: ` html: `
<div class="flex flex-col h-full justify-start p-1 text-white overflow-hidden font-sans"> <div class="flex flex-col h-full w-full justify-start p-1.5 text-white overflow-hidden font-sans rounded-lg shadow-sm" style="background-color: ${bgColor};">
<div class="flex justify-between items-center mb-0.5"> <div class="flex justify-between items-center mb-1">
<span class="text-[9px] font-black bg-black/10 px-1.5 py-0.5 rounded-md leading-none whitespace-nowrap">${timeText}</span> <span class="text-[9px] font-black bg-black/20 px-1.5 py-0.5 rounded-md leading-none whitespace-nowrap">${timeText}</span>
<p class="text-[9px] font-bold opacity-90 truncate ml-1"> <p class="text-[9px] font-bold opacity-90 truncate ml-1">
<i data-lucide="${isBlock ? 'shield-alert' : 'user'}" class="w-2.5 h-2.5 inline mr-0.5"></i>${op} <i data-lucide="${isBlock ? 'shield-alert' : 'user'}" class="w-2.5 h-2.5 inline mr-0.5"></i>${op}
</p> </p>
</div> </div>
<p class="text-[10px] font-black leading-tight tracking-tight truncate">${title}</p> <p class="text-[10px] font-black leading-tight tracking-tight whitespace-normal line-clamp-2">${title}</p>
</div>` </div>`
}; };
}, },
@@ -334,13 +346,11 @@
const filteredEvents = calendarEvents.filter(ev => { const filteredEvents = calendarEvents.filter(ev => {
const props = ev.extendedProps; const props = ev.extendedProps;
// Si es un bloqueo, lo mostramos siempre si cumple el filtro de operario, // Los bloqueos se muestran siempre si cumplen el filtro de operario
// e ignoramos el texto/compañía para que el admin siempre vea los bloqueos de esa persona.
if (props.isBlock) { if (props.isBlock) {
return (opFilter === 'ALL' || props.operatorName === opFilter); return (opFilter === 'ALL' || props.operatorName === opFilter);
} }
// Filtrado normal
const matchText = ev.title.toUpperCase().includes(textFilter) || props.originalData.service_ref.toUpperCase().includes(textFilter); const matchText = ev.title.toUpperCase().includes(textFilter) || props.originalData.service_ref.toUpperCase().includes(textFilter);
const matchOp = opFilter === 'ALL' || props.operatorName === opFilter; const matchOp = opFilter === 'ALL' || props.operatorName === opFilter;
const matchComp = compFilter === 'ALL' || props.companyName === compFilter; const matchComp = compFilter === 'ALL' || props.companyName === compFilter;