feat(logistics): add game-vehicles collection and deployed vehicle views
This commit is contained in:
parent
9a5952ec99
commit
d6ba4ccc39
4 changed files with 649 additions and 0 deletions
320
src/app/(frontend)/logistics/game-vehicles/[id]/page.tsx
Normal file
320
src/app/(frontend)/logistics/game-vehicles/[id]/page.tsx
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
import config from "@payload-config";
|
||||
import { getPayload } from "payload";
|
||||
import { notFound } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import type {
|
||||
GameVehicle,
|
||||
Vehicle,
|
||||
GameStructure,
|
||||
Structure,
|
||||
Resource,
|
||||
} from "@/payload-types";
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
FuelIcon,
|
||||
HeartIcon,
|
||||
MapPinIcon,
|
||||
ShieldIcon,
|
||||
TruckIcon,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
Item,
|
||||
ItemContent,
|
||||
ItemDescription,
|
||||
ItemHeader,
|
||||
ItemMedia,
|
||||
ItemTitle,
|
||||
} from "@/components/ui/item";
|
||||
|
||||
interface GameVehicleDetailPageProps {
|
||||
params: Promise<{ id: string }>;
|
||||
}
|
||||
|
||||
const statusColors: Record<string, string> = {
|
||||
idle: "bg-emerald-500/20 text-emerald-400",
|
||||
assigned: "bg-blue-500/20 text-blue-400",
|
||||
in_transit: "bg-amber-500/20 text-amber-400",
|
||||
damaged: "bg-red-500/20 text-red-400",
|
||||
destroyed: "bg-red-500/20 text-red-400",
|
||||
};
|
||||
|
||||
export default async function GameVehicleDetailPage({
|
||||
params,
|
||||
}: GameVehicleDetailPageProps) {
|
||||
const { id } = await params;
|
||||
const payload = await getPayload({ config });
|
||||
|
||||
const vehicle = await payload
|
||||
.findByID({
|
||||
collection: "game-vehicles",
|
||||
id: Number(id),
|
||||
depth: 2,
|
||||
})
|
||||
.catch(() => null);
|
||||
|
||||
if (!vehicle) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const vehicleData = vehicle as unknown as GameVehicle & {
|
||||
type: Vehicle;
|
||||
};
|
||||
const vehicleType =
|
||||
typeof vehicleData.type === "object" ? vehicleData.type : null;
|
||||
const deployedAt =
|
||||
typeof vehicleData.deployedAt === "object" &&
|
||||
vehicleData.deployedAt !== null
|
||||
? (vehicleData.deployedAt as GameStructure & { type: Structure })
|
||||
: null;
|
||||
const faction =
|
||||
typeof vehicleData.faction === "object" && vehicleData.faction !== null
|
||||
? vehicleData.faction
|
||||
: null;
|
||||
|
||||
const fuelPercent = vehicleType?.fuel?.fuelCapacity
|
||||
? (vehicleData.currentFuel / vehicleType.fuel.fuelCapacity) * 100
|
||||
: 0;
|
||||
|
||||
const storedResources = vehicleData.storedResources ?? [];
|
||||
const resourceCount = storedResources.length;
|
||||
|
||||
return (
|
||||
<div className="p-5 flex flex-col gap-6 max-w-7xl">
|
||||
<Link
|
||||
href="/logistics/game-vehicles"
|
||||
className="text-sm text-muted-foreground hover:text-primary transition-colors flex items-center gap-1 w-fit"
|
||||
>
|
||||
<ArrowLeftIcon className="size-3" />
|
||||
Back to Vehicles
|
||||
</Link>
|
||||
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<TruckIcon className="size-5 text-muted-foreground" />
|
||||
<h1 className="text-lg font-semibold">{vehicleData.name}</h1>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={`text-[10px] font-semibold uppercase tracking-wider px-1.5 py-0.5 rounded ${statusColors[vehicleData.status] ?? "bg-muted text-muted-foreground"}`}
|
||||
>
|
||||
{vehicleData.status === "in_transit"
|
||||
? "In Transit"
|
||||
: vehicleData.status.charAt(0).toUpperCase() +
|
||||
vehicleData.status.slice(1)}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{vehicleType?.name ?? "Unknown type"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
<Item variant="muted" size="sm">
|
||||
<ItemMedia variant="icon">
|
||||
<FuelIcon />
|
||||
</ItemMedia>
|
||||
<ItemContent>
|
||||
<ItemTitle>Fuel</ItemTitle>
|
||||
<ItemDescription>
|
||||
{vehicleData.currentFuel?.toFixed(1)} /{" "}
|
||||
{vehicleType?.fuel?.fuelCapacity ?? "?"}
|
||||
</ItemDescription>
|
||||
</ItemContent>
|
||||
</Item>
|
||||
<Item variant="muted" size="sm">
|
||||
<ItemMedia variant="icon">
|
||||
<HeartIcon />
|
||||
</ItemMedia>
|
||||
<ItemContent>
|
||||
<ItemTitle>Health</ItemTitle>
|
||||
<ItemDescription>
|
||||
{vehicleData.currentHealth}
|
||||
</ItemDescription>
|
||||
</ItemContent>
|
||||
</Item>
|
||||
<Item variant="muted" size="sm">
|
||||
<ItemMedia variant="icon">
|
||||
<MapPinIcon />
|
||||
</ItemMedia>
|
||||
<ItemContent>
|
||||
<ItemTitle>Location</ItemTitle>
|
||||
<ItemDescription>
|
||||
{deployedAt?.name ?? "In transit"}
|
||||
</ItemDescription>
|
||||
</ItemContent>
|
||||
</Item>
|
||||
<Item variant="muted" size="sm">
|
||||
<ItemMedia variant="icon">
|
||||
<ShieldIcon />
|
||||
</ItemMedia>
|
||||
<ItemContent>
|
||||
<ItemTitle>Faction</ItemTitle>
|
||||
<ItemDescription>
|
||||
{faction?.name ?? "Unassigned"}
|
||||
</ItemDescription>
|
||||
</ItemContent>
|
||||
</Item>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 xl:grid-cols-3 gap-6">
|
||||
<div className="xl:col-span-2 flex flex-col gap-6">
|
||||
{/* Fuel Gauge */}
|
||||
<Item variant="outline">
|
||||
<ItemHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<FuelIcon className="size-4 text-muted-foreground" />
|
||||
<ItemTitle>Fuel Tank</ItemTitle>
|
||||
</div>
|
||||
</ItemHeader>
|
||||
<ItemContent className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex-1 h-3 bg-border rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full rounded-full transition-all ${
|
||||
fuelPercent > 20 ? "bg-green-500" : "bg-red-500"
|
||||
}`}
|
||||
style={{
|
||||
width: `${Math.min(100, fuelPercent)}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm font-mono font-semibold">
|
||||
{fuelPercent.toFixed(0)}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-xs text-muted-foreground">
|
||||
<span>
|
||||
{vehicleData.currentFuel?.toFixed(1)} units remaining
|
||||
</span>
|
||||
<span>
|
||||
Capacity: {vehicleType?.fuel?.fuelCapacity ?? "?"} units
|
||||
</span>
|
||||
</div>
|
||||
{vehicleType?.fuel?.fuelType &&
|
||||
typeof vehicleType.fuel.fuelType === "object" && (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Fuel type: {vehicleType.fuel.fuelType.name} · Consumption
|
||||
rate: {vehicleType.fuel.fuelConsumptionRate}/unit
|
||||
</div>
|
||||
)}
|
||||
</ItemContent>
|
||||
</Item>
|
||||
|
||||
{/* Cargo */}
|
||||
<Item variant="outline">
|
||||
<ItemHeader>
|
||||
<ItemTitle>Cargo</ItemTitle>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{resourceCount} item type{resourceCount !== 1 ? "s" : ""}
|
||||
</span>
|
||||
</ItemHeader>
|
||||
<ItemContent>
|
||||
{storedResources.length > 0 ? (
|
||||
<div className="border border-border rounded-lg overflow-hidden">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-muted/50 border-b border-border">
|
||||
<th className="text-left px-4 py-2.5 font-semibold uppercase text-xs tracking-wider text-muted-foreground">
|
||||
Resource
|
||||
</th>
|
||||
<th className="text-right px-4 py-2.5 font-semibold uppercase text-xs tracking-wider text-muted-foreground">
|
||||
Amount
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{storedResources.map((entry, index) => {
|
||||
const resource =
|
||||
typeof entry.resource === "object"
|
||||
? (entry.resource as Resource)
|
||||
: null;
|
||||
return (
|
||||
<tr
|
||||
key={index}
|
||||
className="border-b border-border/50"
|
||||
>
|
||||
<td className="px-4 py-2 font-medium">
|
||||
{resource?.name ?? `Resource #${entry.resource}`}
|
||||
</td>
|
||||
<td className="px-4 py-2 text-right font-mono">
|
||||
{entry.amount?.toLocaleString()}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground italic">
|
||||
Vehicle cargo is empty.
|
||||
</p>
|
||||
)}
|
||||
</ItemContent>
|
||||
</Item>
|
||||
</div>
|
||||
|
||||
{/* Sidebar */}
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* Vehicle Type Info */}
|
||||
<Item variant="outline">
|
||||
<ItemHeader>
|
||||
<ItemTitle>Vehicle Type</ItemTitle>
|
||||
</ItemHeader>
|
||||
<ItemContent>
|
||||
<div className="flex flex-col gap-2 text-sm">
|
||||
<div>
|
||||
<p className="font-semibold">
|
||||
{vehicleType?.name ?? "Unknown"}
|
||||
</p>
|
||||
{vehicleType?.classname && (
|
||||
<p className="text-xs text-muted-foreground font-mono">
|
||||
{vehicleType.classname}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{vehicleType?.category &&
|
||||
vehicleType.category.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{vehicleType.category.map((cat) => (
|
||||
<span
|
||||
key={cat}
|
||||
className="text-[10px] font-semibold uppercase tracking-wider px-1.5 py-0.5 rounded bg-muted"
|
||||
>
|
||||
{cat}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ItemContent>
|
||||
</Item>
|
||||
|
||||
{/* Location */}
|
||||
{deployedAt && (
|
||||
<Link href={`/logistics/structures/${deployedAt.id}`}>
|
||||
<Item
|
||||
variant="outline"
|
||||
className="cursor-pointer group transition-all duration-200 hover:scale-[1.01]"
|
||||
>
|
||||
<ItemHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<MapPinIcon className="size-4 text-muted-foreground" />
|
||||
<ItemTitle>Stationed At</ItemTitle>
|
||||
</div>
|
||||
</ItemHeader>
|
||||
<ItemContent>
|
||||
<p className="text-sm font-semibold group-hover:text-primary transition-colors">
|
||||
{deployedAt.name}
|
||||
</p>
|
||||
</ItemContent>
|
||||
</Item>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
137
src/app/(frontend)/logistics/game-vehicles/page.tsx
Normal file
137
src/app/(frontend)/logistics/game-vehicles/page.tsx
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
import config from "@payload-config";
|
||||
import { getPayload } from "payload";
|
||||
import type { GameVehicle, Vehicle } from "@/payload-types";
|
||||
import { TruckIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Item,
|
||||
ItemContent,
|
||||
ItemDescription,
|
||||
ItemHeader,
|
||||
ItemMedia,
|
||||
ItemTitle,
|
||||
} from "@/components/ui/item";
|
||||
|
||||
export const metadata = {
|
||||
title: "Vehicles — Polaris Task Force",
|
||||
};
|
||||
|
||||
export default async function GameVehiclesPage() {
|
||||
const payload = await getPayload({ config });
|
||||
|
||||
const vehiclesRes = await payload.find({
|
||||
collection: "game-vehicles",
|
||||
limit: 500,
|
||||
depth: 2,
|
||||
});
|
||||
|
||||
const vehicles = vehiclesRes.docs as unknown as (GameVehicle & {
|
||||
type: Vehicle;
|
||||
})[];
|
||||
|
||||
const statusColors: Record<string, string> = {
|
||||
idle: "bg-emerald-500/20 text-emerald-400",
|
||||
assigned: "bg-blue-500/20 text-blue-400",
|
||||
in_transit: "bg-amber-500/20 text-amber-400",
|
||||
damaged: "bg-red-500/20 text-red-400",
|
||||
destroyed: "bg-red-500/20 text-red-400",
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-5 flex flex-col gap-6">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<TruckIcon className="size-5 text-muted-foreground" />
|
||||
<h1 className="text-lg font-semibold">Vehicles</h1>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{vehicles.length} vehicle{vehicles.length !== 1 ? "s" : ""} deployed.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{vehicles.length > 0 ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
{vehicles.map((vehicle) => {
|
||||
const vehicleType =
|
||||
typeof vehicle.type === "object" ? vehicle.type : null;
|
||||
const fuelPercent = vehicleType?.fuel?.fuelCapacity
|
||||
? (vehicle.currentFuel / vehicleType.fuel.fuelCapacity) * 100
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={vehicle.id}
|
||||
href={`/logistics/game-vehicles/${vehicle.id}`}
|
||||
>
|
||||
<Item
|
||||
variant="outline"
|
||||
className="cursor-pointer group transition-all duration-200 hover:scale-[1.01] hover:[box-shadow:inset_0_2px_8px_rgba(255,255,255,0.33)]"
|
||||
>
|
||||
<ItemHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<ItemMedia variant="icon">
|
||||
<TruckIcon />
|
||||
</ItemMedia>
|
||||
<ItemTitle className="flex flex-col">
|
||||
<span>{vehicle.name}</span>
|
||||
<span className="text-xs font-normal text-muted-foreground">
|
||||
{vehicleType?.name ?? "Unknown type"}
|
||||
</span>
|
||||
</ItemTitle>
|
||||
</div>
|
||||
<span
|
||||
className={`text-[10px] font-semibold uppercase tracking-wider px-1.5 py-0.5 rounded ${statusColors[vehicle.status] ?? "bg-muted text-muted-foreground"}`}
|
||||
>
|
||||
{vehicle.status === "in_transit"
|
||||
? "In Transit"
|
||||
: vehicle.status.charAt(0).toUpperCase() +
|
||||
vehicle.status.slice(1)}
|
||||
</span>
|
||||
</ItemHeader>
|
||||
<ItemContent>
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex-1 h-1.5 bg-border rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full rounded-full transition-all ${
|
||||
fuelPercent > 20
|
||||
? "bg-green-500"
|
||||
: "bg-red-500"
|
||||
}`}
|
||||
style={{
|
||||
width: `${Math.min(100, fuelPercent)}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-[10px] font-mono text-muted-foreground">
|
||||
{vehicle.currentFuel?.toFixed(0)} /{" "}
|
||||
{vehicleType?.fuel?.fuelCapacity ?? "?"} fuel
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<span>HP: {vehicle.currentHealth}</span>
|
||||
{vehicleType?.fuel?.fuelType &&
|
||||
typeof vehicleType.fuel.fuelType === "object" && (
|
||||
<span>
|
||||
{vehicleType.fuel.fuelType.name}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ItemContent>
|
||||
<ItemDescription>
|
||||
{vehicleType?.name ?? "Unknown type"}
|
||||
</ItemDescription>
|
||||
</Item>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
|
||||
<p className="text-sm">No vehicles deployed.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
154
src/collections/game/GameVehicles.ts
Normal file
154
src/collections/game/GameVehicles.ts
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
import { CollectionConfig } from "payload";
|
||||
import { isDeveloper } from "@/utils/access-control/isRole";
|
||||
|
||||
export const GameVehicles: CollectionConfig = {
|
||||
slug: "game-vehicles",
|
||||
admin: {
|
||||
group: "Game",
|
||||
useAsTitle: "name",
|
||||
},
|
||||
access: {
|
||||
read: ({ req }) => !!req.user,
|
||||
create: isDeveloper,
|
||||
update: isDeveloper,
|
||||
delete: isDeveloper,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "text",
|
||||
required: true,
|
||||
admin: {
|
||||
description: "Display name or callsign for this vehicle instance.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "type",
|
||||
type: "relationship",
|
||||
relationTo: "vehicles",
|
||||
required: true,
|
||||
admin: {
|
||||
description: "The vehicle type/template this instance is based on.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deployedAt",
|
||||
type: "relationship",
|
||||
relationTo: "game-structures",
|
||||
admin: {
|
||||
description: "The structure where this vehicle is currently stationed. Null if in transit.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "faction",
|
||||
type: "relationship",
|
||||
relationTo: "factions",
|
||||
admin: {
|
||||
description: "The faction that owns this vehicle.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "status",
|
||||
type: "select",
|
||||
options: [
|
||||
{ label: "Idle", value: "idle" },
|
||||
{ label: "Assigned", value: "assigned" },
|
||||
{ label: "In Transit", value: "in_transit" },
|
||||
{ label: "Damaged", value: "damaged" },
|
||||
{ label: "Destroyed", value: "destroyed" },
|
||||
],
|
||||
defaultValue: "idle",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "currentFuel",
|
||||
type: "number",
|
||||
required: true,
|
||||
defaultValue: 0,
|
||||
admin: {
|
||||
description: "Current fuel level. Must be between 0 and the vehicle type's fuel capacity.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "currentHealth",
|
||||
type: "number",
|
||||
required: true,
|
||||
admin: {
|
||||
description: "Current health. Must be between 0 and the vehicle type's max health.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "storedResources",
|
||||
type: "array",
|
||||
fields: [
|
||||
{
|
||||
name: "resource",
|
||||
type: "relationship",
|
||||
relationTo: "resources",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "amount",
|
||||
type: "number",
|
||||
required: true,
|
||||
min: 1,
|
||||
},
|
||||
{
|
||||
name: "gridX",
|
||||
type: "number",
|
||||
required: true,
|
||||
min: 0,
|
||||
},
|
||||
{
|
||||
name: "gridY",
|
||||
type: "number",
|
||||
required: true,
|
||||
min: 0,
|
||||
},
|
||||
{
|
||||
name: "rotated",
|
||||
type: "checkbox",
|
||||
defaultValue: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "voidStorage",
|
||||
type: "array",
|
||||
admin: {
|
||||
description: "Items displaced by grid resizing.",
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "resource",
|
||||
type: "relationship",
|
||||
relationTo: "resources",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "amount",
|
||||
type: "number",
|
||||
required: true,
|
||||
min: 1,
|
||||
},
|
||||
{
|
||||
name: "gridX",
|
||||
type: "number",
|
||||
required: true,
|
||||
min: 0,
|
||||
},
|
||||
{
|
||||
name: "gridY",
|
||||
type: "number",
|
||||
required: true,
|
||||
min: 0,
|
||||
},
|
||||
{
|
||||
name: "rotated",
|
||||
type: "checkbox",
|
||||
defaultValue: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -92,6 +92,44 @@ export const Vehicles: CollectionConfig = {
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "fuel",
|
||||
type: "group",
|
||||
admin: {
|
||||
description:
|
||||
"Fuel configuration for this vehicle. Defines what fuel type it uses, how much it can hold, and how fast it consumes fuel.",
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "fuelType",
|
||||
type: "relationship",
|
||||
relationTo: "resources",
|
||||
required: true,
|
||||
admin: {
|
||||
description:
|
||||
"The type of fuel this vehicle consumes (e.g., gasoline, diesel, jet fuel).",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fuelCapacity",
|
||||
type: "number",
|
||||
required: true,
|
||||
admin: {
|
||||
description: "Maximum fuel units this vehicle's tank can hold.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fuelConsumptionRate",
|
||||
type: "number",
|
||||
required: true,
|
||||
defaultValue: 1,
|
||||
admin: {
|
||||
description:
|
||||
"Fuel consumed per unit of distance traveled.",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue