@tellescope/react-components 1.237.6 → 1.239.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.
Files changed (59) hide show
  1. package/lib/cjs/Forms/forms.d.ts.map +1 -1
  2. package/lib/cjs/Forms/forms.js +29 -27
  3. package/lib/cjs/Forms/forms.js.map +1 -1
  4. package/lib/cjs/Forms/forms.v2.d.ts.map +1 -1
  5. package/lib/cjs/Forms/forms.v2.js +29 -27
  6. package/lib/cjs/Forms/forms.v2.js.map +1 -1
  7. package/lib/cjs/Forms/hooks.d.ts +25 -1
  8. package/lib/cjs/Forms/hooks.d.ts.map +1 -1
  9. package/lib/cjs/Forms/hooks.js +40 -2
  10. package/lib/cjs/Forms/hooks.js.map +1 -1
  11. package/lib/cjs/Forms/inputs.d.ts +6 -2
  12. package/lib/cjs/Forms/inputs.d.ts.map +1 -1
  13. package/lib/cjs/Forms/inputs.js +138 -23
  14. package/lib/cjs/Forms/inputs.js.map +1 -1
  15. package/lib/cjs/Forms/inputs.v2.d.ts +2 -1
  16. package/lib/cjs/Forms/inputs.v2.d.ts.map +1 -1
  17. package/lib/cjs/Forms/inputs.v2.js +26 -9
  18. package/lib/cjs/Forms/inputs.v2.js.map +1 -1
  19. package/lib/cjs/Forms/localization.d.ts.map +1 -1
  20. package/lib/cjs/Forms/localization.js +7 -0
  21. package/lib/cjs/Forms/localization.js.map +1 -1
  22. package/lib/cjs/inputs_shared.d.ts +6 -2
  23. package/lib/cjs/inputs_shared.d.ts.map +1 -1
  24. package/lib/cjs/inputs_shared.js +4 -3
  25. package/lib/cjs/inputs_shared.js.map +1 -1
  26. package/lib/esm/Forms/forms.d.ts.map +1 -1
  27. package/lib/esm/Forms/forms.js +30 -28
  28. package/lib/esm/Forms/forms.js.map +1 -1
  29. package/lib/esm/Forms/forms.v2.d.ts.map +1 -1
  30. package/lib/esm/Forms/forms.v2.js +30 -28
  31. package/lib/esm/Forms/forms.v2.js.map +1 -1
  32. package/lib/esm/Forms/hooks.d.ts +25 -1
  33. package/lib/esm/Forms/hooks.d.ts.map +1 -1
  34. package/lib/esm/Forms/hooks.js +38 -1
  35. package/lib/esm/Forms/hooks.js.map +1 -1
  36. package/lib/esm/Forms/inputs.d.ts +6 -2
  37. package/lib/esm/Forms/inputs.d.ts.map +1 -1
  38. package/lib/esm/Forms/inputs.js +136 -22
  39. package/lib/esm/Forms/inputs.js.map +1 -1
  40. package/lib/esm/Forms/inputs.v2.d.ts +2 -1
  41. package/lib/esm/Forms/inputs.v2.d.ts.map +1 -1
  42. package/lib/esm/Forms/inputs.v2.js +25 -9
  43. package/lib/esm/Forms/inputs.v2.js.map +1 -1
  44. package/lib/esm/Forms/localization.d.ts.map +1 -1
  45. package/lib/esm/Forms/localization.js +7 -0
  46. package/lib/esm/Forms/localization.js.map +1 -1
  47. package/lib/esm/inputs_shared.d.ts +6 -2
  48. package/lib/esm/inputs_shared.d.ts.map +1 -1
  49. package/lib/esm/inputs_shared.js +4 -3
  50. package/lib/esm/inputs_shared.js.map +1 -1
  51. package/lib/tsconfig.tsbuildinfo +1 -1
  52. package/package.json +9 -9
  53. package/src/Forms/forms.tsx +9 -2
  54. package/src/Forms/forms.v2.tsx +9 -2
  55. package/src/Forms/hooks.tsx +57 -2
  56. package/src/Forms/inputs.tsx +246 -17
  57. package/src/Forms/inputs.v2.tsx +29 -11
  58. package/src/Forms/localization.ts +8 -0
  59. package/src/inputs_shared.tsx +6 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tellescope/react-components",
3
- "version": "1.237.6",
3
+ "version": "1.239.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.9.0",
48
48
  "@stripe/react-stripe-js": "2.9.0",
49
49
  "@stripe/stripe-js": "1.52.1",
