Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement event group component #136

Merged
merged 3 commits into from
Dec 19, 2019
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(event groups): display event groups list
Mloweedgar committed Dec 18, 2019
commit ccc1bb81a92a9f4f2691890c6e92bfa1785a98ff
207 changes: 207 additions & 0 deletions src/Events/components/EventGroups/Form/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import { putEventType, postEventType } from '@codetanzania/ewea-api-states';
import { httpActions } from '@codetanzania/ewea-api-client';
import { Button, Form, Input } from 'antd';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import SearchableSelectInput from '../../../../components/SearchableSelectInput';
import { notifyError, notifySuccess } from '../../../../util';

const { getEventGroups } = httpActions;
/**
* @class
* @name EventTypeForm
* @description Render form for creating a new event type
*
* @version 0.1.0
* @since 0.1.0
*/
class EventTypeForm extends Component {
/**
* @function
* @name handleSubmit
* @description call back function to handle submit action
*
* @param {object} e event object
*
* @returns {undefined} does not return anything
*
* @version 0.1.0
* @since 0.1.0
*/
handleSubmit = e => {
e.preventDefault();
const {
form: { validateFieldsAndScroll },
eventType,
isEditForm,
} = this.props;

validateFieldsAndScroll((error, values) => {
if (!error) {
const payload = {
strings: {
name: {
en: values.name,
},
description: {
en: values.description,
},
},
relations: {
group: { _id: values.group },
},
};
if (isEditForm) {
const updatedContact = { ...eventType, ...payload };
putEventType(
updatedContact,
() => {
notifySuccess('Event Type was updated successfully');
},
() => {
notifyError(
'Something occurred while updating Event Type, please try again!'
);
}
);
} else {
postEventType(
payload,
() => {
notifySuccess('Event Type was created successfully');
},
() => {
notifyError(
'Something occurred while saving Event Type, please try again!'
);
}
);
}
}
});
};

render() {
const {
posting,
onCancel,
isEditForm,
eventType,
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 (
<Form onSubmit={this.handleSubmit} autoComplete="off">
{/* Event Type name */}
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<Form.Item {...formItemLayout} label="Name">
{getFieldDecorator('name', {
initialValue: isEditForm ? eventType.strings.name.en : undefined,
rules: [
{
required: true,
message: ' Event Types organization name is required',
},
],
})(<Input />)}
</Form.Item>
{/* end Event Type name */}

{/* Event Type group */}
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<Form.Item {...formItemLayout} label="Group">
{getFieldDecorator('group', {
initialValue:
isEditForm && eventType.relations.group // eslint-disable-line
? eventType.relations.group._id // eslint-disable-line
: undefined,
rules: [{ message: 'Event Type group is required' }],
})(
<SearchableSelectInput
onSearch={getEventGroups}
optionLabel={group => group.strings.name.en}
optionValue="_id"
initialValue={
isEditForm && eventType.relations.group
? eventType.relations.group
: undefined
}
/>
)}
</Form.Item>
{/* end Event Type group */}

{/* Event type */}
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<Form.Item {...formItemLayout} label="Description">
{getFieldDecorator('description', {
initialValue: isEditForm
? eventType.strings.description.en
: undefined,
rules: [{ required: true, message: 'Description is required' }],
})(<Input />)}
</Form.Item>
{/* end Event Type */}

{/* form actions */}
<Form.Item wrapperCol={{ span: 24 }} style={{ textAlign: 'right' }}>
<Button onClick={onCancel}>Cancel</Button>
<Button
style={{ marginLeft: 8 }}
type="primary"
htmlType="submit"
loading={posting}
>
Save
</Button>
</Form.Item>
{/* end form actions */}
</Form>
);
}
}

EventTypeForm.propTypes = {
eventType: PropTypes.shape({
strings: PropTypes.shape({
name: PropTypes.shape({
en: PropTypes.string.isRequired,
}),
description: PropTypes.shape({
en: PropTypes.string.isRequired,
}),
_id: PropTypes.string,
}),
relations: PropTypes.shape({
group: PropTypes.string,
}),
}).isRequired,
isEditForm: PropTypes.bool.isRequired,
posting: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired,
form: PropTypes.shape({
getFieldDecorator: PropTypes.func,
validateFieldsAndScroll: PropTypes.func,
}).isRequired,
};

export default Form.create()(EventTypeForm);
250 changes: 250 additions & 0 deletions src/Events/components/EventGroups/List/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
import { httpActions } from '@codetanzania/ewea-api-client';
import {
deleteEventGroup,
paginateEventGroups,
refreshEventGroups,
} from '@codetanzania/ewea-api-states';
import { List } from 'antd';
import concat from 'lodash/concat';
import intersectionBy from 'lodash/intersectionBy';
import map from 'lodash/map';
import remove from 'lodash/remove';
import uniq from 'lodash/uniq';
import uniqBy from 'lodash/uniqBy';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import ListHeader from '../../../../components/ListHeader';
import Toolbar from '../../../../components/Toolbar';
import { notifyError, notifySuccess } from '../../../../util';
import EventGroupsListItem from '../ListItem';

/* constants */
const nameSpan = { xxl: 5, xl: 5, lg: 4, md: 5, sm: 14, xs: 14 };
const codeSpan = { xxl: 2, xl: 2, lg: 2, md: 2, sm: 5, xs: 5 };
const descriptionSpan = { xxl: 14, xl: 14, lg: 15, md: 14, sm: 0, xs: 0 };

const headerLayout = [
{ ...nameSpan, header: 'Name' },
{ ...codeSpan, header: 'Code' },
{ ...descriptionSpan, header: 'Description' },
];
const { getEventGroupsExportUrl } = httpActions;

/**
* @class
* @name EventGroupsList
*
* @description Render EventGroupsList
* component which have actionBar, event groups
* header and event groups list components
*
* @version 0.1.0
* @since 0.1.0
*/
class EventGroupsList extends Component {
// eslint-disable-next-line react/state-in-constructor
state = {
selectedEventGroups: [],
selectedPages: [],
};

/**
* @function
* @name handleOnSelectFocalPerson
* @description Handle select a single eventGroup action
*
* @param {object} eventGroup selected eventGroup object
*
* @version 0.1.0
* @since 0.1.0
*/
handleOnSelectFocalPerson = eventGroup => {
const { selectedEventGroups } = this.state;
this.setState({
selectedEventGroups: concat([], selectedEventGroups, eventGroup),
});
};

/**
* @function
* @name handleSelectAll
* @description Handle select all event Groups actions from current page
*
* @version 0.1.0
* @since 0.1.0
*/
handleSelectAll = () => {
const { selectedEventGroups, selectedPages } = this.state;
const { eventGroups, page } = this.props;
const selectedList = uniqBy(
[...selectedEventGroups, ...eventGroups],
'_id'
);
const pages = uniq([...selectedPages, page]);
this.setState({
selectedEventGroups: selectedList,
selectedPages: pages,
});
};

/**
* @function
* @name handleDeselectAll
* @description Handle deselect all event Groups in a current page
*
* @returns {undefined} undefined
*
* @version 0.1.0
* @since 0.1.0
*/
handleDeselectAll = () => {
const { eventGroups, page } = this.props;
const { selectedEventGroups, selectedPages } = this.state;
const selectedList = uniqBy([...selectedEventGroups], '_id');
const pages = uniq([...selectedPages]);

remove(pages, item => item === page);

eventGroups.forEach(eventGroup => {
remove(
selectedList,
item => item._id === eventGroup._id // eslint-disable-line
);
});

this.setState({
selectedEventGroups: selectedList,
selectedPages: pages,
});
};

/**
* @function
* @name handleOnDeselectEventGroups
* @description Handle deselect a single eventGroup action
*
* @param {object} eventGroup eventGroup to be removed from selected focalPeople
* @returns {undefined} undefined
*
* @version 0.1.0
* @since 0.1.0
*/
handleOnDeselectEventGroups = eventGroup => {
const { selectedEventGroups } = this.state;
const selectedList = [...selectedEventGroups];

remove(
selectedList,
item => item._id === eventGroup._id // eslint-disable-line
);

this.setState({ selectedEventGroups: selectedList });
};

render() {
const { eventGroups, loading, page, total, onEdit } = this.props;
const { selectedEventGroups, selectedPages } = this.state;
const selectedEventGroupsCount = intersectionBy(
selectedEventGroups,
eventGroups,
'_id'
).length;

return (
<>
{/* toolbar */}
<Toolbar
itemName="Event Groups"
page={page}
total={total}
selectedItemsCount={selectedEventGroupsCount}
exportUrl={getEventGroupsExportUrl({
filter: { _id: map(selectedEventGroups, '_id') },
})}
onPaginate={nextPage => {
paginateEventGroups(nextPage);
}}
onRefresh={() =>
refreshEventGroups(
() => {
notifySuccess('Event Groups refreshed successfully');
},
() => {
notifyError(
'An Error occurred while refreshing Event Groups please contact system administrator'
);
}
)
}
/>
{/* end toolbar */}

{/* eventGroup list header */}
<ListHeader
headerLayout={headerLayout}
onSelectAll={this.handleSelectAll}
onDeselectAll={this.handleDeselectAll}
isBulkSelected={selectedPages.includes(page)}
/>
{/* end eventGroup list header */}

{/* eventGroups list */}
<List
loading={loading}
dataSource={eventGroups}
renderItem={eventGroup => (
<EventGroupsListItem
key={eventGroup._id} // eslint-disable-line
abbreviation={eventGroup.strings.abbreviation.en}
name={eventGroup.strings.name.en}
code={eventGroup.strings.code}
description={
eventGroup.strings.description
? eventGroup.strings.description.en
: 'N/A'
}
isSelected={
// eslint-disable-next-line
map(selectedEventGroups, item => item._id).includes(
eventGroup._id // eslint-disable-line
)
}
onSelectItem={() => {
this.handleOnSelectFocalPerson(eventGroup);
}}
onDeselectItem={() => {
this.handleOnDeselectEventGroups(eventGroup);
}}
onEdit={() => onEdit(eventGroup)}
onArchive={() =>
deleteEventGroup(
eventGroup._id, // eslint-disable-line
() => {
notifySuccess('Event group was archived successfully');
},
() => {
notifyError(
'An Error occurred while archiving Event group please contact system administrator'
);
}
)
}
/>
)}
/>
{/* end eventGroups list */}
</>
);
}
}

EventGroupsList.propTypes = {
loading: PropTypes.bool.isRequired,
eventGroups: PropTypes.arrayOf(PropTypes.shape({ name: PropTypes.string }))
.isRequired,
page: PropTypes.number.isRequired,
total: PropTypes.number.isRequired,
onEdit: PropTypes.func.isRequired,
};

export default EventGroupsList;
178 changes: 178 additions & 0 deletions src/Events/components/EventGroups/ListItem/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { Avatar, Checkbox, Col, Modal, Row } from 'antd';
import PropTypes from 'prop-types';
import randomColor from 'randomcolor';
import React, { Component } from 'react';
import ListItemActions from '../../../../components/ListItemActions';
import './styles.css';

/* constants */
const { confirm } = Modal;
const sideSpan = { xxl: 1, xl: 1, lg: 1, md: 2, sm: 3, xs: 3 };
const nameSpan = { xxl: 5, xl: 5, lg: 4, md: 5, sm: 14, xs: 14 };
const codeSpan = { xxl: 2, xl: 2, lg: 2, md: 2, sm: 5, xs: 5 };
const descriptionSpan = { xxl: 14, xl: 14, lg: 15, md: 14, sm: 0, xs: 0 };
const isHoveredSpan = { xxl: 1, xl: 1, lg: 1, md: 1, sm: 2, xs: 2 };

/**
* @class
* @name EventGroupsListItem
* @description Single event group list item component.
* Render single event group details
*
* @version 0.1.0
* @since 0.1.0
*/
class EventGroupsListItem extends Component {
// eslint-disable-next-line react/state-in-constructor
state = {
isHovered: false,
};

/**
* @function
* @name handleMouseEnter
* useEnter
* @description Handle on MouseEnter ListItem event
*
* @version 0.1.0
* @since 0.1.0
*/
handleMouseEnter = () => {
this.setState({ isHovered: true });
};

/**
* @function
* @name handleMouseLeave
* @description Handle on MouseLeave ListItem event
*
* @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 event group
*
* @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 { abbreviation, description, code, name, onEdit } = this.props;
const { isHovered } = this.state;
const { isSelected } = this.props;
const avatarBackground = randomColor();
let sideComponent = null;

if (isSelected) {
sideComponent = (
<Checkbox
className="Checkbox"
onChange={this.handleToggleSelect}
checked={isSelected}
/>
);
} else {
sideComponent = isHovered ? (
<Checkbox
className="Checkbox"
onChange={this.handleToggleSelect}
checked={isSelected}
/>
) : (
<Avatar style={{ backgroundColor: avatarBackground }}>
{abbreviation}
</Avatar>
);
}

return (
<div
className="EventGroupsListItem"
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
>
<Row>
{/* eslint-disable react/jsx-props-no-spreading */}
<Col {...sideSpan}>{sideComponent}</Col>
<Col {...nameSpan}>{name}</Col>
<Col {...codeSpan}>{code}</Col>
<Col {...descriptionSpan} title={description}>
{' '}
{description}{' '}
</Col>
<Col {...isHoveredSpan}>
{/* eslint-disable react/jsx-props-no-spreading */}
{isHovered && (
<ListItemActions
edit={{
name: 'Edit Event Group',
title: 'Update Event Group Details',
onClick: onEdit,
}}
archive={{
name: 'Archive Event Group',
title: 'Remove Event Group from list of active Event Groups',
onClick: this.showArchiveConfirm,
}}
/>
)}
</Col>
</Row>
</div>
);
}
}

