@medplum/react-scheduling 5.1.37 → 5.1.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,6 @@
1
1
  import type { Appointment } from '@medplum/fhirtypes';
2
+ import type { Coding } from '@medplum/fhirtypes';
3
+ import type { Dereference } from '@medplum/core';
2
4
  import type { HealthcareService } from '@medplum/fhirtypes';
3
5
  import type { HealthcareServiceAvailableTime } from '@medplum/fhirtypes';
4
6
  import type { JSX } from 'react';
@@ -7,16 +9,18 @@ import type { MantineThemeColors } from '@mantine/core';
7
9
  import type { MedplumClient } from '@medplum/core';
8
10
  import type { OperationOutcome } from '@medplum/fhirtypes';
9
11
  import type { Patient } from '@medplum/fhirtypes';
12
+ import type { ReactNode } from 'react';
10
13
  import type { Reference } from '@medplum/fhirtypes';
11
- import type { Resource } from '@medplum/fhirtypes';
12
14
  import type { Schedule } from '@medplum/fhirtypes';
15
+ import type { SchedulingRequirement } from '@medplum/core';
13
16
  import type { Slot } from '@medplum/fhirtypes';
17
+ import type { ValueSetExpansionContains } from '@medplum/fhirtypes';
14
18
  import type { WithId } from '@medplum/core';
15
19
 
16
20
  /**
17
21
  * One way of holding an appointment: a set of actors whose schedules `$find`
18
- * intersects in a single request. A role contributes as many actors as were
19
- * chosen for it, since everything chosen attends.
22
+ * intersects in a single request. Each requirement contributes exactly one
23
+ * actor, since a row is satisfied by any one of its alternatives.
20
24
  */
