feat: create ScheduleFilter with date range and day selection

This commit is contained in:
gnezim
2026-04-05 21:18:26 +03:00
parent dfd267f852
commit 8c68d31e12
2 changed files with 225 additions and 2 deletions
@@ -1,3 +1,79 @@
.schedule-filter { .schedule-filter {
width: 100%; width: 100%;
: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;
}
}
}
}
.schedule-filter__content {
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px 0;
}
.schedule-filter__group {
display: flex;
flex-direction: column;
gap: 8px;
}
.schedule-filter__label {
font-size: 12px;
font-weight: 500;
color: #666;
text-transform: uppercase;
}
.schedule-filter__swap {
align-self: center;
margin: 8px 0;
}
.schedule-filter__days {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 8px;
}
.schedule-filter__day-checkbox {
display: flex;
align-items: center;
gap: 8px;
}
.schedule-filter__day-label {
font-size: 14px;
color: #333;
cursor: pointer;
user-select: none;
}
.schedule-filter__buttons {
display: flex;
gap: 8px;
margin-top: 8px;
:global {
.p-button {
flex: 1;
}
}
} }
@@ -1,10 +1,157 @@
import React from 'react' import React, { useState } from 'react'
import { Accordion, AccordionTab } from 'primereact/accordion'
import { Button } from 'primereact/button'
import { Checkbox } from 'primereact/checkbox'
import { useNavigate } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { CityAutocomplete, City } from '@app/components/city-autocomplete'
import { CalendarInput } from '@app/components/calendar-input'
import './schedule-filter.scss' import './schedule-filter.scss'
const DAYS_OF_WEEK = ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN']
export const ScheduleFilter: React.FC = () => { export const ScheduleFilter: React.FC = () => {
const { t } = useTranslation()
const navigate = useNavigate()
const [departure, setDeparture] = useState<City | null>(null)
const [arrival, setArrival] = useState<City | null>(null)
const [startDate, setStartDate] = useState<Date | null>(new Date())
const [endDate, setEndDate] = useState<Date | null>(new Date(Date.now() + 6 * 24 * 60 * 60 * 1000))
const [selectedDays, setSelectedDays] = useState<string[]>([])
const handleDayToggle = (day: string) => {
setSelectedDays((prev) =>
prev.includes(day) ? prev.filter((d) => d !== day) : [...prev, day]
)
}
const handleSearch = () => {
if (departure && arrival && startDate && endDate) {
const params = btoa(
JSON.stringify({
departure: departure.code,
arrival: arrival.code,
startDate: startDate.toISOString(),
endDate: endDate.toISOString(),
days: selectedDays.length > 0 ? selectedDays : undefined,
})
)
navigate(`/schedule/route/${params}`)
}
}
const handleSwap = () => {
const temp = departure
setDeparture(arrival)
setArrival(temp)
}
const handleClear = () => {
setDeparture(null)
setArrival(null)
setStartDate(new Date())
setEndDate(new Date(Date.now() + 6 * 24 * 60 * 60 * 1000))
setSelectedDays([])
}
return ( return (
<div className="schedule-filter" data-testid="schedule-filter"> <div className="schedule-filter" data-testid="schedule-filter">
{/* Schedule filter will be implemented in next task */} <Accordion activeIndex={0}>
<AccordionTab header={t('SHARED.SEARCH')} data-testid="schedule-filter-tab">
<div className="schedule-filter__content">
{/* Departure City */}
<div className="schedule-filter__group">
<label className="schedule-filter__label">{t('SHARED.DEPARTURE_CITY')}</label>
<CityAutocomplete
value={departure}
onChange={setDeparture}
placeholder={t('SHARED.SELECT_CITY')}
data-testid="schedule-departure-city-input"
/>
</div>
{/* Swap Button */}
<Button
icon="pi pi-arrow-right"
className="p-button-rounded p-button-text schedule-filter__swap"
onClick={handleSwap}
data-testid="schedule-swap-button"
/>
{/* Arrival City */}
<div className="schedule-filter__group">
<label className="schedule-filter__label">{t('SHARED.ARRIVAL_CITY')}</label>
<CityAutocomplete
value={arrival}
onChange={setArrival}
placeholder={t('SHARED.SELECT_CITY')}
data-testid="schedule-arrival-city-input"
/>
</div>
{/* Date Range */}
<div className="schedule-filter__group">
<label className="schedule-filter__label">{t('SHARED.START_DATE')}</label>
<CalendarInput
value={startDate}
onChange={setStartDate}
data-testid="schedule-start-date-input"
/>
</div>
<div className="schedule-filter__group">
<label className="schedule-filter__label">{t('SHARED.END_DATE')}</label>
<CalendarInput
value={endDate}
onChange={setEndDate}
minDate={startDate || undefined}
data-testid="schedule-end-date-input"
/>
</div>
{/* Days of Week Filter */}
<div className="schedule-filter__group">
<label className="schedule-filter__label">{t('SCHEDULE.DAYS_OF_WEEK')}</label>
<div className="schedule-filter__days">
{DAYS_OF_WEEK.map((day) => (
<div key={day} className="schedule-filter__day-checkbox">
<Checkbox
inputId={`day-${day}`}
name={day}
value={day}
onChange={() => handleDayToggle(day)}
checked={selectedDays.includes(day)}
data-testid={`schedule-day-${day}`}
/>
<label htmlFor={`day-${day}`} className="schedule-filter__day-label">
{t(`SCHEDULE.DAY_${day}`)}
</label>
</div>
))}
</div>
</div>
{/* Buttons */}
<div className="schedule-filter__buttons">
<Button
label={t('SHARED.SEARCH')}
icon="pi pi-search"
onClick={handleSearch}
disabled={!departure || !arrival || !startDate || !endDate}
data-testid="schedule-search-button"
/>
<Button
label={t('SHARED.CLEAR')}
icon="pi pi-times"
severity="secondary"
onClick={handleClear}
data-testid="schedule-clear-button"
/>
</div>
</div>
</AccordionTab>
</Accordion>
</div> </div>
) )
} }