feat: integrate React Query for data fetching and state management across dashboard components
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { use, useCallback, useEffect, useState } from "react";
|
||||
import { use, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Button,
|
||||
@@ -93,14 +94,8 @@ type EditForm = {
|
||||
export default function ChargePointDetailPage({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = use(params);
|
||||
|
||||
const [cp, setCp] = useState<ChargePointDetail | null>(null);
|
||||
const [notFound, setNotFound] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// transactions
|
||||
const [txData, setTxData] = useState<PaginatedTransactions | null>(null);
|
||||
const [txPage, setTxPage] = useState(1);
|
||||
const [txLoading, setTxLoading] = useState(true);
|
||||
|
||||
// edit modal
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
@@ -112,42 +107,22 @@ export default function ChargePointDetailPage({ params }: { params: Promise<{ id
|
||||
feePerKwh: "0",
|
||||
});
|
||||
|
||||
const loadCp = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await api.chargePoints.get(id);
|
||||
setCp(data);
|
||||
} catch {
|
||||
setNotFound(true);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [id]);
|
||||
const cpQuery = useQuery({
|
||||
queryKey: ["chargePoint", id],
|
||||
queryFn: () => api.chargePoints.get(id),
|
||||
refetchInterval: 3_000,
|
||||
retry: false,
|
||||
});
|
||||
|
||||
const loadTx = useCallback(
|
||||
async (p: number) => {
|
||||
setTxLoading(true);
|
||||
try {
|
||||
const data = await api.transactions.list({
|
||||
page: p,
|
||||
limit: TX_LIMIT,
|
||||
chargePointId: id,
|
||||
});
|
||||
setTxData(data);
|
||||
} finally {
|
||||
setTxLoading(false);
|
||||
}
|
||||
},
|
||||
[id],
|
||||
);
|
||||
const txQuery = useQuery({
|
||||
queryKey: ["chargePointTransactions", id, txPage],
|
||||
queryFn: () =>
|
||||
api.transactions.list({ page: txPage, limit: TX_LIMIT, chargePointId: id }),
|
||||
refetchInterval: 3_000,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
loadCp();
|
||||
}, [loadCp]);
|
||||
|
||||
useEffect(() => {
|
||||
loadTx(txPage);
|
||||
}, [txPage, loadTx]);
|
||||
const cp = cpQuery.data;
|
||||
const txData = txQuery.data;
|
||||
|
||||
const openEdit = () => {
|
||||
if (!cp) return;
|
||||
@@ -165,13 +140,13 @@ export default function ChargePointDetailPage({ params }: { params: Promise<{ id
|
||||
setEditBusy(true);
|
||||
try {
|
||||
const fee = Math.max(0, Math.round(Number(editForm.feePerKwh) || 0));
|
||||
const updated = await api.chargePoints.update(cp.id, {
|
||||
await api.chargePoints.update(cp.id, {
|
||||
chargePointVendor: editForm.chargePointVendor,
|
||||
chargePointModel: editForm.chargePointModel,
|
||||
registrationStatus: editForm.registrationStatus,
|
||||
feePerKwh: fee,
|
||||
});
|
||||
setCp((prev) => (prev ? { ...prev, ...updated, connectors: prev.connectors } : prev));
|
||||
await cpQuery.refetch();
|
||||
setEditOpen(false);
|
||||
} finally {
|
||||
setEditBusy(false);
|
||||
@@ -188,7 +163,7 @@ export default function ChargePointDetailPage({ params }: { params: Promise<{ id
|
||||
|
||||
// ── Render: loading / not found ──────────────────────────────────────────
|
||||
|
||||
if (loading) {
|
||||
if (cpQuery.isPending) {
|
||||
return (
|
||||
<div className="flex h-48 items-center justify-center">
|
||||
<Spinner />
|
||||
@@ -196,7 +171,7 @@ export default function ChargePointDetailPage({ params }: { params: Promise<{ id
|
||||
);
|
||||
}
|
||||
|
||||
if (notFound || !cp) {
|
||||
if (!cp) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Link
|
||||
@@ -463,7 +438,7 @@ export default function ChargePointDetailPage({ params }: { params: Promise<{ id
|
||||
<Table.Body
|
||||
renderEmptyState={() => (
|
||||
<div className="py-8 text-center text-sm text-muted">
|
||||
{txLoading ? "加载中…" : "暂无充电记录"}
|
||||
{txQuery.isPending ? "加载中…" : "暂无充电记录"}
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
Button,
|
||||
Chip,
|
||||
@@ -67,26 +68,17 @@ const EMPTY_FORM: FormData = {
|
||||
};
|
||||
|
||||
export default function ChargePointsPage() {
|
||||
const [chargePoints, setChargePoints] = useState<ChargePoint[]>([]);
|
||||
const [formOpen, setFormOpen] = useState(false);
|
||||
const [formTarget, setFormTarget] = useState<ChargePoint | null>(null);
|
||||
const [formData, setFormData] = useState<FormData>(EMPTY_FORM);
|
||||
const [formBusy, setFormBusy] = useState(false);
|
||||
const [deleteTarget, setDeleteTarget] = useState<ChargePoint | null>(null);
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
const hasFetched = useRef(false);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
const data = await api.chargePoints.list().catch(() => []);
|
||||
setChargePoints(data);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasFetched.current) {
|
||||
hasFetched.current = true;
|
||||
load();
|
||||
}
|
||||
}, [load]);
|
||||
const { data: chargePoints = [], refetch: refetchList } = useQuery({
|
||||
queryKey: ["chargePoints"],
|
||||
queryFn: () => api.chargePoints.list().catch(() => []),
|
||||
refetchInterval: 3_000,
|
||||
});
|
||||
|
||||
const openCreate = () => {
|
||||
setFormTarget(null);
|
||||
@@ -113,28 +105,23 @@ export default function ChargePointsPage() {
|
||||
const fee = Math.max(0, Math.round(Number(formData.feePerKwh) || 0));
|
||||
if (formTarget) {
|
||||
// Edit
|
||||
const updated = await api.chargePoints.update(String(formTarget.id), {
|
||||
await api.chargePoints.update(String(formTarget.id), {
|
||||
chargePointVendor: formData.chargePointVendor,
|
||||
chargePointModel: formData.chargePointModel,
|
||||
registrationStatus: formData.registrationStatus,
|
||||
feePerKwh: fee,
|
||||
});
|
||||
setChargePoints((prev) =>
|
||||
prev.map((cp) =>
|
||||
cp.id === formTarget.id ? { ...updated, connectors: cp.connectors } : cp,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// Create
|
||||
const created = await api.chargePoints.create({
|
||||
await api.chargePoints.create({
|
||||
chargePointIdentifier: formData.chargePointIdentifier.trim(),
|
||||
chargePointVendor: formData.chargePointVendor.trim() || undefined,
|
||||
chargePointModel: formData.chargePointModel.trim() || undefined,
|
||||
registrationStatus: formData.registrationStatus,
|
||||
feePerKwh: fee,
|
||||
});
|
||||
setChargePoints((prev) => [created, ...prev]);
|
||||
}
|
||||
await refetchList();
|
||||
setFormOpen(false);
|
||||
} finally {
|
||||
setFormBusy(false);
|
||||
@@ -146,7 +133,7 @@ export default function ChargePointsPage() {
|
||||
setDeleting(true);
|
||||
try {
|
||||
await api.chargePoints.delete(String(deleteTarget.id));
|
||||
setChargePoints((prev) => prev.filter((cp) => cp.id !== deleteTarget.id));
|
||||
await refetchList();
|
||||
setDeleteTarget(null);
|
||||
} finally {
|
||||
setDeleting(false);
|
||||
|
||||
Reference in New Issue
Block a user