location filter
This commit is contained in:
@@ -4,7 +4,7 @@ import { Item } from "../types";
|
||||
import { itemAPI } from "../services/api";
|
||||
import ItemCard from "../components/ItemCard";
|
||||
import SearchResultsMap from "../components/SearchResultsMap";
|
||||
import LocationPromptModal from "../components/LocationPromptModal";
|
||||
import FilterPanel from "../components/FilterPanel";
|
||||
import { useAuth } from "../contexts/AuthContext";
|
||||
|
||||
const ItemList: React.FC = () => {
|
||||
@@ -15,8 +15,10 @@ const ItemList: React.FC = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [viewMode, setViewMode] = useState<'list' | 'map'>('list');
|
||||
const [showLocationPrompt, setShowLocationPrompt] = useState(false);
|
||||
const [showFilterPanel, setShowFilterPanel] = useState(false);
|
||||
const [locationName, setLocationName] = useState(searchParams.get("locationName") || "");
|
||||
const locationCheckDone = useRef(false);
|
||||
const filterButtonRef = useRef<HTMLDivElement>(null);
|
||||
const [filters, setFilters] = useState({
|
||||
search: searchParams.get("search") || "",
|
||||
city: searchParams.get("city") || "",
|
||||
@@ -26,13 +28,12 @@ const ItemList: React.FC = () => {
|
||||
radius: searchParams.get("radius") || "",
|
||||
});
|
||||
|
||||
// Check if location is needed and handle accordingly
|
||||
// Auto-apply user's saved address if no location is set
|
||||
useEffect(() => {
|
||||
// Only run this check once per mount
|
||||
if (locationCheckDone.current) return;
|
||||
|
||||
const hasLocation = searchParams.has("lat") || searchParams.has("city") || searchParams.has("zipCode");
|
||||
const hasSearchTerm = searchParams.has("search");
|
||||
|
||||
if (!hasLocation) {
|
||||
// Check user's saved address for lat/lng
|
||||
@@ -47,12 +48,7 @@ const ItemList: React.FC = () => {
|
||||
params.set("radius", "25");
|
||||
locationCheckDone.current = true;
|
||||
navigate(`/items?${params.toString()}`, { replace: true });
|
||||
} else if (!hasSearchTerm) {
|
||||
// No saved address and no search term - show location prompt
|
||||
locationCheckDone.current = true;
|
||||
setShowLocationPrompt(true);
|
||||
} else {
|
||||
// Has search term but no location - just show results without location filter
|
||||
locationCheckDone.current = true;
|
||||
}
|
||||
} else {
|
||||
@@ -74,22 +70,39 @@ const ItemList: React.FC = () => {
|
||||
lng: searchParams.get("lng") || "",
|
||||
radius: searchParams.get("radius") || "",
|
||||
});
|
||||
setLocationName(searchParams.get("locationName") || "");
|
||||
}, [searchParams]);
|
||||
|
||||
const handleLocationSelect = (location: { lat: number; lng: number }) => {
|
||||
const handleApplyFilters = (newFilters: {
|
||||
lat: string;
|
||||
lng: string;
|
||||
radius: string;
|
||||
locationName: string;
|
||||
}) => {
|
||||
const params = new URLSearchParams(searchParams);
|
||||
|
||||
params.set("lat", location.lat.toString());
|
||||
params.set("lng", location.lng.toString());
|
||||
params.set("radius", "25");
|
||||
// Remove city/zipCode since we're using coordinates
|
||||
params.delete("city");
|
||||
params.delete("zipCode");
|
||||
if (newFilters.lat && newFilters.lng) {
|
||||
params.set("lat", newFilters.lat);
|
||||
params.set("lng", newFilters.lng);
|
||||
params.set("radius", newFilters.radius || "25");
|
||||
params.set("locationName", newFilters.locationName);
|
||||
// Remove city/zipCode since we're using coordinates
|
||||
params.delete("city");
|
||||
params.delete("zipCode");
|
||||
} else {
|
||||
// Clear location filters
|
||||
params.delete("lat");
|
||||
params.delete("lng");
|
||||
params.delete("radius");
|
||||
params.delete("locationName");
|
||||
}
|
||||
|
||||
setLocationName(newFilters.locationName);
|
||||
navigate(`/items?${params.toString()}`, { replace: true });
|
||||
setShowLocationPrompt(false);
|
||||
};
|
||||
|
||||
const hasActiveLocationFilter = filters.lat && filters.lng;
|
||||
|
||||
const fetchItems = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
@@ -164,26 +177,58 @@ const ItemList: React.FC = () => {
|
||||
<span className="text-muted">{items.length} items found</span>
|
||||
</div>
|
||||
|
||||
{items.length > 0 && (
|
||||
<div className="btn-group" role="group" aria-label="View toggle">
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
{/* Filter Button */}
|
||||
<div className="position-relative" ref={filterButtonRef} style={{ overflow: 'visible' }}>
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-outline-secondary ${viewMode === 'list' ? 'active' : ''}`}
|
||||
onClick={() => setViewMode('list')}
|
||||
className={`btn btn-outline-secondary ${hasActiveLocationFilter ? 'active' : ''}`}
|
||||
onClick={() => setShowFilterPanel(!showFilterPanel)}
|
||||
data-filter-button
|
||||
>
|
||||
<i className="bi bi-list-ul me-1 me-md-2"></i>
|
||||
<span className="d-none d-md-inline">List</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-outline-secondary ${viewMode === 'map' ? 'active' : ''}`}
|
||||
onClick={() => setViewMode('map')}
|
||||
>
|
||||
<i className="bi bi-geo-alt-fill me-1 me-md-2"></i>
|
||||
<span className="d-none d-md-inline">Map</span>
|
||||
<i className="bi bi-filter me-1 me-md-2 align-middle"></i>
|
||||
<span className="d-none d-md-inline">Filters</span>
|
||||
{hasActiveLocationFilter && (
|
||||
<span className="position-absolute top-0 start-100 translate-middle p-1 bg-primary border border-light rounded-circle">
|
||||
<span className="visually-hidden">Active filters</span>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
<FilterPanel
|
||||
show={showFilterPanel}
|
||||
onClose={() => setShowFilterPanel(false)}
|
||||
currentFilters={{
|
||||
lat: filters.lat,
|
||||
lng: filters.lng,
|
||||
radius: filters.radius,
|
||||
locationName: locationName,
|
||||
}}
|
||||
onApplyFilters={handleApplyFilters}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* View Toggle */}
|
||||
{items.length > 0 && (
|
||||
<div className="btn-group" role="group" aria-label="View toggle">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-outline-secondary ${viewMode === 'list' ? 'active' : ''}`}
|
||||
onClick={() => setViewMode('list')}
|
||||
>
|
||||
<i className="bi bi-list-ul me-1 me-md-2"></i>
|
||||
<span className="d-none d-md-inline">List</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-outline-secondary ${viewMode === 'map' ? 'active' : ''}`}
|
||||
onClick={() => setViewMode('map')}
|
||||
>
|
||||
<i className="bi bi-geo-alt-fill me-1 me-md-2"></i>
|
||||
<span className="d-none d-md-inline">Map</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{items.length === 0 ? (
|
||||
@@ -215,12 +260,6 @@ const ItemList: React.FC = () => {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<LocationPromptModal
|
||||
show={showLocationPrompt}
|
||||
onClose={() => setShowLocationPrompt(false)}
|
||||
onLocationSelect={handleLocationSelect}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user