EventGroupsListItem.propTypes = {
abbreviation: PropTypes.string.isRequired,
code: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
onArchive: PropTypes.func.isRequired,
onEdit: PropTypes.func.isRequired,
isSelected: PropTypes.bool.isRequired,
onSelectItem: PropTypes.func.isRequired,
onDeselectItem: PropTypes.func.isRequired,
};

export default EventGroupsListItem;
42 changes: 42 additions & 0 deletions src/Events/components/EventGroups/ListItem/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.EventGroupsListItem {
width: 100%;
border-bottom: 1px solid #e8e8e8;
padding: 15px 0;
height: 60px;
}

.EventGroupsListItem .Checkbox {
padding: 0 8px;
}

.EventGroupsListItem:hover {
background-color: #f5f5f5;
}

.EventGroupsListItem .actionIcon:hover {
color: #008efa;
background-color: #e8e8e8;
}

.EventGroupsListItem .actionIcon {
padding-top: 8px;
margin-bottom: 5px;
width: 32px;
height: 32px;
font-size: 16px;
display: inline-block;
text-align: center;
border-radius: 50%;
cursor: pointer;
}

.EventGroupsListItem .actionIcon {
display: inline-block;
margin: 0 10px;
font-size: 16px;
}

.EventGroupsListItem .actionIcon:hover {
color: #008efa;
background-color: #e8e8e8;
}
201 changes: 201 additions & 0 deletions src/Events/components/EventGroups/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import {
Connect,
getEventGroups,
openEventGroupForm,
searchEventGroups,
selectEventGroup,
closeEventGroupForm,
} from '@codetanzania/ewea-api-states';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Modal } from 'antd';
import Topbar from '../../../components/Topbar';
import EventGroupForm from './Form';
import EventGroupsList from './List';
import './styles.css';

