feat: rework to use next and host from single dockerfile
This commit is contained in:
108
mta-sign-ui/components/StationSelector.tsx
Normal file
108
mta-sign-ui/components/StationSelector.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { ChevronDownIcon } from '@heroicons/react/24/outline'
|
||||
import axios from 'axios'
|
||||
|
||||
export interface Station {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
interface StationSelectorProps {
|
||||
selectedStation: Station | null
|
||||
onSelect: (station: Station) => void
|
||||
}
|
||||
|
||||
export default function StationSelector({ selectedStation, onSelect }: StationSelectorProps) {
|
||||
const [search, setSearch] = useState('')
|
||||
const [stations, setStations] = useState<Station[]>([])
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const fetchStations = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const params = search ? { search } : {}
|
||||
const response = await axios.get('/api/stations', { params })
|
||||
setStations(response.data.stations || [])
|
||||
} catch (err) {
|
||||
console.error('Error fetching stations:', err)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const debounce = setTimeout(fetchStations, 300)
|
||||
return () => clearTimeout(debounce)
|
||||
}, [search])
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setIsOpen(false)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||
}, [])
|
||||
|
||||
const handleSelect = (station: Station) => {
|
||||
onSelect(station)
|
||||
setSearch('')
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={dropdownRef} className="relative w-full">
|
||||
<div
|
||||
className="flex items-center gap-2 bg-gray-700 px-3 py-2 cursor-pointer"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={selectedStation?.name || 'Search stations...'}
|
||||
value={search}
|
||||
onChange={(e) => {
|
||||
setSearch(e.target.value)
|
||||
setIsOpen(true)
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setIsOpen(true)
|
||||
}}
|
||||
className="flex-1 bg-transparent text-white placeholder-gray-400 outline-none text-sm md:text-base lg:text-lg"
|
||||
/>
|
||||
<ChevronDownIcon
|
||||
className={`w-4 h-4 text-gray-400 transition-transform ${isOpen ? 'rotate-180' : ''}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isOpen && (
|
||||
<div className="absolute z-50 w-full mt-1 bg-gray-800 border border-gray-600 rounded-lg shadow-lg max-h-60 overflow-y-auto">
|
||||
{loading ? (
|
||||
<div className="p-3 text-gray-400 text-center">Loading...</div>
|
||||
) : stations.length === 0 ? (
|
||||
<div className="p-3 text-gray-400 text-center">No stations found</div>
|
||||
) : (
|
||||
stations.map((station) => (
|
||||
<div
|
||||
key={station.id}
|
||||
onClick={() => handleSelect(station)}
|
||||
className={`px-3 py-2 cursor-pointer hover:bg-gray-700 transition-colors ${
|
||||
selectedStation?.id === station.id ? 'bg-gray-700' : ''
|
||||
}`}
|
||||
>
|
||||
<span className="text-white text-sm md:text-base">{station.name}</span>
|
||||
<span className="text-gray-500 text-xs ml-2">({station.id})</span>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user