@medplum/react 0.9.20 → 0.9.23

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.
package/dist/cjs/index.js CHANGED
@@ -1776,18 +1776,24 @@
1776
1776
  }
1777
1777
 
1778
1778
  function DiagnosticReportDisplay(props) {
1779
- var _a;
1779
+ var _a, _b;
1780
1780
  const diagnosticReport = useResource(props.value);
1781
+ const specimen = useResource((_a = diagnosticReport === null || diagnosticReport === void 0 ? void 0 : diagnosticReport.specimen) === null || _a === void 0 ? void 0 : _a[0]);
1781
1782
  if (!diagnosticReport) {
1782
1783
  return null;
1783
1784
  }
1784
- let textContent = undefined;
1785
+ let textContent = '';
1785
1786
  if (diagnosticReport.presentedForm && diagnosticReport.presentedForm.length > 0) {
1786
1787
  const pf = diagnosticReport.presentedForm[0];
1787
- if (((_a = pf.contentType) === null || _a === void 0 ? void 0 : _a.startsWith('text/plain')) && pf.data) {
1788
+ if (((_b = pf.contentType) === null || _b === void 0 ? void 0 : _b.startsWith('text/plain')) && pf.data) {
1788
1789
  textContent = window.atob(pf.data);
1789
1790
  }
1790
1791
  }
1792
+ if (specimen === null || specimen === void 0 ? void 0 : specimen.note) {
1793
+ for (const note of specimen.note) {
1794
+ textContent += note.text + '\n\n';
1795
+ }
1796
+ }
1791
1797
  return (React__default["default"].createElement("div", { className: "medplum-diagnostic-report" },
1792
1798
  React__default["default"].createElement("h1", null, "Diagnostic Report"),
1793
1799
  React__default["default"].createElement("div", { className: "medplum-diagnostic-report-header" },
@@ -1807,8 +1813,8 @@
1807
1813
  diagnosticReport.status && (React__default["default"].createElement("dl", null,
1808
1814
  React__default["default"].createElement("dt", null, "Status"),
1809
1815
  React__default["default"].createElement("dd", null, core.capitalize(diagnosticReport.status))))),
1810
- textContent && React__default["default"].createElement("pre", null, textContent),
1811
- diagnosticReport.result && React__default["default"].createElement(ObservationTable, { value: diagnosticReport.result })));
1816
+ diagnosticReport.result && React__default["default"].createElement(ObservationTable, { value: diagnosticReport.result }),
1817
+ textContent && React__default["default"].createElement("pre", null, textContent.trim())));
1812
1818
  }
1813
1819
  function ObservationTable(props) {
1814
1820
  var _a;
@@ -2184,8 +2190,7 @@
2184
2190
  setHistory({});
2185
2191
  return;
2186
2192
  }
2187
- const batchRequest = buildSearchRequests(resource);
2188
- medplum.post('fhir/R4', batchRequest).then(handleBatchResponse);
2193
+ medplum.executeBatch(buildSearchRequests(resource)).then(handleBatchResponse);
2189
2194
  }, [medplum, resource, buildSearchRequests]);
