feat(#54): Improve display of credit usage and accessibility

This commit is contained in:
Josh Creek
2024-11-13 14:51:13 +00:00
parent 93f41de185
commit d62c92e527
4 changed files with 135 additions and 25 deletions
+17 -1
View File
@@ -17,9 +17,25 @@ export async function load({ locals: { safeGetSession } }) {
const creditsRemaining = await getUserCredits(userId);
const transactions = await getUserCreditTransactions(userId);
const monthlyAggregates = transactions.reduce((acc, transaction) => {
const date = new Date(transaction.created_at);
const month = `${date.getFullYear()}-${date.getMonth() + 1}`;
if (!acc[month]) acc[month] = { credits_added: 0, credits_deducted: 0 };
if (transaction.credits_change > 0) {
acc[month].credits_added += transaction.credits_change;
} else {
acc[month].credits_deducted += transaction.credits_change;
}
return acc;
}, {});
return {
creditsRemaining,
transactions
transactions,
monthlyAggregates
};
} catch (error) {
return {
+96 -24
View File
@@ -1,59 +1,120 @@
<script lang="ts">
import { onMount } from 'svelte';
import Chart, { type ChartData } from 'chart.js/auto';
import { DataTable } from 'simple-datatables';
export let data;
const { creditsRemaining, transactions } = data;
const { creditsRemaining, transactions, monthlyAggregates } = data;
let chartCanvas: HTMLCanvasElement | null = null;
let creditsTrendChart: Chart | null = null;
let noTransactions = transactions ? transactions.length === 0 : true;
let creditsChart: Chart | null = null;
let chartType: 'bar' | 'line' = 'bar';
let chartData: ChartData | null = null;
if (!noTransactions) {
chartData = {
labels: transactions.map((t) => new Date(t.created_at).toLocaleDateString()),
datasets: [
{
label: 'Credit Changes',
data: transactions.map((t) => t.credits_change),
backgroundColor: transactions.map((t) => (t.credits_change > 0 ? 'green' : 'red'))
}
]
};
const monthlyLabels = Object.keys(monthlyAggregates).reverse();
const creditsAdded = monthlyLabels.map((month) => monthlyAggregates[month].credits_added);
const creditsDeducted = monthlyLabels.map((month) =>
Math.abs(monthlyAggregates[month].credits_deducted)
);
const groupedTransactions = transactions.reduce((acc, transaction) => {
const date = new Date(transaction.created_at);
const month = `${date.getFullYear()}-${date.getMonth() + 1}`;
if (!acc[month]) acc[month] = [];
acc[month].push(transaction);
return acc;
}, {});
let cumulativeBalance = 0;
const cumulativeBalanceData = monthlyLabels.map((month) => {
const monthTransactions = groupedTransactions[month] || [];
const monthCreditsChange = monthTransactions.reduce((sum, transaction) => {
return sum + transaction.credits_change;
}, 0);
cumulativeBalance += monthCreditsChange;
return cumulativeBalance;
});
function initialiseChartData() {
if (chartType === 'bar') {
chartData = {
labels: monthlyLabels,
datasets: [
{
label: 'Credits Added',
data: creditsAdded,
backgroundColor: 'green'
},
{
label: 'Credits Deducted',
data: creditsDeducted,
backgroundColor: 'red'
}
]
};
} else if (chartType === 'line') {
chartData = {
labels: monthlyLabels,
datasets: [
{
label: 'Cumulative Balance',
data: cumulativeBalanceData,
borderColor: 'blue',
fill: false
}
]
};
}
}
function updateChart(type: 'bar' | 'line') {
chartType = type;
initialiseChartData();
if (creditsChart) {
creditsChart.config.type = chartType;
creditsChart.data = chartData;
creditsChart.update();
}
}
onMount(() => {
initialiseChartData();
if (chartCanvas && chartData) {
creditsTrendChart = new Chart(chartCanvas, {
type: 'bar',
creditsChart = new Chart(chartCanvas, {
type: chartType,
data: chartData,
options: { responsive: true }
});
}
});
$: if (transactions && chartData) {
chartData.labels = transactions.map((t) => new Date(t.created_at).toLocaleDateString());
chartData.datasets[0].data = transactions.map((t) => t.credits_change);
creditsTrendChart?.update();
}
const dataTable = new DataTable('#transaction-history');
});
</script>
<main class="flex flex-col items-center p-4">
<h1 class="text-3xl font-bold mb-4">Credit Dashboard</h1>
<p class="text-lg">Credits Remaining: {creditsRemaining}</p>
<div class="flex justify-center my-4">
<button on:click={() => updateChart('bar')} class:font-bold={chartType === 'bar'}
>Monthly Usage</button
>
<button on:click={() => updateChart('line')} class:font-bold={chartType === 'line'}
>Cumulative Balance</button
>
</div>
<div class="w-full md:w-3/4 mt-6">
<canvas bind:this={chartCanvas} class="my-8"></canvas>
</div>
<h2 class="text-2xl font-semibold mb-2">Transaction History</h2>
<div class="overflow-x-auto w-full md:w-3/4">
{#if noTransactions}
{#if transactions.length === 0}
<p class="text-lg">No transactions found.</p>
{:else}
<table class="table-auto w-full text-left border-collapse">
<table id="transaction-history" class="table-auto w-full text-left border-collapse">
<thead>
<tr class="border-b">
<th class="px-4 py-2">Date</th>
@@ -88,4 +149,15 @@
table {
margin-top: 1rem;
}
button {
margin: 0 1rem;
padding: 0.5rem 1rem;
border: 1px solid #ccc;
border-radius: 0.5rem;
cursor: pointer;
}
button.font-bold {
font-weight: bold;
background-color: #f0f0f0;
}
</style>