location filter

This commit is contained in:
jackiettran
2025-12-30 14:23:21 -05:00
parent 546c881701
commit 6cf8a009ff
3 changed files with 299 additions and 223 deletions

View File

@@ -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>
);
};