mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
feat(fleet): validate vehicle plate numbers against a letters-and-digits format
Plate, power-plate and trailer accepted any free text — a vehicle could be
saved with a plate of "assadasd". They must be letters, a hyphen, then digits,
like ET-9875 or AA-8642.
The server now enforces it on CreateVehicleDto (and UpdateVehicleDto via
PartialType): each plate is trimmed and upper-cased, then matched against
^[A-Z]{2,3}-\d{2,6}$, so "et-9875" is accepted and stored as ET-9875 while an
empty optional trailer/power plate still passes.
The fleet form gains the same check inline: FleetFormFieldDef takes an optional
pattern, the dialog tests it on submit against the upper-cased value, and the
vehicle config points plate and trailer at a regex that mirrors the server's.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -249,6 +249,16 @@ const FleetFormDialog = ({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format check (e.g. plate numbers). Skipped for an empty optional field —
|
||||
// "required" above already owns the empty case. Upper-cased to match the
|
||||
// server, which stores plates upper-case.
|
||||
if (field.pattern && stringValue && stringValue !== FLEET_SELECT_NONE) {
|
||||
const candidate = field.pattern.uppercase === false ? stringValue : stringValue.toUpperCase();
|
||||
if (!field.pattern.regex.test(candidate)) {
|
||||
next[field.name] = field.pattern.message;
|
||||
}
|
||||
}
|
||||
});
|
||||
setErrors(next);
|
||||
return Object.keys(next).length === 0;
|
||||
|
||||
@@ -69,6 +69,11 @@ export interface FleetFormFieldDef extends FormFieldDef {
|
||||
* (e.g. a license expiry); "past" (default) = cannot be in the future.
|
||||
*/
|
||||
dateBound?: "past" | "future";
|
||||
/**
|
||||
* Format the value must match, checked on submit. The value is upper-cased and
|
||||
* trimmed before the test, matching the server. Empty optional fields skip it.
|
||||
*/
|
||||
pattern?: { regex: RegExp; message: string; uppercase?: boolean };
|
||||
}
|
||||
|
||||
export interface FleetListFilterDef {
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
import type { FleetResourceConfig } from "./resources";
|
||||
|
||||
/**
|
||||
* A plate is two or three letters, a hyphen, then two to six digits — ET-9875,
|
||||
* AA-8642. Mirrors VEHICLE_PLATE_REGEX on the API so the form and the server
|
||||
* agree on what a plate looks like.
|
||||
*/
|
||||
const PLATE_PATTERN = {
|
||||
regex: /^[A-Z]{2,3}-\d{2,6}$/,
|
||||
message: "Use letters and numbers like ET-9875 or AA-8642",
|
||||
};
|
||||
|
||||
const VEHICLE_TYPE_OPTIONS = [
|
||||
{ label: "Truck", value: "TRUCK" },
|
||||
{ label: "Van", value: "VAN" },
|
||||
@@ -77,9 +87,9 @@ export const vehiclesConfig: FleetResourceConfig = {
|
||||
],
|
||||
formFields: [
|
||||
{ name: "code", label: "Code", type: "text" },
|
||||
{ name: "plateNumber", label: "Power Plate No", type: "text", required: true },
|
||||
{ name: "plateNumber", label: "Power Plate No", type: "text", required: true, pattern: PLATE_PATTERN },
|
||||
// { name: "powerPlateNo", label: "Power Plate No", type: "text" },
|
||||
{ name: "trailerPlateNo", label: "Trailer Plate No", type: "text" },
|
||||
{ name: "trailerPlateNo", label: "Trailer Plate No", type: "text", pattern: PLATE_PATTERN },
|
||||
{ name: "vehicleType", label: "Vehicle Type", type: "select", required: true, options: VEHICLE_TYPE_OPTIONS },
|
||||
{ name: "manufacturer", label: "Manufacturer", type: "text", required: true },
|
||||
{ name: "model", label: "Model", type: "text", required: true },
|
||||
|
||||
Reference in New Issue
Block a user