feat: expand SearchBox to support both SL and SBB station search
This commit is contained in:
parent
4e5d897273
commit
6598ebb3bf
1 changed files with 35 additions and 42 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue