Actualizar usuarios.html
This commit is contained in:
301
usuarios.html
301
usuarios.html
@@ -175,126 +175,50 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Cargamos municipios AGRUPADOS por nombre
|
async function fetchMunicipios(provName) {
|
||||||
// 1. Carga municipios agrupados para facilitar la selección masiva
|
const selMun = document.getElementById('selMunicipio');
|
||||||
async function fetchMunicipios(provName) {
|
const loader = document.getElementById('loaderMun');
|
||||||
const selMun = document.getElementById('selMunicipio');
|
selMun.innerHTML = '<option value="">-- Cargando... --</option>';
|
||||||
const loader = document.getElementById('loaderMun');
|
selMun.disabled = true;
|
||||||
selMun.innerHTML = '<option value="">-- Cargando... --</option>';
|
if(!provName) return;
|
||||||
selMun.disabled = true;
|
if (loader) loader.classList.remove('hidden');
|
||||||
if(!provName) return;
|
|
||||||
|
|
||||||
if (loader) loader.classList.remove('hidden');
|
try {
|
||||||
try {
|
const res = await fetch(`${API_URL}/api/geo/municipios/${provName}`, {
|
||||||
const res = await fetch(`${API_URL}/api/geo/municipios/${provName}`, {
|
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.municipios.length > 0) {
|
||||||
|
const grouped = data.municipios.reduce((acc, m) => {
|
||||||
if (data.ok && data.municipios.length > 0) {
|
if(!acc[m.municipio]) acc[m.municipio] = [];
|
||||||
// Agrupamos por nombre de municipio para el selector
|
acc[m.municipio].push(m.codigo_postal);
|
||||||
const grouped = data.municipios.reduce((acc, m) => {
|
return acc;
|
||||||
if(!acc[m.municipio]) acc[m.municipio] = [];
|
}, {});
|
||||||
acc[m.municipio].push(m.codigo_postal);
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
selMun.innerHTML = '<option value="">-- Selecciona Pueblo --</option>';
|
selMun.innerHTML = '<option value="">-- Selecciona Pueblo --</option>';
|
||||||
Object.keys(grouped).sort().forEach(mun => {
|
Object.keys(grouped).sort().forEach(mun => {
|
||||||
const opt = document.createElement('option');
|
const opt = document.createElement('option');
|
||||||
opt.value = mun;
|
opt.value = mun;
|
||||||
// Guardamos la lista real de CPs para que el sistema los procese
|
opt.dataset.cps = JSON.stringify(grouped[mun]);
|
||||||
opt.dataset.cps = JSON.stringify(grouped[mun]);
|
opt.innerText = `${mun.toUpperCase()} (${grouped[mun].length} CPs)`;
|
||||||
opt.innerText = `${mun.toUpperCase()} (${grouped[mun].length} CPs)`;
|
selMun.appendChild(opt);
|
||||||
selMun.appendChild(opt);
|
});
|
||||||
});
|
selMun.innerHTML += '<option value="OTRO">-- NO ESTÁ EN LA LISTA (MANUAL) --</option>';
|
||||||
selMun.innerHTML += '<option value="OTRO">-- NO ESTÁ EN LA LISTA (MANUAL) --</option>';
|
selMun.disabled = false;
|
||||||
selMun.disabled = false;
|
} else {
|
||||||
}
|
selMun.innerHTML = '<option value="OTRO">-- ESCRIBIR MANUALMENTE --</option>';
|
||||||
} catch (e) {
|
selMun.disabled = false;
|
||||||
showToast("Error al conectar con la base de datos", true);
|
}
|
||||||
} finally {
|
} catch (e) {
|
||||||
if (loader) loader.classList.add('hidden');
|
console.error("Error geo:", e);
|
||||||
}
|
showToast("Error base de datos", true);
|
||||||
}
|
} finally {
|
||||||
|
if (loader) loader.classList.add('hidden');
|
||||||
// 2. Añadir zonas al usuario (CORREGIDO: Sin error de validación)
|
|
||||||
function addZoneToUser() {
|
|
||||||
const prov = document.getElementById('selProvince').value;
|
|
||||||
const selMun = document.getElementById('selMunicipio');
|
|
||||||
const munName = selMun.value;
|
|
||||||
|
|
||||||
// Validación corregida
|
|
||||||
if (!prov || !munName) {
|
|
||||||
showToast("Selecciona Provincia y Municipio primero", true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (munName === "OTRO") {
|
|
||||||
const manualCP = document.getElementById('cpInput').value.trim();
|
|
||||||
if(!manualCP) { showToast("Escribe el CP manual en el cuadro de la derecha", true); return; }
|
|
||||||
tempUserZones.push({ province: prov, city: "MANUAL", cps: manualCP });
|
|
||||||
} else {
|
|
||||||
// Extraemos la lista de CPs reales del municipio
|
|
||||||
const selectedOption = selMun.options[selMun.selectedIndex];
|
|
||||||
const cpList = JSON.parse(selectedOption.dataset.cps);
|
|
||||||
|
|
||||||
let addedCount = 0;
|
|
||||||
cpList.forEach(cp => {
|
|
||||||
// Verificamos si el CP individual ya está para no duplicar
|
|
||||||
const alreadyHasIt = tempUserZones.some(z => z.city === munName && z.cps === cp);
|
|
||||||
if (!alreadyHasIt) {
|
|
||||||
tempUserZones.push({ province: prov, city: munName, cps: cp });
|
|
||||||
addedCount++;
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if(addedCount > 0) {
|
|
||||||
showToast(`✅ Añadidos ${addedCount} códigos de ${munName}`);
|
|
||||||
} else {
|
|
||||||
showToast("Esta zona ya estaba completa", true);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
renderTempZones();
|
|
||||||
document.getElementById('cpInput').value = ""; // Limpiamos campo manual
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Renderizado con agrupación visual (Mantiene la tabla limpia)
|
|
||||||
function renderTempZones() {
|
|
||||||
const area = document.getElementById('userZonesArea');
|
|
||||||
if (!area) return;
|
|
||||||
|
|
||||||
area.innerHTML = tempUserZones.length === 0 ? '<p class="text-[10px] text-gray-300 italic p-1">Sin zonas añadidas...</p>' : "";
|
|
||||||
|
|
||||||
// Agrupamos solo para mostrarlo bonito en los chips
|
|
||||||
const visualGroup = tempUserZones.reduce((acc, curr) => {
|
|
||||||
if(!acc[curr.city]) acc[curr.city] = [];
|
|
||||||
acc[curr.city].push(curr.cps);
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
Object.keys(visualGroup).forEach(city => {
|
|
||||||
const cps = visualGroup[city].join(", ");
|
|
||||||
area.innerHTML += `
|
|
||||||
<span class="bg-blue-100 text-blue-700 px-3 py-1.5 rounded-lg text-[10px] font-black border border-blue-200 flex items-center gap-2">
|
|
||||||
${city} (${visualGroup[city].length} CPs)
|
|
||||||
<button type="button" onclick="removeTempCity('${city}')" class="text-blue-400 hover:text-red-500">
|
|
||||||
<i data-lucide="x" class="w-3 h-3"></i>
|
|
||||||
</button>
|
|
||||||
</span>`;
|
|
||||||
});
|
|
||||||
lucide.createIcons();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Función auxiliar para borrar un pueblo entero de la lista temporal
|
|
||||||
function removeTempCity(cityName) {
|
|
||||||
tempUserZones = tempUserZones.filter(z => z.city !== cityName);
|
|
||||||
renderTempZones();
|
|
||||||
}
|
|
||||||
function autoFillCP(munName) {
|
function autoFillCP(munName) {
|
||||||
const sel = document.getElementById('selMunicipio');
|
const sel = document.getElementById('selMunicipio');
|
||||||
const selectedOpt = sel.options[sel.selectedIndex];
|
|
||||||
if (munName === "OTRO") {
|
if (munName === "OTRO") {
|
||||||
const customMun = prompt("Nombre del Municipio:");
|
const customMun = prompt("Nombre del Municipio:");
|
||||||
if (customMun) {
|
if (customMun) {
|
||||||
@@ -303,43 +227,54 @@ function removeTempCity(cityName) {
|
|||||||
newOpt.innerText = customMun.toUpperCase();
|
newOpt.innerText = customMun.toUpperCase();
|
||||||
newOpt.selected = true;
|
newOpt.selected = true;
|
||||||
sel.appendChild(newOpt);
|
sel.appendChild(newOpt);
|
||||||
document.getElementById('cpInput').focus();
|
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(selectedOpt && selectedOpt.dataset.cp) {
|
|
||||||
document.getElementById('cpInput').value = selectedOpt.dataset.cp;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addZoneToUser() {
|
function addZoneToUser() {
|
||||||
const prov = document.getElementById('selProvince').value;
|
const prov = document.getElementById('selProvince').value;
|
||||||
const mun = document.getElementById('selMunicipio').value;
|
const selMun = document.getElementById('selMunicipio');
|
||||||
const cp = document.getElementById('cpInput').value.trim();
|
const munName = selMun.value;
|
||||||
if (!prov || !mun || !cp || mun === "OTRO") { showToast("Completa los datos de zona", true); return; }
|
|
||||||
|
if (!prov || !munName) { showToast("Selecciona Provincia y Municipio", true); return; }
|
||||||
|
|
||||||
if (tempUserZones.some(z => z.city === mun && z.cps === cp)) {
|
if (munName === "OTRO" || selMun.options[selMun.selectedIndex].text.includes("MANUAL")) {
|
||||||
showToast("Esta zona ya está añadida", true); return;
|
const manualCP = document.getElementById('cpInput').value.trim();
|
||||||
|
if(!manualCP) { showToast("Escribe el CP manual", true); return; }
|
||||||
|
tempUserZones.push({ province: prov, city: munName === "OTRO" ? "MANUAL" : munName, cps: manualCP });
|
||||||
|
} else {
|
||||||
|
const cpList = JSON.parse(selMun.options[selMun.selectedIndex].dataset.cps);
|
||||||
|
cpList.forEach(cp => {
|
||||||
|
if (!tempUserZones.some(z => z.city === munName && z.cps === cp)) {
|
||||||
|
tempUserZones.push({ province: prov, city: munName, cps: cp });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
showToast(`Añadidos códigos de ${munName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
tempUserZones.push({ province: prov, city: mun, cps: cp });
|
|
||||||
renderTempZones();
|
renderTempZones();
|
||||||
document.getElementById('cpInput').value = "";
|
document.getElementById('cpInput').value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeTempZone(idx) {
|
function removeTempCity(cityName) {
|
||||||
tempUserZones.splice(idx, 1);
|
tempUserZones = tempUserZones.filter(z => z.city !== cityName);
|
||||||
renderTempZones();
|
renderTempZones();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderTempZones() {
|
function renderTempZones() {
|
||||||
const area = document.getElementById('userZonesArea');
|
const area = document.getElementById('userZonesArea');
|
||||||
area.innerHTML = tempUserZones.length === 0 ? '<p class="text-[10px] text-gray-300 italic p-1">Sin zonas añadidas...</p>' : "";
|
area.innerHTML = tempUserZones.length === 0 ? '<p class="text-[10px] text-gray-300 italic p-1">Sin zonas añadidas...</p>' : "";
|
||||||
tempUserZones.forEach((z, i) => {
|
|
||||||
|
const visualGroup = tempUserZones.reduce((acc, curr) => {
|
||||||
|
if(!acc[curr.city]) acc[curr.city] = [];
|
||||||
|
acc[curr.city].push(curr.cps);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
Object.keys(visualGroup).forEach(city => {
|
||||||
area.innerHTML += `
|
area.innerHTML += `
|
||||||
<span class="bg-blue-100 text-blue-700 px-3 py-1.5 rounded-lg text-[10px] font-black border border-blue-200 flex items-center gap-2">
|
<span class="bg-blue-100 text-blue-700 px-3 py-1.5 rounded-lg text-[10px] font-black border border-blue-200 flex items-center gap-2">
|
||||||
${z.city} (${z.cps})
|
${city} (${visualGroup[city].length} CPs)
|
||||||
<button type="button" onclick="removeTempZone(${i})" class="text-blue-400 hover:text-red-500"><i data-lucide="x" class="w-3 h-3"></i></button>
|
<button type="button" onclick="removeTempCity('${city}')" class="text-blue-400 hover:text-red-500"><i data-lucide="x" class="w-3 h-3"></i></button>
|
||||||
</span>`;
|
</span>`;
|
||||||
});
|
});
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
@@ -349,11 +284,7 @@ function removeTempCity(cityName) {
|
|||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_URL}/guilds`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
|
const res = await fetch(`${API_URL}/guilds`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.ok) {
|
if (data.ok) { availableGuilds = data.guilds; renderGuildsList(); renderGuildsCheckboxes(); }
|
||||||
availableGuilds = data.guilds;
|
|
||||||
renderGuildsList();
|
|
||||||
renderGuildsCheckboxes();
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,10 +309,7 @@ function removeTempCity(cityName) {
|
|||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_URL}/admin/users`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
|
const res = await fetch(`${API_URL}/admin/users`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.ok) {
|
if (data.ok) { currentUsers = data.users; renderUsersTable(); }
|
||||||
currentUsers = data.users;
|
|
||||||
renderUsersTable();
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,11 +339,8 @@ function removeTempCity(cityName) {
|
|||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data)
|
||||||
});
|
});
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
if (json.ok) {
|
if (json.ok) { showToast(isEdit ? "Actualizado" : "Creado"); resetUserForm(); fetchUsers(); }
|
||||||
showToast(isEdit ? "Datos actualizados" : "Usuario creado");
|
else { showToast("Error: " + json.error, true); }
|
||||||
resetUserForm();
|
|
||||||
fetchUsers();
|
|
||||||
} else { showToast("❌ " + (json.error || "Error"), true); }
|
|
||||||
} catch (e) { showToast("Error conexión", true); }
|
} catch (e) { showToast("Error conexión", true); }
|
||||||
finally { btn.disabled = false; btn.innerText = isEdit ? "Actualizar Datos" : "Guardar Usuario y Zonas"; }
|
finally { btn.disabled = false; btn.innerText = isEdit ? "Actualizar Datos" : "Guardar Usuario y Zonas"; }
|
||||||
}
|
}
|
||||||
@@ -429,26 +354,20 @@ function removeTempCity(cityName) {
|
|||||||
document.getElementById('uPhone').value = user.phone;
|
document.getElementById('uPhone').value = user.phone;
|
||||||
document.getElementById('uRole').value = user.role;
|
document.getElementById('uRole').value = user.role;
|
||||||
document.getElementById('uPass').value = "";
|
document.getElementById('uPass').value = "";
|
||||||
|
|
||||||
tempUserZones = user.zones || [];
|
tempUserZones = user.zones || [];
|
||||||
renderTempZones();
|
renderTempZones();
|
||||||
|
|
||||||
const userGuilds = user.guilds || [];
|
const userGuilds = user.guilds || [];
|
||||||
document.querySelectorAll('.guild-checkbox').forEach(cb => {
|
document.querySelectorAll('.guild-checkbox').forEach(cb => { cb.checked = userGuilds.includes(parseInt(cb.value)); });
|
||||||
cb.checked = userGuilds.includes(parseInt(cb.value));
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('formTitle').innerText = "2. Editando Usuario";
|
document.getElementById('formTitle').innerText = "2. Editando Usuario";
|
||||||
document.getElementById('btnSubmitUser').classList.replace('bg-green-600', 'bg-blue-600');
|
|
||||||
document.getElementById('btnCancelEdit').classList.remove('hidden');
|
document.getElementById('btnCancelEdit').classList.remove('hidden');
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteUser(id) {
|
async function deleteUser(id) {
|
||||||
if(!confirm("¿Seguro que quieres borrar a este usuario?")) return;
|
if(!confirm("¿Borrar usuario?")) return;
|
||||||
try {
|
try {
|
||||||
await fetch(`${API_URL}/admin/users/${id}`, { method: 'DELETE', headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
|
await fetch(`${API_URL}/admin/users/${id}`, { method: 'DELETE', headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
|
||||||
fetchUsers(); showToast("Usuario borrado");
|
fetchUsers(); showToast("Borrado");
|
||||||
} catch(e) { showToast("Error", true); }
|
} catch(e) { showToast("Error", true); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,7 +380,6 @@ function removeTempCity(cityName) {
|
|||||||
tempUserZones = [];
|
tempUserZones = [];
|
||||||
renderTempZones();
|
renderTempZones();
|
||||||
document.getElementById('formTitle').innerText = "2. Nuevo Usuario";
|
document.getElementById('formTitle').innerText = "2. Nuevo Usuario";
|
||||||
document.getElementById('btnSubmitUser').classList.replace('bg-blue-600', 'bg-green-600');
|
|
||||||
document.getElementById('btnCancelEdit').classList.add('hidden');
|
document.getElementById('btnCancelEdit').classList.add('hidden');
|
||||||
document.querySelectorAll('.guild-checkbox').forEach(cb => cb.checked = false);
|
document.querySelectorAll('.guild-checkbox').forEach(cb => cb.checked = false);
|
||||||
}
|
}
|
||||||
@@ -470,64 +388,47 @@ function removeTempCity(cityName) {
|
|||||||
const list = document.getElementById('guildsList');
|
const list = document.getElementById('guildsList');
|
||||||
list.innerHTML = "";
|
list.innerHTML = "";
|
||||||
availableGuilds.forEach(g => {
|
availableGuilds.forEach(g => {
|
||||||
list.innerHTML += `<div class="flex justify-between items-center bg-white p-2 rounded border shadow-sm"><span class="text-xs font-black uppercase text-slate-700">${g.name}</span><button type="button" onclick="deleteGuild(${g.id})" class="text-gray-400 hover:text-red-500 transition-colors text-left"><i data-lucide="trash-2" class="w-3.5 h-3.5 text-left"></i></button></div>`;
|
list.innerHTML += `<div class="flex justify-between items-center bg-white p-2 rounded border shadow-sm"><span class="text-xs font-black uppercase text-slate-700">${g.name}</span><button onclick="deleteGuild(${g.id})" class="text-gray-400 hover:text-red-500 transition-colors"><i data-lucide="trash-2" class="w-3.5 h-3.5"></i></button></div>`;
|
||||||
});
|
});
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderGuildsCheckboxes() {
|
function renderGuildsCheckboxes() {
|
||||||
const area = document.getElementById('guildsCheckboxArea');
|
const area = document.getElementById('guildsCheckboxArea');
|
||||||
area.innerHTML = availableGuilds.length === 0 ? '<p class="text-xs text-gray-300 italic">No hay gremios registrados...</p>' : "";
|
area.innerHTML = availableGuilds.length === 0 ? '<p class="text-xs text-gray-300 italic">No hay gremios...</p>' : "";
|
||||||
availableGuilds.forEach(g => {
|
availableGuilds.forEach(g => {
|
||||||
area.innerHTML += `<label class="flex items-center space-x-2 cursor-pointer p-2 hover:bg-white rounded border border-transparent hover:border-green-200 transition-all text-left"><input type="checkbox" value="${g.id}" class="guild-checkbox h-4 w-4 text-green-600 border-gray-300 rounded text-left"><span class="text-[10px] font-black uppercase text-slate-500 text-left">${g.name}</span></label>`;
|
area.innerHTML += `<label class="flex items-center space-x-2 cursor-pointer p-2 hover:bg-white rounded border border-transparent hover:border-green-200 transition-all"><input type="checkbox" value="${g.id}" class="guild-checkbox h-4 w-4 text-green-600 border-gray-300 rounded"><span class="text-[10px] font-black uppercase text-slate-500">${g.name}</span></label>`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderUsersTable() {
|
function renderUsersTable() {
|
||||||
const tbody = document.getElementById('usersListBody');
|
const tbody = document.getElementById('usersListBody');
|
||||||
tbody.innerHTML = currentUsers.length === 0 ? `<tr><td colspan="5" class="p-8 text-center text-gray-300 font-bold uppercase tracking-widest text-left">Cargando equipo...</td></tr>` : "";
|
tbody.innerHTML = currentUsers.length === 0 ? `<tr><td colspan="5" class="p-8 text-center text-gray-300 font-bold uppercase tracking-widest text-left">Cargando equipo...</td></tr>` : "";
|
||||||
|
currentUsers.forEach(u => {
|
||||||
currentUsers.forEach(u => {
|
const uGuildNames = (u.guilds || []).map(gid => availableGuilds.find(ag => ag.id === gid)?.name).filter(Boolean).join(", ");
|
||||||
const uGuildNames = (u.guilds || []).map(gid => availableGuilds.find(ag => ag.id === gid)?.name).filter(Boolean).join(", ");
|
const groupedZones = (u.zones || []).reduce((acc, curr) => {
|
||||||
|
if (!acc[curr.city]) acc[curr.city] = [];
|
||||||
// --- LÓGICA DE AGRUPACIÓN POR MUNICIPIO ---
|
if (!acc[curr.city].includes(curr.cps)) acc[curr.city].push(curr.cps);
|
||||||
const groupedZones = (u.zones || []).reduce((acc, current) => {
|
return acc;
|
||||||
if (!acc[current.city]) {
|
}, {});
|
||||||
acc[current.city] = [];
|
|
||||||
}
|
|
||||||
// Evitamos duplicados de CP en la misma ciudad
|
|
||||||
if (!acc[current.city].includes(current.cps)) {
|
|
||||||
acc[current.city].push(current.cps);
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
// Convertimos el objeto agrupado en HTML elegante
|
const uZonesHtml = Object.keys(groupedZones).map(city => {
|
||||||
const uZonesHtml = Object.keys(groupedZones).map(city => {
|
return `<div class="mb-1 last:mb-0"><span class="font-black text-blue-600 text-[10px] uppercase">📍 ${city}</span> <span class="text-gray-500 text-[9px]">(${groupedZones[city].join(", ")})</span></div>`;
|
||||||
const cps = groupedZones[city].join(", ");
|
}).join("") || '<span class="text-[9px] text-gray-300 italic">Sin zona</span>';
|
||||||
return `<div class="mb-1 last:mb-0">
|
|
||||||
<span class="font-black text-blue-600 text-[10px] uppercase">📍 ${city}</span>
|
|
||||||
<span class="text-gray-500 text-[9px] font-medium">(${cps})</span>
|
|
||||||
</div>`;
|
|
||||||
}).join("") || '<span class="text-[9px] text-gray-300 italic text-left">Sin zona</span>';
|
|
||||||
|
|
||||||
tbody.innerHTML += `
|
tbody.innerHTML += `
|
||||||
<tr class="bg-white hover:bg-gray-50 transition border-b text-left">
|
<tr class="bg-white hover:bg-gray-50 transition border-b">
|
||||||
<td class="p-4"><div class="flex flex-col text-left"><span class="font-black text-slate-800 uppercase text-left">${u.full_name}</span><span class="text-[9px] text-gray-400 font-bold tracking-tighter text-left">${u.email}</span></div></td>
|
<td class="p-4"><div class="flex flex-col"><span class="font-black text-slate-800 uppercase">${u.full_name}</span><span class="text-[9px] text-gray-400 font-bold">${u.email}</span></div></td>
|
||||||
<td class="p-4 font-black text-green-600 text-left">${u.phone}</td>
|
<td class="p-4 font-black text-green-600">${u.phone}</td>
|
||||||
<td class="p-4 text-left"><span class="bg-slate-100 text-slate-600 px-2 py-0.5 rounded text-[10px] font-black uppercase text-left">${u.role}</span><p class="text-[9px] text-gray-400 mt-1 uppercase font-bold text-left">${uGuildNames || '-'}</p></td>
|
<td class="p-4"><span class="bg-slate-100 text-slate-600 px-2 py-0.5 rounded text-[10px] font-black uppercase">${u.role}</span><p class="text-[9px] text-gray-400 mt-1 uppercase font-bold">${uGuildNames || '-'}</p></td>
|
||||||
<td class="p-4 text-left">
|
<td class="p-4 text-left"><div class="max-h-24 overflow-y-auto no-scrollbar">${uZonesHtml}</div></td>
|
||||||
<div class="max-h-24 overflow-y-auto no-scrollbar">
|
<td class="p-4 text-right space-x-3">
|
||||||
${uZonesHtml}
|
<button onclick="editUser(${u.id})" class="text-blue-500 hover:text-blue-700 font-black text-xs uppercase">Editar</button>
|
||||||
</div>
|
<button onclick="deleteUser(${u.id})" class="text-red-400 hover:text-red-700 font-black text-xs">Baja</button>
|
||||||
</td>
|
</td>
|
||||||
<td class="p-4 text-right space-x-3 text-left">
|
</tr>`;
|
||||||
<button onclick="editUser(${u.id})" class="text-blue-500 hover:text-blue-700 font-black text-xs uppercase tracking-widest text-left">Editar</button>
|
});
|
||||||
<button onclick="deleteUser(${u.id})" class="text-red-400 hover:text-red-700 font-black text-xs uppercase tracking-widest text-left">Baja</button>
|
}
|
||||||
</td>
|
|
||||||
</tr>`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function showToast(msg, err=false){
|
function showToast(msg, err=false){
|
||||||
const t=document.getElementById('toast'), m=document.getElementById('toastMsg');
|
const t=document.getElementById('toast'), m=document.getElementById('toastMsg');
|
||||||
|
|||||||
Reference in New Issue
Block a user