@tellescope/react-components 1.153.0 → 1.154.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 (55) hide show
  1. package/lib/cjs/Forms/forms.d.ts.map +1 -1
  2. package/lib/cjs/Forms/forms.js +53 -44
  3. package/lib/cjs/Forms/forms.js.map +1 -1
  4. package/lib/cjs/Forms/hooks.d.ts +1 -1
  5. package/lib/cjs/Forms/hooks.d.ts.map +1 -1
  6. package/lib/cjs/Forms/hooks.js.map +1 -1
  7. package/lib/cjs/Forms/inputs.d.ts +1 -0
  8. package/lib/cjs/Forms/inputs.d.ts.map +1 -1
  9. package/lib/cjs/Forms/inputs.js +140 -31
  10. package/lib/cjs/Forms/inputs.js.map +1 -1
  11. package/lib/cjs/Forms/types.d.ts +1 -1
  12. package/lib/cjs/Forms/types.d.ts.map +1 -1
  13. package/lib/cjs/inputs_shared.d.ts +7 -1
  14. package/lib/cjs/inputs_shared.d.ts.map +1 -1
  15. package/lib/cjs/inputs_shared.js +13 -1
  16. package/lib/cjs/inputs_shared.js.map +1 -1
  17. package/lib/cjs/state.d.ts +72 -0
  18. package/lib/cjs/state.d.ts.map +1 -1
  19. package/lib/cjs/state.js +35 -3
  20. package/lib/cjs/state.js.map +1 -1
  21. package/lib/esm/Forms/forms.d.ts +3 -3
  22. package/lib/esm/Forms/forms.d.ts.map +1 -1
  23. package/lib/esm/Forms/forms.js +55 -46
  24. package/lib/esm/Forms/forms.js.map +1 -1
  25. package/lib/esm/Forms/hooks.d.ts +2 -1
  26. package/lib/esm/Forms/hooks.d.ts.map +1 -1
  27. package/lib/esm/Forms/hooks.js.map +1 -1
  28. package/lib/esm/Forms/inputs.d.ts +2 -1
  29. package/lib/esm/Forms/inputs.d.ts.map +1 -1
  30. package/lib/esm/Forms/inputs.js +138 -30
  31. package/lib/esm/Forms/inputs.js.map +1 -1
  32. package/lib/esm/Forms/inputs.native.d.ts +0 -1
  33. package/lib/esm/Forms/inputs.native.d.ts.map +1 -1
  34. package/lib/esm/Forms/types.d.ts +1 -1
  35. package/lib/esm/Forms/types.d.ts.map +1 -1
  36. package/lib/esm/controls.d.ts +2 -2
  37. package/lib/esm/inputs.d.ts +1 -1
  38. package/lib/esm/inputs_shared.d.ts +7 -1
  39. package/lib/esm/inputs_shared.d.ts.map +1 -1
  40. package/lib/esm/inputs_shared.js +11 -1
  41. package/lib/esm/inputs_shared.js.map +1 -1
  42. package/lib/esm/state.d.ts +324 -252
  43. package/lib/esm/state.d.ts.map +1 -1
  44. package/lib/esm/state.js +30 -0
  45. package/lib/esm/state.js.map +1 -1
  46. package/lib/esm/theme.native.d.ts +0 -1
  47. package/lib/esm/theme.native.d.ts.map +1 -1
  48. package/lib/tsconfig.tsbuildinfo +1 -1
  49. package/package.json +9 -9
  50. package/src/Forms/forms.tsx +14 -3
  51. package/src/Forms/hooks.tsx +1 -1
  52. package/src/Forms/inputs.tsx +161 -27
  53. package/src/Forms/types.ts +1 -1
  54. package/src/inputs_shared.tsx +24 -2
  55. package/src/state.tsx +43 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tellescope/react-components",
3
- "version": "1.153.0",
3
+ "version": "1.154.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.153.0",
51
- "@tellescope/sdk": "^1.153.0",
52
- "@tellescope/types-client": "^1.153.0",
53
- "@tellescope/types-models": "^1.153.0",
54
- "@tellescope/types-utilities": "^1.153.0",
55
- "@tellescope/utilities": "^1.153.0",
56
- "@tellescope/validation": "^1.153.0",
50
+ "@tellescope/constants": "^1.154.0",
51
+ "@tellescope/sdk": "^1.154.0",
52
+ "@tellescope/types-client": "^1.154.0",
53
+ "@tellescope/types-models": "^1.154.0",
54
+ "@tellescope/types-utilities": "^1.154.0",
55
+ "@tellescope/utilities": "^1.154.0",
56
+ "@tellescope/validation": "^1.154.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",
@@ -80,7 +80,7 @@
80
80
  "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
81
81
  "react-native": "^0.65.0 || ^0.66.0 || ^0.67.0 || ^0.68.0 || ^0.71.0"
82
82
  },
83
- "gitHead": "9433f642d09f85275831104fbdf1dd8ddc8b7a66",
83
+ "gitHead": "d96f27c5787398546a644b88fe6ad75208836bad",
84
84
  "publishConfig": {
85
85
  "access": "public"
86
86
  }
@@ -1,8 +1,8 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from "react"
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
2
2
  import { Button, CircularProgress, Flex, LoadingButton, Modal, Paper, Styled, Typography, form_display_text_for_language, useFileUpload, useFormResponses, useSession } from "../index"
3
3
  import { useListForFormFields, useOrganizationTheme, useTellescopeForm, WithOrganizationTheme, Response, FileResponse } from "./hooks"
4
4
  import { ChangeHandler, FormInputs } from "./types"
5
- import { AddressInput, AllergiesInput, AppointmentBookingInput, DatabaseSelectInput, DateInput, DateStringInput, DropdownInput, EmailInput, EmotiiInput, FileInput, FilesInput, HeightInput, HiddenValueInput, InsuranceInput, LanguageSelect, MedicationsInput, MultipleChoiceInput, NumberInput, PhoneInput, Progress, RankingInput, RatingInput, RedirectInput, RelatedContactsInput, SignatureInput, StringInput, StringLongInput, StripeInput, TableInput, TimeInput, defaultButtonStyles } from "./inputs"
5
+ import { AddressInput, AllergiesInput, AppointmentBookingInput, ConditionsInput, DatabaseSelectInput, DateInput, DateStringInput, DropdownInput, EmailInput, EmotiiInput, FileInput, FilesInput, HeightInput, HiddenValueInput, InsuranceInput, LanguageSelect, MedicationsInput, MultipleChoiceInput, NumberInput, PhoneInput, Progress, RankingInput, RatingInput, RedirectInput, RelatedContactsInput, SignatureInput, StringInput, StringLongInput, StripeInput, TableInput, TimeInput, 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"
@@ -169,6 +169,7 @@ export const QuestionForField = ({
169
169
  const HiddenValue = customInputs?.['Hidden Value'] ?? HiddenValueInput
170
170
  const Emotii = customInputs?.['Emotii'] ?? EmotiiInput
171
171
  const Allergies = customInputs?.['Allergies'] ?? AllergiesInput
172
+ const Conditions = customInputs?.['Conditions'] ?? ConditionsInput
172
173
 
173
174
  const validationMessage = validateField(field)
174
175
 
@@ -246,6 +247,9 @@ export const QuestionForField = ({
246
247
  : field.type === 'Allergies' ? (
247
248
  <Allergies enduser={enduser} enduserId={enduserId} field={field} disabled={value.disabled} value={value.answer.value as any} onChange={onFieldChange as ChangeHandler<any>} form={form} />
248
249
  )
250
+ : field.type === 'Conditions' ? (
251
+ <Conditions enduser={enduser} enduserId={enduserId} field={field} disabled={value.disabled} value={value.answer.value as any} onChange={onFieldChange as ChangeHandler<any>} form={form} />
252
+ )
249
253
  : field.type === 'Height' ? (
250
254
  <Height field={field} disabled={value.disabled} value={value.answer.value as any} onChange={onFieldChange as ChangeHandler<any>} form={form} />
251
255
  )
@@ -435,6 +439,7 @@ export const TellescopeSingleQuestionFlow: typeof TellescopeForm = ({
435
439
  }, [])
436
440
 
437
441
  const [uploading, setUploading] = useState(false)
442
+ const [autosubmitting, setAutoSubmitting] = useState(false)
438
443
 
439
444
  useEffect(() => {
440
445
  // ensure redirect question doesn't trip this alert
@@ -449,6 +454,7 @@ export const TellescopeSingleQuestionFlow: typeof TellescopeForm = ({
449
454
  return onSuccess?.({} as any)
450
455
  }
451
456
 
457
+
452
458
  await submit({
453
459
  onSuccess,
454
460
  ...options,
@@ -461,13 +467,18 @@ export const TellescopeSingleQuestionFlow: typeof TellescopeForm = ({
461
467
  })
462
468
  }, [isPreview, onSuccess, submit, beforeunloadHandler])
463
469
 
470
+ const autoSubmitRef = useRef(false)
464
471
  useEffect(() => {
465
472
  if (!activeField.value.options?.autoSubmit) {
466
473
  return
467
474
  }
475
+ if (autoSubmitRef.current) return
468
476
 
469
477
  if (responses.find(r => r.fieldId === activeField.value.id && field_can_autosubmit(activeField.value) && r.answer.value)) {
478
+ autoSubmitRef.current = true
479
+ setAutoSubmitting(true)
470
480
  handleSubmit()
481
+ .finally(() => setAutoSubmitting(false))
471
482
  }
472
483
  }, [handleSubmit, responses, activeField])
473
484
 
@@ -551,7 +562,7 @@ export const TellescopeSingleQuestionFlow: typeof TellescopeForm = ({
551
562
  setUploading(!!selectedFiles.find(r => !!r.blobs?.length))
552
563
  return handleSubmit({ onFileUploadsDone: () => setUploading(false) })
553
564
  }}
554
- disabled={!!validationMessage || currentValue.field?.options?.disableNext === true}
565
+ disabled={!!validationMessage || currentValue.field?.options?.disableNext === true || autosubmitting}
555
566
  submitText={form_display_text_for_language(form, "Submit")}
556
567
  submittingText={
557
568
  submittingStatus === 'uploading-files'
@@ -668,7 +668,7 @@ export const useTellescopeForm = ({ form, urlLogicValue, customization, carePlan
668
668
  selectedFiles.find(f => f.fieldId === activeField.value.id)
669
669
  )
670
670
 
671
- const handleDatabaseSelect = useCallback((databaseRecords: DatabaseRecord[]) => {
671
+ const handleDatabaseSelect = useCallback((databaseRecords: Pick<DatabaseRecord, "values" | "databaseId">[]) => {
672
672
  try {
673
673
  // no need to update if there's no prepopulation
674
674
  if (!fields?.find(f => f.prepopulateFromDatabase?.databaseId && f.prepopulateFromDatabase?.field)) return
@@ -1,11 +1,11 @@
1
1
  import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react"
2
2
  import axios from "axios"
3
- import { Autocomplete, Box, Button, Checkbox, Divider, FormControl, FormControlLabel, FormLabel, Grid, InputLabel, MenuItem, Radio, RadioGroup, Select, SxProps, TextField, TextFieldProps, Typography } from "@mui/material"
3
+ import { Autocomplete, Box, Button, Checkbox, Chip, Divider, FormControl, FormControlLabel, FormLabel, Grid, InputLabel, MenuItem, Radio, RadioGroup, Select, SxProps, TextField, TextFieldProps, Typography } from "@mui/material"
4
4
  import { FormInputProps } from "./types"
5
5
  import { useDropzone } from "react-dropzone"
6
6
  import { CANVAS_TITLE, EMOTII_TITLE, INSURANCE_RELATIONSHIPS, INSURANCE_RELATIONSHIPS_CANVAS, PRIMARY_HEX, RELATIONSHIP_TYPES, TELLESCOPE_GENDERS } from "@tellescope/constants"
7
7
  import { MM_DD_YYYY_to_YYYY_MM_DD, capture_is_supported, downloadFile, first_letter_capitalized, form_response_value_to_string, getLocalTimezone, getPublicFileURL, mm_dd_yyyy, replace_enduser_template_values, truncate_string, user_display_name } from "@tellescope/utilities"
8
- import { Enduser, EnduserRelationship, FormResponseValue, InsuranceRelationship, MultipleChoiceOptions, TellescopeGender } from "@tellescope/types-models"
8
+ import { DatabaseSelectResponse, Enduser, EnduserRelationship, FormResponseValue, InsuranceRelationship, MultipleChoiceOptions, TellescopeGender } 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';
@@ -1447,6 +1447,7 @@ export const StripeInput = ({ field, value, onChange, setCustomerId }: FormInput
1447
1447
  const [isCheckout, setIsCheckout] = useState(false)
1448
1448
  const [stripePromise, setStripePromise] = useState<ReturnType<typeof loadStripe>>()
1449
1449
  const [, { findById: findProduct }] = useProducts({ dontFetch: true })
1450
+ const [answertext, setAnswertext] = useState('')
1450
1451
 
1451
1452
  const fetchRef = useRef(false)
1452
1453
  useEffect(() => {
@@ -1457,12 +1458,13 @@ export const StripeInput = ({ field, value, onChange, setCustomerId }: FormInput
1457
1458
  fetchRef.current = true
1458
1459
 
1459
1460
  session.api.form_responses.stripe_details({ fieldId: field.id })
1460
- .then(({ clientSecret, publishableKey, stripeAccount, businessName, customerId, isCheckout }) => {
1461
+ .then(({ clientSecret, publishableKey, stripeAccount, businessName, customerId, isCheckout, answerText }) => {
1461
1462
  setIsCheckout(!!isCheckout)
1462
1463
  setClientSecret(clientSecret)
1463
1464
  setStripePromise(loadStripe(publishableKey, { stripeAccount }))
1464
1465
  setBusinessName(businessName)
1465
1466
  setCustomerId(customerId)
1467
+ setAnswertext(answerText || '')
1466
1468
  })
1467
1469
  .catch(console.error)
1468
1470
  }, [session, value, field.id])
@@ -1488,7 +1490,7 @@ export const StripeInput = ({ field, value, onChange, setCustomerId }: FormInput
1488
1490
  <EmbeddedCheckoutProvider stripe={stripePromise}
1489
1491
  options={{
1490
1492
  clientSecret,
1491
- onComplete: () => onChange('Completed checkout', field.id),
1493
+ onComplete: () => onChange(answertext || 'Completed checkout', field.id),
1492
1494
  }}
1493
1495
  >
1494
1496
  <EmbeddedCheckout />
@@ -1498,7 +1500,7 @@ export const StripeInput = ({ field, value, onChange, setCustomerId }: FormInput
1498
1500
  <Elements stripe={stripePromise} options={{
1499
1501
  clientSecret,
1500
1502
  }}>
1501
- <StripeForm businessName={businessName} onSuccess={() => onChange('Saved card details', field.id)}
1503
+ <StripeForm businessName={businessName} onSuccess={() => onChange(answertext || 'Saved card details', field.id)}
1502
1504
  cost={cost}
1503
1505
  field={field}
1504
1506
  />
@@ -1661,7 +1663,7 @@ const choicesForDatabase: {
1661
1663
  } = {}
1662
1664
 
1663
1665
  const LOAD_CHOICES_LIMIT = 500
1664
- const useDatabaseChoices = ({ databaseId='', field } : { databaseId?: string, field: FormField }) => {
1666
+ const useDatabaseChoices = ({ databaseId='', field, otherAnswers } : { databaseId?: string, field: FormField, otherAnswers?: DatabaseSelectResponse[] }) => {
1665
1667
  const session = useResolvedSession()
1666
1668
  const [renderCount, setRenderCount] = useState(0)
1667
1669
 
@@ -1697,13 +1699,20 @@ const useDatabaseChoices = ({ databaseId='', field } : { databaseId?: string, fi
1697
1699
 
1698
1700
  return {
1699
1701
  doneLoading: choicesForDatabase[databaseId]?.done ?? false,
1700
- choices: choicesForDatabase[databaseId]?.records ?? [],
1702
+ choices: [
1703
+ ...choicesForDatabase[databaseId]?.records ?? [],
1704
+ ...(otherAnswers || []).map(v => ({
1705
+ id: v.text,
1706
+ databaseId,
1707
+ values: [{ label: field.options?.databaseLabel || '', type: 'Text', value: v.text }],
1708
+ }) as Pick<DatabaseRecord, 'id' | 'values' | 'databaseId'>)
1709
+ ],
1701
1710
  renderCount,
1702
1711
  }
1703
1712
  }
1704
1713
 
1705
1714
 
1706
- const label_for_database_record = (field: FormField, record?: DatabaseRecord) => {
1715
+ const label_for_database_record = (field: FormField, record?: Pick<DatabaseRecord, 'values'>) => {
1707
1716
  if (!record) return ''
1708
1717
 
1709
1718
  const addedLabels = (
@@ -1722,14 +1731,34 @@ const label_for_database_record = (field: FormField, record?: DatabaseRecord) =>
1722
1731
  )
1723
1732
  }
1724
1733
 
1734
+ const get_other_answers = (_value?: DatabaseSelectResponse[], typing?: string) => {
1735
+ try {
1736
+ const existing = (
1737
+ (_value || [])
1738
+ .filter(v => typeof v === 'string' || v.recordId === v.text)
1739
+ .map(v => typeof v === 'string' ? { databaseId: '', recordId: v, text: v } : v)
1740
+ )
1741
+ if (typing) {
1742
+ existing.push({ text: typing, databaseId: '', recordId: typing })
1743
+ }
1744
+
1745
+ return existing
1746
+ } catch(err) { console.error(err) }
1747
+
1748
+ return []
1749
+ }
1750
+
1725
1751
  export const DatabaseSelectInput = ({ field, value: _value, onChange, onDatabaseSelect, responses }: FormInputProps<'Database Select'> & {
1726
1752
  responses: FormResponseValue[],
1727
1753
  }) => {
1754
+ const [typing, setTyping] = useState('')
1728
1755
  const { choices, doneLoading } = useDatabaseChoices({
1729
1756
  databaseId: field.options?.databaseId,
1730
1757
  field,
1758
+ otherAnswers: get_other_answers(_value, field?.options?.other ? typing : undefined),
1731
1759
  })
1732
1760
 
1761
+ console.log(choices, _value)
1733
1762
  const value = React.useMemo(() => {
1734
1763
  try {
1735
1764
  // if the value is a string (some single answer that was save), make sure we coerce to array
@@ -1797,7 +1826,7 @@ export const DatabaseSelectInput = ({ field, value: _value, onChange, onDatabase
1797
1826
  }, [choices, filterResponse, field.options?.databaseFilter, value])
1798
1827
 
1799
1828
  const filteredChoices = useMemo(() => {
1800
- const filtered: typeof filteredChoicesWithPotentialDuplicates = []
1829
+ const filtered = []
1801
1830
 
1802
1831
  const uniques = new Set<string>([])
1803
1832
  for (const c of filteredChoicesWithPotentialDuplicates) {
@@ -1814,6 +1843,7 @@ export const DatabaseSelectInput = ({ field, value: _value, onChange, onDatabase
1814
1843
  if (!doneLoading) return <LinearProgress />
1815
1844
  return (
1816
1845
  <Autocomplete id={field.id} freeSolo={false}
1846
+ componentsProps={{ popper: { sx: { wordBreak: "break-word" } } } }
1817
1847
  options={filteredChoices} multiple={true}
1818
1848
  getOptionLabel={o => (
1819
1849
  Array.isArray(o) // edge case
@@ -1842,7 +1872,19 @@ export const DatabaseSelectInput = ({ field, value: _value, onChange, onDatabase
1842
1872
  field.id,
1843
1873
  )
1844
1874
  }}
1875
+ inputValue={typing}
1876
+ onInputChange={(e, v) => e && setTyping(v)}
1845
1877
  renderInput={params => <TextField {...params} InputProps={{ ...params.InputProps, sx: defaultInputProps.sx }} />}
1878
+ // use custom Chip to ensure very long entries break properly (whitespace: normal)
1879
+ renderTags={(value, getTagProps) =>
1880
+ value.map((value, index) => (
1881
+ <Chip
1882
+ label={<Typography style={{whiteSpace: 'normal'}}>{Array.isArray(value) ? '' : label_for_database_record(field, value)}</Typography>}
1883
+ {...getTagProps({ index })}
1884
+ sx={{height:"100%", py: 0.5 }}
1885
+ />
1886
+ ))
1887
+ }
1846
1888
  />
1847
1889
  )
1848
1890
  }
@@ -2832,49 +2874,141 @@ export const AllergiesInput = ({ goToNextField, goToPreviousField, field, value,
2832
2874
  const [query, setQuery] = useState('')
2833
2875
  const [results, setResults] = useState<{ code: string, display: string }[]>([])
2834
2876
 
2877
+ // if two allergy questions shown in a row, reset state
2878
+ useEffect(() => {
2879
+ setQuery('')
2880
+ setResults([])
2881
+ }, [field.id])
2882
+
2835
2883
  const fetchRef = useRef(query)
2836
2884
  useEffect(() => {
2837
2885
  if (fetchRef.current === query) return
2838
2886
  fetchRef.current = query
2839
2887
 
2888
+ if (!query) return
2889
+
2840
2890
  const t = setTimeout(() => {
2841
- session.api.integrations
2842
- .proxy_read({
2843
- integration: CANVAS_TITLE,
2844
- type: 'allergies',
2845
- query,
2846
- })
2847
- .then((r : { data: AllergyResult }) => {
2891
+ if (field.options?.dataSource === CANVAS_TITLE) {
2892
+ session.api.integrations
2893
+ .proxy_read({
2894
+ integration: CANVAS_TITLE,
2895
+ type: 'allergies',
2896
+ query,
2897
+ })
2898
+ .then((r : { data: AllergyResult }) => {
2899
+ const deduped: typeof results = []
2900
+ const totalResults = (
2901
+ (r.data.entry || [])
2902
+ .flatMap(v => v?.resource?.code?.coding || [])
2903
+ .filter(v => v.system.includes('fdbhealth'))
2904
+ .map(v => ({ code: v.code, display: v.display, system: v.system }))
2905
+ )
2906
+ for (const v of totalResults) {
2907
+ if (deduped.find(d => d.display === v.display)) { continue }
2908
+
2909
+ deduped.push(v)
2910
+ }
2911
+ setResults(deduped)
2912
+ })
2913
+ } else {
2914
+ session.api.allergy_codes.getSome({ search: { query }})
2915
+ .then(results => {
2916
+ const deduped: typeof results = []
2917
+ for (const v of results) {
2918
+ if (deduped.find(d => d.display === v.display)) { continue }
2919
+
2920
+ deduped.push(v)
2921
+ }
2922
+ setResults(deduped)
2923
+ })
2924
+ }
2925
+
2926
+
2927
+ }, 200)
2928
+
2929
+ return () => { clearTimeout(t) }
2930
+ }, [session, query, field?.options?.dataSource])
2931
+
2932
+ return (
2933
+ <Autocomplete multiple value={value || []} options={results} style={{ marginTop: 5 }}
2934
+ noOptionsText={query.length ? 'No results found' : 'Type to start search'}
2935
+ onChange={(e, v) => {
2936
+ if (!v) { return }
2937
+ onChange(v, field.id)
2938
+ setResults([])
2939
+ }}
2940
+ getOptionLabel={v => first_letter_capitalized(v.display)} filterOptions={o => o}
2941
+ inputValue={query} onInputChange={(e, v) => e && setQuery(v) }
2942
+ renderInput={(params) => (
2943
+ <TextField {...params} InputProps={{ ...params.InputProps, sx: defaultInputProps.sx }}
2944
+ required={!field.isOptional} size="small" label="" placeholder="Search allergies..."
2945
+ />
2946
+ )}
2947
+ renderTags={(value, getTagProps) =>
2948
+ value.map((value, index) => (
2949
+ <Chip
2950
+ label={<Typography style={{whiteSpace: 'normal'}}>{value.display}</Typography>}
2951
+ {...getTagProps({ index })}
2952
+ sx={{height:"100%", py: 0.5 }}
2953
+ />
2954
+ ))
2955
+ }
2956
+ />
2957
+ )
2958
+ }
2959
+
2960
+ export const ConditionsInput = ({ goToNextField, goToPreviousField, field, value, onChange, form, formResponseId, ...props }: FormInputProps<'Conditions'>) => {
2961
+ const session = useResolvedSession()
2962
+ const [query, setQuery] = useState('')
2963
+ const [results, setResults] = useState<{ code: string, display: string }[]>([])
2964
+
2965
+ const fetchRef = useRef(query)
2966
+ useEffect(() => {
2967
+ if (fetchRef.current === query) return
2968
+ fetchRef.current = query
2969
+
2970
+ if (!query) return
2971
+
2972
+ const t = setTimeout(() => {
2973
+ session.api.diagnosis_codes.getSome({ search: { query } })
2974
+ .then(codes => {
2848
2975
  const deduped: typeof results = []
2849
- const totalResults = (
2850
- (r.data.entry || [])
2851
- .flatMap(v => v?.resource?.code?.coding || [])
2852
- .filter(v => v.system.includes('fdbhealth'))
2853
- .map(v => ({ code: v.code, display: v.display, system: v.system }))
2854
- )
2855
- for (const v of totalResults) {
2976
+ for (const v of codes) {
2856
2977
  if (deduped.find(d => d.display === v.display)) { continue }
2857
2978
 
2858
2979
  deduped.push(v)
2859
2980
  }
2860
2981
  setResults(deduped)
2861
2982
  })
2862
- }, 99)
2983
+ }, 200)
2863
2984
 
2864
2985
  return () => { clearTimeout(t) }
2865
2986
  }, [session, query])
2866
2987
 
2867
2988
  return (
2868
- <Autocomplete multiple value={value || []} options={[...results, ...(value || [])]} style={{ marginTop: 5 }}
2989
+ <Autocomplete multiple value={value || []} options={results} style={{ marginTop: 5 }}
2869
2990
  noOptionsText={query.length ? 'No results found' : 'Type to start search'}
2870
- onChange={(e, v) => v && onChange(v, field.id)}
2991
+ onChange={(e, v) => {
2992
+ if (!v) { return }
2993
+ onChange(v, field.id)
2994
+ setResults([])
2995
+ }}
2871
2996
  getOptionLabel={v => first_letter_capitalized(v.display)} filterOptions={o => o}
2872
2997
  inputValue={query} onInputChange={(e, v) => e && setQuery(v) }
2873
2998
  renderInput={(params) => (
2874
2999
  <TextField {...params} InputProps={{ ...params.InputProps, sx: defaultInputProps.sx }}
2875
- required={!field.isOptional} size="small" label="" placeholder="Search allergies..."
3000
+ required={!field.isOptional} size="small" label="" placeholder="Search conditions..."
2876
3001
  />
2877
3002
  )}
3003
+ renderTags={(value, getTagProps) =>
3004
+ value.map((value, index) => (
3005
+ <Chip
3006
+ label={<Typography style={{whiteSpace: 'normal'}}>{value.display}</Typography>}
3007
+ {...getTagProps({ index })}
3008
+ sx={{height:"100%", py: 0.5 }}
3009
+ />
3010
+ ))
3011
+ }
2878
3012
  />
2879
3013
  )
2880
3014
  }
@@ -17,7 +17,7 @@ export interface FormInputProps<K extends keyof AnswerForType> {
17
17
  size?: 'small',
18
18
  label?: string,
19
19
  fullWidth?: boolean,
20
- onDatabaseSelect?: (r: DatabaseRecord[]) => void,
20
+ onDatabaseSelect?: (r: Pick<DatabaseRecord, "values" | "databaseId">[]) => void,
21
21
  responses?: Response[]
22
22
  title?: string,
23
23
  enduserId?: string,
@@ -5,8 +5,8 @@ import { LoadFunction, LoadFunctionArguments } from "@tellescope/sdk"
5
5
  import { ALL_ACCESS, UNSEARCHABLE_FIELDS } from "@tellescope/constants"
6
6
  import { SearchAPIProps, useSearchAPI } from "./hooks"
7
7
  import { TextFieldProps } from "./mui"
8
- import { AppointmentBookingPage, AppointmentLocation, AutomationTrigger, CalendarEventTemplate, CallHoldQueue, ChatRoom, Database, DatabaseRecord, Enduser, EnduserOrder, FaxLog, File, Form, FormGroup, Forum, Journey, ManagedContentRecord, MessageTemplateSnippet, Organization, PrescriptionRoute, SuggestedContact, Template, Ticket, TicketQueue, User, UserNotification } from "@tellescope/types-client"
9
- import { Button, Checkbox, Flex, HoverPaper, LoadingButton, LoadingData, LoadingLinear, ScrollingList, SearchTextInput, Typography, useAppointmentBookingPages, useAppointmentLocations, useAutomationTriggers, useCalendarEventTemplates, useCallHoldQueues, useChatRooms, useDatabaseRecords, useDatabases, useEnduserOrders, useEndusers, useFaxLogs, useFiles, useFormGroups, useForms, useForums, useJourneys, useManagedContentRecords, useMessageTemplateSnippets, useNotifications, useOrganization, useOrganizations, usePrescriptionRoutes, useResolvedSession, useSession, useSuggestedContacts, useTemplates, useTicketQueues, useTickets, useUsers, value_is_loaded } from "."
8
+ import { AllergyCode, AppointmentBookingPage, AppointmentLocation, AutomationTrigger, CalendarEventTemplate, CallHoldQueue, ChatRoom, Database, DatabaseRecord, DiagnosisCode, Enduser, EnduserOrder, FaxLog, File, Form, FormGroup, Forum, Journey, ManagedContentRecord, MessageTemplateSnippet, Organization, PrescriptionRoute, SuggestedContact, Template, Ticket, TicketQueue, User, UserNotification } from "@tellescope/types-client"
9
+ import { Button, Checkbox, Flex, HoverPaper, LoadingButton, LoadingData, LoadingLinear, ScrollingList, SearchTextInput, Typography, useAllergyCodes, useAppointmentBookingPages, useAppointmentLocations, useAutomationTriggers, useCalendarEventTemplates, useCallHoldQueues, useChatRooms, useDatabaseRecords, useDatabases, useDiagnosisCodes, useEnduserOrders, useEndusers, useFaxLogs, useFiles, useFormGroups, useForms, useForums, useJourneys, useManagedContentRecords, useMessageTemplateSnippets, useNotifications, useOrganization, useOrganizations, usePrescriptionRoutes, useResolvedSession, useSession, useSuggestedContacts, useTemplates, useTicketQueues, useTickets, useUsers, value_is_loaded } from "."
10
10
  import { SxProps } from "@mui/material"
11
11
  import { AccessPermissions } from "@tellescope/types-models"
12
12
 
@@ -618,6 +618,28 @@ export const SuggestedContactSearch = (props: Omit<GenericSearchProps<SuggestedC
618
618
  )
619
619
  }
620
620
 
621
+ export const AllergyCodeSearch = (props: Omit<GenericSearchProps<AllergyCode>, 'filterKey'> & { filterKey?: string }) => {
622
+ const session = useSession()
623
+ const [, { addLocalElements }] = useAllergyCodes({ dontFetch: true })
624
+ return (
625
+ <ModelSearchInput filterKey="allergy-code" {...props}
626
+ searchAPI={session.api.allergy_codes.getSome}
627
+ onLoad={addLocalElements}
628
+ />
629
+ )
630
+ }
631
+
632
+ export const DiagnosisCodeSearch = (props: Omit<GenericSearchProps<DiagnosisCode>, 'filterKey'> & { filterKey?: string }) => {
633
+ const session = useSession()
634
+ const [, { addLocalElements }] = useDiagnosisCodes({ dontFetch: true })
635
+ return (
636
+ <ModelSearchInput filterKey="diagnoses-code" {...props}
637
+ searchAPI={session.api.diagnosis_codes.getSome}
638
+ onLoad={addLocalElements}
639
+ />
640
+ )
641
+ }
642
+
621
643
  export const CallHoldQueueSearch = (props: Omit<GenericSearchProps<CallHoldQueue>, 'filterKey'> & { filterKey?: string }) => {
622
644
  const session = useSession()
623
645
  const [, { addLocalElements }] = useCallHoldQueues({ dontFetch: true })
package/src/state.tsx CHANGED
@@ -93,6 +93,8 @@ import {
93
93
  FaxLog,
94
94
  CallHoldQueue,
95
95
  SuggestedContact,
96
+ DiagnosisCode,
97
+ AllergyCode,
96
98
  } from "@tellescope/types-client"
97
99
 
98
100
  import {
@@ -107,7 +109,7 @@ import {
107
109
  } from '@tellescope/sdk'
108
110
  import { value_is_loaded } from './loading'
109
111
  import { matches_organization, object_is_empty, objects_equivalent } from '@tellescope/utilities'
110
- import { ModelName, ReadFilter, SortBy } from '@tellescope/types-models'
112
+ import { Diagnosis, ModelName, ReadFilter, SortBy } from '@tellescope/types-models'
111
113
 
112
114
  const RESET_CACHE_TYPE = "cache/reset" as const
113
115
  export const resetStateAction = createAction(RESET_CACHE_TYPE)
@@ -353,6 +355,8 @@ const portalBrandingsSlice = createSliceForList<PortalBranding, 'portal_branding
353
355
  const messageTemplateSnippetsSlice = createSliceForList<MessageTemplateSnippet, 'message_template_snippets'>('message_template_snippets')
354
356
  const faxLogsSlice = createSliceForList<FaxLog, 'fax_logs'>('fax_logs')
355
357
  const suggestedContactsSlice = createSliceForList<SuggestedContact, 'suggested_contacts'>('suggested_contacts')
358
+ const diagnosisCodesSlice = createSliceForList<DiagnosisCode, 'diagnosis_codes'>('diagnosis_codes')
359
+ const allergyCodesSlice = createSliceForList<AllergyCode, 'allergy_codes'>('allergy_codes')
356
360
 
357
361
  const roleBasedAccessPermissionsSlice = createSliceForList<RoleBasedAccessPermission, 'role_based_access_permissions'>('role_based_access_permissions')
358
362
 
@@ -440,6 +444,8 @@ export const sharedConfig = {
440
444
  fax_logs: faxLogsSlice.reducer,
441
445
  call_hold_queues: callHoldQueuesSlice.reducer,
442
446
  suggested_contacts: suggestedContactsSlice.reducer,
447
+ diagnosis_codes: diagnosisCodesSlice.reducer,
448
+ allergy_codes: allergyCodesSlice.reducer,
443
449
  },
444
450
  }
445
451
 
@@ -1256,6 +1262,42 @@ export const usePortalBrandings = (options={} as HookOptions<PortalBranding>) =>
1256
1262
  },
1257
1263
  )
1258
1264
  }
1265
+ export const useAllergyCodes = (options={} as HookOptions<AllergyCode>) => {
1266
+ const session = useResolvedSession()
1267
+
1268
+ return useListStateHook('allergy_codes', useTypedSelector(s => s.allergy_codes), session, allergyCodesSlice,
1269
+ {
1270
+ loadQuery: session.api.allergy_codes.getSome,
1271
+ findOne: session.api.allergy_codes.getOne,
1272
+ findByIds: session.api.allergy_codes.getByIds,
1273
+ addOne: session.api.allergy_codes.createOne,
1274
+ addSome: session.api.allergy_codes.createSome,
1275
+ deleteOne: session.api.allergy_codes.deleteOne,
1276
+ updateOne: session.api.allergy_codes.updateOne,
1277
+ },
1278
+ {
1279
+ ...options,
1280
+ },
1281
+ )
1282
+ }
1283
+ export const useDiagnosisCodes = (options={} as HookOptions<DiagnosisCode>) => {
1284
+ const session = useResolvedSession()
1285
+
1286
+ return useListStateHook('diagnosis_codes', useTypedSelector(s => s.diagnosis_codes), session, diagnosisCodesSlice,
1287
+ {
1288
+ loadQuery: session.api.diagnosis_codes.getSome,
1289
+ findOne: session.api.diagnosis_codes.getOne,
1290
+ findByIds: session.api.diagnosis_codes.getByIds,
1291
+ addOne: session.api.diagnosis_codes.createOne,
1292
+ addSome: session.api.diagnosis_codes.createSome,
1293
+ deleteOne: session.api.diagnosis_codes.deleteOne,
1294
+ updateOne: session.api.diagnosis_codes.updateOne,
1295
+ },
1296
+ {
1297
+ ...options,
1298
+ },
1299
+ )
1300
+ }
1259
1301
  export const useEnduserProblems = (options={} as HookOptions<EnduserProblem>) => {
1260
1302
  const session = useResolvedSession()
1261
1303