Skip to content

Commit

Permalink
feat(approveActivities): add approve activities page
Browse files Browse the repository at this point in the history
 - add ApproveActivitiesComponent
 - add ApproveActivitiesContainer
 - add activities sidebar item
 - add tests
  • Loading branch information
Chris Maina committed Apr 16, 2019
1 parent 782f1bf commit 79c0c64
Show file tree
Hide file tree
Showing 11 changed files with 293 additions and 12 deletions.
54 changes: 54 additions & 0 deletions src/app/ApproveActivities/components/ApproveActivitiesComponent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react';
import dateFns from 'date-fns';
import PropTypes from 'prop-types';

import ActionsComponent from '../../VerifyActivities/components/ActionsComponent';
import { TableComponent, TruncateDescriptionContainer } from '../../common/components';

const ApproveActivitiesComponent = ({ activities }) => {
let tableBodyHtml;
const columnNames = ['Activity', 'Date', 'Points', 'Description', 'Actions'];
if (!activities.length) {
tableBodyHtml = (
<tr className='myactivities__table__row'>
<td colSpan={6} className='myactivities__table__data'>
There is no activities to approve
</td>
</tr>
);
} else {
tableBodyHtml = activities.map((activity) => {
const {
id, activityDate, points, description, category,
} = activity;
return (
<tr key={id} className='myactivities__table__row'>
<td>{category}</td>
<td>{dateFns.format(new Date(activityDate), 'MMM DD YYYY')}</td>
<td>{points}</td>
<td>
<TruncateDescriptionContainer description={description} wordCount={80} />
</td>
<td>
<ActionsComponent />
</td>
</tr>
);
});
}
return (
<TableComponent tableClassName='myactivities__table' tableHeadings={columnNames}>
{tableBodyHtml}
</TableComponent>
);
};

ApproveActivitiesComponent.defaultProps = {
activities: [],
};

ApproveActivitiesComponent.propTypes = {
activities: PropTypes.arrayOf(PropTypes.shape({})),
};

export default ApproveActivitiesComponent;
105 changes: 105 additions & 0 deletions src/app/ApproveActivities/components/ApproveActivitiesContainer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

import societyActions from '../../Societies/operations/actions';

import ApproveActivitiesComponent from './ApproveActivitiesComponent';
import { ButtonComponent, SocietyStatsComponent, TabsComponent } from '../../common/components';

import ACTIVITY_STATUS from '../../common/constants';

export class ApproveActivitiesContainer extends Component {
static defaultProps = {
society: {},
fetchSocietyInfoRequest: null,
fetchSocietyRedemptionsRequest: null,
};

static propTypes = {
society: PropTypes.shape({}),
fetchSocietyInfoRequest: PropTypes.func,
fetchSocietyRedemptionsRequest: PropTypes.func,
};

state = {
selectedSociety: 'istelle',
};

componentDidMount() {
const { selectedSociety } = this.state;
const { fetchSocietyRedemptionsRequest, fetchSocietyInfoRequest } = this.props;
fetchSocietyInfoRequest(selectedSociety.toLowerCase());
fetchSocietyRedemptionsRequest(selectedSociety.toLowerCase());
}

componentDidUpdate(prevProps, prevState) {
const { selectedSociety } = this.state;
const { fetchSocietyRedemptionsRequest, fetchSocietyInfoRequest } = this.props;

if (prevState.selectedSociety !== selectedSociety && !prevProps.society[selectedSociety].redemptions.length) {
fetchSocietyInfoRequest(selectedSociety.toLowerCase());
fetchSocietyRedemptionsRequest(selectedSociety.toLowerCase());
}
}

changeSelectedSociety = (societyName) => {
this.setState({ selectedSociety: societyName });
};

filterActivitiesByPendingStatus = activities => (
activities.filter(item => item.status === ACTIVITY_STATUS.PENDING))

render() {
const { society } = this.props;
const { selectedSociety } = this.state;
const {
usedPoints, pointsEarned, remainingPoints, activitiesLogged, loggedActivities,
} = society[selectedSociety];
const tabNames = ['istelle', 'invictus', 'phoenix', 'sparks'];
const pendingActivities = this.filterActivitiesByPendingStatus(loggedActivities);

return (
<div>
<div className='profile-overview profile-overview--society'>
<div className={`profile-overview__image--society ${selectedSociety.toLowerCase()}`} />
<SocietyStatsComponent
usedPoints={usedPoints}
totalPoints={pointsEarned}
remainingPoints={remainingPoints}
activitiesLogged={activitiesLogged}
className='society-page__stats'
/>
</div>
<div className='user-dashboard__actions user-dashboard__actions--society col-sm-12'>
<TabsComponent
tabNames={tabNames}
selectedTab={selectedSociety}
changeSelectedTab={this.changeSelectedSociety}
/>
<div>
<ButtonComponent className='button__filter'>
<span>Filter</span>
<span className='fa fa-filter' />
</ButtonComponent>
</div>
</div>
<ApproveActivitiesComponent activities={pendingActivities} />
</div>
);
}
}

