Actualizar calendario.html
This commit is contained in:
183
calendario.html
183
calendario.html
@@ -48,6 +48,10 @@
|
||||
/* Estilos para el chat */
|
||||
.msg-me { background-color: #dcfce7; border-bottom-right-radius: 4px; border: 1px solid #bbf7d0; align-self: flex-end; }
|
||||
.msg-other { background-color: #f1f5f9; border-bottom-left-radius: 4px; border: 1px solid #e2e8f0; align-self: flex-start; }
|
||||
|
||||
/* Modal Nuevo Servicio */
|
||||
.input-modern { @apply w-full bg-slate-50 border border-slate-200 px-4 py-3 rounded-xl text-sm font-semibold text-slate-700 outline-none transition-all focus:border-blue-500 focus:bg-white focus:ring-2 focus:ring-blue-100; }
|
||||
.label-modern { @apply block text-[10px] font-black text-slate-500 uppercase tracking-widest mb-1.5 ml-1; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="text-slate-800 font-sans antialiased h-screen flex flex-col overflow-hidden relative text-left">
|
||||
@@ -64,6 +68,9 @@
|
||||
<div id="topNotificationBadge" class="hidden bg-red-500 text-white text-[10px] font-black px-1.5 py-0.5 rounded-full animate-bounce shadow-sm border border-white">!</div>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="openNewServiceModal()" class="w-10 h-10 shrink-0 bg-primary-dynamic rounded-full flex items-center justify-center text-white shadow-md active:scale-90 transition-transform mr-1">
|
||||
<i data-lucide="plus" class="w-5 h-5"></i>
|
||||
</button>
|
||||
<div class="flex gap-1 shrink-0">
|
||||
<button onclick="changeWeek(-1)" class="w-10 h-10 bg-white rounded-full flex items-center justify-center text-slate-600 border border-slate-200 active:bg-slate-50 shadow-sm"><i data-lucide="chevron-left" class="w-5 h-5"></i></button>
|
||||
<button onclick="changeWeek(1)" class="w-10 h-10 bg-white rounded-full flex items-center justify-center text-slate-600 border border-slate-200 active:bg-slate-50 shadow-sm"><i data-lucide="chevron-right" class="w-5 h-5"></i></button>
|
||||
@@ -282,6 +289,69 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="newServiceModal" class="translate-y-full transition-transform duration-300 fixed inset-0 z-[150] flex flex-col bg-slate-50" style="display:none;">
|
||||
<div class="pt-safe bg-white border-b border-slate-200 flex justify-between items-center p-4 shrink-0 shadow-sm">
|
||||
<button type="button" onclick="closeNewServiceModal()" class="bg-slate-100 p-2.5 rounded-full text-slate-600 active:scale-90 transition-transform"><i data-lucide="x" class="w-6 h-6"></i></button>
|
||||
<h2 class="font-black text-slate-800 text-sm uppercase tracking-widest text-center">Nuevo Aviso</h2>
|
||||
<div class="w-10"></div>
|
||||
</div>
|
||||
<form onsubmit="saveNewServiceApp(event)" class="flex-1 overflow-y-auto p-5 space-y-5 no-scrollbar pb-32">
|
||||
<div class="bg-white p-5 rounded-3xl shadow-sm border border-slate-200 space-y-4 text-left">
|
||||
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest flex items-center gap-1.5"><i data-lucide="user" class="w-4 h-4 text-blue-500"></i> Datos Cliente</p>
|
||||
<div>
|
||||
<label class="label-modern">Teléfono</label>
|
||||
<input type="tel" id="nsPhone" oninput="searchClientApp(this.value)" placeholder="Ej: 600123456" class="input-modern" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label-modern">Nombre</label>
|
||||
<input type="text" id="nsName" placeholder="Nombre completo" class="input-modern" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label-modern">Dirección</label>
|
||||
<input type="text" id="nsAddress" placeholder="Calle, número, piso..." class="input-modern" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-5 rounded-3xl shadow-sm border border-slate-200 space-y-4 text-left">
|
||||
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest flex items-center gap-1.5"><i data-lucide="calendar" class="w-4 h-4 text-emerald-500"></i> Cita y Avería</p>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="label-modern">Día</label>
|
||||
<input type="date" id="nsDate" class="input-modern" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label-modern">Hora</label>
|
||||
<input type="time" id="nsTime" class="input-modern" required>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label-modern">Tiempo Estimado</label>
|
||||
<div class="relative">
|
||||
<select id="nsDuration" class="input-modern appearance-none pr-8 cursor-pointer">
|
||||
<option value="15">15 min</option>
|
||||
<option value="30">30 min</option>
|
||||
<option value="45">45 min</option>
|
||||
<option value="60" selected>1 h</option>
|
||||
<option value="90">1.5 h</option>
|
||||
<option value="120">2 h</option>
|
||||
<option value="180">3 h</option>
|
||||
<option value="240">4 h</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">Descripción del Trabajo</label>
|
||||
<textarea id="nsDesc" rows="3" placeholder="Detalle de la avería..." class="input-modern resize-none" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="btnSaveNewApp" class="w-full bg-primary-dynamic text-white font-black py-4 rounded-2xl shadow-xl flex items-center justify-center gap-2 uppercase tracking-widest active:scale-95 transition-all text-xs">
|
||||
<i data-lucide="check-circle" class="w-5 h-5"></i> Crear y Citar
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="toast" class="fixed top-8 left-1/2 -translate-x-1/2 bg-slate-900 text-white px-6 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">
|
||||
<i data-lucide="check" class="w-4 h-4 text-emerald-400"></i>
|
||||
<span id="toastMsg">Guardado</span>
|
||||
@@ -1262,6 +1332,119 @@
|
||||
setTimeout(() => { t.classList.add('opacity-0', 'pointer-events-none', '-translate-y-10'); t.classList.remove('translate-y-0'); }, 2000);
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// FUNCIONES DE ALTA RÁPIDA (OPERARIO)
|
||||
// =====================================
|
||||
function openNewServiceModal() {
|
||||
document.getElementById('nsPhone').value = "";
|
||||
document.getElementById('nsName').value = "";
|
||||
document.getElementById('nsAddress').value = "";
|
||||
document.getElementById('nsDesc').value = "";
|
||||
document.getElementById('nsTime').value = "";
|
||||
document.getElementById('nsDate').value = selectedDateStr;
|
||||
|
||||
const modal = document.getElementById('newServiceModal');
|
||||
modal.style.display = 'flex';
|
||||
setTimeout(() => modal.classList.remove('translate-y-full'), 10);
|
||||
safeLoadIcons();
|
||||
}
|
||||
|
||||
function closeNewServiceModal() {
|
||||
const modal = document.getElementById('newServiceModal');
|
||||
modal.classList.add('translate-y-full');
|
||||
setTimeout(() => modal.style.display = 'none', 300);
|
||||
}
|
||||
|
||||
async function searchClientApp(phone) {
|
||||
if (phone.length < 9) {
|
||||
document.getElementById('nsName').classList.remove('bg-emerald-50');
|
||||
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) {
|
||||
const nameInput = document.getElementById('nsName');
|
||||
if (!nameInput.value) {
|
||||
nameInput.value = data.client.full_name;
|
||||
nameInput.classList.add('bg-emerald-50');
|
||||
}
|
||||
if (!document.getElementById('nsAddress').value && data.client.addresses?.length > 0) {
|
||||
document.getElementById('nsAddress').value = data.client.addresses[0];
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
async function saveNewServiceApp(e) {
|
||||
e.preventDefault();
|
||||
const btn = document.getElementById('btnSaveNewApp');
|
||||
const originalHTML = btn.innerHTML;
|
||||
btn.innerHTML = `<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i> Procesando...`;
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem("token");
|
||||
const payloadDecoded = JSON.parse(atob(token.split('.')[1]));
|
||||
const myWorkerId = payloadDecoded.sub;
|
||||
|
||||
const createData = {
|
||||
phone: document.getElementById('nsPhone').value,
|
||||
name: document.getElementById('nsName').value,
|
||||
address: document.getElementById('nsAddress').value,
|
||||
description: document.getElementById('nsDesc').value,
|
||||
guild_id: null,
|
||||
assigned_to: myWorkerId,
|
||||
duration_minutes: document.getElementById('nsDuration').value,
|
||||
is_urgent: false,
|
||||
is_company: false,
|
||||
company_name: 'Particular',
|
||||
company_ref: null,
|
||||
mode: 'manual'
|
||||
};
|
||||
|
||||
const resCreate = await fetch(`${API_URL}/services/manual-high`, {
|
||||
method: 'POST',
|
||||
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
|
||||
body: JSON.stringify(createData)
|
||||
});
|
||||
const dataCreate = await resCreate.json();
|
||||
|
||||
if (!dataCreate.ok || !dataCreate.id) throw new Error("No se pudo crear el servicio");
|
||||
|
||||
const citadoSt = systemStatuses.find(st => st.name.toLowerCase().includes('citado'));
|
||||
const statusMapId = citadoSt ? String(citadoSt.id) : null;
|
||||
|
||||
const date = document.getElementById('nsDate').value;
|
||||
const time = document.getElementById('nsTime').value;
|
||||
const duration = document.getElementById('nsDuration').value;
|
||||
|
||||
await fetch(`${API_URL}/services/set-appointment/${dataCreate.id}`, {
|
||||
method: 'PUT',
|
||||
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
|
||||
body: JSON.stringify({
|
||||
date: date,
|
||||
time: time,
|
||||
duration_minutes: duration,
|
||||
status_operativo: statusMapId
|
||||
})
|
||||
});
|
||||
|
||||
closeNewServiceModal();
|
||||
showToast("¡Aviso Creado y Citado!");
|
||||
refreshData();
|
||||
|
||||
} catch (err) {
|
||||
showToast("Error al guardar", true);
|
||||
} finally {
|
||||
btn.innerHTML = originalHTML;
|
||||
btn.disabled = false;
|
||||
safeLoadIcons();
|
||||
}
|
||||
}
|
||||
|
||||
function logout() { localStorage.clear(); window.location.href = "index.html"; }
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user