Преглед изворни кода

Merged in feature/loading-indicator (pull request #13)

Feature/loading indicator

Approved-by: Fela Maslen <fela.maslen@mubaloo.com>
Fela Maslen пре 7 година
родитељ
комит
7ffcc92e37

+ 2 - 0
src/components/CrudDocument/index.js

@@ -4,6 +4,8 @@ import classNames from 'classnames';
 
 import CrudField from 'components/CrudField';
 
+import './style.scss';
+
 export default class CrudDocument extends Component {
     static propTypes = {
         id: PropTypes.string.isRequired,

+ 8 - 0
src/components/CrudDocument/style.scss

@@ -0,0 +1,8 @@
+@import '~variables';
+
+.crud-document-wrapper {
+    &.pending {
+        opacity: $opacity-pending;
+    }
+}
+

+ 18 - 0
src/components/LoadingIndicator/index.js

@@ -0,0 +1,18 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+
+import './style.scss';
+
+export default function LoadingIndicator({ loading }) {
+    return (
+        <span className={classNames('loading-indicator', { loading })}>
+            {'Loading...'}
+        </span>
+    );
+}
+
+LoadingIndicator.propTypes = {
+    loading: PropTypes.bool.isRequired
+};
+

+ 8 - 0
src/components/LoadingIndicator/style.scss

@@ -0,0 +1,8 @@
+.loading-indicator {
+    display: none;
+
+    &.loading {
+        display: block;
+    }
+}
+

+ 13 - 2
src/containers/CrudList/index.js

@@ -1,9 +1,13 @@
 import { connect } from 'react-redux';
 import React, { Component } from 'react';
 import PropTypes from 'prop-types';
+import classNames from 'classnames';
 import uniqid from 'uniqid';
 
-import { getDocs } from 'selectors/crud';
+import {
+    getCrudLoading,
+    getDocs
+} from 'selectors/crud';
 
 import {
     docCreated,
@@ -12,10 +16,14 @@ import {
     docDeleted
 } from 'actions/crud';
 
+import LoadingIndicator from 'components/LoadingIndicator';
 import CrudDocument from 'components/CrudDocument';
 import AddCrudItem from 'components/AddCrudItem';
 
+import './style.scss';
+
 const mapStateToProps = (state, { route }) => ({
+    loading: getCrudLoading(state, route),
     items: getDocs(state, route)
 });
 
@@ -36,6 +44,7 @@ class CrudList extends Component {
     static propTypes = {
         title: PropTypes.string.isRequired,
         route: PropTypes.string.isRequired,
+        loading: PropTypes.bool.isRequired,
         docFields: PropTypes.object.isRequired,
         items: PropTypes.array.isRequired,
         onCreate: PropTypes.func.isRequired,
@@ -68,6 +77,7 @@ class CrudList extends Component {
         const {
             title,
             items,
+            loading,
             docFields
         } = this.props;
 
@@ -83,7 +93,7 @@ class CrudList extends Component {
         ));
 
         return (
-            <div className="crud-list-wrapper">
+            <div className={classNames('crud-list-wrapper', { loading })}>
                 <div className="head">
                     <h3 className="title">{title}</h3>
                     <div className="meta">
@@ -91,6 +101,7 @@ class CrudList extends Component {
                             onClick={this.onRefresh}>
                             {'Refresh'}
                         </button>
+                        <LoadingIndicator loading={loading} />
                     </div>
                 </div>
                 <div className="body">

+ 10 - 0
src/containers/CrudList/style.scss

@@ -0,0 +1,10 @@
+@import '~variables';
+
+.crud-list-wrapper {
+    &.loading {
+        .body {
+            opacity: $opacity-pending;
+        }
+    }
+}
+

+ 4 - 0
src/sass/variables.scss

@@ -0,0 +1,4 @@
+$color-grey-md: #999;
+
+$opacity-pending: 0.4;
+

+ 3 - 0
src/selectors/crud.js

@@ -1,5 +1,8 @@
 import { createSelector } from 'reselect';
 
+export const getCrudLoading = (state, route) =>
+    Boolean(state.crud[route] && state.crud[route].loading);
+
 const routeExists = (state, route) => route in state.crud;
 
 function getRouteDocs(state, route) {

+ 37 - 0
test/selectors/crud.spec.js

@@ -1,10 +1,47 @@
 import { expect } from 'chai';
 
 import {
+    getCrudLoading,
     getDocs
 } from 'selectors/crud';
 
 describe('CRUD selectors', () => {
+    describe('getCrudLoading', () => {
+        const route = 'employees';
+
+        it('should return true if the CRUD page is loading', () => {
+            const state = {
+                crud: {
+                    [route]: {
+                        loading: true,
+                        items: []
+                    }
+                }
+            };
+
+            expect(getCrudLoading(state, route)).to.equal(true);
+        });
+
+        it('should return false if the CRUD page is not loading', () => {
+            expect(getCrudLoading({
+                crud: {
+                    [route]: {
+                        loading: false
+                    },
+                    otherRoute: {
+                        loading: true
+                    }
+                }
+            }), route)
+                .to.equal(false);
+
+            expect(getCrudLoading({
+                crud: {}
+            }), route)
+                .to.equal(false);
+        });
+    });
+
     describe('getDocs', () => {
         const route = 'employees';
 

+ 1 - 0
webpack.config.js

@@ -162,6 +162,7 @@ module.exports = {
             reducers: path.resolve(__dirname, 'src/reducers'),
             sagas: path.resolve(__dirname, 'src/sagas'),
             selectors: path.resolve(__dirname, 'src/selectors'),
+            variables: path.resolve(__dirname, 'src/sass/variables.scss'),
             sprite: path.resolve(__dirname, 'src/images/sprite/sprite.scss')
         }
     },