@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tellescope/react-components",
3
- "version": "1.225.0",
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.225.0",
51
- "@tellescope/sdk": "^1.225.0",
52
- "@tellescope/types-client": "^1.225.0",
53
- "@tellescope/types-models": "^1.225.0",
54
- "@tellescope/types-utilities": "^1.225.0",
55
- "@tellescope/utilities": "^1.225.0",
56
- "@tellescope/validation": "^1.225.0",
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": "764982e316e8cf0dba3151e4d0ecf3741bc39725",
86
+ "gitHead": "4c2611222f8b1bfadce79814889db07fee096ce0",
87
87
  "publishConfig": {
88
88
  "access": "public"
89
89
  }
@@ -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, i) => {
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
 
@@ -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
- const [activeField, setActiveField] = useState(root)
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
  }