Add budget spending chart command

This commit is contained in:
2026-08-10 10:53:37 +02:00
parent cd5b076d65
commit 24fcd0b076
4 changed files with 87 additions and 3 deletions

View File

@@ -1,6 +1,16 @@
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const { SlashCommandBuilder, EmbedBuilder, AttachmentBuilder } = require('discord.js');
const axios = require('axios').default;
const { HOUSEHOLD_IDS } = require('../../core/constants.js');
const CHART_COLORS = {
[HOUSEHOLD_IDS.MINZ]: '#4e79a7',
[HOUSEHOLD_IDS.MIFFY]: '#f28e2b',
};
const DISPLAY_NAMES = {
[HOUSEHOLD_IDS.MINZ]: 'Minz',
[HOUSEHOLD_IDS.MIFFY]: 'Miffy',
};
module.exports = {
category: 'utility',
global: true,
@@ -43,6 +53,15 @@ module.exports = {
option.setName('user')
.setRequired(false)
.setDescription('User to view stats for'))
)
.addSubcommand(subcommand =>
subcommand
.setName('chart')
.setDescription('Render a bar chart comparing Minz and Miffy spendings over time')
.addIntegerOption(option =>
option.setName('weeks')
.setRequired(false)
.setDescription('Number of recent weeks to include (default 8)'))
),
async execute(interaction) {
let db = await interaction.client.localDB;
@@ -159,6 +178,70 @@ module.exports = {
await interaction.reply({ embeds: [statsEmbed] });
break;
case 'chart': {
const weeks = Math.min(Math.max(interaction.options.getInteger('weeks') ?? 8, 1), 52);
const chartPeriods = db.prepare(`
SELECT id, start_date, end_date
FROM weekly_budgets
ORDER BY start_date DESC
LIMIT ?
`).all(weeks).reverse();
if (chartPeriods.length === 0) {
await interaction.reply("No budget periods on record.");
return;
}
await interaction.deferReply();
const spentByUserAndPeriod = db.prepare(`
SELECT COALESCE(SUM(amount), 0) as total
FROM grocery_spendings
WHERE budget_id = ? AND discord_id = ?
`);
const labels = chartPeriods.map(period => period.start_date.slice(5));
const datasets = [HOUSEHOLD_IDS.MINZ, HOUSEHOLD_IDS.MIFFY].map(userId => ({
label: DISPLAY_NAMES[userId],
backgroundColor: CHART_COLORS[userId],
data: chartPeriods.map(period => spentByUserAndPeriod.get(period.id, userId).total),
}));
const chartConfig = {
type: 'bar',
data: { labels, datasets },
options: {
title: { display: true, text: 'Weekly Grocery Spending (€)' },
scales: {
yAxes: [{ ticks: { beginAtZero: true }, scaleLabel: { display: true, labelString: '€' } }],
},
},
};
try {
const response = await axios.post('https://quickchart.io/chart', {
chart: chartConfig,
width: 800,
height: 400,
backgroundColor: 'white',
}, { responseType: 'arraybuffer' });
const attachment = new AttachmentBuilder(Buffer.from(response.data), { name: 'budget-chart.png' });
const chartEmbed = new EmbedBuilder()
.setColor(0x0099ff)
.setTitle('Weekly Grocery Spending')
.setDescription(`Last ${chartPeriods.length} week(s): \`${chartPeriods[0].start_date}\` to \`${chartPeriods[chartPeriods.length - 1].end_date}\``)
.setImage('attachment://budget-chart.png')
.setTimestamp();
await interaction.editReply({ embeds: [chartEmbed], files: [attachment] });
} catch (error) {
console.error('[budget chart] Failed to render chart:', error);
await interaction.editReply("Failed to render the chart, sorry!");
}
break;
}
default:
await interaction.reply(`Sub mismatch. Switching on ${interaction.options.getSubcommand()}`);
break;