From 52d16780c9f2ed6c18e0c109887fd1fe2ed3db57 Mon Sep 17 00:00:00 2001 From: 69starman Date: Sat, 30 May 2026 18:21:53 +0000 Subject: [PATCH 1/3] fix: use lightweight static skeleton for chart dynamic loading --- src/app/page.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/page.jsx b/src/app/page.jsx index 3d10380..2748155 100644 --- a/src/app/page.jsx +++ b/src/app/page.jsx @@ -70,7 +70,11 @@ const DashboardTrafficChart = dynamic( () => import("./components/DashboardTrafficChart"), { ssr: false, - loading: () => , + loading: () => ( +
+ +
+ ), }, ); From 80698ce49c9c6e01a137547e5c7cde70cb70d443 Mon Sep 17 00:00:00 2001 From: 69starman Date: Sat, 30 May 2026 11:45:52 +0000 Subject: [PATCH 2/3] chore: compress telemetry chart streams to 50 plot vectors --- src/app/components/DashboardTrafficChart.tsx | 12 +++- src/app/components/RateSparklineCard.tsx | 6 +- src/app/components/chartUtils.ts | 62 ++++++++++++++++++++ 3 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 src/app/components/chartUtils.ts diff --git a/src/app/components/DashboardTrafficChart.tsx b/src/app/components/DashboardTrafficChart.tsx index eab656c..9186a64 100644 --- a/src/app/components/DashboardTrafficChart.tsx +++ b/src/app/components/DashboardTrafficChart.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useEffect, useRef } from "react"; +import React, { useEffect, useMemo, useRef } from "react"; import { Chart, LineController, @@ -12,6 +12,7 @@ import { Tooltip, type ChartConfiguration, } from "chart.js"; +import { compressTelemetryPairs } from "./chartUtils"; Chart.register( LineController, @@ -35,17 +36,22 @@ export default function DashboardTrafficChart({ const canvasRef = useRef(null); const chartRef = useRef | null>(null); + const filtered = useMemo( + () => compressTelemetryPairs(labels, values, 50), + [labels, values], + ); + useEffect(() => { if (!canvasRef.current) return; const config: ChartConfiguration<"line"> = { type: "line", data: { - labels, + labels: filtered.labels, datasets: [ { label: "NGN/XLM traffic", - data: values, + data: filtered.values, borderColor: "#D9F99D", backgroundColor: "rgba(217, 249, 157, 0.12)", fill: true, diff --git a/src/app/components/RateSparklineCard.tsx b/src/app/components/RateSparklineCard.tsx index 3200c91..4bd44b9 100644 --- a/src/app/components/RateSparklineCard.tsx +++ b/src/app/components/RateSparklineCard.tsx @@ -2,6 +2,7 @@ import React, { useMemo } from "react"; import { Shimmer } from "@/components/skeletons"; +import { compressTelemetryStream } from "./chartUtils"; interface RateSparklineCardProps { currency: string; @@ -64,7 +65,10 @@ const RateSparklineCard: React.FC = ({ loading = false, }) => { const isPositive = trend >= 0; - const displayData = sparklineData; + const displayData = useMemo( + () => compressTelemetryStream(sparklineData, 50), + [sparklineData], + ); const formattedRate = useMemo( () => `${currency} ${rate.toFixed(2)}`, diff --git a/src/app/components/chartUtils.ts b/src/app/components/chartUtils.ts new file mode 100644 index 0000000..bdf3a29 --- /dev/null +++ b/src/app/components/chartUtils.ts @@ -0,0 +1,62 @@ +export function compressTelemetryStream(data: T[], maxPoints = 50): T[] { + if (data.length <= maxPoints) { + return data + } + + if (maxPoints <= 0) { + return [] + } + + const step = Math.ceil(data.length / maxPoints) + const compressed: T[] = [] + + for (let index = 0; index < data.length; index += step) { + compressed.push(data[index]) + } + + const lastPoint = data[data.length - 1] + if (compressed[compressed.length - 1] !== lastPoint) { + compressed.push(lastPoint) + } + + return compressed.slice(0, maxPoints) +} + +export function compressTelemetryPairs( + labels: LabelType[], + values: ValueType[], + maxPoints = 50, +) { + if (labels.length !== values.length) { + const minLength = Math.min(labels.length, values.length) + labels = labels.slice(0, minLength) + values = values.slice(0, minLength) + } + + if (labels.length <= maxPoints) { + return { labels, values } + } + + const step = Math.ceil(labels.length / maxPoints) + const compressedLabels: LabelType[] = [] + const compressedValues: ValueType[] = [] + + for (let index = 0; index < labels.length; index += step) { + compressedLabels.push(labels[index]) + compressedValues.push(values[index]) + } + + const lastIndex = labels.length - 1 + const lastLabel = labels[lastIndex] + const lastValue = values[lastIndex] + + if (compressedLabels[compressedLabels.length - 1] !== lastLabel) { + compressedLabels.push(lastLabel) + compressedValues.push(lastValue) + } + + return { + labels: compressedLabels.slice(0, maxPoints), + values: compressedValues.slice(0, maxPoints), + } +} From f4285a40f8862a4852e91061cb8ae71a1000ea70 Mon Sep 17 00:00:00 2001 From: 69starman Date: Sun, 31 May 2026 04:38:56 +0000 Subject: [PATCH 3/3] chore(ui): lightweight skeleton for deferred Dashboard chart (lazy load) --- src/app/page.jsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/app/page.jsx b/src/app/page.jsx index 2748155..d84e2d5 100644 --- a/src/app/page.jsx +++ b/src/app/page.jsx @@ -21,11 +21,9 @@ const LiveNetworkMap = dynamic(() => import("@/app/components/Map"), { loading: () => , }); -const RateSparklineCard = dynamic( - () => import("./components/RateSparklineCard"), - { - ssr: false, - loading: () => , + loading: () => ( +
+ ), }, ); @@ -71,9 +69,13 @@ const DashboardTrafficChart = dynamic( { ssr: false, loading: () => ( +<<<<<<< HEAD
+======= +
+>>>>>>> e4c4bb0 (chore(ui): lightweight skeleton for deferred Dashboard chart (lazy load)) ), }, );