feat(charge-points): add pricing mode for charge points with validation

feat(pricing): implement tariff management with peak, valley, and flat pricing
feat(api): add tariff API for fetching and updating pricing configurations
feat(tariff-schema): create database schema for tariff configuration
feat(pricing-page): create UI for displaying and managing pricing tiers
fix(sidebar): update sidebar to include pricing settings link
This commit is contained in:
2026-03-12 17:23:06 +08:00
parent 2638af3f7f
commit f7ee298060
17 changed files with 2729 additions and 89 deletions

View File

@@ -78,6 +78,7 @@ type EditForm = {
chargePointVendor: string;
chargePointModel: string;
registrationStatus: "Accepted" | "Pending" | "Rejected";
pricingMode: "fixed" | "tou";
feePerKwh: string;
};
@@ -96,6 +97,7 @@ export default function ChargePointDetailPage({ params }: { params: Promise<{ id
chargePointVendor: "",
chargePointModel: "",
registrationStatus: "Pending",
pricingMode: "fixed",
feePerKwh: "0",
});
@@ -121,6 +123,7 @@ export default function ChargePointDetailPage({ params }: { params: Promise<{ id
chargePointVendor: cp.chargePointVendor ?? "",
chargePointModel: cp.chargePointModel ?? "",
registrationStatus: cp.registrationStatus as EditForm["registrationStatus"],
pricingMode: cp.pricingMode,
feePerKwh: String(cp.feePerKwh),
});
setEditOpen(true);
@@ -135,7 +138,8 @@ export default function ChargePointDetailPage({ params }: { params: Promise<{ id
chargePointVendor: editForm.chargePointVendor,
chargePointModel: editForm.chargePointModel,
registrationStatus: editForm.registrationStatus,
feePerKwh: fee,
pricingMode: editForm.pricingMode,
feePerKwh: editForm.pricingMode === "fixed" ? fee : 0,
});
await cpQuery.refetch();
setEditOpen(false);
@@ -309,7 +313,9 @@ export default function ChargePointDetailPage({ params }: { params: Promise<{ id
<div className="flex items-center justify-between gap-4 py-2">
<dt className="shrink-0 text-sm text-muted"></dt>
<dd className="text-sm text-foreground">
{cp.feePerKwh > 0 ? (
{cp.pricingMode === "tou" ? (
<span className="text-accent font-medium"></span>
) : cp.feePerKwh > 0 ? (
<span>
{cp.feePerKwh} /kWh
<span className="ml-1 text-xs text-muted">
@@ -373,7 +379,9 @@ export default function ChargePointDetailPage({ params }: { params: Promise<{ id
<div className="flex items-center justify-between gap-4 py-2">
<dt className="shrink-0 text-sm text-muted"></dt>
<dd className="text-sm text-foreground">
{cp.feePerKwh > 0 ? (
{cp.pricingMode === "tou" ? (
<span className="text-accent font-medium"></span>
) : cp.feePerKwh > 0 ? (
<span>
<span className="font-semibold">¥{(cp.feePerKwh / 100).toFixed(2)}</span>
<span className="ml-1 text-xs text-muted">/kWh</span>
@@ -601,8 +609,33 @@ export default function ChargePointDetailPage({ params }: { params: Promise<{ id
</Select.Popover>
</Select>
</div>
<div className="space-y-1.5">
<Label className="text-sm font-medium"></Label>
<Select
fullWidth
selectedKey={editForm.pricingMode}
onSelectionChange={(key) =>
setEditForm((f) => ({
...f,
pricingMode: String(key) as EditForm["pricingMode"],
}))
}
>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Popover>
<ListBox>
<ListBox.Item id="fixed"></ListBox.Item>
<ListBox.Item id="tou"></ListBox.Item>
</ListBox>
</Select.Popover>
</Select>
</div>
{editForm.pricingMode === "fixed" && (
<TextField fullWidth>
<Label className="text-sm font-medium">/kWh</Label>
<Label className="text-sm font-medium">/kWh</Label>
<Input
type="number"
min="0"
@@ -612,6 +645,7 @@ export default function ChargePointDetailPage({ params }: { params: Promise<{ id
onChange={(e) => setEditForm((f) => ({ ...f, feePerKwh: e.target.value }))}
/>
</TextField>
)}
</Modal.Body>
<Modal.Footer className="flex justify-end gap-2">
<Button variant="ghost" onPress={() => setEditOpen(false)}>