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 f5f45b3..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", @@ -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/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 new file mode 100644 index 0000000..172503e --- /dev/null +++ b/src/modules/reservation/api.js @@ -0,0 +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 29c8462..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,13 +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 }))} + list={infrastructure ? map(infrastructure, (item, key) => ({ key, name: item.name, icon: item.icon })) : []} selected={selected} > - - +
+ + +
); } diff --git a/src/modules/reservation/container.jsx b/src/modules/reservation/container.jsx index 636bd50..ba2bd4b 100644 --- a/src/modules/reservation/container.jsx +++ b/src/modules/reservation/container.jsx @@ -1,16 +1,45 @@ +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'; +import reservationActions from './api'; + +class Container extends Component { + componentWillMount() { + this.props.base(); + } + componentWillReceiveProps(nextProps) { + if (!isEmpty(nextProps.infrastructure)) { + this.props.getCalendarByRoom(nextProps.selected.room); + } + } + render() { + return ; + } +} + +Container.propTypes = { + base: PropTypes.func.isRequired, + getCalendarByRoom: PropTypes.func.isRequired, +}; + const mapStateToProps = (state) => { const { reservation } = state; return { - infrastructure: reservation.base.infrastructure, - blocks: reservation.blocks, - days: reservation.days, + 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, + selected: reservation.selected, }; }; -export default connect(mapStateToProps)(Reservation); +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 96415e6..77757fa 100644 --- a/src/modules/reservation/reducer.jsx +++ b/src/modules/reservation/reducer.jsx @@ -1,29 +1,34 @@ 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'; + +import { RESERVATION_SET_INITIAL_IDS } from './actionTypes'; + +// 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,46 +38,21 @@ const initialState = { // } // }; -const infrastructure = (state = initialState, action = {}) => { +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; } }; -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, + base: init.actions.base.reducer, + selected, data: () => ({ days: { 0: { @@ -145,3 +125,105 @@ export default combineReducers({ ], }), }); + +// 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..86137e1 --- /dev/null +++ b/src/modules/utils/APIModule.js @@ -0,0 +1,106 @@ +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); + this.afterSuccess = self.afterSuccess; + this.errorHandling = self.errorHandling; + } + _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)); this.afterSuccess(dispatch, json); return json; }) + .catch(error => dispatch(this.getState('FAILURE', error))); + }; + } + _check(response) { // warning here + let res = response && response.statusText !== 'No Content' ? response.json() : {}; + if (!response.ok) { + res = res.then((err) => { + throw new Error(err.message); + }); + } + return res; + } + 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) + ), ) );