const mapStateToProps = ({ society }) => ({
society,
});

const mapDispatchToProps = {
fetchSocietyInfoRequest: societyActions.fetchSocietyInfoRequest,
fetchSocietyRedemptionsRequest: societyActions.fetchSocietyRedemptionsRequest,
};

export default connect(
mapStateToProps,
mapDispatchToProps,
)(ApproveActivitiesContainer);
1 change: 1 addition & 0 deletions src/app/ApproveActivities/components/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './ApproveActivitiesContainer';
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import { shallow } from 'enzyme';

import ApproveActivitiesComponent from '../ApproveActivitiesComponent';

import activities from '../../../Dashboard/operations/tests/fixtures';

describe('<ApproveActivitiesComponent />', () => {
const setUpWrapper = ({ activities = [] } = {}) => {
const props = {
activities,
};
return shallow(<ApproveActivitiesComponent {...props} />);
};

it('should have a TableComponent', () => {
const shallowWrapper = setUpWrapper();
expect(shallowWrapper.find('TableComponent')).toHaveLength(1);
});

it('should have a description of no activities when activities prop is empty', () => {
const shallowWrapper = setUpWrapper();
expect(shallowWrapper.find('TableComponent').html()).toContain('There is no activities to approve');
});

it('should have activity table column names', () => {
const shallowWrapper = setUpWrapper({ activities });
expect(shallowWrapper.find('TableComponent').props().tableHeadings).toEqual([
'Activity',
'Date',
'Points',
'Description',
'Actions',
]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from 'react';
import { shallow } from 'enzyme';

import { ApproveActivitiesContainer } from '../ApproveActivitiesContainer';

import ACTIVITY_STATUS from '../../../common/constants';
import activities from '../../../Dashboard/operations/tests/fixtures';
import { redemptions } from '../../../Redemptions/components/tests/fixtures';

describe('<ApproveActivitiesContainer />', () => {
const props = {
society: {
phoenix: {
usedPoints: 100,
remainingPoints: 100,
totalPoints: 200,
activitiesLogged: activities.length,
loggedActivities: activities,
redemptions,
},
istelle: {
usedPoints: 100,
remainingPoints: 100,
totalPoints: 200,
activitiesLogged: activities.length,
loggedActivities: activities,
redemptions,
}
},
fetchSocietyInfoRequest: jest.fn(),
fetchSocietyRedemptionsRequest: jest.fn()
};
const shallowWrapper = shallow(<ApproveActivitiesContainer {...props} />);

it('has a 1 ButtonComponent', () => {
expect(shallowWrapper.find('ButtonComponent')).toHaveLength(1);
});

it('has TabsComponent', () => {
expect(shallowWrapper.find('TabsComponent')).toHaveLength(1);
});

it('changes selectedSociety state when changeSelectedSociety is called with a society name', () => {
const instance = shallowWrapper.instance();
instance.setState({ selectedSociety: 'istelle' });
instance.changeSelectedSociety('phoenix');
expect(instance.state.selectedSociety).toEqual('phoenix');
});

it('returns activities with pending status', () => {
const instance = shallowWrapper.instance();
const pendingActivities = activities.filter(item => item.status === ACTIVITY_STATUS.PENDING);
expect(instance.filterActivitiesByPendingStatus(activities)).toEqual(pendingActivities);
});

it('invokes fetchSocietyInfoRequest when selectedSociety state change', () => {
const instance = shallowWrapper.instance();
const spy = jest.spyOn(instance.props, 'fetchSocietyInfoRequest');
instance.setState({ selectedSociety: 'phoenix' });
expect(spy).toHaveBeenCalled();
});
});
20 changes: 14 additions & 6 deletions src/app/Sidebar/components/SidebarContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,20 @@ export class SidebarContainer extends Component {
);
} else if (userRole && Object.keys(userRole).includes('success ops')) {
navItemHtml = (
<NavItemComponent
route='approve-budget'
iconClassName='sidebar_nav-icon outlinedCheckmark'
labelClassName='sidebar_nav-label'
navItemClassName='sidebar_nav-item'
/>
<>
<NavItemComponent
route='activities'
iconClassName='sidebar_nav-icon outlinedCheckmark'
labelClassName='sidebar_nav-label'
navItemClassName='sidebar_nav-item'
/>
<NavItemComponent
route='budget'
iconClassName='sidebar_nav-icon outlinedCheckmark'
labelClassName='sidebar_nav-label'
navItemClassName='sidebar_nav-item'
/>
</>
);
}
return (
Expand Down
9 changes: 7 additions & 2 deletions src/app/Sidebar/components/tests/SidebarContainer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,13 @@ describe('<SidebarContainer />', () => {
expect(wrapper.html()).toContain('Redemptions');
});

it('has Approve budget navigation item', () => {
it('has Budget navigation item', () => {
const wrapper = setUpWrapper({ userRole: { 'success ops': '12345' }});
expect(wrapper.html()).toContain('Approve budget');
expect(wrapper.html()).toContain('Budget');
});

it('has Activities navigation item', () => {
const wrapper = setUpWrapper({ userRole: { 'success ops': '12345' }});
expect(wrapper.html()).toContain('Activities');
});
});
5 changes: 4 additions & 1 deletion src/app/Sidebar/styles/sidebar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
}

.sidebar_nav {
margin-top: rem(80px);
margin-top: rem(50px);
flex: 2;
}

Expand All @@ -32,6 +32,7 @@
@include widthAndHeight(rem(136px), rem(37px));
margin-bottom: rem(12.94px);
text-decoration: none !important;
align-items: center;

&:hover {
border-left: rem(4px) solid $lightAccent;
Expand Down Expand Up @@ -93,6 +94,8 @@
}

.sidebar__separator--top {
margin: 0;
margin-bottom: 5px;
box-sizing: border-box;
@include widthAndHeight(rem(127.78px), rem(0.81px));
background-color: $separatorBlue;
Expand Down
2 changes: 1 addition & 1 deletion src/app/common/components/NavItemComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const NavItemComponent = ({
return (
<Link to={`/${route}`} className={navItemClassName}>
<div className={iconClassName}>{checkmarkHtml}</div>
<span className={labelClassName}>{capitalize(route.replace(/-/g, " "))}</span>
<span className={labelClassName}>{capitalize(route.replace(/-/g, ' '))}</span>
</Link>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/app/common/components/TabsComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const TabsComponent = ({ selectedTab, changeSelectedTab, tabNames }) => {
return (
<div className='tabs'>
{tabNames
&& tabNames.map((name) => (
&& tabNames.map(name => (
<h3
key={name}
aria-hidden
Expand Down
9 changes: 8 additions & 1 deletion src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import AuthenticateRoute from './app/Authentication/components';
import RedemptionsContainer from './app/Redemptions/components';
import VerifyActivitiesContainer from './app/VerifyActivities/components';
import ApproveBudgetContainer from './app/ApproveBudget/components';
import ApproveActivitiesContainer from './app/ApproveActivities/components';

import { tokenIsValid, getToken } from './app/utils';

Expand Down Expand Up @@ -45,7 +46,13 @@ const Router = () => (
<AuthenticateRoute
isAuthenticated={tokenIsValid(token)}
userInfo={token.UserInfo}
path='/approve-budget'
path='/activities'
component={ApproveActivitiesContainer}
/>
<AuthenticateRoute
isAuthenticated={tokenIsValid(token)}
userInfo={token.UserInfo}
path='/budget'
component={ApproveBudgetContainer}
/>
<AuthenticateRoute
Expand Down

0 comments on commit 79c0c64

Please sign in to comment.