@tellescope/react-components 1.247.0 → 1.249.0

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.
Files changed (81) hide show
  1. package/lib/cjs/Forms/form_responses.d.ts +11 -0
  2. package/lib/cjs/Forms/form_responses.d.ts.map +1 -1
  3. package/lib/cjs/Forms/form_responses.js +38 -2
  4. package/lib/cjs/Forms/form_responses.js.map +1 -1
  5. package/lib/cjs/Forms/forms.d.ts +4 -3
  6. package/lib/cjs/Forms/forms.d.ts.map +1 -1
  7. package/lib/cjs/Forms/forms.js +41 -15
  8. package/lib/cjs/Forms/forms.js.map +1 -1
  9. package/lib/cjs/Forms/forms.v2.d.ts +2 -2
  10. package/lib/cjs/Forms/forms.v2.d.ts.map +1 -1
  11. package/lib/cjs/Forms/forms.v2.js +8 -8
  12. package/lib/cjs/Forms/forms.v2.js.map +1 -1
  13. package/lib/cjs/Forms/hooks.d.ts +2 -0
  14. package/lib/cjs/Forms/hooks.d.ts.map +1 -1
  15. package/lib/cjs/Forms/hooks.js +57 -16
  16. package/lib/cjs/Forms/hooks.js.map +1 -1
  17. package/lib/cjs/Forms/inputs.d.ts +1 -1
  18. package/lib/cjs/Forms/inputs.d.ts.map +1 -1
  19. package/lib/cjs/Forms/inputs.js +6 -3
  20. package/lib/cjs/Forms/inputs.js.map +1 -1
  21. package/lib/cjs/Forms/inputs.v2.d.ts +1 -2
  22. package/lib/cjs/Forms/inputs.v2.d.ts.map +1 -1
  23. package/lib/cjs/Forms/inputs.v2.js +1 -44
  24. package/lib/cjs/Forms/inputs.v2.js.map +1 -1
  25. package/lib/cjs/Forms/types.d.ts +1 -0
  26. package/lib/cjs/Forms/types.d.ts.map +1 -1
  27. package/lib/cjs/state.d.ts +7 -1
  28. package/lib/cjs/state.d.ts.map +1 -1
  29. package/lib/cjs/state.js +43 -1
  30. package/lib/cjs/state.js.map +1 -1
  31. package/lib/esm/CMS/components.d.ts +1 -0
  32. package/lib/esm/CMS/components.d.ts.map +1 -1
  33. package/lib/esm/Forms/form_responses.d.ts +11 -0
  34. package/lib/esm/Forms/form_responses.d.ts.map +1 -1
  35. package/lib/esm/Forms/form_responses.js +37 -2
  36. package/lib/esm/Forms/form_responses.js.map +1 -1
  37. package/lib/esm/Forms/forms.d.ts +6 -5
  38. package/lib/esm/Forms/forms.d.ts.map +1 -1
  39. package/lib/esm/Forms/forms.js +41 -15
  40. package/lib/esm/Forms/forms.js.map +1 -1
  41. package/lib/esm/Forms/forms.v2.d.ts +4 -4
  42. package/lib/esm/Forms/forms.v2.d.ts.map +1 -1
  43. package/lib/esm/Forms/forms.v2.js +8 -8
  44. package/lib/esm/Forms/forms.v2.js.map +1 -1
  45. package/lib/esm/Forms/hooks.d.ts +2 -0
  46. package/lib/esm/Forms/hooks.d.ts.map +1 -1
  47. package/lib/esm/Forms/hooks.js +58 -17
  48. package/lib/esm/Forms/hooks.js.map +1 -1
  49. package/lib/esm/Forms/inputs.d.ts +3 -3
  50. package/lib/esm/Forms/inputs.d.ts.map +1 -1
  51. package/lib/esm/Forms/inputs.js +6 -3
  52. package/lib/esm/Forms/inputs.js.map +1 -1
  53. package/lib/esm/Forms/inputs.native.d.ts +1 -0
  54. package/lib/esm/Forms/inputs.native.d.ts.map +1 -1
  55. package/lib/esm/Forms/inputs.v2.d.ts +1 -2
  56. package/lib/esm/Forms/inputs.v2.d.ts.map +1 -1
  57. package/lib/esm/Forms/inputs.v2.js +1 -44
  58. package/lib/esm/Forms/inputs.v2.js.map +1 -1
  59. package/lib/esm/Forms/types.d.ts +1 -0
  60. package/lib/esm/Forms/types.d.ts.map +1 -1
  61. package/lib/esm/TwilioVideo/hooks.d.ts +1 -1
  62. package/lib/esm/controls.d.ts +2 -2
  63. package/lib/esm/inputs.d.ts +1 -1
  64. package/lib/esm/inputs.native.d.ts +1 -0
  65. package/lib/esm/inputs.native.d.ts.map +1 -1
  66. package/lib/esm/state.d.ts +336 -330
  67. package/lib/esm/state.d.ts.map +1 -1
  68. package/lib/esm/state.js +42 -1
  69. package/lib/esm/state.js.map +1 -1
  70. package/lib/esm/theme.native.d.ts +1 -0
  71. package/lib/esm/theme.native.d.ts.map +1 -1
  72. package/lib/tsconfig.tsbuildinfo +1 -1
  73. package/package.json +9 -9
  74. package/src/Forms/form_responses.tsx +133 -2
  75. package/src/Forms/forms.tsx +52 -20
  76. package/src/Forms/forms.v2.tsx +13 -7
  77. package/src/Forms/hooks.tsx +67 -14
  78. package/src/Forms/inputs.tsx +6 -3
  79. package/src/Forms/inputs.v2.tsx +1 -50
  80. package/src/Forms/types.ts +1 -0
  81. package/src/state.tsx +37 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tellescope/react-components",