2190
2195
  React.useEffect(() => {
2191
2196
  loadTimeline();
@@ -4613,31 +4618,64 @@
4613
4618
  return (React__default["default"].createElement(TextArea, { name: name, defaultValue: initial === null || initial === void 0 ? void 0 : initial.valueString, onChange: (newValue) => onChangeAnswer({ valueString: newValue }) }));
4614
4619
  case exports.QuestionnaireItemType.url:
4615
4620
  return (React__default["default"].createElement(Input, { type: "url", name: name, defaultValue: initial === null || initial === void 0 ? void 0 : initial.valueUri, onChange: (newValue) => onChangeAnswer({ valueUri: newValue }) }));
4616
- case exports.QuestionnaireItemType.choice:
4617
- case exports.QuestionnaireItemType.openChoice:
4618
- return (React__default["default"].createElement("div", null, item.answerOption &&
4619
- item.answerOption.map((option, index) => {
4620
- const valueElementDefinition = core.globalSchema.types['QuestionnaireItemAnswerOption'].properties['value[x]'];
4621
- const optionValue = core.getTypedPropertyValue({ type: 'QuestionnaireItemAnswerOption', value: option }, 'value');
4622
- const initialValue = core.getTypedPropertyValue({ type: 'QuestionnaireItemInitial', value: initial }, 'value');
4623
- const propertyName = 'value' + core.capitalize(optionValue.type);
4624
- const optionName = `${name}-option-${index}`;
4625
- return (React__default["default"].createElement("div", { key: optionName, className: "medplum-questionnaire-option-row" },
4626
- React__default["default"].createElement("div", { className: "medplum-questionnaire-option-checkbox" },
4627
- React__default["default"].createElement("input", { type: "radio", id: optionName, name: name, value: optionValue.value, defaultChecked: initialValue && core.stringify(optionValue) === core.stringify(initialValue), onChange: () => onChangeAnswer({ [propertyName]: optionValue.value }) })),
4628
- React__default["default"].createElement("div", null,
4629
- React__default["default"].createElement("label", { htmlFor: optionName },
4630
- React__default["default"].createElement(ResourcePropertyDisplay, { property: valueElementDefinition, propertyType: optionValue.type, value: optionValue.value })))));
4631
- })));
4632
4621
  case exports.QuestionnaireItemType.attachment:
4633
4622
  return (React__default["default"].createElement(AttachmentInput, { name: name, defaultValue: initial === null || initial === void 0 ? void 0 : initial.valueAttachment, onChange: (newValue) => onChangeAnswer({ valueAttachment: newValue }) }));
4634
4623
  case exports.QuestionnaireItemType.reference:
4635
4624
  return (React__default["default"].createElement(ReferenceInput, { name: name, defaultValue: initial === null || initial === void 0 ? void 0 : initial.valueReference, onChange: (newValue) => onChangeAnswer({ valueReference: newValue }) }));
4636
4625
  case exports.QuestionnaireItemType.quantity:
4637
4626
  return (React__default["default"].createElement(QuantityInput, { name: name, defaultValue: initial === null || initial === void 0 ? void 0 : initial.valueQuantity, onChange: (newValue) => onChangeAnswer({ valueQuantity: newValue }) }));
4627
+ case exports.QuestionnaireItemType.choice:
4628
+ case exports.QuestionnaireItemType.openChoice:
4629
+ if (isDropDownChoice(item)) {
4630
+ return (React__default["default"].createElement(QuestionnaireChoiceDropDownInput, { name: name, item: item, initial: initial, onChangeAnswer: onChangeAnswer }));
4631
+ }
4632
+ else {
4633
+ return (React__default["default"].createElement(QuestionnaireChoiceRadioInput, { name: name, item: item, initial: initial, onChangeAnswer: onChangeAnswer }));
4634
+ }
4638
4635
  }
4639
4636
  return null;
4640
4637
  }
