Sunday, 24 June 2018

react + redux schedule





Index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { Provider } from "react-redux";
import store from "./store";
import 'bootstrap/dist/css/bootstrap.min.css';

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>, document.getElementById('root'));

registerServiceWorker();

---------------------------------------------------------------------------------
App.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import './App.css';
import AddForm from './forms/add';
import DeleteForm from './forms/delete';
import UpdateForm from './forms/update'
import { display_rawSchedules } from './quickFunctions.js';

class App extends Component {
    render() {
        return (
            <div>
                {display_rawSchedules(this.props.schedules)}
                <AddForm />
                <DeleteForm schedules={this.props.schedules} />
                <UpdateForm schedules={this.props.schedules} />
            </div>
        );
    }
}

export default connect(
    (store) => {
        return {
            schedules: store.schedules.schedules,
        };
    }
)(App);

---------------------------------------------------------
store.js

import { applyMiddleware, createStore } from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import promiseMiddleware from 'redux-promise-middleware';

import reducer from "./reducers";

const middleware = applyMiddleware(promiseMiddleware(), thunk, logger);

export default createStore(reducer, middleware);

------------------------------------------------------------------------------
quickFunctions.js

import React from 'react';

export const display_rawSchedules = (data) => {

    const scheduleView = data.map((item, index) => {

        const matchView = data[index].matches.map((item2, index2) =>
            <div key={index2}>
                {"{"}
                home: {item2.home}, away: {item2.away},
                home score: {item2.homeScore}, away score: {item2.awayScore}
                {"}, "}
            </div>
        );

        return (<div key={index}>
            date: {item.date.toDateString()}<br />
            matches: {matchView}<br />
        </div>);

    })
    return (<div>{scheduleView}</div>);

}

export const dateToString = (date) => {
    const year = date.getFullYear().toString();
    const month = date.getMonth() + 1;
    const month0 = month < 10 ? '0' + month.toString() : month.toString();
    const day = date.getDate();
    const day0 = day < 10 ? '0' + day.toString() : day.toString();
    const newDate = year + '-' + month0 + '-' + day0;
    return newDate;
};

export const stringToDate = (input_string) => {
    const year_month_day = input_string.split('-');
    const year = parseInt(year_month_day[0]);
    const month = parseInt(year_month_day[1]) - 1;
    const day = parseInt(year_month_day[2]);
    return new Date(year, month, day);
}

----------------------------------------------------------------------
actions/scheduleAction.js

export function addSchedule(form) {
    return {
        type: "add_schedule",
        payload: form
    }
}

export function deleteAll(schedule_index) {
    return {
        type: "delete_all",
        payload: schedule_index
    }
}

export function deleteSingle(schedule_index, match_index) {
    return {
        type: "delete_single",
        payload: {
            scheduleIndex: schedule_index,
            matchIndex: match_index,
        }
    }
}

export function updateSchedule(form, schedule_index, match_index) {
    return {
        type: "update_schedule",
        payload: {
            form: form,
            scheduleIndex: schedule_index,
            matchIndex: match_index,
        }
    }
}

--------------------------------------------------------------------
reducers/index.js

import { combineReducers } from 'redux';
import schedules from "./scheduleReducer";

export default combineReducers(
    {
        schedules
    })

-------------------------------------------------------
reducers/scheduleReducer.js

import { stringToDate, dateToString } from '../quickFunctions'

