feat: create OnlineBoardFilter with FlightNumber and Route filters

This commit is contained in:
gnezim
2026-04-05 21:07:08 +03:00
parent e53ac746a4
commit 92ffc2a103
7 changed files with 301 additions and 8 deletions
@@ -0,0 +1,52 @@
.flight-number-filter {
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px 0;
}
.flight-number-filter__group {
display: flex;
flex-direction: column;
gap: 8px;
}
.flight-number-filter__label {
font-size: 12px;
font-weight: 500;
color: #666;
text-transform: uppercase;
}
.flight-number-filter__input-group {
display: flex;
align-items: center;
gap: 8px;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 4px;
}
.flight-number-filter__prefix {
padding: 8px 12px;
font-weight: 600;
color: #1976d2;
background: #f5f5f5;
border-radius: 2px;
}
.flight-number-filter__input {
flex: 1;
border: none;
padding: 8px;
font-size: 14px;
outline: none;
&:focus {
background: #f9f9f9;
}
}
.flight-number-filter__search-button {
margin-top: 8px;
}
@@ -0,0 +1,70 @@
import React, { useState } from 'react'
import { Button } from 'primereact/button'
import { useTranslation } from 'react-i18next'
import { CalendarInput } from '@app/components/calendar-input'
import './flight-number-filter.scss'
export interface FlightNumberFilterProps {
onSearch: (flightNumber: string, date: Date) => void
}
export const FlightNumberFilter: React.FC<FlightNumberFilterProps> = ({ onSearch }) => {
const { t } = useTranslation()
const [flightNumber, setFlightNumber] = useState('')
const [date, setDate] = useState<Date | null>(new Date())
const handleSearch = () => {
if (flightNumber && date) {
onSearch(flightNumber, date)
}
}
const handleClear = () => {
setFlightNumber('')
setDate(new Date())
}
return (
<div className="flight-number-filter" data-testid="flight-number-filter">
<div className="flight-number-filter__group">
<label className="flight-number-filter__label">{t('BOARD.FLIGHT_NUMBER')}</label>
<div className="flight-number-filter__input-group">
<span className="flight-number-filter__prefix">SU</span>
<input
type="text"
className="flight-number-filter__input"
placeholder="123"
value={flightNumber}
onChange={(e) => setFlightNumber(e.target.value.toUpperCase())}
data-testid="flight-number-input"
maxLength={4}
/>
<Button
icon="pi pi-times"
className="p-button-rounded p-button-text"
onClick={handleClear}
data-testid="flight-number-clear-button"
/>
</div>
</div>
<div className="flight-number-filter__group">
<label className="flight-number-filter__label">{t('SHARED.DATE')}</label>
<CalendarInput
value={date}
onChange={setDate}
data-testid="flight-number-calendar"
/>
</div>
<Button
label={t('SHARED.SEARCH')}
icon="pi pi-search"
onClick={handleSearch}
disabled={!flightNumber || !date}
className="flight-number-filter__search-button"
data-testid="flight-number-search-button"
/>
</div>
)
}
@@ -1 +1,3 @@
export { OnlineBoardFilter } from './online-board-filter'
export { FlightNumberFilter } from './flight-number-filter'
export { RouteFilter } from './route-filter'
@@ -1,4 +1,23 @@
.online-board-filter {
width: 100%;
min-height: 200px;
:global {
.p-accordion {
border: 1px solid #e0e0e0;
border-radius: 4px;
.p-accordion-header {
padding: 0;
a {
padding: 12px 16px;
font-weight: 500;
}
}
.p-accordion-content {
padding: 0 16px;
}
}
}
}
@@ -1,14 +1,47 @@
import React from 'react'
import React, { useState } from 'react'
import { Accordion, AccordionTab } from 'primereact/accordion'
import { useNavigate } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { FlightNumberFilter } from './flight-number-filter'
import { RouteFilter } from './route-filter'
import './online-board-filter.scss'
export interface OnlineBoardFilterProps {
'data-testid'?: string
export const OnlineBoardFilter: React.FC = () => {
const { t } = useTranslation()
const navigate = useNavigate()
const [activeTab, setActiveTab] = useState(0)
const handleFlightNumberSearch = (flightNumber: string, date: Date) => {
// Navigate to flight number search results
const params = btoa(JSON.stringify({ flightNumber, date: date.toISOString() }))
navigate(`/onlineboard/flight/${params}`)
}
const handleRouteSearch = (departure: string, arrival: string, date: Date) => {
// Navigate to route search results
const params = btoa(JSON.stringify({ departure, arrival, date: date.toISOString() }))
navigate(`/onlineboard/route/${params}`)
}
export const OnlineBoardFilter: React.FC<OnlineBoardFilterProps> = ({ 'data-testid': dataTestId }) => {
return (
<div className="online-board-filter" data-testid={dataTestId || 'online-board-filter'}>
{/* Filter component will be implemented in the next task */}
<div className="online-board-filter" data-testid="online-board-filter">
<Accordion activeIndex={activeTab} onTabChange={(e) => {
const index = typeof e.index === 'number' ? e.index : (Array.isArray(e.index) ? e.index[0] : 0)
setActiveTab(index)
}}>
<AccordionTab
header={t('BOARD.FLIGHT_NUMBER')}
data-testid="flight-filter"
>
<FlightNumberFilter onSearch={handleFlightNumberSearch} />
</AccordionTab>
<AccordionTab
header={t('BOARD.ROUTE')}
data-testid="route-filter"
>
<RouteFilter onSearch={handleRouteSearch} />
</AccordionTab>
</Accordion>
</div>
)
}
@@ -0,0 +1,28 @@
.route-filter {
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px 0;
}
.route-filter__group {
display: flex;
flex-direction: column;
gap: 8px;
}
.route-filter__label {
font-size: 12px;
font-weight: 500;
color: #666;
text-transform: uppercase;
}
.route-filter__swap {
align-self: center;
margin: 8px 0;
}
.route-filter__search-button {
margin-top: 8px;
}
@@ -0,0 +1,89 @@
import React, { useState } from 'react'
import { Button } from 'primereact/button'
import { useTranslation } from 'react-i18next'
import { CityAutocomplete, City } from '@app/components/city-autocomplete'
import { CalendarInput } from '@app/components/calendar-input'
import { TimeSelector } from '@app/components/time-selector'
import './route-filter.scss'
export interface RouteFilterProps {
onSearch: (departure: string, arrival: string, date: Date) => void
}
export const RouteFilter: React.FC<RouteFilterProps> = ({ onSearch }) => {
const { t } = useTranslation()
const [departure, setDeparture] = useState<City | null>(null)
const [arrival, setArrival] = useState<City | null>(null)
const [date, setDate] = useState<Date | null>(new Date())
const [startTime, setStartTime] = useState('00:00')
const [endTime, setEndTime] = useState('23:59')
const handleSearch = () => {
if (departure && arrival && date) {
onSearch(departure.code, arrival.code, date)
}
}
const handleSwap = () => {
const temp = departure
setDeparture(arrival)
setArrival(temp)
}
return (
<div className="route-filter" data-testid="route-filter">
<div className="route-filter__group">
<label className="route-filter__label">{t('SHARED.DEPARTURE_CITY')}</label>
<CityAutocomplete
value={departure}
onChange={setDeparture}
placeholder={t('SHARED.SELECT_CITY')}
data-testid="route-departure-city-input"
/>
</div>
<Button
icon="pi pi-arrow-right"
className="p-button-rounded p-button-text route-filter__swap"
onClick={handleSwap}
data-testid="route-swap-button"
/>
<div className="route-filter__group">
<label className="route-filter__label">{t('SHARED.ARRIVAL_CITY')}</label>
<CityAutocomplete
value={arrival}
onChange={setArrival}
placeholder={t('SHARED.SELECT_CITY')}
data-testid="route-arrival-city-input"
/>
</div>
<div className="route-filter__group">
<label className="route-filter__label">{t('SHARED.DATE')}</label>
<CalendarInput
value={date}
onChange={setDate}
data-testid="route-calendar-input"
/>
</div>
<TimeSelector
startTime={startTime}
endTime={endTime}
onStartTimeChange={setStartTime}
onEndTimeChange={setEndTime}
data-testid="route-time-selector"
/>
<Button
label={t('SHARED.SEARCH')}
icon="pi pi-search"
onClick={handleSearch}
disabled={!departure || !arrival || !date}
className="route-filter__search-button"
data-testid="route-search-button"
/>
</div>
)
}