feat: create ScheduleSearchPage with week day tabs and flight schedule display

This commit is contained in:
gnezim
2026-04-05 21:20:17 +03:00
parent 8c68d31e12
commit 0dc6732947
3 changed files with 301 additions and 0 deletions
@@ -1 +1,2 @@
export { ScheduleStartPage } from './schedule-start-page'
export { ScheduleSearchPage } from './schedule-search-page'
@@ -0,0 +1,134 @@
.schedule-search-page {
width: 100%;
}
.schedule-search-page__content {
display: flex;
flex-direction: column;
gap: 16px;
}
.schedule-search-page__week-tabs {
display: flex;
gap: 8px;
overflow-x: auto;
padding: 8px 0;
margin-bottom: 12px;
border-bottom: 1px solid #e0e0e0;
&::-webkit-scrollbar {
height: 4px;
}
&::-webkit-scrollbar-track {
background: #f5f5f5;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 2px;
}
}
.schedule-search-page__day-tab {
padding: 8px 12px;
border: none;
background: none;
color: #666;
font-size: 13px;
font-weight: 500;
cursor: pointer;
white-space: nowrap;
border-bottom: 3px solid transparent;
transition: all 0.2s ease;
&:hover {
color: #333;
}
&.active {
color: #1976d2;
border-bottom-color: #1976d2;
}
}
.schedule-search-page__flights {
display: flex;
flex-direction: column;
gap: 12px;
}
.schedule-search-page__flight-item {
padding: 16px;
}
.schedule-search-page__flight {
display: grid;
grid-template-columns: 80px 1fr 120px;
gap: 20px;
align-items: start;
@media (max-width: 768px) {
grid-template-columns: 1fr;
gap: 12px;
}
}
.schedule-search-page__flight-number {
font-weight: 700;
font-size: 18px;
color: #1976d2;
}
.schedule-search-page__flight-times {
display: flex;
align-items: center;
gap: 12px;
}
.schedule-search-page__time {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
min-width: 60px;
}
.schedule-search-page__time-label {
font-size: 10px;
color: #999;
text-transform: uppercase;
}
.schedule-search-page__time-value {
font-weight: 600;
font-size: 14px;
color: #333;
}
.schedule-search-page__time-arrow {
color: #ccc;
font-size: 14px;
}
.schedule-search-page__flight-route {
font-size: 13px;
color: #666;
margin-top: 4px;
}
.schedule-search-page__flight-aircraft {
font-size: 12px;
color: #999;
margin-top: 4px;
padding: 4px 8px;
background: #f5f5f5;
border-radius: 3px;
display: inline-block;
}
.schedule-search-page__flight-frequency {
font-size: 12px;
color: #999;
margin-top: 4px;
}
@@ -0,0 +1,166 @@
import React, { useState, useEffect } from 'react'
import { useParams } from 'react-router-dom'
import axios from 'axios'
import { PageLayout } from '@app/components/page-layout'
import { PageTabs } from '@app/components/page-tabs'
import { PageLoader } from '@app/components/page-loader'
import { PageEmptyList } from '@app/components/page-empty-list'
import { Card } from '@app/components/card'
import { useTranslation } from 'react-i18next'
import { ScheduleFilter } from '../components/schedule-filter'
import './schedule-search-page.scss'
interface ScheduleFlight {
id: string
flightNumber: string
departure: {
airport: string
city: string
time: string
}
arrival: {
airport: string
city: string
time: string
}
aircraft?: string
frequency?: string[]
daysOfWeek?: string[]
}
const DAYS_OF_WEEK = ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN']
export const ScheduleSearchPage: React.FC = () => {
const { params: encodedParams } = useParams<{ params: string }>()
const { t } = useTranslation()
const [selectedDay, setSelectedDay] = useState('MON')
const [flights, setFlights] = useState<ScheduleFlight[]>([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [searchParams, setSearchParams] = useState<any>(null)
useEffect(() => {
if (!encodedParams) return
try {
const decoded = JSON.parse(atob(encodedParams))
setSearchParams(decoded)
} catch (err) {
console.error('Failed to decode params:', err)
setError('Invalid search parameters')
}
}, [encodedParams])
useEffect(() => {
if (!searchParams) return
const fetchSchedules = async () => {
try {
setLoading(true)
setError(null)
const queryParams: Record<string, any> = {
departure: searchParams.departure,
arrival: searchParams.arrival,
day: selectedDay,
}
if (searchParams.days && searchParams.days.length > 0) {
if (!searchParams.days.includes(selectedDay)) {
setFlights([])
setLoading(false)
return
}
}
const response = await axios.get('/api/schedule', { params: queryParams })
setFlights(response.data?.flights || [])
} catch (err) {
console.error('Schedule fetch failed:', err)
setError('Failed to load schedule')
setFlights([])
} finally {
setLoading(false)
}
}
fetchSchedules()
}, [searchParams, selectedDay])
if (error) {
return (
<div className="schedule-search-page" data-testid="schedule-search-page">
<PageTabs />
<PageEmptyList message={error} />
</div>
)
}
return (
<div className="schedule-search-page" data-testid="schedule-search-page">
<PageTabs />
<PageLoader isLoading={loading} />
<PageLayout
contentLeft={<ScheduleFilter />}
stickyContent={
<div className="schedule-search-page__week-tabs" data-testid="week-tabs">
{DAYS_OF_WEEK.map((day) => (
<button
key={day}
className={`schedule-search-page__day-tab ${selectedDay === day ? 'active' : ''}`}
onClick={() => setSelectedDay(day)}
data-testid={`day-tab-${day}`}
>
{t(`SCHEDULE.DAY_${day}`)}
</button>
))}
</div>
}
>
<div className="schedule-search-page__content">
{flights.length === 0 && !loading ? (
<PageEmptyList message="No flights scheduled for this day" />
) : (
<div className="schedule-search-page__flights">
{flights.map((flight) => (
<Card
key={flight.id}
className="schedule-search-page__flight-item"
data-testid={`schedule-flight-${flight.id}`}
>
<div className="schedule-search-page__flight">
<div className="schedule-search-page__flight-number">{flight.flightNumber}</div>
<div className="schedule-search-page__flight-times">
<div className="schedule-search-page__time">
<div className="schedule-search-page__time-label">Dep</div>
<div className="schedule-search-page__time-value">{flight.departure.time}</div>
</div>
<div className="schedule-search-page__time-arrow"></div>
<div className="schedule-search-page__time">
<div className="schedule-search-page__time-label">Arr</div>
<div className="schedule-search-page__time-value">{flight.arrival.time}</div>
</div>
</div>
<div className="schedule-search-page__flight-route">
{flight.departure.airport} {flight.arrival.airport}
</div>
{flight.aircraft && (
<div className="schedule-search-page__flight-aircraft">{flight.aircraft}</div>
)}
{flight.frequency && (
<div className="schedule-search-page__flight-frequency">
{flight.frequency.join(', ')}
</div>
)}
</div>
</Card>
))}
</div>
)}
</div>
</PageLayout>
</div>
)
}