4638
+ function QuestionnaireChoiceDropDownInput(props) {
4639
+ const { name, item, initial } = props;
4640
+ const valueElementDefinition = core.globalSchema.types['QuestionnaireItemAnswerOption'].properties['value[x]'];
4641
+ const initialValue = core.getTypedPropertyValue({ type: 'QuestionnaireItemInitial', value: initial }, 'value');
4642
+ return (React__default["default"].createElement("select", { id: name, name: name, className: "medplum-select", onChange: (e) => {
4643
+ const index = e.currentTarget.selectedIndex;
4644
+ if (index === 0) {
4645
+ props.onChangeAnswer({});
4646
+ return;
4647
+ }
4648
+ const option = item.answerOption[index - 1];
4649
+ const optionValue = core.getTypedPropertyValue({ type: 'QuestionnaireItemAnswerOption', value: option }, 'value');
4650
+ const propertyName = 'value' + core.capitalize(optionValue.type);
4651
+ props.onChangeAnswer({ [propertyName]: optionValue.value });
4652
+ } },
4653
+ React__default["default"].createElement("option", null),
4654
+ item.answerOption &&
4655
+ item.answerOption.map((option, index) => {
4656
+ const optionValue = core.getTypedPropertyValue({ type: 'QuestionnaireItemAnswerOption', value: option }, 'value');
4657
+ const optionName = `${name}-option-${index}`;
4658
+ return (React__default["default"].createElement("option", { key: optionName, value: optionValue.value, selected: initialValue && core.stringify(optionValue) === core.stringify(initialValue) },
4659
+ React__default["default"].createElement(ResourcePropertyDisplay, { property: valueElementDefinition, propertyType: optionValue.type, value: optionValue.value })));
4660
+ })));
4661
+ }
4662
+ function QuestionnaireChoiceRadioInput(props) {
4663
+ const { name, item, initial, onChangeAnswer } = props;
4664
+ const valueElementDefinition = core.globalSchema.types['QuestionnaireItemAnswerOption'].properties['value[x]'];
4665
+ const initialValue = core.getTypedPropertyValue({ type: 'QuestionnaireItemInitial', value: initial }, 'value');
4666
+ return (React__default["default"].createElement(React__default["default"].Fragment, null, item.answerOption &&
4667
+ item.answerOption.map((option, index) => {
4668
+ const optionValue = core.getTypedPropertyValue({ type: 'QuestionnaireItemAnswerOption', value: option }, 'value');
4669
+ const propertyName = 'value' + core.capitalize(optionValue.type);
4670
+ const optionName = `${name}-option-${index}`;
4671
+ return (React__default["default"].createElement("div", { key: optionName, className: "medplum-questionnaire-option-row" },
4672
+ React__default["default"].createElement("div", { className: "medplum-questionnaire-option-checkbox" },
4673
+ React__default["default"].createElement("input", { type: "radio", id: optionName, name: name, value: optionValue.value, defaultChecked: initialValue && core.stringify(optionValue) === core.stringify(initialValue), onChange: () => onChangeAnswer({ [propertyName]: optionValue.value }) })),
4674
+ React__default["default"].createElement("div", null,
4675
+ React__default["default"].createElement("label", { htmlFor: optionName },
4676
+ React__default["default"].createElement(ResourcePropertyDisplay, { property: valueElementDefinition, propertyType: optionValue.type, value: optionValue.value })))));
4677
+ })));
4678
+ }
4641
4679
  function buildInitialResponse(questionnaire) {
4642
4680
  const response = {
4643
4681
  resourceType: 'QuestionnaireResponse',
@@ -4663,6 +4701,14 @@
4663
4701
  // have the same properties.
4664
4702
  return Object.assign({}, answer);
4665
4703
  }
4704
+ function isDropDownChoice(item) {
4705
+ var _a;
4706
+ return !!((_a = item.extension) === null || _a === void 0 ? void 0 : _a.some((e) => {
4707
+ var _a, _b, _c;
4708
+ return e.url === 'http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl' &&
4709
+ ((_c = (_b = (_a = e.valueCodeableConcept) === null || _a === void 0 ? void 0 : _a.coding) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.code) === 'drop-down';
4710
+ }));
4711
+ }
4666
4712
 
4667
4713
  function QuestionnaireBuilder(props) {
4668
4714
  const medplum = useMedplum();
@@ -4864,6 +4910,163 @@
4864
4910
  return options.map((option) => (Object.assign(Object.assign({}, option), { id: option.id || generateId() })));
4865
4911
  }
4866
4912
 
4913
+ /**
4914
+ * Dynamically creates a script tag for the specified JavaScript file.
4915
+ * @param src The JavaScript file URL.
4916
+ */
4917
+ function createScriptTag(src, onload) {
4918
+ const head = document.getElementsByTagName('head')[0];
4919
+ const script = document.createElement('script');
4920
+ script.async = true;
4921
+ script.src = src;
4922
+ script.onload = onload || null;
4923
+ head.appendChild(script);
4924
+ }
4925
+
4926
+ function GoogleButton(props) {
4927
+ const medplum = useMedplum();
4928
+ const { googleClientId, handleGoogleCredential } = props;
4929
+ const parentRef = React.useRef(null);
4930
+ const [scriptLoaded, setScriptLoaded] = React.useState(typeof google !== 'undefined');
4931
+ const [initialized, setInitialized] = React.useState(false);
4932
+ const [buttonRendered, setButtonRendered] = React.useState(false);
4933
+ React.useEffect(() => {
4934
+ if (typeof google === 'undefined') {
4935
+ createScriptTag('https://accounts.google.com/gsi/client', () => setScriptLoaded(true));
4936
+ return;
4937
+ }
4938
+ if (!initialized) {
4939
+ google.accounts.id.initialize({
4940
+ client_id: googleClientId,
4941
+ callback: handleGoogleCredential,
4942
+ });
4943
+ setInitialized(true);
4944
+ }
4945
+ if (parentRef.current && !buttonRendered) {
4946
+ google.accounts.id.renderButton(parentRef.current, {});
4947
+ setButtonRendered(true);
4948
+ }
4949
+ }, [medplum, googleClientId, initialized, scriptLoaded, parentRef, buttonRendered, handleGoogleCredential]);
4950
+ if (!googleClientId) {
4951
+ return null;
4952
+ }
4953
+ return React__default["default"].createElement("div", { ref: parentRef });
4954
+ }
4955
+ function getGoogleClientId(clientId) {
4956
+ var _a, _b;
4957
+ if (clientId) {
4958
+ return clientId;
4959
+ }
4960
+ const origin = window.location.protocol + '//' + window.location.host;
4961
+ const authorizedOrigins = (_b = (_a = "http://localhost:3000,http://localhost:6006,https://app.medplum.com,https://docs.medplum.com") === null || _a === void 0 ? void 0 : _a.split(',')) !== null && _b !== void 0 ? _b : [];
4962
+ if (authorizedOrigins.includes(origin)) {
4963
+ return "921088377005-3j1sa10vr6hj86jgmdfh2l53v3mp7lfi.apps.googleusercontent.com";
4964
+ }
4965
+ return undefined;
4966
+ }
4967
+
4968
+ /**
4969
+ * Dynamically loads the recaptcha script.
4970
+ * We do not want to load the script on page load unless the user needs it.
4971
+ */
4972
+ function initRecaptcha() {
4973
+ if (typeof grecaptcha === 'undefined') {
4974
+ createScriptTag('https://www.google.com/recaptcha/api.js?render=' + process.env.RECAPTCHA_SITE_KEY);
4975
+ }
4976
+ }
4977
+ /**
4978
+ * Starts a request to generate a recapcha token.
4979
+ * @returns Promise to a recaptcha token for the current user.
4980
+ */
4981
+ function getRecaptcha() {
4982
+ return new Promise((resolve) => {
4983
+ grecaptcha.ready(() => {
4984
+ grecaptcha.execute(process.env.RECAPTCHA_SITE_KEY, { action: 'submit' }).then(resolve);
4985
+ });
4986
+ });
4987
+ }
4988
+
4989
+ function RegisterForm(props) {
4990
+ const medplum = useMedplum();
4991
+ const googleClientId = getGoogleClientId(props.googleClientId);
4992
+ const [outcome, setOutcome] = React.useState();
4993
+ const issues = getIssuesForExpression(outcome, undefined);
4994
+ React.useEffect(initRecaptcha, []);
4995
+ function handleAuthResponse(registerRequest, partialLogin) {
4996
+ return __awaiter(this, void 0, void 0, function* () {
4997
+ try {
4998
+ let login;
4999
+ if (props.type === 'patient') {
5000
+ login = yield medplum.startNewPatient(registerRequest, partialLogin);
5001
+ }
5002
+ else {
5003
+ login = yield medplum.startNewProject(registerRequest, partialLogin);
5004
+ }
5005
+ yield medplum.processCode(login.code);
5006
+ props.onSuccess();
5007
+ }
5008
+ catch (err) {
5009
+ setOutcome(err);
5010
+ }
5011
+ });
5012
+ }
5013
+ return (React__default["default"].createElement(Document, { width: 450 },
5014
+ React__default["default"].createElement(Form, { style: { maxWidth: 400 }, onSubmit: (formData) => __awaiter(this, void 0, void 0, function* () {
5015
+ try {
5016
+ const recaptchaToken = yield getRecaptcha();
5017
+ const registerRequest = Object.assign(Object.assign({}, formData), { recaptchaToken });
5018
+ const userLogin = yield medplum.startNewUser(registerRequest);
5019
+ handleAuthResponse(registerRequest, userLogin);
5020
+ }
5021
+ catch (err) {
5022
+ setOutcome(err);
5023
+ }
5024
+ }) },
5025
+ React__default["default"].createElement("div", { className: "medplum-center" }, props.children),
5026
+ issues && (React__default["default"].createElement("div", { className: "medplum-input-error" }, issues.map((issue) => {
5027
+ var _a, _b;
5028
+ return (React__default["default"].createElement("div", { "data-testid": "text-field-error", key: (_a = issue.details) === null || _a === void 0 ? void 0 : _a.text }, (_b = issue.details) === null || _b === void 0 ? void 0 : _b.text));
5029
+ }))),
5030
+ googleClientId && (React__default["default"].createElement(React__default["default"].Fragment, null,
5031
+ React__default["default"].createElement("div", { className: "medplum-signin-google-container" },
5032
+ React__default["default"].createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: (response) => __awaiter(this, void 0, void 0, function* () {
5033
+ try {
5034
+ const loginRequest = {
5035
+ googleClientId: response.clientId,
5036
+ googleCredential: response.credential,
5037
+ };
5038
+ const userLogin = yield medplum.startGoogleLogin(loginRequest);
5039
+ const googleClaims = core.parseJWTPayload(loginRequest.googleCredential);
5040
+ const registerRequest = {
5041
+ firstName: googleClaims.given_name,
5042
+ lastName: googleClaims.family_name,
5043
+ email: googleClaims.email,
5044
+ };
5045
+ handleAuthResponse(registerRequest, userLogin);
5046
+ }
5047
+ catch (err) {
5048
+ setOutcome(err);
5049
+ }
5050
+ }) })),
5051
+ React__default["default"].createElement("div", { className: "medplum-signin-separator" }, "or"))),
5052
+ React__default["default"].createElement(FormSection, { title: "First Name", htmlFor: "firstName", outcome: outcome },
5053
+ React__default["default"].createElement(Input, { name: "firstName", type: "text", testid: "firstName", placeholder: "First name", required: true, autoFocus: true, outcome: outcome })),
5054
+ React__default["default"].createElement(FormSection, { title: "Last Name", htmlFor: "lastName", outcome: outcome },
5055
+ React__default["default"].createElement(Input, { name: "lastName", type: "text", testid: "lastName", placeholder: "Last name", required: true, outcome: outcome })),
5056
+ props.type === 'project' && (React__default["default"].createElement(FormSection, { title: "Project Name", htmlFor: "projectName", outcome: outcome },
5057
+ React__default["default"].createElement(Input, { name: "projectName", type: "text", testid: "projectName", placeholder: "My Project", required: true, outcome: outcome }))),
5058
+ React__default["default"].createElement(FormSection, { title: "Email", htmlFor: "email", outcome: outcome },
5059
+ React__default["default"].createElement(Input, { name: "email", type: "email", testid: "email", placeholder: "name@domain.com", required: true, outcome: outcome })),
5060
+ React__default["default"].createElement(FormSection, { title: "Password", htmlFor: "password", outcome: outcome },
5061
+ React__default["default"].createElement(Input, { name: "password", type: "password", testid: "password", autoComplete: "off", required: true, outcome: outcome })),
5062
+ React__default["default"].createElement("div", { className: "medplum-signin-buttons" },
5063
+ React__default["default"].createElement("div", null,
5064
+ React__default["default"].createElement("input", { type: "checkbox", id: "remember", name: "remember", value: "true" }),
5065
+ React__default["default"].createElement("label", { htmlFor: "remember" }, "Remember me")),
5066
+ React__default["default"].createElement("div", null,
5067
+ React__default["default"].createElement(Button, { type: "submit", testid: "submit" }, "Create account"))))));
5068
+ }
5069
+
4867
5070
  function StatusBadge(props) {
4868
5071
  return React__default["default"].createElement("span", { className: `medplum-status medplum-status-${props.status}` }, props.status);
4869
5072
  }
