feat: create BoardSearchResult with flight list and header/body components
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
.board-flight-body {
|
||||||
|
padding: 16px;
|
||||||
|
background: #fafafa;
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-flight-body__section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import './board-flight-body.scss'
|
||||||
|
|
||||||
|
export interface BoardFlightBodyProps {
|
||||||
|
flight: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BoardFlightBody: React.FC<BoardFlightBodyProps> = ({ flight }) => {
|
||||||
|
return (
|
||||||
|
<div className="board-flight-body" data-testid={`flight-body-${flight.id}`}>
|
||||||
|
<div className="board-flight-body__section">
|
||||||
|
<h3>Departure</h3>
|
||||||
|
<p>{flight.departure.airport} - {flight.departure.city}</p>
|
||||||
|
</div>
|
||||||
|
<div className="board-flight-body__section">
|
||||||
|
<h3>Arrival</h3>
|
||||||
|
<p>{flight.arrival.airport} - {flight.arrival.city}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
.board-flight-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--expanded {
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-flight-header__content {
|
||||||
|
flex: 1;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 80px 100px 1fr 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-flight-header__number {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #1976d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-flight-header__status {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #e8f5e9;
|
||||||
|
color: #2e7d32;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-flight-header__times {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
|
||||||
|
span:first-child,
|
||||||
|
span:last-child {
|
||||||
|
min-width: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-flight-header__route {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-flight-header__arrow {
|
||||||
|
margin-left: 16px;
|
||||||
|
color: #999;
|
||||||
|
font-size: 12px;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import './board-flight-header.scss'
|
||||||
|
|
||||||
|
export interface BoardFlightHeaderProps {
|
||||||
|
flight: any
|
||||||
|
isExpanded: boolean
|
||||||
|
onToggle: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BoardFlightHeader: React.FC<BoardFlightHeaderProps> = ({
|
||||||
|
flight,
|
||||||
|
isExpanded,
|
||||||
|
onToggle,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`board-flight-header ${isExpanded ? 'board-flight-header--expanded' : ''}`}
|
||||||
|
onClick={onToggle}
|
||||||
|
data-testid={`flight-header-${flight.id}`}
|
||||||
|
>
|
||||||
|
<div className="board-flight-header__content">
|
||||||
|
<div className="board-flight-header__number">{flight.flightNumber}</div>
|
||||||
|
<div className="board-flight-header__status">{flight.status}</div>
|
||||||
|
<div className="board-flight-header__times">
|
||||||
|
<span>{flight.departure.scheduled}</span>
|
||||||
|
<span>→</span>
|
||||||
|
<span>{flight.arrival.scheduled}</span>
|
||||||
|
</div>
|
||||||
|
<div className="board-flight-header__route">
|
||||||
|
{flight.departure.city} → {flight.arrival.city}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="board-flight-header__arrow">
|
||||||
|
{isExpanded ? '▼' : '▶'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
.board-search-result {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-search-result__item {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-search-result__flight {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
import { Card } from '@app/components/card'
|
||||||
|
import { BoardFlightHeader } from './board-flight-header'
|
||||||
|
import { BoardFlightBody } from './board-flight-body'
|
||||||
|
import './board-search-result.scss'
|
||||||
|
|
||||||
|
export interface Flight {
|
||||||
|
id: string
|
||||||
|
flightNumber: string
|
||||||
|
status: string
|
||||||
|
operator?: string
|
||||||
|
aircraft?: string
|
||||||
|
departure: {
|
||||||
|
airport: string
|
||||||
|
city: string
|
||||||
|
scheduled: string
|
||||||
|
actual?: string
|
||||||
|
terminal?: string
|
||||||
|
gate?: string
|
||||||
|
}
|
||||||
|
arrival: {
|
||||||
|
airport: string
|
||||||
|
city: string
|
||||||
|
scheduled: string
|
||||||
|
actual?: string
|
||||||
|
terminal?: string
|
||||||
|
gate?: string
|
||||||
|
}
|
||||||
|
codesharing?: string[]
|
||||||
|
boarding?: {
|
||||||
|
time: string
|
||||||
|
gate: string
|
||||||
|
}
|
||||||
|
deboarding?: {
|
||||||
|
time: string
|
||||||
|
gate: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BoardSearchResultProps {
|
||||||
|
flights: Flight[]
|
||||||
|
'data-testid'?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BoardSearchResult: React.FC<BoardSearchResultProps> = ({
|
||||||
|
flights,
|
||||||
|
'data-testid': dataTestId,
|
||||||
|
}) => {
|
||||||
|
const [expandedFlightId, setExpandedFlightId] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const toggleExpanded = (flightId: string) => {
|
||||||
|
setExpandedFlightId(expandedFlightId === flightId ? null : flightId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="board-search-result" data-testid={dataTestId || 'board-search-result'}>
|
||||||
|
{flights.map((flight) => (
|
||||||
|
<Card
|
||||||
|
key={flight.id}
|
||||||
|
className="board-search-result__item"
|
||||||
|
data-testid={`flight-item-${flight.id}`}
|
||||||
|
>
|
||||||
|
<div className="board-search-result__flight">
|
||||||
|
<BoardFlightHeader
|
||||||
|
flight={flight}
|
||||||
|
isExpanded={expandedFlightId === flight.id}
|
||||||
|
onToggle={() => toggleExpanded(flight.id)}
|
||||||
|
/>
|
||||||
|
{expandedFlightId === flight.id && (
|
||||||
|
<BoardFlightBody flight={flight} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
export { OnlineBoardFilter } from './online-board-filter'
|
export { OnlineBoardFilter } from './online-board-filter'
|
||||||
export { FlightNumberFilter } from './flight-number-filter'
|
export { FlightNumberFilter } from './flight-number-filter'
|
||||||
export { RouteFilter } from './route-filter'
|
export { RouteFilter } from './route-filter'
|
||||||
|
export { BoardSearchResult } from './board-search-result'
|
||||||
|
export { BoardFlightHeader } from './board-flight-header'
|
||||||
|
export { BoardFlightBody } from './board-flight-body'
|
||||||
|
export type { Flight } from './board-search-result'
|
||||||
|
|||||||
Reference in New Issue
Block a user