feat: create FlightDetailsPage with full flight information
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
.flight-details-page {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.flight-details-page__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flight-details-page__header {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.flight-details-page__header-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.flight-details-page__header-label {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.flight-details-page__header-value {
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.flight-details-page__details {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.flight-details-page__crew {
|
||||
padding: 20px;
|
||||
|
||||
h3 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.flight-details-page__crew-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 16px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.flight-details-page__crew-label {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.flight-details-page__crew-value {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.flight-details-page__seats {
|
||||
padding: 20px;
|
||||
|
||||
h3 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.flight-details-page__seats-info {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.flight-details-page__seats-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: 12px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.flight-details-page__seats-label {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.flight-details-page__seats-value {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.flight-details-page__amenities {
|
||||
padding: 20px;
|
||||
|
||||
h3 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.flight-details-page__amenities-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.flight-details-page__amenity-tag {
|
||||
padding: 6px 12px;
|
||||
background: #e3f2fd;
|
||||
color: #1565c0;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { useParams, 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 { BoardFlightBody } from '../components/board-flight-body'
|
||||
import './flight-details-page.scss'
|
||||
|
||||
interface FlightDetails {
|
||||
id: string
|
||||
flightNumber: string
|
||||
status: string
|
||||
operator?: string
|
||||
aircraft?: string
|
||||
departure: any
|
||||
arrival: any
|
||||
boarding?: any
|
||||
deboarding?: any
|
||||
codesharing?: string[]
|
||||
amenities?: string[]
|
||||
crew?: {
|
||||
captain?: string
|
||||
firstOfficer?: string
|
||||
flightAttendants?: number
|
||||
}
|
||||
seats?: {
|
||||
total: number
|
||||
available: number
|
||||
}
|
||||
}
|
||||
|
||||
export const FlightDetailsPage: React.FC = () => {
|
||||
const { params: encodedParams } = useParams<{ params: string }>()
|
||||
const navigate = useNavigate()
|
||||
const { t } = useTranslation()
|
||||
const [flight, setFlight] = useState<FlightDetails | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!encodedParams) return
|
||||
|
||||
const fetchFlightDetails = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
|
||||
const decoded = JSON.parse(atob(encodedParams))
|
||||
const flightId = decoded.id
|
||||
|
||||
// Fetch flight details from API
|
||||
const response = await axios.get(`/api/flights/${flightId}`)
|
||||
setFlight(response.data)
|
||||
} catch (err) {
|
||||
console.error('Failed to load flight details:', err)
|
||||
setError('Failed to load flight information')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
fetchFlightDetails()
|
||||
}, [encodedParams])
|
||||
|
||||
const handleBack = () => {
|
||||
navigate(-1)
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return <PageLoader isLoading={true} />
|
||||
}
|
||||
|
||||
if (error || !flight) {
|
||||
return (
|
||||
<div className="flight-details-page" data-testid="flight-details-page">
|
||||
<PageTabs />
|
||||
<PageEmptyList message={error || 'Flight not found'} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flight-details-page" data-testid="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="flight-details-page__mini-list" data-testid="flight-mini-list">
|
||||
<h3>{flight.flightNumber}</h3>
|
||||
<p>{flight.departure.airport} → {flight.arrival.airport}</p>
|
||||
<p>{flight.status}</p>
|
||||
</Card>
|
||||
}
|
||||
>
|
||||
<div className="flight-details-page__content">
|
||||
{/* Flight Header Info */}
|
||||
<Card className="flight-details-page__header" data-testid="flight-header-card">
|
||||
<div className="flight-details-page__header-grid">
|
||||
<div className="flight-details-page__header-item">
|
||||
<span className="flight-details-page__header-label">{t('SHARED.FLIGHT')}</span>
|
||||
<span className="flight-details-page__header-value">{flight.flightNumber}</span>
|
||||
</div>
|
||||
<div className="flight-details-page__header-item">
|
||||
<span className="flight-details-page__header-label">{t('SHARED.STATUS')}</span>
|
||||
<span className="flight-details-page__header-value">{flight.status}</span>
|
||||
</div>
|
||||
{flight.aircraft && (
|
||||
<div className="flight-details-page__header-item">
|
||||
<span className="flight-details-page__header-label">{t('SHARED.AIRCRAFT')}</span>
|
||||
<span className="flight-details-page__header-value">{flight.aircraft}</span>
|
||||
</div>
|
||||
)}
|
||||
{flight.operator && (
|
||||
<div className="flight-details-page__header-item">
|
||||
<span className="flight-details-page__header-label">{t('SHARED.OPERATOR')}</span>
|
||||
<span className="flight-details-page__header-value">{flight.operator}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Flight Details */}
|
||||
<Card className="flight-details-page__details" data-testid="flight-details-card">
|
||||
<BoardFlightBody flight={flight} />
|
||||
</Card>
|
||||
|
||||
{/* Crew Information */}
|
||||
{flight.crew && (
|
||||
<Card className="flight-details-page__crew" data-testid="crew-info-card">
|
||||
<h3>{t('BOARD.CREW_INFORMATION')}</h3>
|
||||
<div className="flight-details-page__crew-grid">
|
||||
{flight.crew.captain && (
|
||||
<div>
|
||||
<span className="flight-details-page__crew-label">{t('BOARD.CAPTAIN')}</span>
|
||||
<span className="flight-details-page__crew-value">{flight.crew.captain}</span>
|
||||
</div>
|
||||
)}
|
||||
{flight.crew.firstOfficer && (
|
||||
<div>
|
||||
<span className="flight-details-page__crew-label">{t('BOARD.FIRST_OFFICER')}</span>
|
||||
<span className="flight-details-page__crew-value">{flight.crew.firstOfficer}</span>
|
||||
</div>
|
||||
)}
|
||||
{flight.crew.flightAttendants && (
|
||||
<div>
|
||||
<span className="flight-details-page__crew-label">{t('BOARD.FLIGHT_ATTENDANTS')}</span>
|
||||
<span className="flight-details-page__crew-value">{flight.crew.flightAttendants}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Seat Information */}
|
||||
{flight.seats && (
|
||||
<Card className="flight-details-page__seats" data-testid="seats-info-card">
|
||||
<h3>{t('BOARD.SEATS')}</h3>
|
||||
<div className="flight-details-page__seats-info">
|
||||
<div className="flight-details-page__seats-item">
|
||||
<span className="flight-details-page__seats-label">{t('BOARD.TOTAL_SEATS')}</span>
|
||||
<span className="flight-details-page__seats-value">{flight.seats.total}</span>
|
||||
</div>
|
||||
<div className="flight-details-page__seats-item">
|
||||
<span className="flight-details-page__seats-label">{t('BOARD.AVAILABLE_SEATS')}</span>
|
||||
<span className="flight-details-page__seats-value">{flight.seats.available}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Amenities */}
|
||||
{flight.amenities && flight.amenities.length > 0 && (
|
||||
<Card className="flight-details-page__amenities" data-testid="amenities-card">
|
||||
<h3>{t('BOARD.AMENITIES')}</h3>
|
||||
<div className="flight-details-page__amenities-list">
|
||||
{flight.amenities.map((amenity, idx) => (
|
||||
<span key={idx} className="flight-details-page__amenity-tag">
|
||||
{amenity}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</PageLayout>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export { OnlineBoardStartPage } from './online-board-start-page'
|
||||
export { OnlineBoardSearchPage } from './online-board-search-page'
|
||||
export { FlightDetailsPage } from './flight-details-page'
|
||||
|
||||
Reference in New Issue
Block a user