mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-05-18 20:08:19 +00:00
- Setup wizard: 3-pane layout with right-side live previews, resume banner, edit/copy icons on team step, AI prompt configuration - Forms: employee-create replaces invite-member (no email invites), clinic form with address/hours/payment, doctor form with visit slots - Seed script: aligned to current SDK schema — doctors created as workspace members (HelixEngage Manager role), visitingHours replaced by doctorVisitSlot entity, clinics seeded, portalUserId linked dynamically, SUB/ORIGIN/GQL configurable via env vars - Pages: clinics + doctors CRUD updated for new schema, team settings with temp password + role assignment - New components: time-picker, day-selector, wizard-right-panes, wizard-layout-context, resume-setup-banner - Removed: invite-member-form (replaced by employee-create-form per no-email-invites rule) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
143 lines
6.1 KiB
TypeScript
143 lines
6.1 KiB
TypeScript
import { useContext, type ReactNode } from 'react';
|
|
import { createPortal } from 'react-dom';
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
import { faArrowLeft, faArrowRight, faCircleCheck } from '@fortawesome/pro-duotone-svg-icons';
|
|
import { Button } from '@/components/base/buttons/button';
|
|
import { SETUP_STEP_LABELS, type SetupStepName } from '@/lib/setup-state';
|
|
import { WizardLayoutContext } from './wizard-layout-context';
|
|
|
|
type WizardStepProps = {
|
|
step: SetupStepName;
|
|
isCompleted: boolean;
|
|
isLast: boolean;
|
|
onPrev: (() => void) | null;
|
|
onNext: (() => void) | null;
|
|
onMarkComplete: () => void;
|
|
onFinish: () => void;
|
|
saving?: boolean;
|
|
children: ReactNode;
|
|
// Optional content for the wizard shell's right preview pane.
|
|
// Portaled into the shell's <aside> via WizardLayoutContext when
|
|
// both are mounted. Each step component declares this inline so
|
|
// the per-step data fetching stays in one place.
|
|
rightPane?: ReactNode;
|
|
};
|
|
|
|
// Single-step wrapper. The parent picks which step is active and supplies
|
|
// the form content as children. The step provides title, description,
|
|
// "mark complete" CTA, and prev/next/finish navigation. In Phase 5 the
|
|
// children will be real form components from the corresponding settings
|
|
// pages — for now they're placeholders.
|
|
export const WizardStep = ({
|
|
step,
|
|
isCompleted,
|
|
isLast,
|
|
onPrev,
|
|
onNext,
|
|
onMarkComplete,
|
|
onFinish,
|
|
saving = false,
|
|
children,
|
|
rightPane,
|
|
}: WizardStepProps) => {
|
|
const meta = SETUP_STEP_LABELS[step];
|
|
const { rightPaneEl } = useContext(WizardLayoutContext);
|
|
|
|
return (
|
|
<>
|
|
{rightPane && rightPaneEl && createPortal(rightPane, rightPaneEl)}
|
|
<div className="rounded-xl border border-secondary bg-primary p-8 shadow-xs">
|
|
<div className="mb-6 flex items-start justify-between gap-4">
|
|
<div>
|
|
<h2 className="text-2xl font-bold text-primary">{meta.title}</h2>
|
|
<p className="mt-1 text-sm text-tertiary">{meta.description}</p>
|
|
</div>
|
|
{isCompleted && (
|
|
<span className="inline-flex items-center gap-2 rounded-full bg-success-primary px-3 py-1 text-xs font-medium text-success-primary">
|
|
<FontAwesomeIcon icon={faCircleCheck} className="size-3.5" />
|
|
Complete
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
<div className="mb-8">{children}</div>
|
|
|
|
<div className="flex items-center justify-between gap-4 border-t border-secondary pt-6">
|
|
<Button
|
|
color="secondary"
|
|
size="md"
|
|
isDisabled={!onPrev}
|
|
onClick={onPrev ?? undefined}
|
|
iconLeading={({ className }: { className?: string }) => (
|
|
<FontAwesomeIcon icon={faArrowLeft} className={className} />
|
|
)}
|
|
>
|
|
Previous
|
|
</Button>
|
|
|
|
{/* One primary action at the bottom — never two
|
|
competing buttons. Previously the wizard showed
|
|
Mark complete + Next side-by-side, and users
|
|
naturally clicked Next (rightmost = "continue"),
|
|
skipping the save+complete chain entirely. Result
|
|
was every step staying at 0/6.
|
|
|
|
New behaviour: a single button whose label and
|
|
handler depend on completion state.
|
|
- !isCompleted, not last → "Save and continue"
|
|
calls onMarkComplete (which does save +
|
|
complete + advance via the step component's
|
|
handleSave). Forces the agent through the
|
|
completion path.
|
|
- !isCompleted, last → "Save and finish"
|
|
same chain, plus onFinish at the end.
|
|
- isCompleted, not last → "Continue"
|
|
calls onNext (pure navigation).
|
|
- isCompleted, last → "Finish setup"
|
|
calls onFinish.
|
|
|
|
Free-form navigation is still available via the
|
|
left-side step nav, so users can revisit completed
|
|
steps without re-saving. */}
|
|
<div className="flex items-center gap-3">
|
|
{!isCompleted ? (
|
|
<Button
|
|
color="primary"
|
|
size="md"
|
|
isLoading={saving}
|
|
showTextWhileLoading
|
|
onClick={onMarkComplete}
|
|
iconTrailing={
|
|
isLast
|
|
? undefined
|
|
: ({ className }: { className?: string }) => (
|
|
<FontAwesomeIcon icon={faArrowRight} className={className} />
|
|
)
|
|
}
|
|
>
|
|
{isLast ? 'Save and finish' : 'Save and continue'}
|
|
</Button>
|
|
) : isLast ? (
|
|
<Button color="primary" size="md" onClick={onFinish}>
|
|
Finish setup
|
|
</Button>
|
|
) : (
|
|
<Button
|
|
color="primary"
|
|
size="md"
|
|
isDisabled={!onNext}
|
|
onClick={onNext ?? undefined}
|
|
iconTrailing={({ className }: { className?: string }) => (
|
|
<FontAwesomeIcon icon={faArrowRight} className={className} />
|
|
)}
|
|
>
|
|
Continue
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|