Actualizar servicios.html

This commit is contained in:
2026-02-16 21:42:04 +00:00
parent e4d18f3a72
commit ee5842c259

View File

@@ -16,47 +16,67 @@
<body class="bg-slate-50 text-slate-800 font-sans antialiased text-left">
<div class="flex h-screen overflow-hidden">
<div id="sidebar-container" class="h-full"></div>
<div class="flex-1 flex flex-col overflow-hidden">
<div id="sidebar-container" class="h-full shrink-0"></div>
<div class="flex-1 flex flex-col overflow-hidden relative">
<div id="header-container"></div>
<main class="flex-1 overflow-y-auto p-6 no-scrollbar text-left">
<div class="flex justify-between items-center mb-8">
<div>
<h2 class="text-2xl font-black text-slate-800 tracking-tight">PANEL OPERATIVO</h2>
<p class="text-sm text-slate-500 font-medium">Tablero Kanban de gestión de expedientes.</p>
</div>
<button onclick="openCreateModal()" class="bg-slate-900 hover:bg-blue-600 text-white px-6 py-3 rounded-2xl shadow-xl flex items-center gap-3 font-black text-xs uppercase tracking-widest transition-all active:scale-95">
<i data-lucide="plus-circle" class="w-5 h-5"></i> Nuevo Servicio
</button>
</div>
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-slate-50 p-6 no-scrollbar text-left relative">
<div class="fade-in max-w-full mx-auto space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
<div class="space-y-4 bg-slate-100/50 p-4 rounded-[2rem] border border-slate-200/60">
<div class="flex items-center gap-2 px-2 pb-2 border-b border-slate-200">
<div class="w-2.5 h-2.5 rounded-full bg-rose-500 shadow-[0_0_10px_rgba(244,63,94,0.5)]"></div>
<h3 class="font-black text-slate-600 uppercase text-[10px] tracking-widest">Sin Asignar / En Pausa</h3>
<div class="flex justify-between items-center bg-white p-6 rounded-[2rem] shadow-sm border border-slate-100">
<div>
<h2 class="text-2xl font-black text-slate-800 tracking-tight flex items-center gap-3">
<span class="bg-blue-600 p-2.5 rounded-xl text-white shadow-lg shadow-blue-200"><i data-lucide="kanban"></i></span>
PANEL OPERATIVO
</h2>
<p class="text-sm text-slate-500 mt-1 font-medium">Tablero Kanban de gestión de expedientes.</p>
</div>
<div id="unassigned-list" class="space-y-3"></div>
<button onclick="openCreateModal()" class="bg-slate-900 hover:bg-blue-600 text-white px-6 py-3.5 rounded-2xl shadow-xl flex items-center gap-3 font-black text-xs uppercase tracking-widest transition-all active:scale-95">
<i data-lucide="plus-circle" class="w-5 h-5"></i> Nuevo Servicio
</button>
</div>
<div class="space-y-4 bg-slate-100/50 p-4 rounded-[2rem] border border-slate-200/60">
<div class="flex items-center gap-2 px-2 pb-2 border-b border-slate-200">
<div class="w-2.5 h-2.5 rounded-full bg-blue-500 shadow-[0_0_10px_rgba(59,130,246,0.5)]"></div>
<h3 class="font-black text-slate-600 uppercase text-[10px] tracking-widest">Asignados (Falta Fecha)</h3>
<div class="flex flex-wrap gap-4 items-center bg-white p-4 rounded-[1.5rem] shadow-sm border border-slate-100 mb-6">
<div class="relative flex-1 min-w-[250px]">
<i data-lucide="search" class="w-4 h-4 absolute left-4 top-1/2 -translate-y-1/2 text-slate-400"></i>
<input type="text" id="searchFilter" oninput="renderLists()" placeholder="Buscar por cliente, REF, población, teléfono..." class="w-full pl-11 pr-4 py-3 bg-slate-50 border border-slate-200 rounded-xl text-xs font-bold focus:ring-2 focus:ring-blue-500 outline-none transition-all">
</div>
<div id="pending-list" class="space-y-3"></div>
</div>
<div class="space-y-4 bg-slate-100/50 p-4 rounded-[2rem] border border-slate-200/60">
<div class="flex items-center gap-2 px-2 pb-2 border-b border-slate-200">
<div class="w-2.5 h-2.5 rounded-full bg-emerald-500 shadow-[0_0_10px_rgba(16,185,129,0.5)]"></div>
<h3 class="font-black text-slate-600 uppercase text-[10px] tracking-widest">Citados / En Curso</h3>
<div class="relative w-full md:w-64">
<select id="opFilter" onchange="renderLists()" class="w-full bg-slate-50 border border-slate-200 text-xs font-black px-4 py-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500 uppercase tracking-widest text-slate-600 appearance-none pr-10 cursor-pointer">
<option value="ALL">TODOS LOS OPERARIOS</option>
</select>
<i data-lucide="chevron-down" class="w-4 h-4 absolute right-4 top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none"></i>
</div>
<div id="assigned-list" class="space-y-3"></div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
<div class="space-y-4 bg-slate-100/50 p-4 rounded-[2rem] border border-slate-200/60">
<div class="flex items-center gap-2 px-2 pb-2 border-b border-slate-200">
<div class="w-2.5 h-2.5 rounded-full bg-rose-500 shadow-[0_0_10px_rgba(244,63,94,0.5)]"></div>
<h3 class="font-black text-slate-600 uppercase text-[10px] tracking-widest">Sin Asignar / En Pausa</h3>
</div>
<div id="unassigned-list" class="space-y-3"></div>
</div>
<div class="space-y-4 bg-slate-100/50 p-4 rounded-[2rem] border border-slate-200/60">
<div class="flex items-center gap-2 px-2 pb-2 border-b border-slate-200">
<div class="w-2.5 h-2.5 rounded-full bg-blue-500 shadow-[0_0_10px_rgba(59,130,246,0.5)]"></div>
<h3 class="font-black text-slate-600 uppercase text-[10px] tracking-widest">Asignados (Falta Fecha)</h3>
</div>
<div id="pending-list" class="space-y-3"></div>
</div>
<div class="space-y-4 bg-slate-100/50 p-4 rounded-[2rem] border border-slate-200/60">
<div class="flex items-center gap-2 px-2 pb-2 border-b border-slate-200">
<div class="w-2.5 h-2.5 rounded-full bg-emerald-500 shadow-[0_0_10px_rgba(16,185,129,0.5)]"></div>
<h3 class="font-black text-slate-600 uppercase text-[10px] tracking-widest">Citados / En Curso</h3>
</div>
<div id="assigned-list" class="space-y-3"></div>
</div>
</div>
</div>
</main>
</div>
@@ -212,15 +232,65 @@
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
});
const data = await res.json();
if (data.ok) { localData = data.services; renderLists(); }
if (data.ok) {
localData = data.services;
updateOperatorFilter(); // Actualiza el select de operarios
renderLists();
}
} catch (e) { console.error(e); }
}
function updateOperatorFilter() {
const opSelect = document.getElementById('opFilter');
const currentValue = opSelect.value;
// Extraer nombres únicos de operarios asignados en este panel
const uniqueOps = [...new Set(localData.map(s => s.assigned_name).filter(Boolean))].sort();
let html = '<option value="ALL">TODOS LOS OPERARIOS</option>';
uniqueOps.forEach(op => {
html += `<option value="${op}">${op}</option>`;
});
opSelect.innerHTML = html;
// Restaurar la selección previa si sigue existiendo
if (html.includes(`value="${currentValue}"`)) {
opSelect.value = currentValue;
}
}
function renderLists() {
const searchTerm = document.getElementById('searchFilter').value.toLowerCase();
const selectedOp = document.getElementById('opFilter').value;
// FILTRO DE DATOS
const filteredData = localData.filter(s => {
const raw = s.raw_data;
const name = (raw["Nombre Cliente"] || raw["CLIENTE"] || "").toLowerCase();
const addr = (raw["Dirección"] || raw["DOMICILIO"] || "").toLowerCase();
const pop = (raw["Población"] || raw["POBLACION-PROVINCIA"] || "").toLowerCase();
const phone = (raw["Teléfono"] || raw["TELEFONO"] || "").toLowerCase();
const ref = (s.service_ref || "").toLowerCase();
const assigned = s.assigned_name || "";
// Buscar texto (Nombre, Ref, Dirección, Población, Teléfono)
const matchesSearch = searchTerm === "" ||
name.includes(searchTerm) ||
ref.includes(searchTerm) ||
addr.includes(searchTerm) ||
pop.includes(searchTerm) ||
phone.includes(searchTerm);
// Buscar Operario
const matchesOp = selectedOp === "ALL" || assigned === selectedOp;
return matchesSearch && matchesOp;
});
// Lógica inteligente de 3 columnas apoyada en el raw_data
const unassigned = localData.filter(s => !s.assigned_name || s.raw_data.status_operativo === 'sin_asignar');
const pending = localData.filter(s => s.assigned_name && (!s.raw_data.scheduled_date || s.raw_data.scheduled_date === "") && s.raw_data.status_operativo !== 'sin_asignar');
const assigned = localData.filter(s => s.raw_data.scheduled_date && s.raw_data.scheduled_date !== "" && s.raw_data.status_operativo !== 'sin_asignar');
const unassigned = filteredData.filter(s => !s.assigned_name || s.raw_data.status_operativo === 'sin_asignar');
const pending = filteredData.filter(s => s.assigned_name && (!s.raw_data.scheduled_date || s.raw_data.scheduled_date === "") && s.raw_data.status_operativo !== 'sin_asignar');
const assigned = filteredData.filter(s => s.raw_data.scheduled_date && s.raw_data.scheduled_date !== "" && s.raw_data.status_operativo !== 'sin_asignar');
document.getElementById('unassigned-list').innerHTML = unassigned.length > 0
? unassigned.map(s => cardTemplate(s, 'rose', s.assigned_name ? 'Pausado' : 'Sin Asignar')).join('')
@@ -281,7 +351,6 @@
document.getElementById('detId').value = s.id;
document.getElementById('detRef').innerText = s.service_ref;
// COMPAÑÍA ASEGURADORA
const companyName = raw['Compañía'] || raw['COMPAÑIA'] || raw['Procedencia'] || "Particular";
document.getElementById('detCompany').innerText = companyName;
@@ -301,7 +370,6 @@
document.getElementById('dateInput').value = raw.scheduled_date || "";
document.getElementById('timeInput').value = raw.scheduled_time || "";
// Si no tiene estado previo guardado, mostramos "citado" por defecto o "sin asignar" si procede.
let defaultStatus = raw.status_operativo;
if (!defaultStatus) {
defaultStatus = (!s.assigned_name) ? 'sin_asignar' : 'citado';