@@ -110,6 +145,13 @@ class TeamsList extends Component {
return teams;
}
+ handleTeamSearchTermChanged = e => {
+ this.setState({ teamSearchTerm: e.target.value });
+
+ const value = e.target.value.trim();
+ if (value !== '') this.props.fetchTeams(e.target.value.trim());
+ };
+
renderTeamList() {
const teamRows = this.renderTeamRows();
@@ -124,19 +166,41 @@ class TeamsList extends Component {
)}
+
+
+
+
+
+
+
+
{teamRows.length > 0 ? (
-
-
-
- Team Name |
- Team Leader |
- Region |
- Members |
- {!this.props.currentUser.teamId && | }
-
-
- {teamRows}
-
+
+
+
+
+ Team Name |
+ Team Leader |
+ Region |
+ Members |
+ {!this.props.currentUser.teamId && | }
+
+
+ {teamRows}
+
+
) : (
There are no teams at the moment.
)}
@@ -155,10 +219,17 @@ class TeamsList extends Component {
if (currentUser.teamId) {
output = (
-
- You are on team {teams[currentUser.teamId].name}!{' '}
- View Details.
-
+
+ {teams[currentUser.teamId] && (
+
+ {teams[currentUser.teamId].name}
+
+ )}
+
);
} else {
output = (
@@ -170,7 +241,7 @@ class TeamsList extends Component {
return (
- Personal Team Status
+ Personal Team
{output}
);
@@ -214,5 +285,5 @@ const mapStateToProps = state => {
export default connect(
mapStateToProps,
- { fetchTeams, fetchUsers, editUser, createJoinRequest, fetchJoinRequests }
+ { fetchTeams, fetchCurrentTeam, fetchUsers, editUser, createJoinRequest, fetchJoinRequests }
)(TeamsList);
diff --git a/transaction-client/src/js/components/forms/EditActivityTypeForm.js b/transaction-client/src/js/components/forms/EditActivityTypeForm.js
new file mode 100644
index 00000000..92fbca9b
--- /dev/null
+++ b/transaction-client/src/js/components/forms/EditActivityTypeForm.js
@@ -0,0 +1,121 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import { Field, reduxForm } from 'redux-form';
+import _ from 'lodash';
+
+import { createActivityType, editActivityType } from '../../actions';
+
+import FormModal from '../ui/FormModal';
+import FormInput from '../ui/FormInput';
+
+import * as Constants from '../../Constants';
+
+class EditActivityTypeForm extends React.Component {
+ state = { submitting: false };
+
+ onInit = () => {
+ this.props.initialize(this.props.initialValues);
+ };
+
+ onSubmit = formValues => {
+ if (!this.state.submitting) {
+ this.setState({ submitting: true });
+ }
+
+ if (this.props.formType === Constants.FORM_TYPE.ADD) {
+ this.props.createActivityType(formValues).then(() => {
+ this.toggleModal();
+ });
+ } else {
+ this.props.editActivityType(formValues.id, formValues).then(() => {
+ this.toggleModal();
+ });
+ }
+ };
+
+ toggleModal = () => {
+ this.setState({ submitting: false });
+
+ this.props.toggle();
+ };
+
+ renderRegionOptions() {
+ const regionOptions = Object.values(this.props.regions).map(region => {
+ return (
+
+ );
+ });
+
+ regionOptions.unshift();
+ regionOptions.unshift(
+
+ );
+
+ return regionOptions;
+ }
+
+ render() {
+ const title = this.props.formType === Constants.FORM_TYPE.ADD ? 'Create Activity Type' : 'Edit Activity Type';
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+EditActivityTypeForm.propTypes = {
+ isOpen: PropTypes.bool.isRequired,
+ pristine: PropTypes.bool.isRequired,
+ handleSubmit: PropTypes.func.isRequired,
+};
+
+EditActivityTypeForm.defaultProps = { isOpen: false, pristine: false };
+
+const validate = formValues => {
+ const errors = {};
+
+ if (!formValues.name) {
+ errors.name = 'Name required';
+ }
+
+ if (!formValues.description) {
+ errors.description = 'Description required';
+ }
+
+ return errors;
+};
+
+const form = reduxForm({ form: 'editActivityTypeForm', enableReinitialize: true, validate })(EditActivityTypeForm);
+
+const formConnect = connect(
+ null,
+ { createActivityType, editActivityType }
+)(form);
+
+export default formConnect;
diff --git a/transaction-client/src/js/components/forms/LogActivityForm.js b/transaction-client/src/js/components/forms/LogActivityForm.js
index 49ed31c5..341fed51 100644
--- a/transaction-client/src/js/components/forms/LogActivityForm.js
+++ b/transaction-client/src/js/components/forms/LogActivityForm.js
@@ -6,22 +6,43 @@ import { Field, reduxForm } from 'redux-form';
import moment from 'moment';
import _ from 'lodash';
-import { fetchActivityList, createUserActivity, fetchTeamStandings } from '../../actions';
+import { fetchActivityList, createUserActivity, fetchTeamStandings, fetchEvent } from '../../actions';
import FormModal from '../ui/FormModal';
import FormInput from '../ui/FormInput';
import DatePickerInput from '../ui/DatePickerInput';
+import DropdownInput from '../ui/DropdownInput';
import PageSpinner from '../ui/PageSpinner';
+import * as utils from '../../utils';
+
+const headers = ['> Low Intensity Activities', '> Medium Intensity Activities', '> High Intensity Activities'];
+
class LogActivityForm extends React.Component {
- state = { submitting: false, loading: false };
+ state = { submitting: false, loading: true };
+
+ componentDidMount() {}
onInit = () => {
- if (this.props.activities.length === 0) {
+ const { events, eventId, fetchEvent, activities, fetchActivityList, initialize, initialValues } = this.props;
+
+ const actions = [];
+
+ if (!events[eventId]) {
+ actions.push(utils.buildActionWithParam(fetchEvent, eventId));
+ }
+
+ if (activities.length === 0) {
+ actions.push(utils.buildActionWithParam(fetchActivityList));
+ }
+
+ if (actions.length > 0) {
this.setState({ loading: true });
- this.props.fetchActivityList().then(() => {
- this.props.initialize(this.props.initialValues);
+ Promise.all(actions.map(action => action.action(action.param))).then(() => {
+ initialize(initialValues);
this.setState({ loading: false });
});
+ } else {
+ this.setState({ loading: false });
}
};
@@ -48,31 +69,63 @@ class LogActivityForm extends React.Component {
this.props.toggle();
};
- renderActivityOptions = () => {
- const activityOptions = this.props.activities.map(activity => {
- return (
-
- );
- });
-
- activityOptions.unshift();
- activityOptions.unshift(
-
- );
+ createActivityOptions = () => {
+ const activityOptions = [];
+
+ let i;
+ for (i = 0; i < 3; i++) {
+ activityOptions.push(...this.createActivityIntensitySection(i + 1));
+ }
return activityOptions;
};
+ createActivityIntensitySection(intensity) {
+ const { activities } = this.props;
+ const activityOptions = [];
+
+ activityOptions.push({ type: 'header', text: headers[intensity - 1] });
+
+ activityOptions.push(
+ ..._.orderBy(activities.filter(o => o.name.toLowerCase() !== 'other' && o.intensity === intensity), ['name']).map(
+ o => ({
+ type: 'item',
+ text: o.name,
+ description: o.description,
+ value: o.id,
+ })
+ )
+ );
+
+ activityOptions.push(
+ ...activities
+ .filter(o => o.name.toLowerCase() === 'other' && o.intensity === intensity)
+ .map(o => ({
+ type: 'item',
+ text: o.name,
+ description: o.description,
+ value: o.id,
+ }))
+ );
+
+ return activityOptions;
+ }
+
renderFields = () => {
+ const { eventId, events } = this.props;
+
+ const maxDate = moment.min(moment(), moment(events[eventId].endDate, 'YYYY-MM-DD')).toDate();
+ const minDate = moment(events[eventId].startDate, 'YYYY-MM-DD').toDate();
return (
-
- {this.renderActivityOptions()}
-
+
+
@@ -124,12 +178,13 @@ class LogActivityForm extends React.Component {
}
LogActivityForm.propTypes = {
+ eventId: PropTypes.number.isRequired,
isOpen: PropTypes.bool.isRequired,
pristine: PropTypes.bool.isRequired,
handleSubmit: PropTypes.func.isRequired,
};
-const validate = formValues => {
+const validate = (formValues, props) => {
const errors = {};
if (formValues.activityId <= 0) {
@@ -146,6 +201,19 @@ const validate = formValues => {
errors.activityTimestamp = 'Selected date is in the future';
}
+ const event = props.events[props.eventId];
+
+ if (event) {
+ const eventStartDate = moment(event.startDate, 'YYYY-MM-DD');
+ const eventEndDate = moment(event.endDate, 'YYYY-MM-DD');
+
+ if (activityTimestamp > eventEndDate) {
+ errors.activityTimestamp = 'Selected date is after the event end date';
+ } else if (activityTimestamp < eventStartDate) {
+ errors.activityTimestamp = 'Selected date is before the event start date';
+ }
+ }
+
if (isNaN(formValues.activityHours) || isNaN(formValues.activityMinutes)) {
errors.activityHours = 'Enter a number';
} else {
@@ -182,12 +250,13 @@ const mapStateToProps = (state, ownProps) => {
activityMinutes: 0,
activityId: -1,
},
+ events: state.events,
};
};
const formConnect = connect(
mapStateToProps,
- { fetchActivityList, createUserActivity, fetchTeamStandings }
+ { fetchActivityList, createUserActivity, fetchTeamStandings, fetchEvent }
)(form);
export default formConnect;
diff --git a/transaction-client/src/js/components/fragments/EventListItem.js b/transaction-client/src/js/components/fragments/EventListItem.js
index 6b810bbe..2036a576 100644
--- a/transaction-client/src/js/components/fragments/EventListItem.js
+++ b/transaction-client/src/js/components/fragments/EventListItem.js
@@ -18,17 +18,33 @@ class EventListItem extends React.Component {
this.props.handleArchiveEvent(this.props.event);
};
+ unArchiveEvent = () => {
+ this.props.handleUnArchiveEvent(this.props.event);
+ };
renderEditButton() {
- return (
-
-
-
-
- );
+ if (this.props.isActive) {
+ return (
+
+
+
+
+ );
+ } else {
+ return (
+
+
+
+
+ );
+ }
}
render() {
diff --git a/transaction-client/src/js/components/fragments/ProfileScoresPanel.js b/transaction-client/src/js/components/fragments/ProfileScoresPanel.js
index 93a79df8..b9249a32 100644
--- a/transaction-client/src/js/components/fragments/ProfileScoresPanel.js
+++ b/transaction-client/src/js/components/fragments/ProfileScoresPanel.js
@@ -3,36 +3,28 @@ import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { Row, Col, Alert } from 'reactstrap';
-import { fetchAllUserScores, fetchAllTeamScores, fetchEvents } from '../../actions';
+import { fetchAllUserScores, fetchAllTeamScores } from '../../actions';
import PageSpinner from '../ui/PageSpinner';
import UserScoreCard from './UserScoreCard';
import LogActivityForm from '../forms/LogActivityForm';
import * as Constants from '../../Constants';
-import * as utils from '../../utils';
class ProfileScoresPanel extends React.Component {
- state = { loading: true, showLogActivityForm: false, logActivityEventId: null };
+ state = { loading: true, showLogActivityForm: false, logActivityEventId: null, cancelTokenSource: undefined };
componentDidMount() {
this.setState({ loading: true });
- const { fetchAllUserScores, fetchAllTeamScores, fetchEvents, currentUser } = this.props;
- const actionPromises = [];
+ const { fetchAllUserScores, fetchAllTeamScores, currentUser } = this.props;
if (currentUser.teamId) {
- actionPromises.push(utils.buildActionWithParam(fetchAllUserScores, currentUser.id));
- actionPromises.push(utils.buildActionWithParam(fetchEvents));
- actionPromises.push(utils.buildActionWithParam(fetchAllTeamScores, currentUser.teamId));
- }
-
- Promise.all(
- actionPromises.map(promise => {
- return promise.action(promise.param);
- })
- ).then(() => {
+ Promise.all([fetchAllUserScores(currentUser.id), fetchAllTeamScores(currentUser.teamId)]).then(() => {
+ this.setState({ loading: false });
+ });
+ } else {
this.setState({ loading: false });
- });
+ }
}
showLogActivityForm = eventId => {
@@ -46,7 +38,7 @@ class ProfileScoresPanel extends React.Component {
};
renderUserScores() {
- const { events, userIdToDisplay, teamIdToDisplay, scores, currentUser } = this.props;
+ const { userIdToDisplay, teamIdToDisplay, scores, currentUser } = this.props;
const combinedScores = [];
const userScores = scores.user[userIdToDisplay];
@@ -54,13 +46,21 @@ class ProfileScoresPanel extends React.Component {
if (userScores) {
Object.values(userScores).forEach(score => {
- combinedScores[score.eventId] = { ...combinedScores[score.eventId], userScore: score.score };
+ combinedScores[score.eventId] = {
+ ...combinedScores[score.eventId],
+ userScore: score.score,
+ event: { name: score.eventName, eventId: score.eventId },
+ };
});
}
if (teamScores) {
Object.values(teamScores).forEach(score => {
- combinedScores[score.eventId] = { ...combinedScores[score.eventId], teamScore: score.score };
+ combinedScores[score.eventId] = {
+ ...combinedScores[score.eventId],
+ teamScore: score.score,
+ event: { name: score.eventName, id: score.eventId },
+ };
});
}
@@ -72,7 +72,7 @@ class ProfileScoresPanel extends React.Component {
@@ -138,11 +138,10 @@ const mapStateToProps = state => {
return {
scores: state.scores,
currentUser: state.users.all[state.users.current.id],
- events: state.events,
};
};
export default connect(
mapStateToProps,
- { fetchAllUserScores, fetchAllTeamScores, fetchEvents }
+ { fetchAllUserScores, fetchAllTeamScores }
)(ProfileScoresPanel);
diff --git a/transaction-client/src/js/components/fragments/ScollLoader.js b/transaction-client/src/js/components/fragments/ScollLoader.js
index a5a16d79..35ad7d52 100644
--- a/transaction-client/src/js/components/fragments/ScollLoader.js
+++ b/transaction-client/src/js/components/fragments/ScollLoader.js
@@ -1,4 +1,6 @@
import React from 'react';
+import PropTypes from 'prop-types';
+import { Button } from 'reactstrap';
import _ from 'lodash';
class ScrollLoader extends React.Component {
@@ -15,13 +17,32 @@ class ScrollLoader extends React.Component {
scrollListener = _.debounce(() => {
if (window.innerHeight + window.pageYOffset >= document.documentElement.offsetHeight) {
// Scrolled to the bottom
- if (this.props.loader) this.props.loader();
+ if (this.props.shouldLoad && this.props.loader) this.props.loader();
}
}, 100);
render() {
- return this.props.children;
+ const { children, page, pageCount, loader, shouldLoad } = this.props;
+
+ return (
+
+ {children}
+ {shouldLoad && page < pageCount && (
+
+
+
+ )}
+
+ );
}
}
+ScrollLoader.propTypes = {
+ shouldLoad: PropTypes.bool,
+};
+
+ScrollLoader.defaultProps = { shouldLoad: true };
+
export default ScrollLoader;
diff --git a/transaction-client/src/js/components/fragments/TeamJoinRequestPanel.js b/transaction-client/src/js/components/fragments/TeamJoinRequestPanel.js
index d008102c..24aae897 100644
--- a/transaction-client/src/js/components/fragments/TeamJoinRequestPanel.js
+++ b/transaction-client/src/js/components/fragments/TeamJoinRequestPanel.js
@@ -39,17 +39,13 @@ class TeamJoinRequestPanel extends React.Component {
.then(() => {
return this.props.fetchUser(request.userId);
})
- .then(() => {
- this.closeConfirmDialog();
- });
+ .finally(() => this.closeConfirmDialog());
}
};
rejectRequest = (confirm, request) => {
if (confirm) {
- this.props.rejectJoinRequest(request).then(() => {
- this.closeConfirmDialog();
- });
+ this.props.rejectJoinRequest(request).finally(() => this.closeConfirmDialog());
} else {
this.closeConfirmDialog();
}
diff --git a/transaction-client/src/js/components/fragments/TeamMembersPanel.js b/transaction-client/src/js/components/fragments/TeamMembersPanel.js
index d8f6b822..e7329917 100644
--- a/transaction-client/src/js/components/fragments/TeamMembersPanel.js
+++ b/transaction-client/src/js/components/fragments/TeamMembersPanel.js
@@ -16,12 +16,13 @@ class TeamMembersPanel extends React.Component {
handleRemoveUser = (confirm, user) => {
if (confirm) {
- this.props.leaveTeam(user.teamId, user.id).then(() => {
- if (user.id === this.props.currentUser.id) this.props.fetchCurrentUser();
- else this.props.fetchUser(user.id);
-
- this.closeConfirmDialog();
- });
+ this.props
+ .leaveTeam(user.teamId, user.id)
+ .then(() => {
+ if (user.id === this.props.currentUser.id) this.props.fetchCurrentUser();
+ else this.props.fetchUser(user.id);
+ })
+ .finally(() => this.closeConfirmDialog());
} else {
this.closeConfirmDialog();
}
diff --git a/transaction-client/src/js/components/fragments/UserScoreCard.js b/transaction-client/src/js/components/fragments/UserScoreCard.js
index a1a3a492..cd9c3dec 100644
--- a/transaction-client/src/js/components/fragments/UserScoreCard.js
+++ b/transaction-client/src/js/components/fragments/UserScoreCard.js
@@ -4,6 +4,7 @@ import { Link } from 'react-router-dom';
import { Row, Col, Card, CardBody, CardHeader, Button, Progress } from 'reactstrap';
import LogActivityForm from '../forms/LogActivityForm';
+import ActivityJournalModal from '../ui/ActivityJournalModal';
import * as Constants from '../../Constants';
const ScoreRow = ({ score, description }) => {
@@ -22,7 +23,7 @@ const ScoreRow = ({ score, description }) => {
};
class UserScoreCard extends React.Component {
- state = { loading: true, showLogActivityForm: false };
+ state = { loading: true, showLogActivityForm: false, showActivityJournal: false };
showLogActivityForm = () => {
this.setState({ showLogActivityForm: true });
@@ -34,13 +35,23 @@ class UserScoreCard extends React.Component {
}));
};
- handleOnClick = () => {
+ showActivityJournal = () => {
+ this.setState({ showActivityJournal: true });
+ };
+
+ toggleActivityJournal = () => {
+ this.setState(prevState => ({
+ showActivityJournal: !prevState.showActivityJournal,
+ }));
+ };
+
+ handleShowLogActivityFormClick = () => {
this.showLogActivityForm(this.props.event.id);
};
renderLogActivityButton = () => {
return (
-
@@ -90,6 +106,13 @@ class UserScoreCard extends React.Component {
refreshStandings={refreshStandings}
/>
)}
+ {this.state.showActivityJournal && (
+
+ )}
);
}
diff --git a/transaction-client/src/js/components/ui/ActivityJournalModal.js b/transaction-client/src/js/components/ui/ActivityJournalModal.js
new file mode 100644
index 00000000..1a668bf8
--- /dev/null
+++ b/transaction-client/src/js/components/ui/ActivityJournalModal.js
@@ -0,0 +1,100 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Table } from 'reactstrap';
+import _ from 'lodash';
+import moment from 'moment';
+
+import PageSpinner from './PageSpinner';
+
+import { fetchEventUserActivityList, fetchActivityList } from '../../actions';
+
+class ActivityJournalModal extends React.Component {
+ state = { loading: true };
+
+ componentDidMount() {
+ const { currentUser, eventId, fetchEventUserActivityList, activityTypes, fetchActivityList } = this.props;
+
+ fetchEventUserActivityList(eventId, currentUser.id)
+ .then(() => {
+ if (Object.keys(activityTypes).length === 0) return fetchActivityList();
+ else Promise.resolve();
+ })
+ .then(() => {
+ this.setState({ loading: false });
+ });
+ }
+
+ renderContent() {
+ const { userActivities, activityTypes, eventId, currentUser } = this.props;
+
+ const thisEventUserActivities = Object.values(userActivities).filter(
+ o => o.eventId === eventId && o.userId === currentUser.id
+ );
+
+ const tableRows = _.orderBy(thisEventUserActivities, ['activityTimestamp'], ['desc']).map(o => (
+ |