@tellescope/react-components 1.225.0 → 1.227.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 +50 -7
- package/lib/cjs/Forms/forms.js.map +1 -1
- package/lib/cjs/Forms/hooks.d.ts +3 -1
- package/lib/cjs/Forms/hooks.d.ts.map +1 -1
- package/lib/cjs/Forms/hooks.js +87 -26
- package/lib/cjs/Forms/hooks.js.map +1 -1
- package/lib/cjs/Forms/inputs.d.ts +1 -1
- package/lib/cjs/Forms/inputs.d.ts.map +1 -1
- package/lib/cjs/Forms/inputs.js +189 -28
- package/lib/cjs/Forms/inputs.js.map +1 -1
- package/lib/esm/Forms/forms.d.ts.map +1 -1
- package/lib/esm/Forms/forms.js +50 -7
- package/lib/esm/Forms/forms.js.map +1 -1
- package/lib/esm/Forms/hooks.d.ts +3 -1
- package/lib/esm/Forms/hooks.d.ts.map +1 -1
- package/lib/esm/Forms/hooks.js +87 -26
- package/lib/esm/Forms/hooks.js.map +1 -1
- package/lib/esm/Forms/inputs.d.ts +1 -1
- package/lib/esm/Forms/inputs.d.ts.map +1 -1
- package/lib/esm/Forms/inputs.js +192 -31
- package/lib/esm/Forms/inputs.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/src/Forms/forms.tsx +77 -8
- package/src/Forms/hooks.tsx +77 -4
- package/src/Forms/inputs.tsx +356 -51
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tellescope/react-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.227.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./lib/cjs/index.js",
|
|
6
6
|
"module": "./lib/esm/index.js",
|
|
@@ -47,13 +47,13 @@
|
|
|
47
47
|
"@reduxjs/toolkit": "^1.6.2",
|
|
48
48
|
"@stripe/react-stripe-js": "^2.9.0",
|
|
49
49
|
"@stripe/stripe-js": "^1.52.1",
|
|
50
|
-
"@tellescope/constants": "^1.
|
|
51
|
-
"@tellescope/sdk": "^1.
|
|
52
|
-
"@tellescope/types-client": "^1.
|
|
53
|
-
"@tellescope/types-models": "^1.
|
|
54
|
-
"@tellescope/types-utilities": "^1.
|
|
55
|
-
"@tellescope/utilities": "^1.
|
|
56
|
-
"@tellescope/validation": "^1.
|
|
50
|
+
"@tellescope/constants": "^1.227.0",
|
|
51
|
+
"@tellescope/sdk": "^1.227.0",
|
|
52
|
+
"@tellescope/types-client": "^1.227.0",
|
|
53
|
+
"@tellescope/types-models": "^1.227.0",
|
|
54
|
+
"@tellescope/types-utilities": "^1.227.0",
|
|
55
|
+
"@tellescope/utilities": "^1.227.0",
|
|
56
|
+
"@tellescope/validation": "^1.227.0",
|
|
57
57
|
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
|
58
58
|
"@typescript-eslint/parser": "^4.33.0",
|
|
59
59
|
"css-to-react-native": "^3.0.0",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
84
84
|
"react-native": "^0.65.0 || ^0.66.0 || ^0.67.0 || ^0.68.0 || ^0.71.0"
|
|
85
85
|
},
|
|
86
|
-
"gitHead": "
|
|
86
|
+
"gitHead": "4c2611222f8b1bfadce79814889db07fee096ce0",
|
|
87
87
|
"publishConfig": {
|
|
88
88
|
"access": "public"
|
|
89
89
|
}
|
package/src/Forms/forms.tsx
CHANGED
|
@@ -455,15 +455,16 @@ export const QuestionForField = ({
|
|
|
455
455
|
|
|
456
456
|
export const TellescopeSingleQuestionFlow: typeof TellescopeForm = ({
|
|
457
457
|
form,
|
|
458
|
-
activeField,
|
|
458
|
+
activeField,
|
|
459
459
|
currentFileValue,
|
|
460
|
-
customInputs,
|
|
461
|
-
currentValue,
|
|
460
|
+
customInputs,
|
|
461
|
+
currentValue,
|
|
462
462
|
submitErrorMessage,
|
|
463
463
|
onAddFile,
|
|
464
|
-
onFieldChange,
|
|
464
|
+
onFieldChange,
|
|
465
465
|
goToNextField,
|
|
466
466
|
goToPreviousField,
|
|
467
|
+
isAutoAdvancing,
|
|
467
468
|
isNextDisabled,
|
|
468
469
|
isPreviousDisabled,
|
|
469
470
|
submit,
|
|
@@ -585,6 +586,19 @@ export const TellescopeSingleQuestionFlow: typeof TellescopeForm = ({
|
|
|
585
586
|
}, [form?.realTimeScoring, form?.scoring, responses])
|
|
586
587
|
|
|
587
588
|
if (!(currentValue && currentFileValue)) return <></>
|
|
589
|
+
|
|
590
|
+
// Show loading state while auto-advancing to target question
|
|
591
|
+
if (isAutoAdvancing) {
|
|
592
|
+
return (
|
|
593
|
+
<Flex column alignItems="center" style={{ minHeight: 200, justifyContent: 'center' }}>
|
|
594
|
+
<CircularProgress size={40} />
|
|
595
|
+
<Typography style={{ marginTop: 16, textAlign: 'center' }}>
|
|
596
|
+
Picking up where you left off...
|
|
597
|
+
</Typography>
|
|
598
|
+
</Flex>
|
|
599
|
+
)
|
|
600
|
+
}
|
|
601
|
+
|
|
588
602
|
return (
|
|
589
603
|
submitted
|
|
590
604
|
? <ThanksMessage htmlThanksMessage={htmlThanksMessage} thanksMessage={thanksMessage}
|
|
@@ -1104,16 +1118,71 @@ export const TellescopeSinglePageForm: React.JSXElementConstructor<TellescopeFor
|
|
|
1104
1118
|
}
|
|
1105
1119
|
}
|
|
1106
1120
|
|
|
1121
|
+
// Calculate current score if real-time scoring is enabled
|
|
1122
|
+
const currentScores = useMemo(() => {
|
|
1123
|
+
if (!props.form?.realTimeScoring || !props.form.scoring?.length) return null
|
|
1124
|
+
|
|
1125
|
+
return calculate_form_scoring({
|
|
1126
|
+
response: { responses },
|
|
1127
|
+
form: { scoring: props.form.scoring }
|
|
1128
|
+
})
|
|
1129
|
+
}, [props.form?.realTimeScoring, props.form?.scoring, responses])
|
|
1130
|
+
|
|
1107
1131
|
return (
|
|
1108
1132
|
<Flex flex={1} column>
|
|
1109
|
-
{submitted
|
|
1110
|
-
? <ThanksMessage htmlThanksMessage={htmlThanksMessage} thanksMessage={thanksMessage}
|
|
1111
|
-
showRestartAtEnd={props?.customization?.showRestartAtEnd}
|
|
1133
|
+
{submitted
|
|
1134
|
+
? <ThanksMessage htmlThanksMessage={htmlThanksMessage} thanksMessage={thanksMessage}
|
|
1135
|
+
showRestartAtEnd={props?.customization?.showRestartAtEnd}
|
|
1112
1136
|
/>
|
|
1113
1137
|
: (
|
|
1114
1138
|
<>
|
|
1139
|
+
{/* Real-time scoring display - pinned to top */}
|
|
1140
|
+
{currentScores && currentScores.length > 0 && (
|
|
1141
|
+
<Flex style={{
|
|
1142
|
+
position: 'sticky',
|
|
1143
|
+
top: 0,
|
|
1144
|
+
zIndex: 1000,
|
|
1145
|
+
backgroundColor: 'white',
|
|
1146
|
+
borderBottom: '1px solid #e0e0e0',
|
|
1147
|
+
padding: '12px 0',
|
|
1148
|
+
marginBottom: '16px',
|
|
1149
|
+
width: '100%',
|
|
1150
|
+
justifyContent: 'center'
|
|
1151
|
+
}}>
|
|
1152
|
+
{currentScores.map((score, index) => (
|
|
1153
|
+
<Flex key={index} style={{
|
|
1154
|
+
padding: '10px 14px',
|
|
1155
|
+
backgroundColor: '#f8f9fa',
|
|
1156
|
+
borderRadius: 8,
|
|
1157
|
+
border: `1px solid ${PRIMARY_HEX}20`,
|
|
1158
|
+
marginRight: index < currentScores.length - 1 ? 12 : 0,
|
|
1159
|
+
minWidth: 120,
|
|
1160
|
+
flexDirection: 'column',
|
|
1161
|
+
alignItems: 'center'
|
|
1162
|
+
}}>
|
|
1163
|
+
<Typography style={{
|
|
1164
|
+
fontSize: 12,
|
|
1165
|
+
fontWeight: 'medium',
|
|
1166
|
+
textAlign: 'center',
|
|
1167
|
+
lineHeight: 1.2,
|
|
1168
|
+
marginBottom: 4
|
|
1169
|
+
}}>
|
|
1170
|
+
{score.title}
|
|
1171
|
+
</Typography>
|
|
1172
|
+
<Typography style={{
|
|
1173
|
+
fontWeight: 'bold',
|
|
1174
|
+
color: PRIMARY_HEX,
|
|
1175
|
+
fontSize: 18
|
|
1176
|
+
}}>
|
|
1177
|
+
{score.value}
|
|
1178
|
+
</Typography>
|
|
1179
|
+
</Flex>
|
|
1180
|
+
))}
|
|
1181
|
+
</Flex>
|
|
1182
|
+
)}
|
|
1183
|
+
|
|
1115
1184
|
<Flex flex={1} justifyContent={"center"} column style={{ marginBottom: 15 }}>
|
|
1116
|
-
{list.map((activeField
|
|
1185
|
+
{list.map((activeField) => {
|
|
1117
1186
|
const value = responses.find(r => r.fieldId === activeField.id)!
|
|
1118
1187
|
const file = selectedFiles.find(r => r.fieldId === activeField.id)!
|
|
1119
1188
|
|
package/src/Forms/hooks.tsx
CHANGED
|
@@ -353,6 +353,7 @@ interface UseTellescopeFormOptions {
|
|
|
353
353
|
isInternalNote?: boolean,
|
|
354
354
|
formTitle?: string,
|
|
355
355
|
customization?: FormCustomization,
|
|
356
|
+
startingFieldId?: string,
|
|
356
357
|
ga4measurementId?: string,
|
|
357
358
|
submitRedirectURL?: string,
|
|
358
359
|
rootResponseId?: string,
|
|
@@ -515,7 +516,7 @@ const shouldCallout = (field: FormField | undefined, value: FormResponseValueAns
|
|
|
515
516
|
|
|
516
517
|
export type Response = FormResponseValue & { touched: boolean, includeInSubmit: boolean, field: FormField }
|
|
517
518
|
export type FileResponse = { fieldId: string, fieldTitle: string, blobs?: FileBlob[] }
|
|
518
|
-
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 }: UseTellescopeFormOptions) => {
|
|
519
|
+
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) => {
|
|
519
520
|
const { amPm, hoursAmPm, minutes } = get_time_values(new Date())
|
|
520
521
|
|
|
521
522
|
const root = useTreeForFormFields(fields)
|
|
@@ -531,13 +532,19 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
531
532
|
const [, { updateElement: updateFormResponse, updateLocalElement: updateLocalFormResponse }] = useFormResponses({ dontFetch: true })
|
|
532
533
|
|
|
533
534
|
const [customerId, setCustomerId] = useState<string>()
|
|
534
|
-
|
|
535
|
+
|
|
536
|
+
const [activeField, setActiveField] = useState(root)
|
|
535
537
|
const [submittingStatus, setSubmittingStatus] = useState<SubmitStatus>(undefined)
|
|
536
538
|
const [submitErrorMessage, setSubmitErrorMessage] = useState('')
|
|
537
539
|
const [currentPageIndex, setCurrentPageIndex] = useState(0)
|
|
538
|
-
const [uploadingFiles, setUploadingFiles] = useState<{ fieldId: string }[]>([])
|
|
540
|
+
const [uploadingFiles, setUploadingFiles] = useState<{ fieldId: string }[]>([])
|
|
539
541
|
const prevFieldStackRef = useRef<typeof root[]>([])
|
|
540
542
|
|
|
543
|
+
// Auto-advance state for form continuation
|
|
544
|
+
const [isAutoAdvancing, setIsAutoAdvancing] = useState(false)
|
|
545
|
+
const autoAdvanceCompletedRef = useRef(false)
|
|
546
|
+
const autoAdvanceStartTimeRef = useRef<number | null>(null)
|
|
547
|
+
|
|
541
548
|
const [repeats, setRepeats] = useState({} as Record<string, string | number>)
|
|
542
549
|
|
|
543
550
|
const gaEventRef = useRef({} as Record<string, boolean>)
|
|
@@ -677,6 +684,8 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
677
684
|
setResponses(initializeFields())
|
|
678
685
|
}, [formId, initializeFields])
|
|
679
686
|
|
|
687
|
+
|
|
688
|
+
|
|
680
689
|
// placeholders for initial files, reset when fields prop changes, since questions are now different (e.g. different form selected)
|
|
681
690
|
const fileInitRef = useRef('')
|
|
682
691
|
const initializeFiles = useCallback(() => (
|
|
@@ -704,7 +713,7 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
704
713
|
)
|
|
705
714
|
|
|
706
715
|
const logicOptions: NextFieldLogicOptions = {
|
|
707
|
-
urlLogicValue,
|
|
716
|
+
urlLogicValue,
|
|
708
717
|
activeResponses: responses.filter(r => r.includeInSubmit),
|
|
709
718
|
dateOfBirth: enduser?.dateOfBirth,
|
|
710
719
|
gender: enduser?.gender,
|
|
@@ -712,6 +721,56 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
712
721
|
form,
|
|
713
722
|
}
|
|
714
723
|
|
|
724
|
+
// Auto-advance to target field when startingFieldId is provided
|
|
725
|
+
useEffect(() => {
|
|
726
|
+
if (!startingFieldId || responses.length === 0 || autoAdvanceCompletedRef.current) return
|
|
727
|
+
|
|
728
|
+
// Start timing on first run
|
|
729
|
+
if (autoAdvanceStartTimeRef.current === null) {
|
|
730
|
+
autoAdvanceStartTimeRef.current = Date.now()
|
|
731
|
+
setIsAutoAdvancing(true)
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const finishAutoAdvance = () => {
|
|
735
|
+
const elapsed = Date.now() - (autoAdvanceStartTimeRef.current || 0)
|
|
736
|
+
const remainingTime = Math.max(0, 1000 - elapsed) // Ensure at least 1 second
|
|
737
|
+
|
|
738
|
+
setTimeout(() => {
|
|
739
|
+
setIsAutoAdvancing(false)
|
|
740
|
+
autoAdvanceCompletedRef.current = true
|
|
741
|
+
}, remainingTime)
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Check if we're already at the target
|
|
745
|
+
if (activeField.value.id === startingFieldId) {
|
|
746
|
+
finishAutoAdvance()
|
|
747
|
+
return
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Find current response
|
|
751
|
+
const currentResponse = responses.find(r => r.fieldId === activeField.value.id)
|
|
752
|
+
|
|
753
|
+
// If no response or no answer, we've reached the first unanswered question
|
|
754
|
+
if (!currentResponse || !currentResponse.answer?.value) {
|
|
755
|
+
finishAutoAdvance()
|
|
756
|
+
return
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// Auto-advance to next field using existing logic
|
|
760
|
+
if (!prevFieldStackRef.current.find(v => v.value.id === activeField?.value.id)) {
|
|
761
|
+
prevFieldStackRef.current.push(activeField)
|
|
762
|
+
setCurrentPageIndex(i => i + 1)
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
const nextNode = getNextField(activeField, currentResponse, responses, logicOptions)
|
|
766
|
+
if (nextNode) {
|
|
767
|
+
setActiveField(nextNode)
|
|
768
|
+
// The useEffect will run again with the new activeField
|
|
769
|
+
} else {
|
|
770
|
+
finishAutoAdvance()
|
|
771
|
+
}
|
|
772
|
+
}, [startingFieldId, responses.length, activeField, logicOptions])
|
|
773
|
+
|
|
715
774
|
const handleDatabaseSelect = useCallback((databaseRecords: Pick<DatabaseRecord, "values" | "databaseId">[]) => {
|
|
716
775
|
try {
|
|
717
776
|
// no need to update if there's no prepopulation
|
|
@@ -944,6 +1003,19 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
944
1003
|
if (typeof value.answer.value?.inches !== 'number' || isNaN(value.answer.value?.inches)) {
|
|
945
1004
|
return "Inches must be provided (enter 0 for no inches)"
|
|
946
1005
|
}
|
|
1006
|
+
|
|
1007
|
+
// Convert height to total inches for min/max validation
|
|
1008
|
+
const totalInches = ((value.answer.value?.feet || 0) * 12) + (value.answer.value?.inches || 0)
|
|
1009
|
+
if (field.options?.min !== undefined && field.options.min !== -Infinity && totalInches < field.options.min) {
|
|
1010
|
+
const minFeet = Math.floor(field.options.min / 12)
|
|
1011
|
+
const minInches = field.options.min % 12
|
|
1012
|
+
return `Height must be at least ${minFeet}' ${minInches}"`
|
|
1013
|
+
}
|
|
1014
|
+
if (field.options?.max !== undefined && field.options.max !== Infinity && totalInches > field.options.max) {
|
|
1015
|
+
const maxFeet = Math.floor(field.options.max / 12)
|
|
1016
|
+
const maxInches = field.options.max % 12
|
|
1017
|
+
return `Height must be no more than ${maxFeet}' ${maxInches}"`
|
|
1018
|
+
}
|
|
947
1019
|
}
|
|
948
1020
|
|
|
949
1021
|
if (value.answer.type === 'Related Contacts') {
|
|
@@ -1544,5 +1616,6 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1544
1616
|
logicOptions,
|
|
1545
1617
|
uploadingFiles, setUploadingFiles,
|
|
1546
1618
|
handleFileUpload,
|
|
1619
|
+
isAutoAdvancing,
|
|
1547
1620
|
}
|
|
1548
1621
|
}
|