@@ -4876,7 +5079,7 @@
4876
5079
  const [responseBundle, setResponseBundle] = React.useState();
4877
5080
  React.useEffect(() => {
4878
5081
  if (requestGroup && !startedLoading) {
4879
- medplum.post('fhir/R4', buildBatchRequest(requestGroup)).then(setResponseBundle);
5082
+ medplum.executeBatch(buildBatchRequest(requestGroup)).then(setResponseBundle);
4880
5083
  setStartedLoading(true);
4881
5084
  }
4882
5085
  }, [medplum, requestGroup, startedLoading]);
@@ -5231,6 +5434,165 @@
5231
5434
  return `/${resource.resourceType}/${resource.id}/_history/${(_a = resource.meta) === null || _a === void 0 ? void 0 : _a.versionId}`;
5232
5435
  }
5233
5436
 
5437
+ /**
5438
+ * Returns a month display string (e.g. "January 2020").
5439
+ * @param date Any date within the month.
5440
+ * @returns The month display string (e.g. "January 2020")
5441
+ */
5442
+ function getMonthString(date) {
5443
+ return date.toLocaleString('default', { month: 'long' }) + ' ' + date.getFullYear();
5444
+ }
5445
+ function CalendarInput(props) {
5446
+ const [month, setMonth] = React.useState(getStartMonth);
5447
+ function moveMonth(delta) {
5448
+ setMonth((currMonth) => {
5449
+ const prevMonth = new Date(currMonth.getTime());
5450
+ prevMonth.setMonth(currMonth.getMonth() + delta);
5451
+ return prevMonth;
5452
+ });
5453
+ }
5454
+ const grid = React.useMemo(() => buildGrid(month, props.slots), [month, props.slots]);
5455
+ return (React__default["default"].createElement("div", null,
5456
+ React__default["default"].createElement(InputRow, null,
5457
+ React__default["default"].createElement("p", { style: { flex: 1 } }, getMonthString(month)),
5458
+ React__default["default"].createElement("p", null,
5459
+ React__default["default"].createElement(Button, { label: "Previous month", onClick: () => moveMonth(-1) }, "<"),
5460
+ React__default["default"].createElement(Button, { label: "Next month", onClick: () => moveMonth(1) }, ">"))),
5461
+ React__default["default"].createElement("table", { className: "medplum-calendar-table" },
5462
+ React__default["default"].createElement("thead", null,
5463
+ React__default["default"].createElement("tr", null,
5464
+ React__default["default"].createElement("th", null, "SUN"),
5465
+ React__default["default"].createElement("th", null, "MON"),
5466
+ React__default["default"].createElement("th", null, "TUE"),
5467
+ React__default["default"].createElement("th", null, "WED"),
5468
+ React__default["default"].createElement("th", null, "THU"),
5469
+ React__default["default"].createElement("th", null, "FRI"),
5470
+ React__default["default"].createElement("th", null, "SAT"))),
5471
+ React__default["default"].createElement("tbody", null, grid.map((week, weekIndex) => (React__default["default"].createElement("tr", { key: 'week-' + weekIndex }, week.map((day, dayIndex) => (React__default["default"].createElement("td", { key: 'day-' + dayIndex }, day && (React__default["default"].createElement("button", { disabled: !day.available, onClick: () => props.onClick(day.date) }, day.date.getDate()))))))))))));
5472
+ }
5473
+ function getStartMonth() {
5474
+ const result = new Date();
5475
+ result.setDate(1);
5476
+ result.setHours(0, 0, 0, 0);
5477
+ return result;
5478
+ }
5479
+ function buildGrid(startDate, slots) {
5480
+ const d = new Date(startDate.getFullYear(), startDate.getMonth());
5481
+ const grid = [];
5482
+ let row = [];
5483
+ // Fill leading empty days
5484
+ for (let i = 0; i < d.getDay(); i++) {
5485
+ row.push(undefined);
5486
+ }
5487
+ while (d.getMonth() === startDate.getMonth()) {
5488
+ row.push({
5489
+ date: new Date(d.getTime()),
5490
+ // available: isAvailable(d),
5491
+ available: isDayAvailable(d, slots),
5492
+ });
5493
+ if (d.getDay() === 6) {
5494
+ grid.push(row);
5495
+ row = [];
5496
+ }
5497
+ d.setDate(d.getDate() + 1);
5498
+ }
5499
+ // Fill trailing empty days
5500
+ if (d.getDay() !== 0) {
5501
+ for (let i = d.getDay(); i < 7; i++) {
5502
+ row.push(undefined);
5503
+ }
5504
+ grid.push(row);
5505
+ }
5506
+ return grid;
5507
+ }
5508
+ /**
5509
+ * Returns true if the given date is available for booking.
5510
+ * @param day The day to check.
5511
+ * @param slots The list of available slots.
5512
+ * @returns True if there are any available slots for the day.
5513
+ */
5514
+ function isDayAvailable(day, slots) {
5515
+ // Note that slot start and end time may or may not be in UTC.
5516
+ for (const slot of slots) {
5517
+ const slotStart = new Date(slot.start);
5518
+ if (slotStart.getFullYear() === day.getFullYear() &&
5519
+ slotStart.getMonth() === day.getMonth() &&
5520
+ slotStart.getDate() === day.getDate()) {
5521
+ return true;
5522
+ }
5523
+ }
5524
+ return false;
5525
+ }
5526
+
5527
+ function Scheduler(props) {
5528
+ var _a;
5529
+ const medplum = useMedplum();
5530
+ const schedule = useResource(props.schedule);
5531
+ const [slots, setSlots] = React.useState();
5532
+ const slotsRef = React.useRef();
5533
+ slotsRef.current = slots;
5534
+ const [date, setDate] = React.useState();
5535
+ const [slot, setSlot] = React.useState();
5536
+ const [info, setInfo] = React.useState();
5537
+ const [form, setForm] = React.useState();
5538
+ React.useEffect(() => {
5539
+ if (schedule) {
5540
+ medplum.search('Slot', 'schedule=' + core.getReferenceString(schedule)).then((bundle) => {
5541
+ setSlots(bundle.entry.map((entry) => entry.resource));
5542
+ });
5543
+ }
5544
+ else {
5545
+ setSlots(undefined);
5546
+ }
5547
+ }, [medplum, schedule]);
5548
+ if (!schedule || !slots) {
5549
+ return null;
5550
+ }
5551
+ const actor = (_a = schedule.actor) === null || _a === void 0 ? void 0 : _a[0];
5552
+ return (React__default["default"].createElement("div", { className: "medplum-calendar-container", "data-testid": "scheduler" },
5553
+ React__default["default"].createElement("div", { className: "medplum-calendar-info-pane" },
5554
+ actor && React__default["default"].createElement(Avatar, { value: actor, size: "large" }),
5555
+ actor && (React__default["default"].createElement("h1", null,
5556
+ React__default["default"].createElement(ResourceName, { value: actor }))),
5557
+ React__default["default"].createElement("p", null, "1 hour"),
5558
+ date && React__default["default"].createElement("p", null, date.toLocaleDateString()),
5559
+ slot && React__default["default"].createElement("p", null, formatTime(new Date(slot.start)))),
5560
+ React__default["default"].createElement("div", { className: "medplum-calendar-selection-pane" },
5561
+ !date && (React__default["default"].createElement("div", null,
5562
+ React__default["default"].createElement("h3", null, "Select date"),
5563
+ React__default["default"].createElement(CalendarInput, { slots: slots, onClick: setDate }))),
5564
+ date && !slot && (React__default["default"].createElement("div", null,
5565
+ React__default["default"].createElement("h3", null, "Select time"),
5566
+ slots.map((s) => {
5567
+ const slotStart = new Date(s.start);
5568
+ return (slotStart.getTime() > date.getTime() &&
5569
+ slotStart.getTime() < date.getTime() + 24 * 3600 * 1000 && (React__default["default"].createElement("div", { key: s.id },
5570
+ React__default["default"].createElement(Button, { style: { width: 150 }, onClick: () => setSlot(s) }, formatTime(slotStart)))));
5571
+ }))),
5572
+ date && slot && !info && (React__default["default"].createElement("div", null,
5573
+ React__default["default"].createElement("h3", null, "Enter your info"),
5574
+ React__default["default"].createElement(FormSection, { title: "Name", htmlFor: "name" },
5575
+ React__default["default"].createElement(Input, { name: "name" })),
5576
+ React__default["default"].createElement(FormSection, { title: "Email", htmlFor: "email" },
5577
+ React__default["default"].createElement(Input, { name: "email" })),
5578
+ React__default["default"].createElement(Button, { primary: true, onClick: () => setInfo('info') }, "Next"))),
5579
+ date && slot && info && !form && (React__default["default"].createElement("div", null,
5580
+ React__default["default"].createElement("h3", null, "Custom questions"),
5581
+ React__default["default"].createElement(FormSection, { title: "Question 1", htmlFor: "q1" },
5582
+ React__default["default"].createElement(Input, { name: "q1" })),
5583
+ React__default["default"].createElement(FormSection, { title: "Question 2", htmlFor: "q2" },
5584
+ React__default["default"].createElement(Input, { name: "email" })),
5585
+ React__default["default"].createElement(FormSection, { title: "Question 3", htmlFor: "q3" },
5586
+ React__default["default"].createElement(Input, { name: "email" })),
5587
+ React__default["default"].createElement(Button, { primary: true, onClick: () => setForm('form') }, "Next"))),
5588
+ date && slot && info && form && (React__default["default"].createElement("div", null,
5589
+ React__default["default"].createElement("h3", null, "You're all set!"),
5590
+ React__default["default"].createElement("p", null, "Check your email for a calendar invite."))))));
5591
+ }
5592
+ function formatTime(date) {
5593
+ return date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
5594
+ }
5595
+
5234
5596
  function ServiceRequestTimeline(props) {
5235
5597
  return (React__default["default"].createElement(ResourceTimeline, { value: props.serviceRequest, buildSearchRequests: (resource) => ({
5236
5598
  resourceType: 'Bundle',
@@ -5278,62 +5640,6 @@
5278
5640
  }) }));
5279
5641
  }
