Updated the web #31

Merged
jkuijperm merged 10 commits from dev into main 2026-08-28 11:28:27 +00:00
6 changed files with 211 additions and 21 deletions
Showing only changes of commit 161ea36ff5 - Show all commits

View File

@ -37,6 +37,9 @@
--color-table-alt: #f8faf9;
--color-table-hover: #e9f1ed;
--color-progress-track: #e0e0e0;
--chart-grid-color: rgba(0, 0, 0, 0.1);
--chart-fill-color: rgba(15, 118, 110, 0.2);
}
[data-theme="dark"] {
@ -68,6 +71,9 @@
--color-table-alt: #182420;
--color-table-hover: #20302a;
--color-progress-track: #2a3a33;
--chart-grid-color: rgba(255, 255, 255, 0.12);
--chart-fill-color: rgba(15, 118, 110, 0.35);
}
* {

View File

@ -0,0 +1,88 @@
(function () {
function getActiveTheme() {
return document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light';
}
function getChartColors() {
var styles = getComputedStyle(document.documentElement);
var theme = getActiveTheme();
return {
theme: theme,
gridColor: styles.getPropertyValue('--chart-grid-color').trim() || 'rgba(0, 0, 0, 0.1)',
tickColor: styles.getPropertyValue('--color-text-muted').trim() || '#6b6560',
primaryColor: styles.getPropertyValue('--color-primary').trim() || '#0f766e',
primaryFill: styles.getPropertyValue('--chart-fill-color').trim() || 'rgba(15, 118, 110, 0.2)',
palette: ['#ff6384', '#36a2eb', '#cc65fe', '#ffce56', '#4bc0c0', '#ffa1b5']
};
}
// Aplica los colores de grid/ticks a las escalas cartesianas (x/y) de un gráfico.
// Se usa tanto al crear el gráfico (colores embebidos en las opciones iniciales)
// como al refrescar el tema en caliente sobre un gráfico ya existente.
function applyCartesianColors(chart, colors) {
var scales = chart.options.scales;
if (!scales) {
return;
}
Object.keys(scales).forEach(function (axis) {
var scale = scales[axis];
if (!scale) {
return;
}
// Object.assign crea un objeto grid/ticks nuevo en vez de mutar en el
// sitio (scale.grid.color = ...). Chart.js reconstruye internamente los
// objetos de opciones de un gráfico 'line' ya creado, y mutar una
// propiedad suelta sobre esa estructura reactiva provoca
// "TypeError: t.startsWith is not a function" al redibujar. Reemplazar
// el objeto entero evita tocar esas referencias internas.
if (typeof colors.gridColor === 'string' && colors.gridColor) {
scale.grid = Object.assign({}, scale.grid, { color: colors.gridColor });
}
if (typeof colors.tickColor === 'string' && colors.tickColor) {
scale.ticks = Object.assign({}, scale.ticks, { color: colors.tickColor });
}
});
}
// Los colores de tema se fijan en las opciones al CREAR cada gráfico (evita el
// bug de mutar un gráfico 'line' recién construido). Para que el gráfico
// reaccione a un cambio de tema posterior sin recrearlo, cada plantilla registra
// aquí su instancia junto con un callback que sabe cómo reaplicar sus colores.
var registeredCharts = [];
function registerThemedChart(chart, applyColors) {
registeredCharts.push({ chart: chart, applyColors: applyColors });
}
function refreshThemedCharts() {
var colors = getChartColors();
registeredCharts.forEach(function (entry) {
entry.applyColors(entry.chart, colors);
// 'none' desactiva la animación de la actualización: solo repinta con
// los nuevos colores, sin la transición de entrada de datos.
entry.chart.update('none');
});
}
// Observa el atributo data-theme del <html> (lo cambia el toggle de tema) para
// refrescar todos los gráficos registrados en cuanto el usuario cambia de tema,
// sin necesidad de recargar la página.
new MutationObserver(function (mutations) {
var themeChanged = mutations.some(function (m) { return m.attributeName === 'data-theme'; });
if (themeChanged) {
refreshThemedCharts();
}
}).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
window.getChartColors = getChartColors;
window.applyCartesianColors = applyCartesianColors;
window.registerThemedChart = registerThemedChart;
})();

View File

@ -16,6 +16,7 @@
</script>
<link rel="stylesheet" href="{% static 'expenses/css/base.css' %}">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="{% static 'expenses/js/charts.js' %}"></script>
{% block extra_css %}{% endblock %}
</head>

View File

@ -259,25 +259,32 @@
const catLabels = [{% for row in by_category_chart %}"{{ row.category__name }}",{% endfor %}];
const catData = [{% for row in by_category_chart %}{{ row.total|unlocalize }},{% endfor %}];
new Chart(document.getElementById('categoryChart'), {
const categoryChartColors = getChartColors();
const categoryChart = new Chart(document.getElementById('categoryChart'), {
type: 'pie',
data: {
labels: catLabels,
datasets: [{
data: catData,
backgroundColor: [
'#ff6384', '#36a2eb', '#cc65fe', '#ffce56', '#4bc0c0', '#ffa1b5'
]
backgroundColor: categoryChartColors.palette
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'right' }
legend: {
position: 'right',
labels: { color: categoryChartColors.tickColor }
}
}
}
});
registerThemedChart(categoryChart, function (chart, colors) {
chart.options.plugins.legend.labels.color = colors.tickColor;
});
</script>
<h3>Evolución anual por cuenta ({{ selected_year }})</h3>
@ -297,34 +304,50 @@
const labels = {{ chart_labels|safe }};
const data = {{ chart_data|safe }};
const ctx = document.getElementById('mainChart');
const mainChartColors = getChartColors();
new Chart(ctx, {
const mainChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets:[{
label: 'Gastos (€)',
data: data,
backgroundColor: 'rgba(54, 162, 235, 0.6)',
backgroundColor: 'rgba(54, 162, 235, 1)',
backgroundColor: mainChartColors.primaryColor,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: { y: { beginAtZero: true } }
scales: {
x: {
grid: { color: mainChartColors.gridColor },
ticks: { color: mainChartColors.tickColor }
},
y: {
beginAtZero: true,
grid: { color: mainChartColors.gridColor },
ticks: { color: mainChartColors.tickColor }
}
}
}
});
registerThemedChart(mainChart, function (chart, colors) {
chart.data.datasets[0].backgroundColor = colors.primaryColor;
applyCartesianColors(chart, colors);
});
const allMonths = ["Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"];
{% for acc in accounts_charts %}
(function() {
const dataAccount = {{ acc.data|safe }};
const filteredLabels = allMonths.slice(0, dataAccount.length);
const accountChartColors = getChartColors();
new Chart(
const accountChart = new Chart(
document.getElementById('accountChart{{ acc.id }}'),
{
type: 'line',
@ -334,8 +357,8 @@
label: 'Saldo Real (€)',
data: dataAccount,
fill: false,
borderColor: 'rgba(54, 162, 235, 1)',
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderColor: accountChartColors.primaryColor,
backgroundColor: accountChartColors.primaryFill,
tension: 0.1
}]
},
@ -343,13 +366,25 @@
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
grid: { color: accountChartColors.gridColor },
ticks: { color: accountChartColors.tickColor }
},
y: {
beginAtZero: false
beginAtZero: false,
grid: { color: accountChartColors.gridColor },
ticks: { color: accountChartColors.tickColor }
}
}
}
}
);
registerThemedChart(accountChart, function (chart, colors) {
chart.data.datasets[0].borderColor = colors.primaryColor;
chart.data.datasets[0].backgroundColor = colors.primaryFill;
applyCartesianColors(chart, colors);
});
})();
{% endfor %}
</script>

