Actualizar calendario.html
This commit is contained in:
124
calendario.html
124
calendario.html
@@ -131,7 +131,7 @@
|
|||||||
<button onclick="callClient()" class="w-full bg-primary-dynamic text-white font-black py-4 rounded-2xl shadow-xl flex items-center justify-center gap-3 uppercase text-sm tracking-widest active:scale-95 transition-all">
|
<button onclick="callClient()" class="w-full bg-primary-dynamic text-white font-black py-4 rounded-2xl shadow-xl flex items-center justify-center gap-3 uppercase text-sm tracking-widest active:scale-95 transition-all">
|
||||||
<i data-lucide="phone" class="w-5 h-5 fill-current"></i> Llamar al Cliente
|
<i data-lucide="phone" class="w-5 h-5 fill-current"></i> Llamar al Cliente
|
||||||
</button>
|
</button>
|
||||||
<button onclick="openWhatsApp()" class="w-full mt-3 bg-slate-50 text-slate-700 font-black py-3 rounded-2xl border border-slate-200 flex items-center justify-center gap-2 text-xs uppercase tracking-widest active:scale-95">
|
<button onclick="openWhatsApp()" class="w-full mt-3 bg-slate-50 text-slate-700 font-black py-3 rounded-2xl border border-slate-200 flex items-center justify-center gap-2 text-xs uppercase tracking-widest active:scale-95 transition-all">
|
||||||
<i data-lucide="message-circle" class="w-4 h-4 text-emerald-500"></i> WhatsApp
|
<i data-lucide="message-circle" class="w-4 h-4 text-emerald-500"></i> WhatsApp
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -151,6 +151,55 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white border border-slate-200 rounded-3xl p-5 shadow-sm space-y-4">
|
||||||
|
<p class="text-[10px] font-black text-blue-600 uppercase tracking-widest border-b border-slate-100 pb-2 flex items-center gap-1.5"><i data-lucide="calendar-clock" class="w-4 h-4"></i> Asignar Fechas y Tiempos</p>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-3">
|
||||||
|
<div>
|
||||||
|
<label class="block text-[10px] font-black text-slate-500 uppercase tracking-widest mb-1 ml-1">Fecha</label>
|
||||||
|
<input type="date" id="dateInput" class="w-full bg-slate-50 border border-slate-200 p-3 rounded-xl text-xs font-bold text-slate-700 shadow-sm outline-none focus:border-blue-400 focus:ring-2 focus:ring-blue-100 transition-all">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-[10px] font-black text-slate-500 uppercase tracking-widest mb-1 ml-1">Hora Inicio</label>
|
||||||
|
<input type="time" id="timeInput" class="w-full bg-slate-50 border border-slate-200 p-3 rounded-xl text-xs font-bold text-slate-700 shadow-sm outline-none focus:border-blue-400 focus:ring-2 focus:ring-blue-100 transition-all">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-[10px] font-black text-slate-500 uppercase tracking-widest mb-1 ml-1">Duración Estimada</label>
|
||||||
|
<div class="relative">
|
||||||
|
<select id="durationInput" class="w-full bg-slate-50 border border-slate-200 p-3 rounded-xl text-xs font-bold text-slate-700 shadow-sm outline-none focus:border-blue-400 focus:ring-2 focus:ring-blue-100 transition-all 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="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 h</option>
|
||||||
|
<option value="150">2 h 30 min</option>
|
||||||
|
<option value="180">3 h</option>
|
||||||
|
<option value="210">3 h 30 min</option>
|
||||||
|
<option value="240">4 h (Máx)</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 class="pt-2">
|
||||||
|
<p class="text-[10px] font-black text-slate-800 uppercase ml-1 flex items-center gap-1.5 mb-1.5"><i data-lucide="arrow-right-left" class="w-4 h-4 text-blue-500"></i> Cambio de Estado</p>
|
||||||
|
<div class="relative">
|
||||||
|
<select id="detStatusMap" class="w-full bg-slate-800 text-white border-none p-4 rounded-xl text-xs font-bold shadow-lg outline-none cursor-pointer appearance-none pr-10">
|
||||||
|
</select>
|
||||||
|
<i data-lucide="chevron-down" class="w-4 h-4 text-slate-400 absolute right-4 top-1/2 -translate-y-1/2 pointer-events-none"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="btnSaveAppt" onclick="saveAppointment()" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-black py-3.5 rounded-xl shadow-lg transition-all uppercase tracking-widest text-xs flex items-center justify-center gap-2 mt-2">
|
||||||
|
<i data-lucide="save" class="w-4 h-4"></i> Guardar Cambios
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="desc-box">
|
<div class="desc-box">
|
||||||
<p class="text-[10px] font-black text-primary-dynamic uppercase tracking-widest mb-3 flex items-center gap-2"><i data-lucide="file-text" class="w-4 h-4"></i> Descripción Técnica</p>
|
<p class="text-[10px] font-black text-primary-dynamic uppercase tracking-widest mb-3 flex items-center gap-2"><i data-lucide="file-text" class="w-4 h-4"></i> Descripción Técnica</p>
|
||||||
<div id="detDesc" class="text-sm font-bold text-slate-600 leading-relaxed max-h-60 overflow-y-auto no-scrollbar">--</div>
|
<div id="detDesc" class="text-sm font-bold text-slate-600 leading-relaxed max-h-60 overflow-y-auto no-scrollbar">--</div>
|
||||||
@@ -193,27 +242,21 @@
|
|||||||
// --- SISTEMA DE TEMA DINÁMICO ---
|
// --- SISTEMA DE TEMA DINÁMICO ---
|
||||||
async function applyTheme() {
|
async function applyTheme() {
|
||||||
try {
|
try {
|
||||||
// 1. Intentar cargar desde caché para velocidad
|
|
||||||
let theme = JSON.parse(localStorage.getItem('app_theme'));
|
let theme = JSON.parse(localStorage.getItem('app_theme'));
|
||||||
|
|
||||||
// 2. Consultar al servidor para asegurar actualización
|
|
||||||
const res = await fetch(`${API_URL}/config/company`, {
|
const res = await fetch(`${API_URL}/config/company`, {
|
||||||
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
|
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
if(data.ok && data.config && data.config.portal_settings && data.config.portal_settings.app_settings) {
|
||||||
if(data.ok && data.config && data.config.app_settings) {
|
theme = data.config.portal_settings.app_settings;
|
||||||
theme = data.config.app_settings;
|
|
||||||
localStorage.setItem('app_theme', JSON.stringify(theme));
|
localStorage.setItem('app_theme', JSON.stringify(theme));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(theme) {
|
if(theme) {
|
||||||
document.documentElement.style.setProperty('--primary', theme.primary);
|
document.documentElement.style.setProperty('--primary', theme.primary);
|
||||||
document.documentElement.style.setProperty('--secondary', theme.secondary);
|
document.documentElement.style.setProperty('--secondary', theme.secondary);
|
||||||
document.documentElement.style.setProperty('--app-bg', theme.bg);
|
document.documentElement.style.setProperty('--app-bg', theme.bg);
|
||||||
console.log("🎨 Tema corporativo aplicado desde BD");
|
|
||||||
}
|
}
|
||||||
} catch (e) { console.warn("Error cargando tema dinámico, usando default"); }
|
} catch (e) { console.warn("Usando tema por defecto"); }
|
||||||
}
|
}
|
||||||
|
|
||||||
function safeLoadIcons() {
|
function safeLoadIcons() {
|
||||||
@@ -242,10 +285,7 @@
|
|||||||
if (!localStorage.getItem("token") || localStorage.getItem("role") !== 'operario') {
|
if (!localStorage.getItem("token") || localStorage.getItem("role") !== 'operario') {
|
||||||
window.location.href = "index.html"; return;
|
window.location.href = "index.html"; return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aplicar colores antes de renderizar nada
|
|
||||||
await applyTheme();
|
await applyTheme();
|
||||||
|
|
||||||
safeLoadIcons();
|
safeLoadIcons();
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
currentWeekStart = getMonday(today);
|
currentWeekStart = getMonday(today);
|
||||||
@@ -281,7 +321,7 @@
|
|||||||
<button onclick="selectDate('${isoDate}')" class="day-card ${isSelected ? 'day-active' : 'day-inactive'} border min-w-[4.2rem] rounded-2xl p-4 flex flex-col items-center justify-center shrink-0">
|
<button onclick="selectDate('${isoDate}')" class="day-card ${isSelected ? 'day-active' : 'day-inactive'} border min-w-[4.2rem] rounded-2xl p-4 flex flex-col items-center justify-center shrink-0">
|
||||||
<span class="text-[9px] font-black uppercase opacity-70">${dayName}</span>
|
<span class="text-[9px] font-black uppercase opacity-70">${dayName}</span>
|
||||||
<span class="text-xl font-black mt-1">${dayNum}</span>
|
<span class="text-xl font-black mt-1">${dayNum}</span>
|
||||||
<div class="mt-2 w-full flex justify-center h-1.5"><span id="badge-${isoDate}" class="bg-blue-400 w-1.5 h-1.5 rounded-full hidden"></span></div>
|
<div class="mt-2 w-full flex justify-center h-1.5"><span id="badge-${isoDate}" class="bg-primary-dynamic w-1.5 h-1.5 rounded-full hidden opacity-50"></span></div>
|
||||||
</button>`;
|
</button>`;
|
||||||
}
|
}
|
||||||
updateBadges();
|
updateBadges();
|
||||||
@@ -305,7 +345,14 @@
|
|||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_URL}/statuses`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
|
const res = await fetch(`${API_URL}/statuses`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.ok) systemStatuses = data.statuses;
|
if (data.ok) {
|
||||||
|
systemStatuses = data.statuses;
|
||||||
|
const modalSelect = document.getElementById('detStatusMap');
|
||||||
|
modalSelect.innerHTML = '';
|
||||||
|
systemStatuses.forEach(st => {
|
||||||
|
modalSelect.innerHTML += `<option value="${st.id}">👉 ${st.name.toUpperCase()}</option>`;
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,7 +464,7 @@
|
|||||||
|
|
||||||
const extraContainer = document.getElementById('extraDataBox');
|
const extraContainer = document.getElementById('extraDataBox');
|
||||||
let detailsHtml = '';
|
let detailsHtml = '';
|
||||||
const skipKeys = ["Nombre Cliente", "CLIENTE", "Dirección", "DOMICILIO", "Población", "POBLACION-PROVINCIA", "scheduled_date", "scheduled_time", "status_operativo", "assigned_to", "guild_id", "Código Postal", "assigned_to_name", "Descripción", "DESCRIPCION", "Compañía", "COMPAÑIA"];
|
const skipKeys = ["Nombre Cliente", "CLIENTE", "Dirección", "DOMICILIO", "Población", "POBLACION-PROVINCIA", "scheduled_date", "scheduled_time", "status_operativo", "duration_minutes", "assigned_to", "guild_id", "Código Postal", "assigned_to_name", "Descripción", "DESCRIPCION", "Compañía", "COMPAÑIA"];
|
||||||
|
|
||||||
for(let key in raw) {
|
for(let key in raw) {
|
||||||
if(skipKeys.includes(key)) continue;
|
if(skipKeys.includes(key)) continue;
|
||||||
@@ -427,6 +474,15 @@
|
|||||||
}
|
}
|
||||||
extraContainer.innerHTML = detailsHtml || '<p class="text-xs text-slate-400">Sin datos adicionales.</p>';
|
extraContainer.innerHTML = detailsHtml || '<p class="text-xs text-slate-400">Sin datos adicionales.</p>';
|
||||||
|
|
||||||
|
// CARGAMOS LA FECHA, LA HORA Y LA DURACIÓN EN EL MODAL
|
||||||
|
document.getElementById('dateInput').value = raw.scheduled_date || "";
|
||||||
|
document.getElementById('timeInput').value = raw.scheduled_time || "";
|
||||||
|
document.getElementById('durationInput').value = raw.duration_minutes || "60";
|
||||||
|
|
||||||
|
const dbStat = raw.status_operativo;
|
||||||
|
const foundById = systemStatuses.find(st => String(st.id) === String(dbStat));
|
||||||
|
if(foundById) document.getElementById('detStatusMap').value = foundById.id;
|
||||||
|
|
||||||
const modal = document.getElementById('serviceModal');
|
const modal = document.getElementById('serviceModal');
|
||||||
modal.style.display = 'flex';
|
modal.style.display = 'flex';
|
||||||
setTimeout(() => modal.classList.remove('translate-y-full'), 10);
|
setTimeout(() => modal.classList.remove('translate-y-full'), 10);
|
||||||
@@ -470,14 +526,13 @@
|
|||||||
if(action === 'camino') { word = "camino"; msg = "¿Enviar 'De Camino'?"; }
|
if(action === 'camino') { word = "camino"; msg = "¿Enviar 'De Camino'?"; }
|
||||||
if(action === 'trabajando') { word = "trabaja"; msg = "¿Iniciar trabajo?"; }
|
if(action === 'trabajando') { word = "trabaja"; msg = "¿Iniciar trabajo?"; }
|
||||||
if(action === 'finalizado') { word = "finaliza"; msg = "¿Finalizar expediente?"; }
|
if(action === 'finalizado') { word = "finaliza"; msg = "¿Finalizar expediente?"; }
|
||||||
if(action === 'encuesta') { word = "finaliza"; msg = "¿Enviar encuesta?"; }
|
|
||||||
|
|
||||||
if(!confirm(msg)) return;
|
if(!confirm(msg)) return;
|
||||||
const st = systemStatuses.find(s => s.name.toLowerCase().includes(word));
|
const st = systemStatuses.find(s => s.name.toLowerCase().includes(word));
|
||||||
if(!st) return alert("Estado no encontrado");
|
if(!st) return alert("Estado no encontrado");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_URL}/providers/scraped/${currentServiceId}`, {
|
const res = await fetch(`${API_URL}/services/set-appointment/${currentServiceId}`, {
|
||||||
method: 'PUT', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
|
method: 'PUT', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
|
||||||
body: JSON.stringify({ status_operativo: st.id })
|
body: JSON.stringify({ status_operativo: st.id })
|
||||||
});
|
});
|
||||||
@@ -485,6 +540,37 @@
|
|||||||
} catch (e) { alert("Error"); }
|
} catch (e) { alert("Error"); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- FUNCIÓN ACTUALIZADA PARA CAPTURAR LA DURACIÓN ---
|
||||||
|
async function saveAppointment() {
|
||||||
|
const id = document.getElementById('detId').value;
|
||||||
|
const date = document.getElementById('dateInput').value;
|
||||||
|
const time = document.getElementById('timeInput').value;
|
||||||
|
const duration = document.getElementById('durationInput').value;
|
||||||
|
const statusMap = document.getElementById('detStatusMap').value;
|
||||||
|
|
||||||
|
const selectedSt = systemStatuses.find(st => String(st.id) === String(statusMap));
|
||||||
|
|
||||||
|
if (selectedSt && !selectedSt.is_final && !date && !selectedSt.name.toLowerCase().includes('pausa') && !selectedSt.name.toLowerCase().includes('asignar')) {
|
||||||
|
if(!confirm("No has asignado Fecha para este estado. ¿Deseas continuar?")) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const btn = document.getElementById('btnSaveAppt');
|
||||||
|
const originalContent = btn.innerHTML;
|
||||||
|
btn.innerHTML = `<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Guardando...`;
|
||||||
|
btn.disabled = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fetch(`${API_URL}/services/set-appointment/${id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
|
||||||
|
body: JSON.stringify({ date, time, duration_minutes: duration, status_operativo: statusMap })
|
||||||
|
});
|
||||||
|
|
||||||
|
closeDetailModal(); showToast("Estado actualizado"); refreshData();
|
||||||
|
} catch (e) { alert("Error"); }
|
||||||
|
finally { btn.innerHTML = originalContent; btn.disabled = false; }
|
||||||
|
}
|
||||||
|
|
||||||
function showToast(m) {
|
function showToast(m) {
|
||||||
const t = document.getElementById('toast'); document.getElementById('toastMsg').innerText = m;
|
const t = document.getElementById('toast'); document.getElementById('toastMsg').innerText = m;
|
||||||
t.classList.remove('opacity-0', '-translate-y-10'); t.classList.add('translate-y-0');
|
t.classList.remove('opacity-0', '-translate-y-10'); t.classList.add('translate-y-0');
|
||||||
|
|||||||
Reference in New Issue
Block a user