285 lines
16 KiB
HTML
285 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, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
|
<title>Ranking - IntegraRepara</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://unpkg.com/lucide@latest"></script>
|
|
<style>
|
|
/* VARIABLES CORPORATIVAS DINÁMICAS */
|
|
:root {
|
|
--primary: #2563eb;
|
|
--secondary: #f59e0b;
|
|
--app-bg: #f4f7f9;
|
|
}
|
|
|
|
body { background-color: var(--app-bg); -webkit-tap-highlight-color: transparent; }
|
|
.fade-in { animation: fadeIn 0.4s ease-out forwards; }
|
|
@keyframes fadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
|
|
|
|
.no-scrollbar::-webkit-scrollbar { display: none; }
|
|
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
|
|
.pb-safe { padding-bottom: calc(env(safe-area-inset-bottom) + 80px); }
|
|
|
|
.bg-primary-dynamic { background-color: var(--primary) !important; }
|
|
.text-primary-dynamic { color: var(--primary) !important; }
|
|
.border-primary-dynamic { border-color: var(--primary) !important; }
|
|
|
|
/* Círculo de puntuación animado */
|
|
.score-circle {
|
|
background: conic-gradient(var(--primary) var(--score, 0%), #e2e8f0 0);
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
position: relative;
|
|
transition: --score 1s ease-out;
|
|
}
|
|
.score-inner {
|
|
background-color: white;
|
|
border-radius: 50%;
|
|
width: 85%;
|
|
height: 85%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-shadow: inset 0 2px 10px rgba(0,0,0,0.05);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="text-slate-800 font-sans antialiased h-screen flex flex-col overflow-hidden relative">
|
|
|
|
<header class="bg-white px-5 pt-8 pb-4 shadow-sm z-20 shrink-0 border-b border-slate-100 flex justify-between items-center relative">
|
|
<div>
|
|
<p class="text-[10px] font-black text-primary-dynamic uppercase tracking-widest mb-0.5">Rendimiento Global</p>
|
|
<h1 class="text-2xl font-black tracking-tight text-slate-800 leading-none">Mi Ranking</h1>
|
|
</div>
|
|
<a href="menu.html" class="w-10 h-10 bg-slate-50 rounded-full flex items-center justify-center text-slate-600 border border-slate-200 active:bg-slate-100 transition-colors">
|
|
<i data-lucide="x" class="w-5 h-5"></i>
|
|
</a>
|
|
</header>
|
|
|
|
<main class="flex-1 overflow-y-auto no-scrollbar px-5 pt-6 pb-safe relative z-10" id="mainArea">
|
|
|
|
<div id="loader" class="text-center py-20 opacity-50">
|
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto text-primary-dynamic mb-3"></i>
|
|
<p class="text-xs font-bold uppercase tracking-widest text-slate-400">Calculando estadísticas...</p>
|
|
</div>
|
|
|
|
<div id="rankingContent" class="hidden fade-in space-y-6">
|
|
|
|
<div class="bg-white rounded-[2.5rem] p-6 shadow-xl shadow-slate-200/40 border border-white text-center relative overflow-hidden">
|
|
<div class="absolute -top-10 -right-10 w-32 h-32 bg-primary-dynamic/5 rounded-full blur-2xl"></div>
|
|
<div class="absolute -bottom-10 -left-10 w-32 h-32 bg-secondary/5 rounded-full blur-2xl"></div>
|
|
|
|
<h2 class="text-sm font-black text-slate-500 uppercase tracking-widest mb-6">Puntuación de Calidad</h2>
|
|
|
|
<div class="w-48 h-48 mx-auto score-circle shadow-lg mb-6" id="scoreCircle" style="--score: 0%;">
|
|
<div class="score-inner">
|
|
<span class="text-5xl font-black text-slate-800 tracking-tighter leading-none" id="mainScore">0</span>
|
|
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest mt-1">sobre 100</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="tierBadge" class="inline-flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-black uppercase tracking-widest">
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<h3 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-3 ml-2">Desglose de Puntos</h3>
|
|
|
|
<div class="space-y-3">
|
|
|
|
<div class="bg-white p-4 rounded-2xl border border-slate-100 shadow-sm flex flex-col gap-2">
|
|
<div class="flex justify-between items-center">
|
|
<div class="flex items-center gap-2">
|
|
<div class="w-8 h-8 bg-blue-50 text-blue-500 rounded-lg flex items-center justify-center"><i data-lucide="check-circle-2" class="w-4 h-4"></i></div>
|
|
<h4 class="font-black text-sm text-slate-700">Cierre Rápido</h4>
|
|
</div>
|
|
<span class="font-black text-lg text-slate-800"><span id="ptCierre">0</span><span class="text-xs text-slate-400">/30</span></span>
|
|
</div>
|
|
<div class="w-full bg-slate-100 rounded-full h-1.5 mt-1 overflow-hidden">
|
|
<div class="bg-blue-500 h-1.5 rounded-full transition-all duration-1000" id="barCierre" style="width: 0%"></div>
|
|
</div>
|
|
<p class="text-[9px] font-bold text-slate-400 uppercase">Tiempo medio en terminar un aviso</p>
|
|
</div>
|
|
|
|
<div class="bg-white p-4 rounded-2xl border border-slate-100 shadow-sm flex flex-col gap-2">
|
|
<div class="flex justify-between items-center">
|
|
<div class="flex items-center gap-2">
|
|
<div class="w-8 h-8 bg-emerald-50 text-emerald-500 rounded-lg flex items-center justify-center"><i data-lucide="zap" class="w-4 h-4"></i></div>
|
|
<h4 class="font-black text-sm text-slate-700">Agilidad al Citar</h4>
|
|
</div>
|
|
<span class="font-black text-lg text-slate-800"><span id="ptCita">0</span><span class="text-xs text-slate-400">/30</span></span>
|
|
</div>
|
|
<div class="w-full bg-slate-100 rounded-full h-1.5 mt-1 overflow-hidden">
|
|
<div class="bg-emerald-500 h-1.5 rounded-full transition-all duration-1000" id="barCita" style="width: 0%"></div>
|
|
</div>
|
|
<p class="text-[9px] font-bold text-slate-400 uppercase">Avisos citados en menos de 24h</p>
|
|
</div>
|
|
|
|
<div class="bg-white p-4 rounded-2xl border border-slate-100 shadow-sm flex flex-col gap-2">
|
|
<div class="flex justify-between items-center">
|
|
<div class="flex items-center gap-2">
|
|
<div class="w-8 h-8 bg-purple-50 text-purple-500 rounded-lg flex items-center justify-center"><i data-lucide="layers" class="w-4 h-4"></i></div>
|
|
<h4 class="font-black text-sm text-slate-700">Volumen Diario</h4>
|
|
</div>
|
|
<span class="font-black text-lg text-slate-800"><span id="ptVolumen">0</span><span class="text-xs text-slate-400">/20</span></span>
|
|
</div>
|
|
<div class="w-full bg-slate-100 rounded-full h-1.5 mt-1 overflow-hidden">
|
|
<div class="bg-purple-500 h-1.5 rounded-full transition-all duration-1000" id="barVolumen" style="width: 0%"></div>
|
|
</div>
|
|
<p class="text-[9px] font-bold text-slate-400 uppercase">Cantidad de avisos cerrados al día</p>
|
|
</div>
|
|
|
|
<div class="bg-white p-4 rounded-2xl border border-slate-100 shadow-sm flex flex-col gap-2">
|
|
<div class="flex justify-between items-center">
|
|
<div class="flex items-center gap-2">
|
|
<div class="w-8 h-8 bg-amber-50 text-amber-500 rounded-lg flex items-center justify-center"><i data-lucide="target" class="w-4 h-4"></i></div>
|
|
<h4 class="font-black text-sm text-slate-700">Eficacia Citas</h4>
|
|
</div>
|
|
<span class="font-black text-lg text-slate-800"><span id="ptRecitas">0</span><span class="text-xs text-slate-400">/20</span></span>
|
|
</div>
|
|
<div class="w-full bg-slate-100 rounded-full h-1.5 mt-1 overflow-hidden">
|
|
<div class="bg-amber-500 h-1.5 rounded-full transition-all duration-1000" id="barRecitas" style="width: 0%"></div>
|
|
</div>
|
|
<p class="text-[9px] font-bold text-slate-400 uppercase">Evitar marear/re-citar al asegurado</p>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div class="bg-rose-50 border border-rose-100 p-4 rounded-2xl flex flex-col items-center justify-center text-center">
|
|
<p class="text-[9px] font-black text-rose-500 uppercase tracking-widest mb-1">Penalización</p>
|
|
<p class="text-2xl font-black text-rose-600 leading-none mb-1">-<span id="ptPenalizacion">0</span> pts</p>
|
|
<p class="text-[9px] font-bold text-rose-400 uppercase">Por avisos atascados</p>
|
|
</div>
|
|
<div class="bg-slate-50 border border-slate-200 p-4 rounded-2xl flex flex-col items-center justify-center text-center">
|
|
<p class="text-[9px] font-black text-slate-500 uppercase tracking-widest mb-1">Mes Actual</p>
|
|
<p class="text-2xl font-black text-slate-700 leading-none mb-1"><span id="ptCerrados">0</span></p>
|
|
<p class="text-[9px] font-bold text-slate-400 uppercase">Siniestros Cerrados</p>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="text-center text-[9px] font-bold text-slate-400 mt-4 px-4 uppercase tracking-widest leading-relaxed">
|
|
El ranking se calcula en base a tu rendimiento de los últimos 30 días.
|
|
</p>
|
|
|
|
</div>
|
|
</main>
|
|
|
|
<script>
|
|
const API_URL = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
|
|
? 'http://localhost:3000'
|
|
: 'https://integrarepara-api.integrarepara.es';
|
|
|
|
async function applyTheme() {
|
|
try {
|
|
let theme = JSON.parse(localStorage.getItem('app_theme'));
|
|
const res = await fetch(`${API_URL}/config/company`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
|
|
const data = await res.json();
|
|
if(data.ok && data.config && data.config.portal_settings && data.config.portal_settings.app_settings) {
|
|
theme = data.config.portal_settings.app_settings;
|
|
localStorage.setItem('app_theme', JSON.stringify(theme));
|
|
}
|
|
if(theme) {
|
|
document.documentElement.style.setProperty('--primary', theme.primary);
|
|
document.documentElement.style.setProperty('--secondary', theme.secondary);
|
|
document.documentElement.style.setProperty('--app-bg', theme.bg);
|
|
}
|
|
} catch (e) { console.warn("Tema por defecto"); }
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", async () => {
|
|
if (!localStorage.getItem("token")) {
|
|
window.location.href = "index.html"; return;
|
|
}
|
|
await applyTheme();
|
|
lucide.createIcons();
|
|
fetchRanking();
|
|
});
|
|
|
|
async function fetchRanking() {
|
|
try {
|
|
const res = await fetch(`${API_URL}/ranking`, {
|
|
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
|
|
});
|
|
const data = await res.json();
|
|
|
|
if (data.ok && data.ranking) {
|
|
renderDashboard(data.ranking);
|
|
} else {
|
|
alert("No se pudo cargar el ranking.");
|
|
}
|
|
} catch (e) {
|
|
alert("Error de conexión");
|
|
} finally {
|
|
document.getElementById('loader').classList.add('hidden');
|
|
document.getElementById('rankingContent').classList.remove('hidden');
|
|
}
|
|
}
|
|
|
|
function renderDashboard(ranking) {
|
|
// Animación del número principal
|
|
animateValue("mainScore", 0, ranking.score, 1000);
|
|
|
|
// Animación del círculo CSS
|
|
setTimeout(() => {
|
|
document.getElementById('scoreCircle').style.setProperty('--score', `${ranking.score}%`);
|
|
}, 100);
|
|
|
|
// Tier Badge (Nivel)
|
|
const badge = document.getElementById('tierBadge');
|
|
if (ranking.score >= 90) {
|
|
badge.className = "inline-flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-black uppercase tracking-widest bg-yellow-100 text-yellow-600 border border-yellow-200";
|
|
badge.innerHTML = `<i data-lucide="crown" class="w-5 h-5"></i> Leyenda`;
|
|
} else if (ranking.score >= 70) {
|
|
badge.className = "inline-flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-black uppercase tracking-widest bg-emerald-100 text-emerald-600 border border-emerald-200";
|
|
badge.innerHTML = `<i data-lucide="award" class="w-5 h-5"></i> Profesional`;
|
|
} else if (ranking.score >= 50) {
|
|
badge.className = "inline-flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-black uppercase tracking-widest bg-blue-100 text-blue-600 border border-blue-200";
|
|
badge.innerHTML = `<i data-lucide="thumbs-up" class="w-5 h-5"></i> Buen Trabajo`;
|
|
} else {
|
|
badge.className = "inline-flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-black uppercase tracking-widest bg-rose-100 text-rose-600 border border-rose-200";
|
|
badge.innerHTML = `<i data-lucide="trending-up" class="w-5 h-5"></i> Toca Mejorar`;
|
|
}
|
|
|
|
// Textos de las métricas
|
|
document.getElementById('ptCierre').innerText = ranking.details.cierre;
|
|
document.getElementById('ptCita').innerText = ranking.details.cita;
|
|
document.getElementById('ptVolumen').innerText = ranking.details.volumen;
|
|
document.getElementById('ptRecitas').innerText = ranking.details.recitas;
|
|
document.getElementById('ptPenalizacion').innerText = ranking.details.penalizacion;
|
|
document.getElementById('ptCerrados').innerText = ranking.details.cerrados_mes;
|
|
|
|
// Animación de las barras
|
|
setTimeout(() => {
|
|
document.getElementById('barCierre').style.width = `${(ranking.details.cierre / 30) * 100}%`;
|
|
document.getElementById('barCita').style.width = `${(ranking.details.cita / 30) * 100}%`;
|
|
document.getElementById('barVolumen').style.width = `${(ranking.details.volumen / 20) * 100}%`;
|
|
document.getElementById('barRecitas').style.width = `${(ranking.details.recitas / 20) * 100}%`;
|
|
}, 300);
|
|
|
|
lucide.createIcons();
|
|
}
|
|
|
|
// Helper para animar números progresivamente
|
|
function animateValue(id, start, end, duration) {
|
|
if (start === end) { document.getElementById(id).innerHTML = end; return; }
|
|
let range = end - start;
|
|
let current = start;
|
|
let increment = end > start ? 1 : -1;
|
|
let stepTime = Math.abs(Math.floor(duration / range));
|
|
let obj = document.getElementById(id);
|
|
let timer = setInterval(function() {
|
|
current += increment;
|
|
obj.innerHTML = current;
|
|
if (current == end) clearInterval(timer);
|
|
}, stepTime);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |