fix: remove /login.html references and load real data in dashboard charts

- Replace all /login.html redirects with /login in admin.html
- Remove /login.html route from server
- Update dashboard charts to load real data from API
- Add initCharts() and loadDashboardData() functions
- Remove static chart data and use dynamic API data
- Update stats counters to animate with real values
This commit is contained in:
TenerifeProp Dev
2026-04-06 13:48:33 +01:00
parent 7d2b9f91fa
commit 7904178052
5 changed files with 255 additions and 254 deletions

Binary file not shown.

View File

@@ -3200,7 +3200,7 @@ padding: 15
if (!data.success || !data.data) {
// Not authenticated, redirect to login
window.location.href = '/login.html';
window.location.href = '/login';
return;
}
@@ -3231,7 +3231,7 @@ padding: 15
}
} catch (error) {
console.error('Auth check failed:', error);
window.location.href = '/login.html';
window.location.href = '/login';
}
})();
@@ -3240,10 +3240,10 @@ padding: 15
try {
await fetch('/api/auth/logout', { method: 'POST' });
localStorage.removeItem('user');
window.location.href = '/login.html';
window.location.href = '/login';
} catch (error) {
console.error('Logout failed:', error);
window.location.href = '/login.html';
window.location.href = '/login';
}
}
</script>

View File

@@ -3200,7 +3200,7 @@ padding: 15
if (!data.success || !data.data) {
// Not authenticated, redirect to login
window.location.href = '/login.html';
window.location.href = '/login';
return;
}
@@ -3231,7 +3231,7 @@ padding: 15
}
} catch (error) {
console.error('Auth check failed:', error);
window.location.href = '/login.html';
window.location.href = '/login';
}
})();
@@ -3240,10 +3240,10 @@ padding: 15
try {
await fetch('/api/auth/logout', { method: 'POST' });
localStorage.removeItem('user');
window.location.href = '/login.html';
window.location.href = '/login';
} catch (error) {
console.error('Logout failed:', error);
window.location.href = '/login.html';
window.location.href = '/login';
}
}
</script>

View File

@@ -2807,266 +2807,275 @@ Ver todos
gray: '#94a3b8'
};
// Performance Chart
const performanceCtx = document.getElementById('performanceChart').getContext('2d');
new Chart(performanceCtx, {
type: 'line',
data: {
labels: ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'],
datasets: [{
label: 'Vistas',
data: [1200, 1900, 2400, 2100, 2800, 3200, 2900, 3500, 3100, 3800, 3400, 4200],
borderColor: chartColors.primary,
backgroundColor: 'rgba(26, 95, 74, 0.1)',
fill: true,
tension: 0.4,
pointRadius: 4,
pointHoverRadius: 6
}, {
label: 'Leads',
data: [80, 120, 150, 130, 180, 210, 190, 230, 200, 260, 240, 280],
borderColor: chartColors.secondary,
backgroundColor: 'rgba(212, 168, 83, 0.1)',
fill: true,
tension: 0.4,
pointRadius: 4,
pointHoverRadius: 6
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top',
labels: {
usePointStyle: true,
padding: 20
}
}
// Chart instances storage
let charts = {};
// Initialize charts with empty data
function initCharts() {
const chartColors = {
primary: '#1a5f4a',
primaryLight: '#2d8f6f',
secondary: '#d4a853',
success: '#10b981',
warning: '#f59e0b',
danger: '#ef4444',
info: '#3b82f6',
gray: '#94a3b8'
};
// Performance Chart
const performanceCtx = document.getElementById('performanceChart').getContext('2d');
charts.performance = new Chart(performanceCtx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Vistas',
data: [],
borderColor: chartColors.primary,
backgroundColor: 'rgba(26, 95, 74, 0.1)',
fill: true,
tension: 0.4,
pointRadius: 4,
pointHoverRadius: 6
}, {
label: 'Leads',
data: [],
borderColor: chartColors.secondary,
backgroundColor: 'rgba(212, 168, 83, 0.1)',
fill: true,
tension: 0.4,
pointRadius: 4,
pointHoverRadius: 6
}]
},
scales: {
y: {
beginAtZero: true,
grid: {
color: 'rgba(0,0,0,0.05)'
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top',
labels: { usePointStyle: true, padding: 20 }
}
},
x: {
grid: {
display: false
}
scales: {
y: { beginAtZero: true, grid: { color: 'rgba(0,0,0,0.05)' } },
x: { grid: { display: false } }
}
}
}
});
});
// Traffic Sources Chart
const trafficCtx = document.getElementById('trafficChart').getContext('2d');
new Chart(trafficCtx, {
type: 'doughnut',
data: {
labels: ['Google', 'Directo', 'Instagram', 'Referidos', 'Facebook'],
datasets: [{
data: [35, 25, 18, 12, 10],
backgroundColor: [
chartColors.primary,
chartColors.secondary,
chartColors.info,
chartColors.success,
chartColors.warning
],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
usePointStyle: true,
padding: 15
// Traffic Sources Chart
const trafficCtx = document.getElementById('trafficChart').getContext('2d');
charts.traffic = new Chart(trafficCtx, {
type: 'doughnut',
data: {
labels: ['Directo', 'Búsqueda', 'Social', 'Referido', 'Email'],
datasets: [{
data: [35, 30, 20, 10, 5],
backgroundColor: [
chartColors.primary,
chartColors.secondary,
chartColors.info,
chartColors.success,
chartColors.warning
],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: { usePointStyle: true, padding: 15 }
}
}
},
cutout: '70%'
}
});
// Property Types Chart
const typesCtx = document.getElementById('typesChart').getContext('2d');
new Chart(typesCtx, {
type: 'bar',
data: {
labels: ['Agrícola', 'Urbano', 'Casas', 'Apartamentos', 'Ruinas'],
datasets: [{
label: 'Propiedades',
data: [25, 30, 20, 15, 10],
backgroundColor: [
chartColors.primary,
'#4a90d9',
'#9b59b6',
chartColors.secondary,
chartColors.danger
],
borderRadius: 8
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
},
scales: {
y: {
beginAtZero: true,
grid: { color: 'rgba(0,0,0,0.05)' }
},
x: {
grid: { display: false }
}
cutout: '70%'
}
}
});
});
// Leads Status Chart
const leadsCtx = document.getElementById('leadsChart').getContext('2d');
new Chart(leadsCtx, {
type: 'polarArea',
data: {
labels: ['Nuevos', 'Pendientes', 'Contactados', 'Cualificados', 'Convertidos'],
datasets: [{
data: [12, 8, 15, 6, 4],
backgroundColor: [
'rgba(59, 130, 246, 0.8)',
'rgba(245, 158, 11, 0.8)',
'rgba(16, 185, 129, 0.8)',
'rgba(139, 92, 246, 0.8)',
'rgba(34, 197, 94, 0.8)'
],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: { usePointStyle: true, padding: 10, font: { size: 10 } }
}
// Property Types Chart
const typesCtx = document.getElementById('typesChart').getContext('2d');
charts.types = new Chart(typesCtx, {
type: 'bar',
data: {
labels: ['Urbano', 'Agrícola', 'Casa', 'Apartamento', 'Ruinas'],
datasets: [{
label: 'Propiedades',
data: [0, 0, 0, 0, 0],
backgroundColor: [
chartColors.primary,
'#4a90d9',
'#9b59b6',
chartColors.secondary,
chartColors.danger
],
borderRadius: 8
}]
},
scales: {
r: {
grid: { display: false }
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
y: { beginAtZero: true, grid: { color: 'rgba(0,0,0,0.05)' } },
x: { grid: { display: false } }
}
}
}
});
});
// Top Properties Chart
const topPropsCtx = document.getElementById('topPropertiesChart').getContext('2d');
new Chart(topPropsCtx, {
type: 'bar',
data: {
labels: ['Terreno Adeje', 'Villa Marina', 'Ático Luz', 'Casa Palmera', 'Solar Norte'],
datasets: [{
label: 'Vistas',
data: [1245, 986, 876, 754, 623],
backgroundColor: chartColors.primary,
borderRadius: 6
}]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
x: { grid: { color: 'rgba(0,0,0,0.05)' } },
y: { grid: { display: false } }
// Leads Status Chart
const leadsCtx = document.getElementById('leadsChart').getContext('2d');
charts.leads = new Chart(leadsCtx, {
type: 'doughnut',
data: {
labels: ['Nuevo', 'Contactado', 'Calificado', 'Negociando', 'Cerrado'],
datasets: [{
data: [0, 0, 0, 0, 0],
backgroundColor: [
'#3b82f6',
'#f59e0b',
'#10b981',
'#8b5cf6',
'#1a5f4a'
],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: { usePointStyle: true, padding: 10, font: { size: 10 } }
}
}
}
}
});
});
// Daily Performance Chart (Analytics)
const dailyCtx = document.getElementById('dailyPerformanceChart').getContext('2d');
new Chart(dailyCtx, {
type: 'bar',
data: {
labels: Array.from({length: 30}, (_, i) => `Día ${i + 1}`),
datasets: [{
label: 'Visitantes',
data: Array.from({length: 30}, () => Math.floor(Math.random() * 400) + 100),
backgroundColor: 'rgba(26, 95, 74, 0.7)',
borderRadius: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
y: { beginAtZero: true, grid: { color: 'rgba(0,0,0,0.05)' } },
x: { grid: { display: false } }
// Top Properties Chart
const topPropsCtx = document.getElementById('topPropertiesChart').getContext('2d');
charts.topProperties = new Chart(topPropsCtx, {
type: 'bar',
data: {
labels: [],
datasets: [{
label: 'Vistas',
data: [],
backgroundColor: chartColors.primary,
borderRadius: 6
}]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
x: { grid: { color: 'rgba(0,0,0,0.05)' } },
y: { grid: { display: false } }
}
}
}
});
});
}
// Devices Chart
const devicesCtx = document.getElementById('devicesChart').getContext('2d');
new Chart(devicesCtx, {
type: 'pie',
data: {
labels: ['Móvil', 'Desktop', 'Tablet'],
datasets: [{
data: [58, 35, 7],
backgroundColor: [chartColors.primary, chartColors.secondary, chartColors.info],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'bottom', labels: { usePointStyle: true, padding: 15 } }
// Load dashboard data from API
async function loadDashboardData() {
try {
// Load stats
const statsRes = await fetch('/api/admin/stats', { credentials: 'include' });
const statsData = await statsRes.json();
if (statsData.success) {
const stats = statsData.data;
// Animate stats
animateCounter($('#statViews'), stats.analytics.views || 0, 1500);
animateCounter($('#statLeads'), stats.leads.new || 0, 1500);
animateCounter($('#statClicks'), stats.analytics.inquiries || 0, 1500);
// Update conversion rate
const conversion = stats.leads.total > 0
? ((stats.leads.closed / stats.leads.total) * 100).toFixed(1)
: '0';
$('#statConversion').text(conversion + '%');
}
}
});
// Geo Chart
const geoCtx = document.getElementById('geoChart').getContext('2d');
new Chart(geoCtx, {
type: 'bar',
data: {
labels: ['España', 'Rusia', 'Alemania', 'Reino Unido', 'Francia'],
datasets: [{
label: 'Visitantes',
data: [2456, 1234, 987, 876, 654],
backgroundColor: [
'#e74c3c',
'#3498db',
'#f1c40f',
'#9b59b6',
'#1abc9c'
],
borderRadius: 8
}]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
x: { grid: { color: 'rgba(0,0,0,0.05)' } },
y: { grid: { display: false } }
// Load charts data
const chartsRes = await fetch('/api/admin/analytics/charts', { credentials: 'include' });
const chartsData = await chartsRes.json();
if (chartsData.success) {
const data = chartsData.data;
// Update Performance Chart
if (charts.performance) {
charts.performance.data.labels = data.months;
charts.performance.data.datasets[0].data = data.viewsPerMonth;
charts.performance.data.datasets[1].data = data.leadsPerMonth;
charts.performance.update();
}
// Update Leads Status Chart
if (charts.leads && data.leadsStatus) {
const statusLabels = {
'new': 'Nuevo',
'contacted': 'Contactado',
'qualified': 'Calificado',
'negotiating': 'Negociando',
'closed': 'Cerrado'
};
const statusColors = {
'new': '#3b82f6',
'contacted': '#f59e0b',
'qualified': '#10b981',
'negotiating': '#8b5cf6',
'closed': '#1a5f4a'
};
charts.leads.data.labels = data.leadsStatus.map(l => statusLabels[l.status] || l.status);
charts.leads.data.datasets[0].data = data.leadsStatus.map(l => l.count);
charts.leads.data.datasets[0].backgroundColor = data.leadsStatus.map(l => statusColors[l.status] || '#6c757d');
charts.leads.update();
}
// Update Types Chart (by city instead, since we don't have type distribution)
if (charts.types && data.propertiesByCity) {
const cities = data.propertiesByCity.slice(0, 5).map(c => c.city);
const counts = data.propertiesByCity.slice(0, 5).map(c => c.count);
charts.types.data.labels = cities;
charts.types.data.datasets[0].data = counts;
charts.types.update();
}
}
// Load top properties
const propsRes = await fetch('/api/properties?limit=5&lang=es', { credentials: 'include' });
const propsData = await propsRes.json();
if (propsData.success && propsData.data) {
const topProps = propsData.data
.sort((a, b) => (b.views_count || 0) - (a.views_count || 0))
.slice(0, 5);
if (charts.topProperties) {
charts.topProperties.data.labels = topProps.map(p => p.reference);
charts.topProperties.data.datasets[0].data = topProps.map(p => p.views_count || 0);
charts.topProperties.update();
}
}
} catch (error) {
console.error('Failed to load dashboard data:', error);
}
});
}
// Initialize on page load
initCharts();
loadDashboardData();
// Period buttons
$('.chart-period-btn').on('click', function() {
@@ -3164,13 +3173,6 @@ padding: 15
}, 16);
}
// Animate stats on load
setTimeout(() => {
animateCounter($('#statViews'), 24892, 1500);
animateCounter($('#statClicks'), 3421, 1500);
animateCounter($('#statLeads'), 156, 1500);
}, 500);
// Notifications dropdown
$('.topbar-btn').first().on('click', function() {
// Show notifications

View File

@@ -1434,7 +1434,6 @@ app.get('/admin/settings.html', serveStatic({ path: './public/admin/settings.htm
app.get('/property/*', serveStatic({ path: './public/property.html' }))
app.get('/admin', serveStatic({ path: './public/admin.html' }))
app.get('/login', serveStatic({ path: './public/login.html' }))
app.get('/login.html', serveStatic({ path: './public/login.html' }))
// Fallback to index.html for all other routes
app.get('*', serveStatic({ path: './public/index.html' }))