5280
5642
 
5281
- /**
5282
- * Dynamically creates a script tag for the specified JavaScript file.
5283
- * @param src The JavaScript file URL.
5284
- */
5285
- function createScriptTag(src, onload) {
5286
- const head = document.getElementsByTagName('head')[0];
5287
- const script = document.createElement('script');
5288
- script.async = true;
5289
- script.src = src;
5290
- script.onload = onload || null;
5291
- head.appendChild(script);
5292
- }
5293
-
5294
- function GoogleButton(props) {
5295
- const medplum = useMedplum();
5296
- const { handleGoogleCredential } = props;
5297
- const googleClientId = getGoogleClientId(props.googleClientId);
5298
- const parentRef = React.useRef(null);
5299
- const [scriptLoaded, setScriptLoaded] = React.useState(typeof google !== 'undefined');
5300
- const [initialized, setInitialized] = React.useState(false);
5301
- const [buttonRendered, setButtonRendered] = React.useState(false);
5302
- React.useEffect(() => {
5303
- if (typeof google === 'undefined') {
5304
- createScriptTag('https://accounts.google.com/gsi/client', () => setScriptLoaded(true));
5305
- return;
5306
- }
5307
- if (!initialized) {
5308
- google.accounts.id.initialize({
5309
- client_id: googleClientId,
5310
- callback: handleGoogleCredential,
5311
- });
5312
- setInitialized(true);
5313
- }
5314
- if (parentRef.current && !buttonRendered) {
5315
- google.accounts.id.renderButton(parentRef.current, {});
5316
- setButtonRendered(true);
5317
- }
5318
- }, [medplum, googleClientId, initialized, scriptLoaded, parentRef, buttonRendered, handleGoogleCredential]);
5319
- if (!googleClientId) {
5320
- return null;
5321
- }
5322
- return React__default["default"].createElement("div", { ref: parentRef });
5323
- }
5324
- function getGoogleClientId(clientId) {
5325
- var _a, _b;
5326
- if (clientId) {
5327
- return clientId;
5328
- }
5329
- const origin = window.location.protocol + '//' + window.location.host;
5330
- const authorizedOrigins = (_b = (_a = "http://localhost:3000,http://localhost:6006,https://app.medplum.com,https://docs.medplum.com") === null || _a === void 0 ? void 0 : _a.split(',')) !== null && _b !== void 0 ? _b : [];
5331
- if (authorizedOrigins.includes(origin)) {
5332
- return "921088377005-3j1sa10vr6hj86jgmdfh2l53v3mp7lfi.apps.googleusercontent.com";
5333
- }
5334
- return undefined;
5335
- }
5336
-
5337
5643
  function SignInForm(props) {
5338
5644
  const medplum = useMedplum();
5339
5645
  const [login, setLogin] = React.useState(undefined);
@@ -5375,6 +5681,7 @@
5375
5681
  }
5376
5682
  function AuthenticationForm(props) {
5377
5683
  const medplum = useMedplum();
5684
+ const googleClientId = getGoogleClientId(props.googleClientId);
5378
5685
  const [outcome, setOutcome] = React.useState();
5379
5686
  const issues = getIssuesForExpression(outcome, undefined);
5380
5687
  return (React__default["default"].createElement(Form, { style: { maxWidth: 400 }, onSubmit: (formData) => {
@@ -5395,6 +5702,21 @@
5395
5702
  var _a, _b;
5396
5703
  return (React__default["default"].createElement("div", { "data-testid": "text-field-error", key: (_a = issue.details) === null || _a === void 0 ? void 0 : _a.text }, (_b = issue.details) === null || _b === void 0 ? void 0 : _b.text));
5397
5704
  }))),
5705
+ googleClientId && (React__default["default"].createElement(React__default["default"].Fragment, null,
5706
+ React__default["default"].createElement("div", { className: "medplum-signin-google-container" },
5707
+ React__default["default"].createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: (response) => {
5708
+ medplum
5709
+ .startGoogleLogin({
5710
+ clientId: props.clientId,
5711
+ scope: props.scope,
5712
+ nonce: props.nonce,
5713
+ googleClientId: response.clientId,
5714
+ googleCredential: response.credential,
5715
+ })
5716
+ .then(props.handleAuthResponse)
5717
+ .catch(setOutcome);
5718
+ } })),
5719
+ React__default["default"].createElement("div", { className: "medplum-signin-separator" }, "or"))),
5398
5720
  React__default["default"].createElement(FormSection, { title: "Email", htmlFor: "email", outcome: outcome },
5399
5721
  React__default["default"].createElement(Input, { name: "email", type: "email", testid: "email", required: true, autoFocus: true, outcome: outcome })),
