281 lines
16 KiB
HTML
281 lines
16 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Configuración - IntegraRepara</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://unpkg.com/lucide@latest"></script>
|
|
<style>
|
|
.fade-in { animation: fadeIn 0.3s ease-in-out; }
|
|
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
.scroller::-webkit-scrollbar { width: 6px; }
|
|
.scroller::-webkit-scrollbar-thumb { background-color: #cbd5e1; border-radius: 4px; }
|
|
</style>
|
|
</head>
|
|
<body class="bg-gray-50 text-gray-800 font-sans h-screen overflow-hidden">
|
|
|
|
<div class="flex h-full">
|
|
<div id="sidebar-container" class="h-full"></div>
|
|
|
|
<div class="flex-1 flex flex-col h-full relative">
|
|
<div id="header-container"></div>
|
|
|
|
<main class="flex-1 overflow-hidden flex flex-col p-6 fade-in">
|
|
|
|
<h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center gap-2">
|
|
<i data-lucide="settings" class="text-blue-600"></i> Configuración Avanzada
|
|
</h2>
|
|
|
|
<div class="flex border-b border-gray-200 mb-6 overflow-x-auto">
|
|
<button onclick="showTab('zones')" id="tab-zones" class="tab-btn px-6 py-3 text-sm font-bold text-blue-600 border-b-2 border-blue-600 hover:bg-gray-50 transition whitespace-nowrap">
|
|
<i data-lucide="map" class="inline w-4 h-4 mr-1"></i> Zonas de Actuación
|
|
</button>
|
|
<button onclick="showTab('templates')" id="tab-templates" class="tab-btn px-6 py-3 text-sm font-medium text-gray-500 hover:text-blue-600 hover:bg-gray-50 transition whitespace-nowrap">
|
|
<i data-lucide="file-text" class="inline w-4 h-4 mr-1"></i> Plantillas
|
|
</button>
|
|
<button onclick="showTab('whatsapp')" id="tab-whatsapp" class="tab-btn px-6 py-3 text-sm font-medium text-gray-500 hover:text-blue-600 hover:bg-gray-50 transition whitespace-nowrap">
|
|
<i data-lucide="message-circle" class="inline w-4 h-4 mr-1"></i> WhatsApp
|
|
</button>
|
|
<button onclick="showTab('providers')" id="tab-providers" class="tab-btn px-6 py-3 text-sm font-medium text-gray-500 hover:text-blue-600 hover:bg-gray-50 transition whitespace-nowrap">
|
|
<i data-lucide="truck" class="inline w-4 h-4 mr-1"></i> Proveedores
|
|
</button>
|
|
<button onclick="showTab('portal')" id="tab-portal" class="tab-btn px-6 py-3 text-sm font-medium text-gray-500 hover:text-blue-600 hover:bg-gray-50 transition whitespace-nowrap">
|
|
<i data-lucide="globe" class="inline w-4 h-4 mr-1"></i> Portal Cliente
|
|
</button>
|
|
</div>
|
|
|
|
<div class="flex-1 overflow-hidden relative">
|
|
|
|
<div id="view-zones" class="tab-content h-full flex flex-col">
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 h-full">
|
|
<div class="bg-white rounded-xl shadow border border-gray-100 flex flex-col h-full overflow-hidden">
|
|
<div class="p-3 bg-gray-50 border-b border-gray-200 font-bold text-sm text-gray-700">1. Selecciona Provincia</div>
|
|
<div class="p-2 border-b"><input type="text" id="searchProv" placeholder="Filtrar..." class="w-full text-xs p-2 border rounded" onkeyup="filterProvinces()"></div>
|
|
<div id="listProvinces" class="flex-1 overflow-y-auto scroller p-2 space-y-1"></div>
|
|
</div>
|
|
<div class="bg-white rounded-xl shadow border border-gray-100 flex flex-col h-full overflow-hidden opacity-50 pointer-events-none transition-opacity" id="colZones">
|
|
<div class="p-3 bg-gray-50 border-b border-gray-200 font-bold text-sm text-gray-700 flex justify-between items-center">
|
|
<span>2. Crear/Elegir Zona</span>
|
|
<button onclick="addZonePrompt()" class="text-blue-600 hover:bg-blue-100 p-1 rounded"><i data-lucide="plus" class="w-4 h-4"></i></button>
|
|
</div>
|
|
<div id="listZones" class="flex-1 overflow-y-auto scroller p-2 space-y-1 text-sm">
|
|
<p class="text-center text-gray-400 mt-10">Selecciona una provincia</p>
|
|
</div>
|
|
</div>
|
|
<div class="bg-white rounded-xl shadow border border-gray-100 flex flex-col h-full overflow-hidden opacity-50 pointer-events-none transition-opacity" id="colOperators">
|
|
<div class="p-3 bg-gray-50 border-b border-gray-200 font-bold text-sm text-gray-700 flex justify-between items-center">
|
|
<span>3. Asignar Operarios</span>
|
|
<button onclick="saveZoneAssignment()" class="bg-green-600 text-white text-xs px-3 py-1 rounded hover:bg-green-700 shadow">Guardar</button>
|
|
</div>
|
|
<div id="listOperators" class="flex-1 overflow-y-auto scroller p-2 space-y-1">
|
|
<p class="text-center text-gray-400 mt-10">Selecciona una zona</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="view-templates" class="tab-content hidden h-full">
|
|
<div class="bg-white rounded-xl shadow p-8 text-center border border-gray-100">
|
|
<i data-lucide="file-text" class="w-12 h-12 text-gray-300 mx-auto mb-4"></i>
|
|
<h3 class="text-lg font-bold text-gray-700">Gestor de Plantillas</h3>
|
|
<p class="text-gray-500 mb-4">Configura aquí los textos predefinidos para partes de trabajo y correos.</p>
|
|
<button class="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm">Nueva Plantilla</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="view-whatsapp" class="tab-content hidden h-full">
|
|
<div class="bg-white rounded-xl shadow p-8 text-center border border-gray-100">
|
|
<i data-lucide="message-circle" class="w-12 h-12 text-green-500 mx-auto mb-4"></i>
|
|
<h3 class="text-lg font-bold text-gray-700">Conexión WhatsApp</h3>
|
|
<p class="text-gray-500 mb-4">Estado de la conexión con Evolution API.</p>
|
|
<span class="bg-green-100 text-green-700 px-3 py-1 rounded-full text-xs font-bold">Conectado</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="view-providers" class="tab-content hidden h-full">
|
|
<div class="bg-white rounded-xl shadow p-8 text-center border border-gray-100">
|
|
<i data-lucide="truck" class="w-12 h-12 text-gray-300 mx-auto mb-4"></i>
|
|
<h3 class="text-lg font-bold text-gray-700">Proveedores</h3>
|
|
<p class="text-gray-500 mb-4">Gestión de proveedores de material y servicios externos.</p>
|
|
<button class="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm">Añadir Proveedor</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="view-portal" class="tab-content hidden h-full">
|
|
<div class="bg-white rounded-xl shadow p-8 text-center border border-gray-100">
|
|
<i data-lucide="globe" class="w-12 h-12 text-blue-300 mx-auto mb-4"></i>
|
|
<h3 class="text-lg font-bold text-gray-700">Portal del Cliente</h3>
|
|
<p class="text-gray-500 mb-4">Configura qué pueden ver tus clientes al acceder con su enlace.</p>
|
|
<div class="inline-flex items-center gap-2 border p-2 rounded bg-gray-50">
|
|
<input type="checkbox" checked class="text-blue-600"> <span>Permitir ver fotos</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="toast" class="fixed bottom-5 right-5 bg-slate-800 text-white px-6 py-3 rounded-lg shadow-2xl transform translate-y-20 opacity-0 transition-all duration-300 z-50 flex items-center gap-3"><span id="toastMsg">Msg</span></div>
|
|
|
|
<script src="js/layout.js"></script>
|
|
<script>
|
|
// VARIABLES GLOBALES (Solo para Zonas por ahora)
|
|
let provinces = [], currentProvId = null, currentZoneId = null;
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
if (!localStorage.getItem("token")) window.location.href = "index.html";
|
|
loadProvinces(); // Carga inicial para la pestaña de zonas
|
|
});
|
|
|
|
// --- SISTEMA DE PESTAÑAS ---
|
|
function showTab(tabId) {
|
|
// Ocultar todo
|
|
document.querySelectorAll('.tab-content').forEach(el => el.classList.add('hidden'));
|
|
// Mostrar seleccionado
|
|
document.getElementById(`view-${tabId}`).classList.remove('hidden');
|
|
|
|
// Estilos botones
|
|
document.querySelectorAll('.tab-btn').forEach(el => {
|
|
el.classList.remove('text-blue-600', 'border-b-2', 'border-blue-600', 'font-bold');
|
|
el.classList.add('text-gray-500', 'font-medium');
|
|
});
|
|
const btn = document.getElementById(`tab-${tabId}`);
|
|
btn.classList.add('text-blue-600', 'border-b-2', 'border-blue-600', 'font-bold');
|
|
btn.classList.remove('text-gray-500', 'font-medium');
|
|
}
|
|
|
|
// ============================
|
|
// LÓGICA DE ZONAS (Completa)
|
|
// ============================
|
|
async function loadProvinces() {
|
|
try {
|
|
const res = await fetch(`${API_URL}/provinces`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
|
|
const data = await res.json();
|
|
provinces = data.provinces;
|
|
renderProvinces(provinces);
|
|
} catch(e) {}
|
|
}
|
|
|
|
function renderProvinces(list) {
|
|
const cont = document.getElementById('listProvinces');
|
|
cont.innerHTML = "";
|
|
list.forEach(p => {
|
|
const div = document.createElement('div');
|
|
div.className = "p-2 hover:bg-blue-50 rounded cursor-pointer text-sm flex justify-between items-center group prov-item";
|
|
div.innerHTML = `<span>${p.name}</span><i data-lucide="chevron-right" class="w-4 h-4 text-gray-300"></i>`;
|
|
div.onclick = () => selectProvince(p.id, div);
|
|
cont.appendChild(div);
|
|
});
|
|
lucide.createIcons();
|
|
}
|
|
|
|
function filterProvinces() {
|
|
const term = document.getElementById('searchProv').value.toLowerCase();
|
|
renderProvinces(provinces.filter(p => p.name.toLowerCase().includes(term)));
|
|
}
|
|
|
|
async function selectProvince(id, el) {
|
|
currentProvId = id;
|
|
document.querySelectorAll('.prov-item').forEach(d => d.classList.remove('bg-blue-100', 'font-bold'));
|
|
el.classList.add('bg-blue-100', 'font-bold');
|
|
|
|
document.getElementById('colZones').classList.remove('opacity-50', 'pointer-events-none');
|
|
document.getElementById('colOperators').classList.add('opacity-50', 'pointer-events-none');
|
|
loadZones(id);
|
|
}
|
|
|
|
async function loadZones(provId) {
|
|
const res = await fetch(`${API_URL}/zones?province_id=${provId}`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
|
|
const data = await res.json();
|
|
const list = document.getElementById('listZones');
|
|
list.innerHTML = "";
|
|
|
|
data.zones.forEach(z => {
|
|
const div = document.createElement('div');
|
|
div.className = "p-3 border border-gray-100 rounded hover:border-blue-300 cursor-pointer bg-white mb-2 flex justify-between items-center zone-item";
|
|
div.innerHTML = `<span class="font-medium text-gray-700">${z.name}</span>`;
|
|
|
|
if (z.owner_id) {
|
|
const btn = document.createElement('button');
|
|
btn.innerHTML = '<i data-lucide="trash" class="w-3 h-3"></i>';
|
|
btn.className = "text-red-300 hover:text-red-500";
|
|
btn.onclick = (e) => { e.stopPropagation(); deleteZone(z.id); };
|
|
div.appendChild(btn);
|
|
}
|
|
div.onclick = () => selectZone(z.id, div);
|
|
list.appendChild(div);
|
|
});
|
|
lucide.createIcons();
|
|
}
|
|
|
|
async function addZonePrompt() {
|
|
const name = prompt("Nombre de la nueva zona (ej: Zona Norte):");
|
|
if(!name) return;
|
|
await fetch(`${API_URL}/zones`, {
|
|
method: 'POST', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
|
|
body: JSON.stringify({ province_id: currentProvId, name })
|
|
});
|
|
loadZones(currentProvId);
|
|
}
|
|
|
|
async function deleteZone(id) {
|
|
if(!confirm("¿Borrar zona?")) return;
|
|
await fetch(`${API_URL}/zones/${id}`, { method: 'DELETE', headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
|
|
loadZones(currentProvId);
|
|
}
|
|
|
|
async function selectZone(id, el) {
|
|
currentZoneId = id;
|
|
document.querySelectorAll('.zone-item').forEach(d => d.classList.remove('border-blue-500', 'bg-blue-50'));
|
|
el.classList.add('border-blue-500', 'bg-blue-50');
|
|
document.getElementById('colOperators').classList.remove('opacity-50', 'pointer-events-none');
|
|
loadOperatorsForZone(id);
|
|
}
|
|
|
|
async function loadOperatorsForZone(zoneId) {
|
|
const resOps = await fetch(`${API_URL}/operators`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
|
|
const dataOps = await resOps.json();
|
|
|
|
const resAssigned = await fetch(`${API_URL}/zones/${zoneId}/operators`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
|
|
const dataAssigned = await resAssigned.json();
|
|
const assignedSet = new Set(dataAssigned.assignedIds);
|
|
|
|
const list = document.getElementById('listOperators');
|
|
list.innerHTML = "";
|
|
|
|
dataOps.operators.forEach(op => {
|
|
const checked = assignedSet.has(op.id) ? 'checked' : '';
|
|
list.innerHTML += `
|
|
<label class="flex items-center p-2 hover:bg-gray-50 rounded cursor-pointer">
|
|
<input type="checkbox" value="${op.id}" class="op-zone-check w-4 h-4 text-green-600 rounded mr-3" ${checked}>
|
|
<span class="text-sm font-medium text-gray-700">${op.full_name}</span>
|
|
</label>
|
|
`;
|
|
});
|
|
}
|
|
|
|
async function saveZoneAssignment() {
|
|
const checkboxes = document.querySelectorAll('.op-zone-check:checked');
|
|
const ids = Array.from(checkboxes).map(c => c.value);
|
|
try {
|
|
await fetch(`${API_URL}/zones/${currentZoneId}/assign`, {
|
|
method: 'POST',
|
|
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
|
|
body: JSON.stringify({ operator_ids: ids })
|
|
});
|
|
showToast("Asignación guardada ✅");
|
|
} catch(e) { showToast("Error al guardar", true); }
|
|
}
|
|
|
|
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`;
|
|
m.innerText = msg; t.classList.remove('translate-y-20', 'opacity-0');
|
|
setTimeout(() => t.classList.add('translate-y-20', 'opacity-0'), 3000);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |