Actualizar menu.html

This commit is contained in:
2026-02-24 22:32:15 +00:00
parent 725542dc34
commit 5c0d2c7ed4

121
menu.html
View File

@@ -83,6 +83,14 @@
<h3 class="font-black text-slate-800 text-sm leading-tight z-10">Buscar<br>Servicio</h3> <h3 class="font-black text-slate-800 text-sm leading-tight z-10">Buscar<br>Servicio</h3>
</a> </a>
<a href="ranking.html" class="app-card bg-white p-6 rounded-[2rem] border border-slate-100 flex flex-col items-center text-center active:scale-95 transition-transform relative overflow-hidden col-span-2">
<div class="absolute top-0 right-0 w-16 h-16 bg-indigo-50 rounded-bl-full z-0"></div>
<div class="w-14 h-14 bg-indigo-100 text-indigo-600 rounded-2xl flex items-center justify-center mb-4 z-10 shadow-inner">
<i data-lucide="award" class="w-7 h-7"></i>
</div>
<h3 class="font-black text-slate-800 text-sm leading-tight z-10">Ranking<br>y Estadísticas</h3>
</a>
</div> </div>
</main> </main>
@@ -93,6 +101,7 @@
let userZones = []; let userZones = [];
let userGuilds = []; let userGuilds = [];
let systemStatuses = [];
// --- SISTEMA DE TEMA DINÁMICO --- // --- SISTEMA DE TEMA DINÁMICO ---
async function applyTheme() { async function applyTheme() {
@@ -118,12 +127,11 @@
const token = localStorage.getItem("token"); const token = localStorage.getItem("token");
const role = localStorage.getItem("role"); const role = localStorage.getItem("role");
if (!token || role !== 'operario') { if (!token || (role !== 'operario' && role !== 'operario_cerrado')) {
window.location.href = "index.html"; window.location.href = "index.html";
return; return;
} }
// Aplicamos los colores corporativos de la empresa
await applyTheme(); await applyTheme();
lucide.createIcons(); lucide.createIcons();
@@ -133,19 +141,27 @@
const options = { weekday: 'long', day: 'numeric', month: 'long' }; const options = { weekday: 'long', day: 'numeric', month: 'long' };
document.getElementById('headerDate').innerText = new Date().toLocaleDateString('es-ES', options); document.getElementById('headerDate').innerText = new Date().toLocaleDateString('es-ES', options);
await fetchUserData(); // Para saber los gremios y zonas del operario await loadStatuses();
await fetchUserData();
fetchBadges(); fetchBadges();
}); });
// 1. Obtener zonas y gremios del operario async function loadStatuses() {
try {
const res = await fetch(`${API_URL}/statuses`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json();
if (data.ok) systemStatuses = data.statuses;
} catch (e) { console.error("Error cargando estados:", e); }
}
async function fetchUserData() { async function fetchUserData() {
try { try {
const res = await fetch(`${API_URL}/auth/me`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } }); const res = await fetch(`${API_URL}/auth/me`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json(); const data = await res.json();
if (data.ok && data.user) { if (data.ok && data.user) {
// zones suele venir como array de objetos: [{"cps": "11204"}]
userZones = Array.isArray(data.user.zones) ? data.user.zones.map(z => z.cps) : []; userZones = Array.isArray(data.user.zones) ? data.user.zones.map(z => z.cps) : [];
// Necesitamos cargar los gremios de otra ruta porque auth/me no los trae por defecto
// Necesitamos cargar los gremios
const resG = await fetch(`${API_URL}/admin/users`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } }); const resG = await fetch(`${API_URL}/admin/users`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const dataG = await resG.json(); const dataG = await resG.json();
if(dataG.ok) { if(dataG.ok) {
@@ -158,18 +174,37 @@
async function fetchBadges() { async function fetchBadges() {
try { try {
// 1. Pedimos los servicios ASIGNADOS AL OPERARIO (para el badge naranja) const headers = { "Authorization": `Bearer ${localStorage.getItem("token")}` };
const resActive = await fetch(`${API_URL}/services/active`, {
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
});
const dataActive = await resActive.json();
if (dataActive.ok) { // 1. Cargar servicios activos del operario y peticiones
const myServices = dataActive.services.filter(s => s.provider !== 'SYSTEM_BLOCK'); const resActive = await fetch(`${API_URL}/services/active`, { headers });
const dataActive = await resActive.json();
const resReqs = await fetch(`${API_URL}/agenda/requests`, { headers });
const dataReqs = await resReqs.json();
if (dataActive.ok && dataReqs.ok) {
let sinCitaCount = 0; let sinCitaCount = 0;
myServices.forEach(s => {
// Sumamos las peticiones pendientes
sinCitaCount += dataReqs.requests.length;
// Filtramos y contamos la lista normal
dataActive.services.forEach(s => {
if (s.provider === 'SYSTEM_BLOCK') return;
const raw = s.raw_data || {}; const raw = s.raw_data || {};
if (!raw.scheduled_date) sinCitaCount++; if (raw.appointment_status === 'pending') return; // Ya las contamos arriba
// Si no tiene fecha, lo evaluamos
if (!raw.scheduled_date || raw.scheduled_date.trim() === "") {
// Validar que el estado no sea finalizado/anulado (ej: "Terminado sin fecha")
if (raw.status_operativo) {
const st = systemStatuses.find(x => String(x.id) === String(raw.status_operativo));
if (st && st.is_final) return;
}
sinCitaCount++;
}
}); });
if (sinCitaCount > 0) { if (sinCitaCount > 0) {
@@ -179,42 +214,36 @@
} }
} }
// 2. Pedimos la "Bolsa de Trabajo" (servicios sin asignar que cuadran con CP y Gremio) // 2. Pedimos la "Bolsa de Trabajo" SOLO SI TIENE PERMISO (operario_cerrado NO cuenta bolsa)
// Usamos la ruta genérica de scraped pero filtramos en frontend para que sea más rápido. if (localStorage.getItem("role") === 'operario') {
const resScraped = await fetch(`${API_URL}/providers/scraped`, { const resScraped = await fetch(`${API_URL}/providers/scraped`, { headers });
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } const dataScraped = await resScraped.json();
});
const dataScraped = await resScraped.json();
if (dataScraped.ok) { if (dataScraped.ok) {
let bolsaCount = 0; let bolsaCount = 0;
dataScraped.services.forEach(s => { dataScraped.services.forEach(s => {
// Descartamos si ya tiene asignado o si es un bloqueo if(s.assigned_to || s.provider === 'SYSTEM_BLOCK') return;
if(s.assigned_to || s.provider === 'SYSTEM_BLOCK') return;
const raw = s.raw_data || {};
const raw = s.raw_data || {}; const dbStatus = raw.status_operativo;
const dbStatus = raw.status_operativo; if(s.status === 'archived' || (dbStatus && ['anulado', 'terminado', 'finalizado'].includes(dbStatus.toLowerCase()))) return;
// Si está anulado/finalizado, descartar
if(s.status === 'archived' || (dbStatus && ['anulado', 'terminado', 'finalizado'].includes(dbStatus.toLowerCase()))) return;
// Check de Gremio y Zona const sGuild = String(s.guild_id || raw.guild_id);
const sGuild = String(s.guild_id || raw.guild_id); const sCp = String(raw['Código Postal'] || raw['CP'] || "").trim();
const sCp = String(raw['Código Postal'] || raw['CP'] || "").trim();
const matchGuild = userGuilds.includes(Number(sGuild)) || userGuilds.includes(String(sGuild)); const matchGuild = userGuilds.includes(Number(sGuild)) || userGuilds.includes(String(sGuild));
const matchZone = userZones.some(z => sCp.startsWith(z)); // Coincidencia parcial (ej: zona "11" coge "11204") const matchZone = userZones.some(z => sCp.startsWith(z));
// Si cuadra (o si el operario no tiene filtros estrictos configurados, se lo enseñamos) if((userGuilds.length === 0 || matchGuild) && (userZones.length === 0 || matchZone)) {
if((userGuilds.length === 0 || matchGuild) && (userZones.length === 0 || matchZone)) { bolsaCount++;
bolsaCount++; }
});
if (bolsaCount > 0) {
const b2 = document.getElementById('badgeBolsa');
b2.innerText = `${bolsaCount} libre${bolsaCount > 1 ? 's' : ''}`;
b2.classList.remove('hidden');
} }
});
if (bolsaCount > 0) {
const b2 = document.getElementById('badgeBolsa');
b2.innerText = `${bolsaCount} libre${bolsaCount > 1 ? 's' : ''}`;
b2.classList.remove('hidden');
} }
} }