-
-
+
+
+
+ ESTADO
+ #000
+
+
Título
+
Cliente
+
+ Fecha
-
-
Mapa de Cambios
-
+
+
+
+
+
+
+
+
+
+
+ Historial de Cambios
+
+
+
@@ -109,21 +261,32 @@
document.addEventListener("DOMContentLoaded", () => {
const token = localStorage.getItem("token");
if (!token) window.location.href = "index.html";
+
+ // Valores por defecto
+ document.getElementById('sDate').valueAsDate = new Date();
+ const now = new Date();
+ document.getElementById('sTime').value = now.toTimeString().slice(0,5);
+
fetchStatuses();
fetchServices();
+ loadCompanies();
});
+ // --- NAVEGACIÓN ---
function toggleView(view) {
document.getElementById('servicesListView').classList.add('hidden');
document.getElementById('createServiceView').classList.add('hidden');
+
if(view === 'list') {
document.getElementById('servicesListView').classList.remove('hidden');
- fetchServices();
+ fetchServices(); // Recargar lista al volver
} else {
document.getElementById('createServiceView').classList.remove('hidden');
+ window.scrollTo(0,0);
}
}
+ // --- DATOS MAESTROS ---
async function fetchStatuses() {
try {
const res = await fetch(`${API_URL}/statuses`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
@@ -132,6 +295,32 @@
} catch(e) {}
}
+ async function loadCompanies() {
+ try {
+ const res = await fetch(`${API_URL}/companies`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
+ const data = await res.json();
+ if (data.ok) {
+ const sel = document.getElementById('sCompanyId');
+ sel.innerHTML = '
';
+ data.companies.forEach(c => sel.innerHTML += `
`);
+ }
+ } catch (e) {}
+ }
+
+ async function quickAddCompany() {
+ const name = prompt("Nombre de la nueva compañía:");
+ if(!name) return;
+ try {
+ const res = await fetch(`${API_URL}/companies`, {
+ method: 'POST',
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
+ body: JSON.stringify({ name })
+ });
+ if(res.ok) { showToast("Compañía añadida"); loadCompanies(); }
+ } catch(e) { alert("Error"); }
+ }
+
+ // --- LÓGICA DE SERVICIOS (LISTAR) ---
async function fetchServices() {
try {
const res = await fetch(`${API_URL}/services`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
@@ -139,16 +328,35 @@
const tbody = document.getElementById('servicesTableBody');
tbody.innerHTML = "";
- if(data.services.length === 0) { tbody.innerHTML = `
| No hay servicios registrados. |
`; return; }
+ if(!data.ok || data.services.length === 0) {
+ tbody.innerHTML = `
| No hay servicios registrados. |
`;
+ return;
+ }
data.services.forEach(s => {
const color = s.status_color || 'gray';
+ // Parse fecha
+ const date = new Date(s.created_at);
+ const formattedDate = date.toLocaleDateString('es-ES', { day: '2-digit', month: 'short' });
+
tbody.innerHTML += `
-
- | ${new Date(s.created_at).toLocaleDateString()} |
- ${s.contact_name} ${s.address} |
- ${s.title} |
- ${s.status_name || 'Nuevo'} |
+
+ | ${formattedDate} |
+
+ ${s.contact_name}
+ ${s.address}
+ |
+
+ ${s.description || 'Sin detalles'}
+ ${s.is_urgent ? 'Urgente' : ''}
+ ${s.is_company ? `${s.company_name || 'Compañía'}` : ''}
+ |
+
+
+ ${s.status_name || 'Nuevo'}
+
+ |
|
`;
@@ -157,46 +365,148 @@
} catch (e) { console.error(e); }
}
- async function openDetail(id, client, title, statusName, statusColor) {
+ // --- LÓGICA DE CREAR ---
+ function toggleCompanyFields() {
+ const isChecked = document.getElementById('sIsCompany').checked;
+ const div = document.getElementById('companyFields');
+ if(isChecked) {
+ div.classList.remove('hidden');
+ loadCompanies();
+ } else {
+ div.classList.add('hidden');
+ }
+ }
+
+ async function searchClientByPhone() {
+ const phone = document.getElementById('sPhone').value;
+ if(phone.length < 8) 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 c = data.client;
+ document.getElementById('sName').value = c.full_name;
+ document.getElementById('sEmail').value = c.email || "";
+ document.getElementById('clientFoundMsg').classList.remove('hidden');
+
+ const addresses = c.addresses || [];
+ if (addresses.length > 0) {
+ document.getElementById('sAddress').value = addresses[addresses.length - 1];
+ const select = document.getElementById('sAddressSelect');
+ select.innerHTML = '
';
+ addresses.forEach(addr => select.innerHTML += `
`);
+ if (addresses.length > 1) select.classList.remove('hidden');
+ }
+ } else {
+ document.getElementById('clientFoundMsg').classList.add('hidden');
+ document.getElementById('sAddressSelect').classList.add('hidden');
+ }
+ } catch (e) { console.error(e); }
+ }
+
+ function selectAddress(val) { if(val) document.getElementById('sAddress').value = val; }
+
+ async function createService(e) {
+ e.preventDefault();
+ const btn = document.getElementById('btnSave');
+ btn.disabled = true; btn.innerText = "Guardando...";
+
+ const data = {
+ phone: document.getElementById('sPhone').value,
+ name: document.getElementById('sName').value,
+ address: document.getElementById('sAddress').value,
+ email: document.getElementById('sEmail').value,
+ description: document.getElementById('sDesc').value,
+ scheduled_date: document.getElementById('sDate').value,
+ scheduled_time: document.getElementById('sTime').value,
+ duration: document.getElementById('sDuration').value,
+ is_urgent: document.getElementById('sUrgent').checked,
+ is_company: document.getElementById('sIsCompany').checked,
+ company_id: document.getElementById('sCompanyId').value || null,
+ company_ref: document.getElementById('sCompanyRef').value,
+ internal_notes: document.getElementById('sNotesInternal').value,
+ client_notes: document.getElementById('sNotesClient').value
+ };
+
+ try {
+ const res = await fetch(`${API_URL}/services`, {
+ method: 'POST',
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
+ body: JSON.stringify(data)
+ });
+ const json = await res.json();
+
+ if (json.ok) {
+ showToast("✅ Servicio Creado");
+ document.querySelector('form').reset(); // Limpiar
+ toggleView('list'); // Volver
+ } else {
+ showToast("❌ " + json.error, true);
+ }
+ } catch (e) {
+ showToast("Error de conexión", true);
+ } finally {
+ btn.disabled = false; btn.innerHTML = '
CREAR SERVICIO'; lucide.createIcons();
+ }
+ }
+
+ // --- LÓGICA DE DETALLE Y ESTADOS ---
+ async function openDetail(id, client, title, statusName, statusColor, date) {
currentServiceId = id;
document.getElementById('serviceDetailPanel').classList.remove('hidden');
- document.getElementById('detailTitle').innerText = title;
+
+ // Llenar datos cabecera
+ document.getElementById('detailTitle').innerText = title || 'Servicio General';
document.getElementById('detailClient').innerText = client;
+ document.getElementById('detailId').innerText = `#${id}`;
+ document.getElementById('detailDate').innerText = date;
+
const badge = document.getElementById('detailStatusBadge');
badge.innerText = statusName;
- badge.className = `px-2 py-1 rounded text-xs font-bold text-white mb-2 inline-block bg-${statusColor}-500`;
+ // Limpiar clases previas de color
+ badge.className = `px-2 py-1 rounded text-[10px] font-bold text-white uppercase tracking-wide bg-${statusColor}-500`;
- // Llenar select
+ // Llenar selector de estados
const sel = document.getElementById('newStatusSelect');
sel.innerHTML = "";
allStatuses.forEach(s => {
sel.innerHTML += `
`;
});
- // Cargar Mapa de Estados
+ // Cargar Historial
loadTimeline(id);
}
async function loadTimeline(id) {
const timeline = document.getElementById('statusTimeline');
- timeline.innerHTML = '
Cargando historial...
';
+ timeline.innerHTML = '
Cargando...
';
const res = await fetch(`${API_URL}/services/${id}/logs`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json();
timeline.innerHTML = "";
+ if(data.logs.length === 0) { timeline.innerHTML = '
Sin historial registrado.
'; return; }
+
data.logs.forEach(log => {
const color = log.new_color || 'gray';
+ const date = new Date(log.created_at);
+
timeline.innerHTML += `
-
-
+
+
-
${log.new_status}
-
-
+
+
${log.new_status}
+
+
+
${log.comment || 'Cambio de estado'}
-
Por: ${log.user_name || 'Sistema'}
+
+ Usuario: ${log.user_name || 'Sistema'}
+
`;
@@ -205,6 +515,7 @@
async function updateStatus() {
const statusId = document.getElementById('newStatusSelect').value;
+ if(!statusId) return;
const comment = prompt("Añade un comentario sobre este cambio (opcional):");
try {
@@ -214,38 +525,13 @@
body: JSON.stringify({ status_id: statusId, comment: comment })
});
showToast("Estado actualizado");
- loadTimeline(currentServiceId);
- fetchServices(); // Refrescar lista de fondo
- } catch(e) { showToast("Error", true); }
+ loadTimeline(currentServiceId); // Recargar logs
+ fetchServices(); // Recargar lista fondo
+ } catch(e) { showToast("Error al actualizar", true); }
}
function closeDetailPanel() { document.getElementById('serviceDetailPanel').classList.add('hidden'); }
- // --- TU LÓGICA DE CREAR SERVICIO (RESUMIDA) ---
- async function createService(e) {
- e.preventDefault();
- // ... (Aquí iría la lógica completa de tu formulario anterior para recoger los datos) ...
- // Simulación simple para este ejemplo:
- const data = {
- phone: document.getElementById('sPhone').value,
- name: document.getElementById('sName').value,
- address: document.getElementById('sAddress').value,
- email: document.getElementById('sEmail').value,
- description: document.getElementById('sDesc').value
- };
-
- try {
- const res = await fetch(`${API_URL}/services`, {
- method: 'POST', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
- body: JSON.stringify(data)
- });
- if(res.ok) { showToast("Creado"); toggleView('list'); }
- } catch(e) { showToast("Error", true); }
- }
-
- // --- HELPERS (Buscador clientes, etc) ---
- // (Pega aquí searchClientByPhone, loadCompanies, etc. del código anterior)
-
function showToast(msg, isError = false) {
const t = document.getElementById('toast'), m = document.getElementById('toastMsg');
t.className = `fixed bottom-5 right-5 px-6 py-3 rounded-lg shadow-2xl transition-all duration-300 z-50 flex items-center gap-3 ${isError ? 'bg-red-600' : 'bg-slate-800'} text-white`;