diff --git a/client/package.json b/client/package.json
index 21e3f053..0033b6df 100644
--- a/client/package.json
+++ b/client/package.json
@@ -54,6 +54,7 @@
},
"dependencies": {
"app-module-path": "^1.0.3",
+ "chart.js": "^2.4.0",
"classnames": "^2.1.3",
"draft-js": "^0.8.1",
"draft-js-export-html": "^0.4.0",
@@ -63,6 +64,7 @@
"lodash": "^3.10.0",
"messageformat": "^0.2.2",
"react": "^15.0.1",
+ "react-chartjs-2": "^2.0.0",
"react-document-title": "^1.0.2",
"react-dom": "^15.0.1",
"react-google-recaptcha": "^0.5.2",
diff --git a/client/src/app-components/stats-chart.js b/client/src/app-components/stats-chart.js
new file mode 100644
index 00000000..ca1a2d49
--- /dev/null
+++ b/client/src/app-components/stats-chart.js
@@ -0,0 +1,100 @@
+import React from 'react';
+import {Line} from 'react-chartjs-2';
+
+import i18n from 'lib-app/i18n';
+
+class StatsChart extends React.Component {
+
+ static propTypes = {
+ strokes: React.PropTypes.arrayOf(React.PropTypes.shape({
+ name: React.PropTypes.string,
+ values: React.PropTypes.arrayOf(React.PropTypes.shape({
+ date: React.PropTypes.string,
+ value: React.PropTypes.number
+ }))
+ })),
+ period: React.PropTypes.number
+ };
+
+ render() {
+ return (
+
+
{this.props.items.map(this.renderItem.bind(this))}
);
}
+ getClass() {
+ let classes = {
+ 'toggle-list': true,
+ 'toggle-list_small': this.props.type == 'small',
+ [this.props.className]: (this.props.className)
+ };
+
+ return classNames(classes);
+ }
+
renderItem(obj, index) {
return (
@@ -35,16 +46,17 @@ class ToggleList extends React.Component {
let classes = {
'toggle-list__item': true,
'toggle-list__first-item': (index === 0),
- 'toggle-list__selected': _.includes(this.state.selected, index)
+ 'toggle-list__last-item': (index === this.props.items.length - 1),
+ 'toggle-list__selected': _.includes(this.getSelectedList(), index)
};
return classNames(classes);
}
selectItem(index) {
- let newSelected = _.clone(this.state.selected);
+ let newSelected = _.clone(this.getSelectedList());
- _.includes(this.state.selected, index) ? _.remove(newSelected, _index => _index == index) : newSelected.push(index);
+ _.includes(this.getSelectedList(), index) ? _.remove(newSelected, _index => _index == index) : newSelected.push(index);
this.setState({
selected: newSelected
@@ -58,6 +70,10 @@ class ToggleList extends React.Component {
});
}
}
+
+ getSelectedList() {
+ return (this.props.values === undefined) ? this.state.selected : this.props.values;
+ }
}
export default ToggleList;
diff --git a/client/src/core-components/toggle-list.scss b/client/src/core-components/toggle-list.scss
index 7bf244cb..1ba34f67 100644
--- a/client/src/core-components/toggle-list.scss
+++ b/client/src/core-components/toggle-list.scss
@@ -8,14 +8,28 @@
width: 180px;
height: 120px;
display: inline-block;
- transition: background-color 0.4s ease;
+ transition: box-shadow 0.2s ease-in-out;
+ user-select: none;
+ cursor: default;
}
&__selected {
- background-color: $light-grey;
+ box-shadow: inset 0 -5px 40px 10px rgba(0, 0, 0, 0.08);
}
&__first-item {
border: 1px $light-grey solid;
+ border-radius: 4px 0 0 4px;
+ }
+
+ &__last-item {
+ border-radius: 0 4px 4px 0;
+ }
+
+ &_small {
+ .toggle-list__item {
+ height: 80px;
+ width: 120px;
+ }
}
}
diff --git a/client/src/data/fixtures/system-fixtures.js b/client/src/data/fixtures/system-fixtures.js
index ddff91bd..62878719 100644
--- a/client/src/data/fixtures/system-fixtures.js
+++ b/client/src/data/fixtures/system-fixtures.js
@@ -166,6 +166,71 @@ module.exports = [
};
}
},
+ {
+ path: '/system/get-stats',
+ time: 200,
+ response: function(_data) {
+ let generalVal = _data.staffId;
+
+ let ID = {
+ 'WEEK': 7,
+ 'MONTH': 30,
+ 'QUARTER': 90,
+ 'YEAR': 365
+ };
+
+ let k = ID[_data.period];
+ let DATA = [];
+
+ for (let i = 0; i < k; i++) {
+ if(generalVal){
+ DATA.push({
+ date: '201701' + (i + 103) % 100,
+ type: 'ASSIGN',
+ general: generalVal,
+ value: (Math.floor((Math.random() + 17) * i)).toString()
+ });
+ DATA.push({
+ date: '201701' + (i + 109) % 100,
+ type: 'CLOSE',
+ general: generalVal,
+ value: (Math.floor((Math.random() + 12) * i )).toString()
+ });
+ }
+ else {
+ DATA.push({
+ date: '201701' + (i + 107) % 100,
+ type: 'COMMENT',
+ general: generalVal,
+ value: (Math.floor((Math.random() + 5) * i)).toString()
+ });
+ DATA.push({
+ date: '201701' + (i + 104) % 100,
+ type: 'SIGNUP',
+ general: generalVal,
+ value: (Math.floor(Math.random() * (i - 180) * (i - 185) / 400)).toString()
+ });
+ DATA.push({
+ date: '201701' + (i + 103) % 100,
+ type: 'CLOSE',
+ general: generalVal,
+ value: (Math.floor((Math.random() + 12) * i )).toString()
+ });
+ DATA.push({
+ date: '201701' + (i + 99) % 100,
+ type: 'CREATE_TICKET',
+ general: generalVal,
+ value: (Math.floor((Math.random() + 7) * i)).toString()
+ });
+ }
+ }
+
+ return {
+ status: "success",
+ data: DATA
+ };
+ }
+ },
{
path: '/system/get-logs',
time: 300,
diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js
index a7ec1983..da83ceb7 100644
--- a/client/src/data/languages/en.js
+++ b/client/src/data/languages/en.js
@@ -36,7 +36,7 @@ export default {
'DASHBOARD': 'Dashboard',
'USERS': 'Users',
'SETTINGS': 'Settings',
- 'TICKET_STATS': 'Ticket Stats',
+ 'STATISTICS': 'Statistics',
'LAST_ACTIVITY': 'Last Activity',
'MY_TICKETS': 'My Tickets',
'NEW_TICKETS': 'New Tickets',
@@ -152,6 +152,15 @@ export default {
'ALL_NOTIFICATIONS': 'All notifications',
'VERIFY_SUCCESS': 'User verified',
'VERIFY_FAILED': 'Could not verify',
+ 'STATISTICS': 'Statistics',
+ 'ACTIVITY': 'Activity',
+
+
+ 'CHART_CREATE_TICKET': 'Tickets created',
+ 'CHART_CLOSE': 'Tickets closed',
+ 'CHART_SIGNUP': 'Signups',
+ 'CHART_COMMENT': 'Replies',
+ 'CHART_ASSIGN': 'Assigned',
//ACTIVITIES
'ACTIVITY_COMMENT': 'commented ticket',
@@ -202,7 +211,7 @@ export default {
'ADD_ARTICLE_DESCRIPTION': 'Here you can add an article that will be available for every user. It will be added inside the category {category}.',
'LIST_ARTICLES_DESCRIPTION': 'This is a list of articles that includes information about our services.',
'ADD_TOPIC_DESCRIPTION': 'Here you can add a topic that works as a category for articles.',
- 'DELETE_ARTICLE_DESCRIPTION': 'You\'re going to delete this article for ever.',
+ 'DELETE_ARTICLE_DESCRIPTION': 'You\'re going to delete this article forever.',
'STAFF_MEMBERS_DESCRIPTION': 'Here you can see who are your staff members.',
'ADD_STAFF_DESCRIPTION': 'Here you can add staff members to your teams.',
'EDIT_STAFF_DESCRIPTION': 'Here you can edit information about a staff member.',
@@ -213,6 +222,7 @@ export default {
'SYSTEM_PREFERENCES_DESCRIPTION': 'Here you can edit the preferences of the system.',
'VERIFY_SUCCESS_DESCRIPTION': 'You user has been verified correctly. You can log in now.',
'VERIFY_FAILED_DESCRIPTION': 'The verification could not be done.',
+ 'STATISTICS_DESCRIPTION': 'Here you can view statistics related to tickets and signups.',
//ERRORS
'EMAIL_OR_PASSWORD': 'Email or password invalid',
diff --git a/client/src/reducers/session-reducer.js b/client/src/reducers/session-reducer.js
index be4323cc..a51d7e67 100644
--- a/client/src/reducers/session-reducer.js
+++ b/client/src/reducers/session-reducer.js
@@ -44,7 +44,8 @@ class SessionReducer extends Reducer {
logged: true,
pending: false,
failed: false,
- staff: payload.data.staff
+ staff: payload.data.staff,
+ userId: payload.data.userId
});
}
@@ -75,7 +76,8 @@ class SessionReducer extends Reducer {
initDone: true,
logged: true,
pending: false,
- failed: false
+ failed: false,
+ userId: payload.data.userId
});
}
@@ -117,6 +119,7 @@ class SessionReducer extends Reducer {
onSessionChecked(state) {
let userData = sessionStore.getUserData();
+ let userId = sessionStore.getSessionData().userId;
return _.extend({}, state, {
initDone: true,
@@ -127,7 +130,8 @@ class SessionReducer extends Reducer {
userProfilePic: userData.profilePic,
userLevel: userData.level,
userDepartments: userData.departments,
- userTickets: userData.tickets
+ userTickets: userData.tickets,
+ userId: userId
});
}