@medplum/react-scheduling 5.1.36 → 5.1.38

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';
@@ -10,7 +12,9 @@ import type { Patient } from '@medplum/fhirtypes';
10
12
  import type { Reference } from '@medplum/fhirtypes';
11
13
  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
  /**
@@ -27,15 +31,15 @@ export declare interface ActorCombination {
27
31
  }
28
32
 
29
33
  /**
30
- * What an appointment is being asked for: the schedules chosen, per role.
34
+ * What an appointment is being asked for: the schedules chosen, per actor type.
31
35
  * Everything named attends.
32
36
  */
33
- export declare type ActorSelections = Partial<Record<SchedulingRole, readonly ScheduleCandidate[]>>;
37
+ export declare type ActorSelections = Partial<Record<SchedulingActorType, readonly ScheduleCandidate[]>>;
34
38
 
35
39
  export declare function addDays(date: Date, days: number): Date;
36
40
 
37
41
  /**
38
- * Chooses the actors an appointment is held on, for one role.
42
+ * Chooses the actors an appointment is held on, for one actor type.
39
43
  *
40
44
  * Everything chosen attends: `$find` intersects the schedules behind them,
41
45
  * so naming a second actor narrows the times to the ones both are free for.
@@ -43,13 +47,12 @@ export declare function addDays(date: Date, days: number): Date;
43
47
  * Schedules are searched for as the name is typed (via `AsyncAutocomplete`).
44
48
  *
45
49
  * @param props - The React props.
46
- * @returns The field for one role.
50
+ * @returns The field for one actor type.
47
51
  */
48
52
  export declare function AppointmentActorSelect(props: AppointmentActorSelectProps): JSX.Element;
49
53
 
50
54
  export declare interface AppointmentActorSelectProps {
51
- /** The role being filled. */
52
- readonly role: SchedulingRole;
55
+ readonly actorType: BookableActorType;
53
56
  /** The service being booked. Nothing is offered until it resolves. */
54
57
  readonly service: Reference<HealthcareService> | WithId<HealthcareService> | undefined;
55
58
  /**
@@ -124,6 +127,7 @@ export declare interface AppointmentDayTimesProps {
124
127
  readonly onSelectAppointment: (appointment: Appointment) => void;
125
128
  /** IANA timezone the times are read in. Defaults to the browser's. */
126
129
  readonly timezone?: string;
130
+ readonly viewerTimezone?: string;
127
131
  readonly selected?: Appointment;
128
132
  }
129
133
 
@@ -192,6 +196,18 @@ export declare interface AppointmentProposalFormProps {
192
196
  readonly onToggleTimeFinder?: (open: boolean) => void;
193
197
  /** Called with the visit type as it changes. */
194
198
  readonly onChangeService?: (service: WithId<HealthcareService> | undefined) => void;
199
+ /**
200
+ * Called with the time chosen, and with `undefined` once it is dropped.
201
+ *
202
+ * Every answer above the time drops it — a different day, a different provider —
203
+ * so a host pointing at the time on a calendar of its own takes the marker down
204
+ * rather than leaving it on a time nobody chose.
205
+ */
206
+ readonly onChangeTime?: (time: DateTimeRange | undefined) => void;
207
+ /** The ValueSet the procedure code field binds to. Defaults to the full CPT value set. */
208
+ readonly procedureBinding?: string;
209
+ /** The ValueSet the diagnosis code field binds to. Defaults to the full ICD-10-CM value set. */
210
+ readonly diagnosisBinding?: string;
195
211
  /**
196
212
  * Performs the booking with the proposal the form assembled.
197
213
  *
@@ -246,23 +262,41 @@ export declare function AppointmentSlotGroupCard(props: AppointmentSlotGroupCard
246
262
  export declare interface AppointmentSlotGroupCardProps {
247
263
  readonly group: AppointmentSlotGroup;
248
264
  readonly onSelectAppointment: (appointment: Appointment) => void;
249
- /** IANA timezone the times are shown in. Defaults to the browser's. */
265
+ /** Timezone the times are shown in. */
250
266
  readonly timezone?: string;
267
+ /** The viewer's own timezone. */
268
+ readonly viewerTimezone?: string;
251
269
  readonly selected?: Appointment;
252
270
  readonly disabled?: boolean;
253
271
  }
