feat: rework to use next and host from single dockerfile
This commit is contained in:
106
mta-sign-ui/components/Header.tsx
Normal file
106
mta-sign-ui/components/Header.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
'use client'
|
||||
|
||||
import { useRef } from 'react'
|
||||
import Image from 'next/image'
|
||||
import { ArrowDownTrayIcon, ArrowUpTrayIcon } from '@heroicons/react/24/outline'
|
||||
import { AppConfig } from '@/types/config'
|
||||
|
||||
interface HeaderProps {
|
||||
startTime: string | null
|
||||
lastUpdated: string | null
|
||||
onExportConfig: () => AppConfig
|
||||
onImportConfig: (config: AppConfig) => void
|
||||
}
|
||||
|
||||
export default function Header({ startTime, lastUpdated, onExportConfig, onImportConfig }: HeaderProps) {
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const handleExport = () => {
|
||||
const config = onExportConfig()
|
||||
const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = 'mta-sign-config.json'
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
const handleImport = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0]
|
||||
if (!file) return
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const config = JSON.parse(e.target?.result as string) as AppConfig
|
||||
onImportConfig(config)
|
||||
} catch (err) {
|
||||
console.error('Failed to parse config file:', err)
|
||||
alert('Invalid config file')
|
||||
}
|
||||
}
|
||||
reader.readAsText(file)
|
||||
|
||||
// Reset input so same file can be imported again
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<nav className="bg-gray-900 w-full px-4 py-3 md:px-6 md:py-4">
|
||||
<div className="flex flex-col lg:flex-row items-center justify-between gap-2 lg:gap-4">
|
||||
<div className="flex items-center gap-2 md:gap-4">
|
||||
<Image
|
||||
src="/images/RPI-LOGO.png"
|
||||
alt="Raspberry Pi Logo"
|
||||
width={50}
|
||||
height={50}
|
||||
className="w-8 h-8 md:w-10 md:h-10 lg:w-12 lg:h-12"
|
||||
/>
|
||||
<h1 className="text-2xl md:text-4xl lg:text-5xl xl:text-6xl font-bold text-white">
|
||||
Pi MTA Display!
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={handleExport}
|
||||
className="p-2 rounded-lg bg-gray-700 hover:bg-gray-600 transition-colors"
|
||||
title="Export config"
|
||||
>
|
||||
<ArrowDownTrayIcon className="w-5 h-5 text-white" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="p-2 rounded-lg bg-gray-700 hover:bg-gray-600 transition-colors"
|
||||
title="Import config"
|
||||
>
|
||||
<ArrowUpTrayIcon className="w-5 h-5 text-white" />
|
||||
</button>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".json"
|
||||
onChange={handleImport}
|
||||
className="hidden"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center lg:items-end text-sm md:text-base lg:text-lg xl:text-xl">
|
||||
<div className="text-white">
|
||||
<span className="font-semibold">Last Updated:</span>{' '}
|
||||
<span>{lastUpdated || 'Loading...'}</span>
|
||||
</div>
|
||||
<div className="text-white">
|
||||
<span className="font-semibold">Started:</span>{' '}
|
||||
<span>{startTime || 'Loading...'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {fetchStartDate} from "@/services/mta-api/mta-server";
|
||||
import {MtaStartTime} from "@/services/mta-api/types";
|
||||
import Image from 'next/image';
|
||||
|
||||
const Header = () => {
|
||||
const [startDate, setStartDate] = useState<MtaStartTime | null>(null);
|
||||
const [lastUpdatedDate, setLastUpdatedDate] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
console.log("CALLING API")
|
||||
const mtaData = await fetchStartDate([""])
|
||||
setStartDate(mtaData)
|
||||
setLastUpdatedDate(new Date().toLocaleString("en-US"))
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="align-middle lg:flex w-full">
|
||||
<div className="flex align-middle justify-center lg:justify-normal">
|
||||
<div className="align-middle p-0.5">
|
||||
|
||||
<div style={{width: '100%', aspectRatio: '16/9'}} className="h-5 lg:h-6">
|
||||
<Image src="/images/RPI-LOGO.png" alt="rpi-logo" width="160" height="90" className="w-11"/>
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="lg:text-left text-center mb-4 text-5xl font-extrabold leading-none tracking-tight text-gray-900 lg:text-6xl dark:text-white ">
|
||||
Pi MTA Display!
|
||||
</h1>
|
||||
</div>
|
||||
<div className="lg:flex-grow"></div>
|
||||
<div className="lg:text-right text-center lg:p-2">
|
||||
|
||||
{startDate ? (
|
||||
<h2 className="text-lg lg:text-xl font-bold dark:text-white">Started
|
||||
At: <span>{startDate.startTime.toLocaleString("en-US")}</span></h2>
|
||||
|
||||
) : (
|
||||
<p>Loading data...</p>
|
||||
)}
|
||||
|
||||
<h2 className="text-lg lg:text-xl font-bold dark:text-white">Updated
|
||||
At: <span>{lastUpdatedDate}</span></h2>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
42
mta-sign-ui/components/trains/DirectionCard.tsx
Normal file
42
mta-sign-ui/components/trains/DirectionCard.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
'use client'
|
||||
|
||||
import TrainLine from './TrainLine'
|
||||
|
||||
interface Route {
|
||||
routeId: string
|
||||
arrival_times: number[]
|
||||
}
|
||||
|
||||
interface DirectionCardProps {
|
||||
direction: 'North' | 'South'
|
||||
routes: Route[]
|
||||
}
|
||||
|
||||
const DirectionCard = ({ direction, routes }: DirectionCardProps) => {
|
||||
return (
|
||||
<div className="bg-gray-800 overflow-hidden flex-1">
|
||||
<div className="bg-gray-700 px-3 py-2 md:px-4 md:py-3 lg:px-6 lg:py-4">
|
||||
<h2 className="text-xl md:text-2xl lg:text-3xl xl:text-4xl font-bold text-white">
|
||||
{direction}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="divide-y divide-gray-700">
|
||||
{routes && routes.length > 0 ? (
|
||||
routes.map((route, index) => (
|
||||
<TrainLine
|
||||
key={`${route.routeId}-${index}`}
|
||||
routeId={route.routeId}
|
||||
arrivalTimes={route.arrival_times}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="p-4 text-gray-400 text-center">
|
||||
No trains available
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DirectionCard
|
||||
272
mta-sign-ui/components/trains/StationCard.tsx
Normal file
272
mta-sign-ui/components/trains/StationCard.tsx
Normal file
@@ -0,0 +1,272 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react'
|
||||
import { XMarkIcon } from '@heroicons/react/24/outline'
|
||||
import axios from 'axios'
|
||||
import StationSelector, { Station } from '../StationSelector'
|
||||
import DirectionCard from './DirectionCard'
|
||||
import { StationConfig } from '@/types/config'
|
||||
|
||||
interface Route {
|
||||
routeId: string
|
||||
arrival_times: number[]
|
||||
}
|
||||
|
||||
interface StationData {
|
||||
stationId: string
|
||||
routes: Route[]
|
||||
}
|
||||
|
||||
interface StationCardProps {
|
||||
configId: string
|
||||
initialConfig?: StationConfig
|
||||
availableLines: string[]
|
||||
onConfigChange?: (configId: string, config: StationConfig) => void
|
||||
onRemove?: () => void
|
||||
}
|
||||
|
||||
export default function StationCard({ configId, initialConfig, availableLines, onConfigChange, onRemove }: StationCardProps) {
|
||||
const [selectedStation, setSelectedStation] = useState<Station | null>(
|
||||
initialConfig ? { id: initialConfig.stationId, name: initialConfig.stationName } : null
|
||||
)
|
||||
const [showNorth, setShowNorth] = useState(initialConfig?.showNorth ?? true)
|
||||
const [showSouth, setShowSouth] = useState(initialConfig?.showSouth ?? true)
|
||||
const [selectedLines, setSelectedLines] = useState<Set<string>>(
|
||||
new Set(initialConfig?.selectedLines ?? availableLines)
|
||||
)
|
||||
const [northData, setNorthData] = useState<StationData | null>(null)
|
||||
const [southData, setSouthData] = useState<StationData | null>(null)
|
||||
|
||||
const notifyConfigChange = useCallback(() => {
|
||||
if (onConfigChange && selectedStation) {
|
||||
onConfigChange(configId, {
|
||||
id: configId,
|
||||
stationId: selectedStation.id,
|
||||
stationName: selectedStation.name,
|
||||
showNorth,
|
||||
showSouth,
|
||||
selectedLines: Array.from(selectedLines),
|
||||
})
|
||||
}
|
||||
}, [configId, onConfigChange, selectedStation, showNorth, showSouth, selectedLines])
|
||||
|
||||
useEffect(() => {
|
||||
notifyConfigChange()
|
||||
}, [notifyConfigChange])
|
||||
|
||||
const fetchStationData = useCallback(async () => {
|
||||
if (!selectedStation) return
|
||||
|
||||
try {
|
||||
if (showNorth) {
|
||||
const response = await axios.post(`/api/mta/${selectedStation.id}N`)
|
||||
setNorthData(response.data)
|
||||
}
|
||||
if (showSouth) {
|
||||
const response = await axios.post(`/api/mta/${selectedStation.id}S`)
|
||||
setSouthData(response.data)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching station data:', err)
|
||||
}
|
||||
}, [selectedStation, showNorth, showSouth])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedStation) {
|
||||
fetchStationData()
|
||||
const interval = setInterval(fetchStationData, 5000)
|
||||
return () => clearInterval(interval)
|
||||
}
|
||||
}, [selectedStation, fetchStationData])
|
||||
|
||||
const availableDirections = useMemo(() => {
|
||||
const directions = new Set<string>()
|
||||
if (northData && northData.routes.length > 0) directions.add('North')
|
||||
if (southData && southData.routes.length > 0) directions.add('South')
|
||||
return directions
|
||||
}, [northData, southData])
|
||||
|
||||
const stationAvailableLines = useMemo(() => {
|
||||
const lines = new Set<string>()
|
||||
if (northData) {
|
||||
northData.routes.forEach(route => {
|
||||
const lineId = route.routeId.replace('Route.', '')
|
||||
lines.add(lineId)
|
||||
})
|
||||
}
|
||||
if (southData) {
|
||||
southData.routes.forEach(route => {
|
||||
const lineId = route.routeId.replace('Route.', '')
|
||||
lines.add(lineId)
|
||||
})
|
||||
}
|
||||
return Array.from(lines).sort()
|
||||
}, [northData, southData])
|
||||
|
||||
const toggleLine = (line: string) => {
|
||||
const newSelected = new Set(selectedLines)
|
||||
if (newSelected.has(line)) {
|
||||
newSelected.delete(line)
|
||||
} else {
|
||||
newSelected.add(line)
|
||||
}
|
||||
setSelectedLines(newSelected)
|
||||
}
|
||||
|
||||
const toggleDirection = (direction: 'North' | 'South') => {
|
||||
if (direction === 'North') {
|
||||
setShowNorth(!showNorth)
|
||||
} else {
|
||||
setShowSouth(!showSouth)
|
||||
}
|
||||
}
|
||||
|
||||
const filterRoutes = (routes: Route[]): Route[] => {
|
||||
return routes.filter(route => {
|
||||
const lineId = route.routeId.replace('Route.', '')
|
||||
return selectedLines.has(lineId)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-gray-800 overflow-hidden">
|
||||
<Header
|
||||
selectedStation={selectedStation}
|
||||
onSelectStation={setSelectedStation}
|
||||
showNorth={showNorth}
|
||||
showSouth={showSouth}
|
||||
selectedLines={selectedLines}
|
||||
availableDirections={availableDirections}
|
||||
availableLines={stationAvailableLines}
|
||||
onToggleDirection={toggleDirection}
|
||||
onToggleLine={toggleLine}
|
||||
onRemove={onRemove}
|
||||
/>
|
||||
|
||||
<Content
|
||||
selectedStation={selectedStation}
|
||||
showNorth={showNorth}
|
||||
showSouth={showSouth}
|
||||
northRoutes={northData ? filterRoutes(northData.routes || []) : []}
|
||||
southRoutes={southData ? filterRoutes(southData.routes || []) : []}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface HeaderProps {
|
||||
selectedStation: Station | null
|
||||
onSelectStation: (station: Station) => void
|
||||
showNorth: boolean
|
||||
showSouth: boolean
|
||||
selectedLines: Set<string>
|
||||
availableDirections: Set<string>
|
||||
availableLines: string[]
|
||||
onToggleDirection: (direction: 'North' | 'South') => void
|
||||
onToggleLine: (line: string) => void
|
||||
onRemove?: () => void
|
||||
}
|
||||
|
||||
function Header({
|
||||
selectedStation,
|
||||
onSelectStation,
|
||||
showNorth,
|
||||
showSouth,
|
||||
selectedLines,
|
||||
availableDirections,
|
||||
availableLines,
|
||||
onToggleDirection,
|
||||
onToggleLine,
|
||||
onRemove,
|
||||
}: HeaderProps) {
|
||||
return (
|
||||
<div className="bg-gray-700 px-3 py-2 md:px-4 md:py-3 flex items-center gap-2 md:gap-4">
|
||||
<div className="flex-1">
|
||||
<StationSelector selectedStation={selectedStation} onSelect={onSelectStation} />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 md:gap-3 shrink-0">
|
||||
{availableDirections.has('North') && (
|
||||
<label className="flex items-center gap-1 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={showNorth}
|
||||
onChange={() => onToggleDirection('North')}
|
||||
className="w-4 h-4 rounded"
|
||||
/>
|
||||
<span className="text-white text-xs md:text-sm">N</span>
|
||||
</label>
|
||||
)}
|
||||
|
||||
{availableDirections.has('South') && (
|
||||
<label className="flex items-center gap-1 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={showSouth}
|
||||
onChange={() => onToggleDirection('South')}
|
||||
className="w-4 h-4 rounded"
|
||||
/>
|
||||
<span className="text-white text-xs md:text-sm">S</span>
|
||||
</label>
|
||||
)}
|
||||
|
||||
<div className="flex gap-1 md:gap-2">
|
||||
{availableLines.map(line => (
|
||||
<button
|
||||
key={line}
|
||||
onClick={() => onToggleLine(line)}
|
||||
className={`w-6 h-6 md:w-7 md:h-7 rounded-full text-xs font-bold transition-all ${
|
||||
selectedLines.has(line) ? 'bg-blue-600 text-white' : 'bg-gray-600 text-gray-400'
|
||||
}`}
|
||||
>
|
||||
{line}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{onRemove && (
|
||||
<button
|
||||
onClick={onRemove}
|
||||
className="p-2 rounded-lg bg-red-600 hover:bg-red-500 transition-colors shrink-0"
|
||||
title="Remove station"
|
||||
>
|
||||
<XMarkIcon className="w-4 h-4 md:w-5 md:h-5 text-white" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface ContentProps {
|
||||
selectedStation: Station | null
|
||||
showNorth: boolean
|
||||
showSouth: boolean
|
||||
northRoutes: Route[]
|
||||
southRoutes: Route[]
|
||||
}
|
||||
|
||||
function Content({ selectedStation, showNorth, showSouth, northRoutes, southRoutes }: ContentProps) {
|
||||
if (!selectedStation) {
|
||||
return (
|
||||
<div className="p-8 text-center text-gray-400">
|
||||
Select a station to view train arrivals
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row">
|
||||
{showNorth && (
|
||||
<div className={`flex-1 ${showSouth ? 'border-b md:border-b-0 md:border-r border-gray-700' : ''}`}>
|
||||
<DirectionCard direction="North" routes={northRoutes} />
|
||||
</div>
|
||||
)}
|
||||
{showSouth && (
|
||||
<div className="flex-1">
|
||||
<DirectionCard direction="South" routes={southRoutes} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
43
mta-sign-ui/components/trains/TrainLine.tsx
Normal file
43
mta-sign-ui/components/trains/TrainLine.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
'use client'
|
||||
|
||||
import Image from 'next/image'
|
||||
|
||||
interface TrainLineProps {
|
||||
routeId: string
|
||||
arrivalTimes: number[]
|
||||
}
|
||||
|
||||
const TrainLine = ({ routeId, arrivalTimes }: TrainLineProps) => {
|
||||
const formatTimes = (times: number[]): string => {
|
||||
if (!times || times.length === 0) {
|
||||
return 'No trains'
|
||||
}
|
||||
return times
|
||||
.sort((a, b) => a - b)
|
||||
.join(', ')
|
||||
}
|
||||
|
||||
const getLineImage = (route: string): string => {
|
||||
const cleanRoute = route.replace('Route.', '')
|
||||
return `/images/lines/${cleanRoute}.svg`
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between p-2 md:p-3 lg:p-4 border-b border-gray-700 last:border-b-0 h-16 md:h-20 lg:h-24">
|
||||
<div className="flex items-center gap-2 md:gap-3 lg:gap-4 shrink-0">
|
||||
<Image
|
||||
src={getLineImage(routeId)}
|
||||
alt={`${routeId} line`}
|
||||
width={60}
|
||||
height={60}
|
||||
className="w-8 h-8 md:w-12 md:h-12 lg:w-14 lg:h-14 xl:w-16 xl:h-16"
|
||||
/>
|
||||
</div>
|
||||
<h2 className="text-xl md:text-2xl lg:text-[2.5rem] font-bold text-white text-right flex-1 ml-4 whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
{formatTimes(arrivalTimes)}
|
||||
</h2>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TrainLine
|
||||
@@ -1,33 +0,0 @@
|
||||
'use client'
|
||||
import React, {useState} from 'react';
|
||||
// import {fetchStartDate} from "@/services/mta-api/mta-server";
|
||||
import {MtaStartTime} from "@/services/mta-api/types";
|
||||
// import Image from 'next/image';
|
||||
import {RouteResponse} from "@/gen-sources/mta-sign-api";
|
||||
|
||||
const Line = (props: RouteResponse) => {
|
||||
// const [data, setData] = useState<MtaStartTime | null>(null);
|
||||
//
|
||||
// useEffect(() => {
|
||||
// const fetchData = async () => {
|
||||
// try {
|
||||
// console.log("CALLING API")
|
||||
// const mtaData = await fetchStartDate([""])
|
||||
// setData(mtaData)
|
||||
//
|
||||
// } catch (error) {
|
||||
// console.error('Error fetching data:', error);
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// fetchData();
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<div className="align-middle lg:flex w-full">
|
||||
TRAIN LINE HERE - {props.arrival_times.toJSON().toString()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Line;
|
||||
@@ -1,45 +0,0 @@
|
||||
'use client'
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {AllStationResponse, StationResponse} from "@/gen-sources/mta-sign-api";
|
||||
import {mtaDataClient} from "@/services/mta-api/types";
|
||||
const Station = () => {
|
||||
const [data, setData] = useState<AllStationResponse | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
console.log("CALLING API")
|
||||
const mtaData = await mtaDataClient.getAllApiMtaPost()
|
||||
setData(mtaData.data)
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="align-middle lg:flex w-full">
|
||||
<div className="lg:flex-grow"></div>
|
||||
<div className="lg:text-right text-center lg:p-2">
|
||||
|
||||
{data ? (
|
||||
<h2 className="text-lg lg:text-xl font-bold dark:text-white">
|
||||
{data.stations.map(function (station:any, i:any) {
|
||||
return <span key={i}>{station.stationId}</span>
|
||||
})}
|
||||
Train Line <span>{}</span>
|
||||
</h2>
|
||||
|
||||
) : (
|
||||
<p>Loading data...</p>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Station;
|
||||
Reference in New Issue
Block a user