View File

@ -45,21 +45,39 @@
const data = {{ mini_chart_data|safe }};
const ctx = document.getElementById('miniChart');
const miniChartColors = getChartColors();
new Chart(ctx, {
const miniChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets:[{
label: 'Gastos',
data: data,
backgroundColor: miniChartColors.primaryColor,
}]
},
options: {
responsive: true,
maintainAspectRatio: false
maintainAspectRatio: false,
scales: {
x: {
grid: { color: miniChartColors.gridColor },
ticks: { color: miniChartColors.tickColor }
},
y: {
beginAtZero: true,
grid: { color: miniChartColors.gridColor },
ticks: { color: miniChartColors.tickColor }
}
}
}
});
registerThemedChart(miniChart, function (chart, colors) {
chart.data.datasets[0].backgroundColor = colors.primaryColor;
applyCartesianColors(chart, colors);
});
</script>
</section>

View File

@ -59,31 +59,73 @@
<script>
const monthlyData = {{ monthly_data|safe }};
const monthlyChartColors = getChartColors();
new Chart(document.getElementById('monthlyChart'), {
const monthlyChart = new Chart(document.getElementById('monthlyChart'), {
type: 'bar',
data: {
labels: ['Ene','Feb','Mar','Abr','May','Jun','Jul','Ago','Sep','Oct','Nov','Dic'],
datasets: [{
label: 'Gasto mensual',
data: monthlyData
data: monthlyData,
backgroundColor: monthlyChartColors.primaryColor
}]
},
options: {
scales: {
x: {
grid: { color: monthlyChartColors.gridColor },
ticks: { color: monthlyChartColors.tickColor }
},
y: {
beginAtZero: true,
grid: { color: monthlyChartColors.gridColor },
ticks: { color: monthlyChartColors.tickColor }
}
}
}
});
registerThemedChart(monthlyChart, function (chart, colors) {
chart.data.datasets[0].backgroundColor = colors.primaryColor;
applyCartesianColors(chart, colors);
});
const kmData = {{ km_data|safe }};
const kmDates = {{ km_dates|safe }};
const kmChartColors = getChartColors();
new Chart(document.getElementById('kmChart'), {
const kmChart = new Chart(document.getElementById('kmChart'), {
type: 'line',
data: {
labels: kmDates,
datasets: [{
label: 'Km entre repostajes',
data: kmData
data: kmData,
borderColor: kmChartColors.primaryColor,
backgroundColor: kmChartColors.primaryFill
}]
},
options: {
scales: {
x: {
grid: { color: kmChartColors.gridColor },
ticks: { color: kmChartColors.tickColor }
},
y: {
beginAtZero: true,
grid: { color: kmChartColors.gridColor },
ticks: { color: kmChartColors.tickColor }
}
}
}
});
registerThemedChart(kmChart, function (chart, colors) {
chart.data.datasets[0].borderColor = colors.primaryColor;
chart.data.datasets[0].backgroundColor = colors.primaryFill;
applyCartesianColors(chart, colors);
});
</script>
{% endblock %}