3
- "version": "1.247.0",
3
+ "version": "1.249.0",
4
4
  "description": "",
5
5
  "main": "./lib/cjs/index.js",
6
6
  "module": "./lib/esm/index.js",
@@ -51,13 +51,13 @@
51
51
  "@reduxjs/toolkit": "1.9.0",
52
52
  "@stripe/react-stripe-js": "2.9.0",
53
53
  "@stripe/stripe-js": "1.52.1",
54
- "@tellescope/constants": "1.247.0",
55
- "@tellescope/sdk": "1.247.0",
56
- "@tellescope/types-client": "1.247.0",
57
- "@tellescope/types-models": "1.247.0",
58
- "@tellescope/types-utilities": "1.247.0",
59
- "@tellescope/utilities": "1.247.0",
60
- "@tellescope/validation": "1.247.0",
54
+ "@tellescope/constants": "1.249.0",
55
+ "@tellescope/sdk": "1.249.0",
56
+ "@tellescope/types-client": "1.249.0",
57
+ "@tellescope/types-models": "1.249.0",
58
+ "@tellescope/types-utilities": "1.249.0",
59
+ "@tellescope/utilities": "1.249.0",
60
+ "@tellescope/validation": "1.249.0",
61
61
  "css-to-react-native": "3.0.0",
62
62
  "draft-js": "0.11.7",
63
63
  "draftjs-to-html": "0.9.1",
@@ -84,7 +84,7 @@
84
84
  "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
85
85
  "react-native": "^0.65.0 || ^0.66.0 || ^0.67.0 || ^0.68.0 || ^0.71.0"
86
86
  },
87
- "gitHead": "408421d578d9c63493ff94464da6e4317aa3af20",
87
+ "gitHead": "fb54fd85672184ea09d70e3072e6037eedc84822",
88
88
  "publishConfig": {
89
89
  "access": "public"
90
90
  }
@@ -2,7 +2,7 @@ import React, { useEffect } from "react"
2
2
  import { Divider, Grid, Typography } from "@mui/material"
3
3
  import { Enduser, FormResponse } from "@tellescope/types-client"
4
4
  import { form_response_value_to_string, formatted_date, getOrgnizationLogoURL, remove_script_tags, user_display_name } from "@tellescope/utilities"
5
- import { DownloadFileIconButton, ImageProps, LabeledIconButton, SecureImage, useEndusers, useOrganization, useResolvedSession, useSession, useUsers, value_is_loaded } from "../index"
5
+ import { DownloadFileIconButton, ImageProps, LabeledIconButton, SecureImage, useEndusers, useEnduserMedications, useEnduserObservations, useOrganization, useResolvedSession, useSession, useUsers, value_is_loaded } from "../index"
6
6
  import CloseIcon from '@mui/icons-material/Close';
7
7
  import { DatabaseSelectResponse, FormResponseAnswerAddress, FormResponseValueAnswer } from "@tellescope/types-models"
8
8
  import { Image } from "../layout"