21
25
  export declare interface ActorCombination {
22
26
  /** Matches `getActorGroupKey` of the appointments offered for these actors. */
@@ -27,29 +31,72 @@ export declare interface ActorCombination {
27
31
  }
28
32
 
29
33
  /**
30
- * What an appointment is being asked for: the schedules chosen, per role.
31
- * Everything named attends.
34
+ * One thing an appointment needs, of a single actor type.
35
+ *
36
+ * The candidates in it are alternatives: a row naming two
37
+ * providers asks for *either* of them rather than both.
38
+ */
39
+ export declare interface ActorRequirement {
40
+ readonly id: string;
41
+ readonly candidates: readonly ScheduleCandidate[];
42
+ }
43
+
44
+ /**
45
+ * What an appointment is being asked for, per actor type.
46
+ *
47
+ * Each actor type holds a list of requirements, and the two directions read
48
+ * differently: requirements are ANDed, so two provider rows ask for two
49
+ * providers, while the candidates within one row are ORed.
32
50
  */
33
- export declare type ActorSelections = Partial<Record<SchedulingRole, readonly ScheduleCandidate[]>>;
51
+ export declare type ActorSelections = Partial<Record<SchedulingActorType, readonly ActorRequirement[]>>;
34
52
 
35
53
  export declare function addDays(date: Date, days: number): Date;
36
54
 
37
55
  /**
38
- * Chooses the actors an appointment is held on, for one role.
56
+ * Chooses which actors of one type can hold an appointment.
39
57
  *
40
- * Everything chosen attends: `$find` intersects the schedules behind them,
41
- * so naming a second actor narrows the times to the ones both are free for.
58
+ * The names in one of these fields are **alternatives** to each other.
59
+ * Several actors that all have to attend go in a field each - see {@link AppointmentActorSelections},
60
+ * which arranges these into rows.
42
61
  *
43
- * Schedules are searched for as the name is typed (via `AsyncAutocomplete`).
62
+ * The results are `Schedule` resources, bound to the actors that can hold the appointment.
44
63
  *
45
64
  * @param props - The React props.
46
- * @returns The field for one role.
65
+ * @returns The field for one actor type.
47
66
  */
48
67
  export declare function AppointmentActorSelect(props: AppointmentActorSelectProps): JSX.Element;
49
68
 
69
+ /**
70
+ * Asks what an appointment needs by actor type (e.g., provider, room, device).
71
+ *
72
+ * Each actor type gets a stack of rows. **The rows are ANDed and the names
73
+ * within a row are ORed**: two provider rows ask for two providers, while two
74
+ * names in one row ask for either of them.
75
+ *
76
+ * A row is one alternative set, so it is also one dimension of the search:
77
+ * `getActorCombinations` takes one name from each row, and every way of doing
78
+ * that is one `$find` request.
79
+ *
80
+ * @param props - The React props.
81
+ * @returns One group of rows per bookable actor type.
82
+ */
83
+ export declare function AppointmentActorSelections(props: AppointmentActorSelectionsProps): JSX.Element;
84
+
85
+ export declare interface AppointmentActorSelectionsProps {
86
+ /** What the appointment is being asked for so far, per actor type. */
87
+ readonly value: ActorSelections;
88
+ /** The service being booked. Nothing is offered until it resolves. */
89
+ readonly service: Reference<HealthcareService> | WithId<HealthcareService> | undefined;
90
+ /** The site being booked at. Actors sited elsewhere are left out. */
91
+ readonly location?: Reference<Location_2> | WithId<Location_2>;
92
+ readonly disabled?: boolean;
93
+ /** What is wrong with a type's rows, shown under them. None by default. */
94
+ readonly errors?: Partial<Record<BookableActorType, string>>;
95
+ readonly onChange: (selections: ActorSelections) => void;
96
+ }
97
+
50
98
  export declare interface AppointmentActorSelectProps {
51
- /** The role being filled. */
52
- readonly role: SchedulingRole;
99
+ readonly actorType: BookableActorType;
53
100
  /** The service being booked. Nothing is offered until it resolves. */
54
101
  readonly service: Reference<HealthcareService> | WithId<HealthcareService> | undefined;
55
102
  /**
@@ -62,6 +109,16 @@ export declare interface AppointmentActorSelectProps {
62
109
  readonly onChange: (candidates: readonly ScheduleCandidate[]) => void;
63
110
  readonly error?: string;
64
111
  readonly disabled?: boolean;
112
+ /** Names the field. Defaults to the actor type's own label. */
113
+ readonly label?: ReactNode;
114
+ /** The line under the field. None by default. */
115
+ readonly description?: ReactNode;
116
+ /** What the empty field invites. Defaults to searching the actor type by name. */
117
+ readonly placeholder?: string;
118
+ /** Whether the field must be answered. Defaults to whether its actor type is required. */
119
+ readonly required?: boolean;
120
+ /** Whether to mark the field with an asterisk. Defaults to `required`. */
121
+ readonly withAsterisk?: boolean;
65
122
  }
66
123
 
67
124
  /** What a booking wrote, as `Appointment/$book` returned it. */
@@ -124,6 +181,7 @@ export declare interface AppointmentDayTimesProps {
124
181
  readonly onSelectAppointment: (appointment: Appointment) => void;
125
182
  /** IANA timezone the times are read in. Defaults to the browser's. */
126
183
  readonly timezone?: string;
184
+ readonly viewerTimezone?: string;
127
185
  readonly selected?: Appointment;
128
186
  }
129
187
 
@@ -145,13 +203,6 @@ export declare interface AppointmentOptionRowProps {
145
203
  * Gathers what a visit is held on, finds a time every one of them is free, and
146
204
  * hands the proposal out to be booked.
147
205
  *
148
- * One field per scheduling role, each searching the schedules bookable for the
149
- * chosen visit type. Everything named attends, because `$find` intersects their
150
- * schedules — so naming a second room narrows the times rather than widening them.
151
- *
152
- * Only a time the search offered can be chosen: the field holding it accepts no
153
- * input, so nothing can be booked onto time nobody checked availability for.
154
- *
155
206
  * Writes nothing and announces nothing: `onBook` owns that. Mount this to do
156
207
  * something other than `$book` with the proposal — hold it through `$hold`, or
157
208
  * write it inside a transaction of your own. {@link AppointmentBookingForm} is the
@@ -200,6 +251,10 @@ export declare interface AppointmentProposalFormProps {
200
251
  * rather than leaving it on a time nobody chose.
201
252
  */
202
253
  readonly onChangeTime?: (time: DateTimeRange | undefined) => void;
254
+ /** The ValueSet the procedure code field binds to. Defaults to the full CPT value set. */
255
+ readonly procedureBinding?: string;
256
+ /** The ValueSet the diagnosis code field binds to. Defaults to the full ICD-10-CM value set. */
257
+ readonly diagnosisBinding?: string;
203
258
  /**
204
259
  * Performs the booking with the proposal the form assembled.
205
260
  *
@@ -238,7 +293,7 @@ export declare interface AppointmentServiceSelectProps {
238
293
  export declare interface AppointmentSlotGroup {
239
294
  /** Stable key derived from the actors, so React keys survive a refetch. */
240
295
  readonly key: string;
241
- readonly actors: readonly SchedulingActor[];
296
+ readonly actors: readonly SchedulingActorValue[];
242
297
  readonly durationMinutes: number;
243
298
  /** Sorted by start time. */
244
299
  readonly appointments: readonly Appointment[];
@@ -254,23 +309,43 @@ export declare function AppointmentSlotGroupCard(props: AppointmentSlotGroupCard
254
309
  export declare interface AppointmentSlotGroupCardProps {
255
310
  readonly group: AppointmentSlotGroup;
256
311
  readonly onSelectAppointment: (appointment: Appointment) => void;
257
- /** IANA timezone the times are shown in. Defaults to the browser's. */
312
+ /** Timezone the times are shown in. */
258
313
  readonly timezone?: string;
314
+ /** The viewer's own timezone. */
315
+ readonly viewerTimezone?: string;
259
316
  readonly selected?: Appointment;
260
317
  readonly disabled?: boolean;
261
318
  }
262
319
 
263
320
  /**
264
- * Actor types whose schedules may be offered for booking.
321
+ * Actor types whose schedules may be offered for booking, in the order they are
322
+ * asked about.
265
323
  *
266
- * Does not include `PractitionerRole` to prevent double-booking
267
- * a `Practitioner` who holds multiple roles. See `getSchedulingRole` for how
268
- * `PractitionerRole` is still used to determine eligibility for a schedule.
324
+ * Each type becomes its own question, because `$find` intersects the schedules it
325
+ * is given: a time is offered only when the practitioner *and* the location *and*
326
+ * the device are all free for it. Asking for *either* of two practitioners is a
327
+ * second request rather than a second schedule on the same one — see
328
+ * `getActorCombinations`.
329
+ *
330
+ * Does not include `PractitionerRole` to prevent double-booking a `Practitioner`
331
+ * who holds multiple roles. A practitioner is booked on their `Practitioner`,
332
+ * but their `PractitionerRole`s still decide which schedules they are eligible
333
+ * for and where they practice.
269
334
  */
270
- export declare const BOOKABLE_ACTOR_TYPES: readonly ["Practitioner", "Location", "Device"];
335
+ export declare const BOOKABLE_ACTOR_TYPES: ["Practitioner", "Location", "Device"];
271
336
 
272
337
  export declare type BookableActorType = (typeof BOOKABLE_ACTOR_TYPES)[number];
273
338
 
339
+ /**
340
+ * What the booking form captures for a visit type's requirements: one value per requirement it
341
+ * can be asked for.
342
+ */
343
+ export declare interface BookingRequirementValues {
344
+ readonly procedure: readonly Coding[];
345
+ readonly diagnosis: readonly Coding[];
346
+ readonly medicalNecessity: boolean;
347
+ }
348
+
274
349
  export declare function Calendar(props: CalendarProps): JSX.Element;
275
350
 
276
351
  export declare interface CalendarProps {
@@ -299,6 +374,26 @@ declare interface CommonProps {
299
374
  readonly onCancel?: () => void;
300
375
  }
301
376
 
377
+ /**
378
+ * Counts the combinations the selections expand into, without building them.
379
+ *
380
+ * An upper bound rather than an exact count: the ones that would name the same
381
+ * actor twice are only recognised while building. Enough to decide whether the
382
+ * product is worth expanding at all.
383
+ *
384
+ * @param selections - What has been chosen.
385
+ * @returns How many ways there are of satisfying every row, or 0 when nothing
386
+ * has been chosen.
387
+ */
388
+ export declare function countActorCombinations(selections: ActorSelections): number;
389
+
390
+ /**
391
+ * Opens a new requirement row.
392
+ * @param candidates - What it starts out asking for. Empty by default.
393
+ * @returns The row.
394
+ */
395
+ export declare function createActorRequirement(candidates?: readonly ScheduleCandidate[]): ActorRequirement;
396
+
302
397
  /** The days a search covers. Either end may be open. */
303
398
  export declare interface DateRange {
304
399
  readonly start?: Date;
@@ -310,6 +405,14 @@ export declare type DateTimeRange = {
310
405
  end: Date;
311
406
  };
312
407
 
408
+ /** The value set the diagnosis code field binds to when a host names none. */
409
+ export declare const DEFAULT_DIAGNOSIS_VALUE_SET = "http://hl7.org/fhir/sid/icd-10-cm/vs";
410
+
411
+ /** The value set the procedure code field binds to when a host names none. */
412
+ export declare const DEFAULT_PROCEDURE_VALUE_SET = "http://www.ama-assn.org/go/cpt/vs";
413
+
414
+ export declare const EMPTY_REQUIREMENT_VALUES: BookingRequirementValues;
415
+
313
416
  /**
314
417
  * Returns the last instant of a day, so that a range covers the whole of it.
315
418
  * @param date - Any instant during the day.
@@ -360,14 +463,6 @@ export declare interface FilterCandidatesOptions {
360
463
  readonly signal?: AbortSignal;
361
464
  }
362
465
 
363
- /**
364
- * Says in words which days a search covers.
365
- * @param range - The days asked for.
366
- * @param formatDay - How to name one day. Defaults to naming it with its weekday.
367
- * @returns The range as a phrase, or undefined when both ends are open.
368
- */
369
- export declare function formatDateRange(range: DateRange, formatDay?: (date: Date) => string): string | undefined;
370
-
371
466
  /**
372
467
  * Formats a calendar day as a heading (e.g. "Monday, July 27").
373
468
  * @param date - Local midnight of the day.
@@ -376,29 +471,41 @@ export declare function formatDateRange(range: DateRange, formatDay?: (date: Dat
376
471
  export declare function formatDayHeading(date: Date): string;
377
472
 
378
473
  /**
379
- * Names a calendar day without its weekday (e.g. "July 27").
380
- * @param date - Local midnight of the day.
381
- * @returns The formatted day.
474
+ * Names a timezone the short way it is written beside a time, e.g. "ET" or "GMT+2".
475
+ * @param timezone - IANA timezone identifier. Defaults to the browser's.
476
+ * @returns The zone's short name.
382
477
  */
383
- export declare function formatDayLabel(date: Date): string;
478
+ export declare function formatTimezoneLabel(timezone?: string): string;
384
479
 
385
480
  /**
386
481
  * Formats an instant's time of day in a given timezone (e.g. "12:30 PM").
387
482
  * @param date - The instant to format.
388
483
  * @param timezone - IANA timezone identifier. Defaults to the browser's.
484
+ * @param options - Whether to name the zone as well.
389
485
  * @returns The formatted time.
390
486
  */
391
- export declare function formatZonedTime(date: Date, timezone?: string): string;
487
+ export declare function formatZonedTime(date: Date, timezone?: string, options?: FormatZonedTimeOptions): string;
488
+
489
+ export declare interface FormatZonedTimeOptions {
490
+ /** Names the zone alongside the time, e.g. "12:30 PM ET". */
491
+ readonly withTimezone?: boolean;
492
+ }
392
493
 
393
494
  /**
394
495
  * Builds the sets of actors an appointment could be held on.
395
496
  *
396
497
  * One combination is one `$find` request: the schedules within it are
397
- * intersected, so its times are the times all of those actors are free.
498
+ * intersected, so its times are the times all of those actors are free. Rows are
499
+ * ANDed and the alternatives within a row are ORed, so the combinations are the
500
+ * product of the rows — one alternative taken from each.
501
+ *
502
+ * Ordered as an odometer with the last row turning fastest, so the first
503
+ * combination is every row's first pick and the rounds the search runs stay
504
+ * predictable.
398
505
  *
399
506
  * @param selections - What has been chosen.
400
- * @returns One combination holding every chosen actor, in role order, or an
401
- * empty list when nothing is chosen.
507
+ * @returns One combination per way of satisfying every row, or an empty list
508
+ * when nothing is chosen.
402
509
  */
403
510
  export declare function getActorCombinations(selections: ActorSelections): ActorCombination[];
404
511
 
@@ -410,22 +517,39 @@ export declare function getActorCombinations(selections: ActorSelections): Actor
410
517
  export declare function getActorGroupKey(appointment: Appointment): string;
411
518
 
412
519
  /**
413
- * Names the role an actor is filling, from its own reference.
520
+ * Builds a key identifying a set of actors, independent of their order.
521
+ * @param actors - The actors to key.
522
+ * @returns The key.
523
+ */
524
+ export declare function getActorsKey(actors: readonly Reference[]): string;
525
+
526
+ /**
527
+ * Extracts the resource type of an actor, given as itself or as a reference.
414
528
  *
415
- * Needed where an actor is shown away from the field it was chosen in, which is
416
- * the only thing that would otherwise say which role it answers.
529
+ * Only supports references that may be used for Scheduling operations, which are
530
+ * those that have a qualified `reference` attribute. The attribute must be present,
531
+ * and may not be a reference to a "contained" resource (eg. `{ reference: "#cid" }`)
417
532
  *
418
- * @param actor - A reference to a scheduling actor.
419
- * @returns The role's label, or undefined for a reference of another type.
533
+ * @param actor - A schedulable resource, or a Reference to one
534
+ * @returns The type of the actor.
420
535
  */
421
- export declare function getActorRoleLabel(actor: Reference): string | undefined;
536
+ export declare function getActorType(actor: SchedulingActorValue): SchedulingActorType;
422
537
 
423
538
  /**
424
- * Builds a key identifying a set of actors, independent of their order.
425
- * @param actors - The actors to key.
426
- * @returns The key.
539
+ * Provides the label we use for scheduling resource types in this UI
540
+ *
541
+ * @param resourceType - A scheduling actor type
542
+ * @returns The type's label
427
543
  */
428
- export declare function getActorsKey(actors: readonly Reference[]): string;
544
+ export declare function getActorTypeLabel(resourceType: SchedulingActorType): string;
545
+
546
+ /**
547
+ * Get the actor(s) of an appointment.
548
+ * @param appointment - The proposed appointment.
549
+ * @param actorResources - Map of actor resources that have already been previously loaded.
550
+ * @returns List of actors. Resource if it's already loaded, reference otherwise.
551
+ */
552
+ export declare function getAppointmentActors(appointment: Appointment | undefined, actorResources?: ReadonlyMap<string, SchedulingActorResource>): SchedulingActorValue[];
429
553
 
430
554
  /**
431
555
  * Returns a key uniquely identifying a proposed appointment.
@@ -439,18 +563,24 @@ export declare function getActorsKey(actors: readonly Reference[]): string;
439
563
  export declare function getAppointmentKey(appointment: Appointment): string;
440
564
 
441
565
  /**
442
- * Names a candidate's actor, for use in plain-text option lists.
443
- * @param candidate - The candidate to name.
444
- * @returns The name to show.
566
+ * The IANA timezone the browser is set to.
567
+ * @returns The viewer's own timezone identifier.
445
568
  */
446
- export declare function getCandidateDisplay(candidate: ScheduleCandidate): string;
569
+ export declare function getBrowserTimezone(): string;
447
570
 
448
571
  /**
449
- * Returns the role a candidate fills, from the type of its actor.
572
+ * Returns the actor a candidate's schedule is held on.
450
573
  * @param candidate - The candidate to read.
451
- * @returns The role, or undefined for an actor of a type nothing books against.
574
+ * @returns Its schedule's only actor.
452
575
  */
453
- export declare function getCandidateRole(candidate: ScheduleCandidate): SchedulingRole | undefined;
576
+ export declare function getCandidateActor(candidate: ScheduleCandidate): SchedulingActor;
577
+
578
+ /**
579
+ * Names a candidate's actor, for use in plain-text option lists.
580
+ * @param candidate - The candidate to name.
581
+ * @returns The name to show.
582
+ */
583
+ export declare function getCandidateDisplay(candidate: ScheduleCandidate): string;
454
584
 
455
585
  /**
456
586
  * Counts the days a window covers, a part of a day counting as a whole one.
@@ -510,25 +640,49 @@ export declare function getFindWindowError(range: DateRange): string | undefined
510
640
  export declare function getNativeInputType(type: 'date' | 'time'): string;
511
641
 
512
642
  /**
513
- * Returns the role an actor type is chosen as.
514
- * @param actorType - A `Schedule.actor` resource type.
515
- * @returns The role that actor fills.
643
+ * Returns every row across every actor type, in the order they are asked about.
644
+ * @param selections - What has been chosen.
645
+ * @returns The rows, empty ones included.
516
646
  */
517
- export declare function getSchedulingRole(actorType: SchedulingActorType): SchedulingRole;
647
+ export declare function getRequirements(selections: ActorSelections): ActorRequirement[];
518
648
 
519
649
  /**
520
- * Returns everything chosen, across roles.
650
+ * Flattens the loaded actor resources into a map, keyed by their reference.
521
651
  * @param selections - What has been chosen.
522
- * @returns The chosen candidates, in `SCHEDULING_ROLES` order.
652
+ * @returns The resource behind each chosen actor the search was able to include.
653
+ */
654
+ export declare function getSelectedActorResources(selections: ActorSelections): Map<string, SchedulingActorResource>;
655
+
656
+ /**
657
+ * Returns everything chosen, across rows and actor types.
658
+ * @param selections - What has been chosen.
659
+ * @returns The chosen candidates in `BOOKABLE_ACTOR_TYPES` order, then row order.
523
660
  */
524
661
  export declare function getSelectedCandidates(selections: ActorSelections): ScheduleCandidate[];
525
662
 
526
663
  /**
527
664
  * Reports why the current selections cannot be searched, if they cannot.
528
665
  * @param selections - What has been chosen.
529
- * @returns A message to show the user, or undefined when the search can run.
666
+ * @returns The blocker to show the user, or undefined when the search can run.
667
+ */
668
+ export declare function getSelectionError(selections: ActorSelections): SelectionBlocker | undefined;
669
+
670
+ /**
671
+ * Finds the rows that cannot all be filled at once, when there are any. Specifically happens
672
+ * when two rows offer the same resource, so one resource cannot fill both halves of a visit.
673
+ * @param selections - What has been chosen.
674
+ * @returns The first type whose rows have no answer between them, or undefined when
675
+ * every type can be satisfied.
530
676
  */
531
- export declare function getSelectionError(selections: ActorSelections): string | undefined;
677
+ export declare function getUnsatisfiableRows(selections: ActorSelections): UnsatisfiableRows | undefined;
678
+
679
+ /**
680
+ * Search window for a calendar day in the given timezone, starting from now if that day is already underway.
681
+ * @param day - The day to search, as local midnight. Bound to the site's timezone.
682
+ * @param timezone - IANA timezone identifier. Defaults to the browser's.
683
+ * @returns Range from the start of that day, or from now, through the next day's midnight.
684
+ */
685
+ export declare function getZonedDayRange(day: Date, timezone?: string): Required<DateRange>;
532
686
 
533
687
  /**
534
688
  * Groups proposed appointments into days, and each day into the sets of actors
@@ -539,28 +693,53 @@ export declare function getSelectionError(selections: ActorSelections): string |
539
693
  * @param searched - Days to list whether or not they offer anything, so a searched day
540
694
  * that came back empty still shows up rather than going missing. Read on the local
541
695
  * calendar, matching how a day is picked.
696
+ * @param actorResources - The actors' own resources, keyed by reference, for
697
+ * whichever of them the caller has already read. See {@link getAppointmentActors}.
542
698
  * @returns Days in ascending order, each holding its groups.
543
699
  */
544
- export declare function groupAppointmentsByDay(appointments: readonly Appointment[], timezone?: string, searched?: DateRange): AppointmentDay[];
700
+ export declare function groupAppointmentsByDay(appointments: readonly Appointment[], timezone?: string, searched?: DateRange, actorResources?: ReadonlyMap<string, SchedulingActorResource>): AppointmentDay[];
545
701
 
546
- export declare function isBookableActorType(value: string | undefined): value is BookableActorType;
702
+ /**
703
+ * Whether every requirement a visit type names has been answered.
704
+ * @param values - What the fields are holding.
705
+ * @param requirements - What the visit type requires, from its eligibility codes.
706
+ * @returns True once each required field holds a value. A value nothing asked for is not weighed.
707
+ */
708
+ export declare function hasRequiredValues(values: BookingRequirementValues, requirements: ReadonlySet<SchedulingRequirement>): boolean;
547
709
 
548
710
  /**
549
- * Reports whether a role has to be filled.
550
- * @param role - The role being filled.
711
+ * Reports whether an actorType has to be chosen to be a valid booking
712
+ * @param actorType - The actor type being chosen.
551
713
  * @returns Whether a search can run without it.
552
714
  */
553
- export declare function isRoleRequired(role: SchedulingRole): boolean;
715
+ export declare function isActorTypeRequired(actorType: SchedulingActorType): boolean;
716
+
717
+ export declare function isBookableActorType(value: string | undefined): value is BookableActorType;
718
+
719
+ /**
720
+ * Whether one requirement has been answered.
721
+ * @param requirement - The requirement to weigh.
722
+ * @param values - What the fields are holding.
723
+ * @returns True when the field answering that requirement holds a value.
724
+ */
725
+ export declare function isRequirementAnswered(requirement: SchedulingRequirement, values: BookingRequirementValues): boolean;
554
726
 
555
727
  /**
556
- * Returns whether two instants fall on the same local day.
557
- * @param left - The first instant.
558
- * @param right - The second, or undefined when there is nothing to compare.
559
- * @returns True when both fall on the same local day.
728
+ * Whether a timezone is the viewer's own.
729
+ * @param timezone - IANA timezone the times are in, or undefined when it could not be resolved.
730
+ * @param viewer - The viewer's IANA timezone. Defaults to the browser's
731
+ * @returns True when the zone is the viewer's, or when there is no zone to compare.
560
732
  */
561
- export declare function isSameDay(left: Date, right: Date | undefined): boolean;
733
+ export declare function isViewerTimezone(timezone: string | undefined, viewer?: string): boolean;
562
734
 
563
- export declare function isSchedulingActorType(value: string | undefined): value is SchedulingActorType;
735
+ /**
736
+ * The most alternatives worth expanding into requests at all.
737
+ *
738
+ * The search runs a few combinations at a time, so this is not the size of a
739
+ * round: it is the point past which the whole product is more than anyone is
740
+ * going to sit through, whatever order it is asked in.
741
+ */
742
+ export declare const MAX_ACTOR_COMBINATIONS = 100;
564
743
 
565
744
  /**
566
745
  * The longest window `Appointment/$find` accepts. Requests wider than this are
@@ -618,7 +797,13 @@ export declare function parseDayKey(key: string): Date;
618
797
  */
619
798
  export declare function parseZonedTime(day: Date, time: string, timezone?: string): Date | undefined;
620
799
 
621
- export declare const ROLE_LABELS: Record<SchedulingRole, string>;
800
+ /**
801
+ * Actor types that must be chosen before a search can run.
802
+ *
803
+ * Locations and devices are left optional: a service may have location schedules
804
+ * configured without every booking needing to hold one.
805
+ */
806
+ export declare const REQUIRED_ACTOR_TYPES: ReadonlySet<SchedulingActorType>;
622
807
 
623
808
  /**
624
809
  * Edits weekly availability for one visit service type, either as a Schedule's
@@ -645,7 +830,7 @@ export declare type ScheduleAvailabilityEditorProps = ScheduleOverrideEditorProp
645
830
  export declare interface ScheduleCandidate {
646
831
  readonly schedule: WithId<Schedule>;
647
832
  /** The actor itself, when the search was able to include it. */
648
- readonly actorResource: WithId<Resource> | undefined;
833
+ readonly actorResource: SchedulingActorResource | undefined;
649
834
  }
650
835
 
651
836
  /**
@@ -660,27 +845,21 @@ export declare interface ScheduleOverrideEditorProps extends CommonProps {
660
845
  readonly onSave: (updatedSchedule: Schedule) => void | Promise<void>;
661
846
  }
662
847
 
663
- /** Resource types that may appear in `Schedule.actor`. */
664
- export declare const SCHEDULING_ACTOR_TYPES: readonly ["Practitioner", "PractitionerRole", "Location", "Device"];
665
-
666
- /**
667
- * The parts of an appointment a user chooses, in the order they are asked about.
668
- *
669
- * Each role becomes one question, because `$find` intersects the schedules it is
670
- * given: a time is offered only when the provider *and* the room *and* the
671
- * device are all free for it.
672
- */
673
- export declare const SCHEDULING_ROLES: readonly ["provider", "room", "device"];
674
-
675
848
  /**
676
849
  * A reference to something a Schedule belongs to. The same union an Appointment
677
850
  * accepts as a participant, so an actor can be carried straight across.
678
851
  */
679
852
  export declare type SchedulingActor = Schedule['actor'][number];
680
853
 
681
- export declare type SchedulingActorType = (typeof SCHEDULING_ACTOR_TYPES)[number];
854
+ /** A loaded actor resource. */
855
+ export declare type SchedulingActorResource = WithId<Dereference<SchedulingActor>>;
682
856
 
683
- export declare type SchedulingRole = (typeof SCHEDULING_ROLES)[number];
857
+ export declare type SchedulingActorType = SchedulingActorResource['resourceType'];
858
+
859
+ /**
860
+ * An actor as a reference, or as the resource when it has already been read.
861
+ */
862
+ export declare type SchedulingActorValue = SchedulingActor | SchedulingActorResource;
684
863
 
685
864
  /**
686
865
  * A data-coordination component pairing {@link CalendarsPanel} with {@link MultiCalendar}.
@@ -692,6 +871,9 @@ export declare type SchedulingRole = (typeof SCHEDULING_ROLES)[number];
692
871
  * The form writes the booking and announces what it wrote, which is what puts the
693
872
  * new appointment on the calendar beside it — a host supplies no data for any of it.
694
873
  * What was written is reported through `onBooked`, for a host that wants to say so.
874
+ * - Shows what is booked: clicking an appointment opens {@link AppointmentDetails} in a
875
+ * drawer over the calendar, describing the visit and offering to cancel it. Cancelling
876
+ * is what takes the time back off the calendar, again without a host supplying anything.
695
877
  * - Highlights the time last chosen, wherever it was chosen: the click that opened the
696
878
  * pane, then whatever the form's time search settles on, and nothing while the form
697
879
  * holds no time. The calendar is never moved to reach it — a highlight off the week
@@ -704,7 +886,17 @@ export declare function SchedulingWorkspace(props: SchedulingWorkspaceProps): JS
704
886
 
705
887
  export declare interface SchedulingWorkspaceProps {
706
888
  readonly className?: string;
889
+ /** The ValueSet the procedure code field binds to. Defaults to full CPT valueset. */
890
+ readonly procedureBinding?: string;
891
+ /** The ValueSet the diagnosis code field binds to. Defaults to full ICD-10-CM valueset. */
892
+ readonly diagnosisBinding?: string;
707
893
  readonly onBooked?: (booking: AppointmentBooking) => void | Promise<void>;
894
+ readonly onCancelled?: (appointment: WithId<Appointment>) => void | Promise<void>;
895
+ /**
896
+ * Overrides the value set the appointment detail view offers cancellation reasons
897
+ * from, for a host coding them against its own terminology.
898
+ */
899
+ readonly appointmentCancellationReasonValueSet?: string;
708
900
  }
709
901
 
710
902
  /**
@@ -720,8 +912,8 @@ export declare function searchScheduleCandidates(medplum: MedplumClient, service
720
912
 
721
913
  export declare interface SearchScheduleCandidatesOptions {
722
914
  /** Which of the service's actors to offer. */
723
- readonly role: SchedulingRole;
724
- /** What the user typed. Empty offers whatever the role has, unfiltered by name. */
915
+ readonly actorType: SchedulingActorType;
916
+ /** What the user typed. Empty offers whatever the actor type has, unfiltered by name. */
725
917
  readonly query: string;
726
918
  /**
727
919
  * The site being booked at. Actors sited elsewhere are left out: a room or a
@@ -734,6 +926,20 @@ export declare interface SearchScheduleCandidatesOptions {
734
926
  readonly count?: number;
735
927
  }
736
928
 
929
+ /** Why the current selections cannot be searched. */
930
+ export declare interface SelectionBlocker {
931
+ /** A whole sentence to show the user. */
932
+ readonly message: string;
933
+ readonly severity: SelectionBlockerSeverity;
934
+ }
935
+
936
+ /**
937
+ * Whether a blocker is a form that is not finished yet, or answers that cannot work.
938
+ *
939
+ * Only `invalid` is the user's to undo, so only it is worth showing as an error.
940
+ */
941
+ export declare type SelectionBlockerSeverity = 'incomplete' | 'invalid';
942
+
737
943
  /**
738
944
  * Props for editing a service's own default hours, in place of any one calendar's override.
739
945
  * @param schedule - Omitted, which is what selects this mode.
@@ -778,6 +984,21 @@ export declare function startOfDay(date: Date): Date;
778
984
 
779
985
  export declare type TimeOfDay = 'any' | 'morning' | 'afternoon';
780
986
 
987
+ /**
988
+ * Reads the codes a field is holding into a list of codings.
989
+ * @param elements - What the field is holding.
990
+ * @returns The codings to record, dropping anything that never became a code.
991
+ */
992
+ export declare function toCodings(elements: readonly ValueSetExpansionContains[]): Coding[];
993
+
994
+ /** Rows that cannot all be filled at once, and where to say so. */
995
+ export declare interface UnsatisfiableRows {
996
+ /** The actor type whose rows have no answer between them. */
997
+ readonly actorType: BookableActorType;
998
+ /** What to do about it, to show against those rows. */
999
+ readonly message: string;
1000
+ }
1001
+
781
1002
  /**
782
1003
  * Searches for the times an appointment could be held at.
783
1004
  *