refactor: Implement reactive chart rendering and cleanup using Svelte's $effect on the analytics page.

This commit is contained in:
2025-12-25 20:06:02 +00:00
parent a66e266dbc
commit 6802d3ca7f

View File

@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte";
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import Chart from "chart.js/auto"; import Chart from "chart.js/auto";
import Card from "$lib/components/ui/Card.svelte"; import Card from "$lib/components/ui/Card.svelte";
@@ -9,10 +8,15 @@
let { data } = $props(); let { data } = $props();
let analytics = $derived(data.analytics); let analytics = $derived(data.analytics);
// svelte-ignore non_reactive_update
let timelineCanvas: HTMLCanvasElement; let timelineCanvas: HTMLCanvasElement;
// svelte-ignore non_reactive_update
let pieCanvas: HTMLCanvasElement; let pieCanvas: HTMLCanvasElement;
// svelte-ignore non_reactive_update
let electricityCanvas: HTMLCanvasElement; let electricityCanvas: HTMLCanvasElement;
// svelte-ignore non_reactive_update
let costCanvas: HTMLCanvasElement; let costCanvas: HTMLCanvasElement;
// svelte-ignore non_reactive_update
let printerCanvas: HTMLCanvasElement; let printerCanvas: HTMLCanvasElement;
// Time range options // Time range options
@@ -23,10 +27,9 @@
{ value: "365", label: "1 Year" }, { value: "365", label: "1 Year" },
{ value: "all", label: "All Time" }, { value: "all", label: "All Time" },
]; ];
let selectedRange = $state(analytics.range); let selectedRange = $derived(analytics.range);
function changeRange(range: string) { function changeRange(range: string) {
selectedRange = range;
goto(`/analytics?range=${range}`); goto(`/analytics?range=${range}`);
} }
@@ -49,7 +52,21 @@
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i]; return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
} }
onMount(() => { // Chart instances
let timelineChart: Chart | null = null;
let pieChart: Chart | null = null;
let electricityChart: Chart | null = null;
let costChart: Chart | null = null;
let printerChart: Chart | null = null;
$effect(() => {
// Cleanup previous charts
if (timelineChart) timelineChart.destroy();
if (pieChart) pieChart.destroy();
if (electricityChart) electricityChart.destroy();
if (costChart) costChart.destroy();
if (printerChart) printerChart.destroy();
const chartColors = [ const chartColors = [
"#3b82f6", "#3b82f6",
"#8b5cf6", "#8b5cf6",
@@ -60,10 +77,11 @@
]; ];
// 1. Timeline Chart - Filament Usage // 1. Timeline Chart - Filament Usage
if (timelineCanvas) {
const dates = Object.keys(analytics.usageByDate).slice(-30); const dates = Object.keys(analytics.usageByDate).slice(-30);
const weights = dates.map((d) => analytics.usageByDate[d]); const weights = dates.map((d) => analytics.usageByDate[d]);
new Chart(timelineCanvas, { timelineChart = new Chart(timelineCanvas, {
type: "line", type: "line",
data: { data: {
labels: dates.map((d) => labels: dates.map((d) =>
@@ -101,12 +119,14 @@
}, },
}, },
}); });
}
// 2. Material Pie Chart // 2. Material Pie Chart
if (pieCanvas) {
const materials = Object.keys(analytics.materialUsage); const materials = Object.keys(analytics.materialUsage);
const matWeights = materials.map((m) => analytics.materialUsage[m]); const matWeights = materials.map((m) => analytics.materialUsage[m]);
new Chart(pieCanvas, { pieChart = new Chart(pieCanvas, {
type: "doughnut", type: "doughnut",
data: { data: {
labels: materials, labels: materials,
@@ -122,20 +142,25 @@
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
plugins: { plugins: {
legend: { position: "right", labels: { color: "#94a3b8" } }, legend: {
position: "right",
labels: { color: "#94a3b8" },
},
}, },
}, },
}); });
}
// 3. Electricity Usage Chart // 3. Electricity Usage Chart
const electricDates = Object.keys(analytics.electricityByDate).slice( if (electricityCanvas) {
-30, const electricDates = Object.keys(
); analytics.electricityByDate,
).slice(-30);
const electricityWh = electricDates.map( const electricityWh = electricDates.map(
(d) => analytics.electricityByDate[d] / 1000, (d) => analytics.electricityByDate[d] / 1000,
); );
new Chart(electricityCanvas, { electricityChart = new Chart(electricityCanvas, {
type: "bar", type: "bar",
data: { data: {
labels: electricDates.map((d) => labels: electricDates.map((d) =>
@@ -165,7 +190,11 @@
y: { y: {
grid: { color: "rgba(255,255,255,0.1)" }, grid: { color: "rgba(255,255,255,0.1)" },
ticks: { color: "#94a3b8" }, ticks: { color: "#94a3b8" },
title: { display: true, text: "kWh", color: "#94a3b8" }, title: {
display: true,
text: "kWh",
color: "#94a3b8",
},
}, },
x: { x: {
grid: { display: false }, grid: { display: false },
@@ -174,12 +203,14 @@
}, },
}, },
}); });
}
// 4. Cost Chart // 4. Cost Chart
if (costCanvas) {
const costDates = Object.keys(analytics.costByDate).slice(-30); const costDates = Object.keys(analytics.costByDate).slice(-30);
const costs = costDates.map((d) => analytics.costByDate[d]); const costs = costDates.map((d) => analytics.costByDate[d]);
new Chart(costCanvas, { costChart = new Chart(costCanvas, {
type: "line", type: "line",
data: { data: {
labels: costDates.map((d) => labels: costDates.map((d) =>
@@ -220,10 +251,11 @@
}, },
}, },
}); });
}
// 5. Printer Usage Chart // 5. Printer Usage Chart
if (analytics.printerStats.length > 0) { if (printerCanvas && analytics.printerStats.length > 0) {
new Chart(printerCanvas, { printerChart = new Chart(printerCanvas, {
type: "bar", type: "bar",
data: { data: {
labels: analytics.printerStats.map( labels: analytics.printerStats.map(
@@ -620,9 +652,7 @@
<p class="text-2xl font-bold text-purple-400"> <p class="text-2xl font-bold text-purple-400">
{formatBytes(analytics.totalModelSize)} {formatBytes(analytics.totalModelSize)}
</p> </p>
<p class="text-xs text-slate-400 uppercase"> <p class="text-xs text-slate-400 uppercase">Storage Used</p>
Storage Used
</p>
</div> </div>
</div> </div>
{#if analytics.topModels.length > 0} {#if analytics.topModels.length > 0}