@@ -23,6 +23,135 @@ export const AddressDisplay = ({ value } : { value: Required<FormResponseAnswerA
23
23
  </Grid>
24
24
  )
25
25
 
26
+ type SnapshotRef = { id: string, label: string }
27
+
28
+ export const HistoricalDataSnapshotDisplay = ({ snapshot } : { snapshot: { observations?: SnapshotRef[], medications?: SnapshotRef[], snapshotAt?: string } }) => {
29
+ const { observations: obsRefs = [], medications: medRefs = [], snapshotAt } = snapshot
30
+ const [, { findById: findObservation }] = useEnduserObservations({ dontFetch: true })
31
+ const [, { findById: findMedication }] = useEnduserMedications({ dontFetch: true })
32
+
33
+ const tdStyle = { padding: '6px 8px' } as const
34
+ const deletedStyle = { padding: '6px 8px', color: '#999', fontStyle: 'italic' } as const
35
+
36
+ return (
37
+ <div style={{ marginTop: 10 }}>
38
+ {snapshotAt && (
39
+ <Typography style={{ fontSize: 12, color: '#888', marginBottom: 8 }}>
40
+ Snapshot taken at {formatted_date(new Date(snapshotAt))}
41
+ </Typography>
42
+ )}
43
+
44
+ {obsRefs.length > 0 && (
45
+ <div style={{ marginBottom: 15 }}>
46
+ <Typography style={{ fontWeight: 'bold', marginBottom: 5 }}>Observations</Typography>
47
+ <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 14 }}>
48
+ <thead>
49
+ <tr style={{ borderBottom: '2px solid #ccc', textAlign: 'left' }}>
50
+ <th style={tdStyle}>Date</th>
51
+ <th style={tdStyle}>Type</th>
52
+ <th style={tdStyle}>Value</th>
53
+ <th style={tdStyle}>Category</th>
54
+ <th style={tdStyle}>Status</th>
55
+ </tr>
56
+ </thead>
57
+ <tbody>
58
+ {obsRefs.map((ref, i) => {
59
+ const obs = findObservation(ref.id, { batch: true })
60
+ if (obs === undefined) return (
61
+ <tr key={ref.id || i} style={{ borderBottom: '1px solid #eee' }}>
62
+ <td colSpan={5} style={tdStyle}>Loading...</td>
63
+ </tr>
64
+ )
65
+ if (obs === null) return (
66
+ <tr key={ref.id || i} style={{ borderBottom: '1px solid #eee' }}>
67
+ <td colSpan={5} style={deletedStyle}>{ref.label} — Record no longer available</td>
68
+ </tr>
69
+ )
70
+ return (
71
+ <tr key={obs.id || i} style={{ borderBottom: '1px solid #eee' }}>
72
+ <td style={tdStyle}>{obs.timestamp ? formatted_date(new Date(obs.timestamp)) : '-'}</td>
73
+ <td style={tdStyle}>{obs.type || obs.code || '-'}</td>
74
+ <td style={tdStyle}>
75
+ {obs.measurement ? `${obs.measurement.value} ${obs.measurement.unit}` : obs.qualitativeResult || '-'}
76
+ </td>
77
+ <td style={tdStyle}>{obs.category || '-'}</td>
78
+ <td style={tdStyle}>{obs.status || '-'}</td>
79
+ </tr>
80
+ )
81
+ })}
82
+ </tbody>
83
+ </table>
84
+ </div>
85
+ )}
86
+
87
+ {medRefs.length > 0 && (
88
+ <div style={{ marginBottom: 15 }}>
89
+ <Typography style={{ fontWeight: 'bold', marginBottom: 5 }}>Medications</Typography>
90
+ <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 14 }}>
91
+ <thead>
92
+ <tr style={{ borderBottom: '2px solid #ccc', textAlign: 'left' }}>
93
+ <th style={tdStyle}>Medication</th>
94
+ <th style={tdStyle}>Dosage</th>
95
+ <th style={tdStyle}>Dispensing</th>
96
+ <th style={tdStyle}>Pharmacy</th>
97
+ <th style={tdStyle}>Prescriber</th>
98
+ <th style={tdStyle}>Date</th>
99
+ </tr>
100
+ </thead>
101
+ <tbody>
102
+ {medRefs.map((ref, i) => {
103
+ const med = findMedication(ref.id, { batch: true })
104
+ if (med === undefined) return (
105
+ <tr key={ref.id || i} style={{ borderBottom: '1px solid #eee' }}>
106
+ <td colSpan={6} style={tdStyle}>Loading...</td>
107
+ </tr>
108
+ )
109
+ if (med === null) return (
110
+ <tr key={ref.id || i} style={{ borderBottom: '1px solid #eee' }}>
111
+ <td colSpan={6} style={deletedStyle}>{ref.label} — Record no longer available</td>
112
+ </tr>
113
+ )
114
+ return (
115
+ <tr key={med.id || i} style={{ borderBottom: '1px solid #eee' }}>
116
+ <td style={tdStyle}>
117
+ {med.title || '-'}
118
+ {med.allergyNote ? <div style={{ color: 'red', fontSize: 12 }}>Allergies: {med.allergyNote}</div> : null}
119
+ {med.directions ? <div style={{ color: '#888', fontSize: 12 }}>Directions: {med.directions}</div> : null}
120
+ </td>
121
+ <td style={tdStyle}>
122
+ {med.dosage
123
+ ? med.dosage.description
124
+ ? med.dosage.description
125
+ : `${med.dosage.value || ''}${med.dosage.unit ? ` ${med.dosage.unit}` : ''}${med.dosage.quantity ? ` ${med.dosage.quantity} units` : ''}${med.dosage.frequency ? ` ${!isNaN(parseInt(med.dosage.frequency)) ? `${med.dosage.frequency}x ${med.dosage?.frequencyDescriptor ? `Per ${med.dosage.frequencyDescriptor}` : 'daily'}` : med.dosage.frequency}` : ''}`
126
+ : '-'}
127
+ </td>
128
+ <td style={tdStyle}>
129
+ {med.dispensing ? `${med.dispensing.quantity || ''} ${med.dispensing.unit || ''}`.trim() || '-' : '-'}
130
+ </td>
131
+ <td style={tdStyle}>{med.pharmacyName || med.pharmacyId || '-'}</td>
132
+ <td style={tdStyle}>
133
+ {med.prescriberName || '-'}
134
+ {med.source ? <div style={{ fontStyle: 'italic', fontSize: 12 }}>{med.source}</div> : null}
135
+ {med.notes ? <div style={{ fontSize: 12 }}>{med.notes}</div> : null}
136
+ </td>
137
+ <td style={tdStyle}>
138
+ {formatted_date(new Date(med.startedTakingAt || med.prescribedAt || med.createdAt))}
139
+ </td>
140
+ </tr>
141
+ )
142
+ })}
143
+ </tbody>
144
+ </table>
145
+ </div>
146
+ )}
147
+
148
+ {obsRefs.length === 0 && medRefs.length === 0 && (
149
+ <Typography style={{ fontStyle: 'italic', color: '#888' }}>No historical data recorded</Typography>
150
+ )}
151
+ </div>
152
+ )
153
+ }
154
+
26
155
  export const ResponseAnswer = ({ formResponse, fieldId, isHTML, answer: a, printing, onImageClick } : {
27
156
  answer: FormResponseValueAnswer,
28
157
  formResponse: FormResponse,
@@ -177,7 +306,9 @@ export const ResponseAnswer = ({ formResponse, fieldId, isHTML, answer: a, print
177
306
  </Typography>
178
307
  ) : (
179
308
  a.type === 'description'
180
- ? <></>
309
+ ? (a.value && typeof a.value === 'string' && a.value.startsWith('{'))
310
+ ? (() => { try { return <HistoricalDataSnapshotDisplay snapshot={JSON.parse(a.value)} /> } catch { return <></> } })()
311
+ : <></>
181
312
  : <Typography>No value provided</Typography>
182
313
  )
183
314
  )
@@ -145,6 +145,7 @@ export const QuestionForField = ({
145
145
  uploadingFiles, setUploadingFiles, handleFileUpload,
146
146
  groupFields,
147
147
  AddToDatabase,
148
+ lastNavigationDirectionRef,
148
149
  } : {
149
150
  spacing?: number,
150
151
  form?: Form,
@@ -163,19 +164,19 @@ export const QuestionForField = ({
163
164
  setUploadingFiles: React.Dispatch<React.SetStateAction<{ fieldId: string }[]>>,
164
165
  groupFields?: FormField[],
165
166
  AddToDatabase?: React.JSXElementConstructor<AddToDatabaseProps>,
166
- } & Pick<TellescopeFormProps, "rootResponseId" | "goToNextField" | "groupId" | "groupInstance" | "submit" | "formResponseId" | 'enduserId' | 'isPreviousDisabled' | 'goToPreviousField' | 'enduser' | 'handleDatabaseSelect' | 'onAddFile' | 'onFieldChange' | 'fields' | 'customInputs' | 'responses' | 'selectedFiles' | 'validateField'>) => {
167
+ } & Pick<TellescopeFormProps, "rootResponseId" | "goToNextField" | "groupId" | "groupInstance" | "submit" | "formResponseId" | 'enduserId' | 'isPreviousDisabled' | 'goToPreviousField' | 'enduser' | 'handleDatabaseSelect' | 'onAddFile' | 'onFieldChange' | 'fields' | 'customInputs' | 'responses' | 'selectedFiles' | 'validateField' | 'lastNavigationDirectionRef'>) => {
167
168
  const String = customInputs?.['string'] ?? StringInput
168
169
  const StringLong = customInputs?.['stringLong'] ?? StringLongInput
169
170
  const Email = customInputs?.['email'] ?? EmailInput
170
171
  const Number = customInputs?.['number'] ?? NumberInput
171
- const Phone = customInputs?.['phone'] ?? PhoneInput
172
- const ResolvedDateInput = customInputs?.['date'] ?? DateInput
173
- const Signature = customInputs?.['signature'] ?? SignatureInput
174
- const MultipleChoice = customInputs?.['multiple_choice'] ?? MultipleChoiceInput
175
- const Stripe = customInputs?.['Stripe'] ?? StripeInput
176
- const Chargebee = customInputs?.['Chargebee'] ?? ChargeebeeInput
177
- const File = customInputs?.['file'] ?? FileInput
178
- const Files = customInputs?.['files'] ?? FilesInput
172
+ const Phone = customInputs?.['phone'] ?? PhoneInput
173
+ const ResolvedDateInput = customInputs?.['date'] ?? DateInput
174
+ const Signature = customInputs?.['signature'] ?? SignatureInput
175
+ const MultipleChoice = customInputs?.['multiple_choice'] ?? MultipleChoiceInput
176
+ const Stripe = customInputs?.['Stripe'] ?? StripeInput
177
+ const Chargebee = customInputs?.['Chargebee'] ?? ChargeebeeInput
178
+ const File = customInputs?.['file'] ?? FileInput
179
+ const Files = customInputs?.['files'] ?? FilesInput
179
180
  const Ranking = customInputs?.['ranking'] ?? RankingInput
180
181
  const Rating = customInputs?.['rating'] ?? RatingInput
181
182
  const Address = customInputs?.['Address'] ?? AddressInput
@@ -236,7 +237,7 @@ export const QuestionForField = ({
236
237
  <div style={{ marginTop: 15 }}></div>
237
238
  }
238
239
 
239
- <Description field={field} style={{ fontSize: 16 }} enduserId={enduserId} />
240
+ <Description field={field} style={{ fontSize: 16 }} enduserId={enduserId} onFieldChange={onFieldChange} />
240
241
 
241
242
  {feedback.length > 0 &&
242
243
  <Flex column style={{ marginBottom: 11, marginTop: 3, }}>
@@ -293,7 +294,7 @@ export const QuestionForField = ({
293
294
  <DateStringInput field={field} disabled={value.disabled} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'string'>} form={form} />
294
295
  )
295
296
  : field.type === 'Hidden Value' ? (
296
- <HiddenValue groupFields={groupFields} isSinglePage={isSinglePage} goToNextField={goToNextField} goToPreviousField={goToPreviousField} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<any>} form={form} />
297
+ <HiddenValue groupFields={groupFields} isSinglePage={isSinglePage} goToNextField={goToNextField} goToPreviousField={goToPreviousField} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<any>} form={form} lastNavigationDirectionRef={lastNavigationDirectionRef} />
297
298
  )
298
299
  : field.type === 'Address' ? (
299
300
  <Address field={field} disabled={value.disabled} value={value.answer.value as any} onChange={onFieldChange as ChangeHandler<any>} form={form} />
@@ -320,7 +321,7 @@ export const QuestionForField = ({
320
321
  <String field={field} disabled={value.disabled} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'string'>} form={form} />
321
322
  )
322
323
  : field.type === 'Appointment Booking' ? (
323
- <AppointmentBooking formResponseId={formResponseId} enduserId={enduserId} goToPreviousField={goToPreviousField} isPreviousDisabled={isPreviousDisabled} responses={responses} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'Appointment Booking'>} form={form} />
324
+ <AppointmentBooking key={field.id} formResponseId={formResponseId} enduserId={enduserId} goToPreviousField={goToPreviousField} isPreviousDisabled={isPreviousDisabled} responses={responses} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'Appointment Booking'>} form={form} />
324
325
  )
325
326
  : field.type === 'Stripe' ? (
326
327
  <Stripe enduserId={enduserId} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<any>} setCustomerId={setCustomerId} form={form} responses={responses} enduser={enduser} />
@@ -428,13 +429,13 @@ export const QuestionForField = ({
428
429
  enduser={enduser} goToPreviousField={goToPreviousField} isPreviousDisabled={isPreviousDisabled} goToNextField={goToNextField}
429
430
  form={form} formResponseId={formResponseId} rootResponseId={rootResponseId} submit={submit}
430
431
  repeats={repeats} onRepeatsChange={onRepeatsChange} setCustomerId={setCustomerId}
431
- value={value} file={file}
432
+ value={value} file={file}
432
433
  onAddFile={onAddFile} onFieldChange={onFieldChange}
433
434
  responses={responses} selectedFiles={selectedFiles}
434
435
  validateField={validateField} enduserId={enduserId}
435
436
  spacing={field.options?.groupPadding}
436
437
  logicOptions={logicOptions}
437
- isInQuestionGroup
438
+ isInQuestionGroup
438
439
  groupFields={
439
440
  fields.filter(f => field.options?.subFields?.find(s => s.id === f.id))
440
441
  }
@@ -442,6 +443,7 @@ export const QuestionForField = ({
442
443
  uploadingFiles={uploadingFiles} setUploadingFiles={setUploadingFiles}
443
444
  handleFileUpload={handleFileUpload}
444
445
  AddToDatabase={AddToDatabase}
446
+ lastNavigationDirectionRef={lastNavigationDirectionRef}
445
447
  />
446
448
  </Flex>
447
449
  )
@@ -529,6 +531,7 @@ export const TellescopeSingleQuestionFlow: typeof TellescopeForm = ({
529
531
  groupInstance,
530
532
  logicOptions,
531
533
  uploadingFiles, setUploadingFiles, handleFileUpload,
534
+ lastNavigationDirectionRef,
532
535
  }) => {
533
536
  const beforeunloadHandler = React.useCallback((e: BeforeUnloadEvent) => {
534
537
  try {
@@ -652,6 +655,7 @@ export const TellescopeSingleQuestionFlow: typeof TellescopeForm = ({
652
655
  logicOptions={logicOptions}
653
656
  uploadingFiles={uploadingFiles} setUploadingFiles={setUploadingFiles}
654
657
  handleFileUpload={handleFileUpload}
658
+ lastNavigationDirectionRef={lastNavigationDirectionRef}
655
659
  />
656
660
  </Flex>
657
661
  </Flex>
@@ -1017,7 +1021,7 @@ export const UpdateResponse = ({
1017
1021
  )
1018
1022
  }
1019
1023
 
1020
- const HistoricalDataSection = ({ sources, enduserId } : { sources: HistoricalDataSource[], enduserId: string }) => {
1024
+ const HistoricalDataSection = ({ sources, enduserId, onDataLoaded } : { sources: HistoricalDataSource[], enduserId: string, onDataLoaded?: (json: string) => void }) => {
1021
1025
  const session = useSession({ throwIfMissingContext: false })
1022
1026
  const [observations, setObservations] = useState<any[]>([])
1023
1027
  const [medications, setMedications] = useState<any[]>([])
@@ -1039,6 +1043,8 @@ const HistoricalDataSection = ({ sources, enduserId } : { sources: HistoricalDat
1039
1043
  setError('')
1040
1044
  try {
1041
1045
  const promises: Promise<void>[] = []
1046
+ let loadedObservations: any[] = []
1047
+ let loadedMedications: any[] = []
1042
1048
 
1043
1049
  for (const source of sources) {
1044
1050
  if (source.type === 'Observations') {
@@ -1047,7 +1053,7 @@ const HistoricalDataSection = ({ sources, enduserId } : { sources: HistoricalDat
1047
1053
  filter: { enduserId, ...source.filter },
1048
1054
  limit: source.limit,
1049
1055
  })
1050
- .then((obs: any[]) => setObservations(obs))
1056
+ .then((obs: any[]) => { loadedObservations = obs; setObservations(obs) })
1051
1057
  )
1052
1058
  } else if (source.type === 'Medications') {
1053
1059
  promises.push(
@@ -1055,12 +1061,35 @@ const HistoricalDataSection = ({ sources, enduserId } : { sources: HistoricalDat
1055
1061
  filter: { enduserId, status: { _ne: 'draft' }, ...source.filter },
1056
1062
  limit: source.limit,
1057
1063
  })
1058
- .then((meds: any[]) => setMedications(meds))
1064
+ .then((meds: any[]) => { loadedMedications = meds; setMedications(meds) })
1059
1065
  )
1060
1066
  }
1061
1067
  }
1062
1068
 
1063
1069
  await Promise.all(promises)
1070
+
1071
+ const obsLabel = (o: any) => {
1072
+ const name = o.type || o.code || ''
1073
+ const val = o.measurement ? `${o.measurement.value} ${o.measurement.unit}` : o.qualitativeResult || ''
1074
+ return `${name}${val ? ' ' + val : ''}`.slice(0, 50)
1075
+ }
1076
+ const medLabel = (m: any) => {
1077
+ const dose = m.dosage?.quantity ? ` ${m.dosage.quantity}${m.dosage.unit ? m.dosage.unit : ''}` : ''
1078
+ return `${m.title || ''}${dose}`.slice(0, 50)
1079
+ }
1080
+
1081
+ const MAX_SNAPSHOT_LENGTH = 24000
1082
+ let obsRefs = loadedObservations.map((o: any) => ({ id: o.id, label: obsLabel(o) }))
1083
+ let medRefs = loadedMedications.map((m: any) => ({ id: m.id, label: medLabel(m) }))
1084
+
1085
+ let json = JSON.stringify({ observations: obsRefs, medications: medRefs, snapshotAt: new Date().toISOString() })
1086
+ while (json.length > MAX_SNAPSHOT_LENGTH && (obsRefs.length + medRefs.length) > 0) {
1087
+ if (obsRefs.length >= medRefs.length) { obsRefs.pop() }
1088
+ else { medRefs.pop() }
1089
+ json = JSON.stringify({ observations: obsRefs, medications: medRefs, snapshotAt: new Date().toISOString() })
1090
+ }
1091
+
1092
+ onDataLoaded?.(json)
1064
1093
  } catch (err: any) {
1065
1094
  setError(err?.message || 'Failed to load historical data')
1066
1095
  } finally {
@@ -1183,7 +1212,7 @@ const HistoricalDataSection = ({ sources, enduserId } : { sources: HistoricalDat
1183
1212
  )
1184
1213
  }
1185
1214
 
1186
- export const Description = ({ field, color="primary", style, enduserId } : { field: FormField, color?: string, enduserId?: string } & Styled) => {
1215
+ export const Description = ({ field, color="primary", style, enduserId, onFieldChange } : { field: FormField, color?: string, enduserId?: string, onFieldChange?: (value: any, fieldId: string) => void } & Styled) => {
1187
1216
  const existingContent = (
1188
1217
  !field.htmlDescription && field.description ? (
1189
1218
  <Typography color={color as any} style={style}>
@@ -1203,6 +1232,7 @@ export const Description = ({ field, color="primary", style, enduserId } : { fie
1203
1232
  <HistoricalDataSection
1204
1233
  sources={field.options.historicalDataSources}
1205
1234
  enduserId={enduserId}
1235
+ onDataLoaded={onFieldChange ? (jsonString) => onFieldChange(jsonString, field.id) : undefined}
1206
1236
  />
1207
1237
  ) : null}
1208
1238
  </>
@@ -1269,7 +1299,8 @@ export const TellescopeSinglePageForm: React.JSXElementConstructor<TellescopeFor
1269
1299
  groupInstance,
1270
1300
  uploadingFiles, setUploadingFiles, handleFileUpload,
1271
1301
  AddToDatabase,
1272
- ...props
1302
+ lastNavigationDirectionRef,
1303
+ ...props
1273
1304
  }) => {
1274
1305
  const list = useListForFormFields(fields, responses, { form: props.form, gender: enduser?.gender })
1275
1306
 
@@ -1398,7 +1429,7 @@ export const TellescopeSinglePageForm: React.JSXElementConstructor<TellescopeFor
1398
1429
  enduserId={props.enduserId} formResponseId={props.formResponseId} rootResponseId={rootResponseId} submit={submit}
1399
1430
  enduser={enduser} goToPreviousField={goToPreviousField} isPreviousDisabled={isPreviousDisabled} goToNextField={goToNextField}
1400
1431
  repeats={repeats} onRepeatsChange={setRepeats} setCustomerId={setCustomerId}
1401
- value={value} file={file}
1432
+ value={value} file={file}
1402
1433
  customInputs={customInputs}
1403
1434
  onAddFile={onAddFile} onFieldChange={onFieldChange}
1404
1435
  responses={responses} selectedFiles={selectedFiles}
@@ -1407,6 +1438,7 @@ export const TellescopeSinglePageForm: React.JSXElementConstructor<TellescopeFor
1407
1438
  uploadingFiles={uploadingFiles} setUploadingFiles={setUploadingFiles}
1408
1439
  handleFileUpload={handleFileUpload}
1409
1440
  AddToDatabase={AddToDatabase}
1441
+ lastNavigationDirectionRef={lastNavigationDirectionRef}
1410
1442
  />
1411
1443
  </Flex>
1412
1444
  </Flex>
@@ -154,6 +154,7 @@ export const QuestionForField = ({
154
154
  uploadingFiles, setUploadingFiles, handleFileUpload,
155
155
  groupFields,
156
156
  AddToDatabase,
157
+ lastNavigationDirectionRef,
157
158
  } : {
158
159
  spacing?: number,
159
160
  form?: Form,
@@ -172,7 +173,7 @@ export const QuestionForField = ({
172
173
  setUploadingFiles: React.Dispatch<React.SetStateAction<{ fieldId: string }[]>>,
173
174
  groupFields?: FormField[],
174
175
  AddToDatabase?: React.JSXElementConstructor<AddToDatabaseProps>,
175
- } & Pick<TellescopeFormProps, "rootResponseId" | "goToNextField" | "groupId" | "groupInstance" | "submit" | "formResponseId" | 'enduserId' | 'isPreviousDisabled' | 'goToPreviousField' | 'enduser' | 'handleDatabaseSelect' | 'onAddFile' | 'onFieldChange' | 'fields' | 'customInputs' | 'responses' | 'selectedFiles' | 'validateField'>) => {
176
+ } & Pick<TellescopeFormProps, "rootResponseId" | "goToNextField" | "groupId" | "groupInstance" | "submit" | "formResponseId" | 'enduserId' | 'isPreviousDisabled' | 'goToPreviousField' | 'enduser' | 'handleDatabaseSelect' | 'onAddFile' | 'onFieldChange' | 'fields' | 'customInputs' | 'responses' | 'selectedFiles' | 'validateField' | 'lastNavigationDirectionRef'>) => {
176
177
  const String = customInputs?.['string'] ?? StringInput
177
178
  const StringLong = customInputs?.['stringLong'] ?? StringLongInput
178
179
  const Email = customInputs?.['email'] ?? EmailInput
@@ -303,7 +304,7 @@ export const QuestionForField = ({
303
304
  <DateStringInput field={field} disabled={value.disabled} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'string'>} form={form} />
304
305
  )
305
306
  : field.type === 'Hidden Value' ? (
306
- <HiddenValue groupFields={groupFields} isSinglePage={isSinglePage} goToNextField={goToNextField} goToPreviousField={goToPreviousField} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<any>} form={form} />
307
+ <HiddenValue groupFields={groupFields} isSinglePage={isSinglePage} goToNextField={goToNextField} goToPreviousField={goToPreviousField} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<any>} form={form} lastNavigationDirectionRef={lastNavigationDirectionRef} />
307
308
  )
308
309
  : field.type === 'Address' ? (
309
310
  <Address field={field} disabled={value.disabled} value={value.answer.value as any} onChange={onFieldChange as ChangeHandler<any>} form={form} />
@@ -330,7 +331,7 @@ export const QuestionForField = ({
330
331
  <String field={field} disabled={value.disabled} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'string'>} form={form} error={!!validationMessage && (!['A response is required', 'A value must be checked', 'A file is required', 'Enter a valid phone number', 'Insurer is required'].includes(validationMessage) || value.touched)} />
331
332
  )
332
333
  : field.type === 'Appointment Booking' ? (
333
- <AppointmentBooking formResponseId={formResponseId} enduserId={enduserId} goToPreviousField={goToPreviousField} isPreviousDisabled={isPreviousDisabled} responses={responses} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'Appointment Booking'>} form={form} />
334
+ <AppointmentBooking key={field.id} formResponseId={formResponseId} enduserId={enduserId} goToPreviousField={goToPreviousField} isPreviousDisabled={isPreviousDisabled} responses={responses} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'Appointment Booking'>} form={form} />
334
335
  )
335
336
  : field.type === 'Stripe' ? (
336
337
  <Stripe enduserId={enduserId} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<any>} setCustomerId={setCustomerId} form={form} responses={responses} enduser={enduser} />
@@ -438,13 +439,13 @@ export const QuestionForField = ({
438
439
  enduser={enduser} goToPreviousField={goToPreviousField} isPreviousDisabled={isPreviousDisabled} goToNextField={goToNextField}
439
440
  form={form} formResponseId={formResponseId} rootResponseId={rootResponseId} submit={submit}
440
441
  repeats={repeats} onRepeatsChange={onRepeatsChange} setCustomerId={setCustomerId}
441
- value={value} file={file}
442
+ value={value} file={file}
442
443
  onAddFile={onAddFile} onFieldChange={onFieldChange}
443
444
  responses={responses} selectedFiles={selectedFiles}
444
445
  validateField={validateField} enduserId={enduserId}
445
446
  spacing={field.options?.groupPadding}
446
447
  logicOptions={logicOptions}
447
- isInQuestionGroup
448
+ isInQuestionGroup
448
449
  groupFields={
449
450
  fields.filter(f => field.options?.subFields?.find(s => s.id === f.id))
450
451
  }
@@ -452,6 +453,7 @@ export const QuestionForField = ({
452
453
  uploadingFiles={uploadingFiles} setUploadingFiles={setUploadingFiles}
453
454
  handleFileUpload={handleFileUpload}
454
455
  AddToDatabase={AddToDatabase}
456
+ lastNavigationDirectionRef={lastNavigationDirectionRef}
455
457
  />
456
458
  </Flex>
457
459
  )
@@ -539,6 +541,7 @@ export const TellescopeSingleQuestionFlowV2: typeof TellescopeFormV2 = ({
539
541
  groupInstance,
540
542
  logicOptions,
541
543
  uploadingFiles, setUploadingFiles, handleFileUpload,
544
+ lastNavigationDirectionRef,
542
545
  }) => {
543
546
  const beforeunloadHandler = React.useCallback((e: BeforeUnloadEvent) => {
544
547
  try {
@@ -689,6 +692,7 @@ export const TellescopeSingleQuestionFlowV2: typeof TellescopeFormV2 = ({
689
692
  logicOptions={logicOptions}
690
693
  uploadingFiles={uploadingFiles} setUploadingFiles={setUploadingFiles}
691
694
  handleFileUpload={handleFileUpload}
695
+ lastNavigationDirectionRef={lastNavigationDirectionRef}
692
696
  />
693
697
  </Flex>
694
698
  </Flex>
@@ -1137,7 +1141,8 @@ export const TellescopeSinglePageForm: React.JSXElementConstructor<TellescopeFor
1137
1141
  groupInstance,
1138
1142
  uploadingFiles, setUploadingFiles, handleFileUpload,
1139
1143
  AddToDatabase,
1140
- ...props
1144
+ lastNavigationDirectionRef,
1145
+ ...props
1141
1146
  }) => {
1142
1147
  const list = useListForFormFields(fields, responses, { form: props.form, gender: enduser?.gender })
1143
1148
 
@@ -1266,7 +1271,7 @@ export const TellescopeSinglePageForm: React.JSXElementConstructor<TellescopeFor
1266
1271
  enduserId={props.enduserId} formResponseId={props.formResponseId} rootResponseId={rootResponseId} submit={submit}
1267
1272
  enduser={enduser} goToPreviousField={goToPreviousField} isPreviousDisabled={isPreviousDisabled} goToNextField={goToNextField}
1268
1273
  repeats={repeats} onRepeatsChange={setRepeats} setCustomerId={setCustomerId}
1269
- value={value} file={file}
1274
+ value={value} file={file}
1270
1275
  customInputs={customInputs}
1271
1276
  onAddFile={onAddFile} onFieldChange={onFieldChange}
1272
1277
  responses={responses} selectedFiles={selectedFiles}
@@ -1275,6 +1280,7 @@ export const TellescopeSinglePageForm: React.JSXElementConstructor<TellescopeFor
1275
1280
  uploadingFiles={uploadingFiles} setUploadingFiles={setUploadingFiles}
1276
1281
  handleFileUpload={handleFileUpload}
1277
1282
  AddToDatabase={AddToDatabase}
1283
+ lastNavigationDirectionRef={lastNavigationDirectionRef}
1278
1284
  />
1279
1285
  </Flex>
1280
1286
  </Flex>