254
272
 
255
273
  /**
256
- * Actor types whose schedules may be offered for booking.
274
+ * Actor types whose schedules may be offered for booking, in the order they are
275
+ * asked about.
257
276
  *
258
- * Does not include `PractitionerRole` to prevent double-booking
259
- * a `Practitioner` who holds multiple roles. See `getSchedulingRole` for how
260
- * `PractitionerRole` is still used to determine eligibility for a schedule.
277
+ * Each type becomes one question, because `$find` intersects the schedules it is
278
+ * given: a time is offered only when the practitioner *and* the location *and*
279
+ * the device are all free for it.
280
+ *
281
+ * Does not include `PractitionerRole` to prevent double-booking a `Practitioner`
282
+ * who holds multiple roles. A practitioner is booked on their `Practitioner`,
283
+ * but their `PractitionerRole`s still decide which schedules they are eligible
284
+ * for and where they practice.
261
285
  */
262
- export declare const BOOKABLE_ACTOR_TYPES: readonly ["Practitioner", "Location", "Device"];
286
+ export declare const BOOKABLE_ACTOR_TYPES: ["Practitioner", "Location", "Device"];
263
287
 
264
288
  export declare type BookableActorType = (typeof BOOKABLE_ACTOR_TYPES)[number];
265
289
 
290
+ /**
291
+ * What the booking form captures for a visit type's requirements: one value per requirement it
292
+ * can be asked for.
293
+ */
294
+ export declare interface BookingRequirementValues {
295
+ readonly procedure: readonly Coding[];
296
+ readonly diagnosis: readonly Coding[];
297
+ readonly medicalNecessity: boolean;
298
+ }
299
+
266
300
  export declare function Calendar(props: CalendarProps): JSX.Element;
267
301
 
