Sunday 19 August 2018

react flight

project site: http://chuanshuoge1-react-flight.surge.sh/

top steps, form, bottom right timeline

click timeline button, drawer open right, timeline events logged

popover opens when input entered

select departure return from calendar


filter by other dropdown list

form not complete, error message displays

search history is logged

modal opens when search button clicked and form is complete

book an itinerary form collapsed menu, itinerary is logged

to reselect itinerary, click button on the step, window reopens, change itinerary booking

--src
  --app.js
  --app.css
  --airports.js
--package.json

package.json

{
  "name": "react-flight",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "antd": "^3.8.0",
    "react": "^16.4.2",
    "react-dom": "^16.4.2",
    "react-scripts": "1.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

----------------------------------------------

App.js

import React, { Component } from 'react';
import './App.css';

import airports from './airports';

import 'antd/dist/antd.css';
import {
    Steps, Drawer, Button, Radio, Icon, Tooltip, Badge,
    Timeline, DatePicker, Input, Affix, Row, Col, Select,
    Checkbox, message, Modal, Popover, Menu, Dropdown,
    Collapse, Avatar,
} from 'antd';

const Step = Steps.Step;
const { RangePicker } = DatePicker;
const Option = Select.Option;
const Panel = Collapse.Panel;

class App extends Component {
    constructor(props) {
        super(props);

        this.state = {
            currentStep: 0,
            drawerOpen: false,
            badgeCount: 0,
            roundTrip: 'round',
            departingDate: '',
            returningDate: '',
            translate: false,
            dayRange: 0,
            adult: 1,
            children: 0,
            infants: 0,
            cabin: 'Economy/Coach',
            airline: 'Search All Airlines',
            stopOptions: 'All stops',
            layover: 'Any layover hours',
            checkBox: false,
            fromComplete: true,
            toComplete: true,
            departingComplete: true,
            timeLineArray: [],
            modalVisible: false,
            popoverVisible: false,
            airportFrom: '',
            airportTo: '',
            searchResults: [],
            itinerary: '',
            panelHeader:[],
        };
    }

    componentDidMount() {
        const timeNow = new Date();
        const newTimeLine = <Timeline.Item>Webpage opens {timeNow.toLocaleString()}<br />
        </Timeline.Item>

        //store webpage open event
        this.setState({
            timeLineArray: this.state.timeLineArray.concat(newTimeLine),
            badgeCount: this.state.badgeCount + 1,
        });

        //fake itinerary panel header
        this.setState({
            panelHeader: [
                <div>
                    <Avatar style={{ backgroundColor: 'green' }} icon="dingding" />
                    {' '}Air X <span className='ticketPrice'>$1000</span>
                </div>,
                <div>
                    <Avatar style={{ backgroundColor: 'blue' }} icon="twitter" />
                    {' '}Air Y <span className='ticketPrice'>$900</span>
                </div>,
                <div>
                    <Avatar style={{ backgroundColor: 'gold' }} icon="qq" />
                    {' '}Air Z <span className='ticketPrice'>$800</span>
                </div>,
                <div>
                    <Avatar style={{ backgroundColor: 'red' }} icon="github" />
                    {' '}Air W <span className='ticketPrice'>$750</span>
                </div>,
            ]
        })
    }

    openDrawer() {
        this.setState({ drawerOpen: true })
    }

    closeDrawer() {
        this.setState({ drawerOpen: false })
    }

    handleChange_roundTrip(trip) {
        this.setState({ roundTrip: trip });
    }

    handleChange_date(e) {
        console.log(e);

        const departDate = e.length > 1 ? e[0]._d.toDateString() : e._d.toDateString();
        const returnDate = e.length > 1 ? e[1]._d.toDateString() : null;

        this.setState({ departingDate: departDate })

        this.state.roundTrip === 'round' ? this.setState({ returningDate: returnDate }) : null;
    }

    handleClick_translate() {
        this.setState(prevState => { return { translate: !prevState.translate } });
    }

    handleChange_dayRange(value) {
        this.setState({ dayRange: value });
    }

    handleChange_passenger(value, name) {
        this.setState({ [name]: value })
    }

    handleChange_checkBox(value) {
        this.setState({ checkBox: value })
    }

    async handleClick_search() {
        let searchFormComplete = true;

        //check if from input is complete
        if (this.state.airportFrom.length !== 3) {
            message.error('Please select from airport');
            this.setState({ fromComplete: false });
            searchFormComplete = false;
        } else {
            this.setState({ fromComplete: true });
        }

        //check if to input is complete
        if (this.state.airportTo.length !== 3) {
            message.error('Please select to airport');
            this.setState({ toComplete: false });
            searchFormComplete = false;
        } else {
            this.setState({ toComplete: true });
        }

        //check if departure calendar is complete
        if (this.state.departingDate === '') {
            message.error('Departing Date is not filled');
            this.setState({ departingComplete: false });
            searchFormComplete = false;
        } else {
            this.setState({ departingComplete: true });
        }

        if (!searchFormComplete) { return }
        //form complete
        else {
            //add search history to event
            const dropdown_menu = <Menu>
                <Menu.Item>
                    {this.state.roundTrip} trip<br />
                    From: {this.state.airportFrom}<br />
                    To: {this.state.airportTo}<br />
                    Departing: {this.state.departingDate}<br />
                    Returning:{this.state.returningDate}<br />
                    Date Range: {this.state.dayRange}<br />
                    Adult: {this.state.adult}<br />
                    Children: {this.state.children}<br />
                    Infants: {this.state.infants}<br />
                    Class: {this.state.cabin}<br />
                    Airline:{this.state.airline}<br />
                    Stop: {this.state.stopOptions}<br />
                    Layover: {this.state.layover}<br />
                    Airports within 70 miles: {this.state.checkBox}<br />
                </Menu.Item>
            </Menu>

            const timeNow = new Date();
            const newTimeLine = <Timeline.Item>Flight searched {timeNow.toLocaleString()}<br />

                {/*store search details in dropdown menu*/}
                <Dropdown overlay={dropdown_menu} trigger={['click']}>
                    <a >Show detail... <Icon type="down" /></a>
                </Dropdown>

            </Timeline.Item>

            //increase event counter, show search result
           await this.setState({
                currentStep: 1,
                timeLineArray: this.state.timeLineArray.concat(newTimeLine),
                badgeCount: this.state.badgeCount + 1,
                modalVisible: true,
                searchResults: [],           
            });

            //start flight search (mock)
            this.searchFlights();
        }
    }

     searchFlights() {

        //fake itineraries
        const itinerary = [<div>
            <p className='flightInfo'>9:30a-5:50p</p>
            <p>Connections... Layovers...</p>
            <p>Air X MU 5712 - {this.state.cabin} - Boeing 737-800</p>
        </div>,
            <div>
                <p className='flightInfo'>3:25p - 10:50a</p>
                <p>Connections... Layovers...</p>
                <p>Air Y CA 991 - {this.state.cabin} - Boeing 777-300ER</p>
            </div>,
            <div>
                <p className='flightInfo'> 12:40p - 4:00p+1day</p>
                <p>Connections... Layovers...</p>
                <p>Air Z AC 201 - {this.state.cabin} -  Airbus A321</p>
            </div>,
            <div>
                <p className='flightInfo'>  4:05p - 6:30p</p>
                <p>Connections... Layovers...</p>
                <p>Air W  NH 5702 - {this.state.cabin} - Airbus A319</p>
            </div>,
            ]

        //insert faked new itinerary into searchResult
        const generateItinerary = (newItinerary, header) => this.setState({
            searchResults: this.state.searchResults.concat(
                <div>
                    <Row>
                        <Button size='small' type="primary" block
                            onClick={() => this.handleClick_itinerary(
                                <div>
                                    {header}
                                    {this.state.departingDate}
                                    {newItinerary}
                                    </div>
                            )}
                        >
                            Book this itinerary</Button>
                    </Row>
                    <Row gutter={8}>
                        <Col span={4}>
                            <span className='flightInfo'>{this.state.departingDate}</span>
                        </Col>
                        <Col span={20}>
                            {newItinerary}
                        </Col>
                    </Row>
                </div>
            )
        });   

        //add a new itinerary every second
        generateItinerary(itinerary[0], this.state.panelHeader[0]);

        setTimeout(() => {
            generateItinerary(itinerary[1], this.state.panelHeader[1])
        }, 1000);

        setTimeout(() => {
            generateItinerary(itinerary[2], this.state.panelHeader[2])
        }, 2000);

        setTimeout(() => {
            generateItinerary(itinerary[3], this.state.panelHeader[3])
        }, 3000);
    }

    handleClick_modalCancel = () => {
        this.setState({
            modalVisible: false,
        });
    }

    handleChange_airport(value, name) {
        this.setState({ [name]: value.toUpperCase() });
    }

    //when itinerary is selected, event is logged, seat selection window opens
    handleClick_itinerary(newItinerary) {

        const dropdown_menu = <Menu>
            <Menu.Item>
                {newItinerary}
            </Menu.Item>
        </Menu>

        const timeNow = new Date();
        const newTimeLine = <Timeline.Item>Itinerary selected {timeNow.toLocaleString()}<br />

            {/*hide itinerary in dropdown menu*/}
            <Dropdown overlay={dropdown_menu} trigger={['click']}>
                <a >Show detail... <Icon type="down" /></a>
            </Dropdown>

        </Timeline.Item>

        this.setState({
            itinerary: newItinerary,
            currentStep: 2,
            timeLineArray: this.state.timeLineArray.concat(newTimeLine),
            badgeCount: this.state.badgeCount + 1,
            modalVisible: false,
        });
    }

    //open itinerary window if step>0
    handleClick_gotoStep2() {
        this.state.currentStep > 0 ?
            this.setState({ modalVisible: true }) :
            message.warning('Please search flight first');
    }

    render() {
        {/*********popover will not display if filtered airports are too many*******/ }
        const modalContent_from =
            airports
                .filter(x => x.airport.toUpperCase().includes(this.state.airportFrom)).length < 6 ?
                <div>
                    {
                        airports
                            .filter(x => (x.airport.toUpperCase().includes(this.state.airportFrom)
                                || x.code.toUpperCase().includes(this.state.airportFrom))
                                && x.code.toUpperCase() !== this.state.airportTo)
                            .map((item, index) =>
                                <p className='popover-item' key={item.id}
                                    onClick={() => this.handleChange_airport(item.code, 'airportFrom')}>
                                    {item.airport} ({item.code})</p>
                            )
                    }
                </div> :
                <div>
                    too many...
            </div>

        const modalContent_to =
            airports
                .filter(x => x.airport.toUpperCase().includes(this.state.airportTo)).length < 6 ?
                <div>
                    {
                        airports
                            .filter(x => (x.airport.toUpperCase().includes(this.state.airportTo)
                                || x.code.toUpperCase().includes(this.state.airportTo))
                                && x.code.toUpperCase() !== this.state.airportFrom)
                            .map((item, index) =>
                                <p className='popover-item' key={item.id}
                                    onClick={() => this.handleChange_airport(item.code, 'airportTo')}>
                                    {item.airport} ({item.code})</p>
                            )
                    }
                </div> :
                <div>
                    too many...
            </div>   

        return (
            <div>
                {/**********header, steps**********/}
                <Affix offsetTop={0}>
                    <div className="my-header">
                        <h2 className="my-title">FlyWorld.com</h2>

                        <Steps size="small" current={this.state.currentStep}>
                            <Step title="Search flights" />
                            <Step title={
                                <Button type="primary" size='small'
                                    onClick={()=>this.handleClick_gotoStep2()}
                                >Select itinerary</Button>} />
                            <Step title="Select seat" />
                            <Step title="Pay" />
                        </Steps>
                    </div>
                </Affix>

                {/**********timeLine button, badge, tooltip**********/}
                <div className="timeLine-button">
                    <Tooltip placement="left" title="TimeLine">
                        <Badge count={this.state.badgeCount}>
                            <Button type="primary" shape="circle" size="large"
                                onClick={() => this.openDrawer()}>
                                <Icon type="clock-circle-o" />
                            </Button>
                        </Badge>
                    </Tooltip>
                </div>

                {/**********drawer, timeLine**********/}
                <Drawer
                    title="Events"
                    placement="right"
                    closable={false}
                    onClose={() => this.closeDrawer()}
                    visible={this.state.drawerOpen}
                >
                    <Timeline>
                        {this.state.timeLineArray}
                    </Timeline>,
                </Drawer>

                {/**********form1, transluscent background**********/}
                <div className="form-background">
                    <div className="form-background2">

                        {/**********radio group**********/}
                        <Radio.Group value={this.state.roundTrip} size='small' buttonStyle="solid"
                            onChange={(e) => this.handleChange_roundTrip(e.target.value)}>
                            <Radio.Button value='round'>Round Trip</Radio.Button>
                            <Radio.Button value='single'>Single Trip</Radio.Button>
                        </Radio.Group>

                        {/**********language translator button**********/}
                        <Button className="translate-button" size='small'
                            style={{ backgroundColor: 'rgba(0, 0, 0, 0)', color: 'white', border: 0, }}
                            onClick={() => this.handleClick_translate()}>
                            {this.state.translate ? 'EN' : '中文'}
                        </Button>

                        {/**********popover departure arrival airport**********/}
                        <Row gutter={8}>
                            <Col span={12}>
                                <h4 className="my-label">From</h4>

                                {/**********from airport**********/}
                                <Popover content={modalContent_from} placement='bottom'
                                    trigger='click' title="Select Airports">
                                    <Input size='small' placeholder={'airport name'}
                                        value={this.state.airportFrom}
                                        style={this.state.fromComplete ? null : { border: '2px solid red' }}
                                        onChange={(e) => this.handleChange_airport(e.target.value, 'airportFrom')} />
                                </Popover>
                            </Col>
                            <Col span={12}>
                                <h4 className="my-label">To</h4>

                                {/**********to airport**********/}
                                <Popover content={modalContent_to} placement='bottom'
                                    trigger='click' title="Select Airports">
                                    <Input size='small' placeholder={'airport name'}
                                        value={this.state.airportTo}
                                        style={this.state.toComplete ? null : { border: '2px solid red' }}
                                        onChange={(e) => this.handleChange_airport(e.target.value, 'airportTo')} />
                                </Popover>
                            </Col>
                        </Row>

                        {/**********date picker**********/}
                        {this.state.roundTrip === 'round' ?
                            <h4 className="my-label">Departing - Returning</h4> :
                            <h4 className="my-label">Departing</h4>
                        }

                        {this.state.roundTrip === 'round' ?
                            <RangePicker size='small' placeholder={['Departing', 'Returning']}
                                style={this.state.departingComplete ? null : { border: '2px solid red' }}
                                onChange={(e) => this.handleChange_date(e)} /> :
                            <DatePicker size='small' placeholder='Departing'
                                onChange={(e) => this.handleChange_date(e)} />
                        }

                        <Row>
                            <Col span={10}>
                                {/**********+/- days**********/}
                                <h4 className="my-label">Date Range</h4>

                                <Radio.Group value={this.state.dayRange} size='small' buttonStyle="solid"
                                    onChange={(e) => this.handleChange_dayRange(e.target.value)}>
                                    <Radio.Button value={0}>0</Radio.Button>
                                    <Radio.Button value={1}>{'\u00B1'}1</Radio.Button>
                                    <Radio.Button value={3}>{'\u00B1'}3</Radio.Button>
                                    <Radio.Button value={7}>{'\u00B1'}7</Radio.Button>
                                </Radio.Group>
                                <span className="my-label">{' '}days</span>
                            </Col>

                            {/**********Adults, Children, Infants**********/}
                            <Col span={14}>
                                <Row gutter={8}>
                                    <Col span={7}>
                                        <p className="my-label2">Adults(12+)</p>
                                        <Select defaultValue={1} size='small' style={{ width: 50 }}
                                            onChange={(e) => this.handleChange_passenger(e, 'adult')}>
                                            {passengers(10)}
                                        </Select>
                                    </Col>
                                    <Col span={9}>
                                        <p className="my-label2">Children(2-11)</p>
                                        <Select defaultValue={0} size='small' style={{ width: 50, marginLeft: '10px' }}
                                            onChange={(e) => this.handleChange_passenger(e, 'children')}>
                                            {passengers(7)}
                                        </Select>
                                    </Col>
                                    <Col span={8}>
                                        <p className="my-label2">infants in laps</p>
                                        <Select defaultValue={0} size='small' style={{ width: 50 }}
                                            onChange={(e) => this.handleChange_passenger(e, 'infants')}>
                                            {passengers(5)}
                                        </Select>
                                    </Col>
                                </Row>
                            </Col>
                        </Row>

                        <Row gutter={8}>
                            {/**********Cabin/Class**********/}
                            <Col span={12}>
                                <h4 className="my-label">Select cabin/class</h4>

                                <Select defaultValue='Economy/Coach' size='small' style={{ width: '100%' }}
                                    onChange={(e) => this.handleChange_passenger(e, 'cabin')}>
                                    <Option value='Economy/Coach'>Economy/Coach</Option>
                                    <Option value='Premium Economy'>Premium Economy</Option>
                                    <Option value='Business Class'>Business Class</Option>
                                    <Option value='First Class'>First Class</Option>
                                </Select>
                            </Col>

                            {/**********airline**********/}
                            <Col span={12}>
                                <h4 className="my-label">Select airline</h4>

                                <Select defaultValue='Search All Airlines' size='small' style={{ width: '100%' }}
                                    onChange={(e) => this.handleChange_passenger(e, 'airline')}>
                                    <Option value='US & China Carriers'>US & China Carriers</Option>
                                    <Option value='Chinese Carriers'>Chinese Carriers</Option>
                                    <Option value='Star Alliance'>Star Alliance</Option>
                                    <Option value='Sky Team'>Sky Team</Option>
                                    <Option value='One World'>One World</Option>
                                </Select>
                            </Col>
                        </Row>

                        <Row gutter={8}>
                            {/**********stop options**********/}
                            <Col span={12}>
                                <h4 className="my-label">Select Stop Options</h4>

                                <Select defaultValue='All stops' size='small' style={{ width: '100%' }}
                                    onChange={(e) => this.handleChange_passenger(e, 'stopOptions')}>
                                    <Option value='All stops'>All stops</Option>
                                    <Option value='Non-stop only'>Non-stop only</Option>
                                    <Option value='1-stop only'>1-stop only</Option>
                                    <Option value='2-stop only'>2-stop only</Option>
                                </Select>
                            </Col>

                            {/**********layover**********/}
                            <Col span={12}>
                                <h4 className="my-label">Max Layover Hours</h4>

                                <Select defaultValue='Any layover hours' size='small' style={{ width: '100%' }}
                                    onChange={(e) => this.handleChange_passenger(e, 'layover')}>
                                    <Option value='Any layover hours'>Any layover hours</Option>
                                    <Option value='2 hours less'>2 hours less</Option>
                                    <Option value='4 hours less'>4 hours less</Option>
                                    <Option value='8 hours less'>8 hours less</Option>
                                    <Option value='12 hours less'>12 hours less</Option>
                                </Select>
                            </Col>
                        </Row>

                        {/**********airpot distance**********/}
                        <div style={{ marginTop: '10px' }}>
                            <Checkbox onChange={(e) => this.handleChange_checkBox(e.target.checked)}>
                                <span style={{ color: 'white' }}>
                                    Include airports within 70 miles</span>
                            </Checkbox>
                        </div>

                        {/**********search button**********/}
                        <Button style={{ marginTop: '10px' }} type="primary" icon="search"
                            onClick={() => this.handleClick_search()}>Search</Button>
                    </div>
                </div>

                {/**********modal select flight**********/}
                <Modal
                    visible={this.state.modalVisible}

                    title={<span>{this.state.airportFrom}
                        {this.state.roundTrip === 'round' ? < Icon type="swap" /> : <Icon type="swap-right" />}
                        {this.state.airportTo}
                    </span>}

                    onCancel={() => this.handleClick_modalCancel()}
                    footer={[
                        <Button key="back" onClick={() => this.handleClick_modalCancel()}>Cancel</Button>,
                    ]}
                >
                    {/*search results are collapsed*/}
                    <Collapse defaultActiveKey={['0']} >
                        {this.state.searchResults.map((item, index) =>
                            <Panel header={this.state.panelHeader[index]}
                                key={index}>
                                {item}
                            </Panel>
                        )}
                    </Collapse>
                </Modal>
            </div>
        );
    }
}

const passengers = (num) => {
    const optionArray = [];

    for (let i = 0; i < num; i++) {
        optionArray.push(<Option key={i} value={i}>{i}</Option>)
    }

    return optionArray;
}

export default App;

---------------------------------------------
App.css

.my-title {
    text-align: center;
}

.form-background {
    height: 450px;
    width: 100%;
    background: url(flight-background.jpg);
    background-repeat: no-repeat;
    background-size: 100% 100%;
}

.form-background2 {
    position: absolute;
    height: 430px;
    width: 400px;
    margin-top: 10px;
    margin-left: 10px;
    padding: 10px;
    background-color: rgba(0, 0, 0, 0.6);
}

.my-label {
    margin-top: 10px;
    color: white;
    clear: both;
}

.my-label2 {
    margin-top: 10px;
    color: white;
    font-size: 11px;
}

.my-header {
    background-color: white;
}

.translate-button {
    float: right;
}

.popover-item {
    cursor: pointer;
}

    .popover-item:nth-child(2n) {
        background-color: lightcyan;
    }

.ticketPrice {
    float: right;
    font: bold;
    font-size: 18px;
}

.flightInfo {
    font: bold;
}

-----------------------------------------------

airport.js


const airports = [
    {
        id: 1,
        airport: 'Hartsfield Jackson Atlanta International',
        code: 'ATL'
    },
    {
        id: 2,
        airport: 'Beijing Capital International',
        code: 'PEK'
    },
    {
        id: 3,
        airport: 'Dubai International ',
        code: 'DXB'
    },
    {
        id: 4,
        airport: 'Tokyo Haneda',
        code: 'HND'
    },
    {
        id: 5,
        airport: 'Los Angeles International ',
        code: 'LAX'
    },
    {
        id: 6,
        airport: "O'Hare International",
        code: 'ORD'
    },
    {
        id: 7,
        airport: 'London Heathrow',
        code: 'LHR'
    },
    {
        id: 8,
        airport: 'Hong Kong International',
        code: 'HKG'
    },
    {
        id: 9,
        airport: 'Shanghai Pudong International',
        code: 'PVG'
    },
    {
        id: 10,
        airport: 'Paris Charles de Gaulle',
        code: 'CDG'
    },
    {
        id: 11,
        airport: 'Amsterdam Schiphol ',
        code: 'AMS'
    },
    {
        id: 12,
        airport: "Dallas/Fort Worth International",
        code: 'DFW'
    },
    {
        id: 13,
        airport: 'Guangzhou Baiyun International',
        code: 'CAN'
    },
    {
        id: 14,
        airport: 'Frankfurt',
        code: 'FRA'
    },
    {
        id: 15,
        airport: 'Istanbul Ataturk Airport ',
        code: 'IST'
    },
    {
        id: 16,
        airport: 'Indira Gandhi International Airport',
        code: 'DEL'
    },
    {
        id: 17,
        airport: 'Soekarno-Hatta International Airport',
        code: 'CGK'
    },
    {
        id: 18,
        airport: "Singapore Changi Airport",
        code: 'SIN'
    },
];

export default airports;

---------------------------------------

reference:
http://chuanshuoge2.blogspot.com/2018/08/react-flight-reference.html
https://ant.design/

No comments:

Post a Comment