feat(api): add stats chart endpoint for admin access with time series data
feat(dayjs): integrate dayjs for date handling and formatting across the application refactor(routes): update date handling in id-tags, transactions, users, and dashboard routes to use dayjs style(globals): improve CSS variable definitions for better readability and consistency deps: add dayjs as a dependency for date manipulation
This commit is contained in:
@@ -19,6 +19,7 @@ import {
|
||||
import { ArrowLeft, Pencil, PlugConnection, ArrowRotateRight } from "@gravity-ui/icons";
|
||||
import { api } from "@/lib/api";
|
||||
import { useSession } from "@/lib/auth-client";
|
||||
import dayjs from "@/lib/dayjs";
|
||||
|
||||
// ── Status maps ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -60,8 +61,7 @@ const TX_LIMIT = 10;
|
||||
|
||||
function formatDuration(start: string, stop: string | null): string {
|
||||
if (!stop) return "进行中";
|
||||
const ms = new Date(stop).getTime() - new Date(start).getTime();
|
||||
const min = Math.floor(ms / 60000);
|
||||
const min = dayjs(stop).diff(dayjs(start), "minute");
|
||||
if (min < 60) return `${min} 分钟`;
|
||||
const h = Math.floor(min / 60);
|
||||
const m = min % 60;
|
||||
@@ -69,15 +69,7 @@ function formatDuration(start: string, stop: string | null): string {
|
||||
}
|
||||
|
||||
function relativeTime(iso: string): string {
|
||||
const diff = Date.now() - new Date(iso).getTime();
|
||||
const s = Math.floor(diff / 1000);
|
||||
if (s < 60) return `${s} 秒前`;
|
||||
const m = Math.floor(s / 60);
|
||||
if (m < 60) return `${m} 分钟前`;
|
||||
const h = Math.floor(m / 60);
|
||||
if (h < 24) return `${h} 小时前`;
|
||||
const d = Math.floor(h / 24);
|
||||
return `${d} 天前`;
|
||||
return dayjs(iso).fromNow();
|
||||
}
|
||||
|
||||
// ── Edit form type ─────────────────────────────────────────────────────────
|
||||
@@ -155,7 +147,7 @@ export default function ChargePointDetailPage({ params }: { params: Promise<{ id
|
||||
// Online if last heartbeat within 3× interval
|
||||
const isOnline =
|
||||
cp?.lastHeartbeatAt != null &&
|
||||
Date.now() - new Date(cp.lastHeartbeatAt).getTime() < (cp.heartbeatInterval ?? 60) * 3 * 1000;
|
||||
dayjs().diff(dayjs(cp.lastHeartbeatAt), "second") < (cp.heartbeatInterval ?? 60) * 3;
|
||||
|
||||
const { data: sessionData } = useSession();
|
||||
const isAdmin = sessionData?.user?.role === "admin";
|
||||
@@ -343,7 +335,7 @@ export default function ChargePointDetailPage({ params }: { params: Promise<{ id
|
||||
<dt className="shrink-0 text-sm text-muted">最后心跳</dt>
|
||||
<dd className="text-right text-sm text-foreground">
|
||||
{cp.lastHeartbeatAt ? (
|
||||
<span title={new Date(cp.lastHeartbeatAt).toLocaleString("zh-CN")}>
|
||||
<span title={dayjs(cp.lastHeartbeatAt).format("YYYY/M/D HH:mm:ss")}>
|
||||
{relativeTime(cp.lastHeartbeatAt)}
|
||||
</span>
|
||||
) : (
|
||||
@@ -355,7 +347,7 @@ export default function ChargePointDetailPage({ params }: { params: Promise<{ id
|
||||
<dt className="shrink-0 text-sm text-muted">最后启动通知</dt>
|
||||
<dd className="text-right text-sm text-foreground">
|
||||
{cp.lastBootNotificationAt ? (
|
||||
<span title={new Date(cp.lastBootNotificationAt).toLocaleString("zh-CN")}>
|
||||
<span title={dayjs(cp.lastBootNotificationAt).format("YYYY/M/D HH:mm:ss")}>
|
||||
{relativeTime(cp.lastBootNotificationAt)}
|
||||
</span>
|
||||
) : (
|
||||
@@ -366,7 +358,7 @@ export default function ChargePointDetailPage({ params }: { params: Promise<{ id
|
||||
<div className="flex items-center justify-between gap-4 py-2">
|
||||
<dt className="shrink-0 text-sm text-muted">注册时间</dt>
|
||||
<dd className="text-sm text-foreground">
|
||||
{new Date(cp.createdAt).toLocaleDateString("zh-CN")}
|
||||
{dayjs(cp.createdAt).format("YYYY/M/D")}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
@@ -437,12 +429,7 @@ export default function ChargePointDetailPage({ params }: { params: Promise<{ id
|
||||
{conn.info && <p className="text-xs text-muted">{conn.info}</p>}
|
||||
<p className="text-xs text-muted">
|
||||
更新于{" "}
|
||||
{new Date(conn.lastStatusAt).toLocaleString("zh-CN", {
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})}
|
||||
{dayjs(conn.lastStatusAt).format("MM/DD HH:mm")}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
@@ -489,12 +476,7 @@ export default function ChargePointDetailPage({ params }: { params: Promise<{ id
|
||||
</Table.Cell>
|
||||
<Table.Cell className="font-mono">{tx.idTag}</Table.Cell>
|
||||
<Table.Cell className="tabular-nums text-sm">
|
||||
{new Date(tx.startTimestamp).toLocaleString("zh-CN", {
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})}
|
||||
{dayjs(tx.startTimestamp).format("MM/DD HH:mm")}
|
||||
</Table.Cell>
|
||||
<Table.Cell>{formatDuration(tx.startTimestamp, tx.stopTimestamp)}</Table.Cell>
|
||||
<Table.Cell className="tabular-nums">
|
||||
|
||||
@@ -27,6 +27,7 @@ import Link from "next/link";
|
||||
import { ScrollFade } from "@/components/scroll-fade";
|
||||
import { api, type ChargePoint } from "@/lib/api";
|
||||
import { useSession } from "@/lib/auth-client";
|
||||
import dayjs from "@/lib/dayjs";
|
||||
|
||||
const statusLabelMap: Record<string, string> = {
|
||||
Available: "空闲中",
|
||||
@@ -473,7 +474,7 @@ export default function ChargePointsPage() {
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{cp.lastHeartbeatAt ? (
|
||||
new Date(cp.lastHeartbeatAt).toLocaleString("zh-CN")
|
||||
dayjs(cp.lastHeartbeatAt).format("YYYY/M/D HH:mm:ss")
|
||||
) : (
|
||||
<span className="text-muted">—</span>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user