268
302
  export declare interface CalendarProps {
@@ -302,6 +336,14 @@ export declare type DateTimeRange = {
302
336
  end: Date;
303
337
  };
304
338
 
339
+ /** The value set the diagnosis code field binds to when a host names none. */
340
+ export declare const DEFAULT_DIAGNOSIS_VALUE_SET = "http://hl7.org/fhir/sid/icd-10-cm/vs";
341
+
342
+ /** The value set the procedure code field binds to when a host names none. */
343
+ export declare const DEFAULT_PROCEDURE_VALUE_SET = "http://www.ama-assn.org/go/cpt/vs";
344
+
345
+ export declare const EMPTY_REQUIREMENT_VALUES: BookingRequirementValues;
346
+
305
347
  /**
306
348
  * Returns the last instant of a day, so that a range covers the whole of it.
307
349
  * @param date - Any instant during the day.
@@ -352,13 +394,6 @@ export declare interface FilterCandidatesOptions {
352
394
  readonly signal?: AbortSignal;
353
395
  }
354
396
 
355
- /**
356
- * Says in words which days a search covers.
357
- * @param range - The days asked for.
358
- * @returns The range as a phrase, or undefined when both ends are open.
359
- */
360
- export declare function formatDateRange(range: DateRange): string | undefined;
361
-
362
397
  /**
363
398
  * Formats a calendar day as a heading (e.g. "Monday, July 27").
364
399
  * @param date - Local midnight of the day.
@@ -366,13 +401,26 @@ export declare function formatDateRange(range: DateRange): string | undefined;
366
401
  */
367
402
  export declare function formatDayHeading(date: Date): string;
368
403
 
404
+ /**
405
+ * Names a timezone the short way it is written beside a time, e.g. "ET" or "GMT+2".
406
+ * @param timezone - IANA timezone identifier. Defaults to the browser's.
407
+ * @returns The zone's short name.
408
+ */
409
+ export declare function formatTimezoneLabel(timezone?: string): string;
410
+
369
411
  /**
370
412
  * Formats an instant's time of day in a given timezone (e.g. "12:30 PM").
371
413
  * @param date - The instant to format.
372
414
  * @param timezone - IANA timezone identifier. Defaults to the browser's.
415
+ * @param options - Whether to name the zone as well.
373
416
  * @returns The formatted time.
374
417
  */
375
- export declare function formatZonedTime(date: Date, timezone?: string): string;
418
+ export declare function formatZonedTime(date: Date, timezone?: string, options?: FormatZonedTimeOptions): string;
419
+
420
+ export declare interface FormatZonedTimeOptions {
421
+ /** Names the zone alongside the time, e.g. "12:30 PM ET". */
422
+ readonly withTimezone?: boolean;
423
+ }
376
424
 
377
425
  /**
378
426
  * Builds the sets of actors an appointment could be held on.
@@ -394,22 +442,31 @@ export declare function getActorCombinations(selections: ActorSelections): Actor
394
442
  export declare function getActorGroupKey(appointment: Appointment): string;
395
443
 
396
444
  /**
397
- * Names the role an actor is filling, from its own reference.
445
+ * Builds a key identifying a set of actors, independent of their order.
446
+ * @param actors - The actors to key.
447
+ * @returns The key.
448
+ */
449
+ export declare function getActorsKey(actors: readonly Reference[]): string;
450
+
451
+ /**
452
+ * Extracts the resource type from a reference to an actor.
398
453
  *
399
- * Needed where an actor is shown away from the field it was chosen in, which is
400
- * the only thing that would otherwise say which role it answers.
454
+ * Only supports references that may be used for Scheduling operations, which are
455
+ * those that have a qualified `reference` attribute. The attribute must be present,
456
+ * and may not be a reference to a "contained" resource (eg. `{ reference: "#cid" }`)
401
457
  *
402
- * @param actor - A reference to a scheduling actor.
403
- * @returns The role's label, or undefined for a reference of another type.
458
+ * @param reference - A Reference to a schedulable resource
459
+ * @returns The type that the reference refers to.
404
460
  */
405
- export declare function getActorRoleLabel(actor: Reference): string | undefined;
461
+ export declare function getActorType(reference: SchedulingActor): SchedulingActorType;
406
462
 
407
463
  /**
408
- * Builds a key identifying a set of actors, independent of their order.
409
- * @param actors - The actors to key.
410
- * @returns The key.
464
+ * Provides the label we use for scheduling resource types in this UI
465
+ *
466
+ * @param resourceType - A scheduling actor type
467
+ * @returns The type's label
411
468
  */
412
- export declare function getActorsKey(actors: readonly Reference[]): string;
469
+ export declare function getActorTypeLabel(resourceType: SchedulingActorType): string;
413
470
 
414
471
  /**
415
472
  * Returns a key uniquely identifying a proposed appointment.
@@ -422,6 +479,19 @@ export declare function getActorsKey(actors: readonly Reference[]): string;
422
479
  */
423
480
  export declare function getAppointmentKey(appointment: Appointment): string;
424
481
 
482
+ /**
483
+ * The IANA timezone the browser is set to.
484
+ * @returns The viewer's own timezone identifier.
485
+ */
486
+ export declare function getBrowserTimezone(): string;
487
+
488
+ /**
489
+ * Returns the actor a candidate's schedule is held on.
490
+ * @param candidate - The candidate to read.
491
+ * @returns Its schedule's only actor.
492
+ */
493
+ export declare function getCandidateActor(candidate: ScheduleCandidate): SchedulingActor;
494
+
425
495
  /**
426
496
  * Names a candidate's actor, for use in plain-text option lists.
427
497
  * @param candidate - The candidate to name.
@@ -430,11 +500,12 @@ export declare function getAppointmentKey(appointment: Appointment): string;
430
500
  export declare function getCandidateDisplay(candidate: ScheduleCandidate): string;
431
501
 
432
502
  /**
433
- * Returns the role a candidate fills, from the type of its actor.
434
- * @param candidate - The candidate to read.
435
- * @returns The role, or undefined for an actor of a type nothing books against.
503
+ * Counts the days a window covers, a part of a day counting as a whole one.
504
+ * @param start - The first instant of the window.
505
+ * @param end - Its last instant.
506
+ * @returns The number of days the window reaches over.
436
507
  */
437
- export declare function getCandidateRole(candidate: ScheduleCandidate): SchedulingRole | undefined;
508
+ export declare function getDayCount(start: Date, end: Date): number;
438
509
 
439
510
  /**
440
511
  * Returns an appointment's length in whole minutes.
@@ -486,16 +557,9 @@ export declare function getFindWindowError(range: DateRange): string | undefined
486
557
  export declare function getNativeInputType(type: 'date' | 'time'): string;
487
558
 
488
559
  /**
489
- * Returns the role an actor type is chosen as.
490
- * @param actorType - A `Schedule.actor` resource type.
491
- * @returns The role that actor fills.
492
- */
493
- export declare function getSchedulingRole(actorType: SchedulingActorType): SchedulingRole;
494
-
495
- /**
496
- * Returns everything chosen, across roles.
560
+ * Returns everything chosen, across actor types
497
561
  * @param selections - What has been chosen.
498
- * @returns The chosen candidates, in `SCHEDULING_ROLES` order.
562
+ * @returns The chosen candidates in `BOOKABLE_ACTOR_TYPES` order
499
563
  */
500
564
  export declare function getSelectedCandidates(selections: ActorSelections): ScheduleCandidate[];
501
565
 
@@ -506,34 +570,59 @@ export declare function getSelectedCandidates(selections: ActorSelections): Sche
506
570
  */
507
571
  export declare function getSelectionError(selections: ActorSelections): string | undefined;
508
572
 
573
+ /**
574
+ * Search window for a calendar day in the given timezone, starting from now if that day is already underway.
575
+ * @param day - The day to search, as local midnight. Bound to the site's timezone.
576
+ * @param timezone - IANA timezone identifier. Defaults to the browser's.
577
+ * @returns Range from the start of that day, or from now, through the next day's midnight.
578
+ */
579
+ export declare function getZonedDayRange(day: Date, timezone?: string): Required<DateRange>;
580
+
509
581
  /**
510
582
  * Groups proposed appointments into days, and each day into the sets of actors
511
583
  * offering those times.
512
584
  *
513
585
  * @param appointments - Proposed appointments from `$find`.
514
586
  * @param timezone - IANA timezone identifier. Defaults to the browser's.
587
+ * @param searched - Days to list whether or not they offer anything, so a searched day
588
+ * that came back empty still shows up rather than going missing. Read on the local
589
+ * calendar, matching how a day is picked.
515
590
  * @returns Days in ascending order, each holding its groups.
516
591
  */
517
- export declare function groupAppointmentsByDay(appointments: readonly Appointment[], timezone?: string): AppointmentDay[];
592
+ export declare function groupAppointmentsByDay(appointments: readonly Appointment[], timezone?: string, searched?: DateRange): AppointmentDay[];
518
593
 
519
- export declare function isBookableActorType(value: string | undefined): value is BookableActorType;
594
+ /**
595
+ * Whether every requirement a visit type names has been answered.
596
+ * @param values - What the fields are holding.
597
+ * @param requirements - What the visit type requires, from its eligibility codes.
598
+ * @returns True once each required field holds a value. A value nothing asked for is not weighed.
599
+ */
600
+ export declare function hasRequiredValues(values: BookingRequirementValues, requirements: ReadonlySet<SchedulingRequirement>): boolean;
520
601
 
521
602
  /**
522
- * Reports whether a role has to be filled.
523
- * @param role - The role being filled.
603
+ * Reports whether an actorType has to be chosen to be a valid booking
604
+ * @param actorType - The actor type being chosen.
524
605
  * @returns Whether a search can run without it.
525
606
  */
526
- export declare function isRoleRequired(role: SchedulingRole): boolean;
607
+ export declare function isActorTypeRequired(actorType: SchedulingActorType): boolean;
608
+
609
+ export declare function isBookableActorType(value: string | undefined): value is BookableActorType;
527
610
 
528
611
  /**
529
- * Returns whether two instants fall on the same local day.
530
- * @param left - The first instant.
531
- * @param right - The second, or undefined when there is nothing to compare.
532
- * @returns True when both fall on the same local day.
612
+ * Whether one requirement has been answered.
613
+ * @param requirement - The requirement to weigh.
614
+ * @param values - What the fields are holding.
615
+ * @returns True when the field answering that requirement holds a value.
533
616
  */
534
- export declare function isSameDay(left: Date, right: Date | undefined): boolean;
617
+ export declare function isRequirementAnswered(requirement: SchedulingRequirement, values: BookingRequirementValues): boolean;
535
618
 
536
- export declare function isSchedulingActorType(value: string | undefined): value is SchedulingActorType;
619
+ /**
620
+ * Whether a timezone is the viewer's own.
621
+ * @param timezone - IANA timezone the times are in, or undefined when it could not be resolved.
622
+ * @param viewer - The viewer's IANA timezone. Defaults to the browser's
623
+ * @returns True when the zone is the viewer's, or when there is no zone to compare.
624
+ */
625
+ export declare function isViewerTimezone(timezone: string | undefined, viewer?: string): boolean;
537
626
 
538
627
  /**
539
628
  * The longest window `Appointment/$find` accepts. Requests wider than this are
@@ -591,7 +680,13 @@ export declare function parseDayKey(key: string): Date;
591
680
  */
592
681
  export declare function parseZonedTime(day: Date, time: string, timezone?: string): Date | undefined;
593
682
 
594
- export declare const ROLE_LABELS: Record<SchedulingRole, string>;
683
+ /**
684
+ * Actor types that must be chosen before a search can run.
685
+ *
686
+ * Locations and devices are left optional: a service may have location schedules
687
+ * configured without every booking needing to hold one.
688
+ */
689
+ export declare const REQUIRED_ACTOR_TYPES: ReadonlySet<SchedulingActorType>;
595
690
 
596
691
  /**
597
692
  * Edits weekly availability for one visit service type, either as a Schedule's
@@ -633,27 +728,13 @@ export declare interface ScheduleOverrideEditorProps extends CommonProps {
633
728
  readonly onSave: (updatedSchedule: Schedule) => void | Promise<void>;
634
729
  }
635
730
 
636
- /** Resource types that may appear in `Schedule.actor`. */
637
- export declare const SCHEDULING_ACTOR_TYPES: readonly ["Practitioner", "PractitionerRole", "Location", "Device"];
638
-
639
- /**
640
- * The parts of an appointment a user chooses, in the order they are asked about.
641
- *
642
- * Each role becomes one question, because `$find` intersects the schedules it is
643
- * given: a time is offered only when the provider *and* the room *and* the
644
- * device are all free for it.
645
- */
646
- export declare const SCHEDULING_ROLES: readonly ["provider", "room", "device"];
647
-
648
731
  /**
649
732
  * A reference to something a Schedule belongs to. The same union an Appointment
650
733
  * accepts as a participant, so an actor can be carried straight across.
651
734
  */
652
735
  export declare type SchedulingActor = Schedule['actor'][number];
653
736
 
654
- export declare type SchedulingActorType = (typeof SCHEDULING_ACTOR_TYPES)[number];
655
-
656
- export declare type SchedulingRole = (typeof SCHEDULING_ROLES)[number];
737
+ export declare type SchedulingActorType = Dereference<SchedulingActor>['resourceType'];
657
738
 
658
739
  /**
659
740
  * A data-coordination component pairing {@link CalendarsPanel} with {@link MultiCalendar}.
@@ -665,6 +746,13 @@ export declare type SchedulingRole = (typeof SCHEDULING_ROLES)[number];
665
746
  * The form writes the booking and announces what it wrote, which is what puts the
666
747
  * new appointment on the calendar beside it — a host supplies no data for any of it.
667
748
  * What was written is reported through `onBooked`, for a host that wants to say so.
749
+ * - Shows what is booked: clicking an appointment opens {@link AppointmentDetails} in a
750
+ * drawer over the calendar, describing the visit and offering to cancel it. Cancelling
751
+ * is what takes the time back off the calendar, again without a host supplying anything.
752
+ * - Highlights the time last chosen, wherever it was chosen: the click that opened the
753
+ * pane, then whatever the form's time search settles on, and nothing while the form
754
+ * holds no time. The calendar is never moved to reach it — a highlight off the week
755
+ * on screen is kept, and is drawn again on paging back to it.
668
756
  *
669
757
  * @param props - Component props
670
758
  * @returns A React Node with the coordinated Calendars panel + calendar UI in it
@@ -673,7 +761,17 @@ export declare function SchedulingWorkspace(props: SchedulingWorkspaceProps): JS
673
761
 
674
762
  export declare interface SchedulingWorkspaceProps {
675
763
  readonly className?: string;
764
+ /** The ValueSet the procedure code field binds to. Defaults to full CPT valueset. */
765
+ readonly procedureBinding?: string;
766
+ /** The ValueSet the diagnosis code field binds to. Defaults to full ICD-10-CM valueset. */
767
+ readonly diagnosisBinding?: string;
676
768
  readonly onBooked?: (booking: AppointmentBooking) => void | Promise<void>;
769
+ readonly onCancelled?: (appointment: WithId<Appointment>) => void | Promise<void>;
770
+ /**
771
+ * Overrides the value set the appointment detail view offers cancellation reasons
772
+ * from, for a host coding them against its own terminology.
773
+ */
774
+ readonly appointmentCancellationReasonValueSet?: string;
677
775
  }
