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> ) }
Preview:
downloadDownload PNG
downloadDownload JPEG
downloadDownload SVG
Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!
Click to optimize width for Twitter