@tellescope/react-components 1.245.1 → 1.246.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tellescope/react-components",
3
- "version": "1.245.1",
3
+ "version": "1.246.2",
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.245.1",
55
- "@tellescope/sdk": "1.245.1",
56
- "@tellescope/types-client": "1.245.1",
57
- "@tellescope/types-models": "1.245.1",
58
- "@tellescope/types-utilities": "1.245.1",
59
- "@tellescope/utilities": "1.245.1",
60
- "@tellescope/validation": "1.245.1",
54
+ "@tellescope/constants": "1.246.2",
55
+ "@tellescope/sdk": "1.246.2",
56
+ "@tellescope/types-client": "1.246.2",
57
+ "@tellescope/types-models": "1.246.2",
58
+ "@tellescope/types-utilities": "1.246.2",
59
+ "@tellescope/utilities": "1.246.2",
60
+ "@tellescope/validation": "1.246.2",
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": "3484805db8e2d27d68b6e04c679ead95a06eed94",
87
+ "gitHead": "a9167f6d732de999cf012ffba3004f0d62ae8c14",
88
88
  "publishConfig": {
89
89
  "access": "public"
90
90
  }
@@ -5,7 +5,7 @@ import { ChangeHandler, FormInputs } from "./types"
5
5
  import { AddToDatabaseProps, AddressInput, AllergiesInput, AppointmentBookingInput, BelugaPatientPreferenceInput, BridgeEligibilityInput, CandidEligibilityInput, ChargeebeeInput, ConditionsInput, DatabaseSelectInput, DateInput, DateStringInput, DropdownInput, EmailInput, EmotiiInput, FileInput, FilesInput, HeightInput, HiddenValueInput, InsuranceInput, LanguageSelect, MedicationsInput, MultipleChoiceInput, NumberInput, PharmacySearchInput, PhoneInput, Progress, RankingInput, RatingInput, RedirectInput, RelatedContactsInput, RichTextInput, SignatureInput, StringInput, StringLongInput, StripeInput, TableInput, TimeInput, TimezoneInput, defaultButtonStyles } from "./inputs"
6
6
  import { PRIMARY_HEX } from "@tellescope/constants"
7
7
  import { FormResponse, FormField, Form, Enduser } from "@tellescope/types-client"
8
- import { FormResponseAnswerFileValue, OrganizationTheme } from "@tellescope/types-models"
8
+ import { FormResponseAnswerFileValue, OrganizationTheme, HistoricalDataSource } from "@tellescope/types-models"
9
9
  import { calculate_form_scoring, field_can_autosubmit, form_response_value_to_string, formatted_date, object_is_empty, objects_equivalent, read_local_storage, remove_script_tags, responses_satisfy_conditions, truncate_string } from "@tellescope/utilities"
10
10
  import { Divider } from "@mui/material"
11
11
 
