feat(csms, web): add charge point status and error code to charge point details, hide the connector 0 from connectors view
This commit is contained in:
@@ -17,15 +17,25 @@ app.get("/", async (c) => {
|
||||
.where(isAdmin ? undefined : eq(chargePoint.registrationStatus, "Accepted"))
|
||||
.orderBy(desc(chargePoint.createdAt));
|
||||
|
||||
// Attach connectors (connectorId > 0 only, excludes the main-controller row)
|
||||
const connectors = cps.length
|
||||
? await db
|
||||
if (!cps.length) return c.json([]);
|
||||
|
||||
const cpIdList = sql.raw(`array[${cps.map((cp) => `'${cp.id}'`).join(",")}]`);
|
||||
|
||||
// connectorId > 0: real connectors to display
|
||||
const connectors = await db
|
||||
.select()
|
||||
.from(connector)
|
||||
.where(
|
||||
sql`${connector.chargePointId} = any(${sql.raw(`array[${cps.map((cp) => `'${cp.id}'`).join(",")}]`)}) and ${connector.connectorId} > 0`,
|
||||
)
|
||||
: [];
|
||||
.where(sql`${connector.chargePointId} = any(${cpIdList}) and ${connector.connectorId} > 0`);
|
||||
|
||||
// connectorId = 0: whole-station status row
|
||||
const cpStatusRows = await db
|
||||
.select({
|
||||
chargePointId: connector.chargePointId,
|
||||
status: connector.status,
|
||||
errorCode: connector.errorCode,
|
||||
})
|
||||
.from(connector)
|
||||
.where(sql`${connector.chargePointId} = any(${cpIdList}) and ${connector.connectorId} = 0`);
|
||||
|
||||
const connectorsByCP: Record<string, typeof connectors> = {};
|
||||
for (const conn of connectors) {
|
||||
@@ -33,10 +43,17 @@ app.get("/", async (c) => {
|
||||
connectorsByCP[conn.chargePointId].push(conn);
|
||||
}
|
||||
|
||||
const cpStatusByCP: Record<string, { status: string; errorCode: string }> = {};
|
||||
for (const row of cpStatusRows) {
|
||||
cpStatusByCP[row.chargePointId] = { status: row.status, errorCode: row.errorCode };
|
||||
}
|
||||
|
||||
return c.json(
|
||||
cps.map((cp) => ({
|
||||
...cp,
|
||||
connectors: connectorsByCP[cp.id] ?? [],
|
||||
chargePointStatus: cpStatusByCP[cp.id]?.status ?? null,
|
||||
chargePointErrorCode: cpStatusByCP[cp.id]?.errorCode ?? null,
|
||||
})),
|
||||
);
|
||||
});
|
||||
@@ -88,9 +105,16 @@ app.get("/:id", async (c) => {
|
||||
|
||||
if (!cp) return c.json({ error: "Not found" }, 404);
|
||||
|
||||
const connectors = await db.select().from(connector).where(eq(connector.chargePointId, id));
|
||||
const allConnectors = await db.select().from(connector).where(eq(connector.chargePointId, id));
|
||||
const cpStatus = allConnectors.find((conn) => conn.connectorId === 0);
|
||||
const displayConnectors = allConnectors.filter((conn) => conn.connectorId > 0);
|
||||
|
||||
return c.json({ ...cp, connectors });
|
||||
return c.json({
|
||||
...cp,
|
||||
connectors: displayConnectors,
|
||||
chargePointStatus: cpStatus?.status ?? null,
|
||||
chargePointErrorCode: cpStatus?.errorCode ?? null,
|
||||
});
|
||||
});
|
||||
|
||||
/** PATCH /api/charge-points/:id — update charge point fields */
|
||||
@@ -136,8 +160,16 @@ app.patch("/:id", async (c) => {
|
||||
|
||||
if (!updated) return c.json({ error: "Not found" }, 404);
|
||||
|
||||
const connectors = await db.select().from(connector).where(eq(connector.chargePointId, id));
|
||||
return c.json({ ...updated, connectors });
|
||||
const allConnectors = await db.select().from(connector).where(eq(connector.chargePointId, id));
|
||||
const cpStatus = allConnectors.find((conn) => conn.connectorId === 0);
|
||||
const displayConnectors = allConnectors.filter((conn) => conn.connectorId > 0);
|
||||
|
||||
return c.json({
|
||||
...updated,
|
||||
connectors: displayConnectors,
|
||||
chargePointStatus: cpStatus?.status ?? null,
|
||||
chargePointErrorCode: cpStatus?.errorCode ?? null,
|
||||
});
|
||||
});
|
||||
|
||||
/** DELETE /api/charge-points/:id — delete a charge point (cascades to connectors, transactions, meter values) */
|
||||
|
||||
@@ -240,6 +240,35 @@ export default function ChargePointDetailPage({ params }: { params: Promise<{ id
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Info grid */}
|
||||
{cp.chargePointStatus && (
|
||||
<div
|
||||
className={`flex items-center gap-3 rounded-xl border px-4 py-3 ${
|
||||
cp.chargePointStatus === "Available"
|
||||
? "border-success/30 bg-success/5"
|
||||
: cp.chargePointStatus === "Faulted" || cp.chargePointStatus === "Unavailable"
|
||||
? "border-danger/30 bg-danger/5"
|
||||
: "border-warning/30 bg-warning/5"
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`size-2.5 shrink-0 rounded-full ${statusDotClass[cp.chargePointStatus] ?? "bg-warning"}`}
|
||||
/>
|
||||
<span className="text-sm font-semibold text-foreground">
|
||||
{cp.chargePointStatus === "Available"
|
||||
? "正常"
|
||||
: (statusLabelMap[cp.chargePointStatus] ?? cp.chargePointStatus)}
|
||||
</span>
|
||||
{cp.chargePointErrorCode && cp.chargePointErrorCode !== "NoError" && (
|
||||
<>
|
||||
<span className="text-muted">·</span>
|
||||
<span className="text-xs text-danger">{cp.chargePointErrorCode}</span>
|
||||
</>
|
||||
)}
|
||||
<span className="ml-auto text-xs text-muted">整桩状态</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Info grid */}
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{/* Device info — admin only */}
|
||||
|
||||
@@ -282,8 +282,9 @@ export default function ChargePointsPage() {
|
||||
{isAdmin && <Table.Column>品牌 / 型号</Table.Column>}
|
||||
{isAdmin && <Table.Column>注册状态</Table.Column>}
|
||||
<Table.Column>电价(分/kWh)</Table.Column>
|
||||
<Table.Column>最后心跳</Table.Column>
|
||||
<Table.Column>接口状态</Table.Column>
|
||||
<Table.Column>最后发现</Table.Column>
|
||||
<Table.Column>状态</Table.Column>
|
||||
<Table.Column>接口</Table.Column>
|
||||
{isAdmin && <Table.Column>{""}</Table.Column>}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
@@ -297,6 +298,7 @@ export default function ChargePointsPage() {
|
||||
<Table.Cell>{""}</Table.Cell>
|
||||
<Table.Cell>{""}</Table.Cell>
|
||||
<Table.Cell>{""}</Table.Cell>
|
||||
<Table.Cell>{""}</Table.Cell>
|
||||
{isAdmin && <Table.Cell>{""}</Table.Cell>}
|
||||
</Table.Row>
|
||||
)}
|
||||
@@ -349,6 +351,22 @@ export default function ChargePointsPage() {
|
||||
<span className="text-muted">—</span>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{cp.chargePointStatus ? (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span
|
||||
className={`size-1.5 shrink-0 rounded-full ${statusDotClass[cp.chargePointStatus] ?? "bg-warning"}`}
|
||||
/>
|
||||
<span className="text-sm text-foreground text-nowrap">
|
||||
{cp.chargePointStatus === "Available"
|
||||
? "正常"
|
||||
: (statusLabelMap[cp.chargePointStatus] ?? cp.chargePointStatus)}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-muted">—</span>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{cp.connectors.length === 0 ? (
|
||||
|
||||
@@ -68,6 +68,8 @@ export type ChargePoint = {
|
||||
lastBootNotificationAt: string | null;
|
||||
feePerKwh: number;
|
||||
connectors: ConnectorSummary[];
|
||||
chargePointStatus: string | null;
|
||||
chargePointErrorCode: string | null;
|
||||
};
|
||||
|
||||
export type ChargePointDetail = {
|
||||
@@ -89,6 +91,8 @@ export type ChargePointDetail = {
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
connectors: ConnectorDetail[];
|
||||
chargePointStatus: string | null;
|
||||
chargePointErrorCode: string | null;
|
||||
};
|
||||
|
||||
export type Transaction = {
|
||||
|
||||
Reference in New Issue
Block a user