@tellescope/react-components 1.247.0 → 1.248.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.
- package/lib/cjs/Forms/form_responses.d.ts +11 -0
- package/lib/cjs/Forms/form_responses.d.ts.map +1 -1
- package/lib/cjs/Forms/form_responses.js +38 -2
- package/lib/cjs/Forms/form_responses.js.map +1 -1
- package/lib/cjs/Forms/forms.d.ts +2 -1
- package/lib/cjs/Forms/forms.d.ts.map +1 -1
- package/lib/cjs/Forms/forms.js +34 -8
- package/lib/cjs/Forms/forms.js.map +1 -1
- package/lib/cjs/Forms/forms.v2.js +1 -1
- package/lib/cjs/Forms/forms.v2.js.map +1 -1
- package/lib/cjs/Forms/hooks.d.ts +1 -0
- package/lib/cjs/Forms/hooks.d.ts.map +1 -1
- package/lib/cjs/Forms/hooks.js +5 -4
- package/lib/cjs/Forms/hooks.js.map +1 -1
- package/lib/cjs/state.d.ts +7 -1
- package/lib/cjs/state.d.ts.map +1 -1
- package/lib/cjs/state.js +43 -1
- package/lib/cjs/state.js.map +1 -1
- package/lib/esm/Forms/form_responses.d.ts +11 -0
- package/lib/esm/Forms/form_responses.d.ts.map +1 -1
- package/lib/esm/Forms/form_responses.js +37 -2
- package/lib/esm/Forms/form_responses.js.map +1 -1
- package/lib/esm/Forms/forms.d.ts +2 -1
- package/lib/esm/Forms/forms.d.ts.map +1 -1
- package/lib/esm/Forms/forms.js +34 -8
- package/lib/esm/Forms/forms.js.map +1 -1
- package/lib/esm/Forms/forms.v2.js +1 -1
- package/lib/esm/Forms/forms.v2.js.map +1 -1
- package/lib/esm/Forms/hooks.d.ts +1 -0
- package/lib/esm/Forms/hooks.d.ts.map +1 -1
- package/lib/esm/Forms/hooks.js +6 -5
- package/lib/esm/Forms/hooks.js.map +1 -1
- package/lib/esm/state.d.ts +7 -1
- package/lib/esm/state.d.ts.map +1 -1
- package/lib/esm/state.js +42 -1
- package/lib/esm/state.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/src/Forms/form_responses.tsx +133 -2
- package/src/Forms/forms.tsx +32 -6
- package/src/Forms/forms.v2.tsx +1 -1
- package/src/Forms/hooks.tsx +9 -5
- 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.
|
|
3
|
+
"version": "1.248.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.
|
|
55
|
-
"@tellescope/sdk": "1.
|
|
56
|
-
"@tellescope/types-client": "1.
|
|
57
|
-
"@tellescope/types-models": "1.
|
|
58
|
-
"@tellescope/types-utilities": "1.
|
|
59
|
-
"@tellescope/utilities": "1.
|
|
60
|
-
"@tellescope/validation": "1.
|
|
54
|
+
"@tellescope/constants": "1.248.0",
|
|
55
|
+
"@tellescope/sdk": "1.248.0",
|
|
56
|
+
"@tellescope/types-client": "1.248.0",
|
|
57
|
+
"@tellescope/types-models": "1.248.0",
|
|
58
|
+
"@tellescope/types-utilities": "1.248.0",
|
|
59
|
+
"@tellescope/utilities": "1.248.0",
|
|
60
|
+
"@tellescope/validation": "1.248.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": "
|
|
87
|
+
"gitHead": "c10ecca2464920b76cda6af60fafbd1c9fde5174",
|
|
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
|
)
|
package/src/Forms/forms.tsx
CHANGED
|
@@ -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 }} enduserId={enduserId} />
|
|
239
|
+
<Description field={field} style={{ fontSize: 16 }} enduserId={enduserId} onFieldChange={onFieldChange} />
|
|
240
240
|
|
|
241
241
|
{feedback.length > 0 &&
|
|
242
242
|
<Flex column style={{ marginBottom: 11, marginTop: 3, }}>
|
|
@@ -320,7 +320,7 @@ export const QuestionForField = ({
|
|
|
320
320
|
<String field={field} disabled={value.disabled} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'string'>} form={form} />
|
|
321
321
|
)
|
|
322
322
|
: 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} />
|
|
323
|
+
<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
324
|
)
|
|
325
325
|
: field.type === 'Stripe' ? (
|
|
326
326
|
<Stripe enduserId={enduserId} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<any>} setCustomerId={setCustomerId} form={form} responses={responses} enduser={enduser} />
|
|
@@ -1017,7 +1017,7 @@ export const UpdateResponse = ({
|
|
|
1017
1017
|
)
|
|
1018
1018
|
}
|
|
1019
1019
|
|
|
1020
|
-
const HistoricalDataSection = ({ sources, enduserId } : { sources: HistoricalDataSource[], enduserId: string }) => {
|
|
1020
|
+
const HistoricalDataSection = ({ sources, enduserId, onDataLoaded } : { sources: HistoricalDataSource[], enduserId: string, onDataLoaded?: (json: string) => void }) => {
|
|
1021
1021
|
const session = useSession({ throwIfMissingContext: false })
|
|
1022
1022
|
const [observations, setObservations] = useState<any[]>([])
|
|
1023
1023
|
const [medications, setMedications] = useState<any[]>([])
|
|
@@ -1039,6 +1039,8 @@ const HistoricalDataSection = ({ sources, enduserId } : { sources: HistoricalDat
|
|
|
1039
1039
|
setError('')
|
|
1040
1040
|
try {
|
|
1041
1041
|
const promises: Promise<void>[] = []
|
|
1042
|
+
let loadedObservations: any[] = []
|
|
1043
|
+
let loadedMedications: any[] = []
|
|
1042
1044
|
|
|
1043
1045
|
for (const source of sources) {
|
|
1044
1046
|
if (source.type === 'Observations') {
|
|
@@ -1047,7 +1049,7 @@ const HistoricalDataSection = ({ sources, enduserId } : { sources: HistoricalDat
|
|
|
1047
1049
|
filter: { enduserId, ...source.filter },
|
|
1048
1050
|
limit: source.limit,
|
|
1049
1051
|
})
|
|
1050
|
-
.then((obs: any[]) => setObservations(obs))
|
|
1052
|
+
.then((obs: any[]) => { loadedObservations = obs; setObservations(obs) })
|
|
1051
1053
|
)
|
|
1052
1054
|
} else if (source.type === 'Medications') {
|
|
1053
1055
|
promises.push(
|
|
@@ -1055,12 +1057,35 @@ const HistoricalDataSection = ({ sources, enduserId } : { sources: HistoricalDat
|
|
|
1055
1057
|
filter: { enduserId, status: { _ne: 'draft' }, ...source.filter },
|
|
1056
1058
|
limit: source.limit,
|
|
1057
1059
|
})
|
|
1058
|
-
.then((meds: any[]) => setMedications(meds))
|
|
1060
|
+
.then((meds: any[]) => { loadedMedications = meds; setMedications(meds) })
|
|
1059
1061
|
)
|
|
1060
1062
|
}
|
|
1061
1063
|
}
|
|
1062
1064
|
|
|
1063
1065
|
await Promise.all(promises)
|
|
1066
|
+
|
|
1067
|
+
const obsLabel = (o: any) => {
|
|
1068
|
+
const name = o.type || o.code || ''
|
|
1069
|
+
const val = o.measurement ? `${o.measurement.value} ${o.measurement.unit}` : o.qualitativeResult || ''
|
|
1070
|
+
return `${name}${val ? ' ' + val : ''}`.slice(0, 50)
|
|
1071
|
+
}
|
|
1072
|
+
const medLabel = (m: any) => {
|
|
1073
|
+
const dose = m.dosage?.quantity ? ` ${m.dosage.quantity}${m.dosage.unit ? m.dosage.unit : ''}` : ''
|
|
1074
|
+
return `${m.title || ''}${dose}`.slice(0, 50)
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
const MAX_SNAPSHOT_LENGTH = 24000
|
|
1078
|
+
let obsRefs = loadedObservations.map((o: any) => ({ id: o.id, label: obsLabel(o) }))
|
|
1079
|
+
let medRefs = loadedMedications.map((m: any) => ({ id: m.id, label: medLabel(m) }))
|
|
1080
|
+
|
|
1081
|
+
let json = JSON.stringify({ observations: obsRefs, medications: medRefs, snapshotAt: new Date().toISOString() })
|
|
1082
|
+
while (json.length > MAX_SNAPSHOT_LENGTH && (obsRefs.length + medRefs.length) > 0) {
|
|
1083
|
+
if (obsRefs.length >= medRefs.length) { obsRefs.pop() }
|
|
1084
|
+
else { medRefs.pop() }
|
|
1085
|
+
json = JSON.stringify({ observations: obsRefs, medications: medRefs, snapshotAt: new Date().toISOString() })
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
onDataLoaded?.(json)
|
|
1064
1089
|
} catch (err: any) {
|
|
1065
1090
|
setError(err?.message || 'Failed to load historical data')
|
|
1066
1091
|
} finally {
|
|
@@ -1183,7 +1208,7 @@ const HistoricalDataSection = ({ sources, enduserId } : { sources: HistoricalDat
|
|
|
1183
1208
|
)
|
|
1184
1209
|
}
|
|
1185
1210
|
|
|
1186
|
-
export const Description = ({ field, color="primary", style, enduserId } : { field: FormField, color?: string, enduserId?: string } & Styled) => {
|
|
1211
|
+
export const Description = ({ field, color="primary", style, enduserId, onFieldChange } : { field: FormField, color?: string, enduserId?: string, onFieldChange?: (value: any, fieldId: string) => void } & Styled) => {
|
|
1187
1212
|
const existingContent = (
|
|
1188
1213
|
!field.htmlDescription && field.description ? (
|
|
1189
1214
|
<Typography color={color as any} style={style}>
|
|
@@ -1203,6 +1228,7 @@ export const Description = ({ field, color="primary", style, enduserId } : { fie
|
|
|
1203
1228
|
<HistoricalDataSection
|
|
1204
1229
|
sources={field.options.historicalDataSources}
|
|
1205
1230
|
enduserId={enduserId}
|
|
1231
|
+
onDataLoaded={onFieldChange ? (jsonString) => onFieldChange(jsonString, field.id) : undefined}
|
|
1206
1232
|
/>
|
|
1207
1233
|
) : null}
|
|
1208
1234
|
</>
|
package/src/Forms/forms.v2.tsx
CHANGED
|
@@ -330,7 +330,7 @@ export const QuestionForField = ({
|
|
|
330
330
|
<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
331
|
)
|
|
332
332
|
: 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} />
|
|
333
|
+
<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
334
|
)
|
|
335
335
|
: field.type === 'Stripe' ? (
|
|
336
336
|
<Stripe enduserId={enduserId} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<any>} setCustomerId={setCustomerId} form={form} responses={responses} enduser={enduser} />
|
package/src/Forms/hooks.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, use
|
|
|
2
2
|
import { Session } from "@tellescope/sdk"
|
|
3
3
|
import { ChangeHandler, FormFieldNode } from "./types"
|
|
4
4
|
import { DatabaseRecord, Enduser, Form, FormField, FormResponse } from "@tellescope/types-client"
|
|
5
|
-
import { phoneValidator } from "@tellescope/validation"
|
|
5
|
+
import { phoneValidator, is_valid_mm_dd_yyyy } from "@tellescope/validation"
|
|
6
6
|
import { FileBlob, Indexable } from "@tellescope/types-utilities"
|
|
7
7
|
import { CompoundFilter, EnduserRelationship, FormCustomization, FormFieldOptionDetails, FormResponseAnswerAddress, FormResponseAnswerFileValue, FormResponseValue, FormResponseValueAnswer, OrganizationTheme, PreviousFormCompoundLogic, PreviousFormFieldType, Timezone, TIMEZONES } from "@tellescope/types-models"
|
|
8
8
|
import { WithTheme, contact_is_valid, useAddGTMTag, useFileUpload, useFormFields, useFormResponses, useResolvedSession, value_is_loaded } from "../index"
|
|
@@ -425,6 +425,7 @@ export const useOrganizationTheme = () => {
|
|
|
425
425
|
return context?.theme ?? theme
|
|
426
426
|
}
|
|
427
427
|
|
|
428
|
+
/** @deprecated Use is_valid_mm_dd_yyyy from @tellescope/validation instead — it validates days-in-month and leap years */
|
|
428
429
|
export const isDateString = (_s='') => {
|
|
429
430
|
const s = _s.trim()
|
|
430
431
|
|
|
@@ -442,7 +443,7 @@ export const isDateString = (_s='') => {
|
|
|
442
443
|
// const [mm,dd,yyyy] = s.split('-').map(v => parseInt(v)) // don't shorthand, for radix argument of parseInt gets messed up
|
|
443
444
|
// const d = Date.parse(`${yyyy}-${mm}-${dd}`) // this format should be explicitly supported by all implementations
|
|
444
445
|
// if (isNaN(d)) return false
|
|
445
|
-
|
|
446
|
+
|
|
446
447
|
return true
|
|
447
448
|
}
|
|
448
449
|
const isZIPString = (s='') => /^\d{5}$/.test(s) || /^\d{5}-\d{4}$/.test(s)
|
|
@@ -993,7 +994,7 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
993
994
|
}
|
|
994
995
|
|
|
995
996
|
if (value.answer.type === 'Insurance') {
|
|
996
|
-
if (value.answer.value?.relationshipDetails?.dateOfBirth && !
|
|
997
|
+
if (value.answer.value?.relationshipDetails?.dateOfBirth && !is_valid_mm_dd_yyyy(value.answer.value.relationshipDetails.dateOfBirth)) {
|
|
997
998
|
return "Enter date of birth in MM-DD-YYYY format"
|
|
998
999
|
}
|
|
999
1000
|
if (field.isOptional) return null
|
|
@@ -1240,7 +1241,7 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1240
1241
|
}
|
|
1241
1242
|
}
|
|
1242
1243
|
} else if (value.answer.type === 'dateString') {
|
|
1243
|
-
if (!
|
|
1244
|
+
if (!is_valid_mm_dd_yyyy(value.answer.value)) {
|
|
1244
1245
|
return "Enter a date in MM-DD-YYYY format"
|
|
1245
1246
|
}
|
|
1246
1247
|
} else if (value.answer.type === 'multiple_choice' || value.answer.type === 'Dropdown') {
|
|
@@ -1291,7 +1292,7 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1291
1292
|
for (const row of value.answer.value ?? []) {
|
|
1292
1293
|
for (const cell of row) {
|
|
1293
1294
|
const type = field.options?.tableChoices?.find(t => t.label === cell.label)?.type
|
|
1294
|
-
if (type === 'Date' && !
|
|
1295
|
+
if (type === 'Date' && !is_valid_mm_dd_yyyy(cell.entry)) {
|
|
1295
1296
|
return `Enter a date in MM-DD-YYYY format for ${cell.label} in row ${(value.answer.value?.indexOf(row) ?? 0) + 1}`
|
|
1296
1297
|
}
|
|
1297
1298
|
}
|
|
@@ -1634,6 +1635,9 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1634
1635
|
touched,
|
|
1635
1636
|
isCalledOut: shouldCallout(fields?.find(f => f?.id === fieldId), value),
|
|
1636
1637
|
isHighlightedOnTimeline: fields?.find(f => f?.id === fieldId)?.highlightOnTimeline,
|
|
1638
|
+
// description fields are never "active", so the normal updateInclusion effect won't fire for them
|
|
1639
|
+
// explicitly mark as included when they receive a non-empty string value (historical data snapshot)
|
|
1640
|
+
...(field?.type === 'description' && typeof value === 'string' && value ? { includeInSubmit: true } : {}),
|
|
1637
1641
|
answer: {
|
|
1638
1642
|
...r.answer,
|
|
1639
1643
|
value: value as any,
|
package/src/state.tsx
CHANGED
|
@@ -2637,6 +2637,43 @@ export const useIntegrations = (options={} as HookOptions<Integration>) => {
|
|
|
2637
2637
|
)
|
|
2638
2638
|
}
|
|
2639
2639
|
|
|
2640
|
+
export const useRedactedIntegrations = () => {
|
|
2641
|
+
const session = useSession()
|
|
2642
|
+
const [loadingState, setLoadingState] = useState<LoadedData<Integration[]>>({ status: LoadingStatus.Fetching, value: [] as any })
|
|
2643
|
+
const fetchedRef = useRef(false)
|
|
2644
|
+
|
|
2645
|
+
useEffect(() => {
|
|
2646
|
+
if (fetchedRef.current) return
|
|
2647
|
+
fetchedRef.current = true
|
|
2648
|
+
|
|
2649
|
+
let cancelled = false
|
|
2650
|
+
session.api.integrations.load_redacted({})
|
|
2651
|
+
.then((result: any) => {
|
|
2652
|
+
if (!cancelled) setLoadingState({ status: LoadingStatus.Loaded, value: result.integrations })
|
|
2653
|
+
})
|
|
2654
|
+
.catch((err: any) => {
|
|
2655
|
+
if (!cancelled) setLoadingState({ status: LoadingStatus.Error, value: err })
|
|
2656
|
+
})
|
|
2657
|
+
return () => { cancelled = true }
|
|
2658
|
+
}, [session])
|
|
2659
|
+
|
|
2660
|
+
const updateIntegration = useCallback(async (id: string, updates: Partial<Integration>) => {
|
|
2661
|
+
const result: any = await session.api.integrations.update_settings({ id, updates })
|
|
2662
|
+
setLoadingState((prev: LoadedData<Integration[]>) => {
|
|
2663
|
+
if (prev.status !== LoadingStatus.Loaded) return prev
|
|
2664
|
+
return {
|
|
2665
|
+
...prev,
|
|
2666
|
+
value: prev.value.map((i: Integration) => i.id === id ? { ...i, ...result.integration } : i),
|
|
2667
|
+
}
|
|
2668
|
+
})
|
|
2669
|
+
}, [session])
|
|
2670
|
+
|
|
2671
|
+
return [
|
|
2672
|
+
loadingState,
|
|
2673
|
+
{ updateIntegration },
|
|
2674
|
+
] as const
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2640
2677
|
export const usePortalCustomizations = (options={} as HookOptions<PortalCustomization>) => {
|
|
2641
2678
|
const session = useResolvedSession()
|
|
2642
2679
|
return useListStateHook(
|