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:
Binary file not shown.
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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' }))
|
||||
|
||||
Reference in New Issue
Block a user