diff --git a/backend/app/api/v1/endpoints/artisan.py b/backend/app/api/v1/endpoints/artisan.py index b28a35a..f2fc2ae 100644 --- a/backend/app/api/v1/endpoints/artisan.py +++ b/backend/app/api/v1/endpoints/artisan.py @@ -81,11 +81,11 @@ async def get_nearby_artisans( radius_km: float = Query( 25.0, ge=0, le=200, description="Search radius in kilometers" ), - skill: str - | None = Query(None, description="Filter by skill keyword (e.g., plumber)"), - min_rating: float - | None = Query(None, ge=0, le=5, description="Minimum average rating"), - available: bool | None = Query(None, description="Filter by current availability"), + specialties: list[str] | None = Query(None, description="Filter by skills"), + min_rating: float | None = Query(None, ge=0, le=5, description="Min rating"), + max_price: float | None = Query(None, ge=0, description="Max hourly rate"), + min_experience: int | None = Query(None, ge=0, description="Min experience"), + available: bool | None = Query(None, description="Filter by availability"), page: int = Query(1, ge=1), page_size: int = Query(10, ge=1, le=100), ): @@ -97,8 +97,10 @@ async def get_nearby_artisans( latitude=lat, longitude=lon, radius_km=radius_km, - specialties=[skill] if skill else None, + specialties=specialties, min_rating=min_rating, + max_price=max_price, + min_experience=min_experience, is_available=available if available is not None else True, limit=page_size * page, # Fetch enough for pagination ) @@ -465,6 +467,8 @@ def list_artisans( limit: int = Query(20, ge=1, le=100), specialties: list[str] | None = Query(None), min_rating: float | None = Query(None, ge=0, le=5), + max_price: float | None = Query(None, ge=0), + min_experience: int | None = Query(None, ge=0), is_available: bool | None = Query(None), has_location: bool | None = Query(None), ): @@ -475,6 +479,8 @@ def list_artisans( limit=limit, specialties=specialties, min_rating=min_rating, + max_price=max_price, + min_experience=min_experience, is_available=is_available, has_location=has_location, ) diff --git a/backend/app/schemas/artisan.py b/backend/app/schemas/artisan.py index 9f3353d..e9e5a95 100644 --- a/backend/app/schemas/artisan.py +++ b/backend/app/schemas/artisan.py @@ -166,6 +166,12 @@ class NearbyArtisansRequest(BaseModel): min_rating: float | None = Field( None, ge=0, le=5, description="Minimum rating filter" ) + max_price: float | None = Field( + None, ge=0, description="Maximum hourly rate filter" + ) + min_experience: int | None = Field( + None, ge=0, description="Minimum experience years filter" + ) is_available: bool | None = Field(True, description="Filter by availability") limit: int | None = Field(20, ge=1, le=100, description="Maximum number of results") diff --git a/backend/app/services/artisan.py b/backend/app/services/artisan.py index e78f83d..89a2b07 100644 --- a/backend/app/services/artisan.py +++ b/backend/app/services/artisan.py @@ -162,6 +162,8 @@ def list_artisans( limit: int = 100, specialties: list[str] | None = None, min_rating: float | None = None, + max_price: float | None = None, + min_experience: int | None = None, is_available: bool | None = None, has_location: bool | None = None, ) -> list[Artisan]: @@ -179,6 +181,12 @@ def list_artisans( if min_rating is not None: query = query.filter(Artisan.rating >= min_rating) + if max_price is not None: + query = query.filter(Artisan.hourly_rate <= max_price) + + if min_experience is not None: + query = query.filter(Artisan.experience_years >= min_experience) + if is_available is not None: query = query.filter(Artisan.is_available == is_available) @@ -231,6 +239,12 @@ async def find_nearby_artisans(self, request: NearbyArtisansRequest) -> dict: if request.min_rating is not None: query = query.filter(Artisan.rating >= request.min_rating) + if request.max_price is not None: + query = query.filter(Artisan.hourly_rate <= request.max_price) + + if request.min_experience is not None: + query = query.filter(Artisan.experience_years >= request.min_experience) + if request.is_available is not None: query = query.filter(Artisan.is_available == request.is_available) diff --git a/backend/app/services/artisan_service.py b/backend/app/services/artisan_service.py index c1587bd..4d72365 100644 --- a/backend/app/services/artisan_service.py +++ b/backend/app/services/artisan_service.py @@ -19,6 +19,8 @@ def _build_cache_key(request: NearbyArtisansRequest) -> str: "radius": request.radius_km, "specialties": sorted(request.specialties or []), # order-independent "min_rating": request.min_rating, + "max_price": request.max_price, + "min_experience": request.min_experience, "available": request.is_available, "limit": request.limit, } diff --git a/frontend/app/artisans/page.tsx b/frontend/app/artisans/page.tsx index 03d2d02..94765a7 100644 --- a/frontend/app/artisans/page.tsx +++ b/frontend/app/artisans/page.tsx @@ -5,173 +5,14 @@ import Link from "next/link"; import Navbar from "../../components/ui/Navbar"; import Footer from "../../components/ui/Footer"; import { Button } from "../../components/ui/button"; -import { - Card, - CardContent, -} from "../../components/ui/card"; +import { Card, CardContent } from "../../components/ui/card"; import { api, type ArtisanItem } from "../../lib/api"; -import { Wrench, MapPin, Star, Sparkles, Filter, X, SlidersHorizontal } from "lucide-react"; +import { Wrench, MapPin, Star, Sparkles } from "lucide-react"; import Price from "../../components/ui/Price"; const DEFAULT_LAT = 51.5074; const DEFAULT_LON = -0.1278; -function SkeletonBlock({ - className, -}: { - className: string; -}) { - return