From 377531d08e21fb0201ddbc943d898443ba636b4a Mon Sep 17 00:00:00 2001 From: Gilles Soulier Date: Mon, 25 May 2026 07:00:27 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20recherche=20insensible=20=C3=A0=20la=20c?= =?UTF-8?q?asse=20et=20aux=20accents=20dans=20tous=20les=20filtres?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - utils/search.ts : normalize() (NFD + minuscules) + matchesSearch() - ShoppingPage filteredProducts : matchesSearch sur nom ET marque - Backend searchProducts : ilike sur nom ET marque (or_) - Notes FTS : déjà insensible nativement (plainto_tsquery) Co-Authored-By: Claude Sonnet 4.6 --- backend/app/api/shopping.py | 4 ++-- frontend/src/pages/ShoppingPage.tsx | 8 +++++--- frontend/src/utils/search.ts | 10 ++++++++++ 3 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 frontend/src/utils/search.ts diff --git a/backend/app/api/shopping.py b/backend/app/api/shopping.py index 320f93b..38efe00 100644 --- a/backend/app/api/shopping.py +++ b/backend/app/api/shopping.py @@ -3,7 +3,7 @@ import uuid from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException, Query from fastapi.responses import Response -from sqlalchemy import select, text +from sqlalchemy import select, text, or_ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload @@ -104,7 +104,7 @@ async def search_products( ): stmt = select(Product).order_by(Product.frequency_score.desc(), Product.name) if q: - stmt = stmt.where(Product.name.ilike(f"%{q}%")) + stmt = stmt.where(or_(Product.name.ilike(f"%{q}%"), Product.brand.ilike(f"%{q}%"))) stmt = stmt.limit(limit) result = await session.execute(stmt) return result.scalars().all() diff --git a/frontend/src/pages/ShoppingPage.tsx b/frontend/src/pages/ShoppingPage.tsx index cbd2542..520fa64 100644 --- a/frontend/src/pages/ShoppingPage.tsx +++ b/frontend/src/pages/ShoppingPage.tsx @@ -1,5 +1,6 @@ // frontend/src/pages/ShoppingPage.tsx import { useState, useEffect, useCallback } from 'react' +import { matchesSearch } from '../utils/search' import type { ShoppingListDetail, ShoppingList, Store, Product } from '../api/shopping' import { fetchLists, createList, fetchListDetail, deleteList, @@ -213,9 +214,10 @@ export default function ShoppingPage() { const hasCurrentList = currentList !== null const pastLists = allLists.filter(l => l.status === 'done') - const filteredProducts = products.filter( - p => !itemSearch.trim() || p.name.toLowerCase().includes(itemSearch.trim().toLowerCase()) - ) + const filteredProducts = products.filter(p => { + const term = itemSearch.trim() + return !term || matchesSearch(p.name, term) || matchesSearch(p.brand ?? '', term) + }) return (
diff --git a/frontend/src/utils/search.ts b/frontend/src/utils/search.ts new file mode 100644 index 0000000..f1177dd --- /dev/null +++ b/frontend/src/utils/search.ts @@ -0,0 +1,10 @@ +/** Normalise une chaîne : minuscules + suppression des accents. */ +export function normalize(s: string): string { + return s.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '') +} + +/** Vérifie si `haystack` contient `needle` (insensible casse/accents). */ +export function matchesSearch(haystack: string, needle: string): boolean { + if (!needle) return true + return normalize(haystack).includes(normalize(needle)) +}