474 lines
28 KiB
HTML
474 lines
28 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Agenda - IntegraRepara</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://unpkg.com/lucide@latest"></script>
|
|
<style>
|
|
.fade-in { animation: fadeIn 0.3s ease-in-out; }
|
|
.scroller::-webkit-scrollbar { width: 6px; }
|
|
.scroller::-webkit-scrollbar-thumb { background-color: #cbd5e1; border-radius: 4px; }
|
|
.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:hover { color: #334155; }
|
|
</style>
|
|
</head>
|
|
<body class="bg-slate-50 text-slate-800 font-sans h-screen overflow-hidden flex">
|
|
|
|
<div id="sidebar-container" class="h-full shrink-0"></div>
|
|
|
|
<div class="flex-1 flex flex-col h-full relative min-w-0">
|
|
<div id="header-container"></div>
|
|
|
|
<main class="flex-1 flex flex-col overflow-hidden relative">
|
|
|
|
<div class="bg-white p-6 pb-0 z-20 shrink-0 border-b border-slate-100">
|
|
<h2 class="text-2xl font-black text-slate-800 mb-6 flex items-center gap-3">
|
|
<span class="bg-blue-100 p-2.5 rounded-xl text-blue-600 shadow-sm"><i data-lucide="calendar"></i></span>
|
|
Control de Agenda
|
|
</h2>
|
|
|
|
<div class="flex overflow-x-auto no-scrollbar gap-8 border-b border-slate-200">
|
|
<button onclick="switchTab('requests')" id="tab-requests" class="tab-btn tab-active px-2 py-3 text-sm whitespace-nowrap transition-colors">
|
|
Solicitudes Pendientes
|
|
</button>
|
|
<button onclick="switchTab('blocks')" id="tab-blocks" class="tab-btn tab-inactive px-2 py-3 text-sm whitespace-nowrap transition-colors">
|
|
Bloqueos de Agenda
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex-1 overflow-y-auto scroller p-6 bg-slate-50">
|
|
|
|
<div id="view-requests" class="max-w-5xl mx-auto space-y-4 tab-view">
|
|
<div class="text-center py-20" id="requestsContainer">
|
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin text-slate-300 mx-auto mb-4"></i>
|
|
<p class="font-bold text-slate-400">Cargando solicitudes...</p>
|
|
</div>
|
|
</div>
|
|
|
|
<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-start md:items-end">
|
|
<div class="flex-1 w-full space-y-4">
|
|
<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">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">
|
|
<option value="">Cargando operarios...</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<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">
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-2">
|
|
<div>
|
|
<label class="block text-xs font-black text-slate-500 uppercase tracking-widest mb-1.5">Desde</label>
|
|
<select id="blockTimeStart" 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="09:00">09:00</option><option value="10:00">10:00</option>
|
|
<option value="11:00">11:00</option><option value="12:00">12:00</option>
|
|
<option value="13:00">13:00</option><option value="16:00">16:00</option>
|
|
<option value="17:00">17:00</option><option value="18:00">18:00</option>
|
|
<option value="19:00">19:00</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-black text-slate-500 uppercase tracking-widest mb-1.5">Hasta</label>
|
|
<select id="blockTimeEnd" 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="10:00">10:00</option><option value="11:00">11:00</option>
|
|
<option value="12:00">12:00</option><option value="13:00">13:00</option>
|
|
<option value="14:00">14:00</option><option value="17:00">17:00</option>
|
|
<option value="18:00">18:00</option><option value="19:00">19:00</option>
|
|
<option value="20:00">20:00</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex-1 w-full space-y-4">
|
|
<div>
|
|
<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, 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>
|
|
<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
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white p-5 rounded-2xl border border-slate-200 shadow-sm flex flex-col md:flex-row items-center justify-between mt-8 gap-4">
|
|
<div class="flex items-center gap-3 w-full md:w-auto">
|
|
<div class="bg-blue-50 p-2.5 rounded-xl text-blue-600 shrink-0">
|
|
<i data-lucide="calendar-search" class="w-5 h-5"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-sm font-black text-slate-800 uppercase tracking-widest leading-none">Filtrar por Mes</h3>
|
|
<p class="text-[10px] text-slate-500 font-medium mt-1">Busca bloqueos pasados o futuros</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center gap-3 w-full md:w-auto">
|
|
<input type="month" id="blockMonthFilter" onchange="loadActiveBlocks()" class="w-full md:w-auto 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 cursor-pointer transition-all">
|
|
<button onclick="document.getElementById('blockMonthFilter').value=''; loadActiveBlocks();" class="text-xs font-bold text-slate-400 hover:text-red-500 transition-colors shrink-0">Ver Todos</button>
|
|
</div>
|
|
</div>
|
|
|
|
<h3 class="font-black text-slate-800 text-lg flex items-center gap-2 mt-8">
|
|
<i data-lucide="shield" class="w-5 h-5 text-rose-500"></i> Bloqueos Activos a Futuro
|
|
</h3>
|
|
<div id="blocksList" class="space-y-3 mt-4">
|
|
<p class="text-sm text-slate-400 font-medium">Cargando...</p>
|
|
</div>
|
|
|
|
<h3 class="font-black text-slate-800 text-lg flex items-center gap-2 mt-12 opacity-60 border-t border-slate-200 pt-6">
|
|
<i data-lucide="history" class="w-5 h-5 text-slate-400"></i> Historial Pasado
|
|
</h3>
|
|
<div id="pastBlocksList" class="space-y-3 mt-4 opacity-80 grayscale-[20%]">
|
|
<p class="text-sm text-slate-400 font-medium">Cargando...</p>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<div id="approveModal" class="fixed inset-0 bg-slate-900/60 hidden z-[100] flex items-center justify-center backdrop-blur-sm p-4">
|
|
<div class="bg-white rounded-[2rem] shadow-2xl w-full max-w-md overflow-hidden fade-in">
|
|
<div class="p-6 border-b border-slate-100 bg-slate-50 flex justify-between items-center">
|
|
<h3 class="font-black text-slate-800 text-lg">Confirmar Cita</h3>
|
|
<button onclick="closeApproveModal()" class="text-slate-400 hover:text-red-500"><i data-lucide="x"></i></button>
|
|
</div>
|
|
<div class="p-6 space-y-6">
|
|
<input type="hidden" id="aprvId">
|
|
|
|
<div class="bg-blue-50 p-4 rounded-2xl border border-blue-100 text-blue-800">
|
|
<p class="text-xs font-bold uppercase tracking-widest text-blue-500 mb-1">Cita Solicitada:</p>
|
|
<p class="font-black text-lg" id="aprvDateText"></p>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-xs font-black text-slate-600 uppercase tracking-widest mb-3">Duración estimada del trabajo</label>
|
|
<div class="relative">
|
|
<select id="aprvDuration" 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 appearance-none pr-8 cursor-pointer">
|
|
<option value="15">15 minutos</option>
|
|
<option value="30">30 minutos</option>
|
|
<option value="45">45 minutos</option>
|
|
<option value="60" selected>1 hora</option>
|
|
<option value="75">1 hora 15 min</option>
|
|
<option value="90">1 hora 30 min</option>
|
|
<option value="105">1 hora 45 min</option>
|
|
<option value="120">2 horas</option>
|
|
<option value="150">2 horas 30 min</option>
|
|
<option value="180">3 horas</option>
|
|
<option value="210">3 horas 30 min</option>
|
|
<option value="240">4 horas (Máx)</option>
|
|
</select>
|
|
<i data-lucide="clock" class="w-4 h-4 absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none"></i>
|
|
</div>
|
|
</div>
|
|
|
|
<button onclick="submitApproval()" id="btnSubmitAprv" class="w-full bg-slate-900 text-white font-black py-4 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="check-circle" class="w-4 h-4"></i> Bloquear Calendario
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="js/layout.js"></script>
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
if (!localStorage.getItem("token")) window.location.href = "index.html";
|
|
loadRequests();
|
|
loadOperatorsAndGuilds();
|
|
loadActiveBlocks();
|
|
});
|
|
|
|
// --- SISTEMA DE PESTAÑAS ---
|
|
function switchTab(tabId) {
|
|
document.querySelectorAll('.tab-view').forEach(el => el.classList.add('hidden'));
|
|
document.getElementById(`view-${tabId}`).classList.remove('hidden');
|
|
|
|
document.querySelectorAll('.tab-btn').forEach(el => {
|
|
el.classList.remove('tab-active');
|
|
el.classList.add('tab-inactive');
|
|
});
|
|
|
|
document.getElementById(`tab-${tabId}`).classList.remove('tab-inactive');
|
|
document.getElementById(`tab-${tabId}`).classList.add('tab-active');
|
|
}
|
|
|
|
// --- LÓGICA DE SOLICITUDES ---
|
|
async function loadRequests() {
|
|
const container = document.getElementById('requestsContainer');
|
|
try {
|
|
const res = await fetch(`${API_URL}/agenda/requests`, {
|
|
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
|
|
});
|
|
const data = await res.json();
|
|
|
|
if (!data.ok || data.requests.length === 0) {
|
|
container.innerHTML = `
|
|
<div class="text-center py-20 bg-white rounded-[2rem] border border-slate-100 shadow-sm">
|
|
<div class="w-20 h-20 bg-slate-50 text-slate-300 rounded-full flex items-center justify-center mx-auto mb-4"><i data-lucide="calendar-check" class="w-10 h-10"></i></div>
|
|
<h3 class="font-black text-slate-800 text-xl">Agenda al Día</h3>
|
|
<p class="text-slate-500 font-medium">No tienes solicitudes pendientes de confirmar.</p>
|
|
</div>
|
|
`;
|
|
lucide.createIcons();
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = data.requests.map(r => {
|
|
const raw = r.raw_data;
|
|
const dateParts = raw.requested_date.split('-');
|
|
const niceDate = `${dateParts[2]}/${dateParts[1]}/${dateParts[0]}`;
|
|
|
|
return `
|
|
<div class="bg-white p-6 rounded-[2rem] border border-slate-200 shadow-sm hover:shadow-md transition-shadow flex flex-col md:flex-row gap-6 justify-between items-start md:items-center">
|
|
<div class="flex items-start gap-4">
|
|
<div class="bg-amber-100 text-amber-600 p-4 rounded-2xl shrink-0">
|
|
<i data-lucide="calendar-clock" class="w-8 h-8"></i>
|
|
</div>
|
|
<div>
|
|
<div class="flex items-center gap-2 mb-1">
|
|
<span class="bg-slate-100 text-slate-600 text-[10px] font-black uppercase px-2 py-1 rounded-md">#${r.service_ref}</span>
|
|
<span class="bg-blue-50 text-blue-600 text-[10px] font-black uppercase px-2 py-1 rounded-md flex items-center gap-1"><i data-lucide="hard-hat" class="w-3 h-3"></i> ${r.assigned_name}</span>
|
|
</div>
|
|
<h3 class="font-black text-slate-800 text-lg uppercase">${raw["Nombre Cliente"] || "Cliente"}</h3>
|
|
<p class="text-sm font-medium text-slate-500 mt-1 flex items-center gap-1.5">
|
|
<i data-lucide="map-pin" class="w-4 h-4 text-slate-400"></i> ${raw["Población"] || raw["POBLACION-PROVINCIA"] || ""} - ${raw["Dirección"]}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-col items-end gap-4 w-full md:w-auto">
|
|
<div class="text-right">
|
|
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest">Fecha Propuesta</p>
|
|
<p class="font-black text-xl text-slate-800">${niceDate} <span class="text-blue-600 ml-2">${raw.requested_time}</span></p>
|
|
</div>
|
|
<div class="flex gap-2 w-full md:w-auto">
|
|
<button onclick="rejectRequest(${r.id})" class="flex-1 md:flex-none bg-white border border-rose-200 text-rose-600 hover:bg-rose-50 font-black px-5 py-2.5 rounded-xl transition-all shadow-sm">
|
|
Rechazar
|
|
</button>
|
|
<button onclick="openApproveModal(${r.id}, '${niceDate} a las ${raw.requested_time}')" class="flex-1 md:flex-none bg-emerald-500 hover:bg-emerald-600 text-white font-black px-6 py-2.5 rounded-xl transition-all shadow-md shadow-emerald-200">
|
|
Aprobar
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
lucide.createIcons();
|
|
|
|
} catch (e) {
|
|
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) {
|
|
document.getElementById('aprvId').value = id;
|
|
document.getElementById('aprvDateText').innerText = dateText;
|
|
document.getElementById('aprvDuration').value = "60"; // Reset al valor por defecto
|
|
document.getElementById('approveModal').classList.remove('hidden');
|
|
}
|
|
|
|
function closeApproveModal() {
|
|
document.getElementById('approveModal').classList.add('hidden');
|
|
}
|
|
|
|
async function submitApproval() {
|
|
const id = document.getElementById('aprvId').value;
|
|
const duration = document.getElementById('aprvDuration').value;
|
|
const btn = document.getElementById('btnSubmitAprv');
|
|
|
|
btn.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Confirmando...';
|
|
lucide.createIcons();
|
|
|
|
try {
|
|
const res = await fetch(`${API_URL}/agenda/requests/${id}/approve`, {
|
|
method: 'POST',
|
|
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
|
|
body: JSON.stringify({ duration: parseInt(duration) })
|
|
});
|
|
|
|
if (res.ok) {
|
|
closeApproveModal();
|
|
loadRequests();
|
|
} else {
|
|
alert("Error al confirmar la cita");
|
|
}
|
|
} catch (e) { alert("Error de conexión"); }
|
|
finally {
|
|
btn.innerHTML = '<i data-lucide="check-circle" class="w-4 h-4"></i> Bloquear Calendario';
|
|
lucide.createIcons();
|
|
}
|
|
}
|
|
|
|
async function rejectRequest(id) {
|
|
if(!confirm("¿Rechazar esta solicitud? Se enviará un WhatsApp al cliente pidiéndole que elija otra hora.")) return;
|
|
try {
|
|
const res = await fetch(`${API_URL}/agenda/requests/${id}/reject`, {
|
|
method: 'POST', headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
|
|
});
|
|
if (res.ok) loadRequests();
|
|
else alert("Error al rechazar");
|
|
} catch (e) { alert("Error de conexión"); }
|
|
}
|
|
|
|
// --- LÓGICA DE BLOQUEOS DE AGENDA Y GREMIOS ---
|
|
async function loadOperatorsAndGuilds() {
|
|
try {
|
|
// Cargar Operarios
|
|
const resOp = await fetch(`${API_URL}/operators`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
|
|
const dataOp = await resOp.json();
|
|
if(dataOp.ok) {
|
|
document.getElementById('blockWorker').innerHTML = '<option value="">Seleccione Operario...</option>' +
|
|
dataOp.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); }
|
|
}
|
|
|
|
async function saveBlock() {
|
|
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 timeStart = document.getElementById('blockTimeStart').value;
|
|
const timeEnd = document.getElementById('blockTimeEnd').value;
|
|
const reason = document.getElementById('blockReason').value || (guildId ? `Bloqueo de ${guildName}` : "Bloqueo Total");
|
|
|
|
if(!workerId || !date) return alert("Falta operario o fecha");
|
|
|
|
const startMins = parseInt(timeStart.split(':')[0]) * 60;
|
|
const endMins = parseInt(timeEnd.split(':')[0]) * 60;
|
|
const duration = endMins - startMins;
|
|
|
|
if(duration <= 0) return alert("La hora de fin debe ser mayor a la de inicio");
|
|
|
|
try {
|
|
const res = await fetch(`${API_URL}/agenda/blocks`, {
|
|
method: 'POST',
|
|
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
|
|
body: JSON.stringify({ worker_id: workerId, date: date, time: timeStart, duration: duration, reason: reason, guild_id: guildId, guild_name: guildName })
|
|
});
|
|
|
|
if (res.ok) {
|
|
alert("Bloqueo guardado correctamente");
|
|
loadActiveBlocks();
|
|
}
|
|
} catch(e) { alert("Error de conexión"); }
|
|
}
|
|
|
|
async function loadActiveBlocks() {
|
|
const activeContainer = document.getElementById('blocksList');
|
|
const pastContainer = document.getElementById('pastBlocksList');
|
|
|
|
// Leemos el valor del filtro de mes (Ej: "2026-02")
|
|
const monthFilter = document.getElementById('blockMonthFilter').value;
|
|
|
|
try {
|
|
const res = await fetch(`${API_URL}/agenda/blocks`, {
|
|
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
|
|
});
|
|
const data = await res.json();
|
|
|
|
if (!data.ok) throw new Error("Error fetching blocks");
|
|
|
|
let allBlocks = data.blocks;
|
|
|
|
// APLICAMOS EL FILTRO POR MES SI HAY ALGUNO SELECCIONADO
|
|
if (monthFilter) {
|
|
// Nos quedamos solo con los bloqueos cuya fecha empiece por "YYYY-MM"
|
|
allBlocks = allBlocks.filter(b => b.date.startsWith(monthFilter));
|
|
}
|
|
|
|
// Sacamos la fecha de hoy (YYYY-MM-DD) para comparar
|
|
const todayStr = new Date().toISOString().split('T')[0];
|
|
|
|
const activeBlocks = allBlocks.filter(b => b.date >= todayStr).reverse();
|
|
const pastBlocks = allBlocks.filter(b => b.date < todayStr);
|
|
|
|
// Pintar Activos
|
|
if (activeBlocks.length === 0) {
|
|
activeContainer.innerHTML = `<p class="text-sm text-slate-400 italic bg-white p-4 rounded-xl border border-slate-200 text-center shadow-sm">No hay bloqueos activos en este periodo.</p>`;
|
|
} else {
|
|
activeContainer.innerHTML = activeBlocks.map(b => buildBlockHtml(b, false)).join('');
|
|
}
|
|
|
|
// Pintar Caducados
|
|
if (pastBlocks.length === 0) {
|
|
pastContainer.innerHTML = `<p class="text-sm text-slate-400 italic bg-transparent p-4 text-center">No hay bloqueos antiguos en este periodo.</p>`;
|
|
} else {
|
|
pastContainer.innerHTML = pastBlocks.map(b => buildBlockHtml(b, true)).join('');
|
|
}
|
|
|
|
lucide.createIcons();
|
|
} catch(e) {
|
|
activeContainer.innerHTML = "Error cargando";
|
|
pastContainer.innerHTML = "Error cargando";
|
|
}
|
|
}
|
|
|
|
// Helper visual para pintar la tarjeta según sea activa o caducada
|
|
function buildBlockHtml(b, isPast) {
|
|
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 `
|
|
<div class="bg-white p-4 rounded-xl border ${isPast ? 'border-slate-100 shadow-none' : 'border-slate-200 shadow-sm'} flex justify-between items-center transition-all hover:border-red-200">
|
|
<div>
|
|
<div class="flex items-center gap-2">
|
|
<p class="font-black ${isPast ? 'text-slate-500' : 'text-slate-800'} flex items-center gap-1.5">
|
|
<i data-lucide="hard-hat" class="w-4 h-4 ${isPast ? 'text-slate-300' : 'text-slate-400'}"></i> ${b.worker_name}
|
|
</p>
|
|
${badge}
|
|
</div>
|
|
<p class="text-xs ${isPast ? 'text-slate-400' : 'text-slate-600'} font-bold mt-1.5">${b.date} | ${b.time} (${b.duration} min)</p>
|
|
<p class="text-[10px] text-slate-400 uppercase mt-0.5">${b.reason || 'Sin motivo especificado'}</p>
|
|
</div>
|
|
<button onclick="deleteBlock(${b.id})" class="text-slate-300 hover:text-red-500 bg-slate-50 hover:bg-red-50 p-2.5 rounded-lg transition-colors">
|
|
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
|
</button>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
async function deleteBlock(id) {
|
|
if(!confirm("¿Eliminar este bloqueo?")) return;
|
|
try {
|
|
const res = await fetch(`${API_URL}/agenda/blocks/${id}`, {
|
|
method: 'DELETE',
|
|
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
|
|
});
|
|
if(res.ok) loadActiveBlocks();
|
|
} catch(e) { alert("Error"); }
|
|
}
|
|
|
|
</script>
|
|
</body>
|
|
</html> |