feat(dashboard): add transactions and users management pages with CRUD functionality
feat(auth): implement login page and authentication middleware feat(sidebar): create sidebar component with user info and navigation links feat(api): establish API client for interacting with backend services
This commit is contained in:
126
apps/web/app/dashboard/page.tsx
Normal file
126
apps/web/app/dashboard/page.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import { Card } from "@heroui/react";
|
||||
import { Thunderbolt, PlugConnection, CreditCard, ChartColumn } from "@gravity-ui/icons";
|
||||
import { api } from "@/lib/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type CardColor = "accent" | "success" | "warning" | "default";
|
||||
|
||||
const colorStyles: Record<CardColor, { border: string; bg: string; icon: string }> = {
|
||||
accent: { border: "border-accent", bg: "bg-accent/10", icon: "text-accent" },
|
||||
success: { border: "border-success", bg: "bg-success/10", icon: "text-success" },
|
||||
warning: { border: "border-warning", bg: "bg-warning/10", icon: "text-warning" },
|
||||
default: { border: "border-border", bg: "bg-default", icon: "text-muted" },
|
||||
};
|
||||
|
||||
function StatusDot({ color }: { color: "success" | "warning" | "muted" }) {
|
||||
const cls =
|
||||
color === "success" ? "bg-success" : color === "warning" ? "bg-warning" : "bg-muted/40";
|
||||
return <span className={`inline-block h-1.5 w-1.5 shrink-0 rounded-full ${cls}`} />;
|
||||
}
|
||||
|
||||
function StatCard({
|
||||
title,
|
||||
value,
|
||||
footer,
|
||||
icon: Icon,
|
||||
color = "default",
|
||||
}: {
|
||||
title: string;
|
||||
value: string | number;
|
||||
footer?: React.ReactNode;
|
||||
icon?: React.ComponentType<{ className?: string }>;
|
||||
color?: CardColor;
|
||||
}) {
|
||||
const s = colorStyles[color];
|
||||
return (
|
||||
<Card className={`border-t-2 ${s.border}`}>
|
||||
<Card.Content className="flex flex-col gap-3">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<p className="text-sm text-muted">{title}</p>
|
||||
{Icon && (
|
||||
<div className={`flex size-9 shrink-0 items-center justify-center rounded-xl ${s.bg}`}>
|
||||
<Icon className={`size-4.5 ${s.icon}`} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-3xl font-bold tabular-nums leading-none text-foreground">{value}</p>
|
||||
{footer && (
|
||||
<div className="flex flex-wrap items-center gap-x-1.5 gap-y-1 text-xs text-muted">
|
||||
{footer}
|
||||
</div>
|
||||
)}
|
||||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const stats = await api.stats.get().catch(() => null);
|
||||
|
||||
const todayKwh = stats ? (stats.todayEnergyWh / 1000).toFixed(1) : "—";
|
||||
const offlineCount = (stats?.totalChargePoints ?? 0) - (stats?.onlineChargePoints ?? 0);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold text-foreground">概览</h1>
|
||||
<p className="mt-0.5 text-sm text-muted">实时运营状态</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-5">
|
||||
<StatCard
|
||||
title="充电桩总数"
|
||||
value={stats?.totalChargePoints ?? "—"}
|
||||
icon={PlugConnection}
|
||||
color="accent"
|
||||
footer={
|
||||
<>
|
||||
<StatusDot color="success" />
|
||||
<span className="font-medium text-success">
|
||||
{stats?.onlineChargePoints ?? 0} 在线
|
||||
</span>
|
||||
<span className="text-border">·</span>
|
||||
<span>{offlineCount} 离线</span>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<StatCard
|
||||
title="在线充电桩"
|
||||
value={stats?.onlineChargePoints ?? "—"}
|
||||
icon={PlugConnection}
|
||||
color="success"
|
||||
footer={<span>最近 2 分钟有心跳</span>}
|
||||
/>
|
||||
<StatCard
|
||||
title="进行中充电"
|
||||
value={stats?.activeTransactions ?? "—"}
|
||||
icon={Thunderbolt}
|
||||
color={stats?.activeTransactions ? "warning" : "default"}
|
||||
footer={
|
||||
<>
|
||||
<StatusDot color={stats?.activeTransactions ? "success" : "muted"} />
|
||||
<span className={stats?.activeTransactions ? "font-medium text-success" : ""}>
|
||||
{stats?.activeTransactions ? "活跃中" : "当前空闲"}
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<StatCard
|
||||
title="储值卡总数"
|
||||
value={stats?.totalIdTags ?? "—"}
|
||||
icon={CreditCard}
|
||||
color="default"
|
||||
footer={<span>已注册卡片总量</span>}
|
||||
/>
|
||||
<StatCard
|
||||
title="今日充电量"
|
||||
value={`${todayKwh} kWh`}
|
||||
icon={ChartColumn}
|
||||
color="accent"
|
||||
footer={<span>当日 00:00 起累计</span>}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user