feat: create ScheduleFilter with date range and day selection
This commit is contained in:
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user