mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
175 lines
5.8 KiB
TypeScript
175 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>
|
|
);
|
|
}
|