@tellescope/react-components 1.234.0 → 1.235.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/forms.d.ts.map +1 -1
- package/lib/cjs/Forms/forms.js +37 -35
- 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 +37 -35
- package/lib/cjs/Forms/forms.v2.js.map +1 -1
- package/lib/cjs/Forms/inputs.d.ts +17 -2
- package/lib/cjs/Forms/inputs.d.ts.map +1 -1
- package/lib/cjs/Forms/inputs.js +308 -36
- package/lib/cjs/Forms/inputs.js.map +1 -1
- package/lib/cjs/Forms/inputs.v2.d.ts +3 -2
- package/lib/cjs/Forms/inputs.v2.d.ts.map +1 -1
- package/lib/cjs/Forms/inputs.v2.js +20 -293
- package/lib/cjs/Forms/inputs.v2.js.map +1 -1
- package/lib/cjs/Forms/types.d.ts +4 -0
- package/lib/cjs/Forms/types.d.ts.map +1 -1
- package/lib/esm/Forms/forms.d.ts.map +1 -1
- package/lib/esm/Forms/forms.js +38 -36
- 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 +38 -36
- package/lib/esm/Forms/forms.v2.js.map +1 -1
- package/lib/esm/Forms/inputs.d.ts +17 -2
- package/lib/esm/Forms/inputs.d.ts.map +1 -1
- package/lib/esm/Forms/inputs.js +306 -37
- package/lib/esm/Forms/inputs.js.map +1 -1
- package/lib/esm/Forms/inputs.v2.d.ts +3 -2
- package/lib/esm/Forms/inputs.v2.d.ts.map +1 -1
- package/lib/esm/Forms/inputs.v2.js +15 -289
- package/lib/esm/Forms/inputs.v2.js.map +1 -1
- package/lib/esm/Forms/types.d.ts +4 -0
- package/lib/esm/Forms/types.d.ts.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/src/Forms/forms.tsx +13 -6
- package/src/Forms/forms.v2.tsx +9 -2
- package/src/Forms/inputs.tsx +449 -65
- package/src/Forms/inputs.v2.tsx +12 -593
- package/src/Forms/types.ts +4 -2
package/src/Forms/inputs.tsx
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react"
|
|
2
2
|
import axios from "axios"
|
|
3
|
-
import { Autocomplete, Box, Button, Checkbox, Chip, CircularProgress, Collapse, Divider, FormControl, FormControlLabel, FormLabel, Grid, IconButton as MuiIconButton, InputLabel, MenuItem, Radio, RadioGroup, Select, SxProps, TextField, TextFieldProps, Typography } from "@mui/material"
|
|
3
|
+
import { Autocomplete, Box, Button, Checkbox, Chip, CircularProgress, Collapse, Divider, FormControl, FormControlLabel, FormLabel, Grid, IconButton as MuiIconButton, InputLabel, MenuItem, Paper, Radio, RadioGroup, Select, SxProps, TextField, TextFieldProps, Typography } from "@mui/material"
|
|
4
4
|
import { FormInputProps } from "./types"
|
|
5
5
|
import { useDropzone } from "react-dropzone"
|
|
6
|
-
import { CANVAS_TITLE, EMOTII_TITLE, INSURANCE_RELATIONSHIPS, INSURANCE_RELATIONSHIPS_CANVAS, PRIMARY_HEX, RELATIONSHIP_TYPES, TELLESCOPE_GENDERS } from "@tellescope/constants"
|
|
6
|
+
import { CANVAS_TITLE, BRIDGE_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
8
|
import { Address, DatabaseSelectResponse, Enduser, EnduserRelationship, FormResponseValue, InsuranceRelationship, MedicationResponse, MultipleChoiceOptions, FormFieldOptionDetails, TellescopeGender, TIMEZONES_USA } from "@tellescope/types-models"
|
|
9
9
|
import { VALID_STATES, emailValidator, phoneValidator } from "@tellescope/validation"
|
|
@@ -26,6 +26,16 @@ import { loadStripe } from '@stripe/stripe-js';
|
|
|
26
26
|
import { CheckCircleOutline, Delete, Edit, ExpandMore } from "@mui/icons-material"
|
|
27
27
|
import { WYSIWYG } from "./wysiwyg"
|
|
28
28
|
|
|
29
|
+
// Bridge Eligibility - shared variable for storing most recent eligibility userIds
|
|
30
|
+
const bridgeEligibilityResult = {
|
|
31
|
+
userIds: [] as string[],
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const getBridgeEligibilityUserIds = () => bridgeEligibilityResult.userIds
|
|
35
|
+
export const setBridgeEligibilityUserIds = (userIds: string[]) => {
|
|
36
|
+
bridgeEligibilityResult.userIds = userIds
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
// Debounce hook for search functionality
|
|
30
40
|
const useDebounce = <T,>(value: T, delay: number): T => {
|
|
31
41
|
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
|
@@ -409,15 +419,19 @@ export const TableInput = ({ field, value=[], onChange, ...props }: FormInputPro
|
|
|
409
419
|
)
|
|
410
420
|
}
|
|
411
421
|
|
|
412
|
-
export const AutoFocusTextField = (props: TextFieldProps) =>
|
|
413
|
-
|
|
414
|
-
|
|
422
|
+
export const AutoFocusTextField = (props: TextFieldProps & { inputProps?: { sx: SxProps } }) => {
|
|
423
|
+
const { inputProps, ...textFieldProps } = props
|
|
424
|
+
return <TextField InputProps={inputProps || defaultInputProps} {...textFieldProps} />
|
|
425
|
+
}
|
|
415
426
|
|
|
416
|
-
const CustomDateStringInput = forwardRef((props: TextFieldProps, ref) =>
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
427
|
+
const CustomDateStringInput = forwardRef((props: TextFieldProps & { inputProps?: { sx: SxProps } }, ref) => {
|
|
428
|
+
const { inputProps, ...textFieldProps } = props
|
|
429
|
+
return (
|
|
430
|
+
<TextField InputProps={inputProps || defaultInputProps}
|
|
431
|
+
fullWidth inputRef={ref} {...textFieldProps}
|
|
432
|
+
/>
|
|
433
|
+
)
|
|
434
|
+
})
|
|
421
435
|
export const DateStringInput = ({ field, value, onChange, ...props }: FormInputProps<'string'>) => {
|
|
422
436
|
const inputRef = useRef(null);
|
|
423
437
|
|
|
@@ -543,7 +557,9 @@ export const NumberInput = ({ field, value, onChange, form, ...props }: FormInpu
|
|
|
543
557
|
)
|
|
544
558
|
}
|
|
545
559
|
|
|
546
|
-
export const InsuranceInput = ({ field, onDatabaseSelect, value, onChange, form, responses, enduser, ...props }: FormInputProps<'Insurance'>
|
|
560
|
+
export const InsuranceInput = ({ field, onDatabaseSelect, value, onChange, form, responses, enduser, inputProps, ...props }: FormInputProps<'Insurance'> & {
|
|
561
|
+
inputProps?: { sx: SxProps },
|
|
562
|
+
}) => {
|
|
547
563
|
const session = useResolvedSession()
|
|
548
564
|
|
|
549
565
|
const [payers, setPayers] = useState<{ id: string, name: string, databaseRecord?: DatabaseRecord, type?: string, state?: string }[]>([])
|
|
@@ -566,6 +582,7 @@ export const InsuranceInput = ({ field, onDatabaseSelect, value, onChange, form,
|
|
|
566
582
|
const loadRef = useRef(false) // so session changes don't cause
|
|
567
583
|
useEffect(() => {
|
|
568
584
|
if (field?.options?.dataSource === CANVAS_TITLE) return // instead, look-up while typing against Canvas Search API
|
|
585
|
+
if (field?.options?.dataSource === BRIDGE_TITLE) return // instead, look-up while typing against Bridge Search API
|
|
569
586
|
if (loadRef.current) return
|
|
570
587
|
loadRef.current = true
|
|
571
588
|
|
|
@@ -587,25 +604,32 @@ export const InsuranceInput = ({ field, onDatabaseSelect, value, onChange, form,
|
|
|
587
604
|
|
|
588
605
|
const searchRef = useRef(query)
|
|
589
606
|
useEffect(() => {
|
|
590
|
-
if (field?.options?.dataSource !== CANVAS_TITLE) { return }
|
|
607
|
+
if (field?.options?.dataSource !== CANVAS_TITLE && field?.options?.dataSource !== BRIDGE_TITLE) { return }
|
|
591
608
|
if (!query) return
|
|
592
609
|
if (searchRef.current === query) return
|
|
593
610
|
searchRef.current = query
|
|
594
611
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
612
|
+
const integration = field?.options?.dataSource === CANVAS_TITLE ? CANVAS_TITLE : BRIDGE_TITLE
|
|
613
|
+
const type = field?.options?.dataSource === CANVAS_TITLE ? 'organizations' : 'payers'
|
|
614
|
+
|
|
615
|
+
const t = setTimeout(() => (
|
|
616
|
+
session.api.integrations.proxy_read({
|
|
617
|
+
integration,
|
|
618
|
+
query,
|
|
619
|
+
type,
|
|
620
|
+
})
|
|
621
|
+
.then(({ data }) => {
|
|
622
|
+
try {
|
|
623
|
+
setPayers(data.map((d: any) => ({
|
|
624
|
+
id: field?.options?.dataSource === CANVAS_TITLE ? d.resource.id : d.id,
|
|
625
|
+
name: field?.options?.dataSource === CANVAS_TITLE ? d.resource.name : d.name,
|
|
626
|
+
})))
|
|
627
|
+
} catch(err) { console.error }
|
|
628
|
+
})
|
|
629
|
+
.catch(console.error)
|
|
630
|
+
), 300)
|
|
631
|
+
|
|
632
|
+
return () => { clearTimeout(t) }
|
|
609
633
|
}, [session, field?.options?.dataSource, query])
|
|
610
634
|
|
|
611
635
|
return (
|
|
@@ -639,15 +663,15 @@ export const InsuranceInput = ({ field, onDatabaseSelect, value, onChange, form,
|
|
|
639
663
|
}
|
|
640
664
|
}
|
|
641
665
|
renderInput={(params) => (
|
|
642
|
-
<TextField {...params} InputProps={{ ...params.InputProps, sx: defaultInputProps.sx }}
|
|
666
|
+
<TextField {...params} InputProps={{ ...params.InputProps, sx: (inputProps || defaultInputProps).sx }}
|
|
643
667
|
required={!field.isOptional} size="small" label={"Insurer"}
|
|
644
|
-
placeholder={field.options?.dataSource === CANVAS_TITLE ? "Search insurer..." : "Insurer"}
|
|
668
|
+
placeholder={(field.options?.dataSource === CANVAS_TITLE || field.options?.dataSource === BRIDGE_TITLE) ? "Search insurer..." : "Insurer"}
|
|
645
669
|
/>
|
|
646
670
|
)}
|
|
647
671
|
/>
|
|
648
672
|
</Grid>
|
|
649
673
|
<Grid item xs={12} sm={6}>
|
|
650
|
-
<TextField InputProps={defaultInputProps} required={!field.isOptional} fullWidth value={value?.memberId ?? ''}
|
|
674
|
+
<TextField InputProps={inputProps || defaultInputProps} required={!field.isOptional} fullWidth value={value?.memberId ?? ''}
|
|
651
675
|
onChange={e => onChange({ ...value, memberId: e.target.value }, field.id)}
|
|
652
676
|
label={form_display_text_for_language(form, "Member ID", '')}
|
|
653
677
|
size="small"
|
|
@@ -655,8 +679,8 @@ export const InsuranceInput = ({ field, onDatabaseSelect, value, onChange, form,
|
|
|
655
679
|
</Grid>
|
|
656
680
|
|
|
657
681
|
<Grid item xs={12} sm={6}>
|
|
658
|
-
<TextField InputProps={defaultInputProps} required={false} fullWidth value={value?.planName ?? ''}
|
|
659
|
-
onChange={e => onChange({ ...value, planName: e.target.value }, field.id)}
|
|
682
|
+
<TextField InputProps={inputProps || defaultInputProps} required={false} fullWidth value={value?.planName ?? ''}
|
|
683
|
+
onChange={e => onChange({ ...value, planName: e.target.value }, field.id)}
|
|
660
684
|
label={form_display_text_for_language(form, "Plan Name", '')}
|
|
661
685
|
size="small"
|
|
662
686
|
/>
|
|
@@ -664,14 +688,15 @@ export const InsuranceInput = ({ field, onDatabaseSelect, value, onChange, form,
|
|
|
664
688
|
|
|
665
689
|
<Grid item xs={12} sm={6}>
|
|
666
690
|
<DateStringInput size="small" label="Plan Start Date"
|
|
691
|
+
inputProps={inputProps}
|
|
667
692
|
field={{
|
|
668
693
|
...field,
|
|
669
694
|
isOptional: true, //field.isOptional || field.options?.billingProvider === 'Candid'
|
|
670
|
-
}}
|
|
671
|
-
value={value?.startDate || ''}
|
|
672
|
-
onChange={startDate =>
|
|
673
|
-
onChange({
|
|
674
|
-
...value,
|
|
695
|
+
}}
|
|
696
|
+
value={value?.startDate || ''}
|
|
697
|
+
onChange={startDate =>
|
|
698
|
+
onChange({
|
|
699
|
+
...value,
|
|
675
700
|
startDate,
|
|
676
701
|
}, field.id)
|
|
677
702
|
}
|
|
@@ -680,7 +705,7 @@ export const InsuranceInput = ({ field, onDatabaseSelect, value, onChange, form,
|
|
|
680
705
|
|
|
681
706
|
{field.options?.includeGroupNumber &&
|
|
682
707
|
<Grid item xs={12}>
|
|
683
|
-
<TextField InputProps={defaultInputProps} fullWidth value={value?.groupNumber ?? ''}
|
|
708
|
+
<TextField InputProps={inputProps || defaultInputProps} fullWidth value={value?.groupNumber ?? ''}
|
|
684
709
|
onChange={e => onChange({ ...value, groupNumber: e.target.value }, field.id)}
|
|
685
710
|
label={form_display_text_for_language(form, "Group Number", '')}
|
|
686
711
|
size="small"
|
|
@@ -690,16 +715,17 @@ export const InsuranceInput = ({ field, onDatabaseSelect, value, onChange, form,
|
|
|
690
715
|
|
|
691
716
|
<Grid item xs={12}>
|
|
692
717
|
<StringSelector size="small" label="Relationship to Policy Owner"
|
|
718
|
+
inputProps={inputProps}
|
|
693
719
|
options={
|
|
694
720
|
(
|
|
695
721
|
(field.options?.billingProvider === CANVAS_TITLE || field.options?.dataSource === CANVAS_TITLE )
|
|
696
|
-
? INSURANCE_RELATIONSHIPS_CANVAS
|
|
722
|
+
? INSURANCE_RELATIONSHIPS_CANVAS
|
|
697
723
|
: INSURANCE_RELATIONSHIPS
|
|
698
724
|
)
|
|
699
725
|
.sort((x, y) => x.localeCompare(y))
|
|
700
726
|
}
|
|
701
|
-
value={value?.relationship || 'Self'}
|
|
702
|
-
onChange={relationship =>
|
|
727
|
+
value={value?.relationship || 'Self'}
|
|
728
|
+
onChange={relationship =>
|
|
703
729
|
onChange({ ...value, relationship: relationship as InsuranceRelationship || 'Self' }, field.id)
|
|
704
730
|
}
|
|
705
731
|
/>
|
|
@@ -712,24 +738,24 @@ export const InsuranceInput = ({ field, onDatabaseSelect, value, onChange, form,
|
|
|
712
738
|
</Grid>
|
|
713
739
|
|
|
714
740
|
<Grid item xs={6}>
|
|
715
|
-
<TextField label="First Name" size="small" InputProps={defaultInputProps} fullWidth
|
|
716
|
-
value={value?.relationshipDetails?.fname || ''}
|
|
741
|
+
<TextField label="First Name" size="small" InputProps={inputProps || defaultInputProps} fullWidth
|
|
742
|
+
value={value?.relationshipDetails?.fname || ''}
|
|
717
743
|
required={!field.isOptional}
|
|
718
|
-
onChange={e =>
|
|
719
|
-
onChange({
|
|
720
|
-
...value,
|
|
744
|
+
onChange={e =>
|
|
745
|
+
onChange({
|
|
746
|
+
...value,
|
|
721
747
|
relationshipDetails: { ...value?.relationshipDetails, fname: e.target.value }
|
|
722
748
|
}, field.id)
|
|
723
749
|
}
|
|
724
750
|
/>
|
|
725
751
|
</Grid>
|
|
726
752
|
<Grid item xs={6}>
|
|
727
|
-
<TextField label="Last Name" size="small" InputProps={defaultInputProps} fullWidth
|
|
728
|
-
value={value?.relationshipDetails?.lname || ''}
|
|
753
|
+
<TextField label="Last Name" size="small" InputProps={inputProps || defaultInputProps} fullWidth
|
|
754
|
+
value={value?.relationshipDetails?.lname || ''}
|
|
729
755
|
required={!field.isOptional}
|
|
730
|
-
onChange={e =>
|
|
731
|
-
onChange({
|
|
732
|
-
...value,
|
|
756
|
+
onChange={e =>
|
|
757
|
+
onChange({
|
|
758
|
+
...value,
|
|
733
759
|
relationshipDetails: { ...value?.relationshipDetails, lname: e.target.value }
|
|
734
760
|
}, field.id)
|
|
735
761
|
}
|
|
@@ -737,11 +763,12 @@ export const InsuranceInput = ({ field, onDatabaseSelect, value, onChange, form,
|
|
|
737
763
|
</Grid>
|
|
738
764
|
<Grid item xs={6}>
|
|
739
765
|
<StringSelector options={TELLESCOPE_GENDERS} size="small" label="Gender"
|
|
740
|
-
|
|
766
|
+
inputProps={inputProps}
|
|
767
|
+
value={value?.relationshipDetails?.gender || ''}
|
|
741
768
|
required={!field.isOptional}
|
|
742
|
-
onChange={v =>
|
|
743
|
-
onChange({
|
|
744
|
-
...value,
|
|
769
|
+
onChange={v =>
|
|
770
|
+
onChange({
|
|
771
|
+
...value,
|
|
745
772
|
relationshipDetails: { ...value?.relationshipDetails, gender: v as TellescopeGender }
|
|
746
773
|
}, field.id)
|
|
747
774
|
}
|
|
@@ -749,14 +776,15 @@ export const InsuranceInput = ({ field, onDatabaseSelect, value, onChange, form,
|
|
|
749
776
|
</Grid>
|
|
750
777
|
<Grid item xs={6}>
|
|
751
778
|
<DateStringInput size="small" label="Date of Birth"
|
|
779
|
+
inputProps={inputProps}
|
|
752
780
|
field={{
|
|
753
781
|
...field,
|
|
754
782
|
isOptional: field.isOptional || field.options?.billingProvider === 'Candid'
|
|
755
|
-
}}
|
|
756
|
-
value={value?.relationshipDetails?.dateOfBirth || ''}
|
|
757
|
-
onChange={dateOfBirth =>
|
|
758
|
-
onChange({
|
|
759
|
-
...value,
|
|
783
|
+
}}
|
|
784
|
+
value={value?.relationshipDetails?.dateOfBirth || ''}
|
|
785
|
+
onChange={dateOfBirth =>
|
|
786
|
+
onChange({
|
|
787
|
+
...value,
|
|
760
788
|
relationshipDetails: { ...value?.relationshipDetails, dateOfBirth }
|
|
761
789
|
}, field.id)
|
|
762
790
|
}
|
|
@@ -894,8 +922,8 @@ export const InsuranceInput = ({ field, onDatabaseSelect, value, onChange, form,
|
|
|
894
922
|
field.id
|
|
895
923
|
)}
|
|
896
924
|
renderInput={(params) => (
|
|
897
|
-
<TextField {...params} InputProps={{ ...params.InputProps, sx: defaultInputProps.sx }}
|
|
898
|
-
size={'small'} label={"State"} required={!field.isOptional}
|
|
925
|
+
<TextField {...params} InputProps={{ ...params.InputProps, sx: (inputProps || defaultInputProps).sx }}
|
|
926
|
+
size={'small'} label={"State"} required={!field.isOptional}
|
|
899
927
|
/>
|
|
900
928
|
)}
|
|
901
929
|
{...props}
|
|
@@ -908,7 +936,7 @@ export const InsuranceInput = ({ field, onDatabaseSelect, value, onChange, form,
|
|
|
908
936
|
}
|
|
909
937
|
|
|
910
938
|
|
|
911
|
-
const StringSelector = ({ options, value, onChange, required, getDisplayValue, ...props } : {
|
|
939
|
+
const StringSelector = ({ options, value, onChange, required, getDisplayValue, inputProps, ...props } : {
|
|
912
940
|
options: string[]
|
|
913
941
|
value: string,
|
|
914
942
|
onChange: (v: string) => void,
|
|
@@ -917,11 +945,12 @@ const StringSelector = ({ options, value, onChange, required, getDisplayValue, .
|
|
|
917
945
|
required?: boolean,
|
|
918
946
|
getDisplayValue?: (v: string) => string,
|
|
919
947
|
disabled?: boolean,
|
|
948
|
+
inputProps?: { sx: SxProps },
|
|
920
949
|
}) => (
|
|
921
950
|
<FormControl fullWidth size={props.size} required={required}>
|
|
922
951
|
<InputLabel>{props.label}</InputLabel>
|
|
923
952
|
<Select {...props} value={value} onChange={e => onChange(e.target.value)} fullWidth
|
|
924
|
-
sx={defaultInputProps.sx}
|
|
953
|
+
sx={(inputProps || defaultInputProps).sx}
|
|
925
954
|
>
|
|
926
955
|
{options.map((o, i) => (
|
|
927
956
|
<MenuItem value={o} key={o || i}>{getDisplayValue?.(o) ?? o}</MenuItem>
|
|
@@ -930,9 +959,354 @@ const StringSelector = ({ options, value, onChange, required, getDisplayValue, .
|
|
|
930
959
|
</FormControl>
|
|
931
960
|
)
|
|
932
961
|
|
|
962
|
+
export const BridgeEligibilityInput = ({ field, value, onChange, responses, enduser, inputProps, enduserId, ...props }: FormInputProps<'Bridge Eligibility'> & {
|
|
963
|
+
inputProps?: { sx: SxProps },
|
|
964
|
+
}) => {
|
|
965
|
+
const session = useResolvedSession()
|
|
966
|
+
const [loading, setLoading] = useState(false)
|
|
967
|
+
const [polling, setPolling] = useState(false)
|
|
968
|
+
const [error, setError] = useState<string>()
|
|
969
|
+
|
|
970
|
+
// single-page form must require button-click to check, but 1-page-at-a-time enduser sessions should auto-check
|
|
971
|
+
const isEnduserSession = session.type === 'enduser'
|
|
972
|
+
const eligibilityType = field.options?.bridgeEligibilityType || 'Soft'
|
|
973
|
+
|
|
974
|
+
// Extract payerId from Insurance question response
|
|
975
|
+
const [payerId, memberId, payerName] = useMemo(() => {
|
|
976
|
+
const insuranceResponse = responses?.find(r => r.answer?.type === 'Insurance' && r.answer?.value?.payerId)
|
|
977
|
+
if (insuranceResponse?.answer?.type === 'Insurance') {
|
|
978
|
+
return [
|
|
979
|
+
insuranceResponse.answer.value?.payerId,
|
|
980
|
+
insuranceResponse.answer.value?.memberId,
|
|
981
|
+
insuranceResponse.answer.value?.payerName,
|
|
982
|
+
]
|
|
983
|
+
}
|
|
984
|
+
// existing payer id is automatically resolved on the backend as default
|
|
985
|
+
return []
|
|
986
|
+
}, [responses])
|
|
987
|
+
|
|
988
|
+
// Extract state from Address question or enduser
|
|
989
|
+
const state = useMemo(() => {
|
|
990
|
+
// Find Address field with state value
|
|
991
|
+
const addressResponse = responses?.find(r =>
|
|
992
|
+
r.answer?.type === 'Address' && r.answer?.value?.state
|
|
993
|
+
)
|
|
994
|
+
if (addressResponse?.answer?.type === 'Address') {
|
|
995
|
+
return addressResponse.answer.value?.state
|
|
996
|
+
}
|
|
997
|
+
// enduser state is automatically resolved on the backend as default
|
|
998
|
+
}, [responses])
|
|
999
|
+
|
|
1000
|
+
// Soft eligibility check function
|
|
1001
|
+
const checkProviderEligibility = useCallback(async () => {
|
|
1002
|
+
const serviceTypeId = field.options?.bridgeServiceTypeId
|
|
1003
|
+
|
|
1004
|
+
if (!serviceTypeId) {
|
|
1005
|
+
setError('Bridge Service Type ID not configured')
|
|
1006
|
+
return
|
|
1007
|
+
}
|
|
1008
|
+
// payerId and state can be automatically resolved on the backend, if already saved on Enduser, so not required here
|
|
1009
|
+
|
|
1010
|
+
setLoading(true)
|
|
1011
|
+
setError(undefined)
|
|
1012
|
+
|
|
1013
|
+
try {
|
|
1014
|
+
const { data } = await session.api.integrations.proxy_read({
|
|
1015
|
+
id: enduserId,
|
|
1016
|
+
integration: BRIDGE_TITLE,
|
|
1017
|
+
type: 'provider-eligibility',
|
|
1018
|
+
query: JSON.stringify({
|
|
1019
|
+
serviceTypeId,
|
|
1020
|
+
payerId,
|
|
1021
|
+
state,
|
|
1022
|
+
}),
|
|
1023
|
+
})
|
|
1024
|
+
|
|
1025
|
+
// Store userIds in shared variable for Appointment Booking to use
|
|
1026
|
+
const userIds = data?.userIds || []
|
|
1027
|
+
setBridgeEligibilityUserIds(userIds)
|
|
1028
|
+
|
|
1029
|
+
// Update the answer with the eligibility result
|
|
1030
|
+
onChange({
|
|
1031
|
+
status: data?.status || 'unknown',
|
|
1032
|
+
userIds,
|
|
1033
|
+
}, field.id)
|
|
1034
|
+
} catch (err: any) {
|
|
1035
|
+
setError(err?.message || 'Failed to check eligibility')
|
|
1036
|
+
console.error('Provider eligibility check failed:', err)
|
|
1037
|
+
} finally {
|
|
1038
|
+
setLoading(false)
|
|
1039
|
+
}
|
|
1040
|
+
}, [session, field, payerId, state, onChange, enduserId])
|
|
1041
|
+
|
|
1042
|
+
// Hard eligibility check function with polling
|
|
1043
|
+
const checkServiceEligibility = useCallback(async () => {
|
|
1044
|
+
const serviceTypeId = field.options?.bridgeServiceTypeId
|
|
1045
|
+
|
|
1046
|
+
if (!serviceTypeId) {
|
|
1047
|
+
setError('Bridge Service Type ID not configured')
|
|
1048
|
+
return
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
setLoading(true)
|
|
1052
|
+
setError(undefined)
|
|
1053
|
+
|
|
1054
|
+
try {
|
|
1055
|
+
// Initiate service eligibility check
|
|
1056
|
+
const { data } = await session.api.integrations.proxy_read({
|
|
1057
|
+
id: enduserId,
|
|
1058
|
+
integration: BRIDGE_TITLE,
|
|
1059
|
+
type: 'service-eligibility',
|
|
1060
|
+
query: JSON.stringify({
|
|
1061
|
+
serviceTypeId,
|
|
1062
|
+
payerId,
|
|
1063
|
+
memberId,
|
|
1064
|
+
state,
|
|
1065
|
+
}),
|
|
1066
|
+
})
|
|
1067
|
+
|
|
1068
|
+
const serviceEligibilityId = data?.id
|
|
1069
|
+
if (!serviceEligibilityId) {
|
|
1070
|
+
throw new Error('No service eligibility ID returned')
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
setLoading(false)
|
|
1074
|
+
setPolling(true)
|
|
1075
|
+
|
|
1076
|
+
// Poll for results
|
|
1077
|
+
const pollForResults = async () => {
|
|
1078
|
+
const maxAttempts = 60 // Poll for up to 60 attempts (2 minutes at 2s intervals)
|
|
1079
|
+
let attempts = 0
|
|
1080
|
+
|
|
1081
|
+
const poll = async (): Promise<void> => {
|
|
1082
|
+
if (attempts >= maxAttempts) {
|
|
1083
|
+
setError('Eligibility check timed out. Please try again.')
|
|
1084
|
+
setPolling(false)
|
|
1085
|
+
return
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
attempts++
|
|
1089
|
+
|
|
1090
|
+
try {
|
|
1091
|
+
const { data: pollData } = await session.api.integrations.proxy_read({
|
|
1092
|
+
id: serviceEligibilityId,
|
|
1093
|
+
integration: BRIDGE_TITLE,
|
|
1094
|
+
type: 'service-eligibility-poll',
|
|
1095
|
+
})
|
|
1096
|
+
|
|
1097
|
+
const status = pollData?.status
|
|
1098
|
+
|
|
1099
|
+
// Check if we're in a terminal state
|
|
1100
|
+
if (status && status !== 'PENDING') {
|
|
1101
|
+
// Store userIds in shared variable for Appointment Booking to use
|
|
1102
|
+
const userIds = pollData?.userIds || []
|
|
1103
|
+
setBridgeEligibilityUserIds(userIds)
|
|
1104
|
+
|
|
1105
|
+
// Update the answer with the eligibility result
|
|
1106
|
+
onChange({
|
|
1107
|
+
status: status || 'unknown',
|
|
1108
|
+
userIds,
|
|
1109
|
+
}, field.id)
|
|
1110
|
+
|
|
1111
|
+
setPolling(false)
|
|
1112
|
+
return
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// Still pending, poll again after delay
|
|
1116
|
+
setTimeout(poll, 2000) // Poll every 2 seconds
|
|
1117
|
+
} catch (err: any) {
|
|
1118
|
+
setError(err?.message || 'Failed to poll eligibility status')
|
|
1119
|
+
console.error('Service eligibility polling failed:', err)
|
|
1120
|
+
setPolling(false)
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
poll()
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
pollForResults()
|
|
1128
|
+
} catch (err: any) {
|
|
1129
|
+
setError(err?.message || 'Failed to check service eligibility')
|
|
1130
|
+
console.error('Service eligibility check failed:', err)
|
|
1131
|
+
setLoading(false)
|
|
1132
|
+
setPolling(false)
|
|
1133
|
+
}
|
|
1134
|
+
}, [session, field, payerId, memberId, state, onChange, enduserId])
|
|
1135
|
+
|
|
1136
|
+
// Auto-check eligibility for enduser sessions
|
|
1137
|
+
const autoCheckRef = useRef(false)
|
|
1138
|
+
useEffect(() => {
|
|
1139
|
+
if (!isEnduserSession) return
|
|
1140
|
+
if (autoCheckRef.current) return
|
|
1141
|
+
autoCheckRef.current = true
|
|
1142
|
+
|
|
1143
|
+
if (eligibilityType === 'Hard') {
|
|
1144
|
+
checkServiceEligibility()
|
|
1145
|
+
} else {
|
|
1146
|
+
checkProviderEligibility()
|
|
1147
|
+
}
|
|
1148
|
+
}, [isEnduserSession, eligibilityType, checkProviderEligibility, checkServiceEligibility])
|
|
1149
|
+
|
|
1150
|
+
const errorComponent = useMemo(() => (
|
|
1151
|
+
<Grid container spacing={2} direction="column" alignItems="center" style={{ padding: '20px 0' }}>
|
|
1152
|
+
<Grid item>
|
|
1153
|
+
<Paper style={{
|
|
1154
|
+
padding: 16,
|
|
1155
|
+
backgroundColor: '#ffebee',
|
|
1156
|
+
border: '2px solid #f44336'
|
|
1157
|
+
}}>
|
|
1158
|
+
<Grid container spacing={2} direction="column" alignItems="center">
|
|
1159
|
+
<Grid item>
|
|
1160
|
+
<Typography variant="h2" style={{ color: '#f44336' }}>⚠️</Typography>
|
|
1161
|
+
</Grid>
|
|
1162
|
+
<Grid item>
|
|
1163
|
+
<Typography variant="h6" align="center" color="error">
|
|
1164
|
+
Unable to Check Eligibility
|
|
1165
|
+
</Typography>
|
|
1166
|
+
</Grid>
|
|
1167
|
+
<Grid item>
|
|
1168
|
+
<Typography variant="body2" align="center" style={{ color: '#d32f2f' }}>
|
|
1169
|
+
{error}
|
|
1170
|
+
</Typography>
|
|
1171
|
+
</Grid>
|
|
1172
|
+
</Grid>
|
|
1173
|
+
</Paper>
|
|
1174
|
+
</Grid>
|
|
1175
|
+
</Grid>
|
|
1176
|
+
), [error])
|
|
1177
|
+
|
|
1178
|
+
const checkingEligibilityComponent = useMemo(() => (
|
|
1179
|
+
<Grid container spacing={2} direction="column" alignItems="center" style={{ padding: '20px 0' }}>
|
|
1180
|
+
<Grid item>
|
|
1181
|
+
<CircularProgress size={40} />
|
|
1182
|
+
</Grid>
|
|
1183
|
+
<Grid item>
|
|
1184
|
+
<Typography variant="body1">
|
|
1185
|
+
{polling ? 'Verifying eligibility with insurance...' : 'Checking eligibility...'}
|
|
1186
|
+
</Typography>
|
|
1187
|
+
</Grid>
|
|
1188
|
+
<Grid item>
|
|
1189
|
+
<Typography variant="body2" color="textSecondary">
|
|
1190
|
+
{polling ? 'This usually takes 15-30 seconds' : 'This may take a few moments'}
|
|
1191
|
+
</Typography>
|
|
1192
|
+
</Grid>
|
|
1193
|
+
</Grid>
|
|
1194
|
+
), [polling])
|
|
1195
|
+
|
|
1196
|
+
const resultsComponent = useMemo(() => {
|
|
1197
|
+
const isEligible = value?.status === 'ELIGIBLE'
|
|
1198
|
+
return (
|
|
1199
|
+
<Grid container spacing={2} direction="column">
|
|
1200
|
+
<Grid item>
|
|
1201
|
+
<Paper style={{
|
|
1202
|
+
padding: 16,
|
|
1203
|
+
backgroundColor: isEligible ? '#e8f5e9' : '#fff3e0',
|
|
1204
|
+
border: `2px solid ${isEligible ? '#4caf50' : '#ff9800'}`
|
|
1205
|
+
}}>
|
|
1206
|
+
<Grid container spacing={2} direction="column" alignItems="center">
|
|
1207
|
+
<Grid item>
|
|
1208
|
+
{isEligible ? (
|
|
1209
|
+
<CheckCircleOutline style={{ fontSize: 48, color: '#4caf50' }} />
|
|
1210
|
+
) : (
|
|
1211
|
+
<Typography variant="h2" style={{ color: '#ff9800' }}>⚠️</Typography>
|
|
1212
|
+
)}
|
|
1213
|
+
</Grid>
|
|
1214
|
+
<Grid item>
|
|
1215
|
+
<Typography variant="h6" align="center">
|
|
1216
|
+
{isEligible
|
|
1217
|
+
? `${payerName || 'Your insurance provider'} is accepted!`
|
|
1218
|
+
: 'Eligibility Status: ' + first_letter_capitalized((value?.status || 'Unknown').toLowerCase())
|
|
1219
|
+
}
|
|
1220
|
+
</Typography>
|
|
1221
|
+
</Grid>
|
|
1222
|
+
</Grid>
|
|
1223
|
+
</Paper>
|
|
1224
|
+
</Grid>
|
|
1225
|
+
</Grid>
|
|
1226
|
+
)
|
|
1227
|
+
}, [value])
|
|
1228
|
+
|
|
1229
|
+
// Loading/polling state for enduser sessions
|
|
1230
|
+
if (isEnduserSession) {
|
|
1231
|
+
if (loading || polling) { return checkingEligibilityComponent }
|
|
1232
|
+
if (error) {
|
|
1233
|
+
return errorComponent
|
|
1234
|
+
}
|
|
1235
|
+
if (value?.status) {
|
|
1236
|
+
return resultsComponent
|
|
1237
|
+
}
|
|
1238
|
+
return errorComponent
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// User/admin interface (non-enduser sessions)
|
|
1242
|
+
return (
|
|
1243
|
+
<Grid container spacing={2} direction="column">
|
|
1244
|
+
<Grid item>
|
|
1245
|
+
<Typography variant="body2" color="textSecondary">
|
|
1246
|
+
Eligibility Type: {eligibilityType}
|
|
1247
|
+
</Typography>
|
|
1248
|
+
<Typography variant="body2" color="textSecondary">
|
|
1249
|
+
Service Type: {field.options?.bridgeServiceTypeId || 'Not configured'}
|
|
1250
|
+
</Typography>
|
|
1251
|
+
{state && <Typography variant="body2" color="textSecondary">State: {state}</Typography>}
|
|
1252
|
+
{payerId && <Typography variant="body2" color="textSecondary">Payer ID: {payerId}</Typography>}
|
|
1253
|
+
{memberId && <Typography variant="body2" color="textSecondary">Member ID: {memberId}</Typography>}
|
|
1254
|
+
</Grid>
|
|
1255
|
+
|
|
1256
|
+
{error && (
|
|
1257
|
+
<Grid item>
|
|
1258
|
+
<Typography variant="body2" color="error">{error}</Typography>
|
|
1259
|
+
</Grid>
|
|
1260
|
+
)}
|
|
1261
|
+
|
|
1262
|
+
{polling && (
|
|
1263
|
+
<Grid item>
|
|
1264
|
+
<Typography variant="body2" color="primary">
|
|
1265
|
+
Polling for results... (this may take 15-30 seconds)
|
|
1266
|
+
</Typography>
|
|
1267
|
+
</Grid>
|
|
1268
|
+
)}
|
|
1269
|
+
|
|
1270
|
+
<Grid item container spacing={2}>
|
|
1271
|
+
<Grid item>
|
|
1272
|
+
<LoadingButton
|
|
1273
|
+
variant="outlined"
|
|
1274
|
+
onClick={checkProviderEligibility}
|
|
1275
|
+
submitText="Check Provider Eligibility (Free)"
|
|
1276
|
+
submittingText="Checking..."
|
|
1277
|
+
submitting={loading && !polling}
|
|
1278
|
+
disabled={!field.options?.bridgeServiceTypeId || loading || polling}
|
|
1279
|
+
/>
|
|
1280
|
+
</Grid>
|
|
1281
|
+
<Grid item>
|
|
1282
|
+
<LoadingButton
|
|
1283
|
+
variant="outlined"
|
|
1284
|
+
onClick={checkServiceEligibility}
|
|
1285
|
+
submitText="Check Service Eligibility (Paid)"
|
|
1286
|
+
submittingText={polling ? "Polling..." : "Initiating..."}
|
|
1287
|
+
submitting={loading || polling}
|
|
1288
|
+
disabled={!field.options?.bridgeServiceTypeId || loading || polling}
|
|
1289
|
+
/>
|
|
1290
|
+
</Grid>
|
|
1291
|
+
</Grid>
|
|
1292
|
+
|
|
1293
|
+
{value && (
|
|
1294
|
+
<Grid item>
|
|
1295
|
+
<Typography variant="caption" color="textSecondary">
|
|
1296
|
+
Current Answer:
|
|
1297
|
+
</Typography>
|
|
1298
|
+
<pre style={{ fontSize: 11, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
|
1299
|
+
{JSON.stringify(value, null, 2)}
|
|
1300
|
+
</pre>
|
|
1301
|
+
</Grid>
|
|
1302
|
+
)}
|
|
1303
|
+
</Grid>
|
|
1304
|
+
)
|
|
1305
|
+
}
|
|
1306
|
+
|
|
933
1307
|
const HourSelector = (props : { value: string, onChange: (v: string) => void }) => (
|
|
934
|
-
<StringSelector {...props}
|
|
935
|
-
options={Array(12).fill('').map((_, i) => (i + 1) <= 9 ? `0${i + 1}` : (i + 1).toString())}
|
|
1308
|
+
<StringSelector {...props}
|
|
1309
|
+
options={Array(12).fill('').map((_, i) => (i + 1) <= 9 ? `0${i + 1}` : (i + 1).toString())}
|
|
936
1310
|
/>
|
|
937
1311
|
)
|
|
938
1312
|
const MinuteSelector = (props : { value: string, onChange: (v: string) => void }) => (
|
|
@@ -3685,6 +4059,16 @@ export const AppointmentBookingInput = ({ formResponseId, field, value, onChange
|
|
|
3685
4059
|
.join(',')
|
|
3686
4060
|
}`
|
|
3687
4061
|
}
|
|
4062
|
+
// Filter to Bridge eligibility userIds if option is enabled
|
|
4063
|
+
if (field.options?.useBridgeEligibilityResult) {
|
|
4064
|
+
const bridgeUserIds = getBridgeEligibilityUserIds()
|
|
4065
|
+
|
|
4066
|
+
if (bridgeUserIds.length === 0) {
|
|
4067
|
+
return <Typography>No eligible users found for booking</Typography>
|
|
4068
|
+
}
|
|
4069
|
+
|
|
4070
|
+
bookingURL += `&userIds=${bridgeUserIds.join(',')}`
|
|
4071
|
+
}
|
|
3688
4072
|
// need to use form?.id for internally-submitted forms because formResponseId isn't generated until initial submission or saved draft
|
|
3689
4073
|
if (field.options?.holdAppointmentMinutes && (formResponseId || field?.id)) {
|
|
3690
4074
|
bookingURL += `&formResponseId=${formResponseId || field?.id}`
|