diff --git a/client/src/actions/session-actions.js b/client/src/actions/session-actions.js index 90aa07ae..08c95be8 100644 --- a/client/src/actions/session-actions.js +++ b/client/src/actions/session-actions.js @@ -12,7 +12,7 @@ export default { path: '/user/login', data: loginData }).then((result) => { - store.dispatch(this.getUserData(result.data.userId, result.data.token)); + store.dispatch(this.getUserData(result.data.userId, result.data.token, result.data.staff)); return result; }) @@ -49,7 +49,7 @@ export default { }; }, - getUserData(userId, token) { + getUserData(userId, token, staff) { let data = {}; if (userId && token) { @@ -62,7 +62,7 @@ export default { return { type: 'USER_DATA', payload: API.call({ - path: '/user/get', + path: (staff) ? '/staff/get' : '/user/get', data: data }) } diff --git a/client/src/app-components/ticket-list.js b/client/src/app-components/ticket-list.js new file mode 100644 index 00000000..6bf7f885 --- /dev/null +++ b/client/src/app-components/ticket-list.js @@ -0,0 +1,136 @@ +import React from 'react'; + +import i18n from 'lib-app/i18n'; + +import Table from 'core-components/table'; +import Button from 'core-components/button'; +import Tooltip from 'core-components/tooltip'; + +class TicketList extends React.Component { + static propTypes = { + tickets: React.PropTypes.arrayOf(React.PropTypes.object), + type: React.PropTypes.oneOf([ + 'primary', + 'secondary' + ]) + }; + + static defaultProps = { + tickets: [], + type: 'primary' + }; + + render() { + return ( +
+ + + ); + } + + getTableHeaders() { + if (this.props.type == 'primary' ) { + return [ + { + key: 'number', + value: i18n('NUMBER'), + className: 'ticket-list__number col-md-1' + }, + { + key: 'title', + value: i18n('TITLE'), + className: 'ticket-list__title col-md-6' + }, + { + key: 'department', + value: i18n('DEPARTMENT'), + className: 'ticket-list__department col-md-3' + }, + { + key: 'date', + value: i18n('DATE'), + className: 'ticket-list__date col-md-2' + } + ]; + } else if (this.props.type == 'secondary') { + return [ + { + key: 'number', + value: i18n('NUMBER'), + className: 'ticket-list__number col-md-1' + }, + { + key: 'title', + value: i18n('TITLE'), + className: 'ticket-list__title col-md-4' + }, + { + key: 'priority', + value: i18n('PRIORITY'), + className: 'ticket-list__priority col-md-1' + }, + { + key: 'department', + value: i18n('DEPARTMENT'), + className: 'ticket-list__department col-md-2' + }, + { + key: 'author', + value: i18n('AUTHOR'), + className: 'ticket-list__author col-md-2' + }, + { + key: 'date', + value: i18n('DATE'), + className: 'ticket-list__date col-md-2' + } + ]; + } + } + + getTableRows() { + return this.props.tickets.map(this.gerTicketTableObject.bind(this)); + } + + gerTicketTableObject(ticket) { + let titleText = (ticket.unread) ? ticket.title + ' (1)' : ticket.title; + + return { + number: ( + + {'#' + ticket.ticketNumber} + + ), + title: ( + + ), + priority: this.getTicketPriority(ticket.priority), + department: ticket.department.name, + author: ticket.author.name, + date: ticket.date, + highlighted: ticket.unread + }; + } + getTicketPriority(priority){ + if(priority == 'high'){ + return ( + {i18n('HIGH')} + ); + } + if(priority == 'medium'){ + return ( + {i18n('MEDIUM')} + ); + } + if(priority == 'low'){ + return ( + {i18n('LOW')} + ); + } + } +} + + +export default TicketList; diff --git a/client/src/app-components/tiket-list.scss b/client/src/app-components/tiket-list.scss new file mode 100644 index 00000000..2355c6e4 --- /dev/null +++ b/client/src/app-components/tiket-list.scss @@ -0,0 +1,47 @@ +@import "../scss/vars"; + +.ticket-list { + + &__number { + text-align: left; + } + + &__title { + text-align: left; + } + + &__department { + text-align: right; + } + + &__date { + text-align: right; + } + + &__title-link:hover, + &__title-link:focus { + outline: none; + text-decoration: underline; + } + + &__priority-low, + &__priority-medium, + &__priority-high { + display: inline-block; + border-radius: 10px; + width: 70px; + color: white; + } + + &__priority-low { + background-color: $primary-green; + } + + &__priority-medium { + background-color: $secondary-blue; + } + + &__priority-high { + background-color: $primary-red; + } +} \ No newline at end of file diff --git a/client/src/app/App.js b/client/src/app/App.js index 00f2efdf..52d55cb5 100644 --- a/client/src/app/App.js +++ b/client/src/app/App.js @@ -44,7 +44,9 @@ class App extends React.Component { const validations = { languageChanged: props.config.language !== this.props.config.language, loggedIn: !_.includes(props.location.pathname, '/dashboard') && props.session.logged, - loggedOut: _.includes(props.location.pathname, '/dashboard') && !props.session.logged + loggedOut: _.includes(props.location.pathname, '/dashboard') && !props.session.logged, + loggedInStaff: !_.includes(props.location.pathname, '/admin/panel') && props.session.logged && props.session.staff, + loggedOutStaff: _.includes(props.location.pathname, '/admin/panel') && !props.session.logged }; if (validations.languageChanged) { @@ -55,8 +57,14 @@ class App extends React.Component { browserHistory.push('/'); } - if (validations.loggedIn) { + if (validations.loggedOutStaff) { + browserHistory.push('/admin'); + } + + if (validations.loggedIn && !props.session.staff) { browserHistory.push('/dashboard'); + } else if(validations.loggedInStaff) { + browserHistory.push('/admin/panel'); } } } diff --git a/client/src/app/Routes.js b/client/src/app/Routes.js index 389bf918..0470cd27 100644 --- a/client/src/app/Routes.js +++ b/client/src/app/Routes.js @@ -1,5 +1,5 @@ import React from 'react'; -import {Router, Route, IndexRoute, browserHistory} from 'react-router'; +import {Router, Route, IndexRoute, IndexRedirect, browserHistory} from 'react-router'; import { syncHistoryWithStore } from 'react-router-redux'; import store from 'app/store'; @@ -20,8 +20,35 @@ import DashboardEditProfilePage from 'app/main/dashboard/dashboard-edit-profile/ import DashboardArticlePage from 'app/main/dashboard/dashboard-article/dashboard-article-page'; import DashboardTicketPage from 'app/main/dashboard/dashboard-ticket/dashboard-ticket-page'; +// ADMIN PANEL import AdminLoginPage from 'app/admin/admin-login-page'; -import AdminPanel from 'app/admin/panel/admin-panel'; +import AdminPanelLayout from 'app/admin/panel/admin-panel-layout'; + +import AdminPanelStats from 'app/admin/panel/dashboard/admin-panel-stats'; +import AdminPanelActivity from 'app/admin/panel/dashboard/admin-panel-activity'; +import AdminPanelMyAccount from 'app/admin/panel/dashboard/admin-panel-my-account'; + +import AdminPanelMyTickets from 'app/admin/panel/tickets/admin-panel-my-tickets'; +import AdminPanelNewTickets from 'app/admin/panel/tickets/admin-panel-new-tickets'; +import AdminPanelAllTickets from 'app/admin/panel/tickets/admin-panel-all-tickets'; +import AdminPanelViewTicket from 'app/admin/panel/tickets/admin-panel-view-ticket'; +import AdminPanelCustomResponses from 'app/admin/panel/tickets/admin-panel-custom-responses'; + +import AdminPanelListUsers from 'app/admin/panel/users/admin-panel-list-users'; +import AdminPanelViewUser from 'app/admin/panel/users/admin-panel-view-user'; +import AdminPanelBanUsers from 'app/admin/panel/users/admin-panel-ban-users'; + +import AdminPanelListArticles from 'app/admin/panel/articles/admin-panel-list-articles'; +import AdminPanelViewArticle from 'app/admin/panel/articles/admin-panel-view-article'; + +import AdminPanelStaffMembers from 'app/admin/panel/staff/admin-panel-staff-members'; +import AdminPanelDepartments from 'app/admin/panel/staff/admin-panel-departments'; +import AdminPanelViewStaff from 'app/admin/panel/staff/admin-panel-view-staff'; + +import AdminPanelSystemPreferences from 'app/admin/panel/settings/admin-panel-system-preferences'; +import AdminPanelUserSystem from 'app/admin/panel/settings/admin-panel-user-system'; +import AdminPanelEmailTemplates from 'app/admin/panel/settings/admin-panel-email-templates'; +import AdminPanelCustomFields from 'app/admin/panel/settings/admin-panel-custom-fields'; const history = syncHistoryWithStore(browserHistory, store); @@ -43,10 +70,50 @@ export default ( - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/src/app/admin/admin-login-page.js b/client/src/app/admin/admin-login-page.js index e4727f21..9d7f2770 100644 --- a/client/src/app/admin/admin-login-page.js +++ b/client/src/app/admin/admin-login-page.js @@ -1,33 +1,58 @@ import React from 'react'; +import _ from 'lodash'; +import {connect} from 'react-redux'; import i18n from 'lib-app/i18n'; -import API from 'lib-app/api-call'; +import SessionActions from 'actions/session-actions'; import Form from 'core-components/form'; import FormField from 'core-components/form-field'; import SubmitButton from 'core-components/submit-button'; +import Message from 'core-components/message'; +import Widget from 'core-components/widget'; class AdminLoginPage extends React.Component { - render(){ + render() { return (
-
+
OpenSupports Admin Panel
-
+ - - {i18n('LOG_IN')} + + {i18n('LOG_IN')}
-
+ {this.renderMessage()} +
); } + renderMessage() { + let message = null; + + if(this.props.session.failed) { + message = ( + + {i18n('EMAIL_OR_PASSWORD')} + + ); + } + + return message; + } + onSubmit(formState) { - + this.props.dispatch(SessionActions.login(_.extend({}, formState, { + staff: true + }))); } } -export default AdminLoginPage; \ No newline at end of file +export default connect((store) => { + return { + session: store.session + }; +})(AdminLoginPage); diff --git a/client/src/app/admin/admin-login-page.scss b/client/src/app/admin/admin-login-page.scss index 60a5a738..57b07631 100644 --- a/client/src/app/admin/admin-login-page.scss +++ b/client/src/app/admin/admin-login-page.scss @@ -5,11 +5,7 @@ &__content { margin: 0 auto; - display: inline-block; - background-color: white; padding: 40px; - border-radius: 4px; - text-align: center; } &__image { @@ -21,4 +17,8 @@ margin: 0 auto; display: inline-block; } + + &__error { + margin-top: 30px; + } } \ No newline at end of file diff --git a/client/src/app/admin/panel/admin-panel-layout.js b/client/src/app/admin/panel/admin-panel-layout.js new file mode 100644 index 00000000..59a5e7d9 --- /dev/null +++ b/client/src/app/admin/panel/admin-panel-layout.js @@ -0,0 +1,36 @@ +import React from 'react'; + +import MainLayout from 'app/main/main-layout'; +import AdminPanelStaffWidget from 'app/admin/panel/admin-panel-staff-widget'; +import AdminPanelMenu from 'app/admin/panel/admin-panel-menu'; + +import Widget from 'core-components/widget'; + +class AdminPanel extends React.Component { + + render() { + return ( + +
+
+
+ +
+
+ +
+
+
+
+ + {this.props.children} + +
+
+
+
+ ); + } +} + +export default AdminPanel; \ No newline at end of file diff --git a/client/src/app/admin/panel/admin-panel-layout.scss b/client/src/app/admin/panel/admin-panel-layout.scss new file mode 100644 index 00000000..42a68014 --- /dev/null +++ b/client/src/app/admin/panel/admin-panel-layout.scss @@ -0,0 +1,7 @@ +.admin-panel-layout { + padding: 0 10px; + + &__header { + margin-bottom: 20px; + } +} \ No newline at end of file diff --git a/client/src/app/admin/panel/admin-panel-menu.js b/client/src/app/admin/panel/admin-panel-menu.js new file mode 100644 index 00000000..5f77ef37 --- /dev/null +++ b/client/src/app/admin/panel/admin-panel-menu.js @@ -0,0 +1,202 @@ +import React from 'react'; +import _ from 'lodash'; + +import {dispatch} from 'app/store'; +import i18n from 'lib-app/i18n'; + +import Menu from 'core-components/menu'; + +class AdminPanelMenu extends React.Component { + static contextTypes = { + router: React.PropTypes.object + }; + + static propTypes = { + location: React.PropTypes.object + }; + + render() { + return ( +
+ + +
+ ); + } + + getGroupsMenuProps() { + return { + items: this.getGroups(), + selectedIndex: this.getGroupIndex(), + onItemClick: this.onGroupClick.bind(this), + tabbable: true, + type: 'horizontal' + }; + } + + getGroupMenuProps() { + return { + items: this.getGroupItems(), + selectedIndex: this.getGroupItemIndex(), + onItemClick: this.onGroupItemClick.bind(this), + tabbable: true, + type: 'horizontal-list' + }; + } + + getGroups() { + return this.getRoutes().map((group) => { + return { + content: group.groupName, + icon: group.icon + }; + }); + } + + getGroupItems() { + const group = this.getRoutes()[this.getGroupIndex()]; + + return group.items.map((item) => { + return { + content: item.name + }; + }); + } + + onGroupClick(index) { + this.context.router.push(this.getRoutes()[index].path); + } + + onGroupItemClick(index) { + const group = this.getRoutes()[this.getGroupIndex()]; + + this.context.router.push(group.items[index].path); + } + + getGroupItemIndex() { + const group = this.getRoutes()[this.getGroupIndex()]; + const pathname = this.props.location.pathname; + + return _.findIndex(group.items, {path: pathname}); + } + + getGroupIndex() { + const pathname = this.props.location.pathname; + const groupIndex = _.findLastIndex(this.getRoutes(), (group) => { + return _.includes(pathname, group.path); + }); + + return (groupIndex === -1) ? 0 : groupIndex; + } + + getRoutes() { + return [ + { + groupName: i18n('DASHBOARD'), + path: '/admin/panel', + icon: 'tachometer', + items: [ + { + name: i18n('TICKET_STATS'), + path: '/admin/panel/stats' + }, + { + name: i18n('LAST_ACTIVITY'), + path: '/admin/panel/activity' + } + ] + }, + { + groupName: i18n('TICKETS'), + path: '/admin/panel/tickets', + icon: 'ticket', + items: [ + { + name: i18n('MY_TICKETS'), + path: '/admin/panel/tickets/my-tickets' + }, + { + name: i18n('NEW_TICKETS'), + path: '/admin/panel/tickets/new-tickets' + }, + { + name: i18n('ALL_TICKETS'), + path: '/admin/panel/tickets/all-tickets' + }, + { + name: i18n('CUSTOM_RESPONSES'), + path: '/admin/panel/tickets/custom-responses' + } + ] + }, + { + groupName: i18n('USERS'), + path: '/admin/panel/users', + icon: 'user', + items: [ + { + name: i18n('LIST_USERS'), + path: '/admin/panel/users/list-users' + }, + { + name: i18n('BAN_USERS'), + path: '/admin/panel/users/ban-users' + } + ] + }, + { + groupName: i18n('ARTICLES'), + path: '/admin/panel/articles', + icon: 'book', + items: [ + { + name: i18n('LIST_ARTICLES'), + path: '/admin/panel/articles/list-articles' + } + ] + }, + { + + groupName: i18n('STAFF'), + path: '/admin/panel/staff', + icon: 'users', + items: [ + { + name: i18n('STAFF_MEMBERS'), + path: '/admin/panel/staff/staff-members' + }, + { + name: i18n('DEPARTMENTS'), + path: '/admin/panel/staff/departments' + } + ] + }, + { + + groupName: i18n('SETTINGS'), + path: '/admin/panel/settings', + icon: 'cogs', + items: [ + { + name: i18n('SYSTEM_PREFERENCES'), + path: '/admin/panel/settings/system-preferences' + }, + { + name: i18n('USER_SYSTEM'), + path: '/admin/panel/settings/user-system' + }, + { + name: i18n('EMAIL_TEMPLATES'), + path: '/admin/panel/settings/email-templates' + }, + { + name: i18n('FILTERS_CUSTOM_FIELDS'), + path: '/admin/panel/settings/custom-fields' + } + ] + } + ]; + } +} + +export default AdminPanelMenu; diff --git a/client/src/app/admin/panel/admin-panel-menu.scss b/client/src/app/admin/panel/admin-panel-menu.scss new file mode 100644 index 00000000..89d1a17b --- /dev/null +++ b/client/src/app/admin/panel/admin-panel-menu.scss @@ -0,0 +1,3 @@ +.admin-panel-menu { + +} \ No newline at end of file diff --git a/client/src/app/admin/panel/admin-panel-staff-widget.js b/client/src/app/admin/panel/admin-panel-staff-widget.js new file mode 100644 index 00000000..913f151d --- /dev/null +++ b/client/src/app/admin/panel/admin-panel-staff-widget.js @@ -0,0 +1,52 @@ +import React from 'react'; +import classNames from 'classnames'; +import {connect} from 'react-redux'; + +import i18n from 'lib-app/i18n'; +import Button from 'core-components/button'; +import SessionActions from 'actions/session-actions'; + +class AdminPanelStaffWidget extends React.Component { + + render() { + return ( +
+
+
{this.props.session.userName}
+
+ + | + +
+
+
+ +
+
+ ); + } + + getClass() { + let classes = { + 'admin-panel-staff-widget': true + }; + + classes[this.props.className] = (this.props.className); + + return classNames(classes); + } + + closeSession() { + this.props.dispatch(SessionActions.logout()); + } +} + +export default connect((store) => { + return { + session: store.session + }; +})(AdminPanelStaffWidget); diff --git a/client/src/app/admin/panel/admin-panel-staff-widget.scss b/client/src/app/admin/panel/admin-panel-staff-widget.scss new file mode 100644 index 00000000..1de336ad --- /dev/null +++ b/client/src/app/admin/panel/admin-panel-staff-widget.scss @@ -0,0 +1,47 @@ +@import '../../../scss/vars'; + +.admin-panel-staff-widget { + background-color: $secondary-blue; + position: relative; + width: 100%; + height: 165px; + text-align: center; + + &__profile-pic-wrapper { + position: absolute; + top: 8px; + border: 4px solid $grey; + border-radius: 50%; + width: 90px; + height: 90px; + overflow: hidden; + text-align: center; + left: 50%; + transform: translate(-50%, 0); + } + + &__profile-pic { + height: 100%; + position: absolute; + left: 50%; + transform: translate(-50%, 0); + } + + &__user-data { + position: absolute; + background-color: white; + bottom: 0; + width: 100%; + height: 110px; + padding-top: 48px; + } + + &__name { + font-size: $font-size--md; + } + + &__actions { + font-size: $font-size--xs; + margin-top: 10px; + } +} \ No newline at end of file diff --git a/client/src/app/admin/panel/admin-panel.js b/client/src/app/admin/panel/admin-panel.js deleted file mode 100644 index b0bb5113..00000000 --- a/client/src/app/admin/panel/admin-panel.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; - -class AdminPanel extends React.Component { - render(){ - return ( -
- Admin panel... -
- ); - } -} - -export default AdminPanel; \ No newline at end of file diff --git a/client/src/app/admin/panel/admin-panel.scss b/client/src/app/admin/panel/admin-panel.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/client/src/app/admin/panel/articles/admin-panel-list-articles.js b/client/src/app/admin/panel/articles/admin-panel-list-articles.js new file mode 100644 index 00000000..cb607d94 --- /dev/null +++ b/client/src/app/admin/panel/articles/admin-panel-list-articles.js @@ -0,0 +1,14 @@ +import React from 'react'; + +class AdminPanelListArticles extends React.Component { + + render() { + return ( +
+ /admin/panel/articles/list-articles +
+ ); + } +} + +export default AdminPanelListArticles; \ No newline at end of file diff --git a/client/src/app/admin/panel/articles/admin-panel-view-article.js b/client/src/app/admin/panel/articles/admin-panel-view-article.js new file mode 100644 index 00000000..fa026870 --- /dev/null +++ b/client/src/app/admin/panel/articles/admin-panel-view-article.js @@ -0,0 +1,14 @@ +import React from 'react'; + +class AdminPanelViewArticle extends React.Component { + + render() { + return ( +
+ /admin/panel/articles/view-article +
+ ); + } +} + +export default AdminPanelViewArticle; \ No newline at end of file diff --git a/client/src/app/admin/panel/dashboard/admin-panel-activity.js b/client/src/app/admin/panel/dashboard/admin-panel-activity.js new file mode 100644 index 00000000..9b0cc102 --- /dev/null +++ b/client/src/app/admin/panel/dashboard/admin-panel-activity.js @@ -0,0 +1,14 @@ +import React from 'react'; + +class AdminPanelActivity extends React.Component { + + render() { + return ( +
+ /admin/panel/activity +
+ ); + } +} + +export default AdminPanelActivity; \ No newline at end of file diff --git a/client/src/app/admin/panel/dashboard/admin-panel-my-account.js b/client/src/app/admin/panel/dashboard/admin-panel-my-account.js new file mode 100644 index 00000000..9e938cdd --- /dev/null +++ b/client/src/app/admin/panel/dashboard/admin-panel-my-account.js @@ -0,0 +1,14 @@ +import React from 'react'; + +class AdminPanelMyAccount extends React.Component { + + render() { + return ( +
+ /admin/panel/my-account +
+ ); + } +} + +export default AdminPanelMyAccount; \ No newline at end of file diff --git a/client/src/app/admin/panel/dashboard/admin-panel-stats.js b/client/src/app/admin/panel/dashboard/admin-panel-stats.js new file mode 100644 index 00000000..020264e4 --- /dev/null +++ b/client/src/app/admin/panel/dashboard/admin-panel-stats.js @@ -0,0 +1,14 @@ +import React from 'react'; + +class AdminPanelStats extends React.Component { + + render() { + return ( +
+ /admin/panel/stats +
+ ); + } +} + +export default AdminPanelStats; \ No newline at end of file diff --git a/client/src/app/admin/panel/settings/admin-panel-custom-fields.js b/client/src/app/admin/panel/settings/admin-panel-custom-fields.js new file mode 100644 index 00000000..8ffce603 --- /dev/null +++ b/client/src/app/admin/panel/settings/admin-panel-custom-fields.js @@ -0,0 +1,14 @@ +import React from 'react'; + +class AdminPanelCustomFields extends React.Component { + + render() { + return ( +
+ /admin/panel/settings/custom-fields +
+ ); + } +} + +export default AdminPanelCustomFields; \ No newline at end of file diff --git a/client/src/app/admin/panel/settings/admin-panel-email-templates.js b/client/src/app/admin/panel/settings/admin-panel-email-templates.js new file mode 100644 index 00000000..d111b0f6 --- /dev/null +++ b/client/src/app/admin/panel/settings/admin-panel-email-templates.js @@ -0,0 +1,14 @@ +import React from 'react'; + +class AdminPanelEmailTemplates extends React.Component { + + render() { + return ( +
+ /admin/panel/settings/email-templates +
+ ); + } +} + +export default AdminPanelEmailTemplates; \ No newline at end of file diff --git a/client/src/app/admin/panel/settings/admin-panel-system-preferences.js b/client/src/app/admin/panel/settings/admin-panel-system-preferences.js new file mode 100644 index 00000000..cbeafabe --- /dev/null +++ b/client/src/app/admin/panel/settings/admin-panel-system-preferences.js @@ -0,0 +1,14 @@ +import React from 'react'; + +class AdminPanelSystemPreferences extends React.Component { + + render() { + return ( +
+ /admin/panel/settings/system-preferences +
+ ); + } +} + +export default AdminPanelSystemPreferences; \ No newline at end of file diff --git a/client/src/app/admin/panel/settings/admin-panel-user-system.js b/client/src/app/admin/panel/settings/admin-panel-user-system.js new file mode 100644 index 00000000..671bd69b --- /dev/null +++ b/client/src/app/admin/panel/settings/admin-panel-user-system.js @@ -0,0 +1,14 @@ +import React from 'react'; + +class AdminPanelUserSystem extends React.Component { + + render() { + return ( +
+ /admin/panel/settings/user-system +
+ ); + } +} + +export default AdminPanelUserSystem; \ No newline at end of file diff --git a/client/src/app/admin/panel/staff/admin-panel-departments.js b/client/src/app/admin/panel/staff/admin-panel-departments.js new file mode 100644 index 00000000..768add5f --- /dev/null +++ b/client/src/app/admin/panel/staff/admin-panel-departments.js @@ -0,0 +1,14 @@ +import React from 'react'; + +class AdminPanelDepartments extends React.Component { + + render() { + return ( +
+ /admin/panel/staff/departments +
+ ); + } +} + +export default AdminPanelDepartments; \ No newline at end of file diff --git a/client/src/app/admin/panel/staff/admin-panel-staff-members.js b/client/src/app/admin/panel/staff/admin-panel-staff-members.js new file mode 100644 index 00000000..f869fc8b --- /dev/null +++ b/client/src/app/admin/panel/staff/admin-panel-staff-members.js @@ -0,0 +1,14 @@ +import React from 'react'; + +class AdminPanelStaffMembers extends React.Component { + + render() { + return ( +
+ /admin/panel/staff/staff-members +
+ ); + } +} + +export default AdminPanelStaffMembers; \ No newline at end of file diff --git a/client/src/app/admin/panel/staff/admin-panel-view-staff.js b/client/src/app/admin/panel/staff/admin-panel-view-staff.js new file mode 100644 index 00000000..23076093 --- /dev/null +++ b/client/src/app/admin/panel/staff/admin-panel-view-staff.js @@ -0,0 +1,14 @@ +import React from 'react'; + +class AdminPanelViewStaff extends React.Component { + + render() { + return ( +
+ /admin/panel/staff/view-staff +
+ ); + } +} + +export default AdminPanelViewStaff; \ No newline at end of file diff --git a/client/src/app/admin/panel/tickets/admin-panel-all-tickets.js b/client/src/app/admin/panel/tickets/admin-panel-all-tickets.js new file mode 100644 index 00000000..93a4f327 --- /dev/null +++ b/client/src/app/admin/panel/tickets/admin-panel-all-tickets.js @@ -0,0 +1,14 @@ +import React from 'react'; + +class AdminPanelAllTickets extends React.Component { + + render() { + return ( +
+ /admin/panel/tickets/all-tickets +
+ ); + } +} + +export default AdminPanelAllTickets; \ No newline at end of file diff --git a/client/src/app/admin/panel/tickets/admin-panel-custom-responses.js b/client/src/app/admin/panel/tickets/admin-panel-custom-responses.js new file mode 100644 index 00000000..a4d3c1cf --- /dev/null +++ b/client/src/app/admin/panel/tickets/admin-panel-custom-responses.js @@ -0,0 +1,14 @@ +import React from 'react'; + +class AdminPanelCustomResponses extends React.Component { + + render() { + return ( +
+ /admin/panel/tickets/custom-responses +
+ ); + } +} + +export default AdminPanelCustomResponses; \ No newline at end of file diff --git a/client/src/app/admin/panel/tickets/admin-panel-my-tickets.js b/client/src/app/admin/panel/tickets/admin-panel-my-tickets.js new file mode 100644 index 00000000..03a0ffa2 --- /dev/null +++ b/client/src/app/admin/panel/tickets/admin-panel-my-tickets.js @@ -0,0 +1,14 @@ +import React from 'react'; + +class AdminPanelMyTickets extends React.Component { + + render() { + return ( +
+ /admin/panel/tickets/my-tickets +
+ ); + } +} + +export default AdminPanelMyTickets; \ No newline at end of file diff --git a/client/src/app/admin/panel/tickets/admin-panel-new-tickets.js b/client/src/app/admin/panel/tickets/admin-panel-new-tickets.js new file mode 100644 index 00000000..42d250a8 --- /dev/null +++ b/client/src/app/admin/panel/tickets/admin-panel-new-tickets.js @@ -0,0 +1,14 @@ +import React from 'react'; + +class AdminPanelNewTickets extends React.Component { + + render() { + return ( +
+ /admin/panel/tickets/new-tickets +
+ ); + } +} + +export default AdminPanelNewTickets; \ No newline at end of file diff --git a/client/src/app/admin/panel/tickets/admin-panel-view-ticket.js b/client/src/app/admin/panel/tickets/admin-panel-view-ticket.js new file mode 100644 index 00000000..620324bb --- /dev/null +++ b/client/src/app/admin/panel/tickets/admin-panel-view-ticket.js @@ -0,0 +1,14 @@ +import React from 'react'; + +class AdminPanelViewTicket extends React.Component { + + render() { + return ( +
+ /admin/panel/tickets/view-ticket +
+ ); + } +} + +export default AdminPanelViewTicket; \ No newline at end of file diff --git a/client/src/app/admin/panel/users/admin-panel-ban-users.js b/client/src/app/admin/panel/users/admin-panel-ban-users.js new file mode 100644 index 00000000..acb259e4 --- /dev/null +++ b/client/src/app/admin/panel/users/admin-panel-ban-users.js @@ -0,0 +1,14 @@ +import React from 'react'; + +class AdminPanelBanUsers extends React.Component { + + render() { + return ( +
+ /admin/panel/users/ban-users +
+ ); + } +} + +export default AdminPanelBanUsers; \ No newline at end of file diff --git a/client/src/app/admin/panel/users/admin-panel-list-users.js b/client/src/app/admin/panel/users/admin-panel-list-users.js new file mode 100644 index 00000000..8661164e --- /dev/null +++ b/client/src/app/admin/panel/users/admin-panel-list-users.js @@ -0,0 +1,14 @@ +import React from 'react'; + +class AdminPanelListUsers extends React.Component { + + render() { + return ( +
+ /admin/panel/users/list-users +
+ ); + } +} + +export default AdminPanelListUsers; \ No newline at end of file diff --git a/client/src/app/admin/panel/users/admin-panel-view-user.js b/client/src/app/admin/panel/users/admin-panel-view-user.js new file mode 100644 index 00000000..57b828e6 --- /dev/null +++ b/client/src/app/admin/panel/users/admin-panel-view-user.js @@ -0,0 +1,14 @@ +import React from 'react'; + +class AdminPanelViewUser extends React.Component { + + render() { + return ( +
+ /admin/panel/users/view-user +
+ ); + } +} + +export default AdminPanelViewUser; \ No newline at end of file diff --git a/client/src/app/demo/components-demo-page.js b/client/src/app/demo/components-demo-page.js index 592821ef..f3c474f3 100644 --- a/client/src/app/demo/components-demo-page.js +++ b/client/src/app/demo/components-demo-page.js @@ -65,12 +65,6 @@ let DemoPage = React.createClass({ ) }, - { - title: 'DropDown', - render: ( - - ) - }, { title: 'Primary Menu', render: ( @@ -83,6 +77,30 @@ let DemoPage = React.createClass({ ) }, + { + title: 'Navigation Menu', + render: ( + + ) + }, + { + title: 'Horizontal Menu', + render: ( + + ) + }, + { + title: 'HorizontalList Menu', + render: ( + + ) + }, + { + title: 'DropDown', + render: ( + + ) + }, { title: 'Tooltip', render: ( diff --git a/client/src/app/main/dashboard/dashboard-edit-profile/dashboard-edit-profile-page.js b/client/src/app/main/dashboard/dashboard-edit-profile/dashboard-edit-profile-page.js index 4e32405f..fed82146 100644 --- a/client/src/app/main/dashboard/dashboard-edit-profile/dashboard-edit-profile-page.js +++ b/client/src/app/main/dashboard/dashboard-edit-profile/dashboard-edit-profile-page.js @@ -20,7 +20,6 @@ class DashboardEditProfilePage extends React.Component { messagePass:'' }; - render() { return (
diff --git a/client/src/app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page.js b/client/src/app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page.js index 042f00b4..3ed24c8b 100644 --- a/client/src/app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page.js +++ b/client/src/app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page.js @@ -4,9 +4,7 @@ import {connect} from 'react-redux'; import i18n from 'lib-app/i18n'; import Header from 'core-components/header'; -import Table from 'core-components/table'; -import Button from 'core-components/button'; -import Tooltip from 'core-components/tooltip'; +import TicketList from 'app-components/ticket-list'; import TicketInfo from 'app-components/ticket-info'; class DashboardListTicketsPage extends React.Component { @@ -22,59 +20,10 @@ class DashboardListTicketsPage extends React.Component { return (
-
+ ); } - - getTableHeaders() { - return [ - { - key: 'number', - value: 'Number', - className: 'dashboard-ticket-list__number col-md-1' - }, - { - key: 'title', - value: 'Title', - className: 'dashboard-ticket-list__title col-md-6' - }, - { - key: 'department', - value: 'Department', - className: 'dashboard-ticket-list__department col-md-3' - }, - { - key: 'date', - value: 'Date', - className: 'dashboard-ticket-list__date col-md-2' - } - ]; - } - - getTableRows() { - return this.props.tickets.map(this.gerTicketTableObject.bind(this)); - } - - gerTicketTableObject(ticket) { - let titleText = (ticket.unread) ? ticket.title + ' (1)' : ticket.title; - - return { - number: ( - } > - {'#' + ticket.ticketNumber} - - ), - title: ( - - ), - department: ticket.department.name, - date: ticket.date, - highlighted: ticket.unread - }; - } } diff --git a/client/src/app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page.scss b/client/src/app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page.scss deleted file mode 100644 index 3c9439e1..00000000 --- a/client/src/app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page.scss +++ /dev/null @@ -1,26 +0,0 @@ -@import "../../../../scss/vars"; - -.dashboard-ticket-list { - - &__number { - text-align: left; - } - - &__title { - text-align: left; - } - - &__department { - text-align: right; - } - - &__date { - text-align: right; - } - - &__title-link:hover, - &__title-link:focus { - outline: none; - text-decoration: underline; - } -} \ No newline at end of file diff --git a/client/src/core-components/menu.js b/client/src/core-components/menu.js index 18158405..3a7d849f 100644 --- a/client/src/core-components/menu.js +++ b/client/src/core-components/menu.js @@ -84,7 +84,9 @@ class Menu extends React.Component { let classes = { 'menu': true, 'menu_secondary': (this.props.type === 'secondary'), - 'menu_navigation': (this.props.type === 'navigation') + 'menu_navigation': (this.props.type === 'navigation'), + 'menu_horizontal': (this.props.type === 'horizontal'), + 'menu_horizontal-list': (this.props.type === 'horizontal-list') }; classes[this.props.className] = true; diff --git a/client/src/core-components/menu.scss b/client/src/core-components/menu.scss index ab2c661b..3a425f87 100644 --- a/client/src/core-components/menu.scss +++ b/client/src/core-components/menu.scss @@ -1,5 +1,7 @@ @import "../scss/vars"; +$transition: background-color 0.3s ease, color 0.3s ease; + .menu { &__list { @@ -13,7 +15,7 @@ &__list-item { padding: 8px; - transition: background-color 0.3s ease, color 0.3s ease; + transition: $transition; &_selected, &:hover { @@ -78,4 +80,93 @@ color: $primary-blue; } } + + &_horizontal { + text-align: center; + + .menu__list { + background-color: transparent; + font-size: $font-size--md; + } + + .menu__icon { + display: block; + margin-right: 0; + margin-bottom: 20px; + margin-top: 20px; + font-size: $font-size--xl; + } + + .menu__list-item { + background-color: white; + color: $primary-black; + cursor: pointer; + display: inline-block; + height: 120px; + width: 16.6667%; + + @media (max-width: 667px) { + width: 33.3333%; + } + } + + .menu__list-item_selected { + background-color: $secondary-blue; + color: white; + } + + .menu__list-item:hover, + .menu__list-item:focus { + color: $dark-grey; + outline: none; + } + + .menu__list-item_selected:focus, + .menu__list-item_selected:hover { + color: white; + } + } + + &_horizontal-list { + text-align: left; + background-color: $secondary-blue; + min-height: 45px; + + .menu__list { + background-color: transparent; + font-size: $font-size--sm; + } + + .menu__list-item { + transition: none; + background-color: transparent; + color: white; + cursor: pointer; + display: inline-block; + margin-top: 11px; + margin-bottom: 10px; + margin-left: 20px; + padding: 2px 13px; + } + + .menu__list-item:hover { + transition: $transition; + background-color: transparent; + color: $primary-black; + } + + .menu__list-item:focus { + color: $grey; + outline: none; + } + + .menu__list-item_selected, + .menu__list-item_selected:hover { + transition: $transition; + padding: 2px 13px; + border-radius: 30px; + background-color: $primary-blue; + color: white; + } + } } \ No newline at end of file diff --git a/client/src/data/fixtures/staff-fixtures.js b/client/src/data/fixtures/staff-fixtures.js new file mode 100644 index 00000000..ea6afbcd --- /dev/null +++ b/client/src/data/fixtures/staff-fixtures.js @@ -0,0 +1,23 @@ +module.exports = [ + { + path: '/staff/get', + time: 100, + response: function () { + return { + status: 'success', + data: { + name: 'Emilia Clarke', + email: 'staff@opensupports.com', + profilePic: 'http://i65.tinypic.com/9bep95.jpg', + level: 1, + staff: true, + departments: [ + {id: 1, name: 'Sales Support'}, + {id: 2, name: 'Technical Issues'}, + {id: 3, name: 'System and Administration'} + ] + } + }; + } + } +]; \ No newline at end of file diff --git a/client/src/data/fixtures/user-fixtures.js b/client/src/data/fixtures/user-fixtures.js index 833678fa..529789bc 100644 --- a/client/src/data/fixtures/user-fixtures.js +++ b/client/src/data/fixtures/user-fixtures.js @@ -9,6 +9,7 @@ module.exports = [ response = { status: 'success', data: { + 'staff': data.staff, 'userId': 12, 'token': 'cc6b4921e6733d6aafe284ec0d7be57e', 'rememberToken': (data.remember) ? 'aa41efe0a1b3eeb9bf303e4561ff8392' : null, @@ -147,6 +148,7 @@ module.exports = [ language: 'en', unread: true, closed: false, + priority: 'low', author: { id: 12, name: 'Haskell Curry', @@ -195,6 +197,47 @@ module.exports = [ language: 'en', unread: false, closed: false, + priority: 'medium', + author: { + name: 'Haskell Curry', + email: 'haskell@lambda.com' + }, + owner: { + name: 'Steve Jobs' + }, + comments: [ + { + content: 'Do you have apache installed? It generally happens if you dont have apache.', + author: { + name: 'Steve Jobs', + email: 'jobs@steve.com', + staff: true + } + }, + { + content: 'I have already installed apache, but the problem persists', + author: { + name: 'Haskell Curry', + steve: 'haskell@lambda.com', + staff: false + } + } + ] + }, + { + ticketNumber: '878552', + title: 'Lorem ipsum door', + content: 'I had a problem with the installation of the php server', + department: { + id: 2, + name: 'Environment Setup' + }, + date: '15 Apr 2016', + file: 'http://www.opensupports.com/some_file.zip', + language: 'en', + unread: false, + closed: false, + priority: 'high', author: { name: 'Haskell Curry', email: 'haskell@lambda.com' diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js index 0eea85dc..d76fc07d 100644 --- a/client/src/data/languages/en.js +++ b/client/src/data/languages/en.js @@ -36,8 +36,34 @@ export default { 'CUSTOMER': 'Customer', 'YES': 'Yes', 'CANCEL': 'Cancel', + 'MY_ACCOUNT': 'My Account', + 'DASHBOARD': 'Dashboard', + 'USERS': 'Users', + 'SETTINGS': 'Settings', + 'TICKET_STATS': 'Ticket Stats', + 'LAST_ACTIVITY': 'Last Activity', + 'MY_TICKETS': 'My Tickets', + 'NEW_TICKETS': 'New Tickets', + 'ALL_TICKETS': 'All Tickets', + 'CUSTOM_RESPONSES': 'Custom Responses', + 'LIST_USERS': 'List Users', + 'BAN_USERS': 'Ban Users', + 'LIST_ARTICLES': 'List Articles', + 'STAFF_MEMBERS': 'Staff Members', + 'DEPARTMENTS': 'Departments', + 'SYSTEM_PREFERENCES': 'System Preferences', + 'USER_SYSTEM': 'User System', + 'EMAIL_TEMPLATES': 'Email Templates', + 'FILTERS_CUSTOM_FIELDS': 'Filters and Custom Fields', + 'PRIORITY': 'Priority', + 'NUMBER': 'Number', + 'HIGH': 'High', + 'MEDIUM': 'Medium', + 'LOW': 'Low', + 'TITLE': 'Title', //ERRORS + 'EMAIL_OR_PASSWORD': 'Email or password invalid', 'EMAIL_NOT_EXIST': 'Email does not exist', 'ERROR_EMPTY': 'Invalid value', 'ERROR_PASSWORD': 'Invalid password', diff --git a/client/src/lib-app/fixtures-loader.js b/client/src/lib-app/fixtures-loader.js index fcf645a2..896ff338 100644 --- a/client/src/lib-app/fixtures-loader.js +++ b/client/src/lib-app/fixtures-loader.js @@ -17,6 +17,7 @@ let fixtures = (function () { // FIXTURES fixtures.add(require('data/fixtures/user-fixtures')); +fixtures.add(require('data/fixtures/staff-fixtures')); fixtures.add(require('data/fixtures/ticket-fixtures')); fixtures.add(require('data/fixtures/system-fixtures')); diff --git a/client/src/reducers/session-reducer.js b/client/src/reducers/session-reducer.js index 877bb590..92251dce 100644 --- a/client/src/reducers/session-reducer.js +++ b/client/src/reducers/session-reducer.js @@ -41,7 +41,8 @@ class SessionReducer extends Reducer { return _.extend({}, state, { logged: true, pending: false, - failed: false + failed: false, + staff: payload.data.staff }); } @@ -101,8 +102,12 @@ class SessionReducer extends Reducer { sessionStore.storeUserData(payload.data); return _.extend({}, state, { - userName: userData.name, - userEmail: userData.email, + staff: userData.staff, + userName: userData.name, + userEmail: userData.email, + userProfilePic: userData.profilePic, + userLevel: userData.level, + userDepartments: userData.departments, userTickets: userData.tickets }); } @@ -113,8 +118,12 @@ class SessionReducer extends Reducer { return _.extend({}, state, { initDone: true, logged: true, + staff: userData.staff, userName: userData.name, userEmail: userData.email, + userProfilePic: userData.profilePic, + userLevel: userData.level, + userDepartments: userData.departments, userTickets: userData.tickets }); } diff --git a/server/controllers/staff.php b/server/controllers/staff.php new file mode 100644 index 00000000..21df1a90 --- /dev/null +++ b/server/controllers/staff.php @@ -0,0 +1,9 @@ +setGroupPath('/staff'); + +$systemControllerGroup->addController(new GetStaffController); + +$systemControllerGroup->finalize(); \ No newline at end of file diff --git a/server/controllers/staff/get.php b/server/controllers/staff/get.php new file mode 100644 index 00000000..db922c80 --- /dev/null +++ b/server/controllers/staff/get.php @@ -0,0 +1,36 @@ + 'staff_1', + 'requestData' => [] + ]; + } + + public function handler() { + $user = Controller::getLoggedUser(); + $parsedDepartmentList = []; + $departmentList = $user->sharedDepartmentList; + + foreach($departmentList as $department) { + $parsedDepartmentList[] = [ + 'id' => $department->id, + 'name' => $department->name + ]; + } + + Response::respondSuccess([ + 'name' => $user->name, + 'email' => $user->email, + 'profilePic' => $user->profilePic, + 'level' => $user->level, + 'staff' => true, + 'departments' => $parsedDepartmentList + ]); + } +} \ No newline at end of file diff --git a/server/controllers/system/init-settings.php b/server/controllers/system/init-settings.php index f03960c8..e4d81831 100644 --- a/server/controllers/system/init-settings.php +++ b/server/controllers/system/init-settings.php @@ -15,6 +15,7 @@ class InitSettingsController extends Controller { $this->storeGlobalSettings(); $this->storeMailTemplates(); $this->storeMockedDepartments(); + $this->createMockedStaff(); Response::respondSuccess(); } else { @@ -80,4 +81,18 @@ class InitSettingsController extends Controller { $department->store(); } } + + private function createMockedStaff() { + $staff = new Staff(); + $staff->setProperties([ + 'name' => 'Emilia Clarke', + 'email' => 'staff@opensupports.com', + 'password' => Hashing::hashPassword('staff'), + 'profilePic' => 'http://i65.tinypic.com/9bep95.jpg', + 'level' => 1, + 'sharedDepartmentList' => Department::getAllDepartments(), + 'sharedTicketList' => [] + ]); + $staff->store(); + } } \ No newline at end of file diff --git a/server/controllers/ticket/comment.php b/server/controllers/ticket/comment.php index bc3e4a91..83fb5513 100644 --- a/server/controllers/ticket/comment.php +++ b/server/controllers/ticket/comment.php @@ -44,14 +44,19 @@ class CommentController extends Controller { } private function storeComment() { - $comment = new Comment(); + $comment = Ticketevent::getEvent(Ticketevent::COMMENT); $comment->setProperties(array( 'content' => $this->content, - 'author' => Controller::getLoggedUser(), 'date' => Date::getCurrentDate() )); - $this->ticket->ownCommentList->add($comment); + if(Controller::isStaffLogged()) { + $comment->authorUser = Controller::getLoggedUser(); + } else { + $comment->authorUser = Controller::getLoggedUser(); + } + + $this->ticket->addEvent($comment); $this->ticket->store(); } } \ No newline at end of file diff --git a/server/controllers/user/get.php b/server/controllers/user/get.php index 9310c5cf..23aa331e 100644 --- a/server/controllers/user/get.php +++ b/server/controllers/user/get.php @@ -13,6 +13,11 @@ class GetUserController extends Controller { } public function handler() { + if (Controller::isStaffLogged()) { + Response::respondError(ERRORS::INVALID_CREDENTIALS); + return; + } + $user = Controller::getLoggedUser(); $parsedTicketList = []; $ticketList = $user->sharedTicketList; diff --git a/server/controllers/user/login.php b/server/controllers/user/login.php index b32e757e..07cb3340 100644 --- a/server/controllers/user/login.php +++ b/server/controllers/user/login.php @@ -46,7 +46,7 @@ class LoginController extends Controller { } private function createUserSession() { - Session::getInstance()->createSession($this->userInstance->id); + Session::getInstance()->createSession($this->userInstance->id, Controller::request('staff')); } private function getUserData() { @@ -55,6 +55,7 @@ class LoginController extends Controller { return array( 'userId' => $userInstance->id, 'userEmail' => $userInstance->email, + 'staff' => Controller::request('staff'), 'token' => Session::getInstance()->getToken(), 'rememberToken' => $this->rememberToken ); @@ -64,7 +65,11 @@ class LoginController extends Controller { $email = Controller::request('email'); $password = Controller::request('password'); - return User::authenticate($email, $password); + if(Controller::request('staff')) { + return Staff::authenticate($email, $password); + } else { + return User::authenticate($email, $password); + } } private function getUserByRememberToken() { diff --git a/server/data/ERRORS.php b/server/data/ERRORS.php index 2e627cb9..d173ccfa 100644 --- a/server/data/ERRORS.php +++ b/server/data/ERRORS.php @@ -15,4 +15,5 @@ class ERRORS { const INIT_SETTINGS_DONE = 'Settings already initialized'; const INVALID_OLD_PASSWORD = 'Invalid old password'; const INVALID_CAPTCHA = 'Invalid captcha'; + const INVALID_TICKET_EVENT = 'INVALID_TICKET_EVENT'; } diff --git a/server/libs/Controller.php b/server/libs/Controller.php index 73a642b2..8403f7c5 100644 --- a/server/libs/Controller.php +++ b/server/libs/Controller.php @@ -36,7 +36,13 @@ abstract class Controller { } public static function getLoggedUser() { - return User::getUser((int)self::request('csrf_userid')); + $session = Session::getInstance(); + + if ($session->isStaffLogged()) { + return Staff::getUser((int)self::request('csrf_userid')); + } else { + return User::getUser((int)self::request('csrf_userid')); + } } public static function isUserLogged() { @@ -48,12 +54,8 @@ abstract class Controller { )); } - public static function isStaffLogged() { - return Controller::isUserLogged() && (Controller::getLoggedUser()->admin === 1); - } - - public static function isAdminLogged() { - return Controller::isUserLogged() && (Controller::getLoggedUser()->admin === 2); + public static function isStaffLogged($level = 1) { + return Controller::isUserLogged() && (Controller::getLoggedUser()->level >= $level); } public static function getAppInstance() { diff --git a/server/libs/Validator.php b/server/libs/Validator.php index 7bf85acc..a3c19a98 100644 --- a/server/libs/Validator.php +++ b/server/libs/Validator.php @@ -16,8 +16,9 @@ class Validator { $permissions = [ 'any' => true, 'user' => Controller::isUserLogged(), - 'staff' => Controller::isStaffLogged(), - 'admin' => Controller::isAdminLogged() + 'staff_1' => Controller::isStaffLogged(1), + 'staff_2' => Controller::isStaffLogged(2), + 'staff_3' => Controller::isStaffLogged(3) ]; if (!$permissions[$permission]) { diff --git a/server/models/Comment.php b/server/models/Comment.php deleted file mode 100644 index 1ff8ae86..00000000 --- a/server/models/Comment.php +++ /dev/null @@ -1,14 +0,0 @@ -add(Department::getDataStore($departmentIndex)); + } + + return $departmentList; + } } \ No newline at end of file diff --git a/server/models/Session.php b/server/models/Session.php index 9bb54dd6..9535876a 100644 --- a/server/models/Session.php +++ b/server/models/Session.php @@ -24,8 +24,9 @@ class Session { return self::$instance; } - public function createSession($userId) { + public function createSession($userId, $staff = false) { $this->store('userId', $userId); + $this->store('staff', $staff); $this->store('token', Hashing::generateRandomToken()); } @@ -37,6 +38,10 @@ class Session { return !!$this->getToken(); } + public function isStaffLogged() { + return $this->getStoredData('staff'); + } + public function checkAuthentication($data) { $userId = $this->getStoredData('userId'); $token = $this->getStoredData('token'); diff --git a/server/models/Staff.php b/server/models/Staff.php new file mode 100644 index 00000000..03e3e078 --- /dev/null +++ b/server/models/Staff.php @@ -0,0 +1,33 @@ +password)) ? $user : new NullDataStore(); + } + + public static function getProps() { + return [ + 'name', + 'email', + 'password', + 'profilePic', + 'level', + 'sharedDepartmentList', + 'sharedTicketList' + ]; + } + + public function getDefaultProps() { + return [ + 'level' => 1 + ]; + } + + public static function getUser($value, $property = 'id') { + return parent::getDataStore($value, $property); + } +} diff --git a/server/models/Ticket.php b/server/models/Ticket.php index 8de49c28..03e22fc7 100644 --- a/server/models/Ticket.php +++ b/server/models/Ticket.php @@ -15,9 +15,10 @@ class Ticket extends DataStore { 'date', 'unread', 'closed', + 'priority', 'author', 'owner', - 'ownCommentList' + 'ownTicketeventList' ); } @@ -32,6 +33,7 @@ class Ticket extends DataStore { public function getDefaultProps() { return array( 'owner' => null, + 'priority' => 'low', 'ticketNumber' => $this->generateUniqueTicketNumber() ); } @@ -39,6 +41,7 @@ class Ticket extends DataStore { public function store() { parent::store(); } + public function generateUniqueTicketNumber() { $ticketQuantity = Ticket::count('ticket'); $minValue = 100000; @@ -57,8 +60,6 @@ class Ticket extends DataStore { } public function toArray() { - $author = $this->author; - return [ 'ticketNumber' => $this->ticketNumber, 'title' => $this->title, @@ -72,9 +73,10 @@ class Ticket extends DataStore { 'language' => $this->language, 'unread' => !!$this->unread, 'closed' => !!$this->closed, + 'priority' => $this->priority, 'author' => $this->authorToArray(), 'owner' => $this->ownerToArray(), - 'comments' => $this->commentsToArray() + 'events' => $this->eventsToArray() ]; } @@ -106,23 +108,35 @@ class Ticket extends DataStore { } } - public function commentsToArray() { - $comments = []; + public function eventsToArray() { + $events = []; - foreach ($this->ownCommentList as $comment) { - $comments[] = [ - 'content'=> $comment->content, - 'author' => [ - 'id'=> 15, - 'name' => $comment->author->name, - 'email' => $comment->author->email, - 'staff' => $comment->author->staff - ], - 'date'=> $comment->date, - 'file'=> $comment->file + foreach ($this->ownTicketeventList as $ticketEvent) { + $event = [ + 'type' => $ticketEvent->type, + 'content'=> $ticketEvent->content, + 'author' => [], + 'date'=> $ticketEvent->date, + 'file'=> $ticketEvent->file ]; + + $author = $ticketEvent->getAuthor(); + if(!$author->isNull()) { + $event['author'] = [ + 'id'=> $author->id, + 'name' => $author->name, + 'email' =>$author->email, + 'staff' => $author instanceof Staff + ]; + } + + $events[] = $event; } - return $comments; + return $events; + } + + public function addEvent(Ticketevent $event) { + $this->ownTicketeventList->add($event); } } diff --git a/server/models/Ticketevent.php b/server/models/Ticketevent.php new file mode 100644 index 00000000..81bab0c0 --- /dev/null +++ b/server/models/Ticketevent.php @@ -0,0 +1,60 @@ +setProperties([ + 'type' => $type + ]); + + return $ticketEvent; + } + + public function getProps() { + return [ + 'type', + 'content', + 'file', + 'authorUser', + 'authorStaff', + 'date' + ]; + } + + public function getAuthor() { + if($this->authorUser) { + return $this->authorUser; + } + + if($this->authorStaff) { + return $this->authorStaff; + } + + return new NullDataStore(); + } +} \ No newline at end of file diff --git a/server/models/User.php b/server/models/User.php index eefbc4c5..b25f5a1a 100644 --- a/server/models/User.php +++ b/server/models/User.php @@ -15,16 +15,13 @@ class User extends DataStore { 'email', 'password', 'name', - 'admin', 'sharedTicketList', 'verificationToken', ]; } public function getDefaultProps() { - return [ - 'admin' => 0 - ]; + return []; } public static function getUser($value, $property = 'id') { diff --git a/server/tests/controllers/user/loginTest.php b/server/tests/controllers/user/loginTest.php index 0f78bc03..2a1583f7 100644 --- a/server/tests/controllers/user/loginTest.php +++ b/server/tests/controllers/user/loginTest.php @@ -35,10 +35,11 @@ class LoginControllerTest extends PHPUnit_Framework_TestCase { $this->loginController->handler(); - $this->assertTrue(Session::getInstance()->createSession->hasBeenCalledWithArgs('MOCK_ID')); + $this->assertTrue(Session::getInstance()->createSession->hasBeenCalledWithArgs('MOCK_ID', null)); $this->assertTrue(Response::get('respondSuccess')->hasBeenCalledWithArgs(array( 'userId' => 'MOCK_ID', 'userEmail' => 'MOCK_EMAIL', + 'staff' => null, 'token' => 'TEST_TOKEN', 'rememberToken' => null ))); diff --git a/tests/init.rb b/tests/init.rb index fb875785..db0b6d11 100644 --- a/tests/init.rb +++ b/tests/init.rb @@ -21,3 +21,4 @@ require './user/get.rb' require './ticket/create.rb' require './ticket/comment.rb' require './ticket/get.rb' +require './staff/get.rb' diff --git a/tests/libs.rb b/tests/libs.rb index 907ef709..b8431702 100644 --- a/tests/libs.rb +++ b/tests/libs.rb @@ -26,3 +26,8 @@ class Database end $database = Database.new + +$staff = { + :email => 'staff@opensupports.com', + :password => 'staff' +} diff --git a/tests/scripts.rb b/tests/scripts.rb index f6b3e18d..6b91569e 100644 --- a/tests/scripts.rb +++ b/tests/scripts.rb @@ -11,11 +11,12 @@ class Scripts end end - def self.login(email = 'steve@jobs.com', password = 'custompassword') + def self.login(email = 'steve@jobs.com', password = 'custompassword', staff = false) request('/user/logout') response = request('/user/login', { :email => email, - :password => password + :password => password, + :staff => staff }) if response['data'].any? diff --git a/tests/staff/get.rb b/tests/staff/get.rb new file mode 100644 index 00000000..a0abd11b --- /dev/null +++ b/tests/staff/get.rb @@ -0,0 +1,15 @@ +describe '/staff/get/' do + request('/user/logout') + Scripts.login($staff[:email], $staff[:password], true) + + it 'should return staff member data' do + result = request('/staff/get', { + csrf_userid: $csrf_userid, + csrf_token: $csrf_token + }) + + (result['status']).should.equal('success') + (result['data']['name']).should.equal('Emilia Clarke') + (result['data']['staff']).should.equal(true) + end +end \ No newline at end of file diff --git a/tests/ticket/comment.rb b/tests/ticket/comment.rb index 3d21d6e6..422c2244 100644 --- a/tests/ticket/comment.rb +++ b/tests/ticket/comment.rb @@ -68,9 +68,10 @@ describe '/ticket/comment/' do (result['status']).should.equal('success') ticket = $database.getRow('ticket', @ticketNumber, 'ticket_number') - comment = $database.getRow('comment', ticket['id'], 'ticket_id') + comment = $database.getRow('ticketevent', ticket['id'], 'ticket_id') (comment['content']).should.equal('some comment content') - (comment['author_id']).should.equal($csrf_userid) + (comment['type']).should.equal('COMMENT') + (comment['author_user_id']).should.equal($csrf_userid) end it 'should fail if user is not the author nor owner' do diff --git a/tests/ticket/create.rb b/tests/ticket/create.rb index f5367232..9bde8203 100644 --- a/tests/ticket/create.rb +++ b/tests/ticket/create.rb @@ -100,6 +100,7 @@ describe '/ticket/create' do (ticket['content']).should.equal('The north remembers') (ticket['unread']).should.equal('0') (ticket['closed']).should.equal('0') + (ticket['priority']).should.equal('low') (ticket['department_id']).should.equal('1') (ticket['author_id']).should.equal($csrf_userid) (ticket['ticket_number'].size).should.equal(6) diff --git a/tests/ticket/get.rb b/tests/ticket/get.rb index 8cb77ad4..698192f7 100644 --- a/tests/ticket/get.rb +++ b/tests/ticket/get.rb @@ -15,6 +15,13 @@ describe '/ticket/get/' do csrf_token: $csrf_token }) @ticketNumber = result['data']['ticketNumber'] + + request('/ticket/comment', { + ticketNumber: @ticketNumber, + content: 'some valid comment made', + csrf_userid: $csrf_userid, + csrf_token: $csrf_token + }) end it 'should fail if ticketNumber is invalid' do @@ -46,6 +53,7 @@ describe '/ticket/get/' do result = Scripts.login('cersei@os4.com', 'cersei') $csrf_userid = result['userId'] $csrf_token = result['token'] + result = request('/ticket/get', { ticketNumber: @ticketNumber, csrf_userid: $csrf_userid, @@ -68,6 +76,8 @@ describe '/ticket/get/' do (result['data']['author']['name']).should.equal('Cersei Lannister') (result['data']['author']['email']).should.equal('cersei@os4.com') (result['data']['owner']).should.equal([]) - (result['data']['comments']).should.equal([]) + (result['data']['events'].size).should.equal(1) + (result['data']['events'][0]['type']).should.equal('COMMENT') + (result['data']['events'][0]['content']).should.equal('some valid comment made') end end \ No newline at end of file diff --git a/tests/user/get.rb b/tests/user/get.rb index 543404a8..bf142ffe 100644 --- a/tests/user/get.rb +++ b/tests/user/get.rb @@ -52,6 +52,6 @@ describe '/user/get' do (ticketFromUser['author']['name']).should.equal('User Get') (ticketFromUser['author']['email']).should.equal('user_get@os4.com') (ticketFromUser['owner']).should.equal([]) - (ticketFromUser['comments']).should.equal([]) + (ticketFromUser['events']).should.equal([]) end end \ No newline at end of file diff --git a/tests/user/login.rb b/tests/user/login.rb index 7c0d5738..870cbc1f 100644 --- a/tests/user/login.rb +++ b/tests/user/login.rb @@ -13,13 +13,36 @@ describe '/user/login' do (result['status']).should.equal('fail') end -# it 'should login correctly' do + it 'should login correctly' do + result = request('/user/login', { + email: @loginEmail, + password: @loginPass + }) -# end + (result['status']).should.equal('success') + end -# it 'should fail if already logged in' do + it 'should fail if already logged in' do + result = request('/user/login', { + email: @loginEmail, + password: @loginPass + }) -# end + (result['status']).should.equal('fail') + (result['message']).should.equal('User is already logged in') + end + + it 'should login staff member' do + request('/user/logout', {}) + result = request('/user/login', { + email: $staff[:email], + password: $staff[:password], + staff: true + }) + + (result['status']).should.equal('success') + (result['data']['staff']).should.equal('true') + end it 'should return remember token' do request('/user/logout', {}) @@ -31,7 +54,7 @@ describe '/user/login' do (result['status']).should.equal('success') - @rememberToken = result['data']['rememberToken']# falta comproversion + @rememberToken = result['data']['rememberToken'] @userid = result['data']['userId'] end diff --git a/tests/user/signup.rb b/tests/user/signup.rb index f1309ddc..bebf131d 100644 --- a/tests/user/signup.rb +++ b/tests/user/signup.rb @@ -10,7 +10,6 @@ describe '/user/signup' do (userRow['email']).should.equal('steve@jobs.com') (userRow['name']).should.equal('Steve Jobs') - (userRow['admin']).should.equal('0') end it 'should fail if name is invalid' do