mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 01:48:12 +00:00
fix
This commit is contained in:
@@ -39,6 +39,18 @@ interface DraftField extends CreateFileUploadFieldDto {
|
||||
let draftCounter = 0;
|
||||
const nextKey = () => `draft-${Date.now()}-${++draftCounter}`;
|
||||
|
||||
/**
|
||||
* Extensions offered as checkboxes. Mirrors DOC_EXTENSIONS in the API's
|
||||
* file-upload-settings seeder — the only formats the document flows accept.
|
||||
*
|
||||
* The API validates `allowedExtensions` as plain strings, so free text let
|
||||
* typos ("pd") through silently and the field then rejected every real upload.
|
||||
* A fixed list makes that unrepresentable.
|
||||
*/
|
||||
const FILE_EXTENSION_OPTIONS = ["pdf", "jpg", "jpeg", "png"] as const;
|
||||
|
||||
const KNOWN_EXTENSIONS = new Set<string>(FILE_EXTENSION_OPTIONS);
|
||||
|
||||
function makeEmptyDraft(idx: number): DraftField {
|
||||
return {
|
||||
key: nextKey(),
|
||||
@@ -93,13 +105,19 @@ export default function ManageFileUploadFieldsDialog({
|
||||
}),
|
||||
);
|
||||
|
||||
const updateExtensions = (i: number, raw: string) => {
|
||||
const list = raw
|
||||
.split(",")
|
||||
.map((s) => s.trim().toLowerCase().replace(/^\./, ""))
|
||||
.filter(Boolean);
|
||||
update(i, { allowedExtensions: list });
|
||||
};
|
||||
const toggleExtension = (i: number, ext: string, checked: boolean) =>
|
||||
setFields((prev) =>
|
||||
prev.map((f, idx) => {
|
||||
if (idx !== i) return f;
|
||||
const current = f.allowedExtensions;
|
||||
if (checked) {
|
||||
return current.includes(ext)
|
||||
? f
|
||||
: { ...f, allowedExtensions: [...current, ext] };
|
||||
}
|
||||
return { ...f, allowedExtensions: current.filter((e) => e !== ext) };
|
||||
}),
|
||||
);
|
||||
|
||||
const remove = (i: number) =>
|
||||
setFields((prev) => prev.filter((_, idx) => idx !== i));
|
||||
@@ -214,7 +232,9 @@ export default function ManageFileUploadFieldsDialog({
|
||||
index={i}
|
||||
total={fields.length}
|
||||
onChange={(patch) => update(i, patch)}
|
||||
onChangeExtensions={(raw) => updateExtensions(i, raw)}
|
||||
onToggleExtension={(ext, checked) =>
|
||||
toggleExtension(i, ext, checked)
|
||||
}
|
||||
onMove={(dir) => move(i, dir)}
|
||||
onRemove={() => remove(i)}
|
||||
/>
|
||||
@@ -258,7 +278,7 @@ function FieldEditor({
|
||||
index,
|
||||
total,
|
||||
onChange,
|
||||
onChangeExtensions,
|
||||
onToggleExtension,
|
||||
onMove,
|
||||
onRemove,
|
||||
}: {
|
||||
@@ -266,13 +286,23 @@ function FieldEditor({
|
||||
index: number;
|
||||
total: number;
|
||||
onChange: (patch: Partial<DraftField>) => void;
|
||||
onChangeExtensions: (raw: string) => void;
|
||||
onToggleExtension: (ext: string, checked: boolean) => void;
|
||||
onMove: (dir: -1 | 1) => void;
|
||||
onRemove: () => void;
|
||||
}) {
|
||||
const minFiles = getMinFiles(field);
|
||||
const effectiveMax = field.isMultiple ? field.maxFiles : 1;
|
||||
|
||||
// A field saved before this list existed can hold anything the old free-text
|
||||
// box accepted (e.g. the typo "pd"). Show those alongside the standard ones so
|
||||
// they stay visible and removable instead of silently vanishing on save.
|
||||
const extensionChoices = [
|
||||
...FILE_EXTENSION_OPTIONS,
|
||||
...field.allowedExtensions.filter(
|
||||
(ext) => !KNOWN_EXTENSIONS.has(ext),
|
||||
),
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="rounded-2xl border border-slate-200 bg-white p-4">
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
@@ -348,16 +378,33 @@ function FieldEditor({
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5 md:col-span-2">
|
||||
<Label className="text-xs">Allowed Extensions</Label>
|
||||
<Input
|
||||
value={field.allowedExtensions.join(", ")}
|
||||
onChange={(e) => onChangeExtensions(e.target.value)}
|
||||
placeholder="pdf, docx, jpg"
|
||||
className="font-mono"
|
||||
/>
|
||||
<p className="text-xs text-slate-500">
|
||||
Comma-separated, no leading dot.
|
||||
</p>
|
||||
<Label className="text-xs">Allowed Extensions *</Label>
|
||||
<div className="flex flex-wrap items-center gap-x-4 gap-y-2">
|
||||
{extensionChoices.map((ext) => (
|
||||
<label
|
||||
key={ext}
|
||||
className="flex items-center gap-2 text-sm text-slate-700"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={field.allowedExtensions.includes(ext)}
|
||||
onChange={(e) => onToggleExtension(ext, e.target.checked)}
|
||||
className="h-4 w-4 rounded border-slate-300 text-[#10B981] focus:ring-[#10B981]/20"
|
||||
/>
|
||||
<span className="font-mono">{ext}</span>
|
||||
{!KNOWN_EXTENSIONS.has(ext) ? (
|
||||
<span className="text-xs text-amber-600">(unrecognised)</span>
|
||||
) : null}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
{field.allowedExtensions.length === 0 ? (
|
||||
<p className="text-xs text-red-500">Pick at least one extension.</p>
|
||||
) : (
|
||||
<p className="text-xs text-slate-500">
|
||||
Uploads are rejected unless the file matches one of these.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Freight } from "@edr/types";
|
||||
import type { ColumnFormat, FormFieldDef } from "@/pages/ruleEngine/config/resources";
|
||||
import { EXPORT_TRAIN_OPTIONS, importRunFor } from "@/constants/trainRuns";
|
||||
import { IMPORT_TRAIN_OPTIONS, exportRunFor } from "@/constants/trainRuns";
|
||||
import { vehiclesConfig, VEHICLE_TYPE_OPTIONS, FUEL_TYPE_OPTIONS, VEHICLE_STATUS_OPTIONS } from "./vehicles";
|
||||
import { driversConfig, DRIVER_STATUS_OPTIONS } from "./drivers";
|
||||
|
||||
@@ -299,25 +299,25 @@ export const FLEET_RESOURCES: FleetResourceConfig[] = [
|
||||
],
|
||||
formFields: [
|
||||
// Run numbers are optional — a wagon sits in the fleet unassigned to any
|
||||
// run until an operator picks an export run. The import run is fixed by
|
||||
// run until an operator picks an import run. The export run is fixed by
|
||||
// that choice, so it is derived rather than typed.
|
||||
{
|
||||
name: "exportTrainNumber",
|
||||
label: "Export train number",
|
||||
type: "select",
|
||||
type: "text",
|
||||
description: "Odd — Ethiopia → Djibouti runs",
|
||||
placeholder: "e.g. 8001",
|
||||
options: EXPORT_TRAIN_OPTIONS,
|
||||
derivedValue: (values) => exportRunFor(values.importTrainNumber),
|
||||
// Follows the import run to NULL when that is cleared.
|
||||
clearable: true,
|
||||
},
|
||||
{
|
||||
name: "importTrainNumber",
|
||||
label: "Import train number",
|
||||
type: "text",
|
||||
type: "select",
|
||||
description: "Even — Djibouti → Ethiopia runs",
|
||||
placeholder: "e.g. 8002",
|
||||
derivedValue: (values) => importRunFor(values.exportTrainNumber),
|
||||
// Follows the export run to NULL when that is cleared.
|
||||
options: IMPORT_TRAIN_OPTIONS,
|
||||
clearable: true,
|
||||
},
|
||||
{ name: "wagonNumber", label: "Wagon number", type: "text", required: true },
|
||||
|
||||
Reference in New Issue
Block a user