export class APIError extends Error { constructor( public readonly status: number, message: string, ) { super(message); this.name = "APIError"; } } const CSMS_URL = process.env.NEXT_PUBLIC_CSMS_URL ?? "http://localhost:3001"; async function apiFetch(path: string, init?: RequestInit): Promise { const res = await fetch(`${CSMS_URL}${path}`, { ...init, headers: { "Content-Type": "application/json", ...init?.headers, }, credentials: "include", }); if (!res.ok) { const text = await res.text().catch(() => res.statusText); throw new APIError(res.status, `API ${path} failed (${res.status}): ${text}`); } return res.json() as Promise; } // ── Types ────────────────────────────────────────────────────────────────── export type Stats = { totalChargePoints: number; onlineChargePoints: number; activeTransactions: number; totalIdTags: number; todayEnergyWh: number; todayRevenue: number; totalUsers: number; todayTransactions: number; }; export type UserStats = { totalIdTags: number; totalBalance: number; activeTransactions: number; totalTransactions: number; todayEnergyWh: number; todayTransactions: number; }; export type ConnectorSummary = { id: string; connectorId: number; status: string; lastStatusAt: string | null; }; export type ConnectorDetail = { id: string; connectorId: number; status: string; errorCode: string; info: string | null; vendorId: string | null; vendorErrorCode: string | null; lastStatusAt: string; createdAt: string; updatedAt: string; }; export type ChargePoint = { id: string; chargePointIdentifier: string; chargePointVendor: string | null; chargePointModel: string | null; registrationStatus: string; lastHeartbeatAt: string | null; lastBootNotificationAt: string | null; feePerKwh: number; connectors: ConnectorSummary[]; chargePointStatus: string | null; chargePointErrorCode: string | null; }; export type ChargePointDetail = { id: string; chargePointIdentifier: string; chargePointVendor: string | null; chargePointModel: string | null; chargePointSerialNumber: string | null; firmwareVersion: string | null; iccid: string | null; imsi: string | null; meterSerialNumber: string | null; meterType: string | null; registrationStatus: string; heartbeatInterval: number | null; lastHeartbeatAt: string | null; lastBootNotificationAt: string | null; feePerKwh: number; createdAt: string; updatedAt: string; connectors: ConnectorDetail[]; chargePointStatus: string | null; chargePointErrorCode: string | null; }; export type Transaction = { id: number; chargePointIdentifier: string | null; connectorNumber: number | null; idTag: string; idTagStatus: string | null; idTagUserId: string | null; idTagUserName: string | null; startTimestamp: string; stopTimestamp: string | null; startMeterValue: number | null; stopMeterValue: number | null; energyWh: number | null; stopIdTag: string | null; stopReason: string | null; chargeAmount: number | null; }; export type IdTag = { idTag: string; status: string; expiryDate: string | null; parentIdTag: string | null; userId: string | null; balance: number; createdAt: string; }; export type UserRow = { id: string; name: string | null; email: string; emailVerified: boolean; username: string | null; role: string | null; banned: boolean | null; banReason: string | null; createdAt: string; }; export type PaginatedTransactions = { data: Transaction[]; total: number; page: number; totalPages: number; }; // ── API functions ────────────────────────────────────────────────────────── export const api = { stats: { get: () => apiFetch("/api/stats"), }, chargePoints: { list: () => apiFetch("/api/charge-points"), get: (id: string) => apiFetch(`/api/charge-points/${id}`), create: (data: { chargePointIdentifier: string; chargePointVendor?: string; chargePointModel?: string; registrationStatus?: "Accepted" | "Pending" | "Rejected"; feePerKwh?: number; }) => apiFetch("/api/charge-points", { method: "POST", body: JSON.stringify(data), }), update: ( id: string, data: { feePerKwh?: number; registrationStatus?: "Accepted" | "Pending" | "Rejected"; chargePointVendor?: string; chargePointModel?: string; }, ) => apiFetch(`/api/charge-points/${id}`, { method: "PATCH", body: JSON.stringify(data), }), delete: (id: string) => apiFetch<{ success: true }>(`/api/charge-points/${id}`, { method: "DELETE" }), }, transactions: { list: (params?: { page?: number; limit?: number; status?: "active" | "completed"; chargePointId?: string; }) => { const q = new URLSearchParams(); if (params?.page) q.set("page", String(params.page)); if (params?.limit) q.set("limit", String(params.limit)); if (params?.status) q.set("status", params.status); if (params?.chargePointId) q.set("chargePointId", params.chargePointId); const qs = q.toString(); return apiFetch(`/api/transactions${qs ? "?" + qs : ""}`); }, get: (id: number) => apiFetch(`/api/transactions/${id}`), stop: (id: number) => apiFetch(`/api/transactions/${id}/stop`, { method: "POST", }), delete: (id: number) => apiFetch<{ success: true }>(`/api/transactions/${id}`, { method: "DELETE" }), }, idTags: { list: () => apiFetch("/api/id-tags"), get: (idTag: string) => apiFetch(`/api/id-tags/${idTag}`), claim: () => apiFetch("/api/id-tags/claim", { method: "POST" }), create: (data: { idTag: string; status?: string; expiryDate?: string; parentIdTag?: string; userId?: string | null; balance?: number; }) => apiFetch("/api/id-tags", { method: "POST", body: JSON.stringify(data) }), update: ( idTag: string, data: { status?: string; expiryDate?: string | null; parentIdTag?: string | null; userId?: string | null; balance?: number; }, ) => apiFetch(`/api/id-tags/${idTag}`, { method: "PATCH", body: JSON.stringify(data) }), delete: (idTag: string) => apiFetch<{ success: true }>(`/api/id-tags/${idTag}`, { method: "DELETE" }), }, users: { list: () => apiFetch("/api/users"), create: (data: { name: string; email: string; password: string; username?: string; role?: string; }) => apiFetch<{ user: UserRow }>("/api/auth/admin/create-user", { method: "POST", body: JSON.stringify(data), }), update: ( id: string, data: { name?: string; username?: string | null; role?: string; banned?: boolean; banReason?: string | null; }, ) => apiFetch(`/api/users/${id}`, { method: "PATCH", body: JSON.stringify(data) }), }, setup: { create: (data: { name: string; email: string; username: string; password: string }) => apiFetch<{ success: boolean }>("/api/setup", { method: "POST", body: JSON.stringify(data) }), }, };