Add SQLite passive income dashboard with Excel import and deployment tools
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
(() => {
|
||||
const status = document.getElementById('chart-status');
|
||||
if (typeof Chart === 'undefined') {
|
||||
status.textContent = 'Chart.js konnte nicht vom CDN geladen werden. Alle Werte bleiben in den Tabellen verfügbar.';
|
||||
return;
|
||||
}
|
||||
const data = JSON.parse(document.getElementById('chart-data').textContent);
|
||||
const euro = cents => new Intl.NumberFormat('de-DE', {style: 'currency', currency: 'EUR'}).format(cents / 100);
|
||||
const palette = ['#72e2b0','#79b6ff','#f4c272','#bc9cfa','#f691aa','#67cbd0','#e5db89','#9ec28b','#b2bdec','#daaa81'];
|
||||
Chart.defaults.color = '#a4b3c7';
|
||||
Chart.defaults.borderColor = '#293548';
|
||||
Chart.defaults.font.family = 'system-ui, sans-serif';
|
||||
new Chart(document.getElementById('monthly-chart'), {
|
||||
type: 'line',
|
||||
data: {labels: data.months, datasets: data.years.map((year, i) => ({...year, borderColor: palette[i % palette.length], backgroundColor: palette[i % palette.length], tension: 0.2, pointRadius: 3}))},
|
||||
options: {responsive:true, maintainAspectRatio:false, animation:false, interaction:{mode:'index',intersect:false},
|
||||
scales:{y:{ticks:{callback:euro},title:{display:true,text:'Euro'}}}, plugins:{tooltip:{callbacks:{label:ctx => `${ctx.dataset.label}: ${euro(ctx.raw)}`}}}}
|
||||
});
|
||||
function shares(id, rows) {
|
||||
// Signed corrections cannot be represented truthfully as pie slices.
|
||||
const negative = rows.some(row => row.amount < 0);
|
||||
const total = rows.reduce((sum,row) => sum + row.amount, 0);
|
||||
new Chart(document.getElementById(id), {
|
||||
type: negative ? 'bar' : 'doughnut',
|
||||
data:{labels:rows.map(row => row.name),datasets:[{data:rows.map(row => row.amount),backgroundColor:rows.map((_,i) => palette[i % palette.length]),borderWidth:0}]},
|
||||
options:{responsive:true,maintainAspectRatio:false,animation:false,
|
||||
...(negative ? {scales:{y:{ticks:{callback:euro}}}} : {cutout:'68%'}),
|
||||
plugins:{legend:{display:!negative,position:'bottom',labels:{boxWidth:10,font:{size:11}}},tooltip:{callbacks:{label:ctx => {
|
||||
const amount = rows[ctx.dataIndex].amount;
|
||||
const share = total === 0 ? '–' : new Intl.NumberFormat('de-DE',{minimumFractionDigits:2,maximumFractionDigits:2}).format(amount / total * 100) + ' %';
|
||||
return `${ctx.label}: ${euro(amount)} (${share})`;
|
||||
}}}}}
|
||||
});
|
||||
}
|
||||
shares('shares-chart', data.shares);
|
||||
shares('kinds-chart', data.kinds);
|
||||
status.textContent = data.years.some(year => year.data.some(value => value !== 0)) ? 'Diagramme zeigen tatsächlich erhaltene Einnahmen. Negative Positionssummen werden als Balken dargestellt.' : 'Noch keine tatsächlichen Einnahmen vorhanden.';
|
||||
})();
|
||||
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
document.querySelectorAll('form[data-confirm]').forEach(form => {
|
||||
form.addEventListener('submit', event => {
|
||||
if (!window.confirm(form.dataset.confirm)) event.preventDefault();
|
||||
});
|
||||
});
|
||||
const filters = document.getElementById('income-filters');
|
||||
if (filters) filters.addEventListener('submit', () => {
|
||||
filters.querySelectorAll('select').forEach(select => { if (!select.value) select.disabled = true; });
|
||||
});
|
||||
Reference in New Issue
Block a user