mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
feat: add vessel selection support in LicenseApplicationPage and ConfigDrivenSection
This commit is contained in:
@@ -9,7 +9,9 @@ import {
|
||||
import {
|
||||
conditionHolds,
|
||||
localized,
|
||||
type FormFieldConfig,
|
||||
type FormSectionConfig,
|
||||
type Vessel,
|
||||
} from '@ema-platform/api';
|
||||
import { CountrySelect } from '@ema-platform/ui';
|
||||
|
||||
@@ -21,6 +23,48 @@ interface Props {
|
||||
disabled?: boolean;
|
||||
/** Keyed `${sectionKey}.${fieldKey}` — shown under the offending field. */
|
||||
errors?: Record<string, string>;
|
||||
/** The applicant's registered vessels, for the vessel-picker field. */
|
||||
vessels?: Vessel[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a Vessel's own fields onto whichever sibling fields the backend put
|
||||
* in the same section — same dual key/label matching the nationality prefill
|
||||
* in LicenseApplicationPage uses, so this doesn't depend on exact field-key
|
||||
* naming in the seeded formSchema.
|
||||
*/
|
||||
const VESSEL_FIELD_FILLERS: {
|
||||
matches: (label: string, key: string) => boolean;
|
||||
value: (vessel: Vessel) => unknown;
|
||||
}[] = [
|
||||
{ matches: (l, k) => k === 'registrationNumber' || l.includes('registration number'), value: (v) => v.registrationNumber },
|
||||
{ matches: (l, k) => k === 'vesselName' || l.includes('vessel name'), value: (v) => v.name },
|
||||
{ matches: (l, k) => k === 'category' || k === 'vesselCategory' || l.includes('vessel category'), value: (v) => v.category },
|
||||
{ matches: (l, k) => k === 'vesselType' || l.includes('vessel type'), value: (v) => v.vesselType },
|
||||
{ matches: (l, k) => k === 'imoNumber' || l.includes('imo'), value: (v) => v.imoNumber },
|
||||
{ matches: (l, k) => k === 'hullNumber' || l.includes('hull number'), value: (v) => v.hullNumber },
|
||||
{ matches: (l, k) => k === 'flagState' || l.includes('flag state') || l.includes('flag'), value: (v) => v.flagState },
|
||||
{ matches: (l, k) => k === 'portOfRegistry' || l.includes('port of registry'), value: (v) => v.portOfRegistry },
|
||||
{ matches: (l, k) => k === 'grossTonnage' || l.includes('gross tonnage'), value: (v) => v.grossTonnage },
|
||||
{ matches: (l, k) => k === 'passengerCapacity' || l.includes('passenger capacity'), value: (v) => v.passengerCapacity },
|
||||
{ matches: (l, k) => k === 'lengthMeters' || l.includes('length'), value: (v) => v.lengthMeters },
|
||||
{ matches: (l, k) => k === 'yearBuilt' || l.includes('year built'), value: (v) => v.yearBuilt },
|
||||
{ matches: (l, k) => k === 'engineType' || l.includes('engine type'), value: (v) => v.engineType },
|
||||
{ matches: (l, k) => k === 'enginePowerKw' || l.includes('engine power'), value: (v) => v.enginePowerKw },
|
||||
{ matches: (l, k) => k === 'numberOfEngines' || l.includes('number of engines'), value: (v) => v.numberOfEngines },
|
||||
{ matches: (l, k) => k === 'hullMaterial' || l.includes('hull material'), value: (v) => v.hullMaterial },
|
||||
];
|
||||
|
||||
function fillFromVessel(
|
||||
vessel: Vessel,
|
||||
fields: FormFieldConfig[],
|
||||
onChange: (key: string, value: unknown) => void,
|
||||
) {
|
||||
for (const f of fields) {
|
||||
const label = localized(f.label).toLowerCase();
|
||||
const filler = VESSEL_FIELD_FILLERS.find((m) => m.matches(label, f.key));
|
||||
if (filler) onChange(f.key, filler.value(vessel) ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,6 +81,7 @@ export function ConfigDrivenSection({
|
||||
onChange,
|
||||
disabled,
|
||||
errors = {},
|
||||
vessels = [],
|
||||
}: Props) {
|
||||
const fields = [...(section.fields ?? [])].sort(
|
||||
(a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0),
|
||||
@@ -62,6 +107,10 @@ export function ConfigDrivenSection({
|
||||
// Same field the profile Address tab collects — give it the same
|
||||
// searchable, flag-labeled picker instead of a plain option list.
|
||||
const isNationality = field.key === 'nationality' || label.toLowerCase().includes('nationality');
|
||||
// Options here can't be seeded statically — they're the applicant's
|
||||
// own vessel register, so this overrides whatever type the backend
|
||||
// configured, the same way nationality overrides SELECT above.
|
||||
const isVesselPicker = field.key === 'vesselId' || field.key === 'vessel' || /^(select\s+)?vessel$/i.test(label.trim());
|
||||
|
||||
return (
|
||||
<Grid.Col span={{ base: 12, md: span }} key={field.key}>
|
||||
@@ -72,6 +121,23 @@ export function ConfigDrivenSection({
|
||||
value={(value as string) ?? null}
|
||||
onChange={(v) => onChange(field.key, v)}
|
||||
/>
|
||||
) : isVesselPicker ? (
|
||||
<Select
|
||||
{...common}
|
||||
placeholder="Select a registered vessel"
|
||||
data={vessels.map((v) => ({ value: v.id, label: `${v.name} — ${v.registrationNumber}` }))}
|
||||
value={(value as string) ?? null}
|
||||
onChange={(v) => {
|
||||
onChange(field.key, v);
|
||||
const vessel = vessels.find((x) => x.id === v);
|
||||
// Selecting a different vessel replaces whatever the last
|
||||
// selection filled in — unlike the nationality/ID prefill,
|
||||
// this is a live user choice, not a one-time background fill.
|
||||
if (vessel) fillFromVessel(vessel, fields, onChange);
|
||||
}}
|
||||
searchable
|
||||
clearable={!field.required}
|
||||
/>
|
||||
) : field.type === 'SELECT' ? (
|
||||
<Select
|
||||
{...common}
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
useGetApplicationQuery,
|
||||
useGetAttachmentsQuery,
|
||||
useGetLicenseTypeRequirementsQuery,
|
||||
useGetMyVesselsQuery,
|
||||
usePatchSectionMutation,
|
||||
useRemoveStaffMutation,
|
||||
useResolveRemarkMutation,
|
||||
@@ -68,6 +69,9 @@ export function LicenseApplicationPage() {
|
||||
const { data: config, isLoading: loadingConfig } =
|
||||
useGetLicenseTypeRequirementsQuery({ idOrKey: typeCode });
|
||||
const { profile } = useCurrentProfile();
|
||||
// Only the vessel-select field (ConfigDrivenSection) reads this — fetched
|
||||
// here rather than deeper down since it's the shared source of draft state.
|
||||
const { data: vessels } = useGetMyVesselsQuery();
|
||||
const [createApplication] = useCreateApplicationMutation();
|
||||
const [appId, setAppId] = useState<string | undefined>(applicationId);
|
||||
|
||||
@@ -456,6 +460,7 @@ export function LicenseApplicationPage() {
|
||||
values={draft[section.key] ?? {}}
|
||||
formData={draft}
|
||||
errors={fieldErrors}
|
||||
vessels={vessels}
|
||||
disabled={readOnly || locked}
|
||||
onChange={(key, value) => {
|
||||
setDraft((prev) => ({
|
||||
@@ -587,6 +592,7 @@ export function LicenseApplicationPage() {
|
||||
values={draft[section.key] ?? {}}
|
||||
formData={draft}
|
||||
errors={fieldErrors}
|
||||
vessels={vessels}
|
||||
disabled={readOnly}
|
||||
onChange={(key, value) => {
|
||||
setDraft((prev) => ({
|
||||
|
||||
Reference in New Issue
Block a user