feat: create ScheduleFlightDetailsPage with schedule flight details
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user