678
776
 
679
777
  /**
@@ -689,8 +787,8 @@ export declare function searchScheduleCandidates(medplum: MedplumClient, service
689
787
 
690
788
  export declare interface SearchScheduleCandidatesOptions {
691
789
  /** Which of the service's actors to offer. */
692
- readonly role: SchedulingRole;
693
- /** What the user typed. Empty offers whatever the role has, unfiltered by name. */
790
+ readonly actorType: SchedulingActorType;
791
+ /** What the user typed. Empty offers whatever the actor type has, unfiltered by name. */
694
792
  readonly query: string;
695
793
  /**
696
794
  * The site being booked at. Actors sited elsewhere are left out: a room or a
@@ -738,10 +836,28 @@ export declare interface ServiceDefaultEditorProps extends CommonProps {
738
836
  */
739
837
  export declare function setScheduleAvailability(schedule: Schedule, service: WithId<HealthcareService>, availableTime: HealthcareServiceAvailableTime[]): Schedule;
740
838
 
839
+ /**
840
+ * Returns the first instant of a day, so that a range opens at the top of it.
841
+ * @param date - Any instant during the day.
842
+ * @returns Local midnight at the start of that day.
843
+ */
844
+ export declare function startOfDay(date: Date): Date;
845
+
741
846
  export declare type TimeOfDay = 'any' | 'morning' | 'afternoon';
742
847
 
848
+ /**
849
+ * Reads the codes a field is holding into a list of codings.
850
+ * @param elements - What the field is holding.
851
+ * @returns The codings to record, dropping anything that never became a code.
852
+ */
853
+ export declare function toCodings(elements: readonly ValueSetExpansionContains[]): Coding[];
854
+
743
855
  /**
744
856
  * Searches for the times an appointment could be held at.
857
+ *
858
+ * A window `$find` would refuse is caught here rather than sent: this is the layer that
859
+ * decides whether the request is made, so it is the layer that says why it was not.
860
+ *
745
861
  * @param options - The service, actor combinations, days, and page size.
746
862
  * @returns The times offered, plus load and error state.
747
863
  */
@@ -768,6 +884,8 @@ export declare interface UseProposedAppointmentsResult {
768
884
  readonly loading: boolean;
769
885
  /** Set only when every combination failed. */
770
886
  readonly error: Error | undefined;
887
+ /** A window `$find` will not answer, caught before the request is made. */
888
+ readonly windowError: string | undefined;
771
889
  }
772
890
 
773
891
  /**