feat: create ScheduleFlightDetailsPage with schedule flight details

This commit is contained in:
gnezim
2026-04-05 21:22:01 +03:00
parent 0dc6732947
commit 4b2a03fb18
3 changed files with 421 additions and 0 deletions
@@ -1,2 +1,3 @@
export { ScheduleStartPage } from './schedule-start-page'
export { ScheduleSearchPage } from './schedule-search-page'
export { ScheduleFlightDetailsPage } from './schedule-flight-details-page'
@@ -0,0 +1,174 @@
.schedule-flight-details-page {
width: 100%;
}
.schedule-flight-details-page__content {
display: flex;
flex-direction: column;
gap: 16px;
}
.schedule-flight-details-page__mini-list {
padding: 16px;
h3 {
margin: 0 0 12px 0;
font-size: 18px;
font-weight: 600;
color: #1976d2;
}
p {
margin: 0 0 8px 0;
font-size: 14px;
color: #666;
&:last-child {
margin-bottom: 0;
}
}
}
.schedule-flight-details-page__header {
padding: 20px;
}
.schedule-flight-details-page__header-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 20px;
@media (max-width: 768px) {
grid-template-columns: repeat(2, 1fr);
}
@media (max-width: 480px) {
grid-template-columns: 1fr;
}
}
.schedule-flight-details-page__header-item {
display: flex;
flex-direction: column;
gap: 8px;
}
.schedule-flight-details-page__header-label {
font-size: 12px;
color: #999;
text-transform: uppercase;
font-weight: 500;
}
.schedule-flight-details-page__header-value {
font-size: 18px;
color: #333;
font-weight: 600;
}
.schedule-flight-details-page__departure,
.schedule-flight-details-page__arrival {
padding: 20px;
h3 {
margin: 0 0 16px 0;
font-size: 16px;
font-weight: 600;
color: #333;
}
}
.schedule-flight-details-page__info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 12px;
@media (max-width: 768px) {
grid-template-columns: repeat(2, 1fr);
}
@media (max-width: 480px) {
grid-template-columns: 1fr;
}
}
.schedule-flight-details-page__info-item {
display: flex;
flex-direction: column;
gap: 4px;
padding: 8px;
background: #f5f5f5;
border-radius: 4px;
}
.schedule-flight-details-page__label {
font-size: 11px;
color: #999;
text-transform: uppercase;
font-weight: 500;
}
.schedule-flight-details-page__value {
font-size: 14px;
color: #333;
font-weight: 500;
display: flex;
flex-direction: column;
gap: 2px;
}
.schedule-flight-details-page__city {
font-size: 12px;
color: #999;
}
.schedule-flight-details-page__schedule {
padding: 20px;
h3 {
margin: 0 0 16px 0;
font-size: 16px;
font-weight: 600;
color: #333;
}
}
.schedule-flight-details-page__days {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.schedule-flight-details-page__day {
padding: 8px 12px;
border: 1px solid #e0e0e0;
border-radius: 4px;
background: white;
font-size: 12px;
font-weight: 500;
color: #666;
text-align: center;
min-width: 50px;
&.active {
background: #1976d2;
color: white;
border-color: #1976d2;
}
}
.schedule-flight-details-page__additional {
padding: 20px;
}
.schedule-flight-details-page__detail-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
}
@@ -0,0 +1,246 @@
import React, { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import axios from 'axios'
import { Button } from 'primereact/button'
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 './schedule-flight-details-page.scss'
interface ScheduleFlightDetails {
id: string
flightNumber: string
operator?: string
aircraft?: {
type: string
registration?: string
}
departure: {
airport: string
city: string
time: string
terminal?: string
gate?: string
}
arrival: {
airport: string
city: string
time: string
terminal?: string
gate?: string
}
daysOfWeek?: string[]
frequency?: string
duration?: string
stops?: number
codeshare?: string[]
remarks?: string
}
const DAYS_OF_WEEK = ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN']
export const ScheduleFlightDetailsPage: React.FC = () => {
const navigate = useNavigate()
const { t } = useTranslation()
const [flight, setFlight] = useState<ScheduleFlightDetails | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
const fetchFlightDetails = async () => {
try {
setLoading(true)
setError(null)
// Get flight ID from URL params or other source
// For now, using a placeholder - would be extracted from route
const response = await axios.get('/api/schedule/flight/1')
setFlight(response.data)
} catch (err) {
console.error('Failed to load flight details:', err)
setError('Failed to load flight information')
} finally {
setLoading(false)
}
}
fetchFlightDetails()
}, [])
const handleBack = () => {
navigate(-1)
}
if (loading) {
return <PageLoader isLoading={true} />
}
if (error || !flight) {
return (
<div className="schedule-flight-details-page" data-testid="schedule-flight-details-page">
<PageTabs />
<PageEmptyList message={error || 'Flight not found'} />
</div>
)
}
return (
<div className="schedule-flight-details-page" data-testid="schedule-flight-details-page">
<PageTabs />
<PageLayout
headerLeft={
<Button
icon="pi pi-arrow-left"
label={t('SHARED.BACK')}
onClick={handleBack}
className="p-button-text"
data-testid="back-button"
/>
}
contentLeft={
<Card className="schedule-flight-details-page__mini-list" data-testid="flight-mini-list">
<h3>{flight.flightNumber}</h3>
<p>{flight.departure.airport} {flight.arrival.airport}</p>
{flight.frequency && <p>{flight.frequency}</p>}
</Card>
}
>
<div className="schedule-flight-details-page__content">
{/* Header Card */}
<Card className="schedule-flight-details-page__header" data-testid="flight-header-card">
<div className="schedule-flight-details-page__header-grid">
<div className="schedule-flight-details-page__header-item">
<span className="schedule-flight-details-page__header-label">{t('SHARED.FLIGHT')}</span>
<span className="schedule-flight-details-page__header-value">{flight.flightNumber}</span>
</div>
{flight.operator && (
<div className="schedule-flight-details-page__header-item">
<span className="schedule-flight-details-page__header-label">{t('SHARED.OPERATOR')}</span>
<span className="schedule-flight-details-page__header-value">{flight.operator}</span>
</div>
)}
{flight.aircraft && (
<div className="schedule-flight-details-page__header-item">
<span className="schedule-flight-details-page__header-label">{t('SHARED.AIRCRAFT')}</span>
<span className="schedule-flight-details-page__header-value">{flight.aircraft.type}</span>
</div>
)}
{flight.duration && (
<div className="schedule-flight-details-page__header-item">
<span className="schedule-flight-details-page__header-label">{t('SCHEDULE.DURATION')}</span>
<span className="schedule-flight-details-page__header-value">{flight.duration}</span>
</div>
)}
</div>
</Card>
{/* Departure Section */}
<Card className="schedule-flight-details-page__departure" data-testid="departure-card">
<h3>{t('SHARED.DEPARTURE')}</h3>
<div className="schedule-flight-details-page__info-grid">
<div className="schedule-flight-details-page__info-item">
<span className="schedule-flight-details-page__label">{t('SHARED.AIRPORT')}</span>
<span className="schedule-flight-details-page__value">
{flight.departure.airport}
<span className="schedule-flight-details-page__city">{flight.departure.city}</span>
</span>
</div>
<div className="schedule-flight-details-page__info-item">
<span className="schedule-flight-details-page__label">{t('SHARED.TIME')}</span>
<span className="schedule-flight-details-page__value">{flight.departure.time}</span>
</div>
{flight.departure.terminal && (
<div className="schedule-flight-details-page__info-item">
<span className="schedule-flight-details-page__label">{t('SHARED.TERMINAL')}</span>
<span className="schedule-flight-details-page__value">{flight.departure.terminal}</span>
</div>
)}
{flight.departure.gate && (
<div className="schedule-flight-details-page__info-item">
<span className="schedule-flight-details-page__label">{t('SHARED.GATE')}</span>
<span className="schedule-flight-details-page__value">{flight.departure.gate}</span>
</div>
)}
</div>
</Card>
{/* Arrival Section */}
<Card className="schedule-flight-details-page__arrival" data-testid="arrival-card">
<h3>{t('SHARED.ARRIVAL')}</h3>
<div className="schedule-flight-details-page__info-grid">
<div className="schedule-flight-details-page__info-item">
<span className="schedule-flight-details-page__label">{t('SHARED.AIRPORT')}</span>
<span className="schedule-flight-details-page__value">
{flight.arrival.airport}
<span className="schedule-flight-details-page__city">{flight.arrival.city}</span>
</span>
</div>
<div className="schedule-flight-details-page__info-item">
<span className="schedule-flight-details-page__label">{t('SHARED.TIME')}</span>
<span className="schedule-flight-details-page__value">{flight.arrival.time}</span>
</div>
{flight.arrival.terminal && (
<div className="schedule-flight-details-page__info-item">
<span className="schedule-flight-details-page__label">{t('SHARED.TERMINAL')}</span>
<span className="schedule-flight-details-page__value">{flight.arrival.terminal}</span>
</div>
)}
{flight.arrival.gate && (
<div className="schedule-flight-details-page__info-item">
<span className="schedule-flight-details-page__label">{t('SHARED.GATE')}</span>
<span className="schedule-flight-details-page__value">{flight.arrival.gate}</span>
</div>
)}
</div>
</Card>
{/* Schedule Section */}
<Card className="schedule-flight-details-page__schedule" data-testid="schedule-card">
<h3>{t('SCHEDULE.OPERATING_DAYS')}</h3>
<div className="schedule-flight-details-page__days">
{DAYS_OF_WEEK.map((day) => (
<span
key={day}
className={`schedule-flight-details-page__day ${
flight.daysOfWeek?.includes(day) ? 'active' : ''
}`}
data-testid={`day-${day}`}
>
{day}
</span>
))}
</div>
</Card>
{/* Additional Info */}
{(flight.stops !== undefined || flight.codeshare || flight.remarks) && (
<Card className="schedule-flight-details-page__additional" data-testid="additional-card">
{flight.stops !== undefined && (
<div className="schedule-flight-details-page__detail-item">
<span className="schedule-flight-details-page__label">{t('SCHEDULE.STOPS')}</span>
<span className="schedule-flight-details-page__value">{flight.stops}</span>
</div>
)}
{flight.codeshare && flight.codeshare.length > 0 && (
<div className="schedule-flight-details-page__detail-item">
<span className="schedule-flight-details-page__label">{t('SCHEDULE.CODESHARE')}</span>
<span className="schedule-flight-details-page__value">{flight.codeshare.join(', ')}</span>
</div>
)}
{flight.remarks && (
<div className="schedule-flight-details-page__detail-item">
<span className="schedule-flight-details-page__label">{t('SHARED.REMARKS')}</span>
<span className="schedule-flight-details-page__value">{flight.remarks}</span>
</div>
)}
</Card>
)}
</div>
</PageLayout>
</div>
)
}