feat: expand SearchBox to support both SL and SBB station search

This commit is contained in:
cediackermann 2026-04-20 18:27:24 +02:00
parent 4e5d897273
commit 6598ebb3bf

View file

@ -1,62 +1,55 @@
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';
import { StopLocation } from '../../types'; import { StopLocation } from '../../types';
interface SearchBoxProps { interface SearchBoxProps {
onSelect: (stop: StopLocation) => void; onSelect: (stop: StopLocation) => void;
endpoint: string;
placeholder?: string;
} }
export const SearchBox = ({ onSelect }: SearchBoxProps) => { export const SearchBox = ({ onSelect, endpoint, placeholder = 'Search for a station...' }: SearchBoxProps) => {
const [input, setInput] = useState('');
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
const [results, setResults] = useState<StopLocation[]>([]);
const [loading, setLoading] = useState(false);
const search = async () => { useEffect(() => {
if (!query) return; const timer = setTimeout(() => setQuery(input.trim()), 350);
setLoading(true); return () => clearTimeout(timer);
try { }, [input]);
const response = await fetch(`/api/sl/search?q=${encodeURIComponent(query)}`);
const data = await response.json(); const { data, isFetching } = useQuery<{ StopLocation: StopLocation[] }>({
setResults(data.StopLocation || []); queryKey: ['search', endpoint, query],
} catch (e) { queryFn: () => fetch(`${endpoint}?q=${encodeURIComponent(query)}`).then((r) => r.json()),
console.error('Search failed', e); enabled: query.length > 0,
} finally { staleTime: 60_000,
setLoading(false); });
}
}; const results = data?.StopLocation ?? [];
return ( return (
<div className="w-full max-w-2xl mx-auto mt-8 px-4"> <div className="w-full">
<div className="flex gap-4">
<input <input
type="text" type="text"
value={query} value={input}
onChange={(e) => setQuery(e.target.value)} onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && search()} placeholder={placeholder}
placeholder="Search for a station..." className="w-full bg-black text-white px-4 py-2 border border-white outline-none text-lg"
className="flex-grow bg-surface text-white px-6 py-4 rounded-xl border-none outline-none focus:ring-2 focus:ring-sl-blue transition-all text-xl"
/> />
<button
onClick={search}
className="bg-sl-blue px-8 py-4 rounded-xl font-bold text-xl hover:brightness-110 active:scale-95 transition-all"
>
Search
</button>
</div>
<div className="mt-8 flex flex-col gap-3"> <div className="mt-2 flex flex-col">
{loading && <div className="p-6 bg-surface rounded-xl animate-pulse text-xl">Searching...</div>} {isFetching && <p className="text-gray-500 py-2">Searching...</p>}
{!loading && results.map((stop) => ( {!isFetching && results.map((stop) => (
<div <div
key={stop.id} key={stop.id}
onClick={() => onSelect(stop)} onClick={() => onSelect(stop)}
className="flex justify-between items-center p-6 bg-surface rounded-xl cursor-pointer hover:bg-zinc-800 transition-colors text-xl" className="flex justify-between items-center py-3 border-b border-zinc-700 cursor-pointer text-lg"
> >
<span className="font-medium">{stop.name}</span> <span>{stop.name}</span>
<span className="text-gray-500 text-sm">ID: {stop.id}</span> <span className="text-gray-500 text-sm">{stop.id}</span>
</div> </div>
))} ))}
{!loading && query && results.length === 0 && ( {!isFetching && query && results.length === 0 && (
<div className="p-6 bg-surface rounded-xl text-gray-500 text-xl">No stations found.</div> <p className="text-gray-500 py-2">No stations found.</p>
)} )}
</div> </div>
</div> </div>