SearchForm
Thu May 27 2021 15:30:56 GMT+0000 (Coordinated Universal Time)
import React from 'react'
import { MenuItem, IconButton, Button as MuiButton, Switch, RadioGroup, Radio, FormControlLabel,
Typography, Paper, TextField, Collapse, Card, makeStyles, InputLabel, InputAdornment } from '@material-ui/core'
import Swap from 'components/Icons/Swap'
import FavoriteLoader from './FavoriteLoader.jsx'
import PassengersCount from './PassengersCount.jsx'
import Autocomplete from 'containers/Autocomplete/Autocomplete.js'
import CustomDayPickerInput from 'containers/CustomDayPickerInput/CustomDayPickerInput.jsx'
import styles from './form.cssmodule.scss'
import api from 'api'
import moment from 'moment'
import DropOff from 'components/Icons/DropOff/DropOff.jsx'
import PickUp from 'components/Icons/PickUp/PickUp.jsx'
import { FormattedMessage } from 'react-intl'
import LastBookings from './LastBookings'
import FormSectionHeader from './FormSectionHeader'
import { get } from 'lodash'
import {extrasToGenericSeats} from 'lib/genericSeatsFunctions'
import {KeyboardTimePicker as TimePicker} from '@material-ui/pickers'
import Clock from 'components/Icons/Clock/Clock.jsx'
import ExpandMore from '../Icons/ExpandMore.js'
const useStyles = makeStyles((theme) => ({
input: {
background: '#cee6f3',
color: '#0869af',
// fontSize:'18px'
borderRadius:'10px',
padding: '0.5rem',
}
}))
export default function SearchForm(props){
const classes = useStyles()
const { formData, errors, onAddressSelected, onFieldChange, loadFavorite, intl,
territory, increasePassengers, decreasePassengers, onSubmit, swapActive, swapAddresses,
actualizeDeparture, onDayClick, customFieldInfos, requesting } = props
const { departure, destination, time, timeRestrictionType,
passengers, asap, recurrence, selectedDays, customFields} = formData
const recurringOffers = _.get(props, 'territory.booking.recurring_bookings.offers') || []
const extras = _.get(territory,'extras',{}) // special seats
const extrasTypes = extrasToGenericSeats(extras)
const nbPassengers = _.reduce(extrasTypes, (red, type) => red + _.get(passengers, type, 0), passengers.standard)
return (
<div className={styles.form}>
<Paper>
<form
className={'row'}
style={{justifyContent:'space-around'}}
>
<div className="column padded" style={{ flex: '0 0 45%' }}>
<div className='column' style={{justifyContent:'flex-start'}}>
<div className={`row-only ${styles.autoCompletes}`}>
<div className="column" style={{ width: '100%' }}>
<div className="row-only" style={{margin:'0.5rem 0',alignItems: 'flex-end'}}>
<PickUp width={24} height={24} style={{marginTop:'0.5rem', padding:'0.5rem', backgroundColor:'#f1f0f0', borderRadius:'5px 0 0 5px'}}/>
<Autocomplete
context="search"
inputProps={{
label:intl.formatMessage({id: 'search.pickup.placeholder'}),
helperText:errors.departure,
text:departure.display,
}}
onChange={ e => {
onFieldChange('departure', {
display: e.target.value
})
}}
onSelect={p =>
onAddressSelected(p.suggestion, p.nodeId, 'departure', p.latitude, p.longitude, p.address)
}
/>
</div>
<div className="row-only" style={{margin:'0.5rem 0',alignItems: 'flex-end'}}>
<DropOff width={24} height={24} style={{marginTop:'0.5rem', padding:'0.5rem', backgroundColor:'#f1f0f0', borderRadius:'5px 0 0 5px'}}/>
<Autocomplete
context="search"
inputProps={{
label:intl.formatMessage({id: 'search.dropoff.placeholder'}),
helperText:errors.destination,
text:destination.display
}}
onChange={ e => {
onFieldChange('destination', {
display: e.target.value
})
}}
onSelect={p =>
onAddressSelected(p.suggestion, p.nodeId, 'destination', p.latitude, p.longitude, p.address)
}
/>
</div>
</div>
<div className={`column ${styles.swapIcon}`}>
{swapActive &&
<IconButton
title={intl.formatMessage({ id: 'help.swap' })}
onClick={swapAddresses}
>
<Swap
color="primary"
style={{width:'1.5rem', height:'1.5rem'}}
/>
</IconButton>
}
</div>
</div>
<FavoriteLoader
intl={intl}
loadFavorite={loadFavorite}
/>
<LastBookings />
</div>
</div>
<div className="column aligned padded"
style={{ flex: '0 0 45%'}}>
<div // big vertical container so that the BOOK button is on bottom
className="column aligned fullWidth"
style={{flexGrow:1, justifyContent:'space-around'}}>
<Card elevation={4} style={{width:'100%', padding:'1rem'}}>
<div className="column aligned fullWidth" /* HOUR + DATE + RECURRENCE */>
<FormSectionHeader title={intl.formatMessage({ id:'section.time'})}/>
<RadioGroup aria-label={intl.formatMessage({id:'search.time'})}
// MAYBE REFACTOR INTELLIGENCE ?
row
className="fullWidth"
style={{paddingLeft:'1rem'}}
value={asap ? 'asap' : timeRestrictionType}
onChange={(e)=>{
if (e.target.value === 'asap'){
actualizeDeparture()
}
else {
onFieldChange('asap', false)
onFieldChange('timeRestrictionType', e.target.value)
}
}}>
<FormControlLabel value="asap"
control={<Radio color="primary" id="asap"/>}
label={intl.formatMessage({ id: 'search.departureNow' })}
/>
<FormControlLabel value="departure"
control={<Radio color="primary" id="departure"/>}
label={intl.formatMessage({id: 'search.departure_time'})}
/>
<FormControlLabel value="arrival"
control={<Radio color="primary" id="arrival"/>}
label={intl.formatMessage({id: 'search.arrival_time'})}
/>
</RadioGroup>
<Collapse in={!asap} style={{width:'100%'}} /* HOUR + DATE + RECURRENCE */>
<div // HOUR
className={styles.selectTime}>
<TimePicker
value={window.Cypress ? 1582949766 : time}
onChange={newTime=>{
if (newTime && newTime.isValid()){
onFieldChange('time',newTime.format())
}
}}
autoOk
ampm={false}
disabled={asap}
inputVariant="outlined"
maskChar=" "
// below is because the modal is not keyboard accessible
KeyboardButtonProps={{tabIndex:-1}}
keyboardIcon={<Clock color="grey"/>}
invalidDateMessage={<Typography color="error">"Invalid date"</Typography>}
invalidLabel="false"
helperText={errors && errors.time}
error={Boolean(errors.time)}
label={intl.formatMessage({id:'search.time'})}
id="time"
className={styles.timePicker}
required
/>
{ /** recurrence enabled but "do not repeat" => classic calendar here */
recurringOffers && recurringOffers.length > 0 && recurrence.id === 0 &&
<CustomDayPickerInput
disabled={asap}
tabIndex={0}
selectedDays={(!asap ? selectedDays : [time]) || []}
onDayClick={(day, r) => onDayClick(moment(day).format(), r)}
errorText={errors.selectedDays}
maxBookingDay={_.get(territory, 'booking.max_booking_day')}
name="date"
maxBookableDates={
_.get(territory, 'booking.multi_date.max_bookable_dates')
}
label={intl.formatMessage({
id: _.get(territory, 'booking.multi_date.enabled')
&& _.get(territory, 'booking.multi_date.max_bookable_dates',0) > 1
? 'search.the'
: 'search.monoDate'
})}
/>
}
{ /*recurring two fields (start & end) */
recurringOffers && recurringOffers.length > 0 && recurrence.id !== 0 &&
<div className="column">
<CustomDayPickerInput
name="startDate"
disabled={asap}
// tabIndex={asap ? -1 : 12}
selectedDays={[recurrence.start_datetime]}
onDayClick={(day, requestRide) => onFieldChange('recurrence', {
...recurrence,
start_datetime: moment(day).format()
})}
errorText={errors.selectedDays}
label={intl.formatMessage({
id: 'search.recurrence.start_date'
})}
/>
<CustomDayPickerInput
name="endDate"
disabled={asap}
selectedDays={[recurrence.end_datetime]}
onDayClick={(day, requestRide) => onFieldChange('recurrence', {
...recurrence,
end_datetime: moment(day).format()
})}
// tabIndex={asap ? -1 : 12}
errorText={errors.selectedDays}
label={intl.formatMessage({
id: 'search.recurrence.end_date'
})}
/>
</div>
}
</div>
{ // either calendar OR recurence-offer-choice here
!recurringOffers || recurringOffers.length === 0
// CALENDAR
?
<CustomDayPickerInput
disabled={asap}
tabIndex={0}
selectedDays={(!asap ? selectedDays : [time]) || []}
onDayClick={(day, r) => onDayClick(moment(day).format(), r)}
errorText={errors.selectedDays}
maxBookingDay={_.get(territory, 'booking.max_booking_day')}
name="date"
maxBookableDates={
_.get(territory, 'booking.multi_date.max_bookable_dates')
}
label={intl.formatMessage({
id: _.get(territory, 'booking.multi_date.enabled')
&& _.get(territory, 'booking.multi_date.max_bookable_dates',0) > 1
? 'search.the'
: 'search.monoDate'
})}
/>
:
<div style={{display: 'flex', alignItems:'center'}}>
<label
htmlFor={'recurrence-offer-select'}
variant='body' style={{fontSize:'18px', marginRight:'0.5rem'}}
>
{intl.formatMessage({id:'help.recurrenceOffer'})}
</label>
<TextField // RECURRENCE OFFER
required
id="recurrence-offer-select"
select
name="recurrenceOffer"
className={`column ${styles.selectTargetMode}`}
value={recurrence.id}
style={asap ? { color: '#ccc' } : null}
disabled={asap}
onChange={(e) => {
onFieldChange('recurrence', {
...recurrence,
id: e.target.value
})
}}
// style={{minWidth:'18rem'}}
InputProps={{
className: classes.input,
disableUnderline: true,
}}
SelectProps={{
IconComponent: () => <ExpandMore style={{position:'absolute', right: 10}}/>
}}
>
<MenuItem value={0}>
{intl.formatMessage({
id: 'search.recurrence.doNotRepeat',
})}
</MenuItem>
{/* map on [{id: "work_days", display_name:"Work days"}]}
OR territory.recurringOffers. */}
{recurringOffers.map((offer, index) =>
(<MenuItem key={index} value={offer.id}>
{offer.name}
</MenuItem>)
)}
</TextField>
</div>
}
</Collapse>
</div>
</Card>
<Card elevation={4} style={{width:'100%', padding:'1rem', margin:'1rem'}}>
<div className="column aligned fullWidth" /* OPTIONS : PASSENGERS + CUSTOM FIELDS */>
<FormSectionHeader title={intl.formatMessage({ id:'section.options'})}/>
<div className='fullWidth'>
<PassengersCount
type="standard"
decrease={() => decreasePassengers('standard')}
increase={() => increasePassengers('standard')}
value={passengers.standard}
/>
{ extrasTypes.map( type =>{
return(
<PassengersCount
extras={extras}
key={type + '_counter'}
type={type}
decrease={() => decreasePassengers(type)}
increase={() => increasePassengers(type)}
value={passengers[type]}
/>
)}
)}
</div>
</div>
{ nbPassengers == 0 ?
<Typography
color="error"
variant="caption"
>
{intl.formatMessage({ id: 'search.error.nbPassengers'})}
</Typography>
: null
}
{ /* customFields is a dict. This works similarly to "recurrence" */
customFields &&
<div className="column spaced" style={{flexWrap:'wrap'}}>
{_.map(_.keys(customFieldInfos).sort(), (cf_key, name)=>{
const cf = customFieldInfos[cf_key]
return(
<div
className="ron-only column"
style={{padding:'1rem 0 0 0'}}
key={'cf-'+name}
>
<TextField
id={cf.display_name}
required={Boolean(cf.is_required)}
label={cf.display_name}
style={{width: '90%'}}
select={cf.type === 'select'}
value={_.get(formData, 'customFields.'+cf_key) || ''}
onChange={e => onFieldChange(
'customFields',{
...customFields,
[cf_key]:e.target.value
}
)}
error={Boolean(get(errors,'customFields.'+cf_key))}
helperText={get(errors,'customFields.'+cf_key)}
variant="outlined"
SelectProps={{
IconComponent: () => <ExpandMore style={{position:'absolute', right: 10}}/>
}}
>
{_.map(_.get(cf, 'options', []), (m, index) =>
(<MenuItem key={index} value={m.key}>
{m.display}
</MenuItem>)
)}
</TextField>
</div>
)})
}
</div>
}
</Card>
</div>
</div>
</form>
</Paper>
<div className="row"
style={{ paddingTop:'1rem', flexDirection: 'row-reverse' }}>
<MuiButton
variant="contained"
color="primary"
disabled={requesting}
onClick={()=>onSubmit()}
>
<Typography variant="body1">
{intl.formatMessage({ id: 'search.submit' })}
</Typography>
</MuiButton>
</div>
</div>
)
}



Comments