From d0eaffd2d97e8ce69ed3319762966cde1b022dbb Mon Sep 17 00:00:00 2001 From: ormus Date: Wed, 30 Mar 2016 22:07:38 +0300 Subject: [PATCH] Issue #28: Specoffer list --- package.json | 3 +- src/index.js | 1 + src/modules/enrolments/helpers.js | 3 +- src/modules/navbar/NavBar.jsx | 8 ++ .../rating/container/SpecofferChooser.jsx | 2 +- src/modules/specoffers/actions.js | 19 ++++ src/modules/specoffers/constants.js | 17 ++++ .../specoffers/containers/SpecofferInfo.js | 7 ++ .../containers/SpecoffersListPage.jsx | 97 +++++++++++++++++++ src/modules/specoffers/helpers.js | 50 ++++++++++ src/modules/specoffers/reducer.js | 57 +++++++++++ src/system/reducers/reducers.js | 2 + src/system/routes.js | 5 + 13 files changed, 267 insertions(+), 4 deletions(-) create mode 100644 src/modules/specoffers/actions.js create mode 100644 src/modules/specoffers/constants.js create mode 100644 src/modules/specoffers/containers/SpecofferInfo.js create mode 100644 src/modules/specoffers/containers/SpecoffersListPage.jsx create mode 100644 src/modules/specoffers/helpers.js create mode 100644 src/modules/specoffers/reducer.js diff --git a/package.json b/package.json index fe55eec..4fc3377 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "ums-frontned", + "name": "ums-frontend", "version": "0.0.1", "description": "", "homepage": "https://github.com/mkozhukharenko/ums-frontend", @@ -25,6 +25,7 @@ "amcharts3": "github:amcharts/amcharts3", "ammap3": "github:amcharts/ammap3", "bootstrap": "^3.3.6", + "fixed-data-table": "^0.6.0", "history": "2.0.0", "lodash": "^4.6.1", "lscache": "^1.0.5", diff --git a/src/index.js b/src/index.js index 2bdd1e7..91a23e3 100644 --- a/src/index.js +++ b/src/index.js @@ -6,6 +6,7 @@ import store from 'store'; import routes from './system/routes'; import '../assets/stylesheets/index.css'; import 'bootstrap/dist/css/bootstrap.css'; +import 'fixed-data-table/dist/fixed-data-table.css'; import {syncHistoryWithStore} from 'react-router-redux'; // Create an enhanced history that syncs navigation events with the store diff --git a/src/modules/enrolments/helpers.js b/src/modules/enrolments/helpers.js index d64c603..3ef69f8 100644 --- a/src/modules/enrolments/helpers.js +++ b/src/modules/enrolments/helpers.js @@ -11,8 +11,7 @@ let { /** * check if data is loaded - * @param storeState - * @param reducerName + * @param entityData * @returns {boolean} */ export function isEntityDataLoaded(entityData) { diff --git a/src/modules/navbar/NavBar.jsx b/src/modules/navbar/NavBar.jsx index a3fef8d..443a386 100644 --- a/src/modules/navbar/NavBar.jsx +++ b/src/modules/navbar/NavBar.jsx @@ -29,6 +29,10 @@ export default class NavBar extends Component { Статистика + + Пропозиції + + {this.authItem('rating', 'Рейтинг', {query: this.props.ratingQueryParams})} {this.authItem('persons', 'Персони')} @@ -91,6 +95,10 @@ function select(state) { ratingQueryParams: { departmentId: state.rating.specofferChooser.departmentId, specofferId: state.rating.specofferChooser.specofferId + }, + specoffersQueryParams: { + timePeriodId: state.specoffers.timePeriodId, + limit: state.specoffers.limit } }; } diff --git a/src/modules/rating/container/SpecofferChooser.jsx b/src/modules/rating/container/SpecofferChooser.jsx index f74c812..eb9c0d4 100644 --- a/src/modules/rating/container/SpecofferChooser.jsx +++ b/src/modules/rating/container/SpecofferChooser.jsx @@ -5,7 +5,7 @@ import {connect} from 'react-redux'; import {loadSpecoffersChooser} from './../actions'; import Nav from 'react-bootstrap/lib/Nav'; import NavItem from 'react-bootstrap/lib/NavItem'; -import { LinkContainer } from 'react-router-bootstrap'; +import LinkContainer from 'react-router-bootstrap/lib/LinkContainer'; import find from 'lodash/find' import DepartmentsList from './../components/DepartmentsList' import SpecoffersList from './../components/SpecoffersList' diff --git a/src/modules/specoffers/actions.js b/src/modules/specoffers/actions.js new file mode 100644 index 0000000..b93465e --- /dev/null +++ b/src/modules/specoffers/actions.js @@ -0,0 +1,19 @@ +import {REQUEST_API} from '../../system/constants'; +import * as types from './constants'; + +export function loadSpecoffersList(params) { + return { + type: REQUEST_API, + request: { + url: `/specoffers?timePeriodId=${params.timePeriodId}&limit=${params.limit}`, + actions: { + start: {type: types.LOAD_ALL_SPECOFFERS_START}, + success: {type: types.LOAD_ALL_SPECOFFERS_SUCCESS}, + fail: {type: types.LOAD_ALL_SPECOFFERS_FAIL} + }, + params, + cache: true + }, + interrupt: (store) => !!store.getState().specoffers.resources.length + }; +} \ No newline at end of file diff --git a/src/modules/specoffers/constants.js b/src/modules/specoffers/constants.js new file mode 100644 index 0000000..d09c08c --- /dev/null +++ b/src/modules/specoffers/constants.js @@ -0,0 +1,17 @@ +export const LOAD_ALL_SPECOFFERS_START = 'LOAD_ALL_SPECOFFERS_START'; +export const LOAD_ALL_SPECOFFERS_SUCCESS = 'LOAD_ALL_SPECOFFERS_SUCCESS'; +export const LOAD_ALL_SPECOFFERS_FAIL = 'LOAD_ALL_SPECOFFERS_START'; + +export const SPECOFFERS_REDUCER = 'specoffers'; + +export const FIELD_NAMES = [ + {'name': 'Спеціальність', 'field': 'specialtyId'}, + {'name': 'Структурний підрозділ', 'field': 'departmentId'}, + {'name': 'Тип пропозиції', 'field': 'specofferTypeId'}, + {'name': 'docNum', 'field': 'docNum'}, + {'name': 'weightCertificate', 'field': 'weightCertificate'}, + {'name': 'weightAward', 'field': 'weightAward'}, + {'name': 'Форма навчання', 'field': 'educationFormTypeId'}, + {'name': 'Ліцензований обсяг', 'field': 'licCount'}, + {'name': 'Державне замовлення', 'field': 'stateCount'}, +]; \ No newline at end of file diff --git a/src/modules/specoffers/containers/SpecofferInfo.js b/src/modules/specoffers/containers/SpecofferInfo.js new file mode 100644 index 0000000..d8987f4 --- /dev/null +++ b/src/modules/specoffers/containers/SpecofferInfo.js @@ -0,0 +1,7 @@ +import React, {Component, PropTypes} from 'react'; + +export default class SpecofferInfo extends Component { + render() { + return
SpecofferInfo
; + } +} \ No newline at end of file diff --git a/src/modules/specoffers/containers/SpecoffersListPage.jsx b/src/modules/specoffers/containers/SpecoffersListPage.jsx new file mode 100644 index 0000000..316fe2c --- /dev/null +++ b/src/modules/specoffers/containers/SpecoffersListPage.jsx @@ -0,0 +1,97 @@ +import React, {Component, PropTypes} from 'react'; +import {connect} from 'react-redux'; +import {createSelector} from 'reselect'; +import LinkContainer from 'react-router-bootstrap/lib/LinkContainer'; +import {Table, Column, Cell} from 'fixed-data-table'; + +import Loading from 'loading'; +import {loadSpecoffersList} from './../actions'; +import {isDataForSpecoffersLoaded, decodeSpecoffers} from './../helpers'; +import * as dictConst from '../../dictionaries/constants'; +import loadDictionaries from '../../dictionaries/actions'; +import {SPECOFFERS_REDUCER, FIELD_NAMES} from './../constants'; + +class MyLinkCell extends Component { + render() { + const {rowIndex, field, data, ...props} = this.props; + let path = `/specoffers/enrolments?specOfferId=${data[rowIndex][field]}`; + return ( + + + {data[rowIndex][field]} + + + ); + } +} + +class SpecoffersListPage extends Component { + constructor(props) { + super(props); + } + + componentDidMount() { + const {timePeriodId, limit} = this.props; + this.props.loadDictionaries([dictConst.DEPARTMENTS]); + this.props.loadSpecoffersList({timePeriodId, limit}); + } + + render() { + if (!isDataForSpecoffersLoaded(SPECOFFERS_REDUCER)) { + return ; + } + + let {decodedSpecoffers} = this.props; + + let cells = FIELD_NAMES.map((item) => { + return {item.name}} + cell={props => ( + + {decodedSpecoffers[props.rowIndex][item.field]} + + ) + } + flexGrow={1} + width={20} + /> + }); + + return ( + + №} + cell={ + + } + width={40} + /> + {cells} +
+ ); + } +} + +const mapStateToSpecoffers = createSelector( + (state) => state.specoffers, + (state) => state.dictionaries, + (state, ownProps) => ownProps.location.query, + (specoffers, listOfDict, query) => ({ + decodedSpecoffers: decodeSpecoffers(specoffers, listOfDict), + timePeriodId: query.timePeriodId, + limit: query.limit + }) +); + +export default connect( + mapStateToSpecoffers, + {loadSpecoffersList, loadDictionaries} +)(SpecoffersListPage); \ No newline at end of file diff --git a/src/modules/specoffers/helpers.js b/src/modules/specoffers/helpers.js new file mode 100644 index 0000000..b3131ad --- /dev/null +++ b/src/modules/specoffers/helpers.js @@ -0,0 +1,50 @@ +import store from 'store'; +import * as dictConstants from '../dictionaries/constants'; +import {isDictLoaded} from '../dictionaries/helpers'; + +let { + DEPARTMENTS, + ENROLMENTS_TYPES, + ENROLMENTS_STATUS_TYPES +} = dictConstants; + +/** + * check if data is loaded + * @param entityData + * @returns {boolean} + */ +export function isEntityDataLoaded(entityData) { + return !entityData.isLoading && (entityData.resources && !!entityData.resources.length); +} + +/** + * check if specoffers loaded && dictionaries (used only inside specoffers list container) + * @param reducerName + * @returns {*|boolean} + */ +export function isDataForSpecoffersLoaded(reducerName) { + let state = store.getState(); + return isDictLoaded([DEPARTMENTS], state.dictionaries) + && isEntityDataLoaded(state[reducerName]); +} + +/** + * + * @param rowSpecoffers - list of row specoffers + * @returns {Array} - array of decoded specoffers + */ +export function decodeSpecoffers(rowSpecoffers, dictionaries) { + return rowSpecoffers.resources.map((item)=> { + return decodeOneSpecoffer(item, dictionaries); + }); +} + +export function decodeOneSpecoffer(item, dictionaries) { + if (!item) return {}; + + let {DEPARTMENTS} = dictionaries; + + return Object.assign({}, item, { + departmentId: DEPARTMENTS.resourcesMap[item.departmentId] + }); +} diff --git a/src/modules/specoffers/reducer.js b/src/modules/specoffers/reducer.js new file mode 100644 index 0000000..54007df --- /dev/null +++ b/src/modules/specoffers/reducer.js @@ -0,0 +1,57 @@ +import * as types from './constants'; +import {LOCATION_CHANGE} from 'react-router-redux'; +import { ignoreActions } from 'redux-ignore'; +import {combineReducers} from 'redux'; + +import lcache from '../../system/lcache'; +import { TIMEPERIODID_CHANGED } from '../settings/widget'; + +const defaultState = { + isLoading: false, + resources: [], + timePeriodId: lcache.get('timePeriodId') || 8, + limit: 300, + error: null +}; + +export function specoffers(state = defaultState, action = {}) { + switch (action.type) { + + case types.LOAD_ALL_SPECOFFERS_START: + return Object.assign({}, state, {isLoading: true, resources: []}); + + case types.LOAD_ALL_SPECOFFERS_SUCCESS: + return Object.assign({}, state, + { + isLoading: false + }, + action.response + ); + + case types.LOAD_ALL_SPECOFFERS_FAIL: + return Object.assign({}, state, {isLoading: false}, {error: action.error.message}); + + case TIMEPERIODID_CHANGED: + return Object.assign({}, state, + { + isLoading: false, + resources: [] + } + ); + + case LOCATION_CHANGE: // listen to query parameters changes + //if (action.payload.pathname !== '/specoffers') return state; + let { + timePeriodId = state.timePeriodId, + limit = state.limit } = action.payload.query; + return Object.assign({}, state, {timePeriodId, limit}); + + default: + return state; + } +} + +export default combineReducers({ + specoffers: ignoreActions(specoffers, + (action) => action.type === LOCATION_CHANGE && action.payload.pathname !== '/specoffers') +}); \ No newline at end of file diff --git a/src/system/reducers/reducers.js b/src/system/reducers/reducers.js index 1584ed9..cbb2eaf 100644 --- a/src/system/reducers/reducers.js +++ b/src/system/reducers/reducers.js @@ -4,6 +4,7 @@ import {default as statistics} from './../../modules/statistics/reducer'; import {default as auth} from '../../modules/auth/reducer'; import {default as rating} from '../../modules/rating/reducer'; import {settings} from '../../modules/settings/widget'; +import {specoffers} from '../../modules/specoffers/reducer'; import config from './configReducer'; import {routerReducer} from 'react-router-redux'; import {combineReducers} from 'redux'; @@ -16,6 +17,7 @@ const rootReducer = combineReducers({ config, rating, settings, + specoffers, routing: routerReducer }); diff --git a/src/system/routes.js b/src/system/routes.js index 6b532a5..657ad4a 100644 --- a/src/system/routes.js +++ b/src/system/routes.js @@ -15,6 +15,8 @@ import Statistics from '../modules/statistics/components/Statistics'; import StatisticsIndex from '../modules/statistics/components/StatisticsIndex'; import Chart from '../modules/statistics/containers/Chart'; import SpecofferChooser from '../modules/rating/container/SpecofferChooser'; +import SpecoffersListPage from '../modules/specoffers/containers/SpecoffersListPage'; +import SpecofferInfo from '../modules/specoffers/containers/SpecofferInfo'; //configure permissions export const routes = { @@ -40,6 +42,9 @@ export default ( + + +