5400
5722
  React__default["default"].createElement(FormSection, { title: "Password", htmlFor: "password", outcome: outcome },
@@ -5407,20 +5729,7 @@
5407
5729
  React__default["default"].createElement("input", { type: "checkbox", id: "remember", name: "remember", value: "true" }),
5408
5730
  React__default["default"].createElement("label", { htmlFor: "remember" }, "Remember me")),
5409
5731
  React__default["default"].createElement("div", null,
5410
- React__default["default"].createElement(Button, { type: "submit", testid: "submit" }, "Sign in"))),
5411
- React__default["default"].createElement("div", { className: "medplum-signin-google-container" },
5412
- React__default["default"].createElement(GoogleButton, { googleClientId: props.googleClientId, handleGoogleCredential: (response) => {
5413
- medplum
5414
- .startGoogleLogin({
5415
- clientId: props.clientId,
5416
- scope: props.scope,
5417
- nonce: props.nonce,
5418
- googleClientId: response.clientId,
5419
- googleCredential: response.credential,
5420
- })
5421
- .then(props.handleAuthResponse)
5422
- .catch(setOutcome);
5423
- } }))));
5732
+ React__default["default"].createElement(Button, { type: "submit", testid: "submit" }, "Sign in")))));
5424
5733
  }
5425
5734
  function ProfileForm(props) {
5426
5735
  const medplum = useMedplum();
@@ -5545,6 +5854,7 @@
5545
5854
  exports.RangeDisplay = RangeDisplay;
5546
5855
  exports.RangeInput = RangeInput;
5547
5856
  exports.ReferenceInput = ReferenceInput;
5857
+ exports.RegisterForm = RegisterForm;
5548
5858
  exports.RequestGroupDisplay = RequestGroupDisplay;
5549
5859
  exports.ResourceArrayDisplay = ResourceArrayDisplay;
5550
5860
  exports.ResourceArrayInput = ResourceArrayInput;
@@ -5559,6 +5869,7 @@
5559
5869
  exports.ResourcePropertyInput = ResourcePropertyInput;
5560
5870
  exports.ResourceTable = ResourceTable;
5561
5871
  exports.ResourceTimeline = ResourceTimeline;
5872
+ exports.Scheduler = Scheduler;
5562
5873
  exports.Scrollable = Scrollable;
5563
5874
  exports.SearchChangeEvent = SearchChangeEvent;
5564
5875
  exports.SearchClickEvent = SearchClickEvent;
@@ -5602,11 +5913,13 @@
5602
5913
  exports.deleteFilter = deleteFilter;
5603
5914
  exports.formatRangeString = formatRangeString;
5604
5915
  exports.getOpString = getOpString;
5916
+ exports.getRecaptcha = getRecaptcha;
5605
5917
  exports.getSearchOperators = getSearchOperators;
5606
5918
  exports.getSortField = getSortField;
5607
5919
  exports.getTimeString = getTimeString;
5608
5920
  exports.getValueAndType = getValueAndType;
5609
5921
  exports.hasFilterOnField = hasFilterOnField;
5922
+ exports.initRecaptcha = initRecaptcha;
5610
5923
  exports.isChoiceQuestion = isChoiceQuestion;
5611
5924
  exports.isSortDescending = isSortDescending;
5612
5925
  exports.movePage = movePage;