Actualizar servicios.html
This commit is contained in:
375
servicios.html
375
servicios.html
@@ -129,57 +129,110 @@
|
||||
</div>
|
||||
|
||||
<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="md:col-span-7 space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-12 gap-8">
|
||||
|
||||
<div class="md:col-span-7 space-y-6">
|
||||
|
||||
<div class="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm space-y-4">
|
||||
<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>
|
||||
<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="user" class="w-4 h-4 text-blue-500"></i> Datos del Cliente
|
||||
</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" oninput="searchClientByPhone(this.value)" placeholder="600000000" class="input-modern" required>
|
||||
<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 asegurado" class="input-modern" required>
|
||||
<input type="text" id="nName" placeholder="Nombre del asegurado" class="input-modern" required>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<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>
|
||||
<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">
|
||||
<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>
|
||||
<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 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 class="md:col-span-5 space-y-6">
|
||||
|
||||
<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">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>
|
||||
<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>
|
||||
<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 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>
|
||||
</form>
|
||||
@@ -322,7 +375,6 @@
|
||||
</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">
|
||||
@@ -331,40 +383,6 @@
|
||||
</div>
|
||||
|
||||
<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>
|
||||
let localData = [];
|
||||
let systemStatuses = [];
|
||||
@@ -524,40 +542,58 @@ async function searchClientByPhone(phone) {
|
||||
|
||||
const searchTerm = document.getElementById('searchFilter').value.toLowerCase();
|
||||
const selectedOp = document.getElementById('opFilter').value;
|
||||
const weekValue = document.getElementById('weekFilter').value;
|
||||
|
||||
// Reiniciamos contadores para volver a sumarlos
|
||||
let kpiUnassigned = 0, kpiScheduled = 0, kpiWaiting = 0, kpiIncident = 0;
|
||||
// Inicializamos contadores reales
|
||||
let kpiUnassigned = 0;
|
||||
let kpiScheduled = 0;
|
||||
let kpiWaiting = 0;
|
||||
let kpiIncident = 0;
|
||||
|
||||
const filteredData = localData.filter(s => {
|
||||
const raw = s.raw_data || {};
|
||||
const stateInfo = getServiceStateInfo(s);
|
||||
s._stateInfo = stateInfo;
|
||||
s._stateInfo = stateInfo; // Guardamos info para el render
|
||||
|
||||
// LÓGICA DE CONTADORES REVOLUCIONADA:
|
||||
const stName = stateInfo.name.toLowerCase();
|
||||
|
||||
// --- LÓGICA DE SUMA DE CONTADORES ---
|
||||
// 1. SIN ASIGNAR: No tiene nombre de operario O está en bolsa
|
||||
if (!s.assigned_name || stateInfo.id === 'bolsa' || stName.includes('asignar')) {
|
||||
kpiUnassigned++;
|
||||
} else if (stName.includes('incidencia') || stName.includes('pausa')) {
|
||||
}
|
||||
// 2. INCIDENCIA: El estado tiene la palabra clave
|
||||
else if (stName.includes('incidencia') || stName.includes('pausa')) {
|
||||
kpiIncident++;
|
||||
} else if (raw.scheduled_date && raw.scheduled_date !== "" && !stateInfo.is_final) {
|
||||
}
|
||||
// 3. AGENDADOS: Tiene fecha puesta y no es un estado final
|
||||
else if (raw.scheduled_date && raw.scheduled_date !== "" && !stateInfo.is_final) {
|
||||
kpiScheduled++;
|
||||
} else if (!stateInfo.is_final && !raw.scheduled_date) {
|
||||
}
|
||||
// 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++;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// 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;
|
||||
const matchesStatus = (activeStatusFilter === "ALL") ? true : String(stateInfo.id) === activeStatusFilter;
|
||||
|
||||
return matchesSearch && matchesOp && matchesStatus;
|
||||
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;
|
||||
});
|
||||
|
||||
// Inyectar los números en los circulitos de arriba
|
||||
// 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;
|
||||
@@ -566,7 +602,10 @@ async function searchClientByPhone(phone) {
|
||||
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>`;
|
||||
: `<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();
|
||||
}
|
||||
@@ -577,7 +616,7 @@ async function searchClientByPhone(phone) {
|
||||
const addr = raw["Dirección"] || raw["DOMICILIO"] || "---";
|
||||
const pop = raw["Población"] || raw["POBLACION-PROVINCIA"] || "";
|
||||
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 isUrgent = s.is_urgent === true || (raw['Urgente'] && raw['Urgente'].toLowerCase() === 'sí') || (raw['URGENTE'] && raw['URGENTE'].toLowerCase() === 'si');
|
||||
@@ -617,25 +656,16 @@ async function searchClientByPhone(phone) {
|
||||
</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 class="flex items-center gap-1 text-blue-600 shrink-0 bg-blue-50 px-2 py-1 rounded-md border border-blue-100">
|
||||
<i data-lucide="${iconEstado}" class="w-3 h-3"></i>
|
||||
<span class="text-[9px] font-black uppercase whitespace-nowrap">${raw.scheduled_date ? cita.split('|')[0] : 'Pte. Cita'}</span>
|
||||
<div class="flex items-center justify-between mt-4 pt-4 border-t border-slate-100">
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
<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>
|
||||
<span class="text-[10px] font-black text-slate-600 uppercase truncate" title="${s.assigned_name || 'Sin asignar'}">${s.assigned_name || 'Sin asignar'}</span>
|
||||
</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>`;
|
||||
}
|
||||
@@ -664,29 +694,21 @@ async function searchClientByPhone(phone) {
|
||||
setTimeout(() => { toast.classList.add('hidden'); }, 3500);
|
||||
}
|
||||
|
||||
function openDetail(id, startInEditMode = false) {
|
||||
function openDetail(id) {
|
||||
const s = localData.find(x => x.id === id);
|
||||
if (!s) return;
|
||||
const raw = s.raw_data;
|
||||
|
||||
// 1. IDs y Referencias
|
||||
document.getElementById('detId').value = s.id;
|
||||
document.getElementById('detRef').innerText = s.service_ref;
|
||||
document.getElementById('detCp').value = raw["Código Postal"] || "00000";
|
||||
|
||||
const companyName = raw['Compañía'] || raw['COMPAÑIA'] || raw['Procedencia'] || "Particular";
|
||||
document.getElementById('detCompany').innerText = companyName;
|
||||
|
||||
// 2. RELLENAR CAMPOS EDITABLES (NUEVO)
|
||||
// Rellenamos tanto los visuales como los que usaremos para editar
|
||||
const clientName = raw["Nombre Cliente"] || raw["CLIENTE"] || "Asegurado Sin Nombre";
|
||||
document.getElementById('detName').innerText = clientName;
|
||||
if(document.getElementById('editName')) document.getElementById('editName').value = clientName;
|
||||
document.getElementById('detName').innerText = raw["Nombre Cliente"] || raw["CLIENTE"] || "Asegurado Sin Nombre";
|
||||
|
||||
const rawPhone = raw["Teléfono"] || raw["TELEFONOS"] || raw["TELEFONO"] || "";
|
||||
const matchPhone = rawPhone.toString().match(/[6789]\d{8}/);
|
||||
const singlePhone = matchPhone ? matchPhone[0] : "";
|
||||
if(document.getElementById('editPhone')) document.getElementById('editPhone').value = singlePhone;
|
||||
|
||||
if (singlePhone) {
|
||||
document.getElementById('detPhone').innerText = singlePhone;
|
||||
@@ -700,16 +722,11 @@ async function searchClientByPhone(phone) {
|
||||
document.getElementById('detPhoneLink').classList.add('text-slate-400', 'pointer-events-none');
|
||||
}
|
||||
|
||||
const fullAddr = `${raw["Dirección"] || ""} ${raw["Población"] || ""}`.trim();
|
||||
document.getElementById('detAddrText').innerText = fullAddr || "Dirección no especificada";
|
||||
if(document.getElementById('editAddr')) document.getElementById('editAddr').value = raw["Dirección"] || "";
|
||||
document.getElementById('detAddrText').innerText = `${raw["Dirección"] || "Dirección no especificada"} ${raw["Población"] || ""}`;
|
||||
document.getElementById('detDesc').innerHTML = (raw["Descripción"] || raw["DESCRIPCION"] || "Sin notas.").replace(/\n/g, '<br>');
|
||||
|
||||
const descContent = raw["Descripción"] || raw["DESCRIPCION"] || "Sin notas.";
|
||||
document.getElementById('detDesc').innerHTML = descContent.replace(/\n/g, '<br>');
|
||||
if(document.getElementById('editDesc')) document.getElementById('editDesc').value = descContent;
|
||||
|
||||
// 3. Lógica de Paneles (Asignación/Bolsa)
|
||||
const stateInfo = s._stateInfo;
|
||||
|
||||
if (s.automation_status === 'in_progress') {
|
||||
document.getElementById('panelEnBolsa').classList.remove('hidden');
|
||||
document.getElementById('panelAsignado').classList.add('hidden');
|
||||
@@ -739,15 +756,7 @@ async function searchClientByPhone(phone) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
@@ -863,8 +872,6 @@ async function searchClientByPhone(phone) {
|
||||
async function saveNewService(e) {
|
||||
e.preventDefault();
|
||||
const action = e.submitter.value;
|
||||
const is_company = document.getElementById('isCompanyCheck').checked;
|
||||
|
||||
const data = {
|
||||
phone: document.getElementById('nPhone').value,
|
||||
name: document.getElementById('nName').value,
|
||||
@@ -872,79 +879,20 @@ async function searchClientByPhone(phone) {
|
||||
description: document.getElementById('nDesc').value,
|
||||
guild_id: document.getElementById('nGuild').value,
|
||||
assigned_to: document.getElementById('nWorker').value || null,
|
||||
duration_minutes: document.getElementById('nDuration').value,
|
||||
is_company: is_company,
|
||||
company_name: is_company ? document.getElementById('nCompanySelect').value : 'Particular',
|
||||
company_ref: is_company ? document.getElementById('nExpRef').value : null,
|
||||
mode: action
|
||||
duration_minutes: document.getElementById('nDuration').value // <--- CAPTURA DE DURACIÓN
|
||||
};
|
||||
|
||||
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)
|
||||
method: 'POST', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
|
||||
body: JSON.stringify({ ...data, mode: action })
|
||||
});
|
||||
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"); }
|
||||
if (res.ok) { closeCreateModal(); refreshPanel(); }
|
||||
} catch(e) { alert("Error al guardar"); }
|
||||
}
|
||||
|
||||
function closeDetailModal() { document.getElementById('detailModal').classList.add('hidden'); }
|
||||
// Al abrir el modal, cargamos las compañías configuradas
|
||||
async function openCreateModal() {
|
||||
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'); }
|
||||
function openCreateModal() { document.getElementById('createModal').classList.remove('hidden'); }
|
||||
function closeCreateModal() { document.getElementById('createModal').classList.add('hidden'); }
|
||||
|
||||
async function loadGuilds() {
|
||||
const res = await fetch(`${API_URL}/guilds`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
|
||||
@@ -1018,39 +966,6 @@ async function checkDuplicateRef(ref) {
|
||||
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>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user