export default function reducer(
    state = {
        schedules: [
            {
                date: new Date(2018, 4, 22),
                matches: [
                    { home: "santos", away: "real garcilaso", homeScore: 0, awayScore: 0 },
                    { home: "atletico nacional", away: "colo colo", homeScore: 0, awayScore: 0 },
                    { home: "corinthians", away: "millonarios", homeScore: 0, awayScore: 1 },
                    { home: "independiente", away: "deportivolara", homeScore: 2, awayScore: 0 },
                ]
            },
            {
                date: new Date(2018, 4, 21),
                matches: [
                    { home: "toronto fc", away: "fc dallas", homeScore: 0, awayScore: 2 },
                    { home: "houston dynamo", away: "new york city fc", homeScore: 2, awayScore: 1 },
                    { home: "portland thorns fc", away: "utah royals fc", homeScore: 2, awayScore: 1 },
                    { home: "los angeles galaxy", away: "san joes earthquakes", homeScore: 0, awayScore: 0 },
                ]
            },
            {
                date: new Date(2018, 5, 4),
                matches: [
                    { home: "thailand", away: "china pr", homeScore: 0, awayScore: 1 },
                    { home: "austria", away: "germany", homeScore: 3, awayScore: 1 },
                    { home: "england", away: "nigeria", homeScore: 1, awayScore: 0 },
                    { home: "sweden", away: "denmark", homeScore: 0, awayScore: 0 },
                ]
            },
            {
                date: new Date(2018, 4, 28),
                matches: [
                    { home: "brazil", away: "croatia", homeScore: 2, awayScore: 0 },
                    { home: "costa rica", away: "northern ireland", homeScore: 3, awayScore: 0 },
                    { home: "saudi arabia", away: "peru", homeScore: 0, awayScore: 3 },
                    { home: "bahia", away: "gremio", homeScore: 0, awayScore: 1 },
                ]
            },
        ],

        activeTab: "Monday",
        dateFilter: "",
    },
    action
) {
    switch (action.type) {
        case "add_schedule": {

            const newMatch = {
                home: action.payload.home,
                away: action.payload.away,
                homeScore: action.payload.homeScore,
                awayScore: action.payload.awayScore,
            };

            const newSchedules = [...state.schedules];
            const findDate_index = newSchedules.findIndex(x => dateToString(x.date) === action.payload.date);

            //add to existing schedule
            if (findDate_index >= 0) {
                newSchedules[findDate_index].matches.push(newMatch);
            }
            //create new schedule
            else {
                const newSchedule =
                    {
                        date: stringToDate(action.payload.date),
                        matches: [
                            newMatch,
                        ]
                    };

                newSchedules.push(newSchedule);
            }

            return { ...state, schedules: newSchedules }
        }

        case "delete_all": {
            const newSchedules = [...state.schedules];
            newSchedules.splice(action.payload, 1);

            return { ...state, schedules: newSchedules }
        }

        case "delete_single": {
            const newSchedules = [...state.schedules];
            newSchedules[action.payload.scheduleIndex].
                matches.splice(action.payload.matchIndex, 1);

            return { ...state, schedules: newSchedules }
        }

        case "update_schedule": {
            const newSchedules = [...state.schedules];

            newSchedules[action.payload.scheduleIndex].
                matches[action.payload.matchIndex] = {
                home: action.payload.form.home,
                away: action.payload.form.away,
                homeScore: action.payload.form.homeScore,
                awayScore: action.payload.form.awayScore,
                }

            return { ...state, schedules: newSchedules }
        }

        default:
            break;
    }
    return state;
}

-------------------------------------------------------------------------------
forms/add.js

import React, { Component } from 'react';
import { Button, Form, FormGroup, Input } from 'reactstrap';
import { stringToDate } from '../quickFunctions.js';
import { connect } from 'react-redux';
import { addSchedule } from '../actions/scheduleAction';
import '../App.css';

export class AddForm extends Component {
    constructor(props) {
        super(props);
        this.state = {
            date: "",
            home: "",
            away: "",
            homeScore: 0,
            awayScore: 0,
            status: "",
        };
    }

    handle_submit(e) {

        if (this.state.home === "" || this.state.away === "" || this.state.date === "") {
            return false;
        }
        else {
            const formData = {
                date: this.state.date,
                home: this.state.home,
                away: this.state.away,
                homeScore: this.state.homeScore,
                awayScore: this.state.awayScore,
            };

            this.props.dispatch(addSchedule(formData));
        }

        e.preventDefault();

        const messageStatus = "successfully added to " + stringToDate(this.state.date).toDateString();
        this.setState({ status: messageStatus });
    }
    render() {
        return (
            <div className="my-schedule-form-add">
                <h4>Add Schedule <span className="my-status">{this.state.status}</span></h4>
                <Form inline onSubmit={(e) => this.handle_submit(e)}>
                    <FormGroup >
                        <Input type="date" bsSize="sm" name="matchDate" placeholder="Match Date" required="required"
                            onChange={(e) => this.setState({ date: e.target.value })} />
                    </FormGroup>
                    <FormGroup >
                        <Input bsSize="sm" placeholder="Home Team" required="required"
                            onChange={(e) => this.setState({ home: e.target.value })} />
                    </FormGroup>
                    <FormGroup >
                        <Input bsSize="sm" placeholder="Away Team" required="required"
                            onChange={(e) => this.setState({ away: e.target.value })} />
                    </FormGroup>
                    <FormGroup >
                        <Input type="number" bsSize="sm" placeholder="Home Score"
                            onChange={(e) => this.setState({ homeScore: e.target.value })} />
                    </FormGroup>
                    <FormGroup >
                        <Input type="number" bsSize="sm" placeholder="Away Score"
                            onChange={(e) => this.setState({ awayScore: e.target.value })} />
                    </FormGroup>

                    <Button size="sm" type="submit" onClick={() => this.setState({ status: "" })}> Submit</Button>
                </Form>
            </div>
        );
    }
}

export default connect(
    (store) => {
        return {
            schedules: store.schedules.schedules,
        };
    }
)(AddForm);

------------------------------------------------------------------
forms/delete.js

import React, { Component } from 'react';
import { Button, Input, Row, Col } from 'reactstrap';
import { dateToString } from '../quickFunctions.js';
import { connect } from 'react-redux';
import { deleteAll, deleteSingle } from '../actions/scheduleAction.js';
import '../App.css';

export class DeleteForm extends Component {
    constructor(props) {
        super(props);
        this.state = {
            selectedMatch: 0,
            selectedDate: "",
            dateDisable: false,
            matchDisable: false,
            status:"",
        };
    }

    handle_selectDate(e) {
        this.setState({
            dateDisable: true,
            selectedDate: e.target.value,
        });

    }

    handle_selectMatch(e) {
        this.setState({
            matchDisable: true,
            selectedMatch: parseInt(e.target.value.split(' ')[0].slice(1)) - 1,
        });
    }

    handle_deleteSingle() {
        const selectedSchedule_index = this.props.schedules.findIndex(
            x => dateToString(x.date) === this.state.selectedDate);

        this.props.dispatch(
            deleteSingle(selectedSchedule_index, this.state.selectedMatch));

        const messageStatus = "deleted " + this.state.selectedDate + " match " +
            (this.state.selectedMatch + 1).toString();
        this.setState({ status: messageStatus });
    }

    handle_deleteAll() {
        const selectedSchedule_index = this.props.schedules.findIndex(
            x => dateToString(x.date) === this.state.selectedDate);

        this.props.dispatch(deleteAll(selectedSchedule_index));

        const messageStatus = "deleted " + this.state.selectedDate;
        this.setState({ status: messageStatus });
    }

    render() {
        const dateOptions = this.props.schedules.
            map((item, index) => {
                const dateString = dateToString(item.date);
                return (
                    <option key={dateString}>{dateString}</option>);
            }).
            sort((a, b) => {
                return a.key.localeCompare(b.key)
            });

        dateOptions.unshift(<option key={-1} disabled={this.state.dateDisable}>
            select date</option>);

        const selectedSchedule_index = this.props.schedules.findIndex(
            x => dateToString(x.date) === this.state.selectedDate);

        const matchOptions = selectedSchedule_index >= 0 ?

            this.props.schedules[selectedSchedule_index].matches.
                map((item, index) =>
                    <option key={index}>#{index + 1} {item.home} vs {item.away}</option>
                )
            : [];

        matchOptions.unshift(<option key={-1} disabled={this.state.matchDisable}>
            select match</option>);

        return (
            <div className="my-schedule-form-delete">
                <h4>Delete Schedule <span className="my-status">{this.state.status}</span></h4>
                <Row>
                    <Col xs="3">
                        <Input type="select" bsSize="sm"
                            onChange={(e) => this.handle_selectDate(e)}>
                            {dateOptions}</Input>
                    </Col>
                    <Col xs="5">
                        <Input type="select" bsSize="sm" disabled={!this.state.dateDisable}
                            onChange={(e) => this.handle_selectMatch(e)}>
                            {matchOptions}</Input>
                    </Col>
                    <Col xs="2">
                        <Button size="sm" disabled={!this.state.matchDisable}
                            onClick={() => this.handle_deleteSingle()}>
                            Delete single</Button>
                    </Col>
                    <Col xs="2">
                        <Button size="sm" disabled={!this.state.dateDisable}
                            onClick={() => this.handle_deleteAll()}>
                            Delete all</Button>
                    </Col>
                </Row>
            </div>
        );
    }
}


export default connect(
    (store) => {
        return {
            schedules: store.schedules.schedules,
        };
    }
)(DeleteForm);

------------------------------------------------------------------
forms/update.js

import React, { Component } from 'react';
import { Button, Form, FormGroup, Input, Row, Col } from 'reactstrap';
import { dateToString, stringToDate } from '../quickFunctions.js';
import { connect } from 'react-redux';
import { updateSchedule} from '../actions/scheduleAction.js';
import '../App.css';

export class UpdateForm extends Component {
    constructor(props) {
        super(props);
        this.state = {
            selectedMatch: 0,
            selectedDate: "",
            dateDisable: false,
            matchDisable: false,
            home: "Home Team",
            away: "Away Team",
            homeScore: 0,
            awayScore: 0,
            status:"select date -> match -> update input -> submit",
        };
    }

    handle_selectDate(e) {
        this.setState({
            dateDisable: true,
            selectedDate: e.target.value,
        });

    }

    handle_selectMatch(e) {
        const matchIndex = parseInt(e.target.value.split(' ')[0].slice(1)) - 1;
        const scheduleIndex = this.props.schedules.findIndex(
            x => dateToString(x.date) === this.state.selectedDate);

        const matchSelected = this.props.schedules[scheduleIndex].matches[matchIndex];

        this.setState({
            matchDisable: true,
            selectedMatch: matchIndex,
            home: matchSelected.home,
            away: matchSelected.away,
            homeScore: matchSelected.homeScore,
            awayScore: matchSelected.awayScore,
        });
    }

    handle_submit(e) {

        if (this.state.home === "" || this.state.away === "") {
            return false;
        }
        else {
            const formData = {
                home: this.state.home,
                away: this.state.away,
                homeScore: this.state.homeScore,
                awayScore: this.state.awayScore,
            };

            const selectedSchedule_index = this.props.schedules.findIndex(
                x => dateToString(x.date) === this.state.selectedDate);

            this.props.dispatch(updateSchedule(
                formData,
                selectedSchedule_index,
                this.state.selectedMatch,
            ));
        }

        e.preventDefault();

        const messageStatus = "updated " + this.state.selectedDate + " match " +
            (this.state.selectedMatch+1).toString();
        this.setState({ status: messageStatus });
    }

    render() {
        const dateOptions = this.props.schedules.
            map((item, index) => {
                const dateString = dateToString(item.date);
                return (
                    <option key={dateString}>{dateString}</option>);
            }).
            sort((a, b) => {
                return a.key.localeCompare(b.key)
            });

        dateOptions.unshift(<option key={-1} disabled={this.state.dateDisable}>
            select date</option>);

        const selectedSchedule_index = this.props.schedules.findIndex(
            x => dateToString(x.date) === this.state.selectedDate);

        const matchOptions = selectedSchedule_index >= 0 ?

            this.props.schedules[selectedSchedule_index].matches.
                map((item, index) =>
                    <option key={index}>#{index + 1} {item.home} vs {item.away}</option>
                )
            : [];

        matchOptions.unshift(<option key={-1} disabled={this.state.matchDisable}>
            select match</option>);

        return (
            <div className="my-schedule-form-update">
                <h4>update Schedule <span className="my-status">{this.state.status}</span></h4>
                <Row>
                    <Col xs="3">
                        <Input type="select" bsSize="sm"
                            onChange={(e) => this.handle_selectDate(e)}>
                            {dateOptions}</Input>
                    </Col>
                    <Col xs="5">
                        <Input type="select" bsSize="sm" disabled={!this.state.dateDisable}
                            onChange={(e) => this.handle_selectMatch(e)}>
                            {matchOptions}</Input>
                    </Col>                                     
                </Row>

                <Form inline onSubmit={(e) => this.handle_submit(e)} >             
                    <FormGroup >
                        <Input bsSize="sm" placeholder={this.state.home}
                            onChange={(e) => this.setState({ home: e.target.value })} />
                    </FormGroup>
                    <FormGroup >
                        <Input bsSize="sm" placeholder={this.state.away}
                            onChange={(e) => this.setState({ away: e.target.value })} />
                    </FormGroup>
                    <FormGroup >
                        <span id="update_homeScore">
                            <span className="my-tooltip">home score</span>
                            <Input type="number" bsSize="sm" placeholder={this.state.homeScore}
                                onChange={(e) => e.target.value !== "" ? this.setState({ homeScore: e.target.value }) : null}
                                disabled={this.state.disable_input} />
                        </span>
                    </FormGroup>
                    <FormGroup >
                        <span id="update_awayScore">
                            <span className="my-tooltip">away score</span>
                            <Input type="number" bsSize="sm" placeholder={this.state.awayScore}
                                onChange={(e) => e.target.value !== "" ? this.setState({ awayScore: e.target.value }) : null}
                                disabled={this.state.disable_input} />
                        </span>
                    </FormGroup>

                    <Button size="sm" type="submit" disabled={!this.state.matchDisable}
                        onClick={() => this.setState({ status: "select date -> match -> update input -> submit" })}>
                        Submit</Button>
                </Form>
            </div>
        );
    }
}


export default connect(
    (store) => {
        return {
            schedules: store.schedules.schedules,
        };
    }
)(UpdateForm);

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

reference:
http://chuanshuoge2.blogspot.com/search?q=react-redux
http://chuanshuoge2.blogspot.com/search?q=react+schedule
http://chuanshuoge2.blogspot.com/search?q=react+git
http://chuanshuoge2.blogspot.com/2018/06/react-redux-schedule.html


1 comment: