@tellescope/react-components 1.249.1 → 1.249.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/lib/cjs/Forms/forms.d.ts.map +1 -1
- package/lib/cjs/Forms/forms.js +13 -5
- package/lib/cjs/Forms/forms.js.map +1 -1
- package/lib/cjs/Forms/forms.v2.d.ts.map +1 -1
- package/lib/cjs/Forms/forms.v2.js +13 -5
- package/lib/cjs/Forms/forms.v2.js.map +1 -1
- package/lib/cjs/Forms/hooks.d.ts +2 -1
- package/lib/cjs/Forms/hooks.d.ts.map +1 -1
- package/lib/cjs/Forms/hooks.js +49 -26
- package/lib/cjs/Forms/hooks.js.map +1 -1
- package/lib/cjs/Forms/inputs.d.ts +19 -4
- package/lib/cjs/Forms/inputs.d.ts.map +1 -1
- package/lib/cjs/Forms/inputs.js +224 -173
- package/lib/cjs/Forms/inputs.js.map +1 -1
- package/lib/cjs/Forms/inputs.v2.d.ts +7 -3
- package/lib/cjs/Forms/inputs.v2.d.ts.map +1 -1
- package/lib/cjs/Forms/inputs.v2.js +42 -32
- package/lib/cjs/Forms/inputs.v2.js.map +1 -1
- package/lib/cjs/TwilioVideo/TwilioControls.d.ts.map +1 -1
- package/lib/cjs/TwilioVideo/TwilioControls.js +12 -2
- package/lib/cjs/TwilioVideo/TwilioControls.js.map +1 -1
- package/lib/cjs/TwilioVideo/TwilioLocalPreview.d.ts.map +1 -1
- package/lib/cjs/TwilioVideo/TwilioLocalPreview.js +154 -2
- package/lib/cjs/TwilioVideo/TwilioLocalPreview.js.map +1 -1
- package/lib/cjs/TwilioVideo/TwilioVideoContext.d.ts +7 -0
- package/lib/cjs/TwilioVideo/TwilioVideoContext.d.ts.map +1 -1
- package/lib/cjs/TwilioVideo/TwilioVideoContext.js +148 -1
- package/lib/cjs/TwilioVideo/TwilioVideoContext.js.map +1 -1
- package/lib/esm/Forms/forms.d.ts.map +1 -1
- package/lib/esm/Forms/forms.js +13 -5
- package/lib/esm/Forms/forms.js.map +1 -1
- package/lib/esm/Forms/forms.v2.d.ts.map +1 -1
- package/lib/esm/Forms/forms.v2.js +13 -5
- package/lib/esm/Forms/forms.v2.js.map +1 -1
- package/lib/esm/Forms/hooks.d.ts +2 -1
- package/lib/esm/Forms/hooks.d.ts.map +1 -1
- package/lib/esm/Forms/hooks.js +49 -26
- package/lib/esm/Forms/hooks.js.map +1 -1
- package/lib/esm/Forms/inputs.d.ts +19 -4
- package/lib/esm/Forms/inputs.d.ts.map +1 -1
- package/lib/esm/Forms/inputs.js +69 -20
- package/lib/esm/Forms/inputs.js.map +1 -1
- package/lib/esm/Forms/inputs.v2.d.ts +7 -3
- package/lib/esm/Forms/inputs.v2.d.ts.map +1 -1
- package/lib/esm/Forms/inputs.v2.js +27 -17
- package/lib/esm/Forms/inputs.v2.js.map +1 -1
- package/lib/esm/TwilioVideo/TwilioControls.d.ts.map +1 -1
- package/lib/esm/TwilioVideo/TwilioControls.js +14 -4
- package/lib/esm/TwilioVideo/TwilioControls.js.map +1 -1
- package/lib/esm/TwilioVideo/TwilioLocalPreview.d.ts.map +1 -1
- package/lib/esm/TwilioVideo/TwilioLocalPreview.js +155 -3
- package/lib/esm/TwilioVideo/TwilioLocalPreview.js.map +1 -1
- package/lib/esm/TwilioVideo/TwilioVideoContext.d.ts +7 -0
- package/lib/esm/TwilioVideo/TwilioVideoContext.d.ts.map +1 -1
- package/lib/esm/TwilioVideo/TwilioVideoContext.js +146 -0
- package/lib/esm/TwilioVideo/TwilioVideoContext.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +11 -10
- package/src/Forms/forms.tsx +18 -2
- package/src/Forms/forms.v2.tsx +18 -2
- package/src/Forms/hooks.tsx +67 -30
- package/src/Forms/inputs.tsx +143 -18
- package/src/Forms/inputs.v2.tsx +58 -8
- package/src/TwilioVideo/TwilioControls.tsx +27 -1
- package/src/TwilioVideo/TwilioLocalPreview.tsx +136 -1
- package/src/TwilioVideo/TwilioVideoContext.tsx +126 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tellescope/react-components",
|
|
3
|
-
"version": "1.249.
|
|
3
|
+
"version": "1.249.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./lib/cjs/index.js",
|
|
6
6
|
"module": "./lib/esm/index.js",
|
|
@@ -51,13 +51,14 @@
|
|
|
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.249.
|
|
55
|
-
"@tellescope/sdk": "1.249.
|
|
56
|
-
"@tellescope/types-client": "1.249.
|
|
57
|
-
"@tellescope/types-models": "1.249.
|
|
58
|
-
"@tellescope/types-utilities": "1.249.
|
|
59
|
-
"@tellescope/utilities": "1.249.
|
|
60
|
-
"@tellescope/validation": "1.249.
|
|
54
|
+
"@tellescope/constants": "1.249.2",
|
|
55
|
+
"@tellescope/sdk": "1.249.2",
|
|
56
|
+
"@tellescope/types-client": "1.249.2",
|
|
57
|
+
"@tellescope/types-models": "1.249.2",
|
|
58
|
+
"@tellescope/types-utilities": "1.249.2",
|
|
59
|
+
"@tellescope/utilities": "1.249.2",
|
|
60
|
+
"@tellescope/validation": "1.249.2",
|
|
61
|
+
"@twilio/video-processors": "3.2.0",
|
|
61
62
|
"css-to-react-native": "3.0.0",
|
|
62
63
|
"draft-js": "0.11.7",
|
|
63
64
|
"draftjs-to-html": "0.9.1",
|
|
@@ -76,7 +77,7 @@
|
|
|
76
77
|
"react-native-video": "5.2.1",
|
|
77
78
|
"react-redux": "7.2.9",
|
|
78
79
|
"react-window": "1.8.9",
|
|
79
|
-
"twilio-video": "
|
|
80
|
+
"twilio-video": "2.35.0",
|
|
80
81
|
"yup": "0.32.11"
|
|
81
82
|
},
|
|
82
83
|
"peerDependencies": {
|
|
@@ -84,7 +85,7 @@
|
|
|
84
85
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
85
86
|
"react-native": "^0.65.0 || ^0.66.0 || ^0.67.0 || ^0.68.0 || ^0.71.0"
|
|
86
87
|
},
|
|
87
|
-
"gitHead": "
|
|
88
|
+
"gitHead": "99f8213ce2e4952deb3df95b242b4e9e336d11f0",
|
|
88
89
|
"publishConfig": {
|
|
89
90
|
"access": "public"
|
|
90
91
|
}
|
package/src/Forms/forms.tsx
CHANGED
|
@@ -272,22 +272,38 @@ export const QuestionForField = ({
|
|
|
272
272
|
)
|
|
273
273
|
: field.type === 'file' ? (
|
|
274
274
|
<File field={field} value={file.blobs?.[0] as any} onChange={onAddFile as any} form={form}
|
|
275
|
+
enduserId={enduserId}
|
|
275
276
|
existingFileName={
|
|
276
277
|
value.answer.type === 'file'
|
|
277
278
|
? value.answer.value?.name
|
|
278
279
|
: ''
|
|
279
|
-
}
|
|
280
|
+
}
|
|
280
281
|
handleFileUpload={handleFileUpload} uploadingFiles={uploadingFiles} setUploadingFiles={setUploadingFiles}
|
|
282
|
+
onSelectExistingFile={v => onFieldChange(v as any, field.id)}
|
|
281
283
|
/>
|
|
282
284
|
)
|
|
283
285
|
: field.type === 'files' ? (
|
|
284
286
|
<Files field={field} value={file.blobs as any} onChange={onAddFile as any} form={form}
|
|
287
|
+
enduserId={enduserId}
|
|
285
288
|
// existingFileName={
|
|
286
289
|
// value.answer.type === 'files'
|
|
287
290
|
// ? value.answer.value?.name
|
|
288
291
|
// : ''
|
|
289
|
-
// }
|
|
292
|
+
// }
|
|
290
293
|
handleFileUpload={handleFileUpload} uploadingFiles={uploadingFiles} setUploadingFiles={setUploadingFiles}
|
|
294
|
+
existingSelections={
|
|
295
|
+
value.answer.type === 'files' && Array.isArray(value.answer.value)
|
|
296
|
+
? value.answer.value.filter(av => !file.blobs?.some(b => b.name === av.name))
|
|
297
|
+
: undefined
|
|
298
|
+
}
|
|
299
|
+
onSelectExistingFile={v => {
|
|
300
|
+
const current = value.answer.type === 'files' && Array.isArray(value.answer.value) ? value.answer.value : []
|
|
301
|
+
onFieldChange([...current, v] as any, field.id)
|
|
302
|
+
}}
|
|
303
|
+
onRemoveExistingFile={secureName => {
|
|
304
|
+
const current = value.answer.type === 'files' && Array.isArray(value.answer.value) ? value.answer.value : []
|
|
305
|
+
onFieldChange(current.filter(f => f.secureName !== secureName) as any, field.id)
|
|
306
|
+
}}
|
|
291
307
|
/>
|
|
292
308
|
)
|
|
293
309
|
: field.type === 'dateString' ? (
|
package/src/Forms/forms.v2.tsx
CHANGED
|
@@ -282,22 +282,38 @@ export const QuestionForField = ({
|
|
|
282
282
|
)
|
|
283
283
|
: field.type === 'file' ? (
|
|
284
284
|
<File field={field} value={file.blobs?.[0] as any} onChange={onAddFile as any} form={form}
|
|
285
|
+
enduserId={enduserId}
|
|
285
286
|
existingFileName={
|
|
286
287
|
value.answer.type === 'file'
|
|
287
288
|
? value.answer.value?.name
|
|
288
289
|
: ''
|
|
289
|
-
}
|
|
290
|
+
}
|
|
290
291
|
handleFileUpload={handleFileUpload} uploadingFiles={uploadingFiles} setUploadingFiles={setUploadingFiles}
|
|
292
|
+
onSelectExistingFile={v => onFieldChange(v as any, field.id)}
|
|
291
293
|
/>
|
|
292
294
|
)
|
|
293
295
|
: field.type === 'files' ? (
|
|
294
296
|
<Files field={field} value={file.blobs as any} onChange={onAddFile as any} form={form}
|
|
297
|
+
enduserId={enduserId}
|
|
295
298
|
// existingFileName={
|
|
296
299
|
// value.answer.type === 'files'
|
|
297
300
|
// ? value.answer.value?.name
|
|
298
301
|
// : ''
|
|
299
|
-
// }
|
|
302
|
+
// }
|
|
300
303
|
handleFileUpload={handleFileUpload} uploadingFiles={uploadingFiles} setUploadingFiles={setUploadingFiles}
|
|
304
|
+
existingSelections={
|
|
305
|
+
value.answer.type === 'files' && Array.isArray(value.answer.value)
|
|
306
|
+
? value.answer.value.filter(av => !file.blobs?.some(b => b.name === av.name))
|
|
307
|
+
: undefined
|
|
308
|
+
}
|
|
309
|
+
onSelectExistingFile={v => {
|
|
310
|
+
const current = value.answer.type === 'files' && Array.isArray(value.answer.value) ? value.answer.value : []
|
|
311
|
+
onFieldChange([...current, v] as any, field.id)
|
|
312
|
+
}}
|
|
313
|
+
onRemoveExistingFile={secureName => {
|
|
314
|
+
const current = value.answer.type === 'files' && Array.isArray(value.answer.value) ? value.answer.value : []
|
|
315
|
+
onFieldChange(current.filter(f => f.secureName !== secureName) as any, field.id)
|
|
316
|
+
}}
|
|
301
317
|
/>
|
|
302
318
|
)
|
|
303
319
|
: field.type === 'dateString' ? (
|
package/src/Forms/hooks.tsx
CHANGED
|
@@ -374,9 +374,10 @@ interface UseTellescopeFormOptions {
|
|
|
374
374
|
groupId?: string,
|
|
375
375
|
groupInstance?: string,
|
|
376
376
|
groupPosition?: number,
|
|
377
|
+
getEnduserAISummary?: () => string | undefined,
|
|
377
378
|
}
|
|
378
379
|
|
|
379
|
-
const OrganizationThemeContext = createContext(null as any as {
|
|
380
|
+
const OrganizationThemeContext = createContext(null as any as {
|
|
380
381
|
theme: OrganizationTheme,
|
|
381
382
|
setTheme: (theme: OrganizationTheme) => void,
|
|
382
383
|
businessId?: string,
|
|
@@ -558,7 +559,7 @@ const shouldCallout = (field: FormField | undefined, value: FormResponseValueAns
|
|
|
558
559
|
|
|
559
560
|
export type Response = FormResponseValue & { touched: boolean, includeInSubmit: boolean, field: FormField }
|
|
560
561
|
export type FileResponse = { fieldId: string, fieldTitle: string, blobs?: FileBlob[] }
|
|
561
|
-
export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogicValue, customization, carePlanId, calendarEventId, context, ga4measurementId, rootResponseId, parentResponseId, accessCode, existingResponses, automationStepId, enduserId, formResponseId, fields, isInternalNote, formTitle, submitRedirectURL, enduser, groupId, groupInstance, groupPosition, startingFieldId }: UseTellescopeFormOptions) => {
|
|
562
|
+
export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogicValue, customization, carePlanId, calendarEventId, context, ga4measurementId, rootResponseId, parentResponseId, accessCode, existingResponses, automationStepId, enduserId, formResponseId, fields, isInternalNote, formTitle, submitRedirectURL, enduser, groupId, groupInstance, groupPosition, startingFieldId, getEnduserAISummary }: UseTellescopeFormOptions) => {
|
|
562
563
|
const { amPm, hoursAmPm, minutes } = get_time_values(new Date())
|
|
563
564
|
|
|
564
565
|
const root = useTreeForFormFields(fields)
|
|
@@ -1504,6 +1505,7 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1504
1505
|
customerId,
|
|
1505
1506
|
productIds: responsesToSubmit.flatMap(r => r.field?.options?.productIds ?? []),
|
|
1506
1507
|
utm: get_utm_params(),
|
|
1508
|
+
...(getEnduserAISummary ? { enduserAISummary: getEnduserAISummary() } : {}),
|
|
1507
1509
|
})
|
|
1508
1510
|
|
|
1509
1511
|
// do actual redirect later to prevent popup
|
|
@@ -1678,33 +1680,68 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1678
1680
|
autoAdvanceRef.current = true
|
|
1679
1681
|
}
|
|
1680
1682
|
|
|
1681
|
-
setResponses(rs =>
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1683
|
+
setResponses(rs => {
|
|
1684
|
+
const updated = rs.map(r => r.fieldId !== fieldId ? r : ({
|
|
1685
|
+
...r,
|
|
1686
|
+
touched,
|
|
1687
|
+
isCalledOut: shouldCallout(fields?.find(f => f?.id === fieldId), value),
|
|
1688
|
+
isHighlightedOnTimeline: fields?.find(f => f?.id === fieldId)?.highlightOnTimeline,
|
|
1689
|
+
// description fields are never "active", so the normal updateInclusion effect won't fire for them
|
|
1690
|
+
// explicitly mark as included when they receive a non-empty string value (historical data snapshot)
|
|
1691
|
+
...(field?.type === 'description' && typeof value === 'string' && value ? { includeInSubmit: true } : {}),
|
|
1692
|
+
answer: {
|
|
1693
|
+
...r.answer,
|
|
1694
|
+
value: value as any,
|
|
1695
|
+
},
|
|
1696
|
+
// keep consistent with initialize existing responses
|
|
1697
|
+
computedValueKey: (
|
|
1698
|
+
field?.intakeField === 'height'
|
|
1699
|
+
? 'Height' as const
|
|
1700
|
+
: field?.intakeField === 'weight' && typeof value === 'number'
|
|
1701
|
+
? 'Weight' as const
|
|
1702
|
+
: field?.intakeField === 'dateOfBirth' && r.answer.type === 'dateString'
|
|
1703
|
+
? 'Date of Birth' as const
|
|
1704
|
+
: field?.intakeField === 'gender' && (r.answer.type === 'Dropdown' || r.answer.type === 'multiple_choice')
|
|
1705
|
+
? 'Gender' as const
|
|
1706
|
+
: field?.intakeField === 'Address' && r.answer.type === 'Address'
|
|
1707
|
+
? 'State' as const
|
|
1708
|
+
: undefined
|
|
1709
|
+
)
|
|
1710
|
+
}))
|
|
1711
|
+
|
|
1712
|
+
// Re-apply filter_stale_choices to every other multiple_choice/Dropdown response now that an upstream
|
|
1713
|
+
// answer may have changed which option-level showCondition results have flipped. Without this cascade,
|
|
1714
|
+
// selections made earlier in the session for options that are no longer visible would silently persist
|
|
1715
|
+
// and pass validation since the stored array remains non-empty.
|
|
1716
|
+
return updated.map(r => {
|
|
1717
|
+
if (r.fieldId === fieldId) return r
|
|
1718
|
+
if (r.answer.type !== 'multiple_choice' && r.answer.type !== 'Dropdown') return r
|
|
1719
|
+
const otherField = fields.find(f => f.id === r.fieldId)
|
|
1720
|
+
if (!otherField) return r
|
|
1721
|
+
if (otherField.type !== 'multiple_choice' && otherField.type !== 'Dropdown') return r
|
|
1722
|
+
|
|
1723
|
+
const prior = r.answer.value as string[] | undefined
|
|
1724
|
+
if (!Array.isArray(prior)) return r
|
|
1725
|
+
|
|
1726
|
+
const filtered = filter_stale_choices(prior, otherField, updated, enduser, form)
|
|
1727
|
+
if (!Array.isArray(filtered)) return r
|
|
1728
|
+
|
|
1729
|
+
// shallow array comparison — preserve reference if unchanged so memoized derivations don't churn
|
|
1730
|
+
if (filtered.length === prior.length && filtered.every((v, i) => v === prior[i])) {
|
|
1731
|
+
return r
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
const isEmpty = filtered.length === 0
|
|
1735
|
+
return {
|
|
1736
|
+
...r,
|
|
1737
|
+
...(isEmpty ? { touched: false, includeInSubmit: false } : {}),
|
|
1738
|
+
answer: {
|
|
1739
|
+
...r.answer,
|
|
1740
|
+
value: filtered as any,
|
|
1741
|
+
},
|
|
1742
|
+
}
|
|
1743
|
+
})
|
|
1744
|
+
})
|
|
1708
1745
|
|
|
1709
1746
|
// ensure stripe payment is stored as saved immediately
|
|
1710
1747
|
const saveField = fields.find(f => f.id === fieldId && (f.type === 'Stripe' || f.type === 'Appointment Booking'))
|
|
@@ -1746,7 +1783,7 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1746
1783
|
})
|
|
1747
1784
|
.catch(console.error)
|
|
1748
1785
|
}
|
|
1749
|
-
}, [fields])
|
|
1786
|
+
}, [fields, enduser, form])
|
|
1750
1787
|
|
|
1751
1788
|
const onAddFile = useCallback((blobs?: FileBlob | FileBlob[], fieldId=activeField.value.id) => {
|
|
1752
1789
|
setSelectedFiles(fs => fs.map(f => f.fieldId !== fieldId ? f : ({
|
package/src/Forms/inputs.tsx
CHANGED
|
@@ -5,15 +5,15 @@ import { FormInputProps } from "./types"
|
|
|
5
5
|
import { useDropzone } from "react-dropzone"
|
|
6
6
|
import { CANVAS_TITLE, BRIDGE_TITLE, CANDID_TITLE, EMOTII_TITLE, INSURANCE_RELATIONSHIPS, INSURANCE_RELATIONSHIPS_CANVAS, PRIMARY_HEX, RELATIONSHIP_TYPES, TELLESCOPE_GENDERS } from "@tellescope/constants"
|
|
7
7
|
import { MM_DD_YYYY_to_YYYY_MM_DD, capture_is_supported, downloadFile, emit_gtm_event, first_letter_capitalized, form_response_value_to_string, format_stripe_subscription_interval, getLocalTimezone, getPublicFileURL, mm_dd_yyyy, object_is_empty, replace_enduser_template_values, responses_satisfy_conditions, truncate_string, update_local_storage, user_display_name } from "@tellescope/utilities"
|
|
8
|
-
import { Address, DatabaseSelectResponse, Enduser, EnduserRelationship, FormResponseValue, InsuranceRelationship, MedicationResponse, MultipleChoiceOptions, FormFieldOptionDetails, Pharmacy, TellescopeGender, TIMEZONES_USA } from "@tellescope/types-models"
|
|
8
|
+
import { Address, DatabaseSelectResponse, Enduser, EnduserRelationship, FormResponseAnswerFileValue, FormResponseValue, InsuranceRelationship, MedicationResponse, MultipleChoiceOptions, FormFieldOptionDetails, Pharmacy, TellescopeGender, TIMEZONES_USA } from "@tellescope/types-models"
|
|
9
9
|
import { VALID_STATES, emailValidator, phoneValidator } from "@tellescope/validation"
|
|
10
10
|
import Slider from '@mui/material/Slider';
|
|
11
11
|
import LinearProgress from '@mui/material/LinearProgress';
|
|
12
12
|
|
|
13
13
|
import DatePicker from "react-datepicker";
|
|
14
14
|
import { datepickerCSS } from "./css/react-datepicker" // avoids build issue with RN
|
|
15
|
-
import { CancelIcon, FileBlob, IconButton, LabeledIconButton, LoadingButton, Styled, form_display_text_for_language, isDateString, useProducts, useResolvedSession } from ".."
|
|
16
|
-
import { CalendarEvent, DatabaseRecord, Form, FormField } from "@tellescope/types-client"
|
|
15
|
+
import { CancelIcon, FileBlob, IconButton, LabeledIconButton, LoadingButton, Styled, form_display_text_for_language, isDateString, useFiles, useProducts, useResolvedSession, value_is_loaded } from ".."
|
|
16
|
+
import { CalendarEvent, DatabaseRecord, File as TellescopeFile, Form, FormField } from "@tellescope/types-client"
|
|
17
17
|
import { css } from '@emotion/css'
|
|
18
18
|
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
|
|
19
19
|
import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
|
|
@@ -650,12 +650,18 @@ export const InsuranceInput = ({ field, onDatabaseSelect, value, onChange, form,
|
|
|
650
650
|
<Grid item xs={12} sm={6}>
|
|
651
651
|
<Autocomplete freeSolo={!field.options?.requirePredefinedInsurer} options={payers.map(p => p.name)}
|
|
652
652
|
value={value?.payerName || ''}
|
|
653
|
-
onChange={(e, v) =>
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
653
|
+
onChange={(e, v) => {
|
|
654
|
+
const matched = payers.find(p => p.name === v)
|
|
655
|
+
if (matched?.databaseRecord) {
|
|
656
|
+
onDatabaseSelect?.([matched.databaseRecord])
|
|
657
|
+
}
|
|
658
|
+
onChange({
|
|
659
|
+
...value,
|
|
660
|
+
payerName: v || '',
|
|
661
|
+
payerId: matched?.id || '',
|
|
662
|
+
payerType: matched?.type || '',
|
|
663
|
+
}, field.id)
|
|
664
|
+
}}
|
|
659
665
|
onInputChange={
|
|
660
666
|
field.options?.requirePredefinedInsurer
|
|
661
667
|
? (e, v) => { if (v) { setQuery(v) } }
|
|
@@ -2246,7 +2252,79 @@ export async function convertHEIC (file: FileBlob | string){
|
|
|
2246
2252
|
};
|
|
2247
2253
|
|
|
2248
2254
|
const value_is_image = (f?: { type?: string })=> f?.type?.includes('image')
|
|
2249
|
-
|
|
2255
|
+
|
|
2256
|
+
const fileMatchesValidTypes = (file: { type?: string }, validFileTypes?: string[]) => {
|
|
2257
|
+
if (!validFileTypes?.length) return true
|
|
2258
|
+
if (!file.type) return false
|
|
2259
|
+
return !!validFileTypes.find(t => file.type!.includes(t.toLowerCase()))
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
export const ExistingFilePicker = ({ enduserId, excludedSecureNames, validFileTypes, onSelect, form, label }: {
|
|
2263
|
+
enduserId?: string,
|
|
2264
|
+
excludedSecureNames?: string[],
|
|
2265
|
+
validFileTypes?: string[],
|
|
2266
|
+
onSelect: (file: TellescopeFile) => void,
|
|
2267
|
+
form?: Form,
|
|
2268
|
+
label?: string,
|
|
2269
|
+
}) => {
|
|
2270
|
+
const session = useResolvedSession()
|
|
2271
|
+
const isEnduserSession = session.type === 'enduser'
|
|
2272
|
+
|
|
2273
|
+
const [, { filtered: getFiltered }] = useFiles({
|
|
2274
|
+
loadFilter: { enduserId },
|
|
2275
|
+
dontFetch: !enduserId || isEnduserSession,
|
|
2276
|
+
})
|
|
2277
|
+
const filesLoading = getFiltered(e => (!!enduserId) && (e.enduserId === enduserId))
|
|
2278
|
+
|
|
2279
|
+
const filtered = useMemo(() => {
|
|
2280
|
+
if (!value_is_loaded(filesLoading)) return []
|
|
2281
|
+
return filesLoading.value.filter(f => (
|
|
2282
|
+
!!f.confirmedAt
|
|
2283
|
+
&& fileMatchesValidTypes(f, validFileTypes)
|
|
2284
|
+
&& !excludedSecureNames?.includes(f.secureName)
|
|
2285
|
+
))
|
|
2286
|
+
}, [filesLoading, validFileTypes, excludedSecureNames])
|
|
2287
|
+
|
|
2288
|
+
// Only available in User (staff) sessions — endusers must upload.
|
|
2289
|
+
if (isEnduserSession) return null
|
|
2290
|
+
if (!enduserId) return null
|
|
2291
|
+
if (filtered.length === 0) return null
|
|
2292
|
+
|
|
2293
|
+
return (
|
|
2294
|
+
<Grid item sx={{ mt: 1 }}>
|
|
2295
|
+
<Autocomplete<TellescopeFile>
|
|
2296
|
+
size="small"
|
|
2297
|
+
options={filtered}
|
|
2298
|
+
getOptionLabel={f => f.name}
|
|
2299
|
+
renderOption={(props, option) => (
|
|
2300
|
+
<li {...props} key={option.id}>
|
|
2301
|
+
<Grid container direction="column">
|
|
2302
|
+
<Typography sx={{ fontSize: 14 }}>{option.name}</Typography>
|
|
2303
|
+
{option.timestamp && (
|
|
2304
|
+
<Typography sx={{ fontSize: 12, color: '#666' }}>
|
|
2305
|
+
{new Date(option.timestamp).toLocaleDateString()}
|
|
2306
|
+
</Typography>
|
|
2307
|
+
)}
|
|
2308
|
+
</Grid>
|
|
2309
|
+
</li>
|
|
2310
|
+
)}
|
|
2311
|
+
onChange={(_, value) => {
|
|
2312
|
+
if (value) onSelect(value)
|
|
2313
|
+
}}
|
|
2314
|
+
value={null}
|
|
2315
|
+
blurOnSelect
|
|
2316
|
+
clearOnBlur
|
|
2317
|
+
renderInput={params => (
|
|
2318
|
+
<TextField {...params}
|
|
2319
|
+
label={label || form_display_text_for_language(form, "Or select an existing file from this patient")}
|
|
2320
|
+
/>
|
|
2321
|
+
)}
|
|
2322
|
+
/>
|
|
2323
|
+
</Grid>
|
|
2324
|
+
)
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
export const FileInput = ({ value, onChange, field, existingFileName, uploadingFiles, handleFileUpload, setUploadingFiles, form, enduserId, onSelectExistingFile }: FormInputProps<'file'> & { existingFileName?: string, onSelectExistingFile?: (value: FormResponseAnswerFileValue) => void }) => {
|
|
2250
2328
|
const [error, setError] = useState('')
|
|
2251
2329
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
|
2252
2330
|
onDrop: useCallback(
|
|
@@ -2341,17 +2419,28 @@ export const FileInput = ({ value, onChange, field, existingFileName, uploadingF
|
|
|
2341
2419
|
</Grid>
|
|
2342
2420
|
|
|
2343
2421
|
<Grid item alignSelf="center" sx={{ mt: 0.5 }}>
|
|
2344
|
-
{(!value?.name && existingFileName) &&
|
|
2422
|
+
{(!value?.name && existingFileName) &&
|
|
2345
2423
|
<Typography>{existingFileName} selected!</Typography>
|
|
2346
2424
|
}
|
|
2347
2425
|
</Grid>
|
|
2348
|
-
{
|
|
2426
|
+
{!value && onSelectExistingFile && (
|
|
2427
|
+
<ExistingFilePicker
|
|
2428
|
+
enduserId={enduserId}
|
|
2429
|
+
validFileTypes={field.options?.validFileTypes}
|
|
2430
|
+
form={form}
|
|
2431
|
+
onSelect={file => {
|
|
2432
|
+
setError('')
|
|
2433
|
+
onSelectExistingFile({ secureName: file.secureName, name: file.name, type: file.type })
|
|
2434
|
+
}}
|
|
2435
|
+
/>
|
|
2436
|
+
)}
|
|
2437
|
+
{error &&
|
|
2349
2438
|
<Grid item alignSelf="center" sx={{ mt: 0.5 }}>
|
|
2350
2439
|
<Typography color="error">{error}</Typography>
|
|
2351
2440
|
</Grid>
|
|
2352
2441
|
}
|
|
2353
2442
|
</Grid>
|
|
2354
|
-
)
|
|
2443
|
+
)
|
|
2355
2444
|
}
|
|
2356
2445
|
|
|
2357
2446
|
export const safe_create_url = (file: any) => {
|
|
@@ -2363,8 +2452,15 @@ export const safe_create_url = (file: any) => {
|
|
|
2363
2452
|
}
|
|
2364
2453
|
}
|
|
2365
2454
|
|
|
2366
|
-
export const FilesInput = ({ value, onChange, field, existingFileName, uploadingFiles, handleFileUpload, setUploadingFiles, form }: FormInputProps<'files'> & { existingFileName?: string }) => {
|
|
2455
|
+
export const FilesInput = ({ value, onChange, field, existingFileName, uploadingFiles, handleFileUpload, setUploadingFiles, form, enduserId, existingSelections, onSelectExistingFile, onRemoveExistingFile }: FormInputProps<'files'> & { existingFileName?: string, existingSelections?: FormResponseAnswerFileValue[], onSelectExistingFile?: (value: FormResponseAnswerFileValue) => void, onRemoveExistingFile?: (secureName: string) => void }) => {
|
|
2367
2456
|
const [error, setError] = useState('')
|
|
2457
|
+
|
|
2458
|
+
const safeExistingSelections = Array.isArray(existingSelections) ? existingSelections : undefined
|
|
2459
|
+
|
|
2460
|
+
const excludedSecureNames = useMemo(() => (
|
|
2461
|
+
safeExistingSelections?.map(s => s.secureName)
|
|
2462
|
+
), [safeExistingSelections])
|
|
2463
|
+
|
|
2368
2464
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
|
2369
2465
|
onDrop: useCallback(
|
|
2370
2466
|
async acceptedFiles => {
|
|
@@ -2458,8 +2554,8 @@ export const FilesInput = ({ value, onChange, field, existingFileName, uploading
|
|
|
2458
2554
|
|
|
2459
2555
|
{file.type?.includes('image') && previews[i] &&
|
|
2460
2556
|
<Grid item>
|
|
2461
|
-
<img
|
|
2462
|
-
src={previews[i]!}
|
|
2557
|
+
<img
|
|
2558
|
+
src={previews[i]!}
|
|
2463
2559
|
style={{ maxWidth: '45%', maxHeight: 80, height: '100%' }}
|
|
2464
2560
|
/>
|
|
2465
2561
|
</Grid>
|
|
@@ -2476,15 +2572,44 @@ export const FilesInput = ({ value, onChange, field, existingFileName, uploading
|
|
|
2476
2572
|
</Grid>
|
|
2477
2573
|
</Grid>
|
|
2478
2574
|
))}
|
|
2575
|
+
{safeExistingSelections?.map((selection, i) => (
|
|
2576
|
+
<Grid item key={`existing-${selection.secureName}-${i}`} sx={{ mt: 0.5 }}>
|
|
2577
|
+
<Grid container alignItems="center" justifyContent={"space-between"} wrap="nowrap">
|
|
2578
|
+
<Grid item>
|
|
2579
|
+
<Typography sx={{ mr: 1 }}>{selection.name}</Typography>
|
|
2580
|
+
</Grid>
|
|
2581
|
+
{onRemoveExistingFile &&
|
|
2582
|
+
<Grid item>
|
|
2583
|
+
<LabeledIconButton label={form_display_text_for_language(form, "Remove")}
|
|
2584
|
+
Icon={Delete}
|
|
2585
|
+
onClick={() => onRemoveExistingFile(selection.secureName)}
|
|
2586
|
+
/>
|
|
2587
|
+
</Grid>
|
|
2588
|
+
}
|
|
2589
|
+
</Grid>
|
|
2590
|
+
</Grid>
|
|
2591
|
+
))}
|
|
2479
2592
|
</Grid>
|
|
2593
|
+
{onSelectExistingFile && (
|
|
2594
|
+
<ExistingFilePicker
|
|
2595
|
+
enduserId={enduserId}
|
|
2596
|
+
excludedSecureNames={excludedSecureNames}
|
|
2597
|
+
validFileTypes={field.options?.validFileTypes}
|
|
2598
|
+
form={form}
|
|
2599
|
+
onSelect={file => {
|
|
2600
|
+
setError('')
|
|
2601
|
+
onSelectExistingFile({ secureName: file.secureName, name: file.name, type: file.type })
|
|
2602
|
+
}}
|
|
2603
|
+
/>
|
|
2604
|
+
)}
|
|
2480
2605
|
|
|
2481
|
-
{error &&
|
|
2606
|
+
{error &&
|
|
2482
2607
|
<Grid item alignSelf="center" sx={{ mt: 0.5 }}>
|
|
2483
2608
|
<Typography color="error">{error}</Typography>
|
|
2484
2609
|
</Grid>
|
|
2485
2610
|
}
|
|
2486
2611
|
</Grid>
|
|
2487
|
-
)
|
|
2612
|
+
)
|
|
2488
2613
|
}
|
|
2489
2614
|
|
|
2490
2615
|
const multipleChoiceItemSx: SxProps = {
|
package/src/Forms/inputs.v2.tsx
CHANGED
|
@@ -5,7 +5,7 @@ import { FormInputProps } from "./types"
|
|
|
5
5
|
import { useDropzone } from "react-dropzone"
|
|
6
6
|
import { CANVAS_TITLE, EMOTII_TITLE, INSURANCE_RELATIONSHIPS, INSURANCE_RELATIONSHIPS_CANVAS, PRIMARY_HEX, RELATIONSHIP_TYPES, TELLESCOPE_GENDERS } from "@tellescope/constants"
|
|
7
7
|
import { MM_DD_YYYY_to_YYYY_MM_DD, capture_is_supported, downloadFile, emit_gtm_event, first_letter_capitalized, form_response_value_to_string, getLocalTimezone, getPublicFileURL, mm_dd_yyyy, replace_enduser_template_values, responses_satisfy_conditions, truncate_string, update_local_storage, user_display_name } from "@tellescope/utilities"
|
|
8
|
-
import { Enduser, EnduserRelationship, FormResponseValue, InsuranceRelationship, MedicationResponse, MultipleChoiceOptions, TellescopeGender, TIMEZONES_USA } from "@tellescope/types-models"
|
|
8
|
+
import { Enduser, EnduserRelationship, FormResponseAnswerFileValue, FormResponseValue, InsuranceRelationship, MedicationResponse, MultipleChoiceOptions, TellescopeGender, TIMEZONES_USA } from "@tellescope/types-models"
|
|
9
9
|
import { VALID_STATES, emailValidator, phoneValidator } from "@tellescope/validation"
|
|
10
10
|
import Slider from '@mui/material/Slider';
|
|
11
11
|
import LinearProgress from '@mui/material/LinearProgress';
|
|
@@ -24,6 +24,7 @@ import LanguageIcon from '@mui/icons-material/Language';
|
|
|
24
24
|
import { CheckCircleOutline, Delete, Edit, UploadFile } from "@mui/icons-material"
|
|
25
25
|
import { WYSIWYG } from "./wysiwyg"
|
|
26
26
|
import { useConditionalChoices, Response, dateFromOffsetMs } from "./hooks"
|
|
27
|
+
import { ExistingFilePicker } from "./inputs"
|
|
27
28
|
|
|
28
29
|
export const LanguageSelect = ({ value, ...props }: { value: string, onChange: (s: string) => void}) => (
|
|
29
30
|
<Grid container alignItems="center" justifyContent={"center"} wrap="nowrap" spacing={1}>
|
|
@@ -930,7 +931,8 @@ export async function convertHEIC (file: FileBlob | string){
|
|
|
930
931
|
};
|
|
931
932
|
|
|
932
933
|
const value_is_image = (f?: { type?: string })=> f?.type?.includes('image')
|
|
933
|
-
|
|
934
|
+
|
|
935
|
+
export const FileInput = ({ value, onChange, field, existingFileName, uploadingFiles, handleFileUpload, setUploadingFiles, form, enduserId, onSelectExistingFile }: FormInputProps<'file'> & { existingFileName?: string, onSelectExistingFile?: (value: FormResponseAnswerFileValue) => void }) => {
|
|
934
936
|
const [error, setError] = useState('')
|
|
935
937
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
|
936
938
|
onDrop: useCallback(
|
|
@@ -1029,17 +1031,28 @@ export const FileInput = ({ value, onChange, field, existingFileName, uploadingF
|
|
|
1029
1031
|
</Grid>
|
|
1030
1032
|
|
|
1031
1033
|
<Grid item alignSelf="center" sx={{ mt: 0.5 }}>
|
|
1032
|
-
{(!value?.name && existingFileName) &&
|
|
1034
|
+
{(!value?.name && existingFileName) &&
|
|
1033
1035
|
<Typography>{existingFileName} selected!</Typography>
|
|
1034
1036
|
}
|
|
1035
1037
|
</Grid>
|
|
1036
|
-
{
|
|
1038
|
+
{!value && onSelectExistingFile && (
|
|
1039
|
+
<ExistingFilePicker
|
|
1040
|
+
enduserId={enduserId}
|
|
1041
|
+
validFileTypes={field.options?.validFileTypes}
|
|
1042
|
+
form={form}
|
|
1043
|
+
onSelect={file => {
|
|
1044
|
+
setError('')
|
|
1045
|
+
onSelectExistingFile({ secureName: file.secureName, name: file.name, type: file.type })
|
|
1046
|
+
}}
|
|
1047
|
+
/>
|
|
1048
|
+
)}
|
|
1049
|
+
{error &&
|
|
1037
1050
|
<Grid item alignSelf="center" sx={{ mt: 0.5 }}>
|
|
1038
1051
|
<Typography color="error">{error}</Typography>
|
|
1039
1052
|
</Grid>
|
|
1040
1053
|
}
|
|
1041
1054
|
</Grid>
|
|
1042
|
-
)
|
|
1055
|
+
)
|
|
1043
1056
|
}
|
|
1044
1057
|
|
|
1045
1058
|
export const safe_create_url = (file: any) => {
|
|
@@ -1051,8 +1064,15 @@ export const safe_create_url = (file: any) => {
|
|
|
1051
1064
|
}
|
|
1052
1065
|
}
|
|
1053
1066
|
|
|
1054
|
-
export const FilesInput = ({ value, onChange, field, existingFileName, uploadingFiles, handleFileUpload, setUploadingFiles, form }: FormInputProps<'files'> & { existingFileName?: string }) => {
|
|
1067
|
+
export const FilesInput = ({ value, onChange, field, existingFileName, uploadingFiles, handleFileUpload, setUploadingFiles, form, enduserId, existingSelections, onSelectExistingFile, onRemoveExistingFile }: FormInputProps<'files'> & { existingFileName?: string, existingSelections?: FormResponseAnswerFileValue[], onSelectExistingFile?: (value: FormResponseAnswerFileValue) => void, onRemoveExistingFile?: (secureName: string) => void }) => {
|
|
1055
1068
|
const [error, setError] = useState('')
|
|
1069
|
+
|
|
1070
|
+
const safeExistingSelections = Array.isArray(existingSelections) ? existingSelections : undefined
|
|
1071
|
+
|
|
1072
|
+
const excludedSecureNames = useMemo(() => (
|
|
1073
|
+
safeExistingSelections?.map(s => s.secureName)
|
|
1074
|
+
), [safeExistingSelections])
|
|
1075
|
+
|
|
1056
1076
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
|
1057
1077
|
onDrop: useCallback(
|
|
1058
1078
|
async acceptedFiles => {
|
|
@@ -1164,15 +1184,45 @@ export const FilesInput = ({ value, onChange, field, existingFileName, uploading
|
|
|
1164
1184
|
</Grid>
|
|
1165
1185
|
</Grid>
|
|
1166
1186
|
))}
|
|
1187
|
+
{existingSelections?.map((selection, i) => (
|
|
1188
|
+
<Grid item key={`existing-${selection.secureName}-${i}`} sx={{ mt: 0.5 }}>
|
|
1189
|
+
<Grid container alignItems="center" justifyContent={"space-between"} wrap="nowrap">
|
|
1190
|
+
<Grid item>
|
|
1191
|
+
<Typography sx={{ mr: 1 }}>{selection.name}</Typography>
|
|
1192
|
+
</Grid>
|
|
1193
|
+
{onRemoveExistingFile &&
|
|
1194
|
+
<Grid item>
|
|
1195
|
+
<LabeledIconButton label={form_display_text_for_language(form, "Remove")}
|
|
1196
|
+
Icon={Delete}
|
|
1197
|
+
onClick={() => onRemoveExistingFile(selection.secureName)}
|
|
1198
|
+
/>
|
|
1199
|
+
</Grid>
|
|
1200
|
+
}
|
|
1201
|
+
</Grid>
|
|
1202
|
+
</Grid>
|
|
1203
|
+
))}
|
|
1167
1204
|
</Grid>
|
|
1168
1205
|
|
|
1169
|
-
{
|
|
1206
|
+
{onSelectExistingFile && (
|
|
1207
|
+
<ExistingFilePicker
|
|
1208
|
+
enduserId={enduserId}
|
|
1209
|
+
excludedSecureNames={excludedSecureNames}
|
|
1210
|
+
validFileTypes={field.options?.validFileTypes}
|
|
1211
|
+
form={form}
|
|
1212
|
+
onSelect={file => {
|
|
1213
|
+
setError('')
|
|
1214
|
+
onSelectExistingFile({ secureName: file.secureName, name: file.name, type: file.type })
|
|
1215
|
+
}}
|
|
1216
|
+
/>
|
|
1217
|
+
)}
|
|
1218
|
+
|
|
1219
|
+
{error &&
|
|
1170
1220
|
<Grid item alignSelf="center" sx={{ mt: 0.5 }}>
|
|
1171
1221
|
<Typography color="error">{error}</Typography>
|
|
1172
1222
|
</Grid>
|
|
1173
1223
|
}
|
|
1174
1224
|
</Grid>
|
|
1175
|
-
)
|
|
1225
|
+
)
|
|
1176
1226
|
}
|
|
1177
1227
|
|
|
1178
1228
|
export const MultipleChoiceInput = ({ field, form, value: _value, onChange, responses, enduser }: FormInputProps<'multiple_choice'>) => {
|