diff --git a/backend/db/migrations/004_add_winery_search_index.sql b/backend/db/migrations/004_add_winery_search_index.sql new file mode 100644 index 0000000..e69de29 diff --git a/backend/src/index.ts b/backend/src/index.ts index bb7ebb6..b4956dd 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -71,6 +71,7 @@ const typeDefs = `#graphql health: String! wines( + search: String limit: Int = 20 offset: Int = 0 ): WinePage! @@ -91,8 +92,12 @@ const resolvers = { Query: { health: () => 'OK', - wines: async (_: unknown, args: { limit?: number; offset?: number }) => { + wines: async ( + _: unknown, + args: { search?: string; limit?: number; offset?: number }, + ) => { const { limit, offset } = readPaging(args); + const search = args.search?.trim() || null; const winesResult = await pool.query( ` @@ -112,14 +117,35 @@ const resolvers = { JOIN wineries wi ON w.winery_id = wi.id JOIN regions r ON w.region_id = r.id JOIN countries c ON w.country_code = c.code + WHERE ( + $1::text IS NULL + OR w.name ILIKE $1 || '%' + OR w.name ILIKE '% ' || $1 || '%' + OR wi.name ILIKE $1 || '%' + OR wi.name ILIKE '% ' || $1 || '%' + ) ORDER BY w.name, w.id - LIMIT $1 - OFFSET $2 + LIMIT $2 + OFFSET $3 `, - [limit, offset], + [search, limit, offset], ); - const countResult = await pool.query(`SELECT COUNT(*) FROM wines`); + const countResult = await pool.query( + ` + SELECT COUNT(*) + FROM wines w + JOIN wineries wi ON w.winery_id = wi.id + WHERE ( + $1::text IS NULL + OR w.name ILIKE $1 || '%' + OR w.name ILIKE '% ' || $1 || '%' + OR wi.name ILIKE $1 || '%' + OR wi.name ILIKE '% ' || $1 || '%' + ) + `, + [search], + ); return { items: winesResult.rows, diff --git a/client/src/components/WineCatalog.tsx b/client/src/components/WineCatalog.tsx index 041d3bb..82906d2 100644 --- a/client/src/components/WineCatalog.tsx +++ b/client/src/components/WineCatalog.tsx @@ -2,7 +2,11 @@ import { useEffect, useRef, useState } from 'react'; import { useQuery } from '@apollo/client/react'; import WineList from './WineList'; import Pagination from './Pagination'; -import { WINES_QUERY, type WinesData } from '../graphql/queries'; +import { + WINES_QUERY, + type WinesData, + type WinesVariables, +} from '../graphql/queries'; export const PAGE_SIZE = 20; @@ -18,12 +22,29 @@ export const PAGE_SIZE = 20; */ export default function WineCatalog() { const [page, setPage] = useState(1); + const [searchInput, setSearchInput] = useState(''); + const [search, setSearch] = useState(''); const offset = (page - 1) * PAGE_SIZE; - const { data, previousData, loading, error } = useQuery( - WINES_QUERY, - { variables: { offset, limit: PAGE_SIZE } }, - ); + useEffect(() => { + const timeoutId = window.setTimeout(() => { + setSearch(searchInput.trim()); + setPage(1); + }, 350); + + return () => window.clearTimeout(timeoutId); + }, [searchInput]); + + const { data, previousData, loading, error } = useQuery< + WinesData, + WinesVariables + >(WINES_QUERY, { + variables: { + search: search || null, + offset, + limit: PAGE_SIZE, + }, + }); const shown = data ?? previousData; @@ -53,6 +74,18 @@ export default function WineCatalog() { return ( // aria-busy tells screen readers the content is being updated
+
+ + + setSearchInput(event.target.value)} + placeholder="For eksempel Barolo" + /> +
+