feat(onboarding/phase-6): setup wizard polish, seed script alignment, doctor visit slots

- 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>
This commit is contained in:
2026-04-10 08:37:34 +05:30
parent efe67dc28b
commit f57fbc1f24
25 changed files with 3461 additions and 706 deletions

View File

@@ -0,0 +1,108 @@
import { cx } from "@/utils/cx";
// Keys match the Clinic entity's openMonday..openSunday fields
// directly — no translation layer needed when reading/writing the
// form value into GraphQL mutations.
export type DayKey =
| "monday"
| "tuesday"
| "wednesday"
| "thursday"
| "friday"
| "saturday"
| "sunday";
export type DaySelection = Record<DayKey, boolean>;
const DAYS: { key: DayKey; label: string }[] = [
{ key: "monday", label: "Mon" },
{ key: "tuesday", label: "Tue" },
{ key: "wednesday", label: "Wed" },
{ key: "thursday", label: "Thu" },
{ key: "friday", label: "Fri" },
{ key: "saturday", label: "Sat" },
{ key: "sunday", label: "Sun" },
];
type DaySelectorProps = {
/** Selected-state for each weekday. */
value: DaySelection;
/** Fires with the full updated selection whenever a pill is tapped. */
onChange: (value: DaySelection) => void;
/** Optional heading above the pills. */
label?: string;
/** Optional helper text below the pills. */
hint?: string;
};
// Seven tappable MonSun pills. Used on the Clinic form to pick which
// days the clinic is open, since the Clinic entity has seven separate
// BOOLEAN fields (openMonday..openSunday) — SDK has no MULTI_SELECT.
// Also reusable anywhere else we need a weekly-recurrence picker
// (future: follow-up schedules, on-call rotations).
export const DaySelector = ({ value, onChange, label, hint }: DaySelectorProps) => (
<div className="flex flex-col gap-2">
{label && (
<span className="text-sm font-medium text-secondary">{label}</span>
)}
<div className="flex flex-wrap gap-2">
{DAYS.map(({ key, label: dayLabel }) => {
const isSelected = !!value[key];
return (
<button
key={key}
type="button"
onClick={() => onChange({ ...value, [key]: !isSelected })}
className={cx(
"flex h-10 min-w-12 items-center justify-center rounded-full border px-4 text-sm font-semibold transition duration-100 ease-linear",
isSelected
? "border-brand bg-brand-solid text-white hover:bg-brand-solid_hover"
: "border-secondary bg-primary text-secondary hover:border-primary hover:bg-secondary_hover",
)}
aria-pressed={isSelected}
>
{dayLabel}
</button>
);
})}
</div>
{hint && <span className="text-xs text-tertiary">{hint}</span>}
</div>
);
// Helper factories — use these instead of spelling out the empty
// object literal everywhere.
export const emptyDaySelection = (): DaySelection => ({
monday: false,
tuesday: false,
wednesday: false,
thursday: false,
friday: false,
saturday: false,
sunday: false,
});
// The default new-clinic state: MonSat open, Sun closed. Matches the
// typical Indian outpatient hospital schedule.
export const defaultDaySelection = (): DaySelection => ({
monday: true,
tuesday: true,
wednesday: true,
thursday: true,
friday: true,
saturday: true,
sunday: false,
});
// Format a DaySelection as a compact human-readable string for list
// pages (e.g. "MonFri", "MonSat", "Mon Wed Fri"). Collapses
// consecutive selected days into ranges.
export const formatDaySelection = (sel: DaySelection): string => {
const openKeys = DAYS.filter((d) => sel[d.key]).map((d) => d.label);
if (openKeys.length === 0) return "Closed";
if (openKeys.length === 7) return "Every day";
// Monday-Friday, Monday-Saturday shorthand
if (openKeys.length === 5 && openKeys.join(",") === "Mon,Tue,Wed,Thu,Fri") return "MonFri";
if (openKeys.length === 6 && openKeys.join(",") === "Mon,Tue,Wed,Thu,Fri,Sat") return "MonSat";
return openKeys.join(" ");
};