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>; errors?: Record<string, string>;
/** The applicant's registered vessels, for the vessel-picker field. */ /** The applicant's registered vessels, for the vessel-picker field. */
vessels?: Vessel[]; 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 * Maps a Vessel's own fields onto whichever fields the backend put in the
* in the same section — same dual key/label matching the nationality prefill * license form — same dual key/label matching the nationality prefill in
* in LicenseApplicationPage uses, so this doesn't depend on exact field-key * LicenseApplicationPage uses, so this doesn't depend on exact field-key
* naming in the seeded formSchema. * 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; matches: (label: string, key: string) => boolean;
value: (vessel: Vessel) => unknown; 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 === '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 === 'numberOfEngines' || l.includes('number of engines'), value: (v) => v.numberOfEngines },
{ matches: (l, k) => k === 'hullMaterial' || l.includes('hull material'), value: (v) => v.hullMaterial }, { 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, vessel: Vessel,
fields: FormFieldConfig[], fields: FormFieldConfig[],
onChange: (key: string, value: unknown) => void, onChange: (key: string, value: unknown) => void,
@@ -82,6 +93,7 @@ export function ConfigDrivenSection({
disabled, disabled,
errors = {}, errors = {},
vessels = [], vessels = [],
onVesselSelected,
}: Props) { }: Props) {
const fields = [...(section.fields ?? [])].sort( const fields = [...(section.fields ?? [])].sort(
(a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0), (a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0),
@@ -134,7 +146,7 @@ export function ConfigDrivenSection({
// onChange={(v) => { // onChange={(v) => {
// onChange(field.key, v); // onChange(field.key, v);
// const vessel = vessels.find((x) => x.id === v); // const vessel = vessels.find((x) => x.id === v);
// if (vessel) fillFromVessel(vessel, fields, onChange); // if (vessel) { fillFromVessel(vessel, fields, onChange); onVesselSelected?.(vessel); }
// }} // }}
// searchable // searchable
// clearable={!field.required} // clearable={!field.required}
@@ -147,7 +159,10 @@ export function ConfigDrivenSection({
const v = e.currentTarget.value; const v = e.currentTarget.value;
onChange(field.key, v); onChange(field.key, v);
const vessel = vessels.find((x) => x.id === 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' ? ( ) : field.type === 'SELECT' ? (

View File

@@ -50,10 +50,11 @@ import {
type FieldErrors, type FieldErrors,
type FormFieldConfig, type FormFieldConfig,
type ValidationIssue, type ValidationIssue,
type Vessel,
} from '@ema-platform/api'; } from '@ema-platform/api';
import { getCountryCode, getCountryName, ModalFooter } from '@ema-platform/ui'; import { getCountryCode, getCountryName, ModalFooter } from '@ema-platform/ui';
import { useCurrentProfile } from '@ema-platform/auth'; import { useCurrentProfile } from '@ema-platform/auth';
import { ConfigDrivenSection } from '../components/ConfigDrivenSection'; import { ConfigDrivenSection, fillFromVessel } from '../components/ConfigDrivenSection';
import { DocumentSlots } from '../components/DocumentSlots'; import { DocumentSlots } from '../components/DocumentSlots';
import { StaffEvidence } from '../components/StaffEvidence'; import { StaffEvidence } from '../components/StaffEvidence';
@@ -210,6 +211,20 @@ export function LicenseApplicationPage() {
const readOnly = !['DRAFT', 'RESUBMIT_REQUIRED'].includes(application.status); 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) { async function saveSection(sectionKey: string) {
// During an adjustment round only flagged sections are editable, so don't // During an adjustment round only flagged sections are editable, so don't
// even attempt a write the server would reject. // even attempt a write the server would reject.
@@ -459,6 +474,7 @@ export function LicenseApplicationPage() {
section={section} section={section}
values={draft[section.key] ?? {}} values={draft[section.key] ?? {}}
formData={draft} formData={draft}
onVesselSelected={handleVesselSelected}
errors={fieldErrors} errors={fieldErrors}
vessels={vessels} vessels={vessels}
disabled={readOnly || locked} disabled={readOnly || locked}
@@ -589,6 +605,7 @@ export function LicenseApplicationPage() {
</Text> </Text>
<ConfigDrivenSection <ConfigDrivenSection
section={section} section={section}
onVesselSelected={handleVesselSelected}
values={draft[section.key] ?? {}} values={draft[section.key] ?? {}}
formData={draft} formData={draft}
errors={fieldErrors} errors={fieldErrors}