mirror of
https://github.com/opensupports/opensupports.git
synced 2025-07-28 16:24:42 +02:00
Add edit buttons for fields in ticket viewer (#835)
* Fix style color in autocomplete component and dropdown in ticket search and ticket viewer. * Change style of ticket status edit in ticket viewer. * Redirect to ticket search when author name was clicked in ticket viewer * Add i18n ASSIGNED_TO_ME. * Add content on selected in dropdown component. * Add styles in edit tags in ticket viewers * Add edit owner and delete i18n ASSIGNED_TO_ME. * Redirect to ticket search when owner name was clicked in ticket viewer * Add style in edit department in ticket viewer. * Add nav links in owner and author in ticket viewer.
This commit is contained in:
parent
1ecf619892
commit
938e25b2fa
@ -63,7 +63,7 @@ class TicketQueryFilters extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
<div className="ticket-query-filters__row__filter">
|
<div className="ticket-query-filters__row__filter">
|
||||||
<span>{i18n('STATUS')}</span>
|
<span>{i18n('STATUS')}</span>
|
||||||
<FormField name="closed" field="select" fieldProps={{items: this.getStatusItems()}} />
|
<FormField name="closed" field="select" fieldProps={{items: this.getStatusItems(), className: 'ticket-query-filters__group__container__status-drop-down'}} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ticket-query-filters__row">
|
<div className="ticket-query-filters__row">
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
||||||
&__button {
|
&__button {
|
||||||
margin: 0 10px;
|
margin: 0 10px;
|
||||||
}
|
}
|
||||||
@ -52,6 +53,14 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
|
|
||||||
|
&__status-drop-down > .drop-down__current-item {
|
||||||
|
background-color: $very-light-grey;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background-color: $medium-grey;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import API from 'lib-app/api-call';
|
|||||||
import SessionStore from 'lib-app/session-store';
|
import SessionStore from 'lib-app/session-store';
|
||||||
import MentionsParser from 'lib-app/mentions-parser';
|
import MentionsParser from 'lib-app/mentions-parser';
|
||||||
import history from 'lib-app/history';
|
import history from 'lib-app/history';
|
||||||
|
import searchTicketsUtils from 'lib-app/search-tickets-utils';
|
||||||
|
|
||||||
import TicketEvent from 'app-components/ticket-event';
|
import TicketEvent from 'app-components/ticket-event';
|
||||||
import AreYouSure from 'app-components/are-you-sure';
|
import AreYouSure from 'app-components/are-you-sure';
|
||||||
@ -66,41 +67,58 @@ class TicketViewer extends React.Component {
|
|||||||
newTitle: this.props.ticket.title,
|
newTitle: this.props.ticket.title,
|
||||||
editTitleError: false,
|
editTitleError: false,
|
||||||
editTitleLoading: false,
|
editTitleLoading: false,
|
||||||
|
editStatus: false,
|
||||||
|
editTags: false,
|
||||||
|
editOwner: false,
|
||||||
|
editDepartment: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if(!this.props.staffMembersLoaded && this.props.userStaff) {
|
const {
|
||||||
this.props.dispatch(AdminDataActions.retrieveStaffMembers());
|
staffMembersLoaded,
|
||||||
|
userStaff,
|
||||||
|
dispatch
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if(!staffMembersLoaded && userStaff) {
|
||||||
|
dispatch(AdminDataActions.retrieveStaffMembers());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const ticket = this.props.ticket;
|
const {
|
||||||
|
ticket,
|
||||||
|
userStaff,
|
||||||
|
userId,
|
||||||
|
editable,
|
||||||
|
allowAttachments,
|
||||||
|
assignmentAllowed
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ticket-viewer">
|
<div className="ticket-viewer">
|
||||||
{this.state.editTitle ? this.renderEditableTitle() : this.renderTitleHeader()}
|
{this.state.editTitle ? this.renderEditableTitle() : this.renderTitleHeader()}
|
||||||
{this.props.editable ? this.renderEditableHeaders() : this.renderHeaders()}
|
{editable ? this.renderEditableHeaders() : this.renderHeaders()}
|
||||||
<div className="ticket-viewer__content">
|
<div className="ticket-viewer__content">
|
||||||
<TicketEvent
|
<TicketEvent
|
||||||
loading={this.state.loading}
|
loading={this.state.loading}
|
||||||
type="COMMENT"
|
type="COMMENT"
|
||||||
author={ticket.author}
|
author={ticket.author}
|
||||||
content={this.props.userStaff ? MentionsParser.parse(ticket.content) : ticket.content}
|
content={userStaff ? MentionsParser.parse(ticket.content) : ticket.content}
|
||||||
userStaff={this.props.userStaff}
|
userStaff={userStaff}
|
||||||
userId={this.props.userId}
|
userId={userId}
|
||||||
date={ticket.date}
|
date={ticket.date}
|
||||||
onEdit={this.onEdit.bind(this,0)}
|
onEdit={this.onEdit.bind(this,0)}
|
||||||
edited={ticket.edited}
|
edited={ticket.edited}
|
||||||
file={ticket.file}
|
file={ticket.file}
|
||||||
edit={this.state.edit && this.state.editId == 0}
|
edit={this.state.edit && this.state.editId == 0}
|
||||||
onToggleEdit={this.onToggleEdit.bind(this, 0)}
|
onToggleEdit={this.onToggleEdit.bind(this, 0)}
|
||||||
allowAttachments={this.props.allowAttachments} />
|
allowAttachments={allowAttachments} />
|
||||||
</div>
|
</div>
|
||||||
<div className="ticket-viewer__comments">
|
<div className="ticket-viewer__comments">
|
||||||
{ticket.events && ticket.events.map(this.renderTicketEvent.bind(this))}
|
{ticket.events && ticket.events.map(this.renderTicketEvent.bind(this))}
|
||||||
</div>
|
</div>
|
||||||
{(!this.props.ticket.closed && (this.props.editable || !this.props.assignmentAllowed)) ? this.renderResponseField() : (this.showDeleteButton())? <Button size="medium" onClick={this.onDeleteTicketClick.bind(this)}>{i18n('DELETE_TICKET')}</Button> : null}
|
{(!ticket.closed && (editable || !assignmentAllowed)) ? this.renderResponseField() : (this.showDeleteButton())? <Button size="medium" onClick={this.onDeleteTicketClick.bind(this)}>{i18n('DELETE_TICKET')}</Button> : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -122,20 +140,6 @@ class TicketViewer extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderEditedTitleText(){
|
|
||||||
return(
|
|
||||||
<div className="ticket-viewer__edited-title-text"> {i18n('TITLE_EDITED')} </div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderEditTitleOption() {
|
|
||||||
return(
|
|
||||||
<span className="ticket-viewer__edit-title-icon">
|
|
||||||
<Icon name="pencil" onClick={() => this.setState({editTitle: true})} />
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderEditableTitle(){
|
renderEditableTitle(){
|
||||||
return(
|
return(
|
||||||
<div className="ticket-viewer__header">
|
<div className="ticket-viewer__header">
|
||||||
@ -160,55 +164,110 @@ class TicketViewer extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderEditableHeaders() {
|
renderEditableHeaders() {
|
||||||
const ticket = this.props.ticket;
|
const { userStaff, ticket } = this.props;
|
||||||
const departments = this.getDepartmentsForTransfer();
|
const filtersOnlyWithAuthor = {
|
||||||
|
authors: [
|
||||||
|
{
|
||||||
|
id: ticket.author.id*1,
|
||||||
|
isStaff: ticket.author.staff*1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ticket-viewer__headers">
|
<div className="ticket-viewer__headers">
|
||||||
<div className="ticket-viewer__info">
|
<div className="ticket-viewer__info">
|
||||||
<div className="ticket-viewer__info-container-editable">
|
<div className="ticket-viewer__info-container-editable">
|
||||||
|
<div className="ticket-viewer__info-container-editable__column">
|
||||||
<div className="ticket-viewer__info-header">{i18n('DEPARTMENT')}</div>
|
<div className="ticket-viewer__info-header">{i18n('DEPARTMENT')}</div>
|
||||||
<div className="ticket-viewer__info-value">
|
<div className="ticket-viewer__info-value">
|
||||||
<DepartmentDropdown
|
{
|
||||||
className="ticket-viewer__editable-dropdown"
|
this.state.editDepartment ?
|
||||||
departments={departments}
|
this.renderEditDepartment() :
|
||||||
selectedIndex={_.findIndex(departments, {id: this.props.ticket.department.id})}
|
ticket.department.name
|
||||||
onChange={this.onDepartmentDropdownChanged.bind(this)} />
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{userStaff ? this.renderEditOption("Department") : null}
|
||||||
|
</div>
|
||||||
<div className="ticket-viewer__info-container-editable">
|
<div className="ticket-viewer__info-container-editable">
|
||||||
|
<div className="ticket-viewer__info-container-editable__column">
|
||||||
<div className="ticket-viewer__info-header">{i18n('TAGS')}</div>
|
<div className="ticket-viewer__info-header">{i18n('TAGS')}</div>
|
||||||
<div className="ticket-viewer__info-value">
|
<div className="ticket-viewer__info-value">
|
||||||
<TagSelector
|
{
|
||||||
items={this.props.tags}
|
this.state.editTags ?
|
||||||
values={this.props.ticket.tags}
|
this.renderEditTags() :
|
||||||
onRemoveClick={this.removeTag.bind(this)}
|
this.renderTags()
|
||||||
onTagSelected={this.addTag.bind(this)}
|
}
|
||||||
loading={this.state.tagSelectorLoading}/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{userStaff ? this.renderEditOption("Tags") : null}
|
||||||
|
</div>
|
||||||
<div className="ticket-viewer__info-container-editable">
|
<div className="ticket-viewer__info-container-editable">
|
||||||
|
<div className="ticket-viewer__info-container-editable__column">
|
||||||
<div className="ticket-viewer__info-header">{i18n('OWNER')}</div>
|
<div className="ticket-viewer__info-header">{i18n('OWNER')}</div>
|
||||||
<div className="ticket-viewer__info-value">
|
<div className="ticket-viewer__info-value">
|
||||||
{this.renderAssignStaffList()}
|
{this.renderOwnerNode()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{userStaff ? this.renderEditOption("Owner") : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ticket-viewer__info">
|
<div className="ticket-viewer__info">
|
||||||
<div className="ticket-viewer__info-container-editable">
|
<div className="ticket-viewer__info-container-editable">
|
||||||
|
<div className="ticket-viewer__info-container-editable__column">
|
||||||
<div className="ticket-viewer__info-header">{i18n('AUTHOR')}</div>
|
<div className="ticket-viewer__info-header">{i18n('AUTHOR')}</div>
|
||||||
<div className="ticket-viewer__info-value">{ticket.author.name}</div>
|
<div className="ticket-viewer__info-value">
|
||||||
|
<a className="ticket-viewer__info-author-name" href={this.searchTickets(filtersOnlyWithAuthor)}>
|
||||||
|
{ticket.author.name}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ticket-viewer__info-container-editable">
|
<div className="ticket-viewer__info-container-editable">
|
||||||
|
<div className="ticket-viewer__info-container-editable__column">
|
||||||
<div className="ticket-viewer__info-header">{i18n('STATUS')}</div>
|
<div className="ticket-viewer__info-header">{i18n('STATUS')}</div>
|
||||||
<div className="ticket-viewer__info-value">
|
<div className="ticket-viewer__info-value">
|
||||||
{ticket.closed ?
|
{this.state.editStatus ? this.renderEditStatus() : (ticket.closed ? i18n('CLOSED') : i18n('OPENED'))}
|
||||||
<Button type='secondary' size="extra-small" onClick={this.onReopenClick.bind(this)}>
|
</div>
|
||||||
|
</div>
|
||||||
|
{userStaff ? this.renderEditOption("Status") : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderEditTags() {
|
||||||
|
const {
|
||||||
|
tags,
|
||||||
|
ticket
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ticket-viewer__edit-tags">
|
||||||
|
<TagSelector
|
||||||
|
items={tags}
|
||||||
|
values={ticket.tags}
|
||||||
|
onRemoveClick={this.removeTag.bind(this)}
|
||||||
|
onTagSelected={this.addTag.bind(this)}
|
||||||
|
loading={this.state.tagSelectorLoading} />
|
||||||
|
{this.renderCancelButton("Tags")}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderEditStatus() {
|
||||||
|
return (
|
||||||
|
<div className="ticket-viewer__edit-status__buttons">
|
||||||
|
{this.renderCancelButton("Status")}
|
||||||
|
{this.props.ticket.closed ?
|
||||||
|
<Button type='secondary' size="medium" onClick={this.onReopenClick.bind(this)}>
|
||||||
{i18n('RE_OPEN')}
|
{i18n('RE_OPEN')}
|
||||||
</Button> : i18n('OPENED')}
|
</Button> :
|
||||||
</div>
|
<Button type='secondary' size="medium" onClick={this.onCloseTicketClick.bind(this)}>
|
||||||
</div>
|
{i18n('CLOSE')}
|
||||||
</div>
|
</Button>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -229,10 +288,9 @@ class TicketViewer extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
<div className="ticket-viewer__info-container">
|
<div className="ticket-viewer__info-container">
|
||||||
<div className="ticket-viewer__info-header">{i18n('TAGS')}</div>
|
<div className="ticket-viewer__info-header">{i18n('TAGS')}</div>
|
||||||
<div className="ticket-viewer__info-value">{ticket.tags.length ? ticket.tags.map((tagName,index) => {
|
<div className="ticket-viewer__info-value">
|
||||||
let tag = _.find(this.props.tags, {name:tagName});
|
{this.renderTags()}
|
||||||
return <Tag name={tag && tag.name} color={tag && tag.color} key={index} />
|
</div>
|
||||||
}) : i18n('NONE')}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ticket-viewer__info">
|
<div className="ticket-viewer__info">
|
||||||
@ -245,7 +303,7 @@ class TicketViewer extends React.Component {
|
|||||||
<div className="ticket-viewer__info-container">
|
<div className="ticket-viewer__info-container">
|
||||||
<div className="ticket-viewer__info-header">{i18n('STATUS')}</div>
|
<div className="ticket-viewer__info-header">{i18n('STATUS')}</div>
|
||||||
<div className="ticket-viewer__info-value">
|
<div className="ticket-viewer__info-value">
|
||||||
{i18n((this.props.ticket.closed) ? 'CLOSED' : 'OPENED')}
|
{i18n((ticket.closed) ? 'CLOSED' : 'OPENED')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -253,53 +311,137 @@ class TicketViewer extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderTags() {
|
||||||
|
const {
|
||||||
|
ticket,
|
||||||
|
tags
|
||||||
|
} = this.props;
|
||||||
|
const TAGS = (
|
||||||
|
ticket.tags.length ?
|
||||||
|
ticket.tags.map((tagName, index) => {
|
||||||
|
const tag = _.find(tags, {name: tagName});
|
||||||
|
return <Tag name={tag && tag.name} color={tag && tag.color} key={index} />
|
||||||
|
}) :
|
||||||
|
i18n('NONE')
|
||||||
|
);
|
||||||
|
|
||||||
|
return TAGS;
|
||||||
|
}
|
||||||
|
|
||||||
renderOwnerNode() {
|
renderOwnerNode() {
|
||||||
|
const {
|
||||||
|
assignmentAllowed,
|
||||||
|
ticket
|
||||||
|
} = this.props;
|
||||||
|
const filtersOnlyWithOwner = ticket.owner && {owners: [ticket.owner.id*1]};
|
||||||
let ownerNode = null;
|
let ownerNode = null;
|
||||||
|
|
||||||
if (this.props.assignmentAllowed) {
|
if(assignmentAllowed && ticket.owner) {
|
||||||
ownerNode = this.renderAssignStaffList();
|
ownerNode = (
|
||||||
|
<a className="ticket-viewer__info-owner-name" href={this.searchTickets(filtersOnlyWithOwner)}>
|
||||||
|
{ticket.owner.name}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
ownerNode = (this.props.ticket.owner) ? this.props.ticket.owner.name : i18n('NONE')
|
ownerNode = (
|
||||||
|
<span className="ticket-viewer__info-owner-name">
|
||||||
|
{(ticket.owner) ? ticket.owner.name : i18n('NONE')}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ownerNode;
|
return (assignmentAllowed && this.state.editOwner) ? this.renderEditOwner() : ownerNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAssignStaffList() {
|
renderEditOwner() {
|
||||||
const items = this.getStaffAssignmentItems();
|
const items = this.getStaffAssignmentItems();
|
||||||
const ownerId = this.props.ticket.owner && this.props.ticket.owner.id*1;
|
const { ticket } = this.props;
|
||||||
|
const ownerId = ticket.owner && ticket.owner.id*1;
|
||||||
let selectedIndex = _.findIndex(items, {id: ownerId});
|
let selectedIndex = _.findIndex(items, {id: ownerId});
|
||||||
selectedIndex = (selectedIndex !== -1) ? selectedIndex : 0;
|
selectedIndex = (selectedIndex !== -1) ? selectedIndex : 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="ticket-viewer__edit-owner">
|
||||||
<DropDown
|
<DropDown
|
||||||
className="ticket-viewer__editable-dropdown" items={items}
|
className="ticket-viewer__editable-dropdown" items={items}
|
||||||
selectedIndex={selectedIndex}
|
selectedIndex={selectedIndex}
|
||||||
onChange={this.onAssignmentChange.bind(this)}
|
onChange={this.onAssignmentChange.bind(this)} />
|
||||||
/>
|
{this.renderCancelButton("Owner")}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderEditDepartment() {
|
||||||
|
const { ticket } = this.props;
|
||||||
|
const departments = this.getDepartmentsForTransfer();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ticket-viewer__edit-owner">
|
||||||
|
<DepartmentDropdown
|
||||||
|
className="ticket-viewer__editable-dropdown"
|
||||||
|
departments={departments}
|
||||||
|
selectedIndex={_.findIndex(departments, {id: ticket.department.id})}
|
||||||
|
onChange={this.onDepartmentDropdownChanged.bind(this)} />
|
||||||
|
{this.renderCancelButton("Department")}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderEditTitleOption() {
|
||||||
|
return(
|
||||||
|
<span className="ticket-viewer__edit-title-icon">
|
||||||
|
<Icon name="pencil" onClick={() => this.setState({editTitle: true})} />
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderEditOption(option) {
|
||||||
|
return(
|
||||||
|
<span className="ticket-viewer__edit-icon">
|
||||||
|
<Icon name="pencil" onClick={() => this.setState({["edit"+option]: true})} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderEditedTitleText(){
|
||||||
|
return(
|
||||||
|
<div className="ticket-viewer__edited-title-text"> {i18n('TITLE_EDITED')} </div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCancelButton(option) {
|
||||||
|
return <Button type='link' size="medium" onClick={() => this.setState({["edit"+option]: false})}>{i18n('CANCEL')}</Button>
|
||||||
|
}
|
||||||
|
|
||||||
renderTicketEvent(options, index) {
|
renderTicketEvent(options, index) {
|
||||||
if (this.props.userStaff && typeof options.content === 'string') {
|
const {
|
||||||
|
userStaff,
|
||||||
|
ticket,
|
||||||
|
userId,
|
||||||
|
allowAttachments
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if(userStaff && typeof options.content === 'string') {
|
||||||
options.content = MentionsParser.parse(options.content);
|
options.content = MentionsParser.parse(options.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TicketEvent
|
<TicketEvent
|
||||||
{...options}
|
{...options}
|
||||||
author={(!_.isEmpty(options.author)) ? options.author : this.props.ticket.author}
|
author={(!_.isEmpty(options.author)) ? options.author : ticket.author}
|
||||||
userStaff={this.props.userStaff}
|
userStaff={userStaff}
|
||||||
userId={this.props.userId}
|
userId={userId}
|
||||||
onEdit={this.onEdit.bind(this, options.id)}
|
onEdit={this.onEdit.bind(this, options.id)}
|
||||||
edit={this.state.edit && this.state.editId == options.id}
|
edit={this.state.edit && this.state.editId == options.id}
|
||||||
onToggleEdit={this.onToggleEdit.bind(this, options.id)}
|
onToggleEdit={this.onToggleEdit.bind(this, options.id)}
|
||||||
key={index}
|
key={index}
|
||||||
allowAttachments={this.props.allowAttachments}
|
allowAttachments={allowAttachments} />
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderResponseField() {
|
renderResponseField() {
|
||||||
|
const { allowAttachments } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ticket-viewer__response">
|
<div className="ticket-viewer__response">
|
||||||
<Form {...this.getCommentFormProps()}>
|
<Form {...this.getCommentFormProps()}>
|
||||||
@ -311,12 +453,11 @@ class TicketViewer extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ticket-viewer__response-field row">
|
<div className="ticket-viewer__response-field row">
|
||||||
<FormField name="content" validation="TEXT_AREA" required field="textarea" fieldProps={{allowImages: this.props.allowAttachments}}/>
|
<FormField name="content" validation="TEXT_AREA" required field="textarea" fieldProps={{allowImages: allowAttachments}} />
|
||||||
{(this.props.allowAttachments) ? <FormField name="file" field="file"/> : null}
|
{allowAttachments ? <FormField name="file" field="file" /> : null}
|
||||||
<div className="ticket-viewer__response-buttons">
|
<div className="ticket-viewer__response-buttons">
|
||||||
<SubmitButton type="secondary">{i18n('RESPOND_TICKET')}</SubmitButton>
|
<SubmitButton type="secondary">{i18n('RESPOND_TICKET')}</SubmitButton>
|
||||||
<div>
|
<div>
|
||||||
<Button size="medium" onClick={this.onCloseTicketClick.bind(this)}>{i18n('CLOSE_TICKET')}</Button>
|
|
||||||
{(this.showDeleteButton())? <Button className="ticket-viewer__delete-button" size="medium" onClick={this.onDeleteTicketClick.bind(this)}>{i18n('DELETE_TICKET')}</Button> : null}
|
{(this.showDeleteButton())? <Button className="ticket-viewer__delete-button" size="medium" onClick={this.onDeleteTicketClick.bind(this)}>{i18n('DELETE_TICKET')}</Button> : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -370,6 +511,13 @@ class TicketViewer extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
searchTickets(filters) {
|
||||||
|
const SEARCH_TICKETS_PATH = '/admin/panel/tickets/search-tickets';
|
||||||
|
const urlQuery = filters && searchTicketsUtils.getFiltersForURL({filters});
|
||||||
|
|
||||||
|
return urlQuery && `${SEARCH_TICKETS_PATH}${urlQuery}`;
|
||||||
|
}
|
||||||
|
|
||||||
getCommentFormProps() {
|
getCommentFormProps() {
|
||||||
return {
|
return {
|
||||||
onSubmit: this.onSubmit.bind(this),
|
onSubmit: this.onSubmit.bind(this),
|
||||||
@ -424,10 +572,18 @@ class TicketViewer extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onReopenClick() {
|
onReopenClick() {
|
||||||
|
this.setState({
|
||||||
|
editStatus: false
|
||||||
|
});
|
||||||
|
|
||||||
AreYouSure.openModal(null, this.reopenTicket.bind(this));
|
AreYouSure.openModal(null, this.reopenTicket.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
onCloseTicketClick(event) {
|
onCloseTicketClick(event) {
|
||||||
|
this.setState({
|
||||||
|
editStatus: false
|
||||||
|
});
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
AreYouSure.openModal(null, this.closeTicket.bind(this));
|
AreYouSure.openModal(null, this.closeTicket.bind(this));
|
||||||
}
|
}
|
||||||
@ -646,47 +802,77 @@ class TicketViewer extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onTicketModification() {
|
onTicketModification() {
|
||||||
if (this.props.onChange) {
|
const { onChange } = this.props;
|
||||||
this.props.onChange();
|
if(onChange) {
|
||||||
|
onChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getStaffAssignmentItems() {
|
getStaffAssignmentItems() {
|
||||||
const {staffMembers, userDepartments, userId, ticket} = this.props;
|
const {
|
||||||
|
userDepartments,
|
||||||
|
userId,
|
||||||
|
ticket
|
||||||
|
} = this.props;
|
||||||
const ticketDepartmentId = ticket.department.id;
|
const ticketDepartmentId = ticket.department.id;
|
||||||
|
const FIRST_ITEM = 0;
|
||||||
let staffAssignmentItems = [
|
let staffAssignmentItems = [
|
||||||
{content: 'None', id: 0}
|
{content: i18n('NONE'), contentOnSelected: i18n('NONE'), id: 0}
|
||||||
];
|
];
|
||||||
|
|
||||||
if(_.some(userDepartments, {id: ticketDepartmentId})) {
|
if(_.some(userDepartments, {id: ticketDepartmentId})) {
|
||||||
staffAssignmentItems.push({content: i18n('ASSIGN_TO_ME'), id: userId});
|
staffAssignmentItems.push({
|
||||||
|
content: i18n('ASSIGN_TO_ME'),
|
||||||
|
contentOnSelected: this.getStaffList({onlyMe: true})[FIRST_ITEM].name,
|
||||||
|
id: userId
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
staffAssignmentItems = staffAssignmentItems.concat(
|
staffAssignmentItems = staffAssignmentItems.concat(
|
||||||
_.map(
|
_.map(
|
||||||
_.filter(staffMembers, ({id, departments}) => {
|
this.getStaffList({onlyMe: false}),
|
||||||
return (id != userId) && _.some(departments, {id: ticketDepartmentId});
|
({id, name}) => ({content: name, contentOnSelected: name, id: id*1})
|
||||||
}),
|
|
||||||
({id, name}) => ({content: name, id: id*1})
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
return staffAssignmentItems;
|
return staffAssignmentItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getStaffList(onlyMeObject) {
|
||||||
|
const {
|
||||||
|
userId,
|
||||||
|
staffMembers,
|
||||||
|
ticket
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return _.filter(staffMembers, ({id, departments}) => {
|
||||||
|
const idComparer = onlyMeObject.onlyMe ? (id == userId) : (id != userId);
|
||||||
|
|
||||||
|
return idComparer && _.some(departments, {id: ticket.department.id});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
getDepartmentsForTransfer() {
|
getDepartmentsForTransfer() {
|
||||||
return this.props.ticket.author.staff ? SessionStore.getDepartments() : this.getPublicDepartments();
|
return this.props.ticket.author.staff ? SessionStore.getDepartments() : this.getPublicDepartments();
|
||||||
}
|
}
|
||||||
|
|
||||||
showDeleteButton() {
|
showDeleteButton() {
|
||||||
if(!this.props.ticket.owner) {
|
const {
|
||||||
if(this.props.userLevel === 3) return true;
|
ticket,
|
||||||
if(this.props.userId == this.props.ticket.author.id*1) {
|
userLevel,
|
||||||
if((this.props.userStaff && this.props.ticket.author.staff) || (!this.props.userStaff && !this.props.ticket.author.staff)){
|
userId,
|
||||||
|
userStaff
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if(!ticket.owner) {
|
||||||
|
if(userLevel === 3) return true;
|
||||||
|
if(userId == ticket.author.id*1) {
|
||||||
|
if((userStaff && ticket.author.staff) || (!userStaff && !ticket.author.staff)){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,15 +26,34 @@
|
|||||||
margin: 0 10px;
|
margin: 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__edit-title-icon {
|
&__edit-icon {
|
||||||
color: #414A59;
|
|
||||||
right: 12px;
|
right: 12px;
|
||||||
margin: 0 10px;
|
margin: 0 10px;
|
||||||
|
color: $light-grey;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__edit-title-icon {
|
||||||
|
color: $primary-blue;
|
||||||
|
right: 12px;
|
||||||
|
margin: 0 10px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor:pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__edit-status__buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
&___input-edit-title {
|
&___input-edit-title {
|
||||||
color: black;
|
color: black;
|
||||||
align-items:center;
|
align-items:center;
|
||||||
@ -86,11 +105,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-container-editable {
|
&-container-editable {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
min-width: 300px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.ticket-viewer__edit-icon {
|
||||||
|
color: $primary-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__column {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-width: 300px;
|
min-width: 170px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-header {
|
&-header {
|
||||||
@ -100,7 +133,7 @@
|
|||||||
&-value {
|
&-value {
|
||||||
color: $secondary-blue;
|
color: $secondary-blue;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,14 +7,14 @@
|
|||||||
&__drop-down {
|
&__drop-down {
|
||||||
.drop-down__current-item {
|
.drop-down__current-item {
|
||||||
cursor: text;
|
cursor: text;
|
||||||
background-color: white;
|
background-color: $very-light-grey;
|
||||||
border: 1px solid $grey;
|
border: 1px solid $grey;
|
||||||
min-height: 38px;
|
min-height: 38px;
|
||||||
|
|
||||||
&:focus-within {
|
&:focus-within {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: $primary-blue;
|
border-color: $primary-blue;
|
||||||
background-color: white;
|
background-color: $very-light-grey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ class DropDown extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderCurrentItem() {
|
renderCurrentItem() {
|
||||||
let item = this.props.items[this.getSelectedIndex()];
|
const item = this.props.items[this.getSelectedIndex()];
|
||||||
let iconNode = null;
|
let iconNode = null;
|
||||||
|
|
||||||
if (item.icon) {
|
if (item.icon) {
|
||||||
@ -98,7 +98,7 @@ class DropDown extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{iconNode}{item.content}
|
{iconNode}{item.contentOnSelected ? item.contentOnSelected : item.content}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user