Actualizar servicios.html

This commit is contained in:
2026-03-02 08:41:20 +00:00
parent f740e10cf9
commit 7bcc46431a

View File

@@ -142,6 +142,13 @@
<div> <div>
<label class="label-modern">Teléfono</label> <label class="label-modern">Teléfono</label>
<input type="tel" id="nPhone" placeholder="Ej: 600123456" class="input-modern" required> <input type="tel" id="nPhone" placeholder="Ej: 600123456" class="input-modern" required>
<div id="addrSuggestions" class="hidden mt-2 p-3 bg-amber-50 border border-amber-200 rounded-xl text-[10px] font-bold text-amber-700 flex items-center gap-2">
<i data-lucide="info" class="w-3 h-3"></i>
<span>Este cliente ya tiene direcciones guardadas. Puedes usar la actual o escribir una nueva.</span>
</div>
</div> </div>
<div> <div>
<label class="label-modern">Nombre Completo</label> <label class="label-modern">Nombre Completo</label>
@@ -383,6 +390,40 @@
</div> </div>
<script src="js/layout.js"></script> <script src="js/layout.js"></script>
// --- FUNCIÓN PARA BUSCAR CLIENTE POR TELÉFONO AL ESCRIBIR ---
async function searchClientByPhone(phone) {
if (phone.length < 9) {
document.getElementById('addrSuggestions').classList.add('hidden');
return;
}
try {
const res = await fetch(`${API_URL}/clients/search?phone=${phone}`, {
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
});
const data = await res.json();
if (data.ok && data.client) {
// Rellenar nombre si está vacío
const nameInput = document.getElementById('nName');
if (!nameInput.value) {
nameInput.value = data.client.full_name;
nameInput.classList.add('bg-emerald-50'); // Efecto visual de éxito
}
// Mostrar aviso de direcciones si existen
const addrDiv = document.getElementById('addrSuggestions');
if (data.client.addresses && data.client.addresses.length > 0) {
addrDiv.classList.remove('hidden');
// Si el campo de dirección está vacío, ponemos la primera que tengamos
if (!document.getElementById('nAddr').value) {
document.getElementById('nAddr').value = data.client.addresses[0];
}
}
}
} catch (e) { console.error("Error en buscador rápido:", e); }
}
<script> <script>
let localData = []; let localData = [];
let systemStatuses = []; let systemStatuses = [];
@@ -538,78 +579,57 @@
} }
function renderLists() { function renderLists() {
if(systemStatuses.length === 0) return; if(systemStatuses.length === 0) return;
const searchTerm = document.getElementById('searchFilter').value.toLowerCase(); const searchTerm = document.getElementById('searchFilter').value.toLowerCase();
const selectedOp = document.getElementById('opFilter').value; const selectedOp = document.getElementById('opFilter').value;
const weekValue = document.getElementById('weekFilter').value;
// Inicializamos contadores reales // Reiniciamos contadores para volver a sumarlos
let kpiUnassigned = 0; let kpiUnassigned = 0, kpiScheduled = 0, kpiWaiting = 0, kpiIncident = 0;
let kpiScheduled = 0;
let kpiWaiting = 0;
let kpiIncident = 0;
const filteredData = localData.filter(s => { const filteredData = localData.filter(s => {
const raw = s.raw_data || {}; const raw = s.raw_data || {};
const stateInfo = getServiceStateInfo(s); const stateInfo = getServiceStateInfo(s);
s._stateInfo = stateInfo; // Guardamos info para el render s._stateInfo = stateInfo;
const stName = stateInfo.name.toLowerCase();
// LÓGICA DE CONTADORES REVOLUCIONADA: // --- LÓGICA DE SUMA DE CONTADORES ---
const stName = stateInfo.name.toLowerCase(); if (!s.assigned_name || stateInfo.id === 'bolsa' || stName.includes('asignar')) {
kpiUnassigned++;
// 1. SIN ASIGNAR: No tiene nombre de operario O está en bolsa } else if (stName.includes('incidencia') || stName.includes('pausa')) {
if (!s.assigned_name || stateInfo.id === 'bolsa' || stName.includes('asignar')) { kpiIncident++;
kpiUnassigned++; } else if (raw.scheduled_date && raw.scheduled_date !== "" && !stateInfo.is_final) {
} kpiScheduled++;
// 2. INCIDENCIA: El estado tiene la palabra clave } else if (!stateInfo.is_final && !raw.scheduled_date) {
else if (stName.includes('incidencia') || stName.includes('pausa')) { kpiWaiting++;
kpiIncident++;
}
// 3. AGENDADOS: Tiene fecha puesta y no es un estado final
else if (raw.scheduled_date && raw.scheduled_date !== "" && !stateInfo.is_final) {
kpiScheduled++;
}
// 4. ESPERA CLIENTE: No tiene cita pero ya tiene técnico (o estado específico)
else if (!stateInfo.is_final && (!raw.scheduled_date || stName.includes('espera'))) {
kpiWaiting++;
}
// Filtros de búsqueda
const name = (raw["Nombre Cliente"] || raw["CLIENTE"] || "").toLowerCase();
const ref = (s.service_ref || "").toLowerCase();
const matchesSearch = searchTerm === "" || name.includes(searchTerm) || ref.includes(searchTerm);
const matchesOp = selectedOp === "ALL" || s.assigned_name === selectedOp;
let matchesWeek = true;
if (weekValue !== "" && raw.scheduled_date) {
matchesWeek = isDateInWeekString(raw.scheduled_date, weekValue);
} else if (weekValue !== "") {
matchesWeek = false;
}
let matchesStatus = (activeStatusFilter === "ALL") ? true : String(stateInfo.id) === activeStatusFilter;
return matchesSearch && matchesOp && matchesWeek && matchesStatus;
});
// Actualizamos la vista con los números reales
document.getElementById('kpi-unassigned').innerText = kpiUnassigned;
document.getElementById('kpi-scheduled').innerText = kpiScheduled;
document.getElementById('kpi-waiting').innerText = kpiWaiting;
document.getElementById('kpi-incident').innerText = kpiIncident;
const grid = document.getElementById('servicesGrid');
grid.innerHTML = filteredData.length > 0
? filteredData.map(s => buildGridCard(s)).join('')
: `<div class="col-span-full py-20 text-center bg-white rounded-[2rem] border-2 border-dashed border-slate-200">
<i data-lucide="layout-grid" class="w-12 h-12 text-slate-300 mx-auto mb-3"></i>
<p class="text-slate-400 font-bold uppercase tracking-widest text-sm">No hay servicios que coincidan</p>
</div>`;
lucide.createIcons();
} }
// Filtro de búsqueda visual
const matchesSearch = searchTerm === "" ||
(raw["Nombre Cliente"] || "").toLowerCase().includes(searchTerm) ||
(s.service_ref || "").toLowerCase().includes(searchTerm) ||
(raw["Teléfono"] || "").toLowerCase().includes(searchTerm);
const matchesOp = selectedOp === "ALL" || s.assigned_name === selectedOp;
const matchesStatus = (activeStatusFilter === "ALL") ? true : String(stateInfo.id) === activeStatusFilter;
return matchesSearch && matchesOp && matchesStatus;
});
// Inyectar los números en los circulitos de arriba
document.getElementById('kpi-unassigned').innerText = kpiUnassigned;
document.getElementById('kpi-scheduled').innerText = kpiScheduled;
document.getElementById('kpi-waiting').innerText = kpiWaiting;
document.getElementById('kpi-incident').innerText = kpiIncident;
const grid = document.getElementById('servicesGrid');
grid.innerHTML = filteredData.length > 0
? filteredData.map(s => buildGridCard(s)).join('')
: `<div class="col-span-full py-20 text-center text-slate-400 font-bold">No hay servicios con estos filtros</div>`;
lucide.createIcons();
}
function buildGridCard(s) { function buildGridCard(s) {
const raw = s.raw_data || {}; const raw = s.raw_data || {};
const name = raw["Nombre Cliente"] || raw["CLIENTE"] || "Asegurado Sin Nombre"; const name = raw["Nombre Cliente"] || raw["CLIENTE"] || "Asegurado Sin Nombre";
@@ -970,6 +990,39 @@
lucide.createIcons(); lucide.createIcons();
} }
} }
// --- FUNCIÓN PARA BUSCAR CLIENTE POR TELÉFONO AL ESCRIBIR ---
async function searchClientByPhone(phone) {
if (phone.length < 9) {
document.getElementById('addrSuggestions').classList.add('hidden');
return;
}
try {
const res = await fetch(`${API_URL}/clients/search?phone=${phone}`, {
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
});
const data = await res.json();
if (data.ok && data.client) {
// Rellenar nombre si está vacío
const nameInput = document.getElementById('nName');
if (!nameInput.value) {
nameInput.value = data.client.full_name;
nameInput.classList.add('bg-emerald-50'); // Efecto visual de éxito
}
// Mostrar aviso de direcciones si existen
const addrDiv = document.getElementById('addrSuggestions');
if (data.client.addresses && data.client.addresses.length > 0) {
addrDiv.classList.remove('hidden');
// Si el campo de dirección está vacío, ponemos la primera que tengamos
if (!document.getElementById('nAddr').value) {
document.getElementById('nAddr').value = data.client.addresses[0];
}
}
}
} catch (e) { console.error("Error en buscador rápido:", e); }
}
</script> </script>
</body> </body>
</html> </html>