This commit is contained in:
Guillermo 2018-11-15 20:51:44 -03:00
parent 6e8c3279da
commit e9dfbfb97b
22 changed files with 195 additions and 65 deletions

View File

@ -0,0 +1,31 @@
import React from 'react';
import {connect} from 'react-redux';
import classNames from 'classnames';
import DropDown from 'core-components/drop-down';
import Icon from 'core-components/icon';
class DepartmentDropdown extends React.Component {
static propTypes = {
value: React.PropTypes.number,
onChange: React.PropTypes.func,
departments: React.PropTypes.array
}
render(){
return <DropDown {...this.props} items={this.getDepartments()} />
}
getDepartments(){
let departments = this.props.departments.map((department) => {
if(department.private*1) {
return {content: <span>{department.name} <Icon name='user-secret'/></span>};
}else{
return {content: department.name};
}
});
return departments;
}
}
export default DepartmentDropdown;

View File

@ -5,10 +5,11 @@ import i18n from 'lib-app/i18n';
import DateTransformer from 'lib-core/date-transformer';
import TicketInfo from 'app-components/ticket-info';
import DepartmentDropdown from 'app-components/department-dropdown';
import Table from 'core-components/table';
import Button from 'core-components/button';
import Tooltip from 'core-components/tooltip';
import DropDown from 'core-components/drop-down';
import Icon from 'core-components/icon';
class TicketList extends React.Component {
static propTypes = {
@ -49,14 +50,14 @@ class TicketList extends React.Component {
renderDepartmentsDropDown() {
return (
<div className="ticket-list__department-selector">
<DropDown {...this.getDepartmentDropdownProps()} />
<DepartmentDropdown {...this.getDepartmentDropdownProps()} />
</div>
);
}
getDepartmentDropdownProps() {
return {
items: this.getDepartments(),
departments: this.getDepartments(),
onChange: (event) => {
this.setState({
selectedDepartment: event.index && this.props.departments[event.index - 1].id
@ -80,12 +81,10 @@ class TicketList extends React.Component {
}
getDepartments() {
let departments = this.props.departments.map((department) => {
return {content: department.name};
});
let departments = _.clone(this.props.departments);
departments.unshift({
content: i18n('ALL_DEPARTMENTS')
name: i18n('ALL_DEPARTMENTS')
});
return departments;

View File

@ -20,6 +20,7 @@ import Message from 'core-components/message';
import Icon from 'core-components/icon';
import TextEditor from 'core-components/text-editor';
import InfoTooltip from 'core-components/info-tooltip';
import DepartmentDropdown from 'app-components/department-dropdown';
class TicketViewer extends React.Component {
static propTypes = {
@ -86,6 +87,7 @@ class TicketViewer extends React.Component {
renderEditableHeaders() {
const ticket = this.props.ticket;
const departments = SessionStore.getDepartments();
const priorities = {
'low': 0,
'medium': 1,
@ -106,8 +108,8 @@ class TicketViewer extends React.Component {
</div>
<div className="ticket-viewer__info-row-values row">
<div className="col-md-4">
<DropDown className="ticket-viewer__editable-dropdown"
items={departments.map((department) => {return {content: department.name}})}
<DepartmentDropdown className="ticket-viewer__editable-dropdown"
departments={departments}
selectedIndex={_.findIndex(departments, {id: this.props.ticket.department.id})}
onChange={this.onDepartmentDropdownChanged.bind(this)} />
</div>
@ -299,6 +301,16 @@ class TicketViewer extends React.Component {
}
};
}
getPublicDepartments() {
var publicdepartments = Session.Store.getDepartments().map((department) => {
if(department.private*1){
null;
}else {
department.name;
}
});
return publicdepartments;
}
onDepartmentDropdownChanged(event) {
AreYouSure.openModal(null, this.changeDepartment.bind(this, event.index));

View File

@ -1,4 +1,7 @@
import React from 'react';
import store from 'app/store';
import ConfigActions from 'actions/config-actions';
import MainLayout from 'app/main/main-layout';
import AdminPanelStaffWidget from 'app/admin/panel/admin-panel-staff-widget';
@ -8,6 +11,10 @@ import Widget from 'core-components/widget';
class AdminPanel extends React.Component {
componentDidMount() {
store.dispatch(ConfigActions.updateData());
}
render() {
return (
<MainLayout>
@ -33,4 +40,4 @@ class AdminPanel extends React.Component {
}
}
export default AdminPanel;
export default AdminPanel;

View File

@ -10,6 +10,7 @@ import Form from 'core-components/form';
import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button';
import Button from 'core-components/button';
import Icon from 'core-components/icon';
class AddStaffModal extends React.Component {
@ -63,7 +64,13 @@ class AddStaffModal extends React.Component {
}
getDepartments() {
return SessionStore.getDepartments().map(department => department.name);
return SessionStore.getDepartments().map(department => {
if(department.private*1){
return <spam>{department.name} <Icon name='user-secret'/> </spam>
}else {
return department.name;
}
});
}
onSubmit(form) {
@ -112,4 +119,4 @@ class AddStaffModal extends React.Component {
}
}
export default AddStaffModal;
export default AddStaffModal;

View File

@ -16,6 +16,7 @@ import Form from 'core-components/form';
import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button';
import DropDown from 'core-components/drop-down';
import Icon from 'core-components/icon';
class AdminPanelDepartments extends React.Component {
static defaultProps = {
@ -44,7 +45,13 @@ class AdminPanelDepartments extends React.Component {
</div>
<div className="col-md-8">
<Form {...this.getFormProps()}>
<FormField label={i18n('NAME')} name="name" validation="NAME" required fieldProps={{size: 'large'}}/>
<div>
<FormField className="admin-panel-departments__name" label={i18n('NAME')} name="name" validation="NAME" required fieldProps={{size: 'large'}}/>
<div className="admin-panel-departments__private-option">
<FormField label={i18n('PRIVATE')} name="private" field="checkbox"/>
<InfoTooltip className="admin-panel-departments__info-tooltip" text={i18n('PRIVATE_DEPARTMENT_DESCRIPTION')} />
</div>
</div>
<SubmitButton size="medium" className="admin-panel-departments__update-name-button" type="secondary">
{i18n((this.state.selectedIndex !== -1) ? 'UPDATE_DEPARTMENT' : 'ADD_DEPARTMENT')}
</SubmitButton>
@ -100,6 +107,7 @@ class AdminPanelDepartments extends React.Component {
content: (
<span>
{department.name}
{department.private*1 ? <Icon className="admin-panel-departments__private-icon" name='user-secret'/> : null }
{(!department.owners) ? (
<span className="admin-panel-departments__warning">
<InfoTooltip type="warning" text={i18n('NO_STAFF_ASSIGNED')}/>
@ -143,7 +151,8 @@ class AdminPanelDepartments extends React.Component {
path: '/system/edit-department',
data: {
departmentId: this.getCurrentDepartment().id,
name: form.name
name: form.name,
private: form.private ? 1 : 0
}
}).then(() => {
this.setState({formLoading: false});
@ -153,7 +162,8 @@ class AdminPanelDepartments extends React.Component {
API.call({
path: '/system/add-department',
data: {
name: form.name
name: form.name,
private: form.private ? 1 : 0
}
}).then(() => {
this.retrieveDepartments();
@ -222,4 +232,4 @@ export default connect((store) => {
return {
departments: store.config.departments
};
})(AdminPanelDepartments);
})(AdminPanelDepartments);

View File

@ -2,7 +2,6 @@
.admin-panel-departments {
&__list {
position: relative;
}
@ -12,6 +11,14 @@
min-width: 156px;
}
&__name {
display:inline-block;margin-right: 101px;
}
&__private-option {
display:inline-block;
margin-left: 10px;
}
&__optional-buttons {
float: right;
}
@ -39,4 +46,10 @@
&__transfer-tickets-drop-down {
display: inline-block;
}
}
&__private-icon {
margin-left: 5px;
}
&__info-tooltip {
margin-left: 2px;
}
}

View File

@ -18,6 +18,7 @@ import DropDown from 'core-components/drop-down';
import Button from 'core-components/button';
import Icon from 'core-components/icon';
import Loading from 'core-components/loading';
import DepartmentDropdown from 'app-components/department-dropdown';
class AdminPanelStaffMembers extends React.Component {
@ -45,7 +46,7 @@ class AdminPanelStaffMembers extends React.Component {
<div className="admin-panel-staff-members">
<Header title={i18n('STAFF_MEMBERS')} description={i18n('STAFF_MEMBERS_DESCRIPTION')} />
<div className="admin-panel-staff-members__wrapper">
<DropDown {...this.getDepartmentDropdownProps()} className="admin-panel-staff-members__dropdown" />
<DepartmentDropdown {...this.getDepartmentDropdownProps()} className="admin-panel-staff-members__dropdown" />
<Button onClick={this.onAddNewStaff.bind(this)} size="medium" type="secondary" className="admin-panel-staff-members__button">
<Icon name="user-plus" className=""/> {i18n('ADD_NEW_STAFF')}
</Button>
@ -61,7 +62,7 @@ class AdminPanelStaffMembers extends React.Component {
getDepartmentDropdownProps() {
return {
items: this.getDepartments(),
departments: this.getDepartments(),
onChange: (event) => {
let departments = SessionStore.getDepartments();
this.setState({
@ -97,12 +98,9 @@ class AdminPanelStaffMembers extends React.Component {
}
getDepartments() {
let departments = SessionStore.getDepartments().map((department) => {
return {content: department.name};
});
let departments = _.clone(SessionStore.getDepartments())
departments.unshift({
content: i18n('ALL_DEPARTMENTS')
name: i18n('ALL_DEPARTMENTS')
});
return departments;

View File

@ -23,4 +23,7 @@
text-decoration: none;
}
}
}
&__private {
margin-left: 5px;
}
}

View File

@ -256,7 +256,13 @@ class StaffEditor extends React.Component {
}
getDepartments() {
return SessionStore.getDepartments().map(department => department.name);
return SessionStore.getDepartments().map(department => {
if(department.private*1){
return <spam> {department.name} <Icon name='user-secret'/> </spam>
}else {
return department.name;
}
});
}
getStaffLevelInfo() {

View File

@ -8,6 +8,7 @@ import API from 'lib-app/api-call';
import SessionStore from 'lib-app/session-store';
import LanguageSelector from 'app-components/language-selector';
import Captcha from 'app/main/captcha';
import DepartmentDropdown from 'app-components/department-dropdown';
import Header from 'core-components/header';
import TextEditor from 'core-components/text-editor';
@ -48,8 +49,8 @@ class CreateTicketForm extends React.Component {
{(!this.props.userLogged) ? this.renderEmailAndName() : null}
<FormField label={i18n('TITLE')} name="title" validation="TITLE" required field="input" fieldProps={{size: 'large'}}/>
<div className="row">
<FormField className="col-md-5" label={i18n('DEPARTMENT')} name="departmentIndex" field="select" fieldProps={{
items: SessionStore.getDepartments().map((department) => {return {content: department.name}}),
<FormField className="col-md-5" label={i18n('DEPARTMENT')} name="departmentIndex" field="select" decorator={DepartmentDropdown} fieldProps={{
departments: SessionStore.getDepartments(),
size: 'medium'
}} />
<FormField className="col-md-5" label={i18n('LANGUAGE')} name="language" field="select" decorator={LanguageSelector} fieldProps={{

View File

@ -304,6 +304,7 @@ export default {
'DISABLE_USER_DESCRIPTION': 'User will be disabled and will not be able to sign in and create tickets.',
'PRIVATE_RESPONSE_DESCRIPTION': 'This response will only be seen by staff members',
'PRIVATE_TOPIC_DESCRIPTION': 'This topic will only be seen by staff members',
'PRIVATE_DEPARTMENT_DESCRIPTION': 'This department will only be seen by staff members',
//ERRORS
'EMAIL_OR_PASSWORD': 'Email or password invalid',

View File

@ -103,9 +103,8 @@ class SessionReducer extends Reducer {
onUserDataRetrieved(state, payload) {
let userData = payload.data;
sessionStore.storeUserData(payload.data);
return _.extend({}, state, {
staff: userData.staff,
userName: userData.name,
@ -117,11 +116,11 @@ class SessionReducer extends Reducer {
userSendEmailOnNewTicket: userData.sendEmailOnNewTicket * 1
});
}
onSessionChecked(state) {
let userData = sessionStore.getUserData();
let userId = sessionStore.getSessionData().userId;
return _.extend({}, state, {
initDone: true,
logged: true,
@ -144,4 +143,4 @@ class SessionReducer extends Reducer {
}
}
export default SessionReducer.getInstance();
export default SessionReducer.getInstance();

View File

@ -15,7 +15,7 @@ DataValidator::with('CustomValidations', true);
* @apiPermission staff1
*
* @apiParam {Number} staffId The id of the staff member to be searched.
*
*
* @apiUse NO_PERMISSION
*
* @apiSuccess {Object} data Information about a staff member
@ -57,7 +57,8 @@ class GetStaffController extends Controller {
foreach($departmentList as $department) {
$parsedDepartmentList[] = [
'id' => $department->id,
'name' => $department->name
'name' => $department->name,
'private' => $department->private
];
}
@ -72,4 +73,4 @@ class GetStaffController extends Controller {
'sendEmailOnNewTicket' => $user->sendEmailOnNewTicket
]);
}
}
}

View File

@ -27,6 +27,12 @@ class UnAssignStaffController extends Controller {
const PATH = '/un-assign-ticket';
const METHOD = 'POST';
private $user;
public function __construct($user=null) {
$this->user = $user;
}
public function validations() {
return [
'permission' => 'staff_1',
@ -41,7 +47,7 @@ class UnAssignStaffController extends Controller {
public function handler() {
$ticketNumber = Controller::request('ticketNumber');
$user = Controller::getLoggedUser();
$user = ($this->user? $this->user : Controller::getLoggedUser());
$ticket = Ticket::getByTicketNumber($ticketNumber);
$owner = $ticket->owner;

View File

@ -14,6 +14,7 @@ use Respect\Validation\Validator as DataValidator;
* @apiPermission staff3
*
* @apiParam {String} name Name of the new department.
* @apiParam {Boolean} private Indicates if the deparment is not shown to users.
*
* @apiUse NO_PERMISSION
*
@ -28,17 +29,24 @@ class AddDepartmentController extends Controller {
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => []
'requestData' => [
'name' => [
'validation' => DataValidator::length(2, 100),
'error' => ERRORS::INVALID_NAME
]
]
];
}
public function handler() {
$name = Controller::request('name');
$private = Controller::request('private');
$departmentInstance = new Department();
$departmentInstance->setProperties([
'name' => $name,
'name' => $name ,
'private' => $private ? 1 : 0
]);
$departmentInstance->store();

View File

@ -16,6 +16,7 @@ DataValidator::with('CustomValidations', true);
*
* @apiParam {String} name The new name of the department.
* @apiParam {Number} departmentId The Id of the department.
* @apiParam {Boolean} private Indicates if the department is shown to users;
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_NAME
@ -33,10 +34,6 @@ class EditDepartmentController extends Controller {
return [
'permission' => 'staff_3',
'requestData' => [
'name' => [
'validation' => DataValidator::alnum(),
'error' => ERRORS::INVALID_NAME
],
'departmentId' => [
'validation' => DataValidator::dataStoreId('department'),
'error' => ERRORS::INVALID_DEPARTMENT
@ -46,18 +43,20 @@ class EditDepartmentController extends Controller {
}
public function handler() {
$newname = Controller::request('name');
$departmentId = Controller::request('departmentId');
$private = Controller::request('private');
$departmentInstance = Department::getDataStore($departmentId);
$departmentInstance->name = $newname ;
$newname ? $departmentInstance->name = $newname : null;
$departmentInstance->private = $private ? 1 : 0;
$departmentInstance->store();
Log::createLog('EDIT_DEPARTMENT', $departmentInstance->name);
Response::respondSuccess();
}
}
}

View File

@ -50,7 +50,7 @@ class GetSettingsController extends Controller {
'smtp-host' => Setting::getSetting('smtp-host')->getValue(),
'smtp-user' => Setting::getSetting('smtp-user')->getValue(),
'registration' => Setting::getSetting('registration')->getValue(),
'departments' => Department::getDepartmentNames(),
'departments' => Department::getAllDepartmentNames(),
'supportedLanguages' => Language::getSupportedLanguages(),
'allowedLanguages' => Language::getAllowedLanguages(),
'session-prefix' => Setting::getSetting('session-prefix')
@ -66,7 +66,7 @@ class GetSettingsController extends Controller {
'max-size' => Setting::getSetting('max-size')->getValue(),
'title' => Setting::getSetting('title')->getValue(),
'registration' => Setting::getSetting('registration')->getValue(),
'departments' => Department::getDepartmentNames(),
'departments' => Controller::isStaffLogged() ? Department::getAllDepartmentNames() : Department::getPublicDepartmentNames(),
'supportedLanguages' => Language::getSupportedLanguages(),
'allowedLanguages' => Language::getAllowedLanguages(),
'user-system-enabled' => intval(Setting::getSetting('user-system-enabled')->getValue()),

View File

@ -52,6 +52,10 @@ class ChangeDepartmentController extends Controller {
$department = Department::getDataStore($departmentId);
$user = Controller::getLoggedUser();
if(!$ticket->authorStaffId && $department->private){
throw new Exception(ERRORS::NO_PERMISSION);
}
if($ticket->owner && $ticket->owner->id !== $user->id && $user->level == 1){
throw new Exception(ERRORS::NO_PERMISSION);
}
@ -67,8 +71,8 @@ class ChangeDepartmentController extends Controller {
$ticket->unread = !$ticket->isAuthor($user);
$ticket->store();
if(!$user->sharedDepartmentList->includesId($department->id)) {
$unAssignTicketController = new UnAssignStaffController();
if($ticket->owner && !$ticket->owner->sharedDepartmentList->includesId($department->id)) {
$unAssignTicketController = new UnAssignStaffController($ticket->owner);
$unAssignTicketController->validate();
$unAssignTicketController->handler();
}

View File

@ -99,6 +99,9 @@ class CreateController extends Controller {
$this->email = Controller::request('email');
$this->name = Controller::request('name');
if(!Controller::isStaffLogged() && Department::getDataStore($this->departmentId)->private){
throw new Exception(ERRORS::INVALID_DEPARTMENT);
}
$this->storeTicket();
if(!Controller::isUserSystemEnabled()) {

View File

@ -8,26 +8,28 @@ use RedBeanPHP\Facade as RedBean;
* @apiParam {Number} id Id of the department.
* @apiParam {String} name Name of the department.
* @apiParam {[Staff](#api-Data_Structures-ObjectStaff)[]} owners List of owners of the department.
* @apiParam {Boolean} private Indicates if the departmetn is not shown to users.
*/
class Department extends DataStore {
const TABLE = 'department';
public static function getProps() {
return [
'name',
'sharedTicketList',
'owners'
];
}
public function getDefaultProps() {
return [
'owners' => 0
'owners',
'private'
];
}
public static function getDepartmentNames() {
public function getDefaultProps() {
return [
'owners' => 0
];
}
public static function getAllDepartmentNames() {
$departmentsList = RedBean::findAll(Department::TABLE);
$departmentsNameList = [];
@ -35,12 +37,32 @@ class Department extends DataStore {
$departmentsNameList[] = [
'id' => $department->id,
'name' => $department->name,
'owners' => $department->owners
'owners' => $department->owners,
'private' => $department->private
];
}
return $departmentsNameList;
}
public static function getPublicDepartmentNames() {
$departmentsList = RedBean::findAll(Department::TABLE);
$departmentsNameList = [];
foreach($departmentsList as $department) {
if(!$department->private) {
$departmentsNameList[] = [
'id' => $department->id,
'name' => $department->name,
'owners' => $department->owners,
'private' => $department->private
];
}
}
return $departmentsNameList;
}
public function toArray() {
return [
'id' => $this->id,
@ -48,4 +70,4 @@ class Department extends DataStore {
'owners' => $this->owners
];
}
}
}

View File

@ -7,7 +7,7 @@ install:
@bundle install
run: export MYSQL_HOST=127.0.0.1
run: export MYSQL_PORT=4040
run: export MYSQL_PORT=3306
run:
./run-tests.sh