Make EV charging an amenity toggle and drop filter select

This commit is contained in:
Tero Halla-aho 2025-12-06 23:51:23 +02:00
parent c08dca02e7
commit 3d13bf3ba3
2 changed files with 16 additions and 45 deletions

View file

@ -137,6 +137,7 @@ export default function NewListingPage() {
{ key: 'microwave', label: t('amenityMicrowave'), icon: '🍲', checked: hasMicrowave, toggle: setHasMicrowave }, { key: 'microwave', label: t('amenityMicrowave'), icon: '🍲', checked: hasMicrowave, toggle: setHasMicrowave },
{ key: 'parking', label: t('amenityFreeParking'), icon: '🅿️', checked: hasFreeParking, toggle: setHasFreeParking }, { key: 'parking', label: t('amenityFreeParking'), icon: '🅿️', checked: hasFreeParking, toggle: setHasFreeParking },
{ key: 'ski', label: t('amenitySkiPass'), icon: '⛷️', checked: hasSkiPass, toggle: setHasSkiPass }, { key: 'ski', label: t('amenitySkiPass'), icon: '⛷️', checked: hasSkiPass, toggle: setHasSkiPass },
{ key: 'ev', label: t('amenityEvAvailable'), icon: '⚡', checked: evChargingAvailable, toggle: setEvChargingAvailable },
]; ];
function updateTranslation(locale: Locale, field: keyof LocaleFields, value: string) { function updateTranslation(locale: Locale, field: keyof LocaleFields, value: string) {
@ -704,28 +705,6 @@ export default function NewListingPage() {
</span> </span>
</button> </button>
))} ))}
<div className="amenity-ev">
<div className="amenity-ev-label">{t('evChargingLabel')}</div>
<div style={{ color: '#cbd5e1', fontSize: 12, marginBottom: 6 }}>{t('evChargingExplain')}</div>
<div className="ev-toggle-group">
{[
{ value: true, label: t('evChargingYes'), icon: '⚡' },
{ value: false, label: t('evChargingNo'), icon: '🚗' },
].map((opt) => (
<button
key={String(opt.value)}
type="button"
className={`ev-toggle ${evChargingAvailable === opt.value ? 'active' : ''}`}
onClick={() => setEvChargingAvailable(opt.value)}
>
<span aria-hidden className="amenity-emoji">
{opt.icon}
</span>
{opt.label}
</button>
))}
</div>
</div>
</div> </div>
<div style={{ display: 'grid', gap: 8, gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))' }}> <div style={{ display: 'grid', gap: 8, gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))' }}>
<label> <label>

View file

@ -183,7 +183,6 @@ export default function ListingsIndexPage() {
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
const [city, setCity] = useState(''); const [city, setCity] = useState('');
const [region, setRegion] = useState(''); const [region, setRegion] = useState('');
const [evCharging, setEvCharging] = useState<'ALL' | 'YES' | 'NO'>('ALL');
const [listings, setListings] = useState<ListingResult[]>([]); const [listings, setListings] = useState<ListingResult[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@ -207,10 +206,7 @@ export default function ListingsIndexPage() {
}); });
}, [listings, addressCenter, radiusKm]); }, [listings, addressCenter, radiusKm]);
const filtered = useMemo(() => { const filtered = filteredByAddress;
if (evCharging === 'ALL') return filteredByAddress;
return filteredByAddress.filter((l) => (evCharging === 'YES' ? l.evChargingAvailable : !l.evChargingAvailable));
}, [filteredByAddress, evCharging]);
const amenityOptions = [ const amenityOptions = [
{ key: 'sauna', label: t('amenitySauna'), icon: amenityIcons.sauna }, { key: 'sauna', label: t('amenitySauna'), icon: amenityIcons.sauna },
@ -226,6 +222,7 @@ export default function ListingsIndexPage() {
{ key: 'microwave', label: t('amenityMicrowave'), icon: amenityIcons.microwave }, { key: 'microwave', label: t('amenityMicrowave'), icon: amenityIcons.microwave },
{ key: 'parking', label: t('amenityFreeParking'), icon: amenityIcons.parking }, { key: 'parking', label: t('amenityFreeParking'), icon: amenityIcons.parking },
{ key: 'skipass', label: t('amenitySkiPass'), icon: amenityIcons.ski }, { key: 'skipass', label: t('amenitySkiPass'), icon: amenityIcons.ski },
{ key: 'ev', label: t('amenityEvAvailable'), icon: amenityIcons.ev },
]; ];
async function fetchListings() { async function fetchListings() {
@ -236,10 +233,13 @@ export default function ListingsIndexPage() {
if (query) params.set('q', query); if (query) params.set('q', query);
if (city) params.set('city', city); if (city) params.set('city', city);
if (region) params.set('region', region); if (region) params.set('region', region);
if (evCharging !== 'ALL') params.set('evCharging', evCharging === 'YES' ? 'true' : 'false');
if (startDate) params.set('availableStart', startDate); if (startDate) params.set('availableStart', startDate);
if (endDate) params.set('availableEnd', endDate); if (endDate) params.set('availableEnd', endDate);
amenities.forEach((a) => params.append('amenity', a)); const evSelected = amenities.includes('ev');
if (evSelected) params.set('evCharging', 'true');
amenities
.filter((a) => a !== 'ev')
.forEach((a) => params.append('amenity', a));
const res = await fetch(`/api/listings?${params.toString()}`, { cache: 'no-store' }); const res = await fetch(`/api/listings?${params.toString()}`, { cache: 'no-store' });
const data = await res.json(); const data = await res.json();
if (!res.ok || data.error) { if (!res.ok || data.error) {
@ -314,22 +314,14 @@ export default function ListingsIndexPage() {
/> />
</label> </label>
<label> <label>
{t('cityFilter')} {t('cityFilter')}
<input value={city} onChange={(e) => setCity(e.target.value)} placeholder={t('cityFilter')} /> <input value={city} onChange={(e) => setCity(e.target.value)} placeholder={t('cityFilter')} />
</label> </label>
<label> <label>
{t('regionFilter')} {t('regionFilter')}
<input value={region} onChange={(e) => setRegion(e.target.value)} placeholder={t('regionFilter')} /> <input value={region} onChange={(e) => setRegion(e.target.value)} placeholder={t('regionFilter')} />
</label> </label>
<label> </div>
{t('evChargingLabel')}
<select value={evCharging} onChange={(e) => setEvCharging(e.target.value as any)}>
<option value="ALL">{t('evChargingAny')}</option>
<option value="YES">{t('evChargingYes')}</option>
<option value="NO">{t('evChargingNo')}</option>
</select>
</label>
</div>
<div style={{ display: 'grid', gap: 10, gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', marginTop: 12 }}> <div style={{ display: 'grid', gap: 10, gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', marginTop: 12 }}>
<label> <label>
{t('startDate')} {t('startDate')}