mirror of
https://github.com/opensupports/opensupports.git
synced 2025-07-27 15:54:23 +02:00
Add Custom Fields feature
This commit is contained in:
parent
58c6f2e63f
commit
623a81b51d
@ -88,6 +88,7 @@ class TicketEvent extends React.Component {
|
|||||||
<span className="ticket-event__comment-badge-container">
|
<span className="ticket-event__comment-badge-container">
|
||||||
<span className="ticket-event__comment-badge">{i18n((this.props.author.staff) ? 'STAFF' : 'CUSTOMER')}</span>
|
<span className="ticket-event__comment-badge">{i18n((this.props.author.staff) ? 'STAFF' : 'CUSTOMER')}</span>
|
||||||
</span>
|
</span>
|
||||||
|
{this.props.author.customfields.map(this.renderCustomFieldValue.bind(this))}
|
||||||
{(this.props.private*1) ? this.renderPrivateBadge() : null}
|
{(this.props.private*1) ? this.renderPrivateBadge() : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="ticket-event__comment-date">{DateTransformer.transformToString(this.props.date)}</div>
|
<div className="ticket-event__comment-date">{DateTransformer.transformToString(this.props.date)}</div>
|
||||||
@ -198,7 +199,17 @@ class TicketEvent extends React.Component {
|
|||||||
<div className="ticket-event__file">
|
<div className="ticket-event__file">
|
||||||
{node}
|
{node}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCustomFieldValue(customField) {
|
||||||
|
return (
|
||||||
|
<span className="ticket-event__comment-badge-container">
|
||||||
|
<span className="ticket-event__comment-badge">
|
||||||
|
{customField.customfield}: <span className="ticket-event__comment-badge-value">{customField.value}</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getClass() {
|
getClass() {
|
||||||
|
@ -111,6 +111,10 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__comment-badge-value {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
&_staff {
|
&_staff {
|
||||||
.ticket-event__icon {
|
.ticket-event__icon {
|
||||||
background-color: $primary-blue;
|
background-color: $primary-blue;
|
||||||
|
@ -39,6 +39,7 @@ import AdminPanelCustomResponses from 'app/admin/panel/tickets/admin-panel-custo
|
|||||||
import AdminPanelListUsers from 'app/admin/panel/users/admin-panel-list-users';
|
import AdminPanelListUsers from 'app/admin/panel/users/admin-panel-list-users';
|
||||||
import AdminPanelViewUser from 'app/admin/panel/users/admin-panel-view-user';
|
import AdminPanelViewUser from 'app/admin/panel/users/admin-panel-view-user';
|
||||||
import AdminPanelBanUsers from 'app/admin/panel/users/admin-panel-ban-users';
|
import AdminPanelBanUsers from 'app/admin/panel/users/admin-panel-ban-users';
|
||||||
|
import AdminPanelCustomFields from 'app/admin/panel/users/admin-panel-custom-fields';
|
||||||
|
|
||||||
import AdminPanelListArticles from 'app/admin/panel/articles/admin-panel-list-articles';
|
import AdminPanelListArticles from 'app/admin/panel/articles/admin-panel-list-articles';
|
||||||
import AdminPanelViewArticle from 'app/admin/panel/articles/admin-panel-view-article';
|
import AdminPanelViewArticle from 'app/admin/panel/articles/admin-panel-view-article';
|
||||||
@ -120,6 +121,7 @@ export default (
|
|||||||
<Route path="list-users" component={AdminPanelListUsers} />
|
<Route path="list-users" component={AdminPanelListUsers} />
|
||||||
<Route path="view-user/:userId" component={AdminPanelViewUser} />
|
<Route path="view-user/:userId" component={AdminPanelViewUser} />
|
||||||
<Route path="ban-users" component={AdminPanelBanUsers} />
|
<Route path="ban-users" component={AdminPanelBanUsers} />
|
||||||
|
<Route path="custom-fields" component={AdminPanelCustomFields} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="articles">
|
<Route path="articles">
|
||||||
|
@ -153,6 +153,11 @@ class AdminPanelMenu extends React.Component {
|
|||||||
name: i18n('BAN_USERS'),
|
name: i18n('BAN_USERS'),
|
||||||
path: '/admin/panel/users/ban-users',
|
path: '/admin/panel/users/ban-users',
|
||||||
level: 1
|
level: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n('CUSTOM_FIELDS'),
|
||||||
|
path: '/admin/panel/users/custom-fields',
|
||||||
|
level: 1
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,128 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import i18n from 'lib-app/i18n';
|
||||||
|
import API from 'lib-app/api-call';
|
||||||
|
|
||||||
|
import Header from 'core-components/header';
|
||||||
|
import Button from 'core-components/button';
|
||||||
|
import SubmitButton from 'core-components/submit-button';
|
||||||
|
import Form from 'core-components/form';
|
||||||
|
import FormField from 'core-components/form-field';
|
||||||
|
import Message from 'core-components/message';
|
||||||
|
|
||||||
|
class AdminPanelCustomFieldForm extends React.Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
onClose: React.PropTypes.func,
|
||||||
|
onChange: React.PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
addForm: {},
|
||||||
|
addFormOptions: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="admin-panel-custom-field-form">
|
||||||
|
<Header title={i18n('NEW_CUSTOM_FIELD')} description={i18n('NEW_CUSTOM_FIELD_DESCRIPTION')} />
|
||||||
|
<div className="admin-panel-custom-field-form__form-container">
|
||||||
|
<Form
|
||||||
|
className="admin-panel-custom-field-form__form"
|
||||||
|
loading={this.state.loading}
|
||||||
|
values={this.state.addForm}
|
||||||
|
onChange={this.onAddFormChange.bind(this)}
|
||||||
|
onSubmit={this.onSubmit.bind(this)}>
|
||||||
|
<FormField name="name" validation="NAME" label={i18n('NAME')} field="input" fieldProps={{size: 'large'}} required/>
|
||||||
|
<FormField name="description" label={i18n('FIELD_DESCRIPTION')} field="input" fieldProps={{size: 'large'}}/>
|
||||||
|
<FormField name="type" label={i18n('TYPE')} field="select" fieldProps={{size: 'large', items: [{content: i18n('TEXT_INPUT')}, {content: i18n('SELECT_INPUT')}]}} required/>
|
||||||
|
{this.state.addForm.type ? this.renderOptionFormFields() : null}
|
||||||
|
{this.state.error ? this.renderErrorMessage() : null}
|
||||||
|
<div className="admin-panel-custom-field-form__buttons">
|
||||||
|
<SubmitButton>{i18n('SUBMIT')}</SubmitButton>
|
||||||
|
<Button onClick={this.props.onClose} type="link">{i18n('CLOSE')}</Button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderErrorMessage() {
|
||||||
|
return (
|
||||||
|
<Message type="error">
|
||||||
|
{this.state.error}
|
||||||
|
</Message>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderOptionFormFields() {
|
||||||
|
return (
|
||||||
|
<div className="admin-panel-custom-field-form__options">
|
||||||
|
<div className="admin-panel-custom-field-form__options-title">{i18n('OPTIONS')}</div>
|
||||||
|
{this.state.addFormOptions.map(this.renderFormOption.bind(this))}
|
||||||
|
<Button className="admin-panel-custom-field-form__option-add-button" iconName="plus" size="medium" type="secondary" onClick={this.onAddOptionClick.bind(this)} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFormOption(option, index) {
|
||||||
|
return (
|
||||||
|
<div key={index} className="admin-panel-custom-field-form__option">
|
||||||
|
<FormField className="admin-panel-custom-field-form__option-field" name={`option_${index}`} label={i18n('OPTION', {index: index+1})} type="input"/>
|
||||||
|
<Button className="admin-panel-custom-field-form__option-delete-button" size="medium" iconName="times" onClick={this.onDeleteOptionClick.bind(this, index)}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onAddOptionClick(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
let addFormOptions = _.clone(this.state.addFormOptions);
|
||||||
|
|
||||||
|
addFormOptions.push("");
|
||||||
|
|
||||||
|
this.setState({ addFormOptions });
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeleteOptionClick(index, event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
let addForm = _.clone(this.state.addForm);
|
||||||
|
let addFormOptions = this.state.addFormOptions.filter((option, idx) => idx != index);
|
||||||
|
|
||||||
|
Object.keys(addForm).forEach(key => _.includes(key, 'option_') ? delete addForm[key] : null);
|
||||||
|
addFormOptions.forEach((option, _index) => addForm[`option_${_index}`] = option);
|
||||||
|
|
||||||
|
this.setState({addForm, addFormOptions});
|
||||||
|
}
|
||||||
|
|
||||||
|
onAddFormChange(addForm) {
|
||||||
|
const addFormOptions = this.state.addFormOptions.map((option, index) => addForm[`option_${index}`]);
|
||||||
|
|
||||||
|
this.setState({addForm, addFormOptions});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(form) {
|
||||||
|
this.setState({loading: true, message: null});
|
||||||
|
API.call({
|
||||||
|
path: '/system/add-custom-field',
|
||||||
|
data: {
|
||||||
|
name: form.name,
|
||||||
|
description: form.description,
|
||||||
|
type: form.type ? 'select' : 'text',
|
||||||
|
options: form.type ? JSON.stringify(this.state.addFormOptions) : []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.setState({loading: false, message: null});
|
||||||
|
if(this.props.onChange) this.props.onChange();
|
||||||
|
})
|
||||||
|
.catch(result => this.setState({loading: false, error: result.message}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AdminPanelCustomFieldForm;
|
@ -0,0 +1,30 @@
|
|||||||
|
.admin-panel-custom-field-form {
|
||||||
|
min-width: 400px;
|
||||||
|
|
||||||
|
&__options {
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__option {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
|
||||||
|
&-add-button {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-delete-button {
|
||||||
|
margin-top: 7px;
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
108
client/src/app/admin/panel/users/admin-panel-custom-fields.js
Normal file
108
client/src/app/admin/panel/users/admin-panel-custom-fields.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import i18n from 'lib-app/i18n';
|
||||||
|
import API from 'lib-app/api-call';
|
||||||
|
|
||||||
|
import AdminPanelCustomFieldForm from 'app/admin/panel/users/admin-panel-custom-field-form';
|
||||||
|
import ModalContainer from 'app-components/modal-container';
|
||||||
|
import AreYouSure from 'app-components/are-you-sure';
|
||||||
|
|
||||||
|
import Header from 'core-components/header';
|
||||||
|
import Button from 'core-components/button';
|
||||||
|
import Icon from 'core-components/icon';
|
||||||
|
import InfoTooltip from 'core-components/info-tooltip';
|
||||||
|
import Table from 'core-components/table';
|
||||||
|
|
||||||
|
class AdminPanelCustomFields extends React.Component {
|
||||||
|
|
||||||
|
state = {
|
||||||
|
customFields: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.retrieveCustomFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="admin-panel-custom-fields">
|
||||||
|
<Header title={i18n('CUSTOM_FIELDS')} description={i18n('CUSTOM_FIELDS_DESCRIPTION')} />
|
||||||
|
{this.renderCustomFieldList()}
|
||||||
|
<div className="admin-panel-custom-fields__add-button">
|
||||||
|
<Button type="secondary" onClick={this.onNewCustomFieldClick.bind(this)}>
|
||||||
|
<Icon name="plus"/> {i18n('NEW_CUSTOM_FIELD')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCustomFieldList() {
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
className="admin-panel-custom-fields__list"
|
||||||
|
headers={[
|
||||||
|
{key: 'name', value: 'Name'},
|
||||||
|
{key: 'type', value: 'Type'},
|
||||||
|
{key: 'options', value: 'Options'},
|
||||||
|
{key: 'actions', value: ''},
|
||||||
|
]}
|
||||||
|
rows={this.state.customFields.map(this.getCustomField.bind(this))}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCustomField(customField, index) {
|
||||||
|
const {id, description, name, type, options} = customField;
|
||||||
|
let descriptionInfoTooltip = null;
|
||||||
|
|
||||||
|
if(description) {
|
||||||
|
descriptionInfoTooltip = <InfoTooltip text={description} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: <div>{name} {descriptionInfoTooltip}</div>,
|
||||||
|
type,
|
||||||
|
options: JSON.stringify(options.map(option => option.name)),
|
||||||
|
actions: <Button size="medium" onClick={this.onDeleteCustomField.bind(this, id)}>Remove</Button>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onNewCustomFieldClick() {
|
||||||
|
ModalContainer.openModal(
|
||||||
|
<AdminPanelCustomFieldForm
|
||||||
|
onClose={ModalContainer.closeModal}
|
||||||
|
onChange={() => {
|
||||||
|
this.retrieveCustomFields();
|
||||||
|
ModalContainer.closeModal();
|
||||||
|
}}/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeleteCustomField(id) {
|
||||||
|
AreYouSure.openModal(i18n('DELETE_CUSTOM_FIELD_SURE'), this.deleteCustomField.bind(this, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteCustomField(id) {
|
||||||
|
API.call({
|
||||||
|
path: '/system/delete-custom-field',
|
||||||
|
data: {id}
|
||||||
|
})
|
||||||
|
.catch(() => this.setState({}))
|
||||||
|
.then(() => this.retrieveCustomFields());
|
||||||
|
}
|
||||||
|
|
||||||
|
retrieveCustomFields() {
|
||||||
|
API.call({
|
||||||
|
path: '/system/get-custom-fields',
|
||||||
|
data: {}
|
||||||
|
})
|
||||||
|
.catch(() => this.setState({}))
|
||||||
|
.then(result => this.setState({
|
||||||
|
customFields: result.data
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AdminPanelCustomFields;
|
@ -0,0 +1,11 @@
|
|||||||
|
.admin-panel-custom-fields {
|
||||||
|
|
||||||
|
&__list {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__add-button {
|
||||||
|
text-align: left;
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@ class AdminPanelViewUser extends React.Component {
|
|||||||
email: '',
|
email: '',
|
||||||
verified: true,
|
verified: true,
|
||||||
tickets: [],
|
tickets: [],
|
||||||
|
customfields: [],
|
||||||
invalid: false,
|
invalid: false,
|
||||||
loading: true,
|
loading: true,
|
||||||
disabled: false
|
disabled: false
|
||||||
@ -64,6 +65,7 @@ class AdminPanelViewUser extends React.Component {
|
|||||||
{(!this.state.verified) ? this.renderNotVerified() : null}
|
{(!this.state.verified) ? this.renderNotVerified() : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{this.state.customfields.map(this.renderCustomField.bind(this))}
|
||||||
<div className="admin-panel-view-user__action-buttons">
|
<div className="admin-panel-view-user__action-buttons">
|
||||||
<Button
|
<Button
|
||||||
className="admin-panel-view-user__action-button"
|
className="admin-panel-view-user__action-button"
|
||||||
@ -98,6 +100,17 @@ class AdminPanelViewUser extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderCustomField(customfield) {
|
||||||
|
return (
|
||||||
|
<div className="admin-panel-view-user__info-item">
|
||||||
|
{customfield.customfield}
|
||||||
|
<div className="admin-panel-view-user__info-box">
|
||||||
|
{customfield.value}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getTicketListProps() {
|
getTicketListProps() {
|
||||||
return {
|
return {
|
||||||
type: 'secondary',
|
type: 'secondary',
|
||||||
@ -115,6 +128,7 @@ class AdminPanelViewUser extends React.Component {
|
|||||||
verified: result.data.verified,
|
verified: result.data.verified,
|
||||||
tickets: result.data.tickets,
|
tickets: result.data.tickets,
|
||||||
disabled: result.data.disabled,
|
disabled: result.data.disabled,
|
||||||
|
customfields: result.data.customfields,
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
import API from 'lib-app/api-call';
|
import API from 'lib-app/api-call';
|
||||||
import i18n from 'lib-app/i18n';
|
import i18n from 'lib-app/i18n';
|
||||||
|
|
||||||
|
import SessionActions from 'actions/session-actions';
|
||||||
import AreYouSure from 'app-components/are-you-sure';
|
import AreYouSure from 'app-components/are-you-sure';
|
||||||
|
|
||||||
import Header from 'core-components/header';
|
import Header from 'core-components/header';
|
||||||
@ -13,17 +16,41 @@ import Message from 'core-components/message';
|
|||||||
|
|
||||||
class DashboardEditProfilePage extends React.Component {
|
class DashboardEditProfilePage extends React.Component {
|
||||||
|
|
||||||
state= {
|
static propTypes = {
|
||||||
|
userCustomFields: React.PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
userCustomFields: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
loadingEmail: false,
|
loadingEmail: false,
|
||||||
loadingPass: false,
|
loadingPass: false,
|
||||||
messageEmail:'',
|
messageEmail: '',
|
||||||
messagePass:''
|
messagePass: '',
|
||||||
|
customFields: [],
|
||||||
|
customFieldsFrom: {},
|
||||||
|
loadingCustomFields: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.retrieveCustomFields();
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="edit-profile-page">
|
<div className="edit-profile-page">
|
||||||
<Header title={i18n('EDIT_PROFILE')} description={i18n('EDIT_PROFILE_VIEW_DESCRIPTION')} />
|
<Header title={i18n('EDIT_PROFILE')} description={i18n('EDIT_PROFILE_VIEW_DESCRIPTION')} />
|
||||||
|
<div className="edit-profile-page__title">{i18n('ADDITIONAL_FIELDS')}</div>
|
||||||
|
<Form loading={this.state.loadingCustomFields} values={this.state.customFieldsFrom} onChange={form => this.setState({customFieldsFrom: form})} onSubmit={this.onCustomFieldsSubmit.bind(this)}>
|
||||||
|
<div className="edit-profile-page__custom-fields">
|
||||||
|
{this.state.customFields.map(this.renderCustomField.bind(this))}
|
||||||
|
</div>
|
||||||
|
<div className="row">
|
||||||
|
<SubmitButton>{i18n('SAVE')}</SubmitButton>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
<div className="edit-profile-page__title">{i18n('EDIT_EMAIL')}</div>
|
<div className="edit-profile-page__title">{i18n('EDIT_EMAIL')}</div>
|
||||||
<Form loading={this.state.loadingEmail} onSubmit={this.onSubmitEditEmail.bind(this)}>
|
<Form loading={this.state.loadingEmail} onSubmit={this.onSubmitEditEmail.bind(this)}>
|
||||||
<FormField name="newEmail" label={i18n('NEW_EMAIL')} field="input" validation="EMAIL" fieldProps={{size:'large'}} required/>
|
<FormField name="newEmail" label={i18n('NEW_EMAIL')} field="input" validation="EMAIL" fieldProps={{size:'large'}} required/>
|
||||||
@ -41,6 +68,25 @@ class DashboardEditProfilePage extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderCustomField(customField, key) {
|
||||||
|
if(customField.type === 'text') {
|
||||||
|
return (
|
||||||
|
<div className="edit-profile-page__custom-field" key={key}>
|
||||||
|
<FormField name={customField.name} label={customField.name} infoMessage={customField.description} field="input" fieldProps={{size:'small'}}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const items = customField.options.map(option => ({content: option.name, value: option.name}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="edit-profile-page__custom-field" key={key}>
|
||||||
|
<FormField name={customField.name} label={customField.name} infoMessage={customField.description} field="select" fieldProps={{size:'small', items}}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
renderMessageEmail() {
|
renderMessageEmail() {
|
||||||
switch (this.state.messageEmail) {
|
switch (this.state.messageEmail) {
|
||||||
case 'success':
|
case 'success':
|
||||||
@ -52,6 +98,7 @@ class DashboardEditProfilePage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMessagePass() {
|
renderMessagePass() {
|
||||||
switch (this.state.messagePass) {
|
switch (this.state.messagePass) {
|
||||||
case 'success':
|
case 'success':
|
||||||
@ -62,6 +109,33 @@ class DashboardEditProfilePage extends React.Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCustomFieldsSubmit(form) {
|
||||||
|
const {customFields} = this.state;
|
||||||
|
const parsedFrom = {}
|
||||||
|
|
||||||
|
customFields.forEach(customField => {
|
||||||
|
if(customField.type === 'select') {
|
||||||
|
parsedFrom[`customfield_${customField.name}`] = customField.options[form[customField.name]].name;
|
||||||
|
} else {
|
||||||
|
parsedFrom[`customfield_${customField.name}`] = form[customField.name];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
loadingCustomFields: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
API.call({
|
||||||
|
path: '/user/edit-custom-fields',
|
||||||
|
data: parsedFrom
|
||||||
|
}).then(() => {
|
||||||
|
this.setState({loadingCustomFields: false});
|
||||||
|
this.props.dispatch(SessionActions.getUserData());
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
onSubmitEditEmail(formState) {
|
onSubmitEditEmail(formState) {
|
||||||
AreYouSure.openModal(i18n('EMAIL_WILL_CHANGE'), this.callEditEmailAPI.bind(this, formState));
|
AreYouSure.openModal(i18n('EMAIL_WILL_CHANGE'), this.callEditEmailAPI.bind(this, formState));
|
||||||
}
|
}
|
||||||
@ -115,6 +189,39 @@ class DashboardEditProfilePage extends React.Component {
|
|||||||
}.bind(this));
|
}.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
retrieveCustomFields() {
|
||||||
|
API.call({
|
||||||
|
path: '/system/get-custom-fields',
|
||||||
|
data: {}
|
||||||
|
})
|
||||||
|
.then(result => {
|
||||||
|
const customFieldsFrom = {};
|
||||||
|
const {userCustomFields} = this.props;
|
||||||
|
result.data.forEach(customField => {
|
||||||
|
if(customField.type === 'select') {
|
||||||
|
const index = _.indexOf(customField.options.map(option => option.name), userCustomFields[customField.name]);
|
||||||
|
customFieldsFrom[customField.name] = (index === -1 ? 0 : index);
|
||||||
|
} else {
|
||||||
|
customFieldsFrom[customField.name] = userCustomFields[customField.name] || '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
customFields: result.data,
|
||||||
|
customFieldsFrom,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DashboardEditProfilePage;
|
export default connect((store) => {
|
||||||
|
const userCustomFields = {};
|
||||||
|
|
||||||
|
store.session.userCustomFields.forEach(customField => {
|
||||||
|
userCustomFields[customField.customfield] = customField.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
userCustomFields: userCustomFields || {},
|
||||||
|
};
|
||||||
|
})(DashboardEditProfilePage);
|
||||||
|
@ -13,4 +13,13 @@
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__custom-fields {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__custom-field {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
}
|
}
|
@ -16,18 +16,28 @@ import Header from 'core-components/header';
|
|||||||
|
|
||||||
class MainSignUpWidget extends React.Component {
|
class MainSignUpWidget extends React.Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
onSuccess: React.PropTypes.func,
|
||||||
|
className: React.PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
loading: false,
|
loading: false,
|
||||||
email: null
|
email: null,
|
||||||
|
customFields: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
static propTypes = {
|
|
||||||
onSuccess: React.PropTypes.func,
|
componentDidMount() {
|
||||||
className: React.PropTypes.string
|
API.call({
|
||||||
};
|
path: '/system/get-custom-fields',
|
||||||
|
data: {}
|
||||||
|
})
|
||||||
|
.then(result => this.setState({customFields: result.data}));
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
@ -39,6 +49,7 @@ class MainSignUpWidget extends React.Component {
|
|||||||
<FormField {...this.getInputProps()} label={i18n('EMAIL')} name="email" validation="EMAIL" required/>
|
<FormField {...this.getInputProps()} label={i18n('EMAIL')} name="email" validation="EMAIL" required/>
|
||||||
<FormField {...this.getInputProps(true)} label={i18n('PASSWORD')} name="password" validation="PASSWORD" required/>
|
<FormField {...this.getInputProps(true)} label={i18n('PASSWORD')} name="password" validation="PASSWORD" required/>
|
||||||
<FormField {...this.getInputProps(true)} label={i18n('REPEAT_PASSWORD')} name="repeated-password" validation="REPEAT_PASSWORD" required/>
|
<FormField {...this.getInputProps(true)} label={i18n('REPEAT_PASSWORD')} name="repeated-password" validation="REPEAT_PASSWORD" required/>
|
||||||
|
{this.state.customFields.map(this.renderCustomField.bind(this))}
|
||||||
</div>
|
</div>
|
||||||
<div className="signup-widget__captcha">
|
<div className="signup-widget__captcha">
|
||||||
<Captcha ref="captcha"/>
|
<Captcha ref="captcha"/>
|
||||||
@ -51,6 +62,31 @@ class MainSignUpWidget extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderCustomField(customField, key) {
|
||||||
|
if(customField.type === 'text') {
|
||||||
|
return (
|
||||||
|
<FormField {...this.getInputProps()}
|
||||||
|
name={`customfield_${customField.name}`}
|
||||||
|
key={key}
|
||||||
|
label={customField.name}
|
||||||
|
infoMessage={customField.description}
|
||||||
|
field="input"/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const items = customField.options.map(option => ({content: option.name, value: option.name}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
name={`customfield_${customField.name}`}
|
||||||
|
key={key}
|
||||||
|
label={customField.name}
|
||||||
|
infoMessage={customField.description}
|
||||||
|
field="select"
|
||||||
|
fieldProps={{size:'medium', items}}/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
renderMessage() {
|
renderMessage() {
|
||||||
switch (this.state.message) {
|
switch (this.state.message) {
|
||||||
case 'success':
|
case 'success':
|
||||||
@ -98,9 +134,17 @@ class MainSignUpWidget extends React.Component {
|
|||||||
loading: true
|
loading: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const form = _.clone(formState);
|
||||||
|
|
||||||
|
this.state.customFields.forEach(customField => {
|
||||||
|
if(customField.type === 'select') {
|
||||||
|
form[`customfield_${customField.name}`] = customField.options[form[`customfield_${customField.name}`]].name;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
API.call({
|
API.call({
|
||||||
path: '/user/signup',
|
path: '/user/signup',
|
||||||
data: _.extend({captcha: captcha.getValue()}, formState)
|
data: _.extend({captcha: captcha.getValue()}, form)
|
||||||
}).then(this.onSignupSuccess.bind(this)).catch(this.onSignupFail.bind(this));
|
}).then(this.onSignupSuccess.bind(this)).catch(this.onSignupFail.bind(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ export default {
|
|||||||
'HIGH': 'Hoch',
|
'HIGH': 'Hoch',
|
||||||
'MEDIUM': 'Mittel',
|
'MEDIUM': 'Mittel',
|
||||||
'LOW': 'Niedrig',
|
'LOW': 'Niedrig',
|
||||||
'TITLE': 'Titel',
|
'TITLE': 'Betreff',
|
||||||
'CONTENT': 'Inhalt',
|
'CONTENT': 'Inhalt',
|
||||||
'SAVE': 'Speichern',
|
'SAVE': 'Speichern',
|
||||||
'DISCARD_CHANGES': 'Änderungen verwerfen',
|
'DISCARD_CHANGES': 'Änderungen verwerfen',
|
||||||
|
@ -198,6 +198,15 @@ export default {
|
|||||||
'IMAGE_HEADER_URL': 'Image header URL',
|
'IMAGE_HEADER_URL': 'Image header URL',
|
||||||
'IMAGE_HEADER_DESCRIPTION': 'Image that will be used as header of the email',
|
'IMAGE_HEADER_DESCRIPTION': 'Image that will be used as header of the email',
|
||||||
'EMAIL_SETTINGS': 'Email Settings',
|
'EMAIL_SETTINGS': 'Email Settings',
|
||||||
|
'ADDITIONAL_FIELDS': 'Additonal Fields',
|
||||||
|
'NEW_CUSTOM_FIELD': 'New Custom field',
|
||||||
|
'TYPE': 'Type',
|
||||||
|
'SELECT_INPUT': 'Select input',
|
||||||
|
'TEXT_INPUT': 'Text input',
|
||||||
|
'OPTION': 'Option {index}',
|
||||||
|
'OPTIONS': 'Options',
|
||||||
|
'FIELD_DESCRIPTION': 'Field description (Optional)',
|
||||||
|
'CUSTOM_FIELDS': 'Custom fields',
|
||||||
|
|
||||||
'CHART_CREATE_TICKET': 'Tickets created',
|
'CHART_CREATE_TICKET': 'Tickets created',
|
||||||
'CHART_CLOSE': 'Tickets closed',
|
'CHART_CLOSE': 'Tickets closed',
|
||||||
@ -318,6 +327,8 @@ export default {
|
|||||||
'PRIVATE_DEPARTMENT_DESCRIPTION': 'This department will only be seen by staff members',
|
'PRIVATE_DEPARTMENT_DESCRIPTION': 'This department will only be seen by staff members',
|
||||||
'EMAIL_SETTINGS_DESCRIPTION': 'Here you can edit the settings for receiving and sending email to your customers.',
|
'EMAIL_SETTINGS_DESCRIPTION': 'Here you can edit the settings for receiving and sending email to your customers.',
|
||||||
'IMAP_POLLING_DESCRIPTION': 'Inbox checking will not be done automatically by OpenSupports. You have to make POST requests periodically to this url to process the emails: {url}',
|
'IMAP_POLLING_DESCRIPTION': 'Inbox checking will not be done automatically by OpenSupports. You have to make POST requests periodically to this url to process the emails: {url}',
|
||||||
|
'NEW_CUSTOM_FIELD_DESCRIPTION': 'Here you can create a custom field for an user, it can be a blank text box or a fixed set of options.',
|
||||||
|
'CUSTOM_FIELDS_DESCRIPTION': 'Custom fields are defined additional fields the users are able to fill to provide more information about them.',
|
||||||
|
|
||||||
//ERRORS
|
//ERRORS
|
||||||
'EMAIL_OR_PASSWORD': 'Email or password invalid',
|
'EMAIL_OR_PASSWORD': 'Email or password invalid',
|
||||||
@ -378,6 +389,7 @@ export default {
|
|||||||
'SUCCESSFUL_CONNECTION': 'Successful connection',
|
'SUCCESSFUL_CONNECTION': 'Successful connection',
|
||||||
'UNSUCCESSFUL_CONNECTION': 'Unsuccessful connection',
|
'UNSUCCESSFUL_CONNECTION': 'Unsuccessful connection',
|
||||||
'SERVER_CREDENTIALS_WORKING': 'Server credentials are working correctly',
|
'SERVER_CREDENTIALS_WORKING': 'Server credentials are working correctly',
|
||||||
|
'DELETE_CUSTOM_FIELD_SURE': 'Some users may be using this field. Are you sure you want to delete it?',
|
||||||
|
|
||||||
'LAST_7_DAYS': 'Last 7 days',
|
'LAST_7_DAYS': 'Last 7 days',
|
||||||
'LAST_30_DAYS': 'Last 30 days',
|
'LAST_30_DAYS': 'Last 30 days',
|
||||||
|
@ -113,7 +113,8 @@ class SessionReducer extends Reducer {
|
|||||||
userLevel: userData.level,
|
userLevel: userData.level,
|
||||||
userDepartments: userData.departments,
|
userDepartments: userData.departments,
|
||||||
userTickets: userData.tickets,
|
userTickets: userData.tickets,
|
||||||
userSendEmailOnNewTicket: userData.sendEmailOnNewTicket * 1
|
userSendEmailOnNewTicket: userData.sendEmailOnNewTicket * 1,
|
||||||
|
userCustomFields: userData.customfields
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +133,8 @@ class SessionReducer extends Reducer {
|
|||||||
userDepartments: userData.departments,
|
userDepartments: userData.departments,
|
||||||
userTickets: userData.tickets,
|
userTickets: userData.tickets,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
userSendEmailOnNewTicket: userData.sendEmailOnNewTicket * 1
|
userSendEmailOnNewTicket: userData.sendEmailOnNewTicket * 1,
|
||||||
|
userCustomFields: userData.customfields
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ class GetStaffController extends Controller {
|
|||||||
'level' => $user->level,
|
'level' => $user->level,
|
||||||
'staff' => true,
|
'staff' => true,
|
||||||
'departments' => $parsedDepartmentList,
|
'departments' => $parsedDepartmentList,
|
||||||
'tickets' => $user->sharedTicketList->toArray(),
|
'tickets' => $user->sharedTicketList->toArray(true),
|
||||||
'sendEmailOnNewTicket' => $user->sendEmailOnNewTicket
|
'sendEmailOnNewTicket' => $user->sendEmailOnNewTicket
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -32,5 +32,8 @@ $systemControllerGroup->addController(new EnableUserSystemController);
|
|||||||
$systemControllerGroup->addController(new TestSMTPController);
|
$systemControllerGroup->addController(new TestSMTPController);
|
||||||
$systemControllerGroup->addController(new TestIMAPController);
|
$systemControllerGroup->addController(new TestIMAPController);
|
||||||
$systemControllerGroup->addController(new EmailPollingController);
|
$systemControllerGroup->addController(new EmailPollingController);
|
||||||
|
$systemControllerGroup->addController(new AddCustomFieldController);
|
||||||
|
$systemControllerGroup->addController(new DeleteCustomFieldController);
|
||||||
|
$systemControllerGroup->addController(new GetCustomFieldsController);
|
||||||
|
|
||||||
$systemControllerGroup->finalize();
|
$systemControllerGroup->finalize();
|
||||||
|
98
server/controllers/system/add-custom-field.php
Normal file
98
server/controllers/system/add-custom-field.php
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<?php
|
||||||
|
use Respect\Validation\Validator as DataValidator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /system/add-custom-field Add a custom field
|
||||||
|
* @apiVersion 4.4.0
|
||||||
|
*
|
||||||
|
* @apiName Add Custom field
|
||||||
|
*
|
||||||
|
* @apiGroup System
|
||||||
|
*
|
||||||
|
* @apiDescription This path creates a Custom field.
|
||||||
|
*
|
||||||
|
* @apiPermission staff2
|
||||||
|
*
|
||||||
|
* @apiParam {Number} name Name of the custom field.
|
||||||
|
* @apiParam {String} type One of 'text' and 'select'.
|
||||||
|
* @apiParam {String} options JSON array of strings with the option names.
|
||||||
|
*
|
||||||
|
* @apiUse NO_PERMISSION
|
||||||
|
* @apiUse INVALID_NAME
|
||||||
|
* @apiUse INVALID_CUSTOM_FIELD_TYPE
|
||||||
|
* @apiUse INVALID_CUSTOM_FIELD_OPTIONS
|
||||||
|
* @apiUse CUSTOM_FIELD_ALREADY_EXISTS
|
||||||
|
*
|
||||||
|
* @apiSuccess {Object} data Empty object
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class AddCustomFieldController extends Controller {
|
||||||
|
const PATH = '/add-custom-field';
|
||||||
|
const METHOD = 'POST';
|
||||||
|
|
||||||
|
public function validations() {
|
||||||
|
return [
|
||||||
|
'permission' => 'staff_2',
|
||||||
|
'requestData' => [
|
||||||
|
'name' => [
|
||||||
|
'validation' => DataValidator::length(2, 100),
|
||||||
|
'error' => ERRORS::INVALID_NAME
|
||||||
|
],
|
||||||
|
'type' => [
|
||||||
|
'validation' => DataValidator::oneOf(
|
||||||
|
DataValidator::equals('text'),
|
||||||
|
DataValidator::equals('select')
|
||||||
|
),
|
||||||
|
'error' => ERRORS::INVALID_CUSTOM_FIELD_TYPE
|
||||||
|
],
|
||||||
|
'options' => [
|
||||||
|
'validation' => DataValidator::oneOf(
|
||||||
|
DataValidator::json(),
|
||||||
|
DataValidator::nullType()
|
||||||
|
),
|
||||||
|
'error' => ERRORS::INVALID_CUSTOM_FIELD_OPTIONS
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handler() {
|
||||||
|
$name = Controller::request('name');
|
||||||
|
$type = Controller::request('type');
|
||||||
|
$description = Controller::request('description');
|
||||||
|
$options = Controller::request('options');
|
||||||
|
|
||||||
|
if(!Customfield::getDataStore($name, 'name')->isNull())
|
||||||
|
throw new Exception(ERRORS::CUSTOM_FIELD_ALREADY_EXISTS);
|
||||||
|
|
||||||
|
$customField = new Customfield();
|
||||||
|
$customField->setProperties([
|
||||||
|
'name' => $name,
|
||||||
|
'type' => $type,
|
||||||
|
'description' => $description,
|
||||||
|
'ownCustomfieldoptionList' => $this->getOptionList($options)
|
||||||
|
]);
|
||||||
|
|
||||||
|
$customField->store();
|
||||||
|
|
||||||
|
Response::respondSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOptionList($optionNames) {
|
||||||
|
$options = new DataStoreList();
|
||||||
|
if(!$optionNames) return $options;
|
||||||
|
|
||||||
|
$optionNames = json_decode($optionNames);
|
||||||
|
|
||||||
|
foreach($optionNames as $optionName) {
|
||||||
|
$option = new Customfieldoption();
|
||||||
|
$option->setProperties([
|
||||||
|
'name' => $optionName,
|
||||||
|
]);
|
||||||
|
$options->add($option);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
}
|
56
server/controllers/system/delete-custom-field.php
Normal file
56
server/controllers/system/delete-custom-field.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
use Respect\Validation\Validator as DataValidator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /system/delete-custom-field Delete custom field
|
||||||
|
* @apiVersion 4.4.0
|
||||||
|
*
|
||||||
|
* @apiName Delete a custom field
|
||||||
|
*
|
||||||
|
* @apiGroup System
|
||||||
|
*
|
||||||
|
* @apiDescription This path deletes a custom field and all its uses.
|
||||||
|
*
|
||||||
|
* @apiPermission staff2
|
||||||
|
*
|
||||||
|
* @apiParam {Number} id Id of the custom field to delete.
|
||||||
|
*
|
||||||
|
* @apiUse NO_PERMISSION
|
||||||
|
* @apiUse INVALID_CUSTOM_FIELD
|
||||||
|
*
|
||||||
|
* @apiSuccess {Object} data Empty object
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class DeleteCustomFieldController extends Controller {
|
||||||
|
const PATH = '/delete-custom-field';
|
||||||
|
const METHOD = 'POST';
|
||||||
|
|
||||||
|
public function validations() {
|
||||||
|
return [
|
||||||
|
'permission' => 'staff_2',
|
||||||
|
'requestData' => [
|
||||||
|
'id' => [
|
||||||
|
'validation' => DataValidator::dataStoreId('customfield'),
|
||||||
|
'error' => ERRORS::INVALID_CUSTOM_FIELD,
|
||||||
|
],
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handler() {
|
||||||
|
$customField = Customfield::getDataStore(Controller::request('id'));
|
||||||
|
|
||||||
|
foreach(Users::getAll() as $user) {
|
||||||
|
$customFieldValueList = $user->xownCustomfieldvalueList || [];
|
||||||
|
|
||||||
|
foreach($customFieldValueList as $customFieldValue) {
|
||||||
|
if($customFieldValue->customfield->id == $customField->id) {
|
||||||
|
$user->xownCustomfieldvalueList->remove($customFieldValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$customField->delete();
|
||||||
|
}
|
||||||
|
}
|
38
server/controllers/system/get-custom-fields.php
Normal file
38
server/controllers/system/get-custom-fields.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
use Respect\Validation\Validator as DataValidator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /system/get-custom-fields Get custom fields
|
||||||
|
* @apiVersion 4.4.0
|
||||||
|
*
|
||||||
|
* @apiName Get all Custom field items
|
||||||
|
*
|
||||||
|
* @apiGroup System
|
||||||
|
*
|
||||||
|
* @apiDescription This path retrieves the all CustomField items.
|
||||||
|
*
|
||||||
|
* @apiPermission any
|
||||||
|
*
|
||||||
|
* @apiUse NO_PERMISSION
|
||||||
|
*
|
||||||
|
* @apiSuccess {[Customfield](#api-Data_Structures-ObjectCustomfield)[]} data Array of Customfield
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class GetCustomFieldsController extends Controller {
|
||||||
|
const PATH = '/get-custom-fields';
|
||||||
|
const METHOD = 'POST';
|
||||||
|
|
||||||
|
public function validations() {
|
||||||
|
return [
|
||||||
|
'permission' => 'any',
|
||||||
|
'requestData' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handler() {
|
||||||
|
$customFieldList = Customfield::getAll();
|
||||||
|
|
||||||
|
Response::respondSuccess($customFieldList->toArray());
|
||||||
|
}
|
||||||
|
}
|
@ -20,4 +20,6 @@ $userControllers->addController(new ListBanUserController);
|
|||||||
$userControllers->addController(new VerifyController);
|
$userControllers->addController(new VerifyController);
|
||||||
$userControllers->addController(new EnableUserController);
|
$userControllers->addController(new EnableUserController);
|
||||||
$userControllers->addController(new DisableUserController);
|
$userControllers->addController(new DisableUserController);
|
||||||
|
$userControllers->addController(new EditCustomFieldsController);
|
||||||
|
|
||||||
$userControllers->finalize();
|
$userControllers->finalize();
|
||||||
|
55
server/controllers/user/edit-custom-fields.php
Normal file
55
server/controllers/user/edit-custom-fields.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
use Respect\Validation\Validator as DataValidator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /user/edit-custom-fields Edit custom field values
|
||||||
|
* @apiVersion 4.4.0
|
||||||
|
*
|
||||||
|
* @apiName Edit custom field values
|
||||||
|
*
|
||||||
|
* @apiGroup User
|
||||||
|
*
|
||||||
|
* @apiDescription This path is for editing the custom fields of a user.
|
||||||
|
*
|
||||||
|
* @apiPermission user
|
||||||
|
*
|
||||||
|
* @apiParam {String} userId Id of the user if it is not the one logged. Optional.
|
||||||
|
* @apiParam {String} customfield_ Custom field values for this user.
|
||||||
|
*
|
||||||
|
* @apiUse NO_PERMISSION
|
||||||
|
* @apiUse INVALID_CUSTOM_FIELD_OPTION
|
||||||
|
*
|
||||||
|
* @apiSuccess {Object} data Empty object
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class EditCustomFieldsController extends Controller {
|
||||||
|
const PATH = '/edit-custom-fields';
|
||||||
|
const METHOD = 'POST';
|
||||||
|
|
||||||
|
public function validations() {
|
||||||
|
return [
|
||||||
|
'permission' => 'user',
|
||||||
|
'requestData' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handler() {
|
||||||
|
$userId = Controller::request('userId') * 1;
|
||||||
|
$user = Controller::getLoggedUser();
|
||||||
|
|
||||||
|
if($userId && Controller::isStaffLogged(2)) {
|
||||||
|
$user = User::getDataStore($userId);
|
||||||
|
|
||||||
|
if($user->isNull())
|
||||||
|
throw new RequestException(ERRORS::INVALID_USER);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->setProperties([
|
||||||
|
'xownCustomfieldvalueList' => $this->getCustomFieldValues()
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user->store();
|
||||||
|
Response::respondSuccess();
|
||||||
|
}
|
||||||
|
}
|
@ -66,9 +66,10 @@ class GetUserByIdController extends Controller {
|
|||||||
'name' => $user->name,
|
'name' => $user->name,
|
||||||
'email' => $user->email,
|
'email' => $user->email,
|
||||||
'signupDate' => $user->signupDate,
|
'signupDate' => $user->signupDate,
|
||||||
'tickets' => $tickets->toArray(),
|
'tickets' => $tickets->toArray(true),
|
||||||
'verified' => !$user->verificationToken,
|
'verified' => !$user->verificationToken,
|
||||||
'disabled' => !!$user->disabled
|
'disabled' => !!$user->disabled,
|
||||||
|
'customfields' => $user->xownCustomfieldvalueList->toArray(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,8 @@ class GetUserController extends Controller {
|
|||||||
'name' => $user->name,
|
'name' => $user->name,
|
||||||
'email' => $user->email,
|
'email' => $user->email,
|
||||||
'verified' => !$user->verificationToken,
|
'verified' => !$user->verificationToken,
|
||||||
'tickets' => $parsedTicketList
|
'tickets' => $parsedTicketList,
|
||||||
|
'customfields' => $user->xownCustomfieldvalueList->toArray(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ DataValidator::with('CustomValidations', true);
|
|||||||
* @apiParam {String} email The email of the new user.
|
* @apiParam {String} email The email of the new user.
|
||||||
* @apiParam {String} password The password of the new user.
|
* @apiParam {String} password The password of the new user.
|
||||||
* @apiParam {String} apiKey APIKey to sign up an user if the user system is disabled.
|
* @apiParam {String} apiKey APIKey to sign up an user if the user system is disabled.
|
||||||
|
* @apiParam {String} customfield_ Custom field values for this user.
|
||||||
*
|
*
|
||||||
* @apiUse INVALID_NAME
|
* @apiUse INVALID_NAME
|
||||||
* @apiUse INVALID_EMAIL
|
* @apiUse INVALID_EMAIL
|
||||||
@ -28,6 +29,7 @@ DataValidator::with('CustomValidations', true);
|
|||||||
* @apiUse USER_EXISTS
|
* @apiUse USER_EXISTS
|
||||||
* @apiUse ALREADY_BANNED
|
* @apiUse ALREADY_BANNED
|
||||||
* @apiUse NO_PERMISSION
|
* @apiUse NO_PERMISSION
|
||||||
|
* @apiUse INVALID_CUSTOM_FIELD_OPTION
|
||||||
*
|
*
|
||||||
* @apiSuccess {Object} data Information about created user
|
* @apiSuccess {Object} data Information about created user
|
||||||
* @apiSuccess {Number} data.userId Id of the new user
|
* @apiSuccess {Number} data.userId Id of the new user
|
||||||
@ -131,7 +133,8 @@ class SignUpController extends Controller {
|
|||||||
'tickets' => 0,
|
'tickets' => 0,
|
||||||
'email' => $this->userEmail,
|
'email' => $this->userEmail,
|
||||||
'password' => Hashing::hashPassword($this->userPassword),
|
'password' => Hashing::hashPassword($this->userPassword),
|
||||||
'verificationToken' => (MailSender::getInstance()->isConnected()) ? $this->verificationToken : null
|
'verificationToken' => (MailSender::getInstance()->isConnected()) ? $this->verificationToken : null,
|
||||||
|
'xownCustomfieldvalueList' => $this->getCustomFieldValues()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $userInstance->store();
|
return $userInstance->store();
|
||||||
|
@ -209,7 +209,31 @@
|
|||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* @apiDefine EMAIL_POLLING
|
* @apiDefine EMAIL_POLLING
|
||||||
* @apiError {String} EMAIL_POLLING Email polling
|
* @apiError {String} EMAIL_POLLING Email polling was unsuccesful
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @apiDefine IMAP_CONNECTION
|
||||||
|
* @apiError {String} IMAP_CONNECTION Imap connection was unsuccesfull
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @apiDefine CUSTOM_FIELD_ALREADY_EXISTS
|
||||||
|
* @apiError {String} CUSTOM_FIELD_ALREADY_EXISTS Custom field already exists
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @apiDefine INVALID_CUSTOM_FIELD
|
||||||
|
* @apiError {String} INVALID_CUSTOM_FIELD Custom field id is invalid
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @apiDefine INVALID_CUSTOM_FIELD_TYPE
|
||||||
|
* @apiError {String} INVALID_CUSTOM_FIELD_TYPE The type is invalid
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @apiDefine INVALID_CUSTOM_FIELD_OPTIONS
|
||||||
|
* @apiError {String} INVALID_CUSTOM_FIELD_OPTIONS Options are not a json array
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @apiDefine INVALID_CUSTOM_FIELD_OPTION
|
||||||
|
* @apiError {String} INVALID_CUSTOM_FIELD_OPTION Option is not in the list of possibles
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class ERRORS {
|
class ERRORS {
|
||||||
@ -268,4 +292,9 @@ class ERRORS {
|
|||||||
const INVALID_TEXT_3 = 'INVALID_TEXT_3';
|
const INVALID_TEXT_3 = 'INVALID_TEXT_3';
|
||||||
const DEPARTMENT_PRIVATE_TICKETS = 'DEPARTMENT_PRIVATE_TICKETS';
|
const DEPARTMENT_PRIVATE_TICKETS = 'DEPARTMENT_PRIVATE_TICKETS';
|
||||||
const EMAIL_POLLING = 'EMAIL_POLLING';
|
const EMAIL_POLLING = 'EMAIL_POLLING';
|
||||||
|
const CUSTOM_FIELD_ALREADY_EXISTS = 'CUSTOM_FIELD_ALREADY_EXISTS';
|
||||||
|
const INVALID_CUSTOM_FIELD = 'INVALID_CUSTOM_FIELD';
|
||||||
|
const INVALID_CUSTOM_FIELD_TYPE = 'INVALID_CUSTOM_FIELD_TYPE';
|
||||||
|
const INVALID_CUSTOM_FIELD_OPTIONS = 'INVALID_CUSTOM_FIELD_OPTIONS';
|
||||||
|
const INVALID_CUSTOM_FIELD_OPTION = 'INVALID_CUSTOM_FIELD_OPTION';
|
||||||
}
|
}
|
||||||
|
@ -149,4 +149,42 @@ abstract class Controller {
|
|||||||
public static function isUserSystemEnabled() {
|
public static function isUserSystemEnabled() {
|
||||||
return Setting::getSetting('user-system-enabled')->getValue();
|
return Setting::getSetting('user-system-enabled')->getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getCustomFieldValues() {
|
||||||
|
$customFields = Customfield::getAll();
|
||||||
|
$customFieldValues = new DataStoreList();
|
||||||
|
|
||||||
|
foreach($customFields as $customField) {
|
||||||
|
$value = Controller::request('customfield_' . $customField->name);
|
||||||
|
if($value !== null) {
|
||||||
|
$customFieldValue = new Customfieldvalue();
|
||||||
|
$customFieldValue->setProperties([
|
||||||
|
'customfield' => $customField,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if($customField->type == 'select') {
|
||||||
|
$ok = false;
|
||||||
|
foreach($customField->ownCustomfieldoptionList as $option) {
|
||||||
|
if($option->name == $value) {
|
||||||
|
$customFieldValue->setProperties([
|
||||||
|
'customfieldoption' => $option,
|
||||||
|
'value' => $option->name,
|
||||||
|
]);
|
||||||
|
$ok = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!$ok)
|
||||||
|
throw new RequestException(ERRORS::INVALID_CUSTOM_FIELD_OPTION);
|
||||||
|
} else {
|
||||||
|
$customFieldValue->setProperties([
|
||||||
|
'value' => $value,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$customFieldValues->add($customFieldValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $customFieldValues;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
34
server/models/CustomField.php
Normal file
34
server/models/CustomField.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @api {OBJECT} Customfield Customfield
|
||||||
|
* @apiVersion 4.4.0
|
||||||
|
* @apiGroup Data Structures
|
||||||
|
* @apiParam {Number} id Id of the custom filed.
|
||||||
|
* @apiParam {String} name Name of the custom filed.
|
||||||
|
* @apiParam {String} description Description of the custom field,
|
||||||
|
* @apiParam {String} Type Type of custom field (select or text)
|
||||||
|
* @apiParam {Customfieldoption[]} options List of possible values if it is select
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Customfield extends DataStore {
|
||||||
|
const TABLE = 'customfield';
|
||||||
|
|
||||||
|
public static function getProps() {
|
||||||
|
return [
|
||||||
|
'name',
|
||||||
|
'description',
|
||||||
|
'type',
|
||||||
|
'ownCustomfieldoptionList'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray() {
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'name' => $this->name,
|
||||||
|
'description' => $this->description,
|
||||||
|
'type' => $this->type,
|
||||||
|
'options' => $this->ownCustomfieldoptionList->toArray()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
25
server/models/CustomFieldOption.php
Normal file
25
server/models/CustomFieldOption.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @api {OBJECT} Customfieldoption Customfieldoption
|
||||||
|
* @apiVersion 4.4.0
|
||||||
|
* @apiGroup Data Structures
|
||||||
|
* @apiParam {Number} id Id of the option.
|
||||||
|
* @apiParam {String} name Name of the option.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Customfieldoption extends DataStore {
|
||||||
|
const TABLE = 'customfieldoption';
|
||||||
|
|
||||||
|
public static function getProps() {
|
||||||
|
return [
|
||||||
|
'name'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray() {
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'name' => $this->name
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
30
server/models/CustomFieldValue.php
Normal file
30
server/models/CustomFieldValue.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @api {OBJECT} Customfieldvalue Customfieldvalue
|
||||||
|
* @apiVersion 4.4.0
|
||||||
|
* @apiGroup Data Structures
|
||||||
|
* @apiParam {Number} id Id of the value.
|
||||||
|
* @apiParam {Customfield} customfield Customfield of the value.
|
||||||
|
* @apiParam {String} value Content of the value..
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Customfieldvalue extends DataStore {
|
||||||
|
const TABLE = 'customfieldvalue';
|
||||||
|
|
||||||
|
public static function getProps() {
|
||||||
|
return [
|
||||||
|
'customfield',
|
||||||
|
'value',
|
||||||
|
'customfieldoption'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray() {
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'customfield' => $this->customfield->name,
|
||||||
|
'value' => $this->value,
|
||||||
|
'customfieldoption' => $this->customfieldoption ? $this->customfieldoption->toArray() : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -173,6 +173,7 @@ abstract class DataStore {
|
|||||||
|
|
||||||
$listType = str_replace('List', '', $listType);
|
$listType = str_replace('List', '', $listType);
|
||||||
$listType = str_replace('shared', '', $listType);
|
$listType = str_replace('shared', '', $listType);
|
||||||
|
$listType = str_replace('xown', '', $listType);
|
||||||
$listType = str_replace('own', '', $listType);
|
$listType = str_replace('own', '', $listType);
|
||||||
|
|
||||||
return $listType;
|
return $listType;
|
||||||
|
@ -141,7 +141,8 @@ class Ticket extends DataStore {
|
|||||||
'name' => $author->name,
|
'name' => $author->name,
|
||||||
'staff' => $author instanceof Staff,
|
'staff' => $author instanceof Staff,
|
||||||
'profilePic' => ($author instanceof Staff) ? $author->profilePic : null,
|
'profilePic' => ($author instanceof Staff) ? $author->profilePic : null,
|
||||||
'email' => $author->email
|
'email' => $author->email,
|
||||||
|
'customfields' => $author->xownCustomfieldvalueList ? $author->xownCustomfieldvalueList->toArray() : [],
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
return [
|
return [
|
||||||
|
@ -85,7 +85,8 @@ class Ticketevent extends DataStore {
|
|||||||
'author' => [
|
'author' => [
|
||||||
'name' => $user ? $user->name : null,
|
'name' => $user ? $user->name : null,
|
||||||
'staff' => $user instanceOf Staff,
|
'staff' => $user instanceOf Staff,
|
||||||
'id' => $user ? $user->id : null
|
'id' => $user ? $user->id : null,
|
||||||
|
'customfields' => $user->xownCustomfieldvalueList ? $user->xownCustomfieldvalueList->toArray() : [],
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ use RedBeanPHP\Facade as RedBean;
|
|||||||
* @apiParam {Number} id The id of the user.
|
* @apiParam {Number} id The id of the user.
|
||||||
* @apiParam {String} name The name of the user.
|
* @apiParam {String} name The name of the user.
|
||||||
* @apiParam {Boolean} verified Indicates if the user has verified the email.
|
* @apiParam {Boolean} verified Indicates if the user has verified the email.
|
||||||
|
* @apiParam {[CustomField](#api-Data_Structures-ObjectCustomfield)[]} customfields Indicates the values for custom fields.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class User extends DataStore {
|
class User extends DataStore {
|
||||||
@ -29,7 +30,8 @@ class User extends DataStore {
|
|||||||
'tickets',
|
'tickets',
|
||||||
'sharedTicketList',
|
'sharedTicketList',
|
||||||
'verificationToken',
|
'verificationToken',
|
||||||
'disabled'
|
'disabled',
|
||||||
|
'xownCustomfieldvalueList'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +49,8 @@ class User extends DataStore {
|
|||||||
'id' => $this->id,
|
'id' => $this->id,
|
||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
'verified' => !$this->verificationToken,
|
'verified' => !$this->verificationToken,
|
||||||
'disabled' => $this->disabled
|
'disabled' => $this->disabled,
|
||||||
|
'customfields' => $this->xownCustomfieldvalueList->toArray(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user