Skip to content

Commit

Permalink
feat: dynamic dimension filtering for thematic layers (#86)
Browse files Browse the repository at this point in the history
  • Loading branch information
turban committed Apr 9, 2019
1 parent 52d7fbe commit 057c7f0
Show file tree
Hide file tree
Showing 23 changed files with 1,153 additions and 141 deletions.
25 changes: 14 additions & 11 deletions i18n/en.pot
Expand Up @@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2019-03-26T08:01:11.496Z\n"
"PO-Revision-Date: 2019-03-26T08:01:11.496Z\n"
"POT-Creation-Date: 2019-04-09T10:41:02.448Z\n"
"PO-Revision-Date: 2019-04-09T10:41:02.448Z\n"

msgid "Maps"
msgstr ""
Expand Down Expand Up @@ -92,6 +92,15 @@ msgstr ""
msgid "Color"
msgstr ""

msgid "Add filter"
msgstr ""

msgid "Items"
msgstr ""

msgid "Dimension"
msgstr ""

msgid "Download"
msgstr ""

Expand Down Expand Up @@ -302,15 +311,6 @@ msgstr ""
msgid "Filtering is available after selecting a program stage."
msgstr ""

msgid "Add filter"
msgstr ""

msgid "Delete filter"
msgstr ""

msgid "Remove filter"
msgstr ""

msgid "Operator"
msgstr ""

Expand All @@ -335,6 +335,9 @@ msgstr ""
msgid "is not"
msgstr ""

msgid "Remove filter"
msgstr ""

msgid "Indicator group"
msgstr ""

Expand Down
14 changes: 8 additions & 6 deletions package.json
Expand Up @@ -46,15 +46,16 @@
},
"dependencies": {
"@dhis2/d2-i18n": "^1.0.4",
"@dhis2/d2-ui-analytics": "^0.0.3",
"@dhis2/d2-ui-core": "5.2.10",
"@dhis2/d2-ui-file-menu": "5.2.10",
"@dhis2/d2-ui-interpretations": "5.2.10",
"@dhis2/d2-ui-org-unit-dialog": "5.2.10",
"@dhis2/d2-ui-org-unit-tree": "5.2.10",
"@dhis2/gis-api": "^32.0.18",
"@dhis2/ui": "^1.0.0-beta.15",
"@material-ui/core": "^3.4.0",
"@material-ui/icons": "^3.0.1",
"@material-ui/core": "^3.9.3",
"@material-ui/icons": "^3.0.2",
"@material-ui/lab": "^3.0.0-alpha.23",
"babel-preset-stage-0": "^6.24.1",
"d2": "31.2.2",
Expand All @@ -66,9 +67,9 @@
"loglevel": "^1.6.1",
"promise-polyfill": "^8.1.0",
"prop-types": "^15.6.2",
"react": "16.5.0",
"react": "16.8.6",
"react-color": "^2.13.5",
"react-dom": "16.5.0",
"react-dom": "16.8.6",
"react-jss": "^8.6.1",
"react-redux": "^5.0.7",
"react-sortable-hoc": "^0.8.3",
Expand All @@ -78,6 +79,7 @@
"redux-observable": "^0.18.0",
"redux-thunk": "^2.3.0",
"rxjs": "5.5.6",
"uglifyjs-webpack-plugin": "^2.1.2",
"url-polyfill": "^1.1.0",
"whatwg-fetch": "^3.0.0"
},
Expand Down Expand Up @@ -132,11 +134,11 @@
"script-loader": "^0.7.2",
"style-loader": "^0.20.2",
"susy": "^3.0.3",
"terser-webpack-plugin-legacy": "^1.2.3",
"url-loader": "^0.6.2",
"webpack": "^3.11.0",
"webpack-bundle-analyzer": "^3.0.2",
"webpack-dev-server": "^2.11.1",
"webpack-uglify-js-plugin": "^1.1.9"
"webpack-dev-server": "^2.11.1"
},
"manifest.webapp": {
"name": "DHIS2 Maps",
Expand Down
25 changes: 25 additions & 0 deletions src/actions/dimensions.js
@@ -0,0 +1,25 @@
import * as types from '../constants/actionTypes';

// Load all dimensions
export const loadDimensions = () => ({
type: types.DIMENSIONS_LOAD,
});

// Set all dimensions
export const setDimensions = dimensions => ({
type: types.DIMENSIONS_SET,
payload: dimensions,
});

// Load all dimension items
export const loadDimensionItems = dimensionId => ({
type: types.DIMENSION_ITEMS_LOAD,
dimensionId,
});

// Set all dimension items
export const setDimensionItems = (dimensionId, items) => ({
type: types.DIMENSION_ITEMS_SET,
dimensionId,
payload: items,
});
22 changes: 22 additions & 0 deletions src/actions/layerEdit.js
@@ -1,15 +1,37 @@
import * as types from '../constants/actionTypes';

// Add dimension filter
export const addDimensionFilter = filter => ({
type: types.LAYER_EDIT_DIMENSION_FILTER_ADD,
filter,
});

// Remove dimension filter
export const removeDimensionFilter = index => ({
type: types.LAYER_EDIT_DIMENSION_FILTER_REMOVE,
index,
});

// Chenge a dimension filter in an array
export const changeDimensionFilter = (index, filter) => ({
type: types.LAYER_EDIT_DIMENSION_FILTER_CHANGE,
index,
filter,
});

// Add event filter
export const addFilter = filter => ({
type: types.LAYER_EDIT_FILTER_ADD,
filter,
});

// Remove event filter
export const removeFilter = index => ({
type: types.LAYER_EDIT_FILTER_REMOVE,
index,
});

// Change event filter
export const changeFilter = (index, filter) => ({
type: types.LAYER_EDIT_FILTER_CHANGE,
index,
Expand Down
4 changes: 2 additions & 2 deletions src/components/dataElement/DataElementSelect.js
Expand Up @@ -8,7 +8,7 @@ import { loadDataElements } from '../../actions/dataElements';
export class DataElementSelect extends PureComponent {
static propTypes = {
dataElement: PropTypes.object,
dataElements: PropTypes.object,
dataElements: PropTypes.array,
dataElementGroup: PropTypes.object,
loadDataElements: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
Expand All @@ -19,7 +19,7 @@ export class DataElementSelect extends PureComponent {
componentDidUpdate() {
const { dataElements, dataElementGroup, loadDataElements } = this.props;

if (dataElementGroup && !dataElements[dataElementGroup.id]) {
if (dataElementGroup && !dataElements) {
loadDataElements(dataElementGroup.id);
}
}
Expand Down
71 changes: 71 additions & 0 deletions src/components/dimensions/DimensionFilter.js
@@ -0,0 +1,71 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles';
import i18n from '@dhis2/d2-i18n';
import Button from '@material-ui/core/Button';
import DimensionFilterRow from './DimensionFilterRow';
import {
addDimensionFilter,
removeDimensionFilter,
changeDimensionFilter,
} from '../../actions/layerEdit';

const styles = () => ({
container: {
width: '100%',
height: 300,
paddingTop: 8,
overflowY: 'auto',
},
button: {
marginTop: 8,
},
});

class DimensionFilter extends Component {
static propTypes = {
dimensions: PropTypes.array,
addDimensionFilter: PropTypes.func.isRequired,
removeDimensionFilter: PropTypes.func.isRequired,
changeDimensionFilter: PropTypes.func.isRequired,
classes: PropTypes.object.isRequired,
};

render() {
const {
addDimensionFilter,
classes,
dimensions = [],
changeDimensionFilter,
removeDimensionFilter,
} = this.props;

return (
<div className={classes.container}>
{dimensions.map((item, index) => (
<DimensionFilterRow
key={index}
index={index}
onChange={changeDimensionFilter}
onRemove={removeDimensionFilter}
{...item}
/>
))}
<Button
variant="contained"
color="primary"
onClick={() => addDimensionFilter()}
className={classes.button}
>
{i18n.t('Add filter')}
</Button>
</div>
);
}
}

export default connect(
null,
{ addDimensionFilter, removeDimensionFilter, changeDimensionFilter }
)(withStyles(styles)(DimensionFilter));
62 changes: 62 additions & 0 deletions src/components/dimensions/DimensionFilterRow.js
@@ -0,0 +1,62 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import DimensionSelect from './DimensionSelect';
import DimensionItemsSelect from './DimensionItemsSelect';
import RemoveFilter from '../filter/RemoveFilter';

const styles = theme => ({
container: {
height: 68,
marginBottom: 8,
padding: '-0 56px 0 8px',
backgroundColor: theme.palette.background.grey,
position: 'relative',
clear: 'both',
borderRadius: 4,
border: `1px solid ${theme.palette.divider}`,
},
select: {
marginRight: 24,
float: 'left',
width: 'calc((100% - 48px) / 8 * 3)',
},
});

class DimensionFilterRow extends Component {
static propTypes = {
dimension: PropTypes.string,
items: PropTypes.array,
index: PropTypes.number,
onChange: PropTypes.func.isRequired,
onRemove: PropTypes.func.isRequired,
classes: PropTypes.object.isRequired,
};

onChange(dimension, items) {
const { index, onChange } = this.props;

onChange(index, { dimension, items });
}

render() {
const { dimension, items, index, onRemove, classes } = this.props;

return (
<div className={classes.container}>
<DimensionSelect
dimension={dimension}
onChange={dimension => this.onChange(dimension.id, items)}
/>
<DimensionItemsSelect
dimension={dimension}
value={items ? items.map(item => item.id) : null}
onChange={items => this.onChange(dimension, items)}
/>
<RemoveFilter onClick={() => onRemove(index)} />
</div>
);
}
}

export default withStyles(styles)(DimensionFilterRow);
67 changes: 67 additions & 0 deletions src/components/dimensions/DimensionItemsSelect.js
@@ -0,0 +1,67 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import i18n from '@dhis2/d2-i18n';
import SelectField from '../core/SelectField';
import { loadDimensionItems } from '../../actions/dimensions';

const styles = {
select: {
width: '60%',
},
};

export class DimensionItemsSelect extends Component {
static propTypes = {
dimension: PropTypes.string,
items: PropTypes.array,
value: PropTypes.array,
onChange: PropTypes.func.isRequired,
loadDimensionItems: PropTypes.func.isRequired,
style: PropTypes.object,
errorText: PropTypes.string,
};

componentDidUpdate() {
const { dimension, items, loadDimensionItems } = this.props;

if (dimension && !items) {
loadDimensionItems(dimension);
}
}

onDimensionItemClick = ids => {
const { items, onChange } = this.props;

onChange(ids.map(id => items.find(item => item.id === id)));
};

render() {
const { items, value } = this.props;

if (!items) {
return null;
}

return (
<SelectField
label={i18n.t('Items')}
items={items}
value={value}
multiple={true}
onChange={this.onDimensionItemClick}
style={styles.select}
/>
);
}
}

export default connect(
({ dimensions }, { dimension }) => ({
items:
dimensions && dimension
? dimensions.find(dim => dim.id === dimension).items
: null,
}),
{ loadDimensionItems }
)(DimensionItemsSelect);

0 comments on commit 057c7f0

Please sign in to comment.