50
- "@tellescope/constants": "1.237.6",
51
- "@tellescope/sdk": "1.237.6",
52
- "@tellescope/types-client": "1.237.6",
53
- "@tellescope/types-models": "1.237.6",
54
- "@tellescope/types-utilities": "1.237.6",
55
- "@tellescope/utilities": "1.237.6",
56
- "@tellescope/validation": "1.237.6",
50
+ "@tellescope/constants": "1.239.0",
51
+ "@tellescope/sdk": "1.239.0",
52
+ "@tellescope/types-client": "1.239.0",
53
+ "@tellescope/types-models": "1.239.0",
54
+ "@tellescope/types-utilities": "1.239.0",
55
+ "@tellescope/utilities": "1.239.0",
56
+ "@tellescope/validation": "1.239.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": "27e63e1df7af12bc985c2717d9bbb15bcc684e51",
86
+ "gitHead": "9a05a273aa2c4d1bb38d4252857586fd7732f678",
87
87
  "publishConfig": {
88
88
  "access": "public"
89
89
  }
@@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
2
2
  import { Button, CircularProgress, FileBlob, FileUploadHandler, Flex, LinearProgress, LoadingButton, Modal, Paper, Styled, Typography, form_display_text_for_language, useFileUpload, useFormResponses, useSession } from "../index"
3
3
  import { useListForFormFields, useOrganizationTheme, useTellescopeForm, WithOrganizationTheme, Response, FileResponse, NextFieldLogicOptions } from "./hooks"
4
4
  import { ChangeHandler, FormInputs } from "./types"
5
- import { AddToDatabaseProps, AddressInput, AllergiesInput, AppointmentBookingInput, BelugaPatientPreferenceInput, BridgeEligibilityInput, ChargeebeeInput, ConditionsInput, DatabaseSelectInput, DateInput, DateStringInput, DropdownInput, EmailInput, EmotiiInput, FileInput, FilesInput, HeightInput, HiddenValueInput, InsuranceInput, LanguageSelect, MedicationsInput, MultipleChoiceInput, NumberInput, PhoneInput, Progress, RankingInput, RatingInput, RedirectInput, RelatedContactsInput, RichTextInput, SignatureInput, StringInput, StringLongInput, StripeInput, TableInput, TimeInput, TimezoneInput, defaultButtonStyles } from "./inputs"
5
+ import { AddToDatabaseProps, AddressInput, AllergiesInput, AppointmentBookingInput, BelugaPatientPreferenceInput, BridgeEligibilityInput, ChargeebeeInput, ConditionsInput, DatabaseSelectInput, DateInput, DateStringInput, DropdownInput, EmailInput, EmotiiInput, FileInput, FilesInput, HeightInput, HiddenValueInput, InsuranceInput, LanguageSelect, MedicationsInput, MultipleChoiceInput, NumberInput, PharmacySearchInput, PhoneInput, Progress, RankingInput, RatingInput, RedirectInput, RelatedContactsInput, RichTextInput, SignatureInput, StringInput, StringLongInput, StripeInput, TableInput, TimeInput, TimezoneInput, defaultButtonStyles } from "./inputs"
6
6
  import { PRIMARY_HEX } from "@tellescope/constants"
7
7
  import { FormResponse, FormField, Form, Enduser } from "@tellescope/types-client"
8
8
  import { FormResponseAnswerFileValue, OrganizationTheme } from "@tellescope/types-models"
