"use client"; import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { Button, Chip, Input, Label, ListBox, Modal, Select, Spinner, Table, TextField, } from "@heroui/react"; import { Plus, Pencil, PlugConnection, TrashBin, ArrowRotateRight } from "@gravity-ui/icons"; import Link from "next/link"; import { api, type ChargePoint } from "@/lib/api"; import { useSession } from "@/lib/auth-client"; const statusLabelMap: Record = { Available: "空闲中", Charging: "充电中", Preparing: "准备中", Finishing: "结束中", SuspendedEV: "EV 暂停", SuspendedEVSE: "EVSE 暂停", Reserved: "已预约", Faulted: "故障", Unavailable: "不可用", Occupied: "占用", }; const statusDotClass: Record = { Available: "bg-success", Charging: "bg-accent animate-pulse", Preparing: "bg-warning animate-pulse", Finishing: "bg-warning", SuspendedEV: "bg-warning", SuspendedEVSE: "bg-warning", Reserved: "bg-warning", Faulted: "bg-danger", Unavailable: "bg-danger", Occupied: "bg-warning", }; const registrationColorMap: Record = { Accepted: "success", Pending: "warning", Rejected: "danger", }; type FormData = { chargePointIdentifier: string; chargePointVendor: string; chargePointModel: string; registrationStatus: "Accepted" | "Pending" | "Rejected"; feePerKwh: string; }; const EMPTY_FORM: FormData = { chargePointIdentifier: "", chargePointVendor: "", chargePointModel: "", registrationStatus: "Pending", feePerKwh: "0", }; export default function ChargePointsPage() { const [formOpen, setFormOpen] = useState(false); const [formTarget, setFormTarget] = useState(null); const [formData, setFormData] = useState(EMPTY_FORM); const [formBusy, setFormBusy] = useState(false); const [deleteTarget, setDeleteTarget] = useState(null); const [deleting, setDeleting] = useState(false); const { data: chargePoints = [], refetch: refetchList, isFetching: refreshing } = useQuery({ queryKey: ["chargePoints"], queryFn: () => api.chargePoints.list().catch(() => []), refetchInterval: 3_000, }); const openCreate = () => { setFormTarget(null); setFormData(EMPTY_FORM); setFormOpen(true); }; const openEdit = (cp: ChargePoint) => { setFormTarget(cp); setFormData({ chargePointIdentifier: cp.chargePointIdentifier, chargePointVendor: cp.chargePointVendor ?? "", chargePointModel: cp.chargePointModel ?? "", registrationStatus: cp.registrationStatus as FormData["registrationStatus"], feePerKwh: String(cp.feePerKwh), }); setFormOpen(true); }; const handleSubmit = async () => { if (!formData.chargePointIdentifier.trim()) return; setFormBusy(true); try { const fee = Math.max(0, Math.round(Number(formData.feePerKwh) || 0)); if (formTarget) { // Edit await api.chargePoints.update(String(formTarget.id), { chargePointVendor: formData.chargePointVendor, chargePointModel: formData.chargePointModel, registrationStatus: formData.registrationStatus, feePerKwh: fee, }); } else { // Create await api.chargePoints.create({ chargePointIdentifier: formData.chargePointIdentifier.trim(), chargePointVendor: formData.chargePointVendor.trim() || undefined, chargePointModel: formData.chargePointModel.trim() || undefined, registrationStatus: formData.registrationStatus, feePerKwh: fee, }); } await refetchList(); setFormOpen(false); } finally { setFormBusy(false); } }; const handleDelete = async () => { if (!deleteTarget) return; setDeleting(true); try { await api.chargePoints.delete(String(deleteTarget.id)); await refetchList(); setDeleteTarget(null); } finally { setDeleting(false); } }; const isEdit = formTarget !== null; const { data: sessionData } = useSession(); const isAdmin = sessionData?.user?.role === "admin"; return (

充电桩管理

共 {chargePoints.length} 台设备

{isAdmin && ( )}
{/* Create / Edit modal — admin only */} {isAdmin && ( <> { if (!formBusy) setFormOpen(open); }} > {isEdit ? "编辑充电桩" : "新建充电桩"} setFormData((f) => ({ ...f, chargePointIdentifier: e.target.value })) } />
setFormData((f) => ({ ...f, chargePointVendor: e.target.value })) } /> setFormData((f) => ({ ...f, chargePointModel: e.target.value })) } />
setFormData((f) => ({ ...f, feePerKwh: e.target.value }))} /> {!isEdit && (

手动创建的充电桩默认注册状态为 Pending,需手动改为 Accepted 后才可正常充电。

)}
)} 标识符 {isAdmin && 品牌 / 型号} {isAdmin && 注册状态} 电价(分/kWh) 最后心跳 接口状态 {isAdmin && {""}} {chargePoints.length === 0 && ( 暂无设备 {isAdmin && {""}} {isAdmin && {""}} {""} {""} {""} {isAdmin && {""}} )} {chargePoints.map((cp) => ( {cp.chargePointIdentifier} {isAdmin && ( {cp.chargePointVendor && cp.chargePointModel ? ( `${cp.chargePointVendor} / ${cp.chargePointModel}` ) : ( )} )} {isAdmin && ( {cp.registrationStatus} )} {cp.feePerKwh > 0 ? ( {cp.feePerKwh} 分 (¥{(cp.feePerKwh / 100).toFixed(2)}/kWh) ) : ( 免费 )} {cp.lastHeartbeatAt ? ( new Date(cp.lastHeartbeatAt).toLocaleString("zh-CN") ) : ( )}
{cp.connectors.length === 0 ? ( ) : ( [...cp.connectors] .sort((a, b) => a.connectorId - b.connectorId) .map((conn) => (
#{conn.connectorId} {statusLabelMap[conn.status] ?? conn.status}
)) )}
{isAdmin && (
确认删除充电桩

将删除充电桩{" "} {cp.chargePointIdentifier} 及其所有连接器和充电记录,此操作不可恢复。

)}
))}
); }