/* constants */

/**
* @class
* @name EventGroups
* @description Render Event Groups list which have search box,
* actions and event groups list
*
* @version 0.1.0
* @since 0.1.0
*/
class EventGroups extends Component {
// eslint-disable-next-line react/state-in-constructor
state = {
isEditForm: false,
};

componentDidMount() {
getEventGroups();
}

/**
* @function
* @name openEventGroupsForm
* @description Open event group form
*
* @version 0.1.0
* @since 0.1.0
*/
openEventGroupsForm = () => {
openEventGroupForm();
};

/**
* @function
* @name closeEventGroupsForm
* @description close event group form
*
* @version 0.1.0
* @since 0.1.0
*/
closeEventGroupsForm = () => {
closeEventGroupForm();
this.setState({ isEditForm: false });
};

/**
* @function
* @name searchEventGroups
* @description Search Event Groups List based on supplied filter word
*
* @param {object} event - Event instance
*
* @version 0.1.0
* @since 0.1.0
*/
searchEventGroups = event => {
searchEventGroups(event.target.value);
};

/**
* @function
* @name handleEdit
* @description Handle on Edit action for list item
*
* @param {object} eventType event group to be edited
*
* @version 0.1.0
* @since 0.1.0
*/
handleEdit = eventType => {
selectEventGroup(eventType);
this.setState({ isEditForm: true });
openEventGroupForm();
};

/**
* @function
* @name handleAfterCloseForm
* @description Perform post close form cleanups
*
* @version 0.1.0
* @since 0.1.0
*/
handleAfterCloseForm = () => {
this.setState({ isEditForm: false });
};

render() {
const {
eventGroups,
loading,
page,
posting,
eventType,
showForm,
searchQuery,
total,
} = this.props;
const { isEditForm } = this.state;
return (
<>
{/* Topbar */}
<Topbar
search={{
size: 'large',
placeholder: 'Search for Event groups here ...',
onChange: this.searchEventGroups,
value: searchQuery,
}}
actions={[
{
label: 'New Event Group',
icon: 'plus',
size: 'large',
title: 'Add New Event Group',
onClick: this.openEventGroupsForm,
},
]}
/>
{/* end Topbar */}

<div className="EventGroupsList">
{/* list starts */}
<EventGroupsList
total={total}
page={page}
eventGroups={eventGroups}
loading={loading}
onEdit={this.handleEdit}
/>
{/* end list */}

{/* create/edit form modal */}
<Modal
title={isEditForm ? 'Edit Event Group' : 'Add New Event Group'}
visible={showForm}
className="FormModal"
footer={null}
onCancel={this.closeEventGroupsForm}
destroyOnClose
maskClosable={false}
afterClose={this.handleAfterCloseForm}
>
<EventGroupForm
posting={posting}
isEditForm={isEditForm}
eventType={eventType}
onCancel={this.closeEventGroupsForm}
/>
</Modal>
{/* end create/edit form modal */}
</div>
</>
);
}
}