@@ -192,6 +192,7 @@ export const QuestionForField = ({
192
192
  const Conditions = customInputs?.['Conditions'] ?? ConditionsInput
193
193
  const RichText = customInputs?.['Rich Text'] ?? RichTextInput
194
194
  const BelugaPatientPreference = customInputs?.['Beluga Patient Preference'] ?? BelugaPatientPreferenceInput
195
+ const PharmacySearch = customInputs?.['Pharmacy Search'] ?? PharmacySearchInput
195
196
 
196
197
  const validationMessage = validateField(field)
197
198
 
@@ -344,7 +345,7 @@ export const QuestionForField = ({
344
345
  <Signature enduser={enduser} field={field} value={value.answer.value as any} onChange={onFieldChange as ChangeHandler<'signature'>} form={form}/>
345
346
  )
346
347
  : field.type === 'multiple_choice' ? (
347
- <MultipleChoice field={field} value={value.answer.value as any} onChange={onFieldChange as ChangeHandler<'multiple_choice'>} form={form}/>
348
+ <MultipleChoice field={field} value={value.answer.value as any} onChange={onFieldChange as ChangeHandler<'multiple_choice'>} form={form} responses={responses} enduser={enduser}/>
348
349
  )
349
350
  : field.type === 'Dropdown' ? (
350
351
  <Dropdown field={field} value={value.answer.value as any} onChange={onFieldChange as ChangeHandler<'Dropdown'>} form={form}/>
@@ -378,6 +379,12 @@ export const QuestionForField = ({
378
379
  onChange={onFieldChange as ChangeHandler<'Bridge Eligibility'>}
379
380
  />
380
381
  )
382
+ : field.type === 'Pharmacy Search' ? (
383
+ <PharmacySearch field={field} value={value.answer.value as any} form={form}
384
+ enduser={enduser} responses={responses}
385
+ onChange={onFieldChange as ChangeHandler<'Pharmacy Search'>}
386
+ />
387
+ )
381
388
  : field.type === 'rating' ? (
382
389
  <Rating field={field} value={value.answer.value as any} onChange={onFieldChange as ChangeHandler<'rating'>} form={form}/>
383
390
  )
@@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
2
2
  import { Button, CircularProgress, FileBlob, FileUploadHandler, Flex, LinearProgress, LoadingButton, Modal, Paper, Styled, Typography, WithTheme, form_display_text_for_language, useFileUpload, useFormResponses, useSession } from "../index"
3
3
  import { useListForFormFields, useOrganizationTheme, useTellescopeForm, WithOrganizationTheme, Response, FileResponse, NextFieldLogicOptions } from "./hooks"
4
4
  import { ChangeHandler, FormInputs } from "./types"
5
- import { AddToDatabaseProps, AddressInput, AllergiesInput, AppointmentBookingInput, BelugaPatientPreferenceInput, BridgeEligibilityInput, ChargeebeeInput, ConditionsInput, DatabaseSelectInput, DateInput, DateStringInput, DropdownInput, EmailInput, EmotiiInput, FileInput, FilesInput, HeightInput, HiddenValueInput, InsuranceInput, LanguageSelect, MedicationsInput, MultipleChoiceInput, NumberInput, PhoneInput, Progress, RankingInput, RatingInput, RedirectInput, RelatedContactsInput, RichTextInput, SignatureInput, StringInput, StringLongInput, StripeInput, TableInput, TimeInput, TimezoneInput, defaultButtonStyles } from "./inputs.v2"
5
+ import { AddToDatabaseProps, AddressInput, AllergiesInput, AppointmentBookingInput, BelugaPatientPreferenceInput, BridgeEligibilityInput, ChargeebeeInput, ConditionsInput, DatabaseSelectInput, DateInput, DateStringInput, DropdownInput, EmailInput, EmotiiInput, FileInput, FilesInput, HeightInput, HiddenValueInput, InsuranceInput, LanguageSelect, MedicationsInput, MultipleChoiceInput, NumberInput, PharmacySearchInput, PhoneInput, Progress, RankingInput, RatingInput, RedirectInput, RelatedContactsInput, RichTextInput, SignatureInput, StringInput, StringLongInput, StripeInput, TableInput, TimeInput, TimezoneInput, defaultButtonStyles } from "./inputs.v2"
6
6
  import { PRIMARY_HEX } from "@tellescope/constants"
7
7
  import { FormResponse, FormField, Form, Enduser } from "@tellescope/types-client"
8
8
  import { FormResponseAnswerFileValue, OrganizationTheme } from "@tellescope/types-models"
@@ -185,6 +185,7 @@ export const QuestionForField = ({
185
185
  const Conditions = customInputs?.['Conditions'] ?? ConditionsInput
186
186
  const RichText = customInputs?.['Rich Text'] ?? RichTextInput
187
187
  const BelugaPatientPreference = customInputs?.['Beluga Patient Preference'] ?? BelugaPatientPreferenceInput
188
+ const PharmacySearch = customInputs?.['Pharmacy Search'] ?? PharmacySearchInput
188
189
 
189
190
  const validationMessage = validateField(field)
190
191
 
@@ -338,7 +339,7 @@ export const QuestionForField = ({
338
339
  <Signature enduser={enduser} field={field} value={value.answer.value as any} onChange={onFieldChange as ChangeHandler<'signature'>} form={form}/>
339
340
  )
340
341
  : field.type === 'multiple_choice' ? (
341
- <MultipleChoice field={field} value={value.answer.value as any} onChange={onFieldChange as ChangeHandler<'multiple_choice'>} form={form}/>
342
+ <MultipleChoice field={field} value={value.answer.value as any} onChange={onFieldChange as ChangeHandler<'multiple_choice'>} form={form} responses={responses} enduser={enduser}/>
342
343
  )
343
344
  : field.type === 'Dropdown' ? (
344
345
  <Dropdown field={field} value={value.answer.value as any} onChange={onFieldChange as ChangeHandler<'Dropdown'>} form={form}/>
@@ -372,6 +373,12 @@ export const QuestionForField = ({
372
373
  onChange={onFieldChange as ChangeHandler<'Bridge Eligibility'>}
373
374
  />
374
375
  )
376
+ : field.type === 'Pharmacy Search' ? (
377
+ <PharmacySearch field={field} value={value.answer.value as any} form={form}
378
+ enduser={enduser} responses={responses}
379
+ onChange={onFieldChange as ChangeHandler<'Pharmacy Search'>}
380
+ />
381
+ )
375
382
  : field.type === 'rating' ? (
376
383
  <Rating field={field} value={value.answer.value as any} onChange={onFieldChange as ChangeHandler<'rating'>} form={form}/>
377
384
  )
@@ -4,7 +4,7 @@ import { ChangeHandler, FormFieldNode } from "./types"
4
4
  import { DatabaseRecord, Enduser, Form, FormField, FormResponse } from "@tellescope/types-client"
5
5
  import { phoneValidator } from "@tellescope/validation"
6
6
  import { FileBlob, Indexable } from "@tellescope/types-utilities"
7
- import { CompoundFilter, EnduserRelationship, FormCustomization, FormResponseAnswerAddress, FormResponseAnswerFileValue, FormResponseValue, FormResponseValueAnswer, OrganizationTheme, PreviousFormCompoundLogic, PreviousFormFieldType, Timezone, TIMEZONES } from "@tellescope/types-models"
7
+ import { CompoundFilter, EnduserRelationship, FormCustomization, FormFieldOptionDetails, FormResponseAnswerAddress, FormResponseAnswerFileValue, FormResponseValue, FormResponseValueAnswer, OrganizationTheme, PreviousFormCompoundLogic, PreviousFormFieldType, Timezone, TIMEZONES } from "@tellescope/types-models"
8
8
  import { WithTheme, contact_is_valid, useAddGTMTag, useFileUpload, useFormFields, useFormResponses, useResolvedSession, value_is_loaded } from "../index"
9
9
  import ReactGA from "react-ga4";
10
10
 
@@ -679,7 +679,7 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
679
679
  type: f.type,
680
680
  value: (
681
681
  existing_response_if_compatible(existingResponses, f) ?? (
682
- (f.type === 'Insurance' || f.type === 'Address' || f.type === 'file' || f.type === 'signature' || f.type === 'multiple_choice' || f.type === 'Dropdown' || f.type === 'Table Input' || f.type === 'Database Select' || f.type === 'Medications')
682
+ (f.type === 'Insurance' || f.type === 'Address' || f.type === 'file' || f.type === 'signature' || f.type === 'multiple_choice' || f.type === 'Dropdown' || f.type === 'Table Input' || f.type === 'Database Select' || f.type === 'Medications' || f.type === 'Pharmacy Search')
683
683
  ? undefined
684
684
  : f.type === 'Question Group'
685
685
  ? f.options?.subFields
@@ -1686,3 +1686,58 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
1686
1686
  isAutoAdvancing,
1687
1687
  }
1688
1688
  }
1689
+
1690
+ /**
1691
+ * Hook for conditional visibility of multiple choice options.
1692
+ * Computes visible choices based on showCondition and provides an onChange wrapper
1693
+ * that auto-filters hidden choices from selections (event-driven, not useEffect).
1694
+ */
1695
+ export const useConditionalChoices = ({
1696
+ choices,
1697
+ optionDetails,
1698
+ responses,
1699
+ enduser,
1700
+ form,
1701
+ onChange,
1702
+ fieldId,
1703
+ otherString,
1704
+ }: {
1705
+ choices?: string[]
1706
+ optionDetails?: FormFieldOptionDetails[]
1707
+ responses?: Response[]
1708
+ enduser?: Partial<Enduser>
1709
+ form?: Form
1710
+ onChange: (value: string[], fieldId: string) => void
1711
+ fieldId: string
1712
+ otherString: string
1713
+ }) => {
1714
+ // Compute visible choices based on showCondition
1715
+ const visibleChoices = useMemo(() => {
1716
+ if (!choices) return []
1717
+ return choices.filter((choice, index) => {
1718
+ const optionDetail = optionDetails?.find(d => d.option === choice) ?? optionDetails?.[index]
1719
+ if (!optionDetail?.showCondition || object_is_empty(optionDetail.showCondition)) {
1720
+ return true
1721
+ }
1722
+ return responses_satisfy_conditions(responses || [], optionDetail.showCondition, {
1723
+ dateOfBirth: enduser?.dateOfBirth,
1724
+ gender: enduser?.gender,
1725
+ state: enduser?.state,
1726
+ form,
1727
+ activeResponses: responses,
1728
+ })
1729
+ })
1730
+ }, [choices, optionDetails, responses, enduser, form])
1731
+
1732
+ // Wrap onChange to auto-filter hidden choices (event-driven)
1733
+ const handleChange = useCallback((newValue: string[], fieldId: string) => {
1734
+ // Filter out any hidden choices from the new value
1735
+ // Allow through: visible choices, the "other" string, and values not in the choices array (custom "other" text)
1736
+ const filteredValue = newValue.filter(v =>
1737
+ visibleChoices.includes(v) || v === otherString || !choices?.includes(v)
1738
+ )
1739
+ onChange(filteredValue, fieldId)
1740
+ }, [visibleChoices, otherString, choices, onChange])
1741
+
1742
+ return { visibleChoices, handleChange }
1743
+ }
@@ -5,7 +5,7 @@ import { FormInputProps } from "./types"
5
5
  import { useDropzone } from "react-dropzone"
6
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
- import { Address, DatabaseSelectResponse, Enduser, EnduserRelationship, FormResponseValue, InsuranceRelationship, MedicationResponse, MultipleChoiceOptions, FormFieldOptionDetails, TellescopeGender, TIMEZONES_USA } from "@tellescope/types-models"
8
+ import { Address, DatabaseSelectResponse, Enduser, EnduserRelationship, 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';
@@ -25,6 +25,7 @@ import { Elements, PaymentElement, useStripe, useElements, EmbeddedCheckout, Emb
25
25
  import { loadStripe } from '@stripe/stripe-js';
26
26
  import { CheckCircleOutline, Delete, Edit, ExpandMore } from "@mui/icons-material"
27
27
  import { WYSIWYG } from "./wysiwyg"
28
+ import { useConditionalChoices, Response } from "./hooks"
28
29
 
29
30
  // Bridge Eligibility - shared variable for storing most recent eligibility userIds
30
31
  const bridgeEligibilityResult = {
@@ -1417,6 +1418,207 @@ export const BridgeEligibilityInput = ({ field, value, onChange, responses, endu
1417
1418
  )
1418
1419
  }
1419
1420
 
1421
+ export const PharmacySearchInput = ({
1422
+ field,
1423
+ value: rawValue,
1424
+ onChange,
1425
+ responses,
1426
+ enduser,
1427
+ form,
1428
+ ...props
1429
+ }: Omit<FormInputProps<'string'>, 'value' | 'onChange'> & {
1430
+ value: Pharmacy | undefined,
1431
+ onChange: (v: Pharmacy | undefined, fieldId: string) => void
1432
+ }) => {
1433
+ const value = rawValue as Pharmacy | undefined
1434
+ const session = useResolvedSession()
1435
+
1436
+ // Get initial ZIP code from responses or enduser data
1437
+ const getInitialZipCode = () => {
1438
+ // Check Address field responses first
1439
+ const addressResponse = responses?.find(r =>
1440
+ r.answer?.type === 'Address' && (r.answer?.value as { zipCode?: string })?.zipCode
1441
+ )
1442
+ if (addressResponse?.answer?.type === 'Address') {
1443
+ const addressValue = addressResponse.answer.value as { zipCode?: string }
1444
+ if (addressValue?.zipCode) {
1445
+ return addressValue.zipCode
1446
+ }
1447
+ }
1448
+
1449
+ // Fall back to enduser.zipCode
1450
+ return enduser?.zipCode || ''
1451
+ }
1452
+
1453
+ const [zipCode, setZipCode] = useState(getInitialZipCode())
1454
+ const [loading, setLoading] = useState(false)
1455
+ const [error, setError] = useState<string>()
1456
+ const [pharmacies, setPharmacies] = useState<Pharmacy[]>([])
1457
+ const [hasSearched, setHasSearched] = useState(false)
1458
+
1459
+ const searchPharmacies = useCallback(async () => {
1460
+ if (!zipCode || zipCode.length !== 5) {
1461
+ setError(form_display_text_for_language(form, 'Please enter a valid 5-digit ZIP code'))
1462
+ return
1463
+ }
1464
+
1465
+ setLoading(true)
1466
+ setError(undefined)
1467
+ setHasSearched(true)
1468
+
1469
+ try {
1470
+ const { data } = await session.api.integrations.proxy_read({
1471
+ integration: 'ScriptSure',
1472
+ type: 'pharmacy-search',
1473
+ query: JSON.stringify({ zipCode }),
1474
+ })
1475
+
1476
+ setPharmacies(data || [])
1477
+
1478
+ if (!data?.length) {
1479
+ setError(form_display_text_for_language(form, 'No pharmacies found for this ZIP code'))
1480
+ }
1481
+ } catch (err: any) {
1482
+ setError(err?.message || form_display_text_for_language(form, 'Failed to search pharmacies'))
1483
+ setPharmacies([])
1484
+ } finally {
1485
+ setLoading(false)
1486
+ }
1487
+ }, [session, zipCode, form])
1488
+
1489
+ const handleSelectPharmacy = (pharmacy: Pharmacy) => {
1490
+ onChange({
1491
+ npi: pharmacy.npi,
1492
+ ncpdpId: pharmacy.ncpdpId,
1493
+ businessName: pharmacy.businessName,
1494
+ primaryTelephone: pharmacy.primaryTelephone || '',
1495
+ addressLine1: pharmacy.addressLine1,
1496
+ city: pharmacy.city,
1497
+ stateProvince: pharmacy.stateProvince,
1498
+ postalCode: pharmacy.postalCode,
1499
+ }, field.id)
1500
+ }
1501
+
1502
+ const formatPharmacyAddress = (p: Pharmacy) => {
1503
+ const parts = [p.addressLine1, p.city, p.stateProvince, p.postalCode].filter(Boolean)
1504
+ return parts.join(', ')
1505
+ }
1506
+
1507
+ return (
1508
+ <Grid container direction="column" spacing={2}>
1509
+ {/* ZIP Code Input */}
1510
+ <Grid item>
1511
+ <Grid container spacing={2} alignItems="flex-end">
1512
+ <Grid item xs={8} sm={6}>
1513
+ <TextField
1514
+ fullWidth
1515
+ size="small"
1516
+ label={form_display_text_for_language(form, "ZIP Code")}
1517
+ value={zipCode}
1518
+ onChange={(e) => setZipCode(e.target.value.replace(/\D/g, '').slice(0, 5))}
1519
+ InputProps={defaultInputProps}
1520
+ placeholder="12345"
1521
+ required={!field.isOptional}
1522
+ />
1523
+ </Grid>
1524
+ <Grid item xs={4} sm={6}>
1525
+ <LoadingButton
1526
+ variant="contained"
1527
+ onClick={searchPharmacies}
1528
+ submitText={form_display_text_for_language(form, "Search")}
1529
+ submittingText={form_display_text_for_language(form, "Searching...")}
1530
+ submitting={loading}
1531
+ disabled={zipCode.length !== 5 || loading}
1532
+ style={{ width: '100%', marginTop: 0 }}
1533
+ />
1534
+ </Grid>
1535
+ </Grid>
1536
+ </Grid>
1537
+
1538
+ {/* Error Message */}
1539
+ {error && (
1540
+ <Grid item>
1541
+ <Typography color="error" sx={{ fontSize: 14 }}>
1542
+ {error}
1543
+ </Typography>
1544
+ </Grid>
1545
+ )}
1546
+
1547
+ {/* Selected Pharmacy Display */}
1548
+ {value && (
1549
+ <Grid item>
1550
+ <Paper elevation={2} sx={{ p: 2, backgroundColor: '#e8f5e9' }}>
1551
+ <Grid container alignItems="center" justifyContent="space-between">
1552
+ <Grid item xs>
1553
+ <Typography variant="subtitle1" fontWeight="bold">
1554
+ <CheckCircleOutline sx={{ color: 'success.main', mr: 1, verticalAlign: 'middle' }} />
1555
+ {value.businessName}
1556
+ </Typography>
1557
+ <Typography variant="body2" color="text.secondary">
1558
+ {formatPharmacyAddress(value)}
1559
+ </Typography>
1560
+ {value.primaryTelephone && (
1561
+ <Typography variant="body2" color="text.secondary">
1562
+ {value.primaryTelephone}
1563
+ </Typography>
1564
+ )}
1565
+ </Grid>
1566
+ <Grid item>
1567
+ <MuiIconButton onClick={() => onChange(undefined as any, field.id)} size="small">
1568
+ <CancelIcon />
1569
+ </MuiIconButton>
1570
+ </Grid>
1571
+ </Grid>
1572
+ </Paper>
1573
+ </Grid>
1574
+ )}
1575
+
1576
+ {/* Search Results */}
1577
+ {!value && hasSearched && pharmacies.length > 0 && (
1578
+ <Grid item>
1579
+ <Typography variant="subtitle2" sx={{ mb: 1 }}>
1580
+ {pharmacies.length} {form_display_text_for_language(form, pharmacies.length === 1 ? 'pharmacy found' : 'pharmacies found')}
1581
+ </Typography>
1582
+ <Box sx={{ maxHeight: 300, overflow: 'auto' }}>
1583
+ {pharmacies.map((pharmacy, index) => (
1584
+ <Paper
1585
+ key={`${pharmacy.ncpdpId}-${index}`}
1586
+ elevation={1}
1587
+ sx={{
1588
+ p: 1.5,
1589
+ mb: 1,
1590
+ cursor: 'pointer',
1591
+ '&:hover': { backgroundColor: '#f5f5f5' },
1592
+ }}
1593
+ onClick={() => handleSelectPharmacy(pharmacy)}
1594
+ >
1595
+ <Typography variant="subtitle2" fontWeight="medium">
1596
+ {pharmacy.businessName}
1597
+ </Typography>
1598
+ <Typography variant="body2" color="text.secondary">
1599
+ {formatPharmacyAddress(pharmacy)}
1600
+ </Typography>
1601
+ {pharmacy.primaryTelephone && (
1602
+ <Typography variant="caption" color="text.secondary">
1603
+ {pharmacy.primaryTelephone}
1604
+ </Typography>
1605
+ )}
1606
+ </Paper>
1607
+ ))}
1608
+ </Box>
1609
+ </Grid>
1610
+ )}
1611
+
1612
+ {/* Loading State */}
1613
+ {loading && (
1614
+ <Grid item>
1615
+ <LinearProgress />
1616
+ </Grid>
1617
+ )}
1618
+ </Grid>
1619
+ )
1620
+ }
1621
+
1420
1622
  const HourSelector = (props : { value: string, onChange: (v: string) => void }) => (
1421
1623
  <StringSelector {...props}
1422
1624
  options={Array(12).fill('').map((_, i) => (i + 1) <= 9 ? `0${i + 1}` : (i + 1).toString())}
@@ -1998,7 +2200,7 @@ const multipleChoiceItemSx: SxProps = {
1998
2200
  },
1999
2201
  }
2000
2202
 
2001
- export const MultipleChoiceInput = ({ field, form, value: _value, onChange }: FormInputProps<'multiple_choice'>) => {
2203
+ export const MultipleChoiceInput = ({ field, form, value: _value, onChange, responses, enduser }: FormInputProps<'multiple_choice'>) => {
2002
2204
  const value = typeof _value === 'string' ? [_value] : _value // if loading existingResponses, allows them to be a string
2003
2205
  const { choices, radio, other, optionDetails } = field.options as MultipleChoiceOptions
2004
2206
  const [expandedDescriptions, setExpandedDescriptions] = useState<Record<number, boolean>>({})
@@ -2007,6 +2209,18 @@ export const MultipleChoiceInput = ({ field, form, value: _value, onChange }: Fo
2007
2209
  const enteringOtherStringRef = React.useRef('') // if typing otherString as prefix of a checkbox value, don't auto-select
2008
2210
  const otherString = value?.find(v => v === enteringOtherStringRef.current || !(choices ?? [])?.find(c => c === v)) ?? ''
2009
2211
 
2212
+ // Conditional visibility for choices
2213
+ const { visibleChoices, handleChange } = useConditionalChoices({
2214
+ choices,
2215
+ optionDetails,
2216
+ responses: responses as Response[] | undefined,
2217
+ enduser,
2218
+ form,
2219
+ onChange,
2220
+ fieldId: field.id,
2221
+ otherString,
2222
+ })
2223
+
2010
2224
  const getDescriptionForChoice = useCallback((choice: string) => {
2011
2225
  return optionDetails?.find(detail => detail.option === choice)?.description
2012
2226
  }, [optionDetails])
@@ -2031,7 +2245,7 @@ export const MultipleChoiceInput = ({ field, form, value: _value, onChange }: Fo
2031
2245
  defaultValue="female"
2032
2246
  name={`radio-group-${field.id}`}
2033
2247
  >
2034
- {(choices ?? []).map((c, i) => {
2248
+ {visibleChoices.map((c, i) => {
2035
2249
  const description = getDescriptionForChoice(c)
2036
2250
  const hasDescription = !!description
2037
2251
  const isExpanded = expandedDescriptions[i]
@@ -2043,7 +2257,7 @@ export const MultipleChoiceInput = ({ field, form, value: _value, onChange }: Fo
2043
2257
  sx={{ ...multipleChoiceItemSx, flex: 1, marginLeft: '0px' }}
2044
2258
  checked={!!value?.includes(c) && c !== otherString}
2045
2259
  control={
2046
- <Radio onClick={() => onChange(value?.includes(c) ? [] : [c], field.id)} />
2260
+ <Radio onClick={() => handleChange(value?.includes(c) ? [] : [c], field.id)} />
2047
2261
  }
2048
2262
  label={
2049
2263
  <Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}>
@@ -2083,7 +2297,7 @@ export const MultipleChoiceInput = ({ field, form, value: _value, onChange }: Fo
2083
2297
  </RadioGroup>
2084
2298
  </FormControl>
2085
2299
  ) : (
2086
- (choices ?? []).map((c, i) => {
2300
+ visibleChoices.map((c, i) => {
2087
2301
  const description = getDescriptionForChoice(c)
2088
2302
  const hasDescription = !!description
2089
2303
  const isExpanded = expandedDescriptions[i]
@@ -2104,7 +2318,7 @@ export const MultipleChoiceInput = ({ field, form, value: _value, onChange }: Fo
2104
2318
  if ((e.target as HTMLElement).closest('.expand-button')) {
2105
2319
  return
2106
2320
  }
2107
- onChange(
2321
+ handleChange(
2108
2322
  (
2109
2323
  value?.includes(c)
2110
2324
  ? (
@@ -2174,9 +2388,9 @@ export const MultipleChoiceInput = ({ field, form, value: _value, onChange }: Fo
2174
2388
  // onClick={() => !otherChecked && handleOtherChecked()} // allow click to enable when disabled
2175
2389
  onChange={e => {
2176
2390
  enteringOtherStringRef.current = e.target.value
2177
- onChange(
2391
+ handleChange(
2178
2392
  (
2179
- radio
2393
+ radio
2180
2394
  ? (
2181
2395
  e.target.value.trim()
2182
2396
  ? [e.target.value]
@@ -2186,7 +2400,7 @@ export const MultipleChoiceInput = ({ field, form, value: _value, onChange }: Fo
2186
2400
  e.target.value.trim()
2187
2401
  // remove existing other string (if exists) and append new one
2188
2402
  ? [...(value ?? []).filter(v => v !== otherString), e.target.value]
2189
- : value?.filter(v => v !== otherString)
2403
+ : (value ?? []).filter(v => v !== otherString)
2190
2404
  )
2191
2405
  ),
2192
2406
  field.id,
@@ -2885,6 +3099,7 @@ export const DatabaseSelectInput = ({ AddToDatabase, field, value: _value, onCha
2885
3099
  }) => {
2886
3100
  const [typing, setTyping] = useState('')
2887
3101
  const [open, setOpen] = useState(false)
3102
+ const selectedRecordsCache = useRef<Map<string, DatabaseRecord>>(new Map())
2888
3103
  const { addChoice, choices, doneLoading, isSearching, minSearchChars } = useDatabaseChoices({
2889
3104
  databaseId: field.options?.databaseId,
2890
3105
  field,
@@ -2894,15 +3109,29 @@ export const DatabaseSelectInput = ({ AddToDatabase, field, value: _value, onCha
2894
3109
 
2895
3110
  const value = React.useMemo(() => {
2896
3111
  try {
2897
- // if the value is a string (some single answer that was save), make sure we coerce to array
3112
+ // if the value is a string (some single answer that was saved), make sure we coerce to array
2898
3113
  const __value = typeof _value === 'string' ? [_value] : _value
2899
- return (
2900
- (__value?.map(v =>
2901
- choices.find(c =>
2902
- c.id === v.recordId || (typeof v === 'string' && label_for_database_record(field, c) === v)
2903
- )
2904
- )?.filter(v => v!) ?? []) as DatabaseRecord[]
2905
- )
3114
+ const result: DatabaseRecord[] = []
3115
+
3116
+ for (const v of (__value ?? [])) {
3117
+ const recordId = typeof v === 'string' ? v : v.recordId
3118
+
3119
+ // First try to find in current choices
3120
+ const found = choices.find(c =>
3121
+ c.id === recordId || (typeof v === 'string' && label_for_database_record(field, c) === v)
3122
+ )
3123
+
3124
+ if (found) {
3125
+ // Update cache with found record
3126
+ selectedRecordsCache.current.set(found.id, found as DatabaseRecord)
3127
+ result.push(found as DatabaseRecord)
3128
+ } else if (recordId && selectedRecordsCache.current.has(recordId)) {
3129
+ // Use cached record if not in current choices (e.g., during search)
3130
+ result.push(selectedRecordsCache.current.get(recordId)!)
3131
+ }
3132
+ }
3133
+
3134
+ return result
2906
3135
  } catch(err) {
2907
3136
  console.error('Error resolving database answers for _value', err)
2908
3137
  return []