@@ -236,7 +236,7 @@ export const QuestionForField = ({
236
236
  <div style={{ marginTop: 15 }}></div>
237
237
  }
238
238
 
239
- <Description field={field} style={{ fontSize: 16 }} />
239
+ <Description field={field} style={{ fontSize: 16 }} enduserId={enduserId} />
240
240
 
241
241
  {feedback.length > 0 &&
242
242
  <Flex column style={{ marginBottom: 11, marginTop: 3, }}>
@@ -1017,20 +1017,195 @@ export const UpdateResponse = ({
1017
1017
  )
1018
1018
  }
1019
1019
 
1020
- export const Description = ({ field, color="primary", style } : { field: FormField, color?: string } & Styled) => {
1021
- if (!field.htmlDescription && field.description) {
1020
+ const HistoricalDataSection = ({ sources, enduserId } : { sources: HistoricalDataSource[], enduserId: string }) => {
1021
+ const session = useSession({ throwIfMissingContext: false })
1022
+ const [observations, setObservations] = useState<any[]>([])
1023
+ const [medications, setMedications] = useState<any[]>([])
1024
+ const [loading, setLoading] = useState(true)
1025
+ const [error, setError] = useState('')
1026
+
1027
+ const loadedKeyRef = useRef('')
1028
+
1029
+ useEffect(() => {
1030
+ if (!session) return
1031
+
1032
+ // Build a stable key from enduserId + sources to avoid re-fetching on object reference changes
1033
+ const key = enduserId + ':' + JSON.stringify(sources)
1034
+ if (loadedKeyRef.current === key) return
1035
+ loadedKeyRef.current = key
1036
+
1037
+ const loadData = async () => {
1038
+ setLoading(true)
1039
+ setError('')
1040
+ try {
1041
+ const promises: Promise<void>[] = []
1042
+
1043
+ for (const source of sources) {
1044
+ if (source.type === 'Observations') {
1045
+ promises.push(
1046
+ session.api.enduser_observations.getSome({
1047
+ filter: { enduserId, ...source.filter },
1048
+ limit: source.limit,
1049
+ })
1050
+ .then((obs: any[]) => setObservations(obs))
1051
+ )
1052
+ } else if (source.type === 'Medications') {
1053
+ promises.push(
1054
+ session.api.enduser_medications.getSome({
1055
+ filter: { enduserId, status: { _ne: 'draft' }, ...source.filter },
1056
+ limit: source.limit,
1057
+ })
1058
+ .then((meds: any[]) => setMedications(meds))
1059
+ )
1060
+ }
1061
+ }
1062
+
1063
+ await Promise.all(promises)
1064
+ } catch (err: any) {
1065
+ setError(err?.message || 'Failed to load historical data')
1066
+ } finally {
1067
+ setLoading(false)
1068
+ }
1069
+ }
1070
+
1071
+ loadData()
1072
+ }, [session, enduserId, sources])
1073
+
1074
+ if (!session) return null
1075
+
1076
+ if (loading) {
1022
1077
  return (
1078
+ <Flex style={{ padding: 10, justifyContent: 'center' }}>
1079
+ <CircularProgress size={24} />
1080
+ </Flex>
1081
+ )
1082
+ }
1083
+
1084
+ if (error) {
1085
+ return (
1086
+ <Typography color="error" style={{ padding: 10 }}>
1087
+ {error}
1088
+ </Typography>
1089
+ )
1090
+ }
1091
+
1092
+ const hasObservations = sources.some(s => s.type === 'Observations')
1093
+ const hasMedications = sources.some(s => s.type === 'Medications')
1094
+
1095
+ return (
1096
+ <div style={{ marginTop: 10 }}>
1097
+ {hasObservations && (
1098
+ <div style={{ marginBottom: 15 }}>
1099
+ <Typography style={{ fontWeight: 'bold', marginBottom: 5 }}>Observations</Typography>
1100
+ {observations.length === 0 ? (
1101
+ <Typography style={{ fontStyle: 'italic', color: '#888' }}>No observations found</Typography>
1102
+ ) : (
1103
+ <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 14 }}>
1104
+ <thead>
1105
+ <tr style={{ borderBottom: '2px solid #ccc', textAlign: 'left' }}>
1106
+ <th style={{ padding: '6px 8px' }}>Date</th>
1107
+ <th style={{ padding: '6px 8px' }}>Type</th>
1108
+ <th style={{ padding: '6px 8px' }}>Value</th>
1109
+ <th style={{ padding: '6px 8px' }}>Category</th>
1110
+ <th style={{ padding: '6px 8px' }}>Status</th>
1111
+ </tr>
1112
+ </thead>
1113
+ <tbody>
1114
+ {observations.map((obs: any) => (
1115
+ <tr key={obs.id} style={{ borderBottom: '1px solid #eee' }}>
1116
+ <td style={{ padding: '6px 8px' }}>{obs.timestamp ? formatted_date(new Date(obs.timestamp)) : '-'}</td>
1117
+ <td style={{ padding: '6px 8px' }}>{obs.type || obs.code || '-'}</td>
1118
+ <td style={{ padding: '6px 8px' }}>
1119
+ {obs.measurement ? `${obs.measurement.value} ${obs.measurement.unit}` : obs.qualitativeResult || '-'}
1120
+ </td>
1121
+ <td style={{ padding: '6px 8px' }}>{obs.category || '-'}</td>
1122
+ <td style={{ padding: '6px 8px' }}>{obs.status || '-'}</td>
1123
+ </tr>
1124
+ ))}
1125
+ </tbody>
1126
+ </table>
1127
+ )}
1128
+ </div>
1129
+ )}
1130
+
1131
+ {hasMedications && (
1132
+ <div style={{ marginBottom: 15 }}>
1133
+ <Typography style={{ fontWeight: 'bold', marginBottom: 5 }}>Medications</Typography>
1134
+ {medications.length === 0 ? (
1135
+ <Typography style={{ fontStyle: 'italic', color: '#888' }}>No medications found</Typography>
1136
+ ) : (
1137
+ <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 14 }}>
1138
+ <thead>
1139
+ <tr style={{ borderBottom: '2px solid #ccc', textAlign: 'left' }}>
1140
+ <th style={{ padding: '6px 8px' }}>Medication</th>
1141
+ <th style={{ padding: '6px 8px' }}>Dosage</th>
1142
+ <th style={{ padding: '6px 8px' }}>Dispensing</th>
1143
+ <th style={{ padding: '6px 8px' }}>Pharmacy</th>
1144
+ <th style={{ padding: '6px 8px' }}>Prescriber</th>
1145
+ <th style={{ padding: '6px 8px' }}>Date</th>
1146
+ </tr>
1147
+ </thead>
1148
+ <tbody>
1149
+ {medications.map((med: any) => (
1150
+ <tr key={med.id} style={{ borderBottom: '1px solid #eee' }}>
1151
+ <td style={{ padding: '6px 8px' }}>
1152
+ {med.title || '-'}
1153
+ {med.allergyNote ? <div style={{ color: 'red', fontSize: 12 }}>Allergies: {med.allergyNote}</div> : null}
1154
+ {med.directions ? <div style={{ color: '#888', fontSize: 12 }}>Directions: {med.directions}</div> : null}
1155
+ </td>
1156
+ <td style={{ padding: '6px 8px' }}>
1157
+ {med.dosage
1158
+ ? med.dosage.description
1159
+ ? med.dosage.description
1160
+ : `${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}` : ''}`
1161
+ : '-'}
1162
+ </td>
1163
+ <td style={{ padding: '6px 8px' }}>
1164
+ {med.dispensing ? `${med.dispensing.quantity || ''} ${med.dispensing.unit || ''}`.trim() || '-' : '-'}
1165
+ </td>
1166
+ <td style={{ padding: '6px 8px' }}>{med.pharmacyName || med.pharmacyId || '-'}</td>
1167
+ <td style={{ padding: '6px 8px' }}>
1168
+ {med.prescriberName || '-'}
1169
+ {med.source ? <div style={{ fontStyle: 'italic', fontSize: 12 }}>{med.source}</div> : null}
1170
+ {med.notes ? <div style={{ fontSize: 12 }}>{med.notes}</div> : null}
1171
+ </td>
1172
+ <td style={{ padding: '6px 8px' }}>
1173
+ {formatted_date(new Date(med.startedTakingAt || med.prescribedAt || med.createdAt))}
1174
+ </td>
1175
+ </tr>
1176
+ ))}
1177
+ </tbody>
1178
+ </table>
1179
+ )}
1180
+ </div>
1181
+ )}
1182
+ </div>
1183
+ )
1184
+ }
1185
+
1186
+ export const Description = ({ field, color="primary", style, enduserId } : { field: FormField, color?: string, enduserId?: string } & Styled) => {
1187
+ const existingContent = (
1188
+ !field.htmlDescription && field.description ? (
1023
1189
  <Typography color={color as any} style={style}>
1024
1190
  {field.description}
1025
1191
  </Typography>
1026
- )
1027
- }
1028
- if (!field.htmlDescription) return null
1192
+ ) : field.htmlDescription ? (
1193
+ <span dangerouslySetInnerHTML={{
1194
+ __html: remove_script_tags(field.htmlDescription)
1195
+ }} />
1196
+ ) : null
1197
+ )
1029
1198
 
1030
1199
  return (
1031
- <span dangerouslySetInnerHTML={{
1032
- __html: remove_script_tags(field.htmlDescription)
1033
- }} />
1200
+ <>
1201
+ {existingContent}
1202
+ {field.type === 'description' && field.options?.historicalDataSources?.length && enduserId ? (
1203
+ <HistoricalDataSection
1204
+ sources={field.options.historicalDataSources}
1205
+ enduserId={enduserId}
1206
+ />
1207
+ ) : null}
1208
+ </>
1034
1209
  )
1035
1210
  }
1036
1211