From 22b5eab7694ebedb01949129794e41b4d0c3923a Mon Sep 17 00:00:00 2001
From: ZhenYi <434836402@qq.com>
Date: Thu, 23 Apr 2026 15:44:10 +0800
Subject: [PATCH] fix(admin): restore LineChart component
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
LineChart is used by the dashboard page to render DAU trend charts.
It was accidentally removed during metrics cleanup — restore it.
---
admin/src/components/admin/LineChart.tsx | 169 +++++++++++++++++++++++
1 file changed, 169 insertions(+)
create mode 100644 admin/src/components/admin/LineChart.tsx
diff --git a/admin/src/components/admin/LineChart.tsx b/admin/src/components/admin/LineChart.tsx
new file mode 100644
index 0000000..f50da74
--- /dev/null
+++ b/admin/src/components/admin/LineChart.tsx
@@ -0,0 +1,169 @@
+"use client";
+
+import { format, parseISO } from "date-fns";
+
+interface DataPoint {
+ date: string;
+ value: number;
+}
+
+interface LineChartProps {
+ data: DataPoint[];
+ width?: number;
+ height?: number;
+ color?: string;
+ label: string;
+ unit?: string;
+}
+
+export default function LineChart({
+ data,
+ width = 600,
+ height = 200,
+ color = "#6366f1",
+ label,
+ unit = "",
+}: LineChartProps) {
+ if (data.length === 0) {
+ return (
+
+ 暂无数据
+
+ );
+ }
+
+ const padding = { top: 16, right: 16, bottom: 36, left: 44 };
+ const chartWidth = width - padding.left - padding.right;
+ const chartHeight = height - padding.top - padding.bottom;
+
+ const values = data.map((d) => d.value);
+ const maxValue = Math.max(...values, 1);
+ const minValue = 0;
+
+ function xScale(i: number) {
+ return padding.left + (i / (data.length - 1 || 1)) * chartWidth;
+ }
+
+ function yScale(v: number) {
+ return padding.top + chartHeight - ((v - minValue) / (maxValue - minValue || 1)) * chartHeight;
+ }
+
+ // Build SVG path
+ const pathD = data
+ .map((d, i) => `${i === 0 ? "M" : "L"} ${xScale(i).toFixed(1)} ${yScale(d.value).toFixed(1)}`)
+ .join(" ");
+
+ // Area path (fill under the line)
+ const areaD =
+ pathD +
+ ` L ${xScale(data.length - 1).toFixed(1)} ${(padding.top + chartHeight).toFixed(1)}` +
+ ` L ${padding.left} ${(padding.top + chartHeight).toFixed(1)} Z`;
+
+ // Y-axis ticks (5 ticks)
+ const yTicks = Array.from({ length: 5 }, (_, i) => {
+ const v = minValue + ((maxValue - minValue) * i) / 4;
+ return { v, y: yScale(v) };
+ });
+
+ // X-axis ticks (show every nth label based on data length)
+ const step = Math.max(1, Math.floor(data.length / 6));
+ const xTicks = data.filter((_, i) => i % step === 0 || i === data.length - 1);
+
+ const gradientId = `grad-${label.replace(/\s/g, "-")}`;
+
+ return (
+
+ );
+}