feat: add onVesselSelected callback to ConfigDrivenSection for handling vessel selection

This commit is contained in:
estifanos
2026-08-11 07:54:06 +00:00
parent 8446b28549
commit 4c30bdf803
2 changed files with 41 additions and 9 deletions

View File

@@ -25,15 +25,23 @@ interface Props {
errors?: Record<string, string>;
/** The applicant's registered vessels, for the vessel-picker field. */
vessels?: Vessel[];
/**
* Fired when the vessel picker's value resolves to a known vessel. Vessel
* Information and Current Ownership are separate form sections, so this
* component (one instance per section) can only fill its own fields —
* the parent uses this to fill the rest via the same `VESSEL_FIELD_FILLERS`.
*/
onVesselSelected?: (vessel: Vessel) => void;
}
/**
* 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.
* Maps a Vessel's own fields onto whichever fields the backend put in the
* license form — 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. Exported so the parent page can apply it
* across every section, not just the one the vessel picker lives in.
*/
const VESSEL_FIELD_FILLERS: {
export const VESSEL_FIELD_FILLERS: {
matches: (label: string, key: string) => boolean;
value: (vessel: Vessel) => unknown;
}[] = [
@@ -53,9 +61,12 @@ const VESSEL_FIELD_FILLERS: {
{ 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 },
// Current Ownership: only the "current" owner, never "new owner" —
// that's who the vessel is being transferred to, not on file anywhere.
{ matches: (l, k) => k === 'currentOwnerName' || k === 'currentOwner' || l.includes('current owner'), value: (v) => v.ownerName },
];
function fillFromVessel(
export function fillFromVessel(
vessel: Vessel,
fields: FormFieldConfig[],
onChange: (key: string, value: unknown) => void,
@@ -82,6 +93,7 @@ export function ConfigDrivenSection({
disabled,
errors = {},
vessels = [],
onVesselSelected,
}: Props) {
const fields = [...(section.fields ?? [])].sort(
(a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0),
@@ -134,7 +146,7 @@ export function ConfigDrivenSection({
// onChange={(v) => {
// onChange(field.key, v);
// const vessel = vessels.find((x) => x.id === v);
// if (vessel) fillFromVessel(vessel, fields, onChange);
// if (vessel) { fillFromVessel(vessel, fields, onChange); onVesselSelected?.(vessel); }
// }}
// searchable
// clearable={!field.required}
@@ -147,7 +159,10 @@ export function ConfigDrivenSection({
const v = e.currentTarget.value;
onChange(field.key, v);
const vessel = vessels.find((x) => x.id === v);
if (vessel) fillFromVessel(vessel, fields, onChange);
if (vessel) {
fillFromVessel(vessel, fields, onChange);
onVesselSelected?.(vessel);
}
}}
/>
) : field.type === 'SELECT' ? (

View File

@@ -50,10 +50,11 @@ import {
type FieldErrors,
type FormFieldConfig,
type ValidationIssue,
type Vessel,
} from '@ema-platform/api';
import { getCountryCode, getCountryName, ModalFooter } from '@ema-platform/ui';
import { useCurrentProfile } from '@ema-platform/auth';
import { ConfigDrivenSection } from '../components/ConfigDrivenSection';
import { ConfigDrivenSection, fillFromVessel } from '../components/ConfigDrivenSection';
import { DocumentSlots } from '../components/DocumentSlots';
import { StaffEvidence } from '../components/StaffEvidence';
@@ -210,6 +211,20 @@ export function LicenseApplicationPage() {
const readOnly = !['DRAFT', 'RESUBMIT_REQUIRED'].includes(application.status);
// Vessel Information and Current Ownership are separate form sections, so
// ConfigDrivenSection (one instance per section) can't fill both itself —
// it reports the pick up here and this fans it out across every section.
function handleVesselSelected(vessel: Vessel) {
for (const section of config?.licenseType.formSchema.sections ?? []) {
fillFromVessel(vessel, section.fields, (key, value) => {
setDraft((prev) => ({
...prev,
[section.key]: { ...(prev[section.key] ?? {}), [key]: value },
}));
});
}
}
async function saveSection(sectionKey: string) {
// During an adjustment round only flagged sections are editable, so don't
// even attempt a write the server would reject.
@@ -459,6 +474,7 @@ export function LicenseApplicationPage() {
section={section}
values={draft[section.key] ?? {}}
formData={draft}
onVesselSelected={handleVesselSelected}
errors={fieldErrors}
vessels={vessels}
disabled={readOnly || locked}
@@ -589,6 +605,7 @@ export function LicenseApplicationPage() {
</Text>
<ConfigDrivenSection
section={section}
onVesselSelected={handleVesselSelected}
values={draft[section.key] ?? {}}
formData={draft}
errors={fieldErrors}