mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-05-18 20:08:19 +00:00
feat(onboarding/phase-5): wire real forms into the setup wizard
Replaces the Phase 2 StepPlaceholder with six dedicated wizard step components, each wrapping the corresponding Phase 3/4 form. The parent setup-wizard.tsx is now a thin dispatcher that owns shell state + markSetupStepComplete wiring; each step owns its own data load, form state, validation, and save action. - src/components/setup/wizard-step-types.ts — shared WizardStepComponentProps shape - src/components/setup/wizard-step-identity.tsx — minimal brand form (hospital name + logo URL) hitting /api/config/theme, links out to /branding for full customisation - src/components/setup/wizard-step-clinics.tsx — ClinicForm + createClinic mutation, always presents an empty "add new" form - src/components/setup/wizard-step-doctors.tsx — DoctorForm with clinic dropdown, blocks with an inline warning when no clinics exist yet - src/components/setup/wizard-step-team.tsx — InviteMemberForm with real roles fetched from getRoles, sends invitations via sendInvitations - src/components/setup/wizard-step-telephony.tsx — loads masked config from /api/config/telephony, validates required Ozonetel fields on save - src/components/setup/wizard-step-ai.tsx — loads AI config, clamps temperature 0..2, doesn't auto-advance (last step, admin taps Finish) - src/pages/setup-wizard.tsx — dispatches to the right step component based on activeStep, passes a WizardStepComponentProps bundle Each step calls onComplete(step) after a successful save, which updates the shared SetupState so the left-nav badges reflect the new status immediately. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
101
src/components/setup/wizard-step-doctors.tsx
Normal file
101
src/components/setup/wizard-step-doctors.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { WizardStep } from './wizard-step';
|
||||
import {
|
||||
DoctorForm,
|
||||
doctorFormToGraphQLInput,
|
||||
emptyDoctorFormValues,
|
||||
type DoctorFormValues,
|
||||
} from '@/components/forms/doctor-form';
|
||||
import { apiClient } from '@/lib/api-client';
|
||||
import { notify } from '@/lib/toast';
|
||||
import type { WizardStepComponentProps } from './wizard-step-types';
|
||||
|
||||
// Doctor step — mirrors the clinics step but also fetches the clinic list
|
||||
// for the DoctorForm's clinic dropdown. If there are no clinics yet we let
|
||||
// the admin know they need to complete step 2 first (the wizard doesn't
|
||||
// force ordering, but a doctor without a clinic is useless).
|
||||
|
||||
type ClinicLite = { id: string; clinicName: string | null };
|
||||
|
||||
export const WizardStepDoctors = (props: WizardStepComponentProps) => {
|
||||
const [values, setValues] = useState<DoctorFormValues>(emptyDoctorFormValues);
|
||||
const [clinics, setClinics] = useState<ClinicLite[]>([]);
|
||||
const [loadingClinics, setLoadingClinics] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
apiClient
|
||||
.graphql<{ clinics: { edges: { node: ClinicLite }[] } }>(
|
||||
`{ clinics(first: 100) { edges { node { id clinicName } } } }`,
|
||||
undefined,
|
||||
{ silent: true },
|
||||
)
|
||||
.then((data) => setClinics(data.clinics.edges.map((e) => e.node)))
|
||||
.catch(() => setClinics([]))
|
||||
.finally(() => setLoadingClinics(false));
|
||||
}, []);
|
||||
|
||||
const clinicOptions = useMemo(
|
||||
() => clinics.map((c) => ({ id: c.id, label: c.clinicName ?? 'Unnamed clinic' })),
|
||||
[clinics],
|
||||
);
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!values.firstName.trim() || !values.lastName.trim()) {
|
||||
notify.error('First and last name are required');
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
try {
|
||||
await apiClient.graphql(
|
||||
`mutation CreateDoctor($data: DoctorCreateInput!) {
|
||||
createDoctor(data: $data) { id }
|
||||
}`,
|
||||
{ data: doctorFormToGraphQLInput(values) },
|
||||
);
|
||||
notify.success('Doctor added', `Dr. ${values.firstName} ${values.lastName}`);
|
||||
await props.onComplete('doctors');
|
||||
setValues(emptyDoctorFormValues());
|
||||
props.onAdvance();
|
||||
} catch (err) {
|
||||
console.error('[wizard/doctors] save failed', err);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<WizardStep
|
||||
step="doctors"
|
||||
isCompleted={props.isCompleted}
|
||||
isLast={props.isLast}
|
||||
onPrev={props.onPrev}
|
||||
onNext={props.onNext}
|
||||
onMarkComplete={handleSave}
|
||||
onFinish={props.onFinish}
|
||||
saving={saving}
|
||||
>
|
||||
{loadingClinics ? (
|
||||
<p className="text-sm text-tertiary">Loading clinics…</p>
|
||||
) : clinics.length === 0 ? (
|
||||
<div className="rounded-lg border border-dashed border-warning bg-warning-primary p-4">
|
||||
<p className="text-sm font-semibold text-warning-primary">Add a clinic first</p>
|
||||
<p className="mt-1 text-xs text-tertiary">
|
||||
You need at least one clinic before you can assign doctors. Go back to the{' '}
|
||||
<b>Clinics</b> step and add a branch first.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{props.isCompleted && (
|
||||
<div className="mb-5 rounded-lg border border-secondary bg-secondary p-4 text-xs text-tertiary">
|
||||
You've already added at least one doctor. Fill the form again to add another, or
|
||||
click <b>Next</b> to continue.
|
||||
</div>
|
||||
)}
|
||||
<DoctorForm value={values} onChange={setValues} clinics={clinicOptions} />
|
||||
</>
|
||||
)}
|
||||
</WizardStep>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user