Actualizar agenda.html

This commit is contained in:
2026-02-21 16:55:39 +00:00
parent b4c0270ded
commit d053daa495

View File

@@ -10,7 +10,6 @@
.fade-in { animation: fadeIn 0.3s ease-in-out; } .fade-in { animation: fadeIn 0.3s ease-in-out; }
.scroller::-webkit-scrollbar { width: 6px; } .scroller::-webkit-scrollbar { width: 6px; }
.scroller::-webkit-scrollbar-thumb { background-color: #cbd5e1; border-radius: 4px; } .scroller::-webkit-scrollbar-thumb { background-color: #cbd5e1; border-radius: 4px; }
/* Clases para tabs activas/inactivas */
.tab-active { color: #2563eb; border-bottom-width: 3px; border-color: #2563eb; font-weight: 900; } .tab-active { color: #2563eb; border-bottom-width: 3px; border-color: #2563eb; font-weight: 900; }
.tab-inactive { color: #64748b; font-weight: 500; border-bottom-width: 3px; border-color: transparent; } .tab-inactive { color: #64748b; font-weight: 500; border-bottom-width: 3px; border-color: transparent; }
.tab-inactive:hover { color: #334155; } .tab-inactive:hover { color: #334155; }
@@ -52,18 +51,26 @@
<div id="view-blocks" class="max-w-5xl mx-auto space-y-6 tab-view hidden fade-in"> <div id="view-blocks" class="max-w-5xl mx-auto space-y-6 tab-view hidden fade-in">
<div class="bg-white p-6 rounded-[2rem] border border-slate-200 shadow-sm flex flex-col md:flex-row gap-6 items-end"> <div class="bg-white p-6 rounded-[2rem] border border-slate-200 shadow-sm flex flex-col md:flex-row gap-6 items-start md:items-end">
<div class="flex-1 w-full space-y-4"> <div class="flex-1 w-full space-y-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>
<label class="block text-xs font-black text-slate-500 uppercase tracking-widest mb-1.5">1. Selecciona Operario</label> <label class="block text-xs font-black text-slate-500 uppercase tracking-widest mb-1.5">1. Operario</label>
<select id="blockWorker" class="w-full bg-slate-50 border border-slate-200 text-sm font-bold text-slate-700 p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500"> <select id="blockWorker" class="w-full bg-slate-50 border border-slate-200 text-sm font-bold text-slate-700 p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500">
<option value="">Cargando operarios...</option> <option value="">Cargando operarios...</option>
</select> </select>
</div> </div>
<div class="grid grid-cols-2 gap-4">
<div> <div>
<label class="block text-xs font-black text-slate-500 uppercase tracking-widest mb-1.5">2. Fecha</label> <label class="block text-xs font-black text-slate-500 uppercase tracking-widest mb-1.5">2. Gremio (Opcional)</label>
<select id="blockGuild" class="w-full bg-slate-50 border border-slate-200 text-sm font-bold text-slate-700 p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500">
<option value="">Bloqueo Total (Todos los gremios)</option>
</select>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-black text-slate-500 uppercase tracking-widest mb-1.5">3. Fecha</label>
<input type="date" id="blockDate" class="w-full bg-slate-50 border border-slate-200 text-sm font-bold text-slate-700 p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500"> <input type="date" id="blockDate" class="w-full bg-slate-50 border border-slate-200 text-sm font-bold text-slate-700 p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500">
</div> </div>
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
@@ -94,7 +101,7 @@
<div class="flex-1 w-full space-y-4"> <div class="flex-1 w-full space-y-4">
<div> <div>
<label class="block text-xs font-black text-slate-500 uppercase tracking-widest mb-1.5">Motivo (Opcional)</label> <label class="block text-xs font-black text-slate-500 uppercase tracking-widest mb-1.5">Motivo (Opcional)</label>
<input type="text" id="blockReason" placeholder="Ej: Médico, Asuntos propios, Vacaciones..." class="w-full bg-slate-50 border border-slate-200 text-sm font-bold text-slate-700 p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500"> <input type="text" id="blockReason" placeholder="Ej: Médico, Curso, Saturación..." class="w-full bg-slate-50 border border-slate-200 text-sm font-bold text-slate-700 p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500">
</div> </div>
<button onclick="saveBlock()" class="w-full bg-slate-900 text-white font-black py-3 rounded-xl shadow-lg hover:bg-blue-600 transition-all uppercase tracking-widest text-xs flex items-center justify-center gap-2"> <button onclick="saveBlock()" class="w-full bg-slate-900 text-white font-black py-3 rounded-xl shadow-lg hover:bg-blue-600 transition-all uppercase tracking-widest text-xs flex items-center justify-center gap-2">
<i data-lucide="lock" class="w-4 h-4"></i> Generar Bloqueo <i data-lucide="lock" class="w-4 h-4"></i> Generar Bloqueo
@@ -155,10 +162,8 @@
<script> <script>
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
if (!localStorage.getItem("token")) window.location.href = "index.html"; if (!localStorage.getItem("token")) window.location.href = "index.html";
// Pre-seleccionar pestaña 1
loadRequests(); loadRequests();
// Cargar datos para pestaña 2 en segundo plano loadOperatorsAndGuilds();
loadOperators();
loadActiveBlocks(); loadActiveBlocks();
}); });
@@ -239,11 +244,13 @@
}).join(''); }).join('');
lucide.createIcons(); lucide.createIcons();
} catch (e) { } catch (e) {
container.innerHTML = '<p class="text-center text-red-500 font-bold">Error de conexión</p>'; container.innerHTML = '<p class="text-center text-red-500 font-bold">Error de conexión</p>';
} }
} }
// --- FUNCIONES DEL MODAL DE APROBACIÓN ---
function openApproveModal(id, dateText) { function openApproveModal(id, dateText) {
document.getElementById('aprvId').value = id; document.getElementById('aprvId').value = id;
document.getElementById('aprvDateText').innerText = dateText; document.getElementById('aprvDateText').innerText = dateText;
@@ -303,31 +310,39 @@
} catch (e) { alert("Error de conexión"); } } catch (e) { alert("Error de conexión"); }
} }
// --- LÓGICA DE BLOQUEOS DE AGENDA --- // --- LÓGICA DE BLOQUEOS DE AGENDA Y GREMIOS ---
async function loadOperators() { async function loadOperatorsAndGuilds() {
try { try {
const res = await fetch(`${API_URL}/operators`, { // Cargar Operarios
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } const resOp = await fetch(`${API_URL}/operators`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
}); const dataOp = await resOp.json();
const data = await res.json(); if(dataOp.ok) {
if(data.ok) { document.getElementById('blockWorker').innerHTML = '<option value="">Seleccione Operario...</option>' +
const sel = document.getElementById('blockWorker'); dataOp.operators.map(o => `<option value="${o.id}">${o.full_name}</option>`).join('');
sel.innerHTML = '<option value="">Seleccione Operario...</option>' + }
data.operators.map(o => `<option value="${o.id}">${o.full_name}</option>`).join('');
// Cargar Gremios
const resG = await fetch(`${API_URL}/guilds`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const dataG = await resG.json();
if(dataG.ok) {
document.getElementById('blockGuild').innerHTML = '<option value="">Bloqueo Total (Todos los gremios)</option>' +
dataG.guilds.map(g => `<option value="${g.id}">${g.name}</option>`).join('');
} }
} catch (e) { console.error(e); } } catch (e) { console.error(e); }
} }
async function saveBlock() { async function saveBlock() {
const workerId = document.getElementById('blockWorker').value; const workerId = document.getElementById('blockWorker').value;
const guildSelect = document.getElementById('blockGuild');
const guildId = guildSelect.value;
const guildName = guildId ? guildSelect.options[guildSelect.selectedIndex].text : null;
const date = document.getElementById('blockDate').value; const date = document.getElementById('blockDate').value;
const timeStart = document.getElementById('blockTimeStart').value; const timeStart = document.getElementById('blockTimeStart').value;
const timeEnd = document.getElementById('blockTimeEnd').value; const timeEnd = document.getElementById('blockTimeEnd').value;
const reason = document.getElementById('blockReason').value || "Bloqueo Administrativo"; const reason = document.getElementById('blockReason').value || (guildId ? `Bloqueo de ${guildName}` : "Bloqueo Total");
if(!workerId || !date) return alert("Falta operario o fecha"); if(!workerId || !date) return alert("Falta operario o fecha");
// Calculamos duración en minutos
const startMins = parseInt(timeStart.split(':')[0]) * 60; const startMins = parseInt(timeStart.split(':')[0]) * 60;
const endMins = parseInt(timeEnd.split(':')[0]) * 60; const endMins = parseInt(timeEnd.split(':')[0]) * 60;
const duration = endMins - startMins; const duration = endMins - startMins;
@@ -338,7 +353,7 @@
const res = await fetch(`${API_URL}/agenda/blocks`, { const res = await fetch(`${API_URL}/agenda/blocks`, {
method: 'POST', method: 'POST',
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }, headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
body: JSON.stringify({ worker_id: workerId, date: date, time: timeStart, duration: duration, reason: reason }) body: JSON.stringify({ worker_id: workerId, date: date, time: timeStart, duration: duration, reason: reason, guild_id: guildId, guild_name: guildName })
}); });
if (res.ok) { if (res.ok) {
@@ -362,13 +377,21 @@
} }
container.innerHTML = data.blocks.map(b => { container.innerHTML = data.blocks.map(b => {
// Etiqueta visual si es de un gremio concreto o total
const badge = b.guild_name
? `<span class="bg-indigo-100 text-indigo-700 px-2 py-0.5 rounded text-[9px] uppercase tracking-widest font-black">Solo ${b.guild_name}</span>`
: `<span class="bg-rose-100 text-rose-700 px-2 py-0.5 rounded text-[9px] uppercase tracking-widest font-black">Bloqueo Total</span>`;
return ` return `
<div class="bg-white p-4 rounded-xl border border-rose-100 flex justify-between items-center shadow-sm"> <div class="bg-white p-4 rounded-xl border border-slate-200 flex justify-between items-center shadow-sm">
<div> <div>
<p class="font-black text-slate-800 flex items-center gap-2"> <div class="flex items-center gap-2">
<p class="font-black text-slate-800 flex items-center gap-1.5">
<i data-lucide="hard-hat" class="w-4 h-4 text-slate-400"></i> ${b.worker_name} <i data-lucide="hard-hat" class="w-4 h-4 text-slate-400"></i> ${b.worker_name}
</p> </p>
<p class="text-xs text-rose-600 font-bold mt-1">${b.date} | ${b.time} (${b.duration} min)</p> ${badge}
</div>
<p class="text-xs text-slate-600 font-bold mt-1.5">${b.date} | ${b.time} (${b.duration} min)</p>
<p class="text-[10px] text-slate-500 uppercase mt-0.5">${b.reason}</p> <p class="text-[10px] text-slate-500 uppercase mt-0.5">${b.reason}</p>
</div> </div>
<button onclick="deleteBlock(${b.id})" class="text-slate-300 hover:text-red-500 p-2 transition-colors"> <button onclick="deleteBlock(${b.id})" class="text-slate-300 hover:text-red-500 p-2 transition-colors">