diff --git a/src/Resources/components/Adjustments/index.js b/src/Resources/components/Adjustments/index.js
index a1dada16..4b481056 100644
--- a/src/Resources/components/Adjustments/index.js
+++ b/src/Resources/components/Adjustments/index.js
@@ -50,7 +50,7 @@ class Adjustments extends Component {
showFilters: false,
};
- componentWillMount() {
+ componentDidMount() {
getAdjustments();
}
diff --git a/src/Resources/components/ItemUnits/Form/index.js b/src/Resources/components/ItemUnits/Form/index.js
new file mode 100644
index 00000000..7f45e70c
--- /dev/null
+++ b/src/Resources/components/ItemUnits/Form/index.js
@@ -0,0 +1,196 @@
+import { putItemUnit, postItemUnit } from '@codetanzania/emis-api-states';
+import { Button, Form, Input, Col, Row } from 'antd';
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { notifyError, notifySuccess } from '../../../../util';
+import './styles.css';
+
+const { TextArea } = Input;
+
+// eslint-disable-next-line jsdoc/require-returns
+/**
+ * @class
+ * @name ItemUnitForm
+ * @description Render form for creating a new Item Unit Of Measure
+ *
+ * @version 0.1.0
+ * @since 0.1.0
+ */
+class ItemUnitForm extends Component {
+ static propTypes = {
+ isEditForm: PropTypes.bool.isRequired,
+ itemUnit: PropTypes.shape({
+ name: PropTypes.string,
+ type: PropTypes.string,
+ maxStockAllowed: PropTypes.string,
+ minStockAllowed: PropTypes.string,
+ }),
+ form: PropTypes.shape({ getFieldDecorator: PropTypes.func }).isRequired,
+ onCancel: PropTypes.func.isRequired,
+ posting: PropTypes.bool.isRequired,
+ types: PropTypes.arrayOf(PropTypes.string).isRequired,
+ };
+
+ static defaultProps = {
+ itemUnit: null,
+ };
+
+ // eslint-disable-next-line jsdoc/require-returns
+ /**
+ * @function
+ * @name onChangeColor
+ * @description call back function to handle color change
+ *
+ * @param {Object} colors colors object
+ * @param {Object} colors.color current color
+ *
+ * @version 0.1.0
+ * @since 0.1.0
+ */
+ onChangeColor = ({ color }) => {
+ const {
+ form: { setFieldsValue },
+ } = this.props;
+ setFieldsValue({ color });
+ };
+
+ // eslint-disable-next-line jsdoc/require-returns
+ /**
+ * @function
+ * @name handleSubmit
+ * @description call back function to handle submit action
+ *
+ * @param {Object} e event object
+ *
+ * @version 0.1.0
+ * @since 0.1.0
+ */
+ handleSubmit = e => {
+ e.preventDefault();
+
+ const {
+ form: { validateFieldsAndScroll },
+ itemUnit,
+ isEditForm,
+ } = this.props;
+
+ validateFieldsAndScroll((error, values) => {
+ if (!error) {
+ if (isEditForm) {
+ const updatedItem = Object.assign({}, itemUnit, values);
+ putItemUnit(
+ updatedItem,
+ () => {
+ notifySuccess('Unit of measure was updated successfully');
+ },
+ () => {
+ notifyError(
+ 'An error occurred while updating unit of measure, please try again!'
+ );
+ }
+ );
+ } else {
+ postItemUnit(
+ values,
+ () => {
+ notifySuccess('Unit of measure was created successfully');
+ },
+ () => {
+ notifyError(
+ 'An error occurred while saving unit of measure, please try again!'
+ );
+ }
+ );
+ }
+ }
+ });
+ };
+
+ render() {
+ const {
+ isEditForm,
+ itemUnit,
+ posting,
+ onCancel,
+ form: { getFieldDecorator },
+ } = this.props;
+
+ const formItemLayout = {
+ labelCol: {
+ xs: { span: 24 },
+ sm: { span: 24 },
+ md: { span: 24 },
+ lg: { span: 24 },
+ xl: { span: 24 },
+ xxl: { span: 24 },
+ },
+ wrapperCol: {
+ xs: { span: 24 },
+ sm: { span: 24 },
+ md: { span: 24 },
+ lg: { span: 24 },
+ xl: { span: 24 },
+ xxl: { span: 24 },
+ },
+ };
+
+ return (
+
+ {getFieldDecorator('description', {
+ initialValue: isEditForm ? itemUnit.description : undefined,
+ rules: [{ message: 'Add summaries' }],
+ })()}
+
+ {/* end description */}
+
+ {/* form actions */}
+
+
+
+
+ {/* end form actions */}
+
+ );
+ }
+}
+
+export default Form.create()(ItemUnitForm);
diff --git a/src/Resources/components/ItemUnits/Form/styles.css b/src/Resources/components/ItemUnits/Form/styles.css
new file mode 100644
index 00000000..e69de29b
diff --git a/src/Resources/components/ItemUnits/List/index.js b/src/Resources/components/ItemUnits/List/index.js
new file mode 100644
index 00000000..ddbcc6b7
--- /dev/null
+++ b/src/Resources/components/ItemUnits/List/index.js
@@ -0,0 +1,234 @@
+import { httpActions } from '@codetanzania/emis-api-client';
+import {
+ refreshItemUnits,
+ paginateItemUnits,
+ deleteItemUnit,
+} from '@codetanzania/emis-api-states';
+import { List } from 'antd';
+import concat from 'lodash/concat';
+import map from 'lodash/map';
+import remove from 'lodash/remove';
+import uniq from 'lodash/uniq';
+import uniqBy from 'lodash/uniqBy';
+import intersectionBy from 'lodash/intersectionBy';
+import PropTypes from 'prop-types';
+import React, { Fragment, Component } from 'react';
+import ItemUnitListItem from '../ListItem';
+import ListHeader from '../../../../components/ListHeader';
+import Toolbar from '../../../../components/Toolbar';
+import { notifyError, notifySuccess } from '../../../../util';
+
+/* constants */
+const headerLayout = [
+ { span: 6, header: 'Name' },
+ { span: 6, header: 'Abbreviation' },
+ { span: 6, header: 'Description' },
+];
+
+const { getItemUnitsExportUrl } = httpActions;
+
+/**
+ * @class
+ * @name ItemUnitList
+ * @description Render item unit of measure list which have search box and actions
+ *
+ * @version 0.1.0
+ * @since 0.1.0
+ */
+class ItemUnitList extends Component {
+ static propTypes = {
+ loading: PropTypes.bool.isRequired,
+ itemUnits: PropTypes.arrayOf(
+ PropTypes.shape({
+ name: PropTypes.string,
+ })
+ ).isRequired,
+ total: PropTypes.number.isRequired,
+ page: PropTypes.number.isRequired,
+ onEdit: PropTypes.func.isRequired,
+ };
+
+ state = {
+ selectedItemUnit: [],
+ selectedPages: [],
+ };
+
+ /**
+ * @function
+ * @name handleSelectItemUnit
+ * @description Handle select single item unit of measure checkbox
+ *
+ * @param {Object} itemUnit selected item unit of measure object
+ *
+ * @version 0.1.0
+ * @since 0.1.0
+ */
+ handleSelectItemUnit = itemUnit => {
+ const { selectedItemUnit } = this.state;
+ this.setState({
+ selectedItemUnit: concat([], selectedItemUnit, itemUnit),
+ });
+ };
+
+ /**
+ * @function
+ * @name handleSelectAll
+ * @description Handle select all item unit of measure action in current page
+ *
+ * @version 0.1.0
+ * @since 0.1.0
+ */
+ handleSelectAll = () => {
+ const { selectedItemUnit, selectedPages } = this.state;
+ const { itemUnits, page } = this.props;
+ const selectedList = uniqBy([...selectedItemUnit, ...itemUnits], '_id');
+ const pages = uniq([...selectedPages, page]);
+ this.setState({
+ selectedItemUnit: selectedList,
+ selectedPages: pages,
+ });
+ };
+
+ /**
+ * @function
+ * @name handleDeselectItemUnit
+ * @description Handle deselect a single item unit of measure checkbox
+ *
+ * @param {Object} itemUnit item unit of measure objected to be removed from
+ * list of selected adjustments
+ * @returns {undefined} undefined
+ *
+ * @version 0.1.0
+ * @since 0.1.0
+ */
+ handleDeselectItemUnit = itemUnit => {
+ const { selectedItemUnit } = this.state;
+ const selectedList = [...selectedItemUnit];
+
+ remove(selectedList, item => item._id === itemUnit._id); // eslint-disable-line
+
+ this.setState({
+ selectedItemUnit: selectedList,
+ });
+ };
+
+ /**
+ * @function
+ * @name handleDeselectAll
+ * @description Handle deselect all item unit of measure in a current page
+ *
+ * @returns {undefined} undefined
+ *
+ * @version 0.1.0
+ * @since 0.1.0
+ */
+ handleDeselectAll = () => {
+ const { itemUnits, page } = this.props;
+ const { selectedItemUnit, selectedPages } = this.state;
+ const selectedList = [...selectedItemUnit];
+ const pages = [...selectedPages];
+
+ remove(pages, item => item === page);
+
+ itemUnits.forEach(itemUnit => {
+ remove(selectedList, item => item._id === itemUnit._id); // eslint-disable-line
+ });
+
+ this.setState({
+ selectedItemUnit: selectedList,
+ selectedPages: pages,
+ });
+ };
+
+ render() {
+ const { itemUnits, loading, total, page, onEdit } = this.props;
+ const { selectedItemUnit, selectedPages } = this.state;
+ const selectedItemUnitsCount = intersectionBy(
+ this.state.selectedItemUnit,
+ itemUnits,
+ '_id'
+ ).length;
+
+ return (
+
+ {/* toolbar */}
+ {
+ paginateItemUnits(nextPage);
+ }}
+ exportUrl={getItemUnitsExportUrl({
+ filter: { _id: map(selectedItemUnit, '_id') },
+ })}
+ onRefresh={() =>
+ refreshItemUnits(
+ () => {
+ notifySuccess('Unit of measure refreshed successfully');
+ },
+ () => {
+ notifyError(
+ 'An Error occurred while refreshing unit of measure please contact system administrator'
+ );
+ }
+ )
+ }
+ />
+ {/* end toolbar */}
+
+ {/* list header */}
+
+ {/* list header */}
+
+ {/* Item Unit Of Measure list */}
+ (
+ {
+ this.handleSelectItemUnit(itemUnit);
+ }}
+ onDeselectItem={() => {
+ this.handleDeselectItemUnit(itemUnit);
+ }}
+ onEdit={() => onEdit(itemUnit)}
+ onArchive={() =>
+ deleteItemUnit(
+ itemUnit._id, // eslint-disable-line
+ () => {
+ notifySuccess('Unit of measure was archived successfully');
+ },
+ () => {
+ notifyError(
+ `An error occurred while archiving unit of measure please contact
+ system administrator`
+ );
+ }
+ )
+ }
+ />
+ )}
+ />
+ {/* end Item Unit Of Measure list */}
+
+ );
+ }
+}
+
+export default ItemUnitList;
diff --git a/src/Resources/components/ItemUnits/ListItem/index.js b/src/Resources/components/ItemUnits/ListItem/index.js
new file mode 100644
index 00000000..99a46c7e
--- /dev/null
+++ b/src/Resources/components/ItemUnits/ListItem/index.js
@@ -0,0 +1,179 @@
+import { Avatar, Col, Row, Checkbox, Modal } from 'antd';
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import truncate from 'lodash/truncate';
+import ListItemActions from '../../../../components/ListItemActions';
+import './styles.css';
+
+const { confirm } = Modal;
+
+/**
+ * @class
+ * @name ItemUnitListItem
+ * @description Single item unit of measure list item component.
+ * Render single item unit of measure list item details
+ *
+ * @version 0.1.0
+ * @since 0.1.0
+ */
+class ItemUnitListItem extends Component {
+ /* props validation */
+ static propTypes = {
+ name: PropTypes.string.isRequired,
+ description: PropTypes.string.isRequired,
+ color: PropTypes.string.isRequired,
+ abbreviation: PropTypes.number.isRequired,
+ isSelected: PropTypes.bool.isRequired,
+ onSelectItem: PropTypes.func.isRequired,
+ onDeselectItem: PropTypes.func.isRequired,
+ onEdit: PropTypes.func.isRequired,
+ onArchive: PropTypes.func.isRequired,
+ };
+
+ state = {
+ isHovered: false,
+ };
+
+ /**
+ * @function
+ * @name handleMouseEnter
+ * @description show item actions on hover
+ *
+ * @version 0.1.0
+ * @since 0.1.0
+ */
+ handleMouseEnter = () => {
+ this.setState({ isHovered: true });
+ };
+
+ /**
+ * @function
+ * @name handleMouseLeave
+ * @description hide item actions on hover
+ *
+ * @version 0.1.0
+ * @since 0.1.0
+ */
+ handleMouseLeave = () => {
+ this.setState({ isHovered: false });
+ };
+
+ /**
+ * @function
+ * @name handleToggleSelect
+ * @description Handle toggling list item checkbox
+ *
+ * @param {Object} event Event object
+ *
+ * @version 0.1.0
+ * @since 0.1.0
+ */
+ handleToggleSelect = event => {
+ const { isSelected } = this.state;
+ const { onSelectItem, onDeselectItem } = this.props;
+
+ this.setState({ isSelected: !isSelected });
+
+ if (event.target.checked) {
+ onSelectItem();
+ } else {
+ onDeselectItem();
+ }
+ };
+
+ /**
+ * @function
+ * @name showArchiveConfirm
+ * @description show confirm modal before archiving a item unit of measure
+ *
+ * @version 0.1.0
+ * @since 0.1.0
+ */
+ showArchiveConfirm = () => {
+ const { name, onArchive } = this.props;
+ confirm({
+ title: `Are you sure you want to archive ${name} ?`,
+ okText: 'Yes',
+ okType: 'danger',
+ cancelText: 'No',
+ onOk() {
+ onArchive();
+ },
+ });
+ };
+
+ render() {
+ const {
+ name,
+ description,
+ color,
+ abbreviation,
+ isSelected,
+ onEdit,
+ } = this.props;
+ const { isHovered } = this.state;
+ let sideComponent = null;
+
+ if (isSelected) {
+ sideComponent = (
+
+ );
+ } else {
+ sideComponent = isHovered ? (
+
+ ) : (
+
+ {name.charAt(0).toUpperCase()}
+
+ );
+ }
+
+ return (
+
+
+ {sideComponent}
+ {name}
+ {abbreviation}
+
+ {description
+ ? truncate(description, {
+ length: 60,
+ })
+ : 'N/A'}
+
+
+ {isHovered && (
+
+ )}
+
+
+
+ );
+ }
+}
+
+export default ItemUnitListItem;
diff --git a/src/Resources/components/ItemUnits/ListItem/styles.css b/src/Resources/components/ItemUnits/ListItem/styles.css
new file mode 100644
index 00000000..07562d5d
--- /dev/null
+++ b/src/Resources/components/ItemUnits/ListItem/styles.css
@@ -0,0 +1,41 @@
+.ItemUnitListItem {
+ width: 100%;
+ border-bottom: 1px solid #e8e8e8;
+ padding: 15px 0;
+ height: 60px;
+}
+
+.ItemUnitListItem .Checkbox {
+ padding: 0 8px;
+}
+
+.ItemUnitListItem:hover {
+ background-color: #f5f5f5;
+}
+
+.ItemUnitListItem .actionIcon:hover {
+ color: #008EFA;
+ background-color: #e8e8e8;
+}
+
+.ItemUnitListItem .actionIcon {
+ padding-top: 8px;
+ margin-bottom: 5px;
+ width: 32px;
+ height: 32px;
+ font-size: 16px;
+ display: inline-block;
+ text-align: center;
+ border-radius: 50%;
+}
+
+.ItemUnitListItem .actionIcon {
+ display: inline-block;
+ margin: 0 10px;
+ font-size: 16px;
+}
+
+.ItemUnitListItem .actionIcon:hover {
+ color: #008EFA;
+ background-color: #e8e8e8;
+}
\ No newline at end of file
diff --git a/src/Resources/components/ItemUnits/index.js b/src/Resources/components/ItemUnits/index.js
new file mode 100644
index 00000000..348e2039
--- /dev/null
+++ b/src/Resources/components/ItemUnits/index.js
@@ -0,0 +1,200 @@
+import {
+ Connect,
+ openItemUnitForm,
+ closeItemUnitForm,
+ selectItemUnit,
+ getItemUnits,
+ searchItemUnits,
+} from '@codetanzania/emis-api-states';
+import { Modal } from 'antd';
+import PropTypes from 'prop-types';
+import React, { Component, Fragment } from 'react';
+import Topbar from '../../../components/Topbar';
+import ItemUnitList from './List';
+import ItemUnitForm from './Form';
+import './styles.css';
+
+/**
+ * @class
+ * @name ItemUnit
+ * @description Render Item unit of measure module which has search box,
+ * actions and list of Item unit of measures
+ *
+ * @version 0.1.0
+ * @since 0.1.0
+ */
+class ItemUnit extends Component {
+ static propTypes = {
+ loading: PropTypes.bool.isRequired,
+ posting: PropTypes.bool.isRequired,
+ itemUnits: PropTypes.arrayOf(PropTypes.shape({ name: PropTypes.string }))
+ .isRequired,
+ itemUnit: PropTypes.shape({ name: PropTypes.string }),
+ total: PropTypes.number.isRequired,
+ page: PropTypes.number.isRequired,
+ searchQuery: PropTypes.string,
+ showForm: PropTypes.bool.isRequired,
+ };
+
+ static defaultProps = {
+ searchQuery: undefined,
+ itemUnit: null,
+ };
+
+ state = {
+ isEditForm: false,
+ };
+
+ componentDidMount() {
+ getItemUnits();
+ }
+
+ /**
+ * @function
+ * @name openForm
+ * @description Open role form
+ *
+ * @version 0.1.0
+ * @since 0.1.0
+ */
+ openForm = () => {
+ openItemUnitForm();
+ };
+
+ /**
+ * @function
+ * @name openForm
+ * @description close role form
+ *
+ * @returns {undefined} - Nothing is returned
+ *
+ * @version 0.1.0
+ * @since 0.1.0
+ */
+ closeForm = () => {
+ closeItemUnitForm();
+ this.setState({ isEditForm: false });
+ };
+
+ /**
+ * @function
+ * @name handleEdit
+ * @description Handle on Edit action for list item
+ *
+ * @param {Object} value - item to be edited
+ *
+ * @version 0.1.0
+ * @since 0.1.0
+ */
+ handleEdit = value => {
+ selectItemUnit(value);
+ this.setState({ isEditForm: true });
+ openItemUnitForm();
+ };
+
+ /**
+ * @function
+ * @name handleAfterCloseForm
+ * @description Performs after close form cleanups
+ *
+ * @version 0.1.0
+ * @since 0.1.0
+ */
+ handleAfterCloseForm = () => {
+ this.setState({ isEditForm: false });
+ };
+
+ /**
+ * @function
+ * @name searchRoles
+ * @description Search item unit of measure List based on supplied filter word
+ *
+ * @param {Object} event - Event instance
+ * @returns {undefined} - Nothing is returned
+ *
+ * @version 0.1.0
+ * @since 0.1.0
+ */
+ searchItemUnit = event => {
+ searchItemUnits(event.target.value);
+ };
+
+ render() {
+ const {
+ itemUnits,
+ loading,
+ showForm,
+ posting,
+ page,
+ total,
+ itemUnit,
+ searchQuery,
+ } = this.props;
+ const { isEditForm } = this.state;
+ return (
+
+ {/* Topbar */}
+
+ {/* end Topbar */}
+
+ {/* list starts */}
+
+ {/* end list */}
+
+ {/* create/edit form modal */}
+
+
+
+ {/* end create/edit form modal */}
+
+
+ );
+ }
+}
+
+export default Connect(ItemUnit, {
+ itemUnits: 'itemUnits.list',
+ itemUnit: 'itemUnits.selected',
+ loading: 'itemUnits.loading',
+ page: 'itemUnits.page',
+ showForm: 'itemUnits.showForm',
+ total: 'itemUnits.total',
+ searchQuery: 'itemUnits.q',
+});
diff --git a/src/Resources/components/ItemUnits/styles.css b/src/Resources/components/ItemUnits/styles.css
new file mode 100644
index 00000000..a68564d9
--- /dev/null
+++ b/src/Resources/components/ItemUnits/styles.css
@@ -0,0 +1,8 @@
+.ItemUnitList {
+ padding: 40px;
+ }
+
+ .ItemUnitList .searchBox {
+ width: 50%;
+ }
+
\ No newline at end of file
diff --git a/src/Resources/index.js b/src/Resources/index.js
index ff54dff3..1c575422 100644
--- a/src/Resources/index.js
+++ b/src/Resources/index.js
@@ -6,6 +6,7 @@ import itemCategoriesIcon from '../assets/icons/resources/itemcategory.svg';
import stockIcon from '../assets/icons/resources/stock.svg';
import utilizationIcon from '../assets/icons/resources/utilization-disabled.svg';
import warehousesIcon from '../assets/icons/resources/warehouse.svg';
+import itemUnitsIcon from '../assets/icons/resources/itemunit.svg';
import NavigationMenu from '../components/NavigationMenu';
import modules from '../modules.json';
@@ -41,11 +42,11 @@ const routes = [
description: modules.resourcesConsumption,
disabled: true,
},
+ { name: 'Warehouses', path: '/warehouses', icon: warehousesIcon },
{
- name: 'Warehouses',
- path: '/warehouses',
- icon: warehousesIcon,
- description: modules.resourcesWarehouses,
+ name: 'Item Unit Of Measure',
+ path: '/unitsofmeasure',
+ icon: itemUnitsIcon,
},
];
diff --git a/src/Resources/layouts/ItemUnit.js b/src/Resources/layouts/ItemUnit.js
new file mode 100644
index 00000000..c4db0531
--- /dev/null
+++ b/src/Resources/layouts/ItemUnit.js
@@ -0,0 +1,15 @@
+import React from 'react';
+// import UIState from '../../components/UIState';
+import ItemUnit from '../components/ItemUnits';
+
+/**
+ * @function
+ * @name ResourcesItemUnitLayout
+ * @description Render resources Item unit of measure layout
+ *
+ * @version 0.1.0
+ * @since 0.1.0
+ */
+const ResourcesItemUnitLayout = () => ;
+
+export default ResourcesItemUnitLayout;
diff --git a/src/layouts/BaseLayout.js b/src/layouts/BaseLayout.js
index 9d452841..87bf8f72 100644
--- a/src/layouts/BaseLayout.js
+++ b/src/layouts/BaseLayout.js
@@ -62,6 +62,8 @@ import StakeholdersNotificationsLayout from '../Stakeholders/layouts/Notificatio
import StakeholdersRolesLayout from '../Stakeholders/layouts/Roles';
import HeaderNavMenu from './components/HeaderNavMenu';
import PageNotFound from '../components/UIState/PageNotFound';
+import ResourcesItemUnitLayout from '../Resources/layouts/ItemUnit';
+
import './styles.css';
/* constants */
@@ -193,6 +195,10 @@ const breadcrumbNameMap = {
title: 'List of adjusted resources',
},
'/resources/items': { name: 'Items', title: 'List of available items' },
+ '/resources/warehouses': {
+ name: 'Warehouses',
+ title: 'List of available warehouses',
+ },
'/resources/items-categories': {
name: 'Item Categories',
title: 'List of available item categories',
@@ -203,9 +209,9 @@ const breadcrumbNameMap = {
name: 'Utilizations',
title: 'Resource utilizations',
},
- '/resources/warehouses': {
- name: 'Warehouses',
- title: 'List of available warehouses',
+ '/resources/unitsofmeasure': {
+ name: 'Item Unit',
+ title: 'List of available units of measure for items',
},
/* Stakeholders Routes */
'/stakeholders/focalpeople': {
@@ -493,6 +499,11 @@ const BaseLayout = withRouter(props => {
path="/resources/items"
component={ResourcesItemsLayout}
/>
+