Permite anular servicios sin tener que asignártelos primero
solicitar.html: botón "Anular Aviso" en la Bolsa de Trabajo. buscar.html: botón de anular en la pestaña "Sin Asignar" y en "Mis Asignados". asignados.html: botón "Anular Servicio" en la ficha de un servicio ya asignado pero sin cita. En los tres casos se cambia el estado a Anulado directamente, sin tocar assigned_to, para no disparar el WhatsApp de "servicio asignado".
This commit is contained in:
@@ -193,6 +193,10 @@
|
|||||||
<button onclick="saveAppointment()" id="btnSaveAppt" disabled class="w-full bg-slate-300 text-white font-black py-4 rounded-2xl shadow-sm transition-all flex items-center justify-center gap-2 uppercase tracking-widest text-xs cursor-not-allowed">
|
<button onclick="saveAppointment()" id="btnSaveAppt" disabled class="w-full bg-slate-300 text-white font-black py-4 rounded-2xl shadow-sm transition-all flex items-center justify-center gap-2 uppercase tracking-widest text-xs cursor-not-allowed">
|
||||||
<i data-lucide="calendar-check" class="w-5 h-5"></i> Confirmar Cita
|
<i data-lucide="calendar-check" class="w-5 h-5"></i> Confirmar Cita
|
||||||
</button>
|
</button>
|
||||||
|
<!-- 🆕 Anular un servicio ya asignado pero aún sin cita, sin tener que agendarlo primero -->
|
||||||
|
<button onclick="anularServicio()" id="btnAnularAsig" class="w-full mt-3 bg-white text-rose-600 font-black py-3 rounded-2xl border border-rose-200 flex items-center justify-center gap-2 uppercase tracking-widest text-xs active:scale-95 transition-transform">
|
||||||
|
<i data-lucide="ban" class="w-4 h-4"></i> Anular Servicio
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1029,6 +1033,42 @@ function renderServices(servicesToRender = localServices) {
|
|||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🆕 Anula un servicio ya asignado pero todavía sin cita, sin obligar a agendarlo antes.
|
||||||
|
async function anularServicio() {
|
||||||
|
const id = document.getElementById('detId').value;
|
||||||
|
if (!confirm("¿Seguro que quieres anular este servicio? No podrás deshacerlo desde aquí.")) return;
|
||||||
|
|
||||||
|
const stAnulado = systemStatuses.find(st => st.name.toLowerCase().includes('anula'));
|
||||||
|
if (!stAnulado) return alert("El sistema no tiene un estado 'Anulado' configurado. Avisa a la oficina.");
|
||||||
|
|
||||||
|
const btn = document.getElementById('btnAnularAsig');
|
||||||
|
const originalContent = btn.innerHTML;
|
||||||
|
btn.innerHTML = `<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Anulando...`;
|
||||||
|
btn.disabled = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_URL}/services/set-appointment/${id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
|
||||||
|
body: JSON.stringify({ status_operativo: String(stAnulado.id) })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
showToast("Servicio anulado");
|
||||||
|
closeModal('actionModal', 'modalContent');
|
||||||
|
refreshData();
|
||||||
|
} else {
|
||||||
|
alert("Error al anular.");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
alert("Error de conexión");
|
||||||
|
} finally {
|
||||||
|
btn.innerHTML = originalContent;
|
||||||
|
btn.disabled = false;
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
}
|
||||||
function showToast(msg) {
|
function showToast(msg) {
|
||||||
const t = document.getElementById('toast');
|
const t = document.getElementById('toast');
|
||||||
document.getElementById('toastMsg').innerText = msg;
|
document.getElementById('toastMsg').innerText = msg;
|
||||||
|
|||||||
41
buscar.html
41
buscar.html
@@ -444,6 +444,14 @@
|
|||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// 🆕 Botón Anular: disponible en ambas pestañas, para no tener que asignarse
|
||||||
|
// el servicio primero solo para poder cancelarlo (y evitar el WhatsApp de "asignado").
|
||||||
|
const anularBtn = `
|
||||||
|
<button onclick="anularServicio(${s.id})" class="bg-white hover:bg-rose-50 border border-rose-200 text-rose-600 font-black py-2.5 px-3 rounded-xl flex items-center justify-center transition-colors" title="Anular">
|
||||||
|
<i data-lucide="ban" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
if (currentTab === 'mine') {
|
if (currentTab === 'mine') {
|
||||||
if (!isScheduled) {
|
if (!isScheduled) {
|
||||||
actionButtons = `
|
actionButtons = `
|
||||||
@@ -459,19 +467,20 @@
|
|||||||
</button>
|
</button>
|
||||||
` + actionButtons;
|
` + actionButtons;
|
||||||
}
|
}
|
||||||
|
actionButtons = actionButtons + anularBtn;
|
||||||
} else if (currentTab === 'unassigned') {
|
} else if (currentTab === 'unassigned') {
|
||||||
// GOAL 2: BOTÓN ASIGNÁRMELO
|
// GOAL 2: BOTÓN ASIGNÁRMELO
|
||||||
actionButtons = `
|
actionButtons = `
|
||||||
<button onclick="claimService(${s.id})" class="flex-1 bg-emerald-500 hover:bg-emerald-600 text-white font-black py-2.5 rounded-xl flex items-center justify-center gap-1.5 transition-colors text-[10px] uppercase tracking-widest shadow-md">
|
<button onclick="claimService(${s.id})" class="flex-1 bg-emerald-500 hover:bg-emerald-600 text-white font-black py-2.5 rounded-xl flex items-center justify-center gap-1.5 transition-colors text-[10px] uppercase tracking-widest shadow-md">
|
||||||
<i data-lucide="hand" class="w-4 h-4"></i> ¡Asignármelo!
|
<i data-lucide="hand" class="w-4 h-4"></i> ¡Asignármelo!
|
||||||
</button>
|
</button>
|
||||||
` + actionButtons;
|
` + actionButtons + anularBtn;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="bg-white p-5 rounded-3xl border border-slate-200 shadow-sm relative overflow-hidden flex flex-col gap-4">
|
<div class="bg-white p-5 rounded-3xl border border-slate-200 shadow-sm relative overflow-hidden flex flex-col gap-4">
|
||||||
${isUrgent ? '<div class="absolute top-0 right-0 bg-red-500 text-white text-[8px] font-black px-3 py-1 rounded-bl-xl uppercase tracking-widest shadow-sm z-10">Urgente</div>' : ''}
|
${isUrgent ? '<div class="absolute top-0 right-0 bg-red-500 text-white text-[8px] font-black px-3 py-1 rounded-bl-xl uppercase tracking-widest shadow-sm z-10">Urgente</div>' : ''}
|
||||||
|
|
||||||
<div class="flex justify-between items-start">
|
<div class="flex justify-between items-start">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center 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 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 border ${colorData.border}">
|
||||||
@@ -481,7 +490,7 @@
|
|||||||
<span class="text-[9px] text-slate-400 font-bold uppercase bg-slate-50 border border-slate-100 px-2.5 py-1.5 rounded-lg">#${s.service_ref}</span>
|
<span class="text-[9px] text-slate-400 font-bold uppercase bg-slate-50 border border-slate-100 px-2.5 py-1.5 rounded-lg">#${s.service_ref}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p class="text-[9px] font-black text-slate-400 uppercase tracking-widest mb-0.5 truncate">${company}</p>
|
<p class="text-[9px] font-black text-slate-400 uppercase tracking-widest mb-0.5 truncate">${company}</p>
|
||||||
<h3 class="font-black text-slate-800 text-base uppercase leading-tight truncate">${name}</h3>
|
<h3 class="font-black text-slate-800 text-base uppercase leading-tight truncate">${name}</h3>
|
||||||
@@ -553,6 +562,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🆕 Anula un servicio directamente, esté o no asignado, sin pasar por "Asignármelo" primero
|
||||||
|
// (así no se le manda al cliente el WhatsApp de "servicio asignado" para algo que se va a cancelar).
|
||||||
|
async function anularServicio(id) {
|
||||||
|
if (!confirm("¿Seguro que quieres anular este servicio? No podrás deshacerlo desde aquí.")) return;
|
||||||
|
|
||||||
|
const stAnulado = systemStatuses.find(st => st.name.toLowerCase().includes('anula'));
|
||||||
|
if (!stAnulado) return alert("El sistema no tiene un estado 'Anulado' configurado. Avisa a la oficina.");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_URL}/providers/scraped/${id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
|
||||||
|
body: JSON.stringify({ status_operativo: String(stAnulado.id) })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
showToast("Servicio anulado");
|
||||||
|
loadAllData();
|
||||||
|
} else {
|
||||||
|
alert("Error al anular.");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
alert("Error de conexión");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function quickCopyPortalLink(serviceId, phone, name, addr, btnElement) {
|
async function quickCopyPortalLink(serviceId, phone, name, addr, btnElement) {
|
||||||
if (!phone) return alert("El cliente no tiene teléfono guardado");
|
if (!phone) return alert("El cliente no tiene teléfono guardado");
|
||||||
|
|
||||||
|
|||||||
@@ -117,6 +117,11 @@
|
|||||||
</button>
|
</button>
|
||||||
<p class="text-center text-[10px] font-bold text-slate-400 mt-4">Al quedártelo, el servicio pasará a tu lista de Asignados.</p>
|
<p class="text-center text-[10px] font-bold text-slate-400 mt-4">Al quedártelo, el servicio pasará a tu lista de Asignados.</p>
|
||||||
|
|
||||||
|
<!-- 🆕 Anular sin tener que asignártelo primero (evita el WhatsApp de "asignado" al cliente) -->
|
||||||
|
<button onclick="anularServicio()" id="btnAnularBolsa" class="w-full mt-3 bg-white text-rose-600 font-black py-3.5 rounded-2xl border border-rose-200 active:scale-95 transition-transform flex items-center justify-center gap-2 uppercase tracking-widest text-xs">
|
||||||
|
<i data-lucide="ban" class="w-4 h-4"></i> Anular Aviso
|
||||||
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="toast" class="fixed top-5 left-1/2 -translate-x-1/2 bg-slate-800 text-white px-5 py-3 rounded-full shadow-2xl z-[200] font-bold text-xs uppercase tracking-widest flex items-center gap-2 transition-all duration-300 opacity-0 pointer-events-none transform -translate-y-10">
|
<div id="toast" class="fixed top-5 left-1/2 -translate-x-1/2 bg-slate-800 text-white px-5 py-3 rounded-full shadow-2xl z-[200] font-bold text-xs uppercase tracking-widest flex items-center gap-2 transition-all duration-300 opacity-0 pointer-events-none transform -translate-y-10">
|
||||||
@@ -440,6 +445,42 @@
|
|||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 🆕 Anula el aviso directamente desde la bolsa, sin tener que asignártelo primero
|
||||||
|
// (así el cliente NO recibe el WhatsApp de "servicio asignado" por algo que ni siquiera vas a coger).
|
||||||
|
async function anularServicio() {
|
||||||
|
const id = document.getElementById('detId').value;
|
||||||
|
if (!confirm("¿Seguro que quieres anular este aviso? No podrás deshacerlo desde aquí.")) return;
|
||||||
|
|
||||||
|
const stAnulado = systemStatuses.find(st => st.name.toLowerCase().includes('anula'));
|
||||||
|
if (!stAnulado) return alert("El sistema no tiene un estado 'Anulado' configurado. Avisa a la oficina.");
|
||||||
|
|
||||||
|
const btn = document.getElementById('btnAnularBolsa');
|
||||||
|
const originalContent = btn.innerHTML;
|
||||||
|
btn.innerHTML = `<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Anulando...`;
|
||||||
|
btn.disabled = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_URL}/providers/scraped/${id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
|
||||||
|
body: JSON.stringify({ status_operativo: String(stAnulado.id) })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
showToast("Aviso anulado");
|
||||||
|
closeModal();
|
||||||
|
loadAvailableServices();
|
||||||
|
} else {
|
||||||
|
alert("Error al anular.");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
alert("Error de conexión");
|
||||||
|
} finally {
|
||||||
|
btn.innerHTML = originalContent;
|
||||||
|
btn.disabled = false;
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
}
|
||||||
// 🔥 FUNCIÓN DE FILTRADO Y ORDENACIÓN 🔥
|
// 🔥 FUNCIÓN DE FILTRADO Y ORDENACIÓN 🔥
|
||||||
function filterServices() {
|
function filterServices() {
|
||||||
const query = document.getElementById('searchInput').value.toLowerCase().trim();
|
const query = document.getElementById('searchInput').value.toLowerCase().trim();
|
||||||
|
|||||||
Reference in New Issue
Block a user