From 0366db1a2f1017f209fa84a23c4bf2db8f958d77 Mon Sep 17 00:00:00 2001 From: gnezim Date: Sun, 5 Apr 2026 21:11:56 +0300 Subject: [PATCH] feat: implement BoardFlightHeader with full flight details display --- .../components/board-flight-header.scss | 193 +++++++++++++++--- .../components/board-flight-header.tsx | 124 +++++++++-- .../components/board-search-result.tsx | 2 +- 3 files changed, 281 insertions(+), 38 deletions(-) diff --git a/apps/react/src/app/features/online-board/components/board-flight-header.scss b/apps/react/src/app/features/online-board/components/board-flight-header.scss index c3e977da3..59030bb1c 100644 --- a/apps/react/src/app/features/online-board/components/board-flight-header.scss +++ b/apps/react/src/app/features/online-board/components/board-flight-header.scss @@ -7,67 +7,210 @@ user-select: none; border-bottom: 1px solid #f0f0f0; transition: background 0.2s ease; + gap: 16px; &:hover { background: #f9f9f9; } + &:focus { + outline: 2px solid #1976d2; + outline-offset: -2px; + } + &--expanded { background: #f5f5f5; border-bottom: 1px solid #e0e0e0; } } -.board-flight-header__content { +.board-flight-header__main { flex: 1; display: grid; - grid-template-columns: 80px 100px 1fr 1fr; - gap: 16px; + grid-template-columns: 100px 120px 180px 1fr 120px; + gap: 20px; align-items: center; + @media (max-width: 1024px) { + grid-template-columns: 80px 100px 150px 1fr; + gap: 12px; + + .board-flight-header__aircraft { + display: none; + } + } + @media (max-width: 768px) { grid-template-columns: 1fr; gap: 8px; } } +.board-flight-header__column { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; +} + +.board-flight-header__flight-number { + flex-direction: column; + align-items: flex-start; +} + .board-flight-header__number { - font-weight: 600; - font-size: 16px; + font-weight: 700; + font-size: 18px; color: #1976d2; } -.board-flight-header__status { - padding: 4px 8px; - border-radius: 4px; - background: #e8f5e9; - color: #2e7d32; - font-size: 12px; +.board-flight-header__operator { + font-size: 11px; + color: #999; + text-transform: uppercase; font-weight: 500; +} + +.board-flight-header__status-col { + justify-content: center; +} + +.board-flight-header__status { + padding: 4px 10px; + border-radius: 4px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; white-space: nowrap; + + &.status-on-time { + background: #e8f5e9; + color: #2e7d32; + } + + &.status-delayed { + background: #fff3e0; + color: #e65100; + } + + &.status-cancelled { + background: #ffebee; + color: #c62828; + } + + &.status-active { + background: #e3f2fd; + color: #1565c0; + } + + &.status-default { + background: #f5f5f5; + color: #666; + } } .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; - } + justify-content: space-between; } -.board-flight-header__route { - color: #666; +.board-flight-header__time { + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; + min-width: 60px; +} + +.board-flight-header__time-label { + font-size: 10px; + color: #999; + text-transform: uppercase; + font-weight: 500; +} + +.board-flight-header__time-value { + font-weight: 600; + font-size: 14px; + color: #333; +} + +.board-flight-header__time-actual { + font-size: 10px; + color: #f57c00; + font-weight: 500; +} + +.board-flight-header__arrow-icon { + color: #ccc; font-size: 14px; } -.board-flight-header__arrow { - margin-left: 16px; - color: #999; - font-size: 12px; - transition: transform 0.2s ease; +.board-flight-header__route { + flex-direction: column; + align-items: flex-start; + gap: 4px; +} + +.board-flight-header__route-city { + display: flex; + flex-direction: column; + gap: 2px; + font-size: 13px; + font-weight: 500; + color: #333; +} + +.board-flight-header__route-city-name { + font-size: 11px; + color: #999; +} + +.board-flight-header__route-arrow { + color: #ccc; + font-size: 12px; +} + +.board-flight-header__aircraft { + flex-direction: column; + align-items: center; + gap: 4px; +} + +.board-flight-header__aircraft-type { + font-size: 12px; + color: #666; + font-weight: 500; + padding: 4px 8px; + background: #f5f5f5; + border-radius: 3px; +} + +.board-flight-header__expand-arrow { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border-radius: 4px; + transition: all 0.2s ease; + flex-shrink: 0; + color: #999; + + &:hover { + background: #e8e8e8; + } + + &.expanded { + color: #1976d2; + transform: rotate(180deg); + } + + :global { + .pi { + font-size: 18px; + } + } } diff --git a/apps/react/src/app/features/online-board/components/board-flight-header.tsx b/apps/react/src/app/features/online-board/components/board-flight-header.tsx index 3a604f7e5..95f9068af 100644 --- a/apps/react/src/app/features/online-board/components/board-flight-header.tsx +++ b/apps/react/src/app/features/online-board/components/board-flight-header.tsx @@ -1,37 +1,137 @@ import React from 'react' +import { useTranslation } from 'react-i18next' import './board-flight-header.scss' export interface BoardFlightHeaderProps { - flight: any + flight: { + id: string + flightNumber: string + status: 'ON_TIME' | 'DELAYED' | 'CANCELLED' | 'BOARDING' | 'DEPARTED' | 'ARRIVED' | 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[] + } isExpanded: boolean onToggle: () => void } +const getStatusColor = (status: string): string => { + switch (status) { + case 'ON_TIME': + return 'status-on-time' + case 'DELAYED': + return 'status-delayed' + case 'CANCELLED': + return 'status-cancelled' + case 'BOARDING': + case 'DEPARTED': + case 'ARRIVED': + return 'status-active' + default: + return 'status-default' + } +} + +const getStatusLabel = (status: string, t: any): string => { + const key = `FLIGHT_STATUS.${status}` + return t(key, status) +} + export const BoardFlightHeader: React.FC = ({ flight, isExpanded, onToggle, }) => { + const { t } = useTranslation() + return (
{ + if (e.key === 'Enter' || e.key === ' ') { + onToggle() + } + }} data-testid={`flight-header-${flight.id}`} > -
-
{flight.flightNumber}
-
{flight.status}
-
- {flight.departure.scheduled} - - {flight.arrival.scheduled} +
+ {/* Flight Number Column */} +
+
{flight.flightNumber}
+ {flight.operator && ( +
{flight.operator}
+ )}
-
- {flight.departure.city} → {flight.arrival.city} + + {/* Status Column */} +
+ + {getStatusLabel(flight.status, t)} +
+ + {/* Times Column */} +
+
+
Dep
+
{flight.departure.scheduled}
+ {flight.departure.actual && ( +
{flight.departure.actual}
+ )} +
+
+
+
Arr
+
{flight.arrival.scheduled}
+ {flight.arrival.actual && ( +
{flight.arrival.actual}
+ )} +
+
+ + {/* Route Column */} +
+
+ {flight.departure.airport} + {flight.departure.city} +
+
+
+ {flight.arrival.airport} + {flight.arrival.city} +
+
+ + {/* Aircraft Column */} + {flight.aircraft && ( +
+
{flight.aircraft}
+
+ )}
-
- {isExpanded ? '▼' : '▶'} + + {/* Expand/Collapse Arrow */} +
+
) diff --git a/apps/react/src/app/features/online-board/components/board-search-result.tsx b/apps/react/src/app/features/online-board/components/board-search-result.tsx index a8e0832b2..6e8bceaa7 100644 --- a/apps/react/src/app/features/online-board/components/board-search-result.tsx +++ b/apps/react/src/app/features/online-board/components/board-search-result.tsx @@ -7,7 +7,7 @@ import './board-search-result.scss' export interface Flight { id: string flightNumber: string - status: string + status: 'ON_TIME' | 'DELAYED' | 'CANCELLED' | 'BOARDING' | 'DEPARTED' | 'ARRIVED' | string operator?: string aircraft?: string departure: {