From 4fa4684093afd89542e91677e5acf1f8674f6556 Mon Sep 17 00:00:00 2001 From: adelethorberg-sudo Date: Tue, 20 Oct 2026 12:42:59 +0200 Subject: [PATCH] feature/explore-by-taste-endring2 --- backend/src/index.ts | 54 +++++++++++++++--------- client/src/components/ExploreByTaste.tsx | 19 ++++----- client/src/data/taste.ts | 2 +- client/src/graphql/queries.ts | 3 +- 4 files changed, 46 insertions(+), 32 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 1a7054c..852ee98 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -153,7 +153,7 @@ const typeDefs = `#graphql ): WinePage! # Viner til smakskartet. Bare viner med både fylde og syre kommer med. - tasteMap(filter: TasteFilter, limit: Int = 150): WinePage! + tasteMap(filter: TasteFilter, limit: Int = 100): WinePage! tasteOptions: TasteOptions! } @@ -349,7 +349,7 @@ const resolvers = { _: unknown, args: { filter?: TasteFilter | null; limit?: number }, ) => { - const limit = args.limit ?? 150; + const limit = args.limit ?? 100; if (!Number.isInteger(limit) || limit < 1 || limit > TASTE_MAP_MAX) { throw new GraphQLError(`limit must be between 1 and ${TASTE_MAP_MAX}`, { extensions: { code: 'BAD_USER_INPUT' }, @@ -360,26 +360,40 @@ const resolvers = { // Vi viser de vinene flest har vurdert, så kartet fylles med kjente // viner i stedet for viner med én enkelt vurdering på 5.0. + // De mest vurderte er nesten bare rødvin, så vi tar dem på omgang per + // type: den mest vurderte av hver type først, så den nest mest vurderte + // osv. Uten typefilter blir kartet en jevn miks (100 viner = 20 av hver + // av de fem typene); med typefilter finnes bare én type, og rekkefølgen + // blir som før. 'Dessert' og 'Dessert/Port' regnes som én type, slik + // som i fargeforklaringen på kartet. const winesResult = await pool.query( ` - SELECT - w.id, - w.name, - w.type, - w.abv::float AS abv, - w.body, - w.acidity, - wi.name AS winery, - r.name AS region, - c.name AS country, - w.avg_rating::float AS "avgRating", - w.rating_count AS "ratingCount" - FROM wines w - 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 ${sql} - ORDER BY w.rating_count DESC, w.id + SELECT * + FROM ( + SELECT + w.id, + w.name, + w.type, + w.abv::float AS abv, + w.body, + w.acidity, + wi.name AS winery, + r.name AS region, + c.name AS country, + w.avg_rating::float AS "avgRating", + w.rating_count AS "ratingCount", + ROW_NUMBER() OVER ( + PARTITION BY + CASE WHEN w.type LIKE 'Dessert%' THEN 'Dessert' ELSE w.type END + ORDER BY w.rating_count DESC, w.id + ) AS type_rank + FROM wines w + 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 ${sql} + ) ranked + ORDER BY type_rank, "ratingCount" DESC, id LIMIT $${params.length + 1} `, [...params, limit], diff --git a/client/src/components/ExploreByTaste.tsx b/client/src/components/ExploreByTaste.tsx index a8a30ad..b6a7a1d 100644 --- a/client/src/components/ExploreByTaste.tsx +++ b/client/src/components/ExploreByTaste.tsx @@ -46,7 +46,8 @@ function toVariables(filter: TasteFilter) { * only display what they get as props (same split as WineCatalog/WineList). * * Scalability: the map never loads the whole dataset. The backend returns at - * most TASTE_MAP_LIMIT wines (the most-rated ones that match the filters), + * most TASTE_MAP_LIMIT wines (the most-rated ones that match the filters, + * taken in turn from each wine type so the map is not almost all red), * and totalCount tells how many match in total. */ export default function ExploreByTaste() { @@ -92,17 +93,15 @@ export default function ExploreByTaste() { aria-label="Taste map" aria-busy={loading} > +

Wine map

{/* role="status" announces the new count when filters change */} -

- {shown - ? `${shown.totalCount.toLocaleString('en')} wines found` - : 'Loading wines …'} +

+ {!shown + ? 'Loading wines …' + : shown.totalCount > wines.length + ? `Showing ${wines.length} wines` + : `${shown.totalCount.toLocaleString('en')} ${shown.totalCount === 1 ? 'wine' : 'wines'} found.`}

- {shown && shown.totalCount > wines.length && ( -

- The map shows the {wines.length} most-rated of them. -

- )} {error && (

Could not load wines. Please try again later.

diff --git a/client/src/data/taste.ts b/client/src/data/taste.ts index 98f4da2..c953fd3 100644 --- a/client/src/data/taste.ts +++ b/client/src/data/taste.ts @@ -39,7 +39,7 @@ export function wineTypeColor(type: string): string { } /** How many wines the map shows at most. The backend allows up to 300. */ -export const TASTE_MAP_LIMIT = 150; +export const TASTE_MAP_LIMIT = 100; /** "Only popular wines" means at least this many ratings. */ export const POPULAR_MIN_RATINGS = 100; diff --git a/client/src/graphql/queries.ts b/client/src/graphql/queries.ts index aa33aef..c8d73f2 100644 --- a/client/src/graphql/queries.ts +++ b/client/src/graphql/queries.ts @@ -45,7 +45,8 @@ export type WinesVariables = { /** * Wines for the Explore by taste map. * - * The backend returns the most-rated wines that match the filters, at most + * The backend returns the most-rated wines that match the filters, taken in + * turn from each wine type so all types show up, at most * `limit` of them. totalCount is the number of all matching wines. */ export const TASTE_MAP_QUERY = gql`