'use client'; import { FunctionComponent, useState } from 'react'; import { useTranslations } from 'next-intl'; import FormLabel from '@mui/material/FormLabel'; import Stack from '@mui/material/Stack'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import { AppButton } from '@/components/common'; import GenderToggle from '@/components/GenderToggle'; import ConditionChips from '@/components/ConditionChips'; import RelationSelect from '@/components/RelationSelect'; import { digitsOnly } from '@/utils'; import { CONDITION_CODES, RELATION_CODES } from '@/services/patients/constants'; import { ageToBirthDate, birthDateToAge } from '@/services/patients/age'; import type { ConditionCode, CreatePatientInput, Gender, Patient, Relation } from '@/services/patients/types'; const MAX_AGE = 120; export interface PatientFormProps { /** Prefill for edit, or a relation carried from the onboarding relation step. */ initial?: Partial>; /** Show the relation picker (E1 add/edit). Onboarding hides it — A3 already chose the relation. */ showRelation?: boolean; submitLabel: string; submitting?: boolean; onSubmit: (input: CreatePatientInput) => void; onCancel?: () => void; cancelLabel?: string; } // A single full-name field (per the wireframe) maps to the contract's first/last/display. function splitName(fullName: string): Pick { const displayName = fullName.trim(); const parts = displayName.split(/\s+/); const firstName = parts[0] ?? ''; const lastName = parts.slice(1).join(' ') || firstName; return { displayName, firstName, lastName }; } /** * The A4 patient form — full name, age, **required** gender, optional condition chips, and * (for E1) the relation. Reused for create and edit. Gender is required and never defaulted; * age maps to `birthDate`. Strings come from the `onboarding` namespace. * @component PatientForm */ const PatientForm: FunctionComponent = ({ initial, showRelation = false, submitLabel, submitting = false, onSubmit, onCancel, cancelLabel, }) => { const t = useTranslations('onboarding'); const [fullName, setFullName] = useState(initial?.displayName ?? ''); const [age, setAge] = useState(() => { const initialAge = birthDateToAge(initial?.birthDate); return initialAge == null ? '' : String(initialAge); }); const [gender, setGender] = useState(initial?.gender ?? null); const [conditions, setConditions] = useState(initial?.conditions ?? []); const [relation, setRelation] = useState(initial?.relation ?? null); const [nameError, setNameError] = useState(false); const [ageError, setAgeError] = useState(false); const [genderError, setGenderError] = useState(false); const conditionOptions = CONDITION_CODES.map((code) => ({ code, label: t(`condition_${code}`) })); const relationOptions = RELATION_CODES.map((code) => ({ code, label: t(`relation_${code}`) })); const handleSubmit = () => { const name = fullName.trim(); const ageNum = Number(digitsOnly(age)); const nameInvalid = name.length === 0; const ageInvalid = age.trim().length === 0 || !Number.isInteger(ageNum) || ageNum < 0 || ageNum > MAX_AGE; const genderInvalid = gender == null; setNameError(nameInvalid); setAgeError(ageInvalid); setGenderError(genderInvalid); if (nameInvalid || ageInvalid || genderInvalid) return; onSubmit({ ...splitName(name), birthDate: ageToBirthDate(ageNum), gender: gender as Gender, bloodType: null, initialMedicalNotes: null, relation, conditions: conditions as ConditionCode[], }); }; return ( { setFullName(event.target.value); if (nameError) setNameError(false); }} error={nameError} helperText={nameError ? t('name_required') : undefined} fullWidth /> { setAge(digitsOnly(event.target.value).slice(0, 3)); if (ageError) setAgeError(false); }} error={ageError} helperText={ageError ? t('age_invalid') : undefined} slotProps={{ htmlInput: { dir: 'ltr', inputMode: 'numeric', style: { textAlign: 'start' } } }} sx={{ maxWidth: 160 }} /> {t('gender_label')} { setGender(next); if (genderError) setGenderError(false); }} maleLabel={t('gender_male')} femaleLabel={t('gender_female')} error={genderError} ariaLabel={t('gender_label')} /> {genderError ? ( {t('gender_required')} ) : null} {t('conditions_label')} {t('conditions_hint')} {showRelation ? ( {t('relation_title')} setRelation(code as Relation)} /> ) : null} {onCancel ? ( {cancelLabel} ) : null} {submitLabel} ); }; export default PatientForm;