diff --git a/apps/portal/src/components/global/CompaniesTypeahead.tsx b/apps/portal/src/components/global/CompaniesTypeahead.tsx new file mode 100644 index 00000000..b25f9cd8 --- /dev/null +++ b/apps/portal/src/components/global/CompaniesTypeahead.tsx @@ -0,0 +1,40 @@ +import { useState } from 'react'; +import type { TypeaheadOption } from '@tih/ui'; +import { Typeahead } from '@tih/ui'; + +import { trpc } from '~/utils/trpc'; + +type Props = Readonly<{ + disabled?: boolean; + onSelect: (option: TypeaheadOption) => void; +}>; + +export default function CompaniesTypeahead({ disabled, onSelect }: Props) { + const [query, setQuery] = useState(''); + const companies = trpc.useQuery([ + 'companies.list', + { + name: query, + }, + ]); + + const { data } = companies; + + return ( + ({ + id, + label: name, + value: id, + })) ?? [] + } + onQueryChange={setQuery} + onSelect={onSelect} + /> + ); +} diff --git a/apps/portal/src/pages/index.tsx b/apps/portal/src/pages/index.tsx index c5bf7af5..368ce97b 100644 --- a/apps/portal/src/pages/index.tsx +++ b/apps/portal/src/pages/index.tsx @@ -1,11 +1,23 @@ +import { useState } from 'react'; +import type { TypeaheadOption } from '@tih/ui'; + +import CompaniesTypeahead from '~/components/global/CompaniesTypeahead'; + export default function HomePage() { + const [selectedCompany, setSelectedCompany] = + useState(null); + return (
-
+

Homepage

+ setSelectedCompany(option)} + /> +
{JSON.stringify(selectedCompany, null, 2)}
diff --git a/apps/portal/src/server/router/companies-router.ts b/apps/portal/src/server/router/companies-router.ts new file mode 100644 index 00000000..ce532e64 --- /dev/null +++ b/apps/portal/src/server/router/companies-router.ts @@ -0,0 +1,23 @@ +import { z } from 'zod'; + +import { createRouter } from './context'; + +export const companiesRouter = createRouter().query('list', { + input: z.object({ + name: z.string(), + }), + async resolve({ ctx, input }) { + return await ctx.prisma.company.findMany({ + orderBy: { + name: 'desc', + }, + take: 10, + where: { + name: { + contains: input.name, + mode: 'insensitive', + }, + }, + }); + }, +}); diff --git a/apps/portal/src/server/router/index.ts b/apps/portal/src/server/router/index.ts index 5783f9fc..e8e6713e 100644 --- a/apps/portal/src/server/router/index.ts +++ b/apps/portal/src/server/router/index.ts @@ -1,5 +1,6 @@ import superjson from 'superjson'; +import { companiesRouter } from './companies-router'; import { createRouter } from './context'; import { protectedExampleRouter } from './protected-example-router'; import { questionsQuestionRouter } from './questions-question-router'; @@ -19,6 +20,7 @@ export const appRouter = createRouter() .merge('auth.', protectedExampleRouter) .merge('todos.', todosRouter) .merge('todos.user.', todosUserRouter) + .merge('companies.', companiesRouter) .merge('resumes.resume.', resumesRouter) .merge('resumes.resume.user.', resumesResumeUserRouter) .merge('resumes.star.user.', resumesStarUserRouter) diff --git a/apps/storybook/stories/typeahead.stories.tsx b/apps/storybook/stories/typeahead.stories.tsx index 0d846045..a0de94d8 100644 --- a/apps/storybook/stories/typeahead.stories.tsx +++ b/apps/storybook/stories/typeahead.stories.tsx @@ -14,6 +14,9 @@ export default { label: { control: 'text', }, + noResultsMessage: { + control: 'text', + }, }, component: Typeahead, parameters: { @@ -62,9 +65,9 @@ export function Basic({ isLabelHidden={isLabelHidden} label={label} options={filteredPeople} - selectedOption={selectedEntry} + value={selectedEntry} onQueryChange={setQuery} - onSelectOption={setSelectedEntry} + onSelect={setSelectedEntry} /> ); } diff --git a/packages/ui/src/Typeahead/Typeahead.tsx b/packages/ui/src/Typeahead/Typeahead.tsx index d80d9200..2197716d 100644 --- a/packages/ui/src/Typeahead/Typeahead.tsx +++ b/packages/ui/src/Typeahead/Typeahead.tsx @@ -14,30 +14,51 @@ type Props = Readonly<{ disabled?: boolean; isLabelHidden?: boolean; label: string; + noResultsMessage?: string; + nullable?: boolean; onQueryChange: ( value: string, event: React.ChangeEvent, ) => void; - onSelectOption: (option: TypeaheadOption) => void; + onSelect: (option: TypeaheadOption) => void; options: ReadonlyArray; - selectedOption: TypeaheadOption; + value?: TypeaheadOption; }>; export default function Typeahead({ disabled = false, isLabelHidden, label, + noResultsMessage = 'No results', + nullable = false, options, onQueryChange, - selectedOption, - onSelectOption, + value, + onSelect, }: Props) { const [query, setQuery] = useState(''); return ( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + multiple={false} + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + nullable={nullable} + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + value={value} + onChange={(newValue) => { + if (newValue == null) { + return; + } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + onSelect(newValue as TypeaheadOption); + }}> - (option as unknown as TypeaheadOption).label + (option as unknown as TypeaheadOption)?.label } onChange={(event) => { - !disabled && onQueryChange(event.target.value, event); + setQuery(event.target.value); + onQueryChange(event.target.value, event); }} /> @@ -76,7 +98,7 @@ export default function Typeahead({ {options.length === 0 && query !== '' ? (
- Nothing found. + {noResultsMessage}
) : ( options.map((option) => (