From cc96ba8acfaa94e87f58ebee6d1ba565a176bbf3 Mon Sep 17 00:00:00 2001 From: Jeff Sieu Date: Sun, 6 Nov 2022 18:02:27 +0800 Subject: [PATCH] [ui][typeahead] refactor hooks into separate files --- .../forms/CreateQuestionEncounterForm.tsx | 9 +-- .../questions/typeahead/CompanyTypeahead.tsx | 2 +- .../questions/typeahead/RoleTypeahead.tsx | 2 +- .../components/shared/CompaniesTypeahead.tsx | 23 +------- .../components/shared/CountriesTypeahead.tsx | 55 +------------------ .../components/shared/JobTitlesTypeahead.tsx | 19 +------ .../src/utils/shared/useCompanyOptions.ts | 22 ++++++++ .../src/utils/shared/useCountryOptions.ts | 55 +++++++++++++++++++ .../src/utils/shared/useJobTitleOptions.ts | 18 ++++++ 9 files changed, 105 insertions(+), 100 deletions(-) create mode 100644 apps/portal/src/utils/shared/useCompanyOptions.ts create mode 100644 apps/portal/src/utils/shared/useCountryOptions.ts create mode 100644 apps/portal/src/utils/shared/useJobTitleOptions.ts diff --git a/apps/portal/src/components/questions/forms/CreateQuestionEncounterForm.tsx b/apps/portal/src/components/questions/forms/CreateQuestionEncounterForm.tsx index aa91b6ff..f158ede5 100644 --- a/apps/portal/src/components/questions/forms/CreateQuestionEncounterForm.tsx +++ b/apps/portal/src/components/questions/forms/CreateQuestionEncounterForm.tsx @@ -3,11 +3,12 @@ import { useState } from 'react'; import { CheckIcon } from '@heroicons/react/20/solid'; import { Button } from '@tih/ui'; -import { useCompanyOptions } from '~/components/shared/CompaniesTypeahead'; -import { useJobTitleOptions } from '~/components/shared/JobTitlesTypeahead'; import type { Month } from '~/components/shared/MonthYearPicker'; import MonthYearPicker from '~/components/shared/MonthYearPicker'; +import useCompanyOptions from '~/utils/shared/useCompanyOptions'; +import useJobTitleOptions from '~/utils/shared/useJobTitleOptions'; + import CompanyTypeahead from '../typeahead/CompanyTypeahead'; import LocationTypeahead, { useLocationOptions, @@ -49,7 +50,7 @@ export default function CreateQuestionEncounterForm({ const { data: allCompanyOptions } = useCompanyOptions(''); const { data: allLocationOptions } = useLocationOptions(''); - const allRoleOptions = useJobTitleOptions(''); + const allRoleOptions = useJobTitleOptions(''); if (submitted) { return ( @@ -61,7 +62,7 @@ export default function CreateQuestionEncounterForm({ } return ( -
+

I saw this question {step <= 1 ? 'at' : step === 2 ? 'for' : 'on'}

diff --git a/apps/portal/src/components/questions/typeahead/CompanyTypeahead.tsx b/apps/portal/src/components/questions/typeahead/CompanyTypeahead.tsx index bf950a49..a00cd3c7 100644 --- a/apps/portal/src/components/questions/typeahead/CompanyTypeahead.tsx +++ b/apps/portal/src/components/questions/typeahead/CompanyTypeahead.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; -import { useCompanyOptions } from '~/components/shared/CompaniesTypeahead'; +import useCompanyOptions from '~/utils/shared/useCompanyOptions'; import type { ExpandedTypeaheadProps } from './ExpandedTypeahead'; import ExpandedTypeahead from './ExpandedTypeahead'; diff --git a/apps/portal/src/components/questions/typeahead/RoleTypeahead.tsx b/apps/portal/src/components/questions/typeahead/RoleTypeahead.tsx index 829c8ad2..c0bb2a8c 100644 --- a/apps/portal/src/components/questions/typeahead/RoleTypeahead.tsx +++ b/apps/portal/src/components/questions/typeahead/RoleTypeahead.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; -import { useJobTitleOptions } from '~/components/shared/JobTitlesTypeahead'; +import useJobTitleOptions from '~/utils/shared/useJobTitleOptions'; import type { ExpandedTypeaheadProps } from './ExpandedTypeahead'; import ExpandedTypeahead from './ExpandedTypeahead'; diff --git a/apps/portal/src/components/shared/CompaniesTypeahead.tsx b/apps/portal/src/components/shared/CompaniesTypeahead.tsx index f51ba8d7..07d61669 100644 --- a/apps/portal/src/components/shared/CompaniesTypeahead.tsx +++ b/apps/portal/src/components/shared/CompaniesTypeahead.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import type { TypeaheadOption } from '@tih/ui'; import { Typeahead } from '@tih/ui'; -import { trpc } from '~/utils/trpc'; +import useCompanyOptions from '~/utils/shared/useCompanyOptions'; type BaseProps = Pick< ComponentProps, @@ -21,27 +21,6 @@ type Props = BaseProps & value?: TypeaheadOption | null; }>; -export function useCompanyOptions(query: string) { - const companies = trpc.useQuery([ - 'companies.list', - { - name: query, - }, - ]); - - const { data, ...restQuery } = companies; - - return { - data: - data?.map(({ id, name }) => ({ - id, - label: name, - value: id, - })) ?? [], - ...restQuery, - }; -} - export default function CompaniesTypeahead({ onSelect, value, diff --git a/apps/portal/src/components/shared/CountriesTypeahead.tsx b/apps/portal/src/components/shared/CountriesTypeahead.tsx index 2c6b73d0..2f4bb2a0 100644 --- a/apps/portal/src/components/shared/CountriesTypeahead.tsx +++ b/apps/portal/src/components/shared/CountriesTypeahead.tsx @@ -1,10 +1,9 @@ import type { ComponentProps } from 'react'; import { useState } from 'react'; -import type { Country } from '@prisma/client'; import type { TypeaheadOption } from '@tih/ui'; import { Typeahead } from '@tih/ui'; -import { trpc } from '~/utils/trpc'; +import useCountryOptions from '~/utils/shared/useCountryOptions'; type BaseProps = Pick< ComponentProps, @@ -24,58 +23,6 @@ type Props = BaseProps & value?: TypeaheadOption | null; }>; -function stringPositionComparator(a: string, b: string, query: string): number { - const normalizedQueryString = query.trim().toLocaleLowerCase(); - const positionA = a.toLocaleLowerCase().indexOf(normalizedQueryString); - const positionB = b.toLocaleLowerCase().indexOf(normalizedQueryString); - return ( - (positionA === -1 ? 9999 : positionA) - - (positionB === -1 ? 9999 : positionB) - ); -} - -export function useCompareCountry(query: string) { - return (a: Country, b: Country) => { - const normalizedQueryString = query.trim().toLocaleLowerCase(); - if ( - a.code.toLocaleLowerCase() === normalizedQueryString || - b.code.toLocaleLowerCase() === normalizedQueryString - ) { - return stringPositionComparator(a.code, b.code, normalizedQueryString); - } - - return stringPositionComparator(a.name, b.name, normalizedQueryString); - }; -} - -export function useCountryOptions(query: string) { - const countries = trpc.useQuery([ - 'locations.countries.list', - { - name: query, - }, - ]); - - const { data, ...restQuery } = countries; - - const compareCountry = useCompareCountry(query); - - const countryOptions = (data ?? []) - // Client-side sorting by position of query string appearing - // in the country name since we can't do that in Prisma. - .sort(compareCountry) - .map(({ id, name }) => ({ - id, - label: name, - value: id, - })); - - return { - ...restQuery, - data: countryOptions, - }; -} - export default function CountriesTypeahead({ excludedValues, label = 'Country', diff --git a/apps/portal/src/components/shared/JobTitlesTypeahead.tsx b/apps/portal/src/components/shared/JobTitlesTypeahead.tsx index 165d0077..131db3d0 100644 --- a/apps/portal/src/components/shared/JobTitlesTypeahead.tsx +++ b/apps/portal/src/components/shared/JobTitlesTypeahead.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import type { TypeaheadOption } from '@tih/ui'; import { Typeahead } from '@tih/ui'; -import { JobTitleLabels } from './JobTitles'; +import useJobTitleOptions from '~/utils/shared/useJobTitleOptions'; type BaseProps = Pick< ComponentProps, @@ -24,23 +24,6 @@ type Props = BaseProps & value?: TypeaheadOption | null; }>; -const sortedJobTitleOptions = Object.entries(JobTitleLabels) - .map(([slug, { label, ranking }]) => ({ - id: slug, - label, - ranking, - value: slug, - })) - .sort((a, b) => b.ranking - a.ranking); - -export function useJobTitleOptions(query: string) { - const jobTitles = sortedJobTitleOptions.filter(({ label }) => - label.toLocaleLowerCase().includes(query.trim().toLocaleLowerCase()), - ); - - return jobTitles; -} - export default function JobTitlesTypeahead({ excludedValues, label: labelProp = 'Job Title', diff --git a/apps/portal/src/utils/shared/useCompanyOptions.ts b/apps/portal/src/utils/shared/useCompanyOptions.ts new file mode 100644 index 00000000..b9437f8d --- /dev/null +++ b/apps/portal/src/utils/shared/useCompanyOptions.ts @@ -0,0 +1,22 @@ +import { trpc } from '../trpc'; + +export default function useCompanyOptions(query: string) { + const companies = trpc.useQuery([ + 'companies.list', + { + name: query, + }, + ]); + + const { data, ...restQuery } = companies; + + return { + data: + data?.map(({ id, name }) => ({ + id, + label: name, + value: id, + })) ?? [], + ...restQuery, + }; +} diff --git a/apps/portal/src/utils/shared/useCountryOptions.ts b/apps/portal/src/utils/shared/useCountryOptions.ts new file mode 100644 index 00000000..f5f1700c --- /dev/null +++ b/apps/portal/src/utils/shared/useCountryOptions.ts @@ -0,0 +1,55 @@ +import type { Country } from '@prisma/client'; + +import { trpc } from '../trpc'; + +function stringPositionComparator(a: string, b: string, query: string): number { + const normalizedQueryString = query.trim().toLocaleLowerCase(); + const positionA = a.toLocaleLowerCase().indexOf(normalizedQueryString); + const positionB = b.toLocaleLowerCase().indexOf(normalizedQueryString); + return ( + (positionA === -1 ? 9999 : positionA) - + (positionB === -1 ? 9999 : positionB) + ); +} + +export function useCompareCountry(query: string) { + return (a: Country, b: Country) => { + const normalizedQueryString = query.trim().toLocaleLowerCase(); + if ( + a.code.toLocaleLowerCase() === normalizedQueryString || + b.code.toLocaleLowerCase() === normalizedQueryString + ) { + return stringPositionComparator(a.code, b.code, normalizedQueryString); + } + + return stringPositionComparator(a.name, b.name, normalizedQueryString); + }; +} + +export default function useCountryOptions(query: string) { + const countries = trpc.useQuery([ + 'locations.countries.list', + { + name: query, + }, + ]); + + const { data, ...restQuery } = countries; + + const compareCountry = useCompareCountry(query); + + const countryOptions = (data ?? []) + // Client-side sorting by position of query string appearing + // in the country name since we can't do that in Prisma. + .sort(compareCountry) + .map(({ id, name }) => ({ + id, + label: name, + value: id, + })); + + return { + ...restQuery, + data: countryOptions, + }; +} diff --git a/apps/portal/src/utils/shared/useJobTitleOptions.ts b/apps/portal/src/utils/shared/useJobTitleOptions.ts new file mode 100644 index 00000000..8c8bda9b --- /dev/null +++ b/apps/portal/src/utils/shared/useJobTitleOptions.ts @@ -0,0 +1,18 @@ +import { JobTitleLabels } from '~/components/shared/JobTitles'; + +const sortedJobTitleOptions = Object.entries(JobTitleLabels) + .map(([slug, { label, ranking }]) => ({ + id: slug, + label, + ranking, + value: slug, + })) + .sort((a, b) => b.ranking - a.ranking); + +export default function useJobTitleOptions(query: string) { + const jobTitles = sortedJobTitleOptions.filter(({ label }) => + label.toLocaleLowerCase().includes(query.trim().toLocaleLowerCase()), + ); + + return jobTitles; +}