fix: recherche insensible à la casse et aux accents dans tous les filtres
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import uuid
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
from fastapi.responses import Response
|
from fastapi.responses import Response
|
||||||
from sqlalchemy import select, text
|
from sqlalchemy import select, text, or_
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ async def search_products(
|
|||||||
):
|
):
|
||||||
stmt = select(Product).order_by(Product.frequency_score.desc(), Product.name)
|
stmt = select(Product).order_by(Product.frequency_score.desc(), Product.name)
|
||||||
if q:
|
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)
|
stmt = stmt.limit(limit)
|
||||||
result = await session.execute(stmt)
|
result = await session.execute(stmt)
|
||||||
return result.scalars().all()
|
return result.scalars().all()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// frontend/src/pages/ShoppingPage.tsx
|
// frontend/src/pages/ShoppingPage.tsx
|
||||||
import { useState, useEffect, useCallback } from 'react'
|
import { useState, useEffect, useCallback } from 'react'
|
||||||
|
import { matchesSearch } from '../utils/search'
|
||||||
import type { ShoppingListDetail, ShoppingList, Store, Product } from '../api/shopping'
|
import type { ShoppingListDetail, ShoppingList, Store, Product } from '../api/shopping'
|
||||||
import {
|
import {
|
||||||
fetchLists, createList, fetchListDetail, deleteList,
|
fetchLists, createList, fetchListDetail, deleteList,
|
||||||
@@ -213,9 +214,10 @@ export default function ShoppingPage() {
|
|||||||
const hasCurrentList = currentList !== null
|
const hasCurrentList = currentList !== null
|
||||||
const pastLists = allLists.filter(l => l.status === 'done')
|
const pastLists = allLists.filter(l => l.status === 'done')
|
||||||
|
|
||||||
const filteredProducts = products.filter(
|
const filteredProducts = products.filter(p => {
|
||||||
p => !itemSearch.trim() || p.name.toLowerCase().includes(itemSearch.trim().toLowerCase())
|
const term = itemSearch.trim()
|
||||||
)
|
return !term || matchesSearch(p.name, term) || matchesSearch(p.brand ?? '', term)
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', minHeight: '100%' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', minHeight: '100%' }}>
|
||||||
|
|||||||
@@ -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))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user