commit
515e615418
|
@ -125,4 +125,4 @@ export default {
|
|||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -126,4 +126,4 @@ class ActivityRow extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default ActivityRow;
|
||||
export default ActivityRow;
|
||||
|
|
|
@ -20,7 +20,8 @@ class TicketEvent extends React.Component {
|
|||
]),
|
||||
author: React.PropTypes.object,
|
||||
content: React.PropTypes.string,
|
||||
date: React.PropTypes.string
|
||||
date: React.PropTypes.string,
|
||||
private: React.PropTypes.string,
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -83,7 +84,8 @@ class TicketEvent extends React.Component {
|
|||
<span className="ticket-event__comment-pointer" />
|
||||
<div className="ticket-event__comment-author">
|
||||
<span className="ticket-event__comment-author-name">{this.props.author.name}</span>
|
||||
<span className="ticket-event__comment-author-type">({i18n((this.props.author.staff) ? 'STAFF' : 'CUSTOMER')})</span>
|
||||
<span className="ticket-event__comment-author-type">{i18n((this.props.author.staff) ? 'STAFF' : 'CUSTOMER')}</span>
|
||||
{(this.props.private*1) ? <span className="ticket-event__comment-author-type">{i18n('PRIVATE')}</span> : null}
|
||||
</div>
|
||||
<div className="ticket-event__comment-date">{DateTransformer.transformToString(this.props.date)}</div>
|
||||
<div className="ticket-event__comment-content" dangerouslySetInnerHTML={{__html: this.props.content}}></div>
|
||||
|
@ -205,7 +207,8 @@ class TicketEvent extends React.Component {
|
|||
'ticket-event_close': this.props.type === 'CLOSE',
|
||||
'ticket-event_reopen': this.props.type === 'RE_OPEN',
|
||||
'ticket-event_department': this.props.type === 'DEPARTMENT_CHANGED',
|
||||
'ticket-event_priority': this.props.type === 'PRIORITY_CHANGED'
|
||||
'ticket-event_priority': this.props.type === 'PRIORITY_CHANGED',
|
||||
'ticket-event_private': this.props.private*1,
|
||||
};
|
||||
|
||||
return classNames(classes);
|
||||
|
|
|
@ -66,10 +66,14 @@
|
|||
color: $primary-black;
|
||||
|
||||
&-type {
|
||||
font-size: 10px;
|
||||
font-size: 10.6px;
|
||||
padding-left: 10px;
|
||||
color: $secondary-blue;
|
||||
font-variant: small-caps;
|
||||
background-color: very-light-grey;
|
||||
border: 2px solid;
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,6 +82,8 @@
|
|||
border: 2px solid $light-grey;
|
||||
border-bottom: none;
|
||||
padding: 12px;
|
||||
font-size: 10.6px;
|
||||
font-family: helvetica;
|
||||
background-color: $light-grey;
|
||||
|
||||
}
|
||||
|
@ -176,4 +182,24 @@
|
|||
padding-top: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&_private {
|
||||
.ticket-event__comment-pointer {
|
||||
border-right-color: $light-yellow;
|
||||
}
|
||||
.ticket-event__comment-date {
|
||||
background-color: $light-yellow;
|
||||
border-color: $light-yellow;
|
||||
}
|
||||
.ticket-event__comment-content {
|
||||
background-color: $very-light-yellow;
|
||||
border-color: $very-light-yellow;
|
||||
}
|
||||
.ticket-event__staff-pic {
|
||||
background-color: $light-yellow;
|
||||
border-color: $light-yellow;
|
||||
}
|
||||
.ticket-event__file {
|
||||
background-color: $light-yellow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,8 @@ class TicketViewer extends React.Component {
|
|||
state = {
|
||||
loading: false,
|
||||
commentValue: TextEditor.createEmpty(),
|
||||
commentEdited: false
|
||||
commentEdited: false,
|
||||
commentPrivate: false
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -211,19 +212,22 @@ class TicketViewer extends React.Component {
|
|||
renderResponseField() {
|
||||
return (
|
||||
<div className="ticket-viewer__response">
|
||||
<div className="ticket-viewer__response-title row">{i18n('RESPOND')}</div>
|
||||
{this.renderCustomResponses()}
|
||||
<div className="ticket-viewer__response-field row">
|
||||
<Form {...this.getCommentFormProps()}>
|
||||
<Form {...this.getCommentFormProps()}>
|
||||
<div className="ticket-viewer__response-title row">{i18n('RESPOND')}</div>
|
||||
<div className="ticket-viewer__actions row">
|
||||
{this.renderCustomResponses()}
|
||||
{this.renderPrivate()}
|
||||
</div>
|
||||
<div className="ticket-viewer__response-field row">
|
||||
<FormField name="content" validation="TEXT_AREA" required field="textarea" fieldProps={{allowImages: this.props.allowAttachments}}/>
|
||||
{(this.props.allowAttachments) ? <FormField name="file" field="file"/> : null}
|
||||
<div className="ticket-viewer__response-buttons">
|
||||
<SubmitButton type="secondary">{i18n('RESPOND_TICKET')}</SubmitButton>
|
||||
<Button size="medium" onClick={this.onCloseTicketClick.bind(this)}>{i18n('CLOSE_TICKET')}</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
{(this.state.commentError) ? this.renderCommentError() : null}
|
||||
</div>
|
||||
{(this.state.commentError) ? this.renderCommentError() : null}
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -243,7 +247,7 @@ class TicketViewer extends React.Component {
|
|||
});
|
||||
|
||||
customResponsesNode = (
|
||||
<div className="ticket-viewer__response-custom row">
|
||||
<div className="ticket-viewer__response-custom">
|
||||
<DropDown items={customResponses} size="medium" onChange={this.onCustomResponsesChanged.bind(this)}/>
|
||||
</div>
|
||||
);
|
||||
|
@ -258,6 +262,18 @@ class TicketViewer extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
renderPrivate() {
|
||||
if (this.props.userStaff) {
|
||||
return (
|
||||
<div className="ticket-viewer__private">
|
||||
<FormField label={i18n('PRIVATE')} name="private" field="checkbox"/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
getCommentFormProps() {
|
||||
return {
|
||||
onSubmit: this.onSubmit.bind(this),
|
||||
|
@ -265,11 +281,13 @@ class TicketViewer extends React.Component {
|
|||
onChange: (formState) => {this.setState({
|
||||
commentValue: formState.content,
|
||||
commentFile: formState.file,
|
||||
commentEdited: true
|
||||
commentEdited: true,
|
||||
commentPrivate: formState.private
|
||||
})},
|
||||
values: {
|
||||
'content': this.state.commentValue,
|
||||
'file': this.state.commentFile
|
||||
'file': this.state.commentFile,
|
||||
'private': this.state.commentPrivate
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -387,7 +405,7 @@ class TicketViewer extends React.Component {
|
|||
dataAsForm: true,
|
||||
data: _.extend({
|
||||
ticketNumber: this.props.ticket.ticketNumber
|
||||
}, formState, TextEditor.getContentFormData(formState.content))
|
||||
}, formState, {private: formState.private ? 1 : 0}, TextEditor.getContentFormData(formState.content))
|
||||
}).then(this.onCommentSuccess.bind(this), this.onCommentFail.bind(this));
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
font-size: 16px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
&__private {
|
||||
|
||||
}
|
||||
|
||||
&__number {
|
||||
color: white;
|
||||
|
@ -76,11 +79,19 @@
|
|||
padding: 20px 0 0 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&-buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
}
|
||||
&__actions {
|
||||
background-color: $very-light-grey;
|
||||
|
||||
display:flex;
|
||||
align-items:center;justify-content: space-between;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ class AdminLoginPage extends React.Component {
|
|||
loadingLogin: false,
|
||||
loadingRecover: false
|
||||
};
|
||||
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!prevProps.session.failed && this.props.session.failed) {
|
||||
this.refs.loginForm.refs.password.focus();
|
||||
|
@ -34,7 +34,7 @@ class AdminLoginPage extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
return (
|
||||
<div className="admin-login-page">
|
||||
<WidgetTransition sideToShow={this.state.sideToShow} className={classNames('admin-login-page__container', this.props.className)}>
|
||||
{this.renderLogin()}
|
||||
|
@ -43,7 +43,7 @@ class AdminLoginPage extends React.Component {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
renderLogin() {
|
||||
return (
|
||||
<div>
|
||||
|
@ -65,7 +65,7 @@ class AdminLoginPage extends React.Component {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
renderPasswordRecovery() {
|
||||
return (
|
||||
<div>
|
||||
|
@ -161,7 +161,7 @@ class AdminLoginPage extends React.Component {
|
|||
loginFormErrors: errors
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
onRecoverFormErrorsValidation(errors) {
|
||||
this.setState({
|
||||
recoverFormErrors: errors
|
||||
|
|
|
@ -224,4 +224,4 @@ export default connect((store) => {
|
|||
return {
|
||||
level: store.session.userLevel
|
||||
};
|
||||
})(AdminPanelMenu);
|
||||
})(AdminPanelMenu);
|
||||
|
|
|
@ -14,7 +14,7 @@ class AdminPanelStats extends React.Component {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default AdminPanelStats;
|
||||
export default AdminPanelStats;
|
||||
|
|
|
@ -183,6 +183,7 @@ export default {
|
|||
'HIMSELF': 'ele mesmo',
|
||||
'ADD_USER': 'Adicionar usuário',
|
||||
'UPLOAD_FILE': 'Subir arquivo',
|
||||
'PRIVATE': 'privado',
|
||||
'ENABLE_USER': 'Ativar usuário',
|
||||
'DISABLE_USER': 'Desativar usuário',
|
||||
|
||||
|
@ -244,7 +245,7 @@ export default {
|
|||
'INSTALLATION_COMPLETED': 'Instalação completa.',
|
||||
'INSTALLATION_COMPLETED_DESCRIPTION': 'A instalação do OpenSupports está concluída. Redirecionando para o painel de administração ...',
|
||||
|
||||
'STEP_TITLE': 'Passo {current} de {total} - {title}',
|
||||
'STEP_TITLE': 'Passo {current} de {total} - {title}',
|
||||
'STEP_1_DESCRIPTION': 'Selecione o idioma preferido para o assistente de instalação.',
|
||||
'STEP_2_DESCRIPTION': 'Aqui estão listados os requisitos para executar o OpenSupports. Certifique-se de que todos os requisitos estão satisfeitos.',
|
||||
'STEP_3_DESCRIPTION': 'Preencha a configuração do banco de dados MySQL.',
|
||||
|
|
|
@ -184,6 +184,7 @@ export default {
|
|||
'HIMSELF': '他自己',
|
||||
'ADD_USER': '添加用户',
|
||||
'UPLOAD_FILE': '上传文件',
|
||||
'PRIVATE': '私人的',
|
||||
'ENABLE_USER': '启用用户',
|
||||
'DISABLE_USER': '禁用用户',
|
||||
|
||||
|
|
|
@ -184,6 +184,7 @@ export default {
|
|||
'HIMSELF': 'selbst',
|
||||
'ADD_USER': 'Benutzer hinzufügen',
|
||||
'UPLOAD_FILE': 'Datei hochladen',
|
||||
'PRIVATE': 'Privatgelände',
|
||||
'ENABLE_USER': 'Benutzer aktivieren',
|
||||
'DISABLE_USER': 'Benutzer deaktivieren',
|
||||
|
||||
|
|
|
@ -184,6 +184,7 @@ export default {
|
|||
'HIMSELF': 'himself',
|
||||
'ADD_USER': 'Add user',
|
||||
'UPLOAD_FILE': 'Upload file',
|
||||
'PRIVATE': 'private',
|
||||
'ENABLE_USER': 'Enable User',
|
||||
'DISABLE_USER': 'Disable User',
|
||||
|
||||
|
|
|
@ -184,6 +184,7 @@ export default {
|
|||
'HIMSELF': 'si mismo',
|
||||
'ADD_USER': 'Añadir un usuario',
|
||||
'UPLOAD_FILE': 'Subir archivo',
|
||||
'PRIVATE': 'privado',
|
||||
'ENABLE_USER': 'Habilitar usuario',
|
||||
'DISABLE_USER': 'Deshabilitar usuario',
|
||||
|
||||
|
|
|
@ -184,6 +184,7 @@ export default {
|
|||
'HIMSELF': 'lui-même',
|
||||
'ADD_USER': 'Ajouter un utilisateur',
|
||||
'UPLOAD_FILE': 'Téléverser un fichier',
|
||||
'PRIVATE': 'privé',
|
||||
'ENABLE_USER': 'Activer l\'utilisateur',
|
||||
'DISABLE_USER': 'Désactiver l\'utilisateur',
|
||||
|
||||
|
|
|
@ -184,6 +184,7 @@
|
|||
'HIMSELF': 'ο ίδιος',
|
||||
'ADD_USER': 'Πρόσθεσε χρήστη',
|
||||
'UPLOAD_FILE': 'Ανέβασμα αρχείου',
|
||||
'PRIVATE': 'ιδιωτικός',
|
||||
'ENABLE_USER': 'Ενεργοποίηση χρήστη',
|
||||
'DISABLE_USER': 'Απενεργοποίηση χρήστη',
|
||||
|
||||
|
|
|
@ -184,6 +184,7 @@ export default {
|
|||
'HIMSELF': 'स्वयं',
|
||||
'ADD_USER': 'उपयोगकर्ता जोड़ें',
|
||||
'UPLOAD_FILE': 'दस्तावेज अपलोड करें',
|
||||
'PRIVATE': 'निजी',
|
||||
'ENABLE_USER': 'उपयोगकर्ता सक्षम करें',
|
||||
'DISABLE_USER': 'उपयोगकर्ता को अक्षम करें',
|
||||
|
||||
|
|
|
@ -184,6 +184,7 @@ export default {
|
|||
'HIMSELF': 'lui stesso',
|
||||
'ADD_USER': 'Aggiungi utente',
|
||||
'UPLOAD_FILE': 'Caricare un file',
|
||||
'PRIVATE': 'privato',
|
||||
'ENABLE_USER': 'Abilita utente',
|
||||
'DISABLE_USER': 'Disabilita utente',
|
||||
|
||||
|
|
|
@ -184,6 +184,7 @@ export default {
|
|||
'HIMSELF': '彼自身',
|
||||
'ADD_USER': 'ユーザーを追加する',
|
||||
'UPLOAD_FILE': 'ファイルをアップロードする',
|
||||
'PRIVATE': 'プライベート',
|
||||
'ENABLE_USER': 'ユーザーを有効にする',
|
||||
'DISABLE_USER': 'ユーザーを無効にする',
|
||||
|
||||
|
|
|
@ -184,6 +184,7 @@ export default {
|
|||
'HIMSELF': 'zichzelf',
|
||||
'ADD_USER': 'Voeg gebruiker toe',
|
||||
'UPLOAD_FILE': 'Upload bestand',
|
||||
'PRIVATE': 'privaat',
|
||||
'ENABLE_USER': 'Schakel gebruiker in',
|
||||
'DISABLE_USER': 'Gebruiker uitschakelen',
|
||||
|
||||
|
|
|
@ -184,6 +184,7 @@ export default {
|
|||
'HIMSELF': 'ele mesmo',
|
||||
'ADD_USER': 'Adicionar usuário',
|
||||
'UPLOAD_FILE': 'Subir arquivo',
|
||||
'PRIVATE': 'privado',
|
||||
'ENABLE_USER': 'Ativar usuário',
|
||||
'DISABLE_USER': 'Desativar usuário',
|
||||
|
||||
|
|
|
@ -184,6 +184,7 @@ export default {
|
|||
'HIMSELF': 'сам',
|
||||
'ADD_USER': 'Добавить пользователя',
|
||||
'UPLOAD_FILE': 'Загрузить файл',
|
||||
'PRIVATE': 'частный',
|
||||
'ENABLE_USER': 'Включить пользователя',
|
||||
'DISABLE_USER': 'Отключить пользователя',
|
||||
|
||||
|
|
|
@ -184,6 +184,7 @@ export default {
|
|||
'HIMSELF': 'kendisi',
|
||||
'ADD_USER': 'Kullanıcı Ekle',
|
||||
'UPLOAD_FILE': 'Dosya yükleme',
|
||||
'PRIVATE': 'gizli',
|
||||
'ENABLE_USER': 'Kullanıcıyı Etkinleştir',
|
||||
'DISABLE_USER': 'Kullanıcıyı Devre Dışı Bırak',
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ $primary-blue: #414A59;
|
|||
$secondary-blue: #20B8c5;
|
||||
|
||||
$primary-yellow: #E5D151;
|
||||
$light-yellow: rgba(229,209,81,0.6);
|
||||
$very-light-yellow: #ffffe0;
|
||||
|
||||
$primary-green: #82CA9C;
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ DataValidator::with('CustomValidations', true);
|
|||
*
|
||||
* @apiParam {String} content Content of the comment.
|
||||
* @apiParam {Number} ticketNumber The number of the ticket to comment.
|
||||
* @apiParam {Boolean} private Indicates if the comment is not shown to users.
|
||||
* @apiParam {Number} images The number of images in the content
|
||||
* @apiParam image_i The image file of index `i` (mutiple params accepted)
|
||||
* @apiParam file The file you with to upload.
|
||||
|
@ -118,7 +119,8 @@ class CommentController extends Controller {
|
|||
$comment->setProperties(array(
|
||||
'content' => $this->replaceWithImagePaths($imagePaths, $this->content),
|
||||
'file' => ($fileUploader instanceof FileUploader) ? $fileUploader->getFileName() : null,
|
||||
'date' => Date::getCurrentDate()
|
||||
'date' => Date::getCurrentDate(),
|
||||
'private' => (Controller::isStaffLogged() && Controller::request('private')) ? 1 : 0
|
||||
));
|
||||
|
||||
if(Controller::isStaffLogged()) {
|
||||
|
|
|
@ -170,7 +170,8 @@ class Ticket extends DataStore {
|
|||
'content'=> $ticketEvent->content,
|
||||
'author' => [],
|
||||
'date'=> $ticketEvent->date,
|
||||
'file'=> $ticketEvent->file
|
||||
'file'=> $ticketEvent->file,
|
||||
'private'=> $ticketEvent->private,
|
||||
];
|
||||
|
||||
$author = $ticketEvent->getAuthor();
|
||||
|
@ -184,6 +185,10 @@ class Ticket extends DataStore {
|
|||
];
|
||||
}
|
||||
|
||||
if(!Controller::isStaffLogged() && $ticketEvent->private) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$events[] = $event;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
* @apiParam {Boolean} author.staff Indicates if the author is a staff.
|
||||
* @apiParam {String} date The date of the ticket event.
|
||||
* @apiParam {String} file The file of the ticket event.
|
||||
* @apiParam {Boolean} private Indicates if this event is not shown to users.
|
||||
*/
|
||||
|
||||
class Ticketevent extends DataStore {
|
||||
|
@ -58,7 +59,8 @@ class Ticketevent extends DataStore {
|
|||
'file',
|
||||
'authorUser',
|
||||
'authorStaff',
|
||||
'date'
|
||||
'date',
|
||||
'private'
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ describe '/staff/get-new-tickets' do
|
|||
})
|
||||
|
||||
(result['status']).should.equal('success')
|
||||
(result['data'].size).should.equal(9)
|
||||
(result['data'].size).should.equal(8)
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -142,4 +142,46 @@ describe '/ticket/comment/' do
|
|||
(result['status']).should.equal('fail')
|
||||
(result['message']).should.equal('NO_PERMISSION')
|
||||
end
|
||||
|
||||
it 'should keep private on 0 if an user creates a private comment' do
|
||||
Scripts.login('commenter@os4.com', 'commenter')
|
||||
|
||||
result = request('/ticket/comment', {
|
||||
content: 'this is not a private comment',
|
||||
ticketNumber: @ticketNumber,
|
||||
csrf_userid: $csrf_userid,
|
||||
csrf_token: $csrf_token,
|
||||
private: 1
|
||||
})
|
||||
|
||||
(result['status']).should.equal('success')
|
||||
comment = $database.getRow('ticketevent', 'this is not a private comment', 'content')
|
||||
(comment['private']).should.equal("0")
|
||||
request('/user/logout')
|
||||
|
||||
end
|
||||
|
||||
it 'should change private to 1 if a staff creates a private comment' do
|
||||
request('/user/logout')
|
||||
|
||||
Scripts.login('jorah@opensupports.com', 'testpassword', true)
|
||||
|
||||
request('/staff/assign-ticket', {
|
||||
ticketNumber: @ticketNumber,
|
||||
csrf_userid: $csrf_userid,
|
||||
csrf_token: $csrf_token,
|
||||
})
|
||||
|
||||
result = request('/ticket/comment', {
|
||||
content: 'this is a private comment',
|
||||
ticketNumber: @ticketNumber,
|
||||
csrf_userid: $csrf_userid,
|
||||
csrf_token: $csrf_token,
|
||||
private: 1
|
||||
})
|
||||
puts result['message']
|
||||
(result['status']).should.equal('success')
|
||||
comment = $database.getRow('ticketevent', 'this is a private comment', 'content')
|
||||
(comment['private']).should.equal("1")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,7 +18,7 @@ describe '/ticket/seen' do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'when a user is logged' do
|
||||
describe 'when an user is logged' do
|
||||
|
||||
request('/user/logout')
|
||||
Scripts.login()
|
||||
|
@ -36,4 +36,4 @@ describe '/ticket/seen' do
|
|||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue