From 171489321daaff33289d9c0ba458d90aae5d3f77 Mon Sep 17 00:00:00 2001 From: Luis Villalobos Date: Sun, 26 Feb 2017 21:18:15 -0500 Subject: [PATCH 1/2] APIModule added & first request --- package.json | 3 +- src/env.js | 2 + src/modules/reservation/api.js | 13 + .../components/Reservation/index.jsx | 10 +- src/modules/reservation/container.jsx | 33 ++- src/modules/reservation/reducer.jsx | 271 +++++++++--------- src/modules/utils/APIModule.js | 112 ++++++++ src/modules/utils/store.jsx | 10 +- 8 files changed, 308 insertions(+), 146 deletions(-) create mode 100644 src/env.js create mode 100644 src/modules/reservation/api.js create mode 100644 src/modules/utils/APIModule.js diff --git a/package.json b/package.json index f5f45b3..8dbbddc 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "react": "^15.4.2", "react-dom": "^15.4.2", "react-redux": "^5.0.2", - "redux": "^3.6.0" + "redux": "^3.6.0", + "redux-thunk": "^2.2.0" } } diff --git a/src/env.js b/src/env.js new file mode 100644 index 0000000..d844367 --- /dev/null +++ b/src/env.js @@ -0,0 +1,2 @@ +export const API = 'http://api.labcomp.edwarbaron.me/api/v1'; +export default {}; diff --git a/src/modules/reservation/api.js b/src/modules/reservation/api.js new file mode 100644 index 0000000..0bc4486 --- /dev/null +++ b/src/modules/reservation/api.js @@ -0,0 +1,13 @@ +// APIModule +import { Module } from '../utils/APIModule'; + +export const init = new Module('init', [ + { + name: 'base', + method: 'GET', + url: 'timetable/base', + }]); + +export default { + base: init.actions.base.request, +}; diff --git a/src/modules/reservation/components/Reservation/index.jsx b/src/modules/reservation/components/Reservation/index.jsx index 29c8462..e9c0e2c 100644 --- a/src/modules/reservation/components/Reservation/index.jsx +++ b/src/modules/reservation/components/Reservation/index.jsx @@ -17,11 +17,15 @@ class Reservation extends Component { const { selected } = this.state; return ( ({ key, name: item.name, icon: item.icon }))} + list={infrastructure ? map(infrastructure, (item, key) => ({ key, name: item.name, icon: item.icon })) : []} selected={selected} > - - + {infrastructure && + [ + , + , + ] + } ); } diff --git a/src/modules/reservation/container.jsx b/src/modules/reservation/container.jsx index 636bd50..0842663 100644 --- a/src/modules/reservation/container.jsx +++ b/src/modules/reservation/container.jsx @@ -1,16 +1,37 @@ +import React, { Component, PropTypes } from 'react'; +import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import Reservation from './components/Reservation'; +import reservationActions from './api'; + +class Container extends Component { + componentWillMount() { + this.props.base(); + } + render() { + return ; + } +} + +Container.propTypes = { + base: PropTypes.func.isRequired, +}; + const mapStateToProps = (state) => { const { reservation } = state; return { - infrastructure: reservation.base.infrastructure, - blocks: reservation.blocks, - days: reservation.days, - data: reservation.data, - timetables: reservation.timetables, + infrastructure: reservation.base.response && reservation.base.response.infrastructures, + blocks: reservation.base.response && reservation.base.response.blocks, + days: reservation.base.response && reservation.base.response.days, + // data: reservation.data, + // timetables: reservation.timetables, }; }; -export default connect(mapStateToProps)(Reservation); +const mapDispatchToProps = dispatch => bindActionCreators({ + base: reservationActions.base, +}, dispatch); + +export default connect(mapStateToProps, mapDispatchToProps)(Container); diff --git a/src/modules/reservation/reducer.jsx b/src/modules/reservation/reducer.jsx index 96415e6..8632c65 100644 --- a/src/modules/reservation/reducer.jsx +++ b/src/modules/reservation/reducer.jsx @@ -1,29 +1,32 @@ import { combineReducers } from 'redux'; -const initialState = { - 1: { - name: 'Laboratorios', - icon: '', - rooms: { - 1: { - name: 'C01', - characteristics: [ - { - name: 'Infraestructura', - icon: '', - characteristics: [ - { - name: 'Aire Acondicionado', - icon: '', - value: 'Si', - }, - ], - }, - ], - }, - }, - }, -}; +import { init } from './api'; + +// const initialState = { +// 1: { +// name: 'Laboratorios', +// icon: '', +// rooms: { +// 1: { +// name: 'C01', +// characteristics: [ +// { +// name: 'Infraestructura', +// icon: '', +// characteristics: [ +// { +// name: 'Aire Acondicionado', +// icon: '', +// value: 'Si', +// }, +// ], +// }, +// ], +// }, +// }, +// }, +// }; + // const rooms = (state = {}, action = {}) => { // switch (action.type) { @@ -33,115 +36,119 @@ const initialState = { // } // }; -const infrastructure = (state = initialState, action = {}) => { - switch (action.type) { - default: - return state; - } -}; +// const infrastructure = (state = initialState, action = {}) => { +// switch (action.type) { +// default: +// return state; +// } +// }; -const base = combineReducers({ - infrastructure, -}); +// const base = combineReducers({ +// infrastructure, +// }); export default combineReducers({ - blocks: () => ({ - 1: '7:00', - 2: '8:00', - 3: '9:00', - 4: '10:00', - 5: '11:00', - 6: '12:00', - 7: '13:00', - 8: '14:00', - 9: '15:00', - 10: '16:00', - 11: '17:00', - 12: '18:00', - 13: '19:00', - 14: '20:00', - 15: '21:00', - 16: '22:00', - }), - days: () => ({ - 0: 'Lunes', - 1: 'Martes', - 2: 'Miércoles', - 3: 'Jueves', - 4: 'Vernes', - 5: 'Sábado', - 6: 'Domingo', - }), - base, - data: () => ({ - days: { - 0: { - blocks: { - 1: { - section: { - code: 'm1', - subject: 'Multimedia', - subject_code: '123', - color: '#e2c376', - }, - }, - 2: { - section: { - code: 'm1', - subject: 'Multimedia', - subject_code: '123', - color: '#e2c376', - }, - }, - }, - }, - 1: { - blocks: {}, - }, - 2: { - blocks: {}, - }, - 3: { - blocks: { - 1: {}, - 2: {}, - 3: {}, - 4: { - section: { - code: 'm1', - subject: 'Computación I', - subject_code: '123', - color: '#e2c376', - }, - }, - }, - }, - 4: { - blocks: {}, - }, - 5: { - blocks: {}, - }, - 6: { - blocks: {}, - }, - }, - }), - timetables: () => ({ - rows: [ - { - section: { - code: 'm1', - subject: 'Multimedia', - subject_code: '123', - color: '#e2c376', - }, - day: 0, - blocks: [ - 1, - 2, - ], - }, - ], - }), + base: init.actions.base.reducer, }); + +// export default combineReducers({ +// blocks: () => ({ +// 1: '7:00', +// 2: '8:00', +// 3: '9:00', +// 4: '10:00', +// 5: '11:00', +// 6: '12:00', +// 7: '13:00', +// 8: '14:00', +// 9: '15:00', +// 10: '16:00', +// 11: '17:00', +// 12: '18:00', +// 13: '19:00', +// 14: '20:00', +// 15: '21:00', +// 16: '22:00', +// }), +// days: () => ({ +// 0: 'Lunes', +// 1: 'Martes', +// 2: 'Miércoles', +// 3: 'Jueves', +// 4: 'Vernes', +// 5: 'Sábado', +// 6: 'Domingo', +// }), +// base, +// data: () => ({ +// days: { +// 0: { +// blocks: { +// 1: { +// section: { +// code: 'm1', +// subject: 'Multimedia', +// subject_code: '123', +// color: '#e2c376', +// }, +// }, +// 2: { +// section: { +// code: 'm1', +// subject: 'Multimedia', +// subject_code: '123', +// color: '#e2c376', +// }, +// }, +// }, +// }, +// 1: { +// blocks: {}, +// }, +// 2: { +// blocks: {}, +// }, +// 3: { +// blocks: { +// 1: {}, +// 2: {}, +// 3: {}, +// 4: { +// section: { +// code: 'm1', +// subject: 'Computación I', +// subject_code: '123', +// color: '#e2c376', +// }, +// }, +// }, +// }, +// 4: { +// blocks: {}, +// }, +// 5: { +// blocks: {}, +// }, +// 6: { +// blocks: {}, +// }, +// }, +// }), +// timetables: () => ({ +// rows: [ +// { +// section: { +// code: 'm1', +// subject: 'Multimedia', +// subject_code: '123', +// color: '#e2c376', +// }, +// day: 0, +// blocks: [ +// 1, +// 2, +// ], +// }, +// ], +// }), +// }); diff --git a/src/modules/utils/APIModule.js b/src/modules/utils/APIModule.js new file mode 100644 index 0000000..c54e804 --- /dev/null +++ b/src/modules/utils/APIModule.js @@ -0,0 +1,112 @@ +import { API } from '../../env'; + +const headers = new Headers(); +headers.append('Content-Type', 'application/json'); + +export class ActionType { + constructor(moduleName, name) { + this.keys = {}; + ['REQUEST', 'SUCCESS', 'FAILURE'].forEach((result) => { + const value = `${moduleName}/${name.toUpperCase()}@${result.toUpperCase()}`; + this.keys[result] = `${moduleName}_${name}_${result}`.toUpperCase(); + this[this.keys[result]] = value; + }); + } + getConstant(result) { + return this[this.keys[result]]; + } +} + +export class Action { + constructor(self, moduleName, initialState) { + this.initialState = Object.assign({ + requested: false, + }, initialState); + this.method = this._method(self); + this.url = this._url(self); + this.actionsType = new ActionType(moduleName, self.name); + this.reducer = this._reducer.bind(this); + this.request = this._request.bind(this); + } + _method(self) { + if (self.method) { + return self.method; + } else if (self.body) { + return 'POST'; + } + return 'GET'; + } + _url(self) { + if (self.url) { + return `${API}/${self.url}`; + } + return `${API}/`; + } + _reducer(state = this.initialState, action = {}) { + switch (action.type) { + case this.actionsType.getConstant('REQUEST'): + return Object.assign({}, state, { + requested: true, + }); + case this.actionsType.getConstant('SUCCESS'): + return Object.assign({}, state, { + completed: true, + requested: false, + response: action.args, + }); + case this.actionsType.getConstant('FAILURE'): + return Object.assign({}, state, { + requested: false, + error: action.args, + }); + default: + return state; + } + } + _request(body = null) { + return (dispatch) => { + const data = { + method: this.method, + body, + }; + fetch(this.url, data) + .then(json => this._check(json)) + .then((json) => { dispatch(this.getState('REQUEST')); return json; }) + .then((json) => { dispatch(this.getState('SUCCESS', json)); return json; }) + .catch(error => dispatch(this.getState('FAILURE', error))); + }; + } + _check(response) { + let res = response && response.statusText !== 'No Content' ? response.json() : {}; + if (!response.ok) { + res = res.then((err) => { + throw new Error(err.message); + }); + } + return res; + } + _success(json) { + this.object.afterSuccess(json); + return this.getState('SUCCESS', json); + } + _failure(json) { + this.object.errorHandling(json); + return this.getState('FAILURE', json); + } + getState(result, args = {}) { + return { + type: this.actionsType.getConstant(result), + args, + }; + } +} + +export class Module { + constructor(name, actions, initialState = {}) { + this.name = name; + this.actions = {}; + actions.forEach((action) => { + this.actions[action.name] = new Action(action, name, initialState); + }); + } +} diff --git a/src/modules/utils/store.jsx b/src/modules/utils/store.jsx index 3271394..4bbddec 100644 --- a/src/modules/utils/store.jsx +++ b/src/modules/utils/store.jsx @@ -1,16 +1,18 @@ -import { createStore } from 'redux'; - +import { createStore, applyMiddleware } from 'redux'; +import thunk from 'redux-thunk'; // import Middleware, { // LOGGER_MIDDLEWARE, // THUNK_MIDDLEWARE // } from './middlewares/factory'; -// const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; +const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; export default (initialState, reducers) => ( createStore( reducers, initialState, - window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), + composeEnhancers( + applyMiddleware(thunk) + ), ) ); From 3fce855082243233b41abcfdcb47e970823e4707 Mon Sep 17 00:00:00 2001 From: Luis Villalobos Date: Wed, 1 Mar 2017 22:35:47 -0500 Subject: [PATCH 2/2] Advances --- .eslintrc | 3 +- package.json | 2 +- src/modules/reservation/actionTypes.js | 2 + src/modules/reservation/actions.js | 10 ++ src/modules/reservation/api.js | 16 ++++ .../reservation/components/Calendar/index.jsx | 3 +- .../components/Reservation/index.jsx | 12 +-- src/modules/reservation/container.jsx | 18 +++- src/modules/reservation/reducer.jsx | 95 +++++++++++++++++-- src/modules/utils/APIModule.js | 14 +-- 10 files changed, 141 insertions(+), 34 deletions(-) diff --git a/.eslintrc b/.eslintrc index 31ba803..6839e27 100644 --- a/.eslintrc +++ b/.eslintrc @@ -7,6 +7,7 @@ "extends": "airbnb", "rules": { "indent": [2, 2], - "no-underscore-dangle": [2, { "allow": ["__REDUX_DEVTOOLS_EXTENSION__"] }] + "no-underscore-dangle": 0, + "class-methods-use-this": 0 } } diff --git a/package.json b/package.json index 8dbbddc..23605a9 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "lint:project": "eslint --cache ./src/modules", "lint:styles": "sass-lint './src/styles/**/*.s+(a|c)ss' -v -q", "clean": "./scripts/clean.sh", - "main": "npm-run-all clean lint build webpack", + "main": "npm-run-all clean build webpack", "main:watch": "concurrently --kill-others \"npm run watch\" \"webpack-dev-server\"", "build": "./scripts/build.sh", "watch": "./scripts/watch.sh", diff --git a/src/modules/reservation/actionTypes.js b/src/modules/reservation/actionTypes.js index e69de29..6e9af46 100644 --- a/src/modules/reservation/actionTypes.js +++ b/src/modules/reservation/actionTypes.js @@ -0,0 +1,2 @@ +export const RESERVATION_SET_INITIAL_IDS = 'reservation/SET_INITIAL_IDS'; +export default {}; diff --git a/src/modules/reservation/actions.js b/src/modules/reservation/actions.js index e69de29..fc9165b 100644 --- a/src/modules/reservation/actions.js +++ b/src/modules/reservation/actions.js @@ -0,0 +1,10 @@ +import { RESERVATION_SET_INITIAL_IDS } from './actionTypes'; + +const setInitialIDs = iDs => ({ + type: RESERVATION_SET_INITIAL_IDS, + payload: iDs, +}); + +export default { + setInitialIDs, +}; diff --git a/src/modules/reservation/api.js b/src/modules/reservation/api.js index 0bc4486..172503e 100644 --- a/src/modules/reservation/api.js +++ b/src/modules/reservation/api.js @@ -1,13 +1,29 @@ +import map from 'lodash/map'; + // APIModule import { Module } from '../utils/APIModule'; +import actions from './actions'; export const init = new Module('init', [ { name: 'base', method: 'GET', url: 'timetable/base', + afterSuccess: (dispatch, json) => { + const infrastructureID = map(json.infrastructures, (infrastructure, key) => (key))[0]; + const roomID = map(json.infrastructures[infrastructureID].rooms, (room, key) => (key))[0]; + const iDs = [infrastructureID, roomID]; + dispatch(actions.setInitialIDs(iDs)); + }, + }, + { + name: 'getCalendarByRoom', + method: 'GET', + url: 'timetable/room/:id', + afterSuccess: () => (null), }]); export default { base: init.actions.base.request, + getCalendarByRoom: init.actions.getCalendarByRoom.request, }; diff --git a/src/modules/reservation/components/Calendar/index.jsx b/src/modules/reservation/components/Calendar/index.jsx index 343b40a..ec6ac19 100644 --- a/src/modules/reservation/components/Calendar/index.jsx +++ b/src/modules/reservation/components/Calendar/index.jsx @@ -1,6 +1,7 @@ import React, { Component, PropTypes } from 'react'; import map from 'lodash/map'; import keys from 'lodash/keys'; +import isEmpty from 'lodash/isEmpty'; // import classnames from 'classnames'; @@ -70,7 +71,7 @@ class Calendar extends Component { ))}
- { map(data.days, (day, key) => ( + { !isEmpty(data) && map(data.days, (day, key) => (
{ map(day.blocks, (block, blockKey) => { if (block.section) { diff --git a/src/modules/reservation/components/Reservation/index.jsx b/src/modules/reservation/components/Reservation/index.jsx index e9c0e2c..b6e145d 100644 --- a/src/modules/reservation/components/Reservation/index.jsx +++ b/src/modules/reservation/components/Reservation/index.jsx @@ -1,5 +1,6 @@ import React, { Component, PropTypes } from 'react'; import map from 'lodash/map'; +import isEmpty from 'lodash/isEmpty'; import Tabs from '../../../main/components/Tabs'; import Rooms from '../Rooms'; @@ -15,17 +16,16 @@ class Reservation extends Component { render() { const { infrastructure, blocks, days, data } = this.props; const { selected } = this.state; + const rooms = !isEmpty(infrastructure) ? infrastructure[selected].rooms : {}; return ( ({ key, name: item.name, icon: item.icon })) : []} selected={selected} > - {infrastructure && - [ - , - , - ] - } +
+ + +
); } diff --git a/src/modules/reservation/container.jsx b/src/modules/reservation/container.jsx index 0842663..ba2bd4b 100644 --- a/src/modules/reservation/container.jsx +++ b/src/modules/reservation/container.jsx @@ -1,6 +1,7 @@ import React, { Component, PropTypes } from 'react'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; +import isEmpty from 'lodash/isEmpty'; import Reservation from './components/Reservation'; @@ -10,6 +11,11 @@ class Container extends Component { componentWillMount() { this.props.base(); } + componentWillReceiveProps(nextProps) { + if (!isEmpty(nextProps.infrastructure)) { + this.props.getCalendarByRoom(nextProps.selected.room); + } + } render() { return ; } @@ -17,21 +23,23 @@ class Container extends Component { Container.propTypes = { base: PropTypes.func.isRequired, + getCalendarByRoom: PropTypes.func.isRequired, }; const mapStateToProps = (state) => { const { reservation } = state; return { - infrastructure: reservation.base.response && reservation.base.response.infrastructures, - blocks: reservation.base.response && reservation.base.response.blocks, - days: reservation.base.response && reservation.base.response.days, - // data: reservation.data, - // timetables: reservation.timetables, + infrastructure: reservation.base.response ? reservation.base.response.infrastructures : {}, + blocks: reservation.base.response ? reservation.base.response.blocks : {}, + days: reservation.base.response ? reservation.base.response.days : {}, + data: reservation.data, + selected: reservation.selected, }; }; const mapDispatchToProps = dispatch => bindActionCreators({ base: reservationActions.base, + getCalendarByRoom: reservationActions.getCalendarByRoom, }, dispatch); export default connect(mapStateToProps, mapDispatchToProps)(Container); diff --git a/src/modules/reservation/reducer.jsx b/src/modules/reservation/reducer.jsx index 8632c65..77757fa 100644 --- a/src/modules/reservation/reducer.jsx +++ b/src/modules/reservation/reducer.jsx @@ -2,6 +2,8 @@ import { combineReducers } from 'redux'; import { init } from './api'; +import { RESERVATION_SET_INITIAL_IDS } from './actionTypes'; + // const initialState = { // 1: { // name: 'Laboratorios', @@ -36,19 +38,92 @@ import { init } from './api'; // } // }; -// const infrastructure = (state = initialState, action = {}) => { -// switch (action.type) { -// default: -// return state; -// } -// }; - -// const base = combineReducers({ -// infrastructure, -// }); +const selected = (state = {}, action = {}) => { + switch (action.type) { + case RESERVATION_SET_INITIAL_IDS: + return Object.assign({}, state, { + infrastructure: action.payload[0], + room: action.payload[1], + }); + default: + return state; + } +}; export default combineReducers({ base: init.actions.base.reducer, + selected, + data: () => ({ + days: { + 0: { + blocks: { + 1: { + section: { + code: 'm1', + subject: 'Multimedia', + subject_code: '123', + color: '#e2c376', + }, + }, + 2: { + section: { + code: 'm1', + subject: 'Multimedia', + subject_code: '123', + color: '#e2c376', + }, + }, + }, + }, + 1: { + blocks: {}, + }, + 2: { + blocks: {}, + }, + 3: { + blocks: { + 1: {}, + 2: {}, + 3: {}, + 4: { + section: { + code: 'm1', + subject: 'Computación I', + subject_code: '123', + color: '#e2c376', + }, + }, + }, + }, + 4: { + blocks: {}, + }, + 5: { + blocks: {}, + }, + 6: { + blocks: {}, + }, + }, + }), + timetables: () => ({ + rows: [ + { + section: { + code: 'm1', + subject: 'Multimedia', + subject_code: '123', + color: '#e2c376', + }, + day: 0, + blocks: [ + 1, + 2, + ], + }, + ], + }), }); // export default combineReducers({ diff --git a/src/modules/utils/APIModule.js b/src/modules/utils/APIModule.js index c54e804..86137e1 100644 --- a/src/modules/utils/APIModule.js +++ b/src/modules/utils/APIModule.js @@ -27,6 +27,8 @@ export class Action { this.actionsType = new ActionType(moduleName, self.name); this.reducer = this._reducer.bind(this); this.request = this._request.bind(this); + this.afterSuccess = self.afterSuccess; + this.errorHandling = self.errorHandling; } _method(self) { if (self.method) { @@ -72,11 +74,11 @@ export class Action { fetch(this.url, data) .then(json => this._check(json)) .then((json) => { dispatch(this.getState('REQUEST')); return json; }) - .then((json) => { dispatch(this.getState('SUCCESS', json)); return json; }) + .then((json) => { dispatch(this.getState('SUCCESS', json)); this.afterSuccess(dispatch, json); return json; }) .catch(error => dispatch(this.getState('FAILURE', error))); }; } - _check(response) { + _check(response) { // warning here let res = response && response.statusText !== 'No Content' ? response.json() : {}; if (!response.ok) { res = res.then((err) => { @@ -85,14 +87,6 @@ export class Action { } return res; } - _success(json) { - this.object.afterSuccess(json); - return this.getState('SUCCESS', json); - } - _failure(json) { - this.object.errorHandling(json); - return this.getState('FAILURE', json); - } getState(result, args = {}) { return { type: this.actionsType.getConstant(result),