Añadir agenda.html

This commit is contained in:
2026-02-21 16:05:14 +00:00
parent 0796d14098
commit 686482a5aa

226
agenda.html Normal file
View File

@@ -0,0 +1,226 @@
<!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; }
</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">
<button class="px-2 py-3 text-sm font-black text-blue-600 border-b-[3px] border-blue-600 whitespace-nowrap">
Solicitudes Pendientes
</button>
</div>
</div>
<div class="flex-1 overflow-y-auto scroller p-6 bg-slate-50">
<div class="max-w-5xl mx-auto space-y-4" id="requestsContainer">
<div class="text-center py-20">
<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>
</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="grid grid-cols-3 gap-2">
<button type="button" onclick="setDuration(15, this)" class="dur-btn bg-white border-2 border-slate-200 text-slate-600 font-bold py-2 rounded-xl transition-all hover:bg-slate-50">15 min</button>
<button type="button" onclick="setDuration(30, this)" class="dur-btn bg-white border-2 border-slate-200 text-slate-600 font-bold py-2 rounded-xl transition-all hover:bg-slate-50">30 min</button>
<button type="button" onclick="setDuration(45, this)" class="dur-btn bg-white border-2 border-slate-200 text-slate-600 font-bold py-2 rounded-xl transition-all hover:bg-slate-50">45 min</button>
<button type="button" onclick="setDuration(60, this)" class="dur-btn bg-blue-600 border-2 border-blue-600 text-white font-bold py-2 rounded-xl transition-all shadow-md selected">1 hora</button>
<button type="button" onclick="setDuration(90, this)" class="dur-btn bg-white border-2 border-slate-200 text-slate-600 font-bold py-2 rounded-xl transition-all hover:bg-slate-50">1.5 horas</button>
<button type="button" onclick="setDuration(120, this)" class="dur-btn bg-white border-2 border-slate-200 text-slate-600 font-bold py-2 rounded-xl transition-all hover:bg-slate-50">2 horas</button>
<button type="button" onclick="setDuration(180, this)" class="dur-btn bg-white border-2 border-slate-200 text-slate-600 font-bold py-2 rounded-xl transition-all hover:bg-slate-50">3 horas</button>
<button type="button" onclick="setDuration(240, this)" class="dur-btn bg-white border-2 border-slate-200 text-slate-600 font-bold py-2 rounded-xl transition-all hover:bg-slate-50">4 horas</button>
</div>
<input type="hidden" id="aprvDuration" value="60">
</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();
});
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('approveModal').classList.remove('hidden');
}
function closeApproveModal() {
document.getElementById('approveModal').classList.add('hidden');
}
function setDuration(mins, btnEl) {
document.getElementById('aprvDuration').value = mins;
document.querySelectorAll('.dur-btn').forEach(b => {
b.classList.remove('bg-blue-600', 'text-white', 'border-blue-600', 'shadow-md', 'selected');
b.classList.add('bg-white', 'text-slate-600', 'border-slate-200');
});
btnEl.classList.remove('bg-white', 'text-slate-600', 'border-slate-200');
btnEl.classList.add('bg-blue-600', 'text-white', 'border-blue-600', 'shadow-md', 'selected');
}
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(); // Recarga la lista
} 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"); }
}
</script>
</body>
</html>