turash/bugulma/frontend/components/discovery/DiscoverySearchBar.tsx

180 lines
5.8 KiB
TypeScript

import { Card, FormField } from '@/components/ui';
import Button from '@/components/ui/Button';
import Flex from '@/components/ui/Flex';
import Grid from '@/components/ui/Grid';
import Input from '@/components/ui/Input';
import Select from '@/components/ui/Select';
import Stack from '@/components/ui/Stack';
import { useTranslation } from '@/hooks/useI18n';
import { getCategories, type SearchQuery } from '@/services/discovery-api';
import { useQuery } from '@tanstack/react-query';
import { Filter, MapPin, Search } from 'lucide-react';
import React, { useMemo, useState } from 'react';
interface DiscoverySearchBarProps {
onSearch: (query: SearchQuery) => void;
initialQuery?: SearchQuery;
showFilters?: boolean;
}
export default function DiscoverySearchBar({
onSearch,
initialQuery,
showFilters = true,
}: DiscoverySearchBarProps) {
const { t } = useTranslation();
const [query, setQuery] = useState(initialQuery?.query || '');
const [category, setCategory] = useState(initialQuery?.categories?.[0] || '');
const [radius, setRadius] = useState(initialQuery?.radius_km?.toString() || '50');
const [showAdvanced, setShowAdvanced] = useState(false);
// Fetch categories from backend
const { data: categoriesData, isLoading: categoriesLoading } = useQuery({
queryKey: ['discovery-categories'],
queryFn: getCategories,
staleTime: 1000 * 60 * 60, // Cache for 1 hour
});
// Combine all categories for the dropdown
const allCategories = useMemo(() => {
if (!categoriesData) return [];
// Combine all category types, removing duplicates
const combined = [
...categoriesData.products,
...categoriesData.services,
...categoriesData.community,
];
// Remove duplicates and sort
return Array.from(new Set(combined)).sort();
}, [categoriesData]);
const handleSearch = () => {
const searchQuery: SearchQuery = {
query: query || undefined,
categories: category ? [category] : undefined,
radius_km: radius ? parseFloat(radius) : undefined,
limit: 20,
};
// Try to get user location if available
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
searchQuery.latitude = position.coords.latitude;
searchQuery.longitude = position.coords.longitude;
onSearch(searchQuery);
},
() => {
// Location denied or unavailable, search without location
onSearch(searchQuery);
}
);
} else {
onSearch(searchQuery);
}
};
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handleSearch();
}
};
return (
<Stack className="w-full">
<Flex gap="sm">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input
type="text"
placeholder={t('discoveryPage.searchPlaceholder')}
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyPress={handleKeyPress}
className="pl-10"
/>
</div>
<Button onClick={handleSearch} size="default">
{t('discoveryPage.searchButton')}
</Button>
</Flex>
{showFilters && (
<Flex wrap="wrap" gap="sm">
<Select
value={category}
onChange={(e) => setCategory(e.target.value)}
className="w-[180px]"
disabled={categoriesLoading}
>
<option value="">{t('discoveryPage.allCategories')}</option>
{categoriesLoading ? (
<option value="" disabled>
Loading...
</option>
) : (
allCategories.map((cat) => (
<option key={cat} value={cat}>
{cat.charAt(0).toUpperCase() + cat.slice(1).replace(/_/g, ' ')}
</option>
))
)}
</Select>
<div className="relative flex-1 min-w-[120px]">
<MapPin className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input
type="number"
placeholder={t('discoveryPage.radius')}
value={radius}
onChange={(e) => setRadius(e.target.value)}
className="pl-10"
/>
</div>
<Button
variant="outline"
size="sm"
onClick={() => setShowAdvanced(!showAdvanced)}
>
<Filter className="mr-2 h-4 w-4" />
{t('discoveryPage.filters')}
</Button>
</Flex>
)}
{showAdvanced && showFilters && (
<Card className="p-4">
<Stack spacing="sm">
<Grid cols={2} gap="md">
<FormField label={t('discoveryPage.minPrice')}>
<Input
type="number"
placeholder="0"
value={initialQuery?.min_price?.toString() || ''}
/>
</FormField>
<FormField label={t('discoveryPage.maxPrice')}>
<Input
type="number"
placeholder="1000"
value={initialQuery?.max_price?.toString() || ''}
/>
</FormField>
</Grid>
<FormField label={t('discoveryPage.availability')}>
<Select defaultValue={initialQuery?.availability_status || ''}>
<option value="">{t('discoveryPage.any')}</option>
<option value="available">{t('discoveryPage.available')}</option>
<option value="limited">{t('discoveryPage.limited')}</option>
<option value="out_of_stock">{t('discoveryPage.outOfStock')}</option>
</Select>
</FormField>
</Stack>
</Card>
)}
</Stack>
);
}