EventGroups.propTypes = {
loading: PropTypes.bool.isRequired,
eventGroups: PropTypes.arrayOf(PropTypes.shape({ name: PropTypes.string }))
.isRequired,
eventType: PropTypes.shape({ name: PropTypes.string }),
page: PropTypes.number.isRequired,
searchQuery: PropTypes.string,
total: PropTypes.number.isRequired,
posting: PropTypes.bool.isRequired,
showForm: PropTypes.bool.isRequired,
};

EventGroups.defaultProps = {
eventType: null,
searchQuery: undefined,
};

export default Connect(EventGroups, {
eventGroups: 'eventGroups.list',
eventType: 'eventGroups.selected',
loading: 'eventGroups.loading',
posting: 'eventGroups.posting',
page: 'eventGroups.page',
showForm: 'eventGroups.showForm',
total: 'eventGroups.total',
searchQuery: 'eventGroups.q',
});
31 changes: 31 additions & 0 deletions src/Events/components/EventGroups/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.EventGroupsList {
padding: 0 40px;
}

.FormModal {
width: 50% !important;
}

@media screen and (max-width: 1024px) {
.FormModal {
width: 70% !important;
}
}

@media screen and (max-width: 768px) {
.EventGroupsList {
padding: 0 30px;
}
.FormModal {
width: 80% !important;
}
}

@media screen and (max-width: 426px) {
.EventGroupsList {
padding: 0 20px;
}
.FormModal {
width: 90% !important;
}
}