Actualizar servicios.html
This commit is contained in:
675
servicios.html
675
servicios.html
@@ -128,61 +128,114 @@
|
|||||||
<button onclick="closeCreateModal()" class="text-slate-400 hover:text-red-500 bg-slate-50 p-2 rounded-full transition-colors"><i data-lucide="x" class="w-5 h-5"></i></button>
|
<button onclick="closeCreateModal()" class="text-slate-400 hover:text-red-500 bg-slate-50 p-2 rounded-full transition-colors"><i data-lucide="x" class="w-5 h-5"></i></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onsubmit="saveNewService(event)" class="flex-1 overflow-y-auto no-scrollbar p-6 bg-slate-50/50">
|
<form onsubmit="saveNewService(event)" class="flex-1 overflow-y-auto no-scrollbar p-6 bg-slate-50/50">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-12 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-12 gap-8">
|
||||||
<div class="md:col-span-7 space-y-4">
|
|
||||||
<div class="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm space-y-4">
|
<div class="md:col-span-7 space-y-6">
|
||||||
<h4 class="label-modern border-b pb-2"><i data-lucide="user" class="w-3 h-3 inline mr-1"></i> Datos del Cliente</h4>
|
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div class="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm space-y-4">
|
||||||
<div>
|
<h4 class="font-black text-slate-800 uppercase text-xs flex items-center gap-2 border-b border-slate-100 pb-3 mb-4">
|
||||||
<label class="label-modern">Teléfono</label>
|
<i data-lucide="user" class="w-4 h-4 text-blue-500"></i> Datos del Cliente
|
||||||
<input type="tel" id="nPhone" oninput="searchClientByPhone(this.value)" placeholder="600000000" class="input-modern" required>
|
</h4>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="label-modern">Teléfono</label>
|
||||||
|
<input type="tel" id="nPhone" placeholder="Ej: 600123456" class="input-modern" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="label-modern">Nombre Completo</label>
|
||||||
|
<input type="text" id="nName" placeholder="Nombre del asegurado" class="input-modern" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="label-modern">Dirección Completa</label>
|
||||||
|
<input type="text" id="nAddr" placeholder="Calle, número, piso, población..." class="input-modern" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
|
||||||
|
<h4 class="font-black text-slate-800 uppercase text-xs flex items-center gap-2 border-b border-slate-100 pb-3 mb-4">
|
||||||
|
<i data-lucide="align-left" class="w-4 h-4 text-amber-500"></i> Detalles de la Avería
|
||||||
|
</h4>
|
||||||
|
<label class="label-modern">Descripción</label>
|
||||||
|
<textarea id="nDesc" placeholder="Describe el problema a reparar..." rows="4" class="input-modern resize-none"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<label class="label-modern">Nombre Completo</label>
|
<div class="md:col-span-5 space-y-6">
|
||||||
<input type="text" id="nName" placeholder="Nombre asegurado" class="input-modern" required>
|
|
||||||
|
<div class="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm space-y-4">
|
||||||
|
<h4 class="font-black text-slate-800 uppercase text-xs flex items-center gap-2 border-b border-slate-100 pb-3 mb-4">
|
||||||
|
<i data-lucide="hammer" class="w-4 h-4 text-purple-500"></i> Asignación
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="label-modern">Gremio Recomendado</label>
|
||||||
|
<div class="relative">
|
||||||
|
<select id="nGuild" class="input-modern appearance-none cursor-pointer pr-10" onchange="loadOps(this.value, 'nWorker')">
|
||||||
|
<option value="">Seleccionar gremio...</option>
|
||||||
|
</select>
|
||||||
|
<i data-lucide="chevron-down" class="w-4 h-4 absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="label-modern">Operario</label>
|
||||||
|
<div class="relative">
|
||||||
|
<select id="nWorker" class="input-modern appearance-none cursor-pointer pr-8">
|
||||||
|
<option value="">(Sin asignar)</option>
|
||||||
|
</select>
|
||||||
|
<i data-lucide="chevron-down" class="w-4 h-4 absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="label-modern">Duración (Estimada)</label>
|
||||||
|
<div class="relative">
|
||||||
|
<select id="nDuration" class="input-modern appearance-none cursor-pointer pr-8">
|
||||||
|
<option value="15">15 min</option>
|
||||||
|
<option value="30">30 min</option>
|
||||||
|
<option value="45">45 min</option>
|
||||||
|
<option value="60" selected>1 hora</option>
|
||||||
|
<option value="75">1 h 15 min</option>
|
||||||
|
<option value="90">1 h 30 min</option>
|
||||||
|
<option value="105">1 h 45 min</option>
|
||||||
|
<option value="120">2 horas</option>
|
||||||
|
<option value="150">2 h 30 min</option>
|
||||||
|
<option value="180">3 horas</option>
|
||||||
|
<option value="210">3 h 30 min</option>
|
||||||
|
<option value="240">4 horas (Max)</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-3">
|
||||||
|
<button type="submit" name="action" value="manual" class="w-full bg-emerald-600 hover:bg-emerald-500 text-white font-black py-4 rounded-xl text-sm uppercase tracking-widest shadow-[0_8px_20px_-6px_rgba(5,150,105,0.5)] transition-all active:scale-95 flex items-center justify-center gap-2">
|
||||||
|
<i data-lucide="check-circle" class="w-5 h-5"></i> Crear y Guardar
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="relative flex items-center justify-center py-2">
|
||||||
|
<span class="border-t border-slate-200 w-full absolute"></span>
|
||||||
|
<span class="bg-slate-50 px-3 text-[9px] font-black text-slate-400 uppercase tracking-widest relative z-10">Opciones Automáticas</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" name="action" value="auto" class="w-full bg-slate-800 hover:bg-slate-700 text-white font-black py-3 rounded-xl text-xs uppercase tracking-widest shadow-md transition-all active:scale-95 flex items-center justify-between px-5 border border-slate-600">
|
||||||
|
<div class="text-left">
|
||||||
|
<span class="block">Mandar a la Cola</span>
|
||||||
|
<span class="block text-[9px] text-slate-400 font-medium normal-case mt-0.5">Busca operario vía WhatsApp</span>
|
||||||
|
</div>
|
||||||
|
<i data-lucide="zap" class="w-5 h-5 text-amber-400"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</form>
|
||||||
<label class="label-modern">Dirección</label>
|
|
||||||
<input type="text" id="nAddr" placeholder="Calle, número, población..." class="input-modern" required>
|
|
||||||
<div id="addrSuggestions" class="hidden mt-2 p-2 bg-amber-50 border border-amber-200 rounded-lg text-[10px] font-bold text-amber-700">⚠️ Cliente con otras direcciones registradas.</div>
|
|
||||||
</div>
|
|
||||||
<div class="pt-2">
|
|
||||||
<label class="flex items-center gap-3 cursor-pointer">
|
|
||||||
<input type="checkbox" id="isCompanyCheck" onchange="toggleCompanyFields(this.checked)" class="w-5 h-5 rounded text-blue-600">
|
|
||||||
<span class="text-xs font-black text-slate-700 uppercase">¿Es de Compañía de Seguros?</span>
|
|
||||||
</label>
|
|
||||||
<div id="companyFields" class="hidden mt-4 space-y-4 p-4 bg-blue-50/30 rounded-xl border border-blue-100">
|
|
||||||
<select id="nCompanySelect" class="input-modern bg-white"><option value="">Seleccionar compañía...</option></select>
|
|
||||||
<input type="text" id="nExpRef" onblur="checkDuplicateRef(this.value)" placeholder="Nº Expediente" class="input-modern bg-white uppercase">
|
|
||||||
<p id="refAlert" class="hidden text-[10px] text-red-600 font-bold mt-1 uppercase">⚠️ Referencia ya existente</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
|
|
||||||
<label class="label-modern">Descripción de la Avería</label>
|
|
||||||
<textarea id="nDesc" placeholder="Detalla el problema..." rows="6" class="input-modern resize-none"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="md:col-span-5 space-y-4">
|
|
||||||
<div class="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm space-y-5">
|
|
||||||
<h4 class="label-modern border-b pb-2"><i data-lucide="settings" class="w-3 h-3 inline mr-1"></i> Asignación y Tiempo</h4>
|
|
||||||
<select id="nGuild" class="input-modern" onchange="loadOps(this.value, 'nWorker')"><option value="">Gremio...</option></select>
|
|
||||||
<select id="nWorker" class="input-modern"><option value="">Operario...</option></select>
|
|
||||||
<div>
|
|
||||||
<label class="label-modern">Duración Estimada</label>
|
|
||||||
<select id="nDuration" class="input-modern font-black text-blue-600 uppercase">
|
|
||||||
<option value="15">15 min</option><option value="30">30 min</option><option value="60" selected>1 hora</option>
|
|
||||||
<option value="90">1.5 horas</option><option value="120">2 horas</option><option value="180">3 horas</option><option value="240">4 horas</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="submit" name="action" value="manual" class="w-full bg-emerald-600 text-white font-black py-4 rounded-2xl text-xs uppercase shadow-lg">Crear y Guardar</button>
|
|
||||||
<button type="submit" name="action" value="auto" class="w-full bg-slate-900 text-white font-black py-4 rounded-2xl text-xs uppercase flex items-center justify-center gap-2"><i data-lucide="zap" class="w-4 h-4 text-amber-400"></i> Mandar a la Bolsa</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -322,7 +375,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="toastAppt" class="fixed bottom-8 right-8 bg-emerald-600 text-white px-6 py-4 rounded-2xl shadow-2xl hidden z-[200] font-bold text-sm flex items-center gap-3 transition-colors duration-300">
|
<div id="toastAppt" class="fixed bottom-8 right-8 bg-emerald-600 text-white px-6 py-4 rounded-2xl shadow-2xl hidden z-[200] font-bold text-sm flex items-center gap-3 transition-colors duration-300">
|
||||||
@@ -331,40 +383,6 @@
|
|||||||
</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 = [];
|
||||||
@@ -520,125 +538,137 @@ async function searchClientByPhone(phone) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
// Reiniciamos contadores para volver a sumarlos
|
// Inicializamos contadores reales
|
||||||
let kpiUnassigned = 0, kpiScheduled = 0, kpiWaiting = 0, kpiIncident = 0;
|
let kpiUnassigned = 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;
|
s._stateInfo = stateInfo; // Guardamos info para el render
|
||||||
const stName = stateInfo.name.toLowerCase();
|
|
||||||
|
|
||||||
// --- LÓGICA DE SUMA DE CONTADORES ---
|
// LÓGICA DE CONTADORES REVOLUCIONADA:
|
||||||
if (!s.assigned_name || stateInfo.id === 'bolsa' || stName.includes('asignar')) {
|
const stName = stateInfo.name.toLowerCase();
|
||||||
kpiUnassigned++;
|
|
||||||
} else if (stName.includes('incidencia') || stName.includes('pausa')) {
|
// 1. SIN ASIGNAR: No tiene nombre de operario O está en bolsa
|
||||||
kpiIncident++;
|
if (!s.assigned_name || stateInfo.id === 'bolsa' || stName.includes('asignar')) {
|
||||||
} else if (raw.scheduled_date && raw.scheduled_date !== "" && !stateInfo.is_final) {
|
kpiUnassigned++;
|
||||||
kpiScheduled++;
|
}
|
||||||
} else if (!stateInfo.is_final && !raw.scheduled_date) {
|
// 2. INCIDENCIA: El estado tiene la palabra clave
|
||||||
kpiWaiting++;
|
else if (stName.includes('incidencia') || stName.includes('pausa')) {
|
||||||
|
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";
|
||||||
const addr = raw["Dirección"] || raw["DOMICILIO"] || "---";
|
const addr = raw["Dirección"] || raw["DOMICILIO"] || "---";
|
||||||
const pop = raw["Población"] || raw["POBLACION-PROVINCIA"] || "";
|
const pop = raw["Población"] || raw["POBLACION-PROVINCIA"] || "";
|
||||||
const fullAddr = `${addr} ${pop}`.trim();
|
const fullAddr = `${addr} ${pop}`.trim();
|
||||||
const cita = raw.scheduled_date ? `${raw.scheduled_date.split('-').reverse().slice(0,2).join('/')} | ${raw.scheduled_time}` : 'Pte. Cita';
|
const cita = raw.scheduled_date ? `${raw.scheduled_date} | ${raw.scheduled_time}` : 'Pendiente Cita';
|
||||||
const companyName = raw['Compañía'] || raw['COMPAÑIA'] || raw['Procedencia'] || (s.provider === 'MANUAL' ? 'PARTICULAR' : 'ASEGURADORA');
|
const companyName = raw['Compañía'] || raw['COMPAÑIA'] || raw['Procedencia'] || (s.provider === 'MANUAL' ? 'PARTICULAR' : 'ASEGURADORA');
|
||||||
|
|
||||||
const isUrgent = s.is_urgent === true || (raw['Urgente'] && raw['Urgente'].toLowerCase() === 'sí') || (raw['URGENTE'] && raw['URGENTE'].toLowerCase() === 'si');
|
const isUrgent = s.is_urgent === true || (raw['Urgente'] && raw['Urgente'].toLowerCase() === 'sí') || (raw['URGENTE'] && raw['URGENTE'].toLowerCase() === 'si');
|
||||||
|
|
||||||
const stateInfo = s._stateInfo;
|
const stateInfo = s._stateInfo;
|
||||||
const colorData = colorDict[stateInfo.color] || colorDict['gray'];
|
const colorData = colorDict[stateInfo.color] || colorDict['gray'];
|
||||||
|
|
||||||
let iconEstado = 'tag';
|
let iconEstado = 'tag';
|
||||||
if(raw.scheduled_date) iconEstado = 'calendar';
|
if(raw.scheduled_date) iconEstado = 'calendar';
|
||||||
if(stateInfo.name.toLowerCase().includes('camino')) iconEstado = 'car';
|
if(stateInfo.name.toLowerCase().includes('camino')) iconEstado = 'car';
|
||||||
if(stateInfo.name.toLowerCase().includes('reparaci') || stateInfo.name.toLowerCase().includes('trabaja')) iconEstado = 'wrench';
|
if(stateInfo.name.toLowerCase().includes('reparaci') || stateInfo.name.toLowerCase().includes('trabaja')) iconEstado = 'wrench';
|
||||||
if(stateInfo.name.toLowerCase().includes('incidencia') || stateInfo.name.toLowerCase().includes('pausado')) iconEstado = 'alert-triangle';
|
if(stateInfo.name.toLowerCase().includes('incidencia') || stateInfo.name.toLowerCase().includes('pausado')) iconEstado = 'alert-triangle';
|
||||||
|
|
||||||
const inProgressBadge = s.automation_status === 'in_progress' ? `<span class="absolute -top-3 -right-3 bg-amber-400 text-white px-3 py-1 rounded-full text-[10px] font-black uppercase shadow-lg border-2 border-white flex items-center gap-1 animate-pulse z-10"><i data-lucide="bot" class="w-3 h-3"></i> En Bolsa</span>` : '';
|
const inProgressBadge = s.automation_status === 'in_progress' ? `<span class="absolute -top-3 -right-3 bg-amber-400 text-white px-3 py-1 rounded-full text-[10px] font-black uppercase shadow-lg border-2 border-white flex items-center gap-1 animate-pulse z-10"><i data-lucide="bot" class="w-3 h-3"></i> En Bolsa</span>` : '';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="bg-white p-5 rounded-3xl border border-slate-200 shadow-sm card-hover text-left flex flex-col justify-between relative cursor-pointer" onclick="openDetail(${s.id})">
|
<div class="bg-white p-5 rounded-3xl border border-slate-200 shadow-sm card-hover text-left flex flex-col justify-between relative cursor-pointer" onclick="openDetail(${s.id})">
|
||||||
${inProgressBadge}
|
${inProgressBadge}
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div class="flex items-start justify-between w-full gap-2">
|
<div class="flex items-start justify-between w-full gap-2">
|
||||||
<span class="text-[9px] font-black ${colorData.bg} ${colorData.text} px-2.5 py-1.5 rounded-lg uppercase tracking-wider flex items-center gap-1.5 truncate border ${colorData.border}">
|
<span class="text-[9px] font-black ${colorData.bg} ${colorData.text} px-2.5 py-1.5 rounded-lg uppercase tracking-wider flex items-center gap-1.5 truncate border ${colorData.border}">
|
||||||
<div class="w-2 h-2 rounded-full ${colorData.dot}"></div>
|
<div class="w-2 h-2 rounded-full ${colorData.dot}"></div>
|
||||||
<span class="truncate">${stateInfo.name}</span>
|
<span class="truncate">${stateInfo.name}</span>
|
||||||
</span>
|
</span>
|
||||||
${isUrgent ? `<span class="bg-red-500 text-white px-2 py-1 rounded-lg text-[9px] font-black uppercase shadow-sm animate-pulse shrink-0">🔥 URGENTE</span>` : `<span class="text-[10px] text-slate-400 font-bold uppercase bg-slate-50 border border-slate-100 px-2.5 py-1 rounded-lg shrink-0">#${s.service_ref}</span>`}
|
${isUrgent ? `<span class="bg-red-500 text-white px-2 py-1 rounded-lg text-[9px] font-black uppercase shadow-sm animate-pulse shrink-0">🔥 URGENTE</span>` : `<span class="text-[10px] text-slate-400 font-bold uppercase bg-slate-50 border border-slate-100 px-2.5 py-1 rounded-lg shrink-0">#${s.service_ref}</span>`}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p class="text-[9px] font-black text-blue-500 uppercase tracking-widest mb-0.5 truncate">${companyName}</p>
|
<p class="text-[9px] font-black text-blue-500 uppercase tracking-widest mb-0.5 truncate">${companyName}</p>
|
||||||
<h4 class="font-black text-slate-800 uppercase text-base leading-tight line-clamp-2" title="${name}">${name}</h4>
|
<h4 class="font-black text-slate-800 uppercase text-base leading-tight line-clamp-2" title="${name}">${name}</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-slate-50/50 p-3 rounded-xl border border-slate-100">
|
<div class="bg-slate-50/50 p-3 rounded-xl border border-slate-100">
|
||||||
<p class="text-[10px] text-slate-600 font-medium uppercase line-clamp-2 flex items-start gap-1.5" title="${fullAddr}">
|
<p class="text-[10px] text-slate-600 font-medium uppercase line-clamp-2 flex items-start gap-1.5" title="${fullAddr}">
|
||||||
<i data-lucide="map-pin" class="w-3.5 h-3.5 mt-0.5 text-slate-400 shrink-0"></i> ${fullAddr}
|
<i data-lucide="map-pin" class="w-3.5 h-3.5 mt-0.5 text-slate-400 shrink-0"></i> ${fullAddr}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center justify-between mt-4 pt-4 border-t border-slate-100 gap-2">
|
|
||||||
<div class="flex items-center gap-1 min-w-0 flex-1">
|
|
||||||
<div class="bg-slate-100 p-1 rounded-lg text-slate-500 shrink-0"><i data-lucide="hard-hat" class="w-3 h-3"></i></div>
|
|
||||||
<span class="text-[10px] font-black text-slate-600 uppercase truncate flex-1" style="max-width: 70px;" title="${s.assigned_name || 'Sin asignar'}">${s.assigned_name || 'Sin asignar'}</span>
|
|
||||||
|
|
||||||
<div class="flex items-center gap-0.5 shrink-0">
|
|
||||||
<button onclick="event.stopPropagation(); window.location.href='trazabilidad.html?id=${s.id}'" class="p-1.5 text-slate-300 hover:text-blue-600 transition-colors" title="Ver Historial">
|
|
||||||
<i data-lucide="history" class="w-4 h-4"></i>
|
|
||||||
</button>
|
|
||||||
<button onclick="event.stopPropagation(); openDetail(${s.id}, true)" class="p-1.5 text-slate-300 hover:text-amber-600 transition-colors" title="Editar Ficha">
|
|
||||||
<i data-lucide="edit-3" class="w-4 h-4"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center gap-1 text-blue-600 shrink-0 bg-blue-50 px-2 py-1 rounded-md border border-blue-100">
|
<div class="flex items-center justify-between mt-4 pt-4 border-t border-slate-100">
|
||||||
<i data-lucide="${iconEstado}" class="w-3 h-3"></i>
|
<div class="flex items-center gap-2 min-w-0">
|
||||||
<span class="text-[9px] font-black uppercase whitespace-nowrap">${raw.scheduled_date ? cita.split('|')[0] : 'Pte. Cita'}</span>
|
<div class="bg-slate-100 p-1.5 rounded-lg text-slate-500 shrink-0"><i data-lucide="hard-hat" class="w-3.5 h-3.5"></i></div>
|
||||||
</div>
|
<span class="text-[10px] font-black text-slate-600 uppercase truncate" title="${s.assigned_name || 'Sin asignar'}">${s.assigned_name || 'Sin asignar'}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
${raw.scheduled_date ? `
|
||||||
}
|
<div class="flex items-center gap-1.5 text-blue-600 shrink-0 ml-2 bg-blue-50 px-2 py-1 rounded-md border border-blue-100">
|
||||||
|
<i data-lucide="${iconEstado}" class="w-3.5 h-3.5"></i>
|
||||||
|
<span class="text-[9px] font-black uppercase">${cita}</span>
|
||||||
|
</div>` : ''}
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
function shakeCard(element, status) {
|
function shakeCard(element, status) {
|
||||||
element.classList.add('shake');
|
element.classList.add('shake');
|
||||||
@@ -664,92 +694,71 @@ async function searchClientByPhone(phone) {
|
|||||||
setTimeout(() => { toast.classList.add('hidden'); }, 3500);
|
setTimeout(() => { toast.classList.add('hidden'); }, 3500);
|
||||||
}
|
}
|
||||||
|
|
||||||
function openDetail(id, startInEditMode = false) {
|
function openDetail(id) {
|
||||||
const s = localData.find(x => x.id === id);
|
const s = localData.find(x => x.id === id);
|
||||||
if (!s) return;
|
if (!s) return;
|
||||||
const raw = s.raw_data;
|
const raw = s.raw_data;
|
||||||
|
document.getElementById('detId').value = s.id;
|
||||||
|
document.getElementById('detRef').innerText = s.service_ref;
|
||||||
|
document.getElementById('detCp').value = raw["Código Postal"] || "00000";
|
||||||
|
|
||||||
// 1. IDs y Referencias
|
const companyName = raw['Compañía'] || raw['COMPAÑIA'] || raw['Procedencia'] || "Particular";
|
||||||
document.getElementById('detId').value = s.id;
|
document.getElementById('detCompany').innerText = companyName;
|
||||||
document.getElementById('detRef').innerText = s.service_ref;
|
document.getElementById('detName').innerText = raw["Nombre Cliente"] || raw["CLIENTE"] || "Asegurado Sin Nombre";
|
||||||
document.getElementById('detCp').value = raw["Código Postal"] || "00000";
|
|
||||||
|
|
||||||
const companyName = raw['Compañía'] || raw['COMPAÑIA'] || raw['Procedencia'] || "Particular";
|
const rawPhone = raw["Teléfono"] || raw["TELEFONOS"] || raw["TELEFONO"] || "";
|
||||||
document.getElementById('detCompany').innerText = companyName;
|
const matchPhone = rawPhone.toString().match(/[6789]\d{8}/);
|
||||||
|
const singlePhone = matchPhone ? matchPhone[0] : "";
|
||||||
|
|
||||||
// 2. RELLENAR CAMPOS EDITABLES (NUEVO)
|
if (singlePhone) {
|
||||||
// Rellenamos tanto los visuales como los que usaremos para editar
|
document.getElementById('detPhone').innerText = singlePhone;
|
||||||
const clientName = raw["Nombre Cliente"] || raw["CLIENTE"] || "Asegurado Sin Nombre";
|
document.getElementById('detPhoneLink').href = `tel:+34${singlePhone}`;
|
||||||
document.getElementById('detName').innerText = clientName;
|
document.getElementById('detPhoneLink').classList.remove('text-slate-400', 'pointer-events-none');
|
||||||
if(document.getElementById('editName')) document.getElementById('editName').value = clientName;
|
document.getElementById('detPhoneLink').classList.add('text-blue-600');
|
||||||
|
} else {
|
||||||
|
document.getElementById('detPhone').innerText = "Sin Teléfono";
|
||||||
|
document.getElementById('detPhoneLink').href = "#";
|
||||||
|
document.getElementById('detPhoneLink').classList.remove('text-blue-600');
|
||||||
|
document.getElementById('detPhoneLink').classList.add('text-slate-400', 'pointer-events-none');
|
||||||
|
}
|
||||||
|
|
||||||
const rawPhone = raw["Teléfono"] || raw["TELEFONOS"] || raw["TELEFONO"] || "";
|
document.getElementById('detAddrText').innerText = `${raw["Dirección"] || "Dirección no especificada"} ${raw["Población"] || ""}`;
|
||||||
const matchPhone = rawPhone.toString().match(/[6789]\d{8}/);
|
document.getElementById('detDesc').innerHTML = (raw["Descripción"] || raw["DESCRIPCION"] || "Sin notas.").replace(/\n/g, '<br>');
|
||||||
const singlePhone = matchPhone ? matchPhone[0] : "";
|
|
||||||
if(document.getElementById('editPhone')) document.getElementById('editPhone').value = singlePhone;
|
|
||||||
|
|
||||||
if (singlePhone) {
|
const stateInfo = s._stateInfo;
|
||||||
document.getElementById('detPhone').innerText = singlePhone;
|
|
||||||
document.getElementById('detPhoneLink').href = `tel:+34${singlePhone}`;
|
|
||||||
document.getElementById('detPhoneLink').classList.remove('text-slate-400', 'pointer-events-none');
|
|
||||||
document.getElementById('detPhoneLink').classList.add('text-blue-600');
|
|
||||||
} else {
|
|
||||||
document.getElementById('detPhone').innerText = "Sin Teléfono";
|
|
||||||
document.getElementById('detPhoneLink').href = "#";
|
|
||||||
document.getElementById('detPhoneLink').classList.remove('text-blue-600');
|
|
||||||
document.getElementById('detPhoneLink').classList.add('text-slate-400', 'pointer-events-none');
|
|
||||||
}
|
|
||||||
|
|
||||||
const fullAddr = `${raw["Dirección"] || ""} ${raw["Población"] || ""}`.trim();
|
if (s.automation_status === 'in_progress') {
|
||||||
document.getElementById('detAddrText').innerText = fullAddr || "Dirección no especificada";
|
document.getElementById('panelEnBolsa').classList.remove('hidden');
|
||||||
if(document.getElementById('editAddr')) document.getElementById('editAddr').value = raw["Dirección"] || "";
|
document.getElementById('panelAsignado').classList.add('hidden');
|
||||||
|
document.getElementById('panelSinAsignar').classList.add('hidden');
|
||||||
|
}
|
||||||
|
else if (s.assigned_name && stateInfo.id !== 'bolsa' && !stateInfo.name.toLowerCase().includes('asignar') && !stateInfo.name.toLowerCase().includes('desasignado')) {
|
||||||
|
document.getElementById('panelEnBolsa').classList.add('hidden');
|
||||||
|
document.getElementById('panelAsignado').classList.remove('hidden');
|
||||||
|
document.getElementById('panelSinAsignar').classList.add('hidden');
|
||||||
|
|
||||||
const descContent = raw["Descripción"] || raw["DESCRIPCION"] || "Sin notas.";
|
document.getElementById('detWorker').innerText = s.assigned_name;
|
||||||
document.getElementById('detDesc').innerHTML = descContent.replace(/\n/g, '<br>');
|
document.getElementById('dateInput').value = raw.scheduled_date || "";
|
||||||
if(document.getElementById('editDesc')) document.getElementById('editDesc').value = descContent;
|
document.getElementById('timeInput').value = raw.scheduled_time || "";
|
||||||
|
document.getElementById('detStatusMap').value = stateInfo.id;
|
||||||
|
} else {
|
||||||
|
document.getElementById('panelEnBolsa').classList.add('hidden');
|
||||||
|
document.getElementById('panelAsignado').classList.add('hidden');
|
||||||
|
document.getElementById('panelSinAsignar').classList.remove('hidden');
|
||||||
|
|
||||||
// 3. Lógica de Paneles (Asignación/Bolsa)
|
const rawGuildId = s.guild_id || raw['guild_id'] || raw.guild_id;
|
||||||
const stateInfo = s._stateInfo;
|
if(rawGuildId) {
|
||||||
if (s.automation_status === 'in_progress') {
|
document.getElementById('reGremio').value = String(rawGuildId);
|
||||||
document.getElementById('panelEnBolsa').classList.remove('hidden');
|
loadOps(rawGuildId, 'reOperario');
|
||||||
document.getElementById('panelAsignado').classList.add('hidden');
|
} else {
|
||||||
document.getElementById('panelSinAsignar').classList.add('hidden');
|
document.getElementById('reGremio').value = "";
|
||||||
}
|
document.getElementById('reOperario').innerHTML = '<option value="">-- Elija gremio primero --</option>';
|
||||||
else if (s.assigned_name && stateInfo.id !== 'bolsa' && !stateInfo.name.toLowerCase().includes('asignar') && !stateInfo.name.toLowerCase().includes('desasignado')) {
|
}
|
||||||
document.getElementById('panelEnBolsa').classList.add('hidden');
|
}
|
||||||
document.getElementById('panelAsignado').classList.remove('hidden');
|
|
||||||
document.getElementById('panelSinAsignar').classList.add('hidden');
|
|
||||||
|
|
||||||
document.getElementById('detWorker').innerText = s.assigned_name;
|
document.getElementById('detailModal').classList.remove('hidden');
|
||||||
document.getElementById('dateInput').value = raw.scheduled_date || "";
|
lucide.createIcons();
|
||||||
document.getElementById('timeInput').value = raw.scheduled_time || "";
|
|
||||||
document.getElementById('detStatusMap').value = stateInfo.id;
|
|
||||||
} else {
|
|
||||||
document.getElementById('panelEnBolsa').classList.add('hidden');
|
|
||||||
document.getElementById('panelAsignado').classList.add('hidden');
|
|
||||||
document.getElementById('panelSinAsignar').classList.remove('hidden');
|
|
||||||
|
|
||||||
const rawGuildId = s.guild_id || raw['guild_id'] || raw.guild_id;
|
|
||||||
if(rawGuildId) {
|
|
||||||
document.getElementById('reGremio').value = String(rawGuildId);
|
|
||||||
loadOps(rawGuildId, 'reOperario');
|
|
||||||
} else {
|
|
||||||
document.getElementById('reGremio').value = "";
|
|
||||||
document.getElementById('reOperario').innerHTML = '<option value="">-- Elija gremio primero --</option>';
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 4. ACTIVAR MODO EDICIÓN SI SE PULSÓ EL LÁPIZ (NUEVO)
|
|
||||||
document.getElementById('detailModal').classList.remove('hidden');
|
|
||||||
|
|
||||||
if (startInEditMode) {
|
|
||||||
enableEditing();
|
|
||||||
} else {
|
|
||||||
cancelEditing(); // Resetear a modo lectura
|
|
||||||
}
|
|
||||||
|
|
||||||
lucide.createIcons();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function stopAutomation() {
|
async function stopAutomation() {
|
||||||
const id = document.getElementById('detId').value;
|
const id = document.getElementById('detId').value;
|
||||||
@@ -861,90 +870,29 @@ async function searchClientByPhone(phone) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveNewService(e) {
|
async function saveNewService(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const action = e.submitter.value;
|
const action = e.submitter.value;
|
||||||
const is_company = document.getElementById('isCompanyCheck').checked;
|
const data = {
|
||||||
|
phone: document.getElementById('nPhone').value,
|
||||||
const data = {
|
name: document.getElementById('nName').value,
|
||||||
phone: document.getElementById('nPhone').value,
|
address: document.getElementById('nAddr').value,
|
||||||
name: document.getElementById('nName').value,
|
description: document.getElementById('nDesc').value,
|
||||||
address: document.getElementById('nAddr').value,
|
guild_id: document.getElementById('nGuild').value,
|
||||||
description: document.getElementById('nDesc').value,
|
assigned_to: document.getElementById('nWorker').value || null,
|
||||||
guild_id: document.getElementById('nGuild').value,
|
duration_minutes: document.getElementById('nDuration').value // <--- CAPTURA DE DURACIÓN
|
||||||
assigned_to: document.getElementById('nWorker').value || null,
|
};
|
||||||
duration_minutes: document.getElementById('nDuration').value,
|
try {
|
||||||
is_company: is_company,
|
const res = await fetch(`${API_URL}/services/manual-high`, {
|
||||||
company_name: is_company ? document.getElementById('nCompanySelect').value : 'Particular',
|
method: 'POST', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
|
||||||
company_ref: is_company ? document.getElementById('nExpRef').value : null,
|
body: JSON.stringify({ ...data, mode: action })
|
||||||
mode: action
|
});
|
||||||
};
|
if (res.ok) { closeCreateModal(); refreshPanel(); }
|
||||||
|
} catch(e) { alert("Error al guardar"); }
|
||||||
try {
|
|
||||||
const res = await fetch(`${API_URL}/services/manual-high`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
|
|
||||||
body: JSON.stringify(data)
|
|
||||||
});
|
|
||||||
if (res.ok) {
|
|
||||||
showToast("✅ Expediente creado con éxito");
|
|
||||||
closeCreateModal();
|
|
||||||
refreshPanel();
|
|
||||||
} else {
|
|
||||||
const err = await res.json();
|
|
||||||
alert("Error: " + (err.error || "No se pudo guardar"));
|
|
||||||
}
|
}
|
||||||
} catch(e) { alert("Error de conexión"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeDetailModal() { document.getElementById('detailModal').classList.add('hidden'); }
|
function closeDetailModal() { document.getElementById('detailModal').classList.add('hidden'); }
|
||||||
// Al abrir el modal, cargamos las compañías configuradas
|
function openCreateModal() { document.getElementById('createModal').classList.remove('hidden'); }
|
||||||
async function openCreateModal() {
|
function closeCreateModal() { document.getElementById('createModal').classList.add('hidden'); }
|
||||||
document.getElementById('createModal').classList.remove('hidden');
|
|
||||||
// Limpiamos campos
|
|
||||||
document.getElementById('isCompanyCheck').checked = false;
|
|
||||||
toggleCompanyFields(false);
|
|
||||||
|
|
||||||
// Cargar compañías desde tu ruta de configuración
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${API_URL}/companies`, {
|
|
||||||
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
const sel = document.getElementById('nCompanySelect');
|
|
||||||
sel.innerHTML = '<option value="">Seleccionar compañía...</option>';
|
|
||||||
data.companies.forEach(c => {
|
|
||||||
sel.innerHTML += `<option value="${c.name}">${c.name.toUpperCase()}</option>`;
|
|
||||||
});
|
|
||||||
} catch(e) {}
|
|
||||||
lucide.createIcons();
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleCompanyFields(show) {
|
|
||||||
const fields = document.getElementById('companyFields');
|
|
||||||
fields.classList.toggle('hidden', !show);
|
|
||||||
if(!show) {
|
|
||||||
document.getElementById('nExpRef').value = "";
|
|
||||||
document.getElementById('refAlert').classList.add('hidden');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkDuplicateRef(ref) {
|
|
||||||
if(!ref) return;
|
|
||||||
const res = await fetch(`${API_URL}/services/check-ref?ref=${ref}`, {
|
|
||||||
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
const alertEl = document.getElementById('refAlert');
|
|
||||||
const inputEl = document.getElementById('nExpRef');
|
|
||||||
|
|
||||||
if(data.exists) {
|
|
||||||
alertEl.classList.remove('hidden');
|
|
||||||
inputEl.classList.add('border-red-500', 'bg-red-50');
|
|
||||||
} else {
|
|
||||||
alertEl.classList.add('hidden');
|
|
||||||
inputEl.classList.remove('border-red-500', 'bg-red-50');
|
|
||||||
}
|
|
||||||
} function closeCreateModal() { document.getElementById('createModal').classList.add('hidden'); }
|
|
||||||
|
|
||||||
async function loadGuilds() {
|
async function loadGuilds() {
|
||||||
const res = await fetch(`${API_URL}/guilds`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
|
const res = await fetch(`${API_URL}/guilds`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
|
||||||
@@ -1018,39 +966,6 @@ async function checkDuplicateRef(ref) {
|
|||||||
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>
|
||||||
Reference in New Issue
Block a user