mirror of
https://github.com/opensupports/opensupports.git
synced 2025-04-08 18:35:06 +02:00
Compare commits
134 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
20720ca4f9 | ||
|
40016635a8 | ||
|
68b2d2bf63 | ||
|
36c5f3264b | ||
|
82fd54ffd9 | ||
|
8c732f5dda | ||
|
b620baf5ed | ||
|
d3b47eaa73 | ||
|
90e7923aec | ||
|
0ecf88237f | ||
|
713a5b5ee1 | ||
|
b578a26225 | ||
|
93fa9e12a3 | ||
|
3720baf370 | ||
|
b1ef69c60c | ||
|
615f42a91b | ||
|
39f8a601db | ||
|
c82aaa001e | ||
|
04923b0e9d | ||
|
5e7f39df05 | ||
|
e190710252 | ||
|
a74d6ab4d7 | ||
|
a5da776d27 | ||
|
41d7aa5406 | ||
|
f2adb160be | ||
|
62bd70cc3b | ||
|
0f6c64674e | ||
|
861f7dc254 | ||
|
83b8e8094b | ||
|
81cc579d57 | ||
|
b3c8819d83 | ||
|
86cad910ec | ||
|
f890fdc2d3 | ||
|
c64eb9930b | ||
|
74870c632a | ||
|
e0738f1c88 | ||
|
545f88236d | ||
|
2e5bfa611e | ||
|
784d470ba5 | ||
|
54a5a9803d | ||
|
d5552c0f73 | ||
|
daf1db847c | ||
|
2df12aa5e3 | ||
|
c80c026617 | ||
|
9cf71dcf66 | ||
|
a264d384a1 | ||
|
d90b7d48c3 | ||
|
527843f00f | ||
|
3e3a95f518 | ||
|
639d40ddb0 | ||
|
fbee7275d5 | ||
|
7df190ea01 | ||
|
1228d593d0 | ||
|
c1a7befbed | ||
|
6e195a9109 | ||
|
bf7c1ba8f9 | ||
|
8b4b73402e | ||
|
4cf446483d | ||
|
ea6bc7f436 | ||
|
37209ef3fc | ||
|
83e75cc572 | ||
|
9291aa66a4 | ||
|
b9f5f7fcf1 | ||
|
fe1dd1bd48 | ||
|
5dd6b7acdc | ||
|
4bd8df1d5e | ||
|
8d7b178fa1 | ||
|
237801e9ed | ||
|
950439bf47 | ||
|
6cb538616d | ||
|
156b285344 | ||
|
b39e4c2a5f | ||
|
5a1b558a6d | ||
|
4b9a55b334 | ||
|
e7daf76274 | ||
|
402af565a9 | ||
|
f5e9b2602c | ||
|
f1d746f9f9 | ||
|
c0fc7933ed | ||
|
f31586874e | ||
|
d2c126aad9 | ||
|
ffd09b841f | ||
|
e23468d3be | ||
|
84f36e89dc | ||
|
9715cdc9a1 | ||
|
b39dca642b | ||
|
6f6acc925d | ||
|
d2e6dc2fbe | ||
|
0df57af11e | ||
|
8400a1caf0 | ||
|
f045c08b2e | ||
|
645e64532b | ||
|
b9f935df5f | ||
|
edddc8d2c5 | ||
|
eaeaaa647e | ||
|
d7ccff1a5a | ||
|
c5f1aa2b92 | ||
|
9ed4caf202 | ||
|
018863ab3e | ||
|
7015e21966 | ||
|
7cdb6d3603 | ||
|
1836849fa5 | ||
|
9f9e1dbd91 | ||
|
60b1b5eec5 | ||
|
7b4427d3e3 | ||
|
09150d6940 | ||
|
02cf8f0da3 | ||
|
e15bd15f07 | ||
|
c657d8291f | ||
|
b2e43430b1 | ||
|
8c17d22ab3 | ||
|
143776febe | ||
|
6536050fdd | ||
|
9a4374d371 | ||
|
b8be664809 | ||
|
e9a1a2e5be | ||
|
0f976ebde9 | ||
|
c64f1f1ea6 | ||
|
5d4fe0250b | ||
|
af15d0116d | ||
|
6ccb389492 | ||
|
ae076de88f | ||
|
59fb9eaef3 | ||
|
ffe7ef8e0b | ||
|
27e86c934c | ||
|
064f00388a | ||
|
fc93ad4c00 | ||
|
c22a1cdbb3 | ||
|
89cb4c18fd | ||
|
167d7927db | ||
|
55c89d58cc | ||
|
0bcc775944 | ||
|
e6441179c9 | ||
|
45a59e0b20 |
@ -5,16 +5,22 @@ orbs:
|
||||
aws-cli: circleci/aws-cli@1.0.0
|
||||
jobs:
|
||||
install_composer_packages:
|
||||
executor: php/default
|
||||
docker:
|
||||
- image: 'cimg/base:edge'
|
||||
steps:
|
||||
- checkout
|
||||
- php/install-php:
|
||||
version: '7.0'
|
||||
- php/install-composer
|
||||
|
||||
- run:
|
||||
name: Install php extensions
|
||||
command: |
|
||||
sudo add-apt-repository ppa:ondrej/php
|
||||
sudo apt install php-imap
|
||||
sudo apt update
|
||||
sudo apt install php7.0-imap -y
|
||||
sudo apt install php7.0-xml -y
|
||||
sudo apt install zip unzip php7.0-zip php7.0-mbstring -y
|
||||
|
||||
- php/install-packages:
|
||||
app-dir: server/
|
||||
|
14
.github/workflows/run-tests.yml
vendored
Normal file
14
.github/workflows/run-tests.yml
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
name: run-tests
|
||||
on: [push]
|
||||
jobs:
|
||||
run-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: cd server && make build
|
||||
- run: cd server && make run
|
||||
- run: cd server && make install-not-interactive
|
||||
- run: cd server && make setup-vendor-permissions
|
||||
- run: cd server && make test-not-interactive
|
||||
- run: cd tests && make build
|
||||
- run: cd tests && make run-not-interactive
|
80
DEVELOPMENT.md
Normal file
80
DEVELOPMENT.md
Normal file
@ -0,0 +1,80 @@
|
||||
# Development
|
||||
|
||||
Here is a guide of how to set up the development environment in OpenSupports.
|
||||
|
||||
## Requirements
|
||||
* PHP 5.6+
|
||||
* MySQL 4.1+
|
||||
|
||||
### Getting up and running FRONT-END (client folder)
|
||||
1. Update: `sudo apt update`
|
||||
2. Clone this repo: `git clone https://github.com/opensupports/opensupports.git`
|
||||
3. Install `nvm`: https://github.com/nvm-sh/nvm
|
||||
4. Use node version 11.15.0: `nvm install 11` followed by `nvm use 11`
|
||||
5. Go to client: `cd opensupports/client`
|
||||
6. Install dependencies: `npm install`
|
||||
7. Rebuild node-sass: `npm rebuild node-sass`
|
||||
8. Run: `npm start` (PHP server api it must be running at :8080)
|
||||
10. Go to the main app: `http://localhost:3000/app`
|
||||
11. Your browser will automatically be opened and directed to the browser-sync proxy address.
|
||||
12. Use `npm start-fixtures` to enable fixtures and not require php server to be running.
|
||||
|
||||
OpenSupport uses by default the port 3000, but this port could already be used. If this is the case, you can modify this in the file: `client/webpack.config.js`.
|
||||
|
||||
##### Production Task
|
||||
|
||||
Just as there is a task for development, there is also a `npm build` task for putting the project into a production-ready state. This will run each of the tasks, while also adding the image minification task discussed above and the result store in `dist/` folder.
|
||||
|
||||
**Reminder:** Notice there is `index.html` and `index.php`. The first one searches the backend server where `config.js` says it, the second one uses `/api` to find the server. If you want to run OpenSupports in a single server, then use `index.php`.
|
||||
|
||||
#### Frontend Unit Testing
|
||||
1. Do the steps described before.
|
||||
2. Install mocha: `npm install -g mocha@6.2.0`
|
||||
3. Run `npm test` to run the tests.
|
||||
|
||||
### Getting up and running BACK-END (server folder)
|
||||
1. Install [Docker CE](https://docs.docker.com/install/)
|
||||
2. Go to the server folder: `cd opensupports/server`
|
||||
3. Run `make build` to build the images
|
||||
4. Run `make install` to install composer dependencies
|
||||
|
||||
- `make run` runs the backend and database
|
||||
- `make stop` stop backend and database server
|
||||
- `make log` show live server logs
|
||||
- `make db` access to mysql database console
|
||||
- `make sh` access to backend docker container bash
|
||||
- `make test` run phpunit tests
|
||||
- `make doc` to build the documentation (requires `apidoc`)
|
||||
|
||||
Server api runs on `http://localhost:8080/`
|
||||
Also, there's a *phpmyadmin* instance running on `http://localhost:6060/`,
|
||||
you can access with the username `root` and empty password
|
||||
|
||||
##### Building
|
||||
Once you've installed dependencies for frontend and backend, you can run `./build.sh` and it will generate a zip file inside `dist/` ready for distribution. You can use this file to install OpenSupports on a serving following the [installation instructions](https://github.com/opensupports/opensupports/wiki/Installation)
|
||||
|
||||
##### BACKEND API RUBY TESTING
|
||||
|
||||
1. Go to tests folder: `cd opensupports/tests`
|
||||
2. Run `make build` to install ruby container and its required dependencies
|
||||
|
||||
- `make run` for running tests (database will be cleared)
|
||||
- `make clear` for clearing database
|
||||
|
||||
##### BACKEND FAKE SMTP SERVER
|
||||
If you're doing development, you can use a FakeSMTP server to see the mails that are being sent.
|
||||
|
||||
1. Install Java if you don't have it yet:
|
||||
|
||||
`sudo apt-get install default-jre`
|
||||
`sudo apt-get install default-jdk`
|
||||
|
||||
2. [Download FakeSMTP](https://nilhcem.github.io/FakeSMTP/download.html)
|
||||
|
||||
3. Extract the file from the zip and run it:
|
||||
|
||||
`java -jar fakeSMTP-2.0.jar`
|
||||
|
||||
4. Set the port to 7070 and start the SMTP server.
|
||||
|
||||
5. Every time the application sends an email, it will be reflected there.
|
6
Makefile
6
Makefile
@ -22,7 +22,11 @@ deploy-staging-population:
|
||||
--header 'Circle-Token: ${CIRCLE_API_USER_TOKEN}' \
|
||||
--header 'content-type: application/json' \
|
||||
--data '{"branch":"master","parameters":{"server_to_deploy": "dev3"}}'
|
||||
|
||||
curl --request POST \
|
||||
--url https://circleci.com/api/v2/project/github/opensupports/staging-population/pipeline \
|
||||
--header 'Circle-Token: ${CIRCLE_API_USER_TOKEN}' \
|
||||
--header 'content-type: application/json' \
|
||||
--data '{"branch":"master","parameters":{"server_to_deploy": "dev4"}}'
|
||||
build-release-bundles:
|
||||
$(eval UPGRADE_ZIP="opensupports_v$(VERSION)_update.zip")
|
||||
./build.sh
|
||||
|
100
README.md
100
README.md
@ -1,89 +1,61 @@
|
||||

|
||||
<div align="center">
|
||||
|
||||
[](https://travis-ci.org/opensupports/opensupports) v4.9.0
|
||||

|
||||
|
||||
OpenSupports is an open source ticket system built primarily with PHP and ReactJS.
|
||||
Please, visit our website for more information: [http://www.opensupports.com/](http://www.opensupports.com/)
|
||||
OpenSupports is a simple and beautiful open source ticket system. <br />
|
||||
<a href="https://www.opensupports.com/"><strong>Learn more »</strong></a>
|
||||
<br />
|
||||
<p align="center">
|
||||
<a href="https://www.opensupports.com/">Website</a> •
|
||||
<a href="https://docs.opensupports.com/">Docs</a> •
|
||||
<a href="https://opensupports.com/demo/">Demo</a> •
|
||||
<a href="https://www.opensupports.com/pricing/">Official Subscription</a>
|
||||
</p>
|
||||
|
||||
## Requirements
|
||||
* PHP 5.6+
|
||||
* MySQL 4.1+
|
||||
</div>
|
||||
|
||||
## Development
|
||||
Here is a guide of how to set up the development environment in OpenSupports.
|
||||
## 🌱 About the Project
|
||||
|
||||
### Getting up and running FRONT-END (client folder)
|
||||
1. Update: `sudo apt-get update`
|
||||
2. Clone this repo: `git clone https://github.com/opensupports/opensupports.git`
|
||||
3. Install node 4.x version:
|
||||
- `sudo apt-get install curl`
|
||||
- `curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -`
|
||||
- `sudo apt-get install -y nodejs`
|
||||
4. Install npm: `sudo apt-get install npm`
|
||||
5. Go to client: `cd opensupports/client`
|
||||
6. Install dependencies: `npm install`
|
||||
7. Rebuild node-sass: `npm rebuild node-sass`
|
||||
8. Run: `npm start` (PHP server api it must be running at :8080)
|
||||
10. Go to the main app: `http://localhost:3000/app` or to the component demo `http://localhost:3000/demo`
|
||||
11. Your browser will automatically be opened and directed to the browser-sync proxy address.
|
||||
12. Use `npm start-fixtures` to enable fixtures and not require php server to be running.
|
||||
### What Customers See
|
||||
|
||||
OpenSupport uses by default the port 3000, but this port could already be used. If this is the case, you can modify this in the file: `client/webpack.config.js`.
|
||||

|
||||
|
||||
##### Production Task
|
||||
### What Staff Members See
|
||||
|
||||
Just as there is a task for development, there is also a `npm build` task for putting the project into a production-ready state. This will run each of the tasks, while also adding the image minification task discussed above and the result store in `dist/` folder.
|
||||

|
||||
|
||||
**Reminder:** Notice there is `index.html` and `index.php`. The first one searches the backend server where `config.js` says it, the second one uses `/api` to find the server. If you want to run OpenSupports in a single server, then use `index.php`.
|
||||
## 🙌🏼 Ticket System for Absolutely Everyone
|
||||
|
||||
#### Frontend Unit Testing
|
||||
1. Do the steps described before.
|
||||
2. Install mocha: `npm install -g mocha@6.2.0`
|
||||
3. Run `npm test` to run the tests.
|
||||
OpenSupports is a simple and beautiful open source ticket system.
|
||||
|
||||
### Getting up and running BACK-END (server folder)
|
||||
1. Install [Docker CE](https://docs.docker.com/install/)
|
||||
2. Go to the server folder: `cd opensupports/server`
|
||||
3. Run `make build` to build the images
|
||||
4. Run `make install` to install composer dependencies
|
||||
It is a web application that provides you with a better management of your users’ queries. They send you tickets through OpenSupports and you can handle them appropriately.
|
||||
|
||||
- `make run` runs the backend and database
|
||||
- `make stop` stop backend and database server
|
||||
- `make log` show live server logs
|
||||
- `make db` access to mysql database console
|
||||
- `make sh` access to backend docker container bash
|
||||
- `make test` run phpunit tests
|
||||
- `make doc` to build the documentation (requires `apidoc`)
|
||||
Self-hosted, or [hosted by us](https://www.opensupports.com/pricing/), API-driven, and ready to be deployed on your own domain.
|
||||
|
||||
Server api runs on `http://localhost:8080/`
|
||||
Also, there's a *phpmyadmin* instance running on `http://localhost:6060/`,
|
||||
you can access with the username `root` and empty password
|
||||
## 🧐 Stay Up-to-Date
|
||||
|
||||
##### Building
|
||||
Once you've installed dependencies for frontend and backend, you can run `./build.sh` and it will generate a zip file inside `dist/` ready for distribution. You can use this file to install OpenSupports on a serving following the [installation instructions](https://github.com/opensupports/opensupports/wiki/Installation)
|
||||
OpenSupports is growing and steadily incorporating new features. You might want to **add a star to the project** (or watch updates) to be notified about new releases.
|
||||
|
||||
##### BACKEND API RUBY TESTING
|
||||
## 💪🏼 Features
|
||||
|
||||
1. Go to tests folder: `cd opensupports/tests`
|
||||
2. Run `make build` to install ruby container and its required dependencies
|
||||
Check out our [most important features](https://opensupports.com/features) at our website.
|
||||
|
||||
- `make run` for running tests (database will be cleared)
|
||||
- `make clear` for clearing database
|
||||
Are we missing something? [Suggest an improvement](https://github.com/opensupports/opensupports/issues/new)!
|
||||
|
||||
##### BACKEND FAKE SMTP SERVER
|
||||
If you're doing development, you can use a FakeSMTP server to see the mails that are being sent.
|
||||
## 🛠 Install
|
||||
|
||||
1. Install Java if you don't have it yet:
|
||||
OpenSupports can be hosted on your own servers, or [hosted by us](https://www.opensupports.com/pricing/).
|
||||
|
||||
`sudo apt-get install default-jre`
|
||||
`sudo apt-get install default-jdk`
|
||||
There are multiple benefits to having the system hosted by its creators, including official support into any problem you might encounter.
|
||||
|
||||
2. [Download FakeSMTP](https://nilhcem.github.io/FakeSMTP/download.html)
|
||||
But in the case you prefer your development team to deal with the installation, maintenance (upgrades, backups, etc.), and integrations, we charge you nothing for it, OpenSupports is **free and open-source**!
|
||||
|
||||
3. Extract the file from the zip and run it:
|
||||
Check out our [installation guide](https://docs.opensupports.com/guides/installation/).
|
||||
|
||||
`java -jar fakeSMTP-2.0.jar`
|
||||
## 👨🏼💻 Development
|
||||
|
||||
4. Set the port to 7070 and start the SMTP server.
|
||||
Are you a programmer? You can help us to fix bugs and build OpenSupports' features!
|
||||
|
||||
5. Every time the application sends an email, it will be reflected there.
|
||||
Check out our [development guide](./DEVELOPMENT.md) to get your development environment up and running.
|
||||
|
||||
And even if you are not a programmer, you can help us by [reporting problems or suggesting improvements](https://github.com/opensupports/opensupports/issues/new), we love feedback and learn a lot from it!
|
||||
|
19
SECURITY.md
Normal file
19
SECURITY.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Security Policy
|
||||
This document is intended to provide a guide to properly disclosure security issues found in our open source software.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you find a vulnerability or potential security issue in OpenSupports. Please contact us at contact@opensupports.com
|
||||
|
||||
We will acknowledge your email within 48 hours, and will send a more detailed response within 48 hours indicating the next steps in handling your report. After the initial reply to your report, we will endeavor to keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
|
||||
|
||||
## Disclosure Policy
|
||||
|
||||
When we receive a security bug report, we will assign it to a
|
||||
primary handler. This person will coordinate the fix and release process,
|
||||
involving the following steps:
|
||||
|
||||
* Confirm the problem and determine the affected versions.
|
||||
* Audit code to find any potential similar problems.
|
||||
* Prepare fixes for all releases still under maintenance. These fixes will be
|
||||
released as fast as possible in a new OpenSupports version.
|
11
client/Makefile
Normal file
11
client/Makefile
Normal file
@ -0,0 +1,11 @@
|
||||
build:
|
||||
@docker pull node:11.15.0
|
||||
|
||||
run: stop
|
||||
@docker run --platform=linux/amd64 --network os-net --name opensupports-client -v $(PWD):/client:delegated -p 3000:3000 node:11.15.0 sh -c "cd client && npm install && npm start"
|
||||
|
||||
sh: stop
|
||||
@docker run -it --platform=linux/amd64 --network os-net --name opensupports-client -v $(PWD):/client:delegated -p 3000:3000 node:11.15.0 sh -c "bash"
|
||||
|
||||
stop:
|
||||
@docker rm -f opensupports-client || true
|
821
client/package-lock.json
generated
821
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "OpenSupports",
|
||||
"version": "4.9.0",
|
||||
"version": "4.11.0",
|
||||
"author": "Ivan Diaz <contact@opensupports.com>",
|
||||
"description": "Open source ticket system made with PHP and ReactJS",
|
||||
"repository": {
|
||||
@ -9,11 +9,11 @@
|
||||
},
|
||||
"private": false,
|
||||
"engines": {
|
||||
"node": "^0.12.x",
|
||||
"npm": "^2.1.x"
|
||||
"node": "^11.15.x",
|
||||
"npm": "^6.7.x"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server",
|
||||
"start": "webpack-dev-server --display-reasons --display-error-details --history-api-fallback --progress --colors",
|
||||
"start-fixtures": "webpack-dev-server --env.FIXTURES=1",
|
||||
"build": "./node_modules/.bin/rimraf build && NODE_ENV=production ./node_modules/.bin/webpack -p --devtool none",
|
||||
"test": "export NODE_PATH=src && mocha src/lib-test/preprocessor.js --require @babel/register --recursive src/**/**/__tests__/*-test.js"
|
||||
@ -30,7 +30,7 @@
|
||||
"axios-mock-adapter": "^1.15.0",
|
||||
"babel-loader": "^8.0.6",
|
||||
"babel-plugin-add-module-exports": "^1.0.2",
|
||||
"browser-sync": "^2.7.13",
|
||||
"browser-sync": "^2.27.5",
|
||||
"chai": "^3.5.0",
|
||||
"copy-webpack-plugin": "^5.0.3",
|
||||
"css-loader": "^3.0.0",
|
||||
@ -74,12 +74,13 @@
|
||||
"html-to-text": "^4.0.0",
|
||||
"keycode": "^2.1.4",
|
||||
"localStorage": "^1.0.3",
|
||||
"lodash": "^4.17.15",
|
||||
"lodash": "^4.17.21",
|
||||
"messageformat": "^0.2.2",
|
||||
"moment": "^2.27.0",
|
||||
"qs": "^6.5.2",
|
||||
"query-string": "^6.12.1",
|
||||
"quill-image-resize-module-react": "^3.0.0",
|
||||
"quill-magic-url": "^4.1.3",
|
||||
"random-string": "^0.2.0",
|
||||
"react": "^15.4.2",
|
||||
"react-chartjs-2": "^2.10.0",
|
||||
@ -94,4 +95,4 @@
|
||||
"redux": "^3.5.2",
|
||||
"redux-promise-middleware": "^3.3.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
export default {
|
||||
login: stub(),
|
||||
logout: stub(),
|
||||
initSession: stub()
|
||||
checkSession: stub()
|
||||
};
|
@ -88,7 +88,7 @@
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('initSession action', function () {
|
||||
// describe('checkSession action', function () {
|
||||
// beforeEach(function () {
|
||||
// APICallMock.call.returns({
|
||||
// then: function (resolve) {
|
||||
@ -125,7 +125,7 @@
|
||||
// }
|
||||
// });
|
||||
|
||||
// expect(SessionActions.initSession().type).to.equal('CHECK_SESSION');
|
||||
// expect(SessionActions.checkSession().type).to.equal('CHECK_SESSION');
|
||||
// expect(storeMock.dispatch).to.have.been.calledWith({type: 'SESSION_CHECKED'});
|
||||
// expect(APICallMock.call).to.have.been.calledWith({
|
||||
// path: '/user/check-session',
|
||||
@ -136,7 +136,7 @@
|
||||
// it('should return CHECK_SESSION and dispatch LOGOUT_FULFILLED if session is not active and no remember data', function () {
|
||||
// sessionStoreMock.isRememberDataExpired.returns(true);
|
||||
|
||||
// expect(SessionActions.initSession().type).to.equal('CHECK_SESSION');
|
||||
// expect(SessionActions.checkSession().type).to.equal('CHECK_SESSION');
|
||||
// expect(storeMock.dispatch).to.have.been.calledWith({type: 'LOGOUT_FULFILLED'});
|
||||
// expect(APICallMock.call).to.have.been.calledWith({
|
||||
// path: '/user/check-session',
|
||||
@ -147,7 +147,7 @@
|
||||
// it('should return CHECK_SESSION and dispatch LOGIN_AUTO if session is not active but remember data exists', function () {
|
||||
// sessionStoreMock.isRememberDataExpired.returns(false);
|
||||
|
||||
// expect(SessionActions.initSession().type).to.equal('CHECK_SESSION');
|
||||
// expect(SessionActions.checkSession().type).to.equal('CHECK_SESSION');
|
||||
// expect(storeMock.dispatch).to.not.have.been.calledWith({type: 'LOGOUT_FULFILLED'});
|
||||
// expect(APICallMock.call).to.have.been.calledWith({
|
||||
// path: '/user/check-session',
|
||||
|
@ -12,22 +12,22 @@ export default {
|
||||
};
|
||||
},
|
||||
|
||||
retrieveMyTickets(page, closed = 0, departmentId = 0) {
|
||||
retrieveMyTickets({page, closed = 0, departmentId = 0, pageSize = 10}) {
|
||||
return {
|
||||
type: 'MY_TICKETS',
|
||||
payload: API.call({
|
||||
path: '/staff/get-tickets',
|
||||
data: {page, closed, departmentId}
|
||||
data: {page, closed, departmentId, pageSize}
|
||||
})
|
||||
};
|
||||
},
|
||||
|
||||
retrieveNewTickets(page = 1, departmentId = 0) {
|
||||
retrieveNewTickets({page, departmentId = 0, pageSize = 10}) {
|
||||
return {
|
||||
type: 'NEW_TICKETS',
|
||||
payload: API.call({
|
||||
path: '/staff/get-new-tickets',
|
||||
data: {page, departmentId}
|
||||
data: {page, departmentId, pageSize}
|
||||
})
|
||||
};
|
||||
},
|
||||
|
@ -9,14 +9,15 @@ export default {
|
||||
payload: {}
|
||||
}
|
||||
},
|
||||
retrieveSearchTickets(ticketQueryListState, filters = {}) {
|
||||
retrieveSearchTickets(ticketQueryListState, filters = {}, pageSize = 10) {
|
||||
return {
|
||||
type: 'SEARCH_TICKETS',
|
||||
payload: API.call({
|
||||
path: '/ticket/search',
|
||||
data: {
|
||||
...filters,
|
||||
page: ticketQueryListState.page
|
||||
page: ticketQueryListState.page,
|
||||
pageSize
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -28,7 +29,7 @@ export default {
|
||||
}
|
||||
},
|
||||
changeFilters(listConfig) {
|
||||
const filtersForAPI = searchTicketsUtils.prepareFiltersForAPI(listConfig.filters);
|
||||
const filtersForAPI = searchTicketsUtils.getFiltersForAPI(listConfig.filters);
|
||||
|
||||
return {
|
||||
type: 'SEARCH_FILTERS_CHANGE_FILTERS',
|
||||
@ -48,7 +49,7 @@ export default {
|
||||
}
|
||||
},
|
||||
changePage(filtersWithPage) {
|
||||
const filtersForAPI = searchTicketsUtils.prepareFiltersForAPI(filtersWithPage);
|
||||
const filtersForAPI = searchTicketsUtils.getFiltersForAPI(filtersWithPage);
|
||||
const currentPath = window.location.pathname;
|
||||
const urlQuery = searchTicketsUtils.getFiltersForURL({
|
||||
filters: filtersForAPI,
|
||||
@ -63,7 +64,7 @@ export default {
|
||||
}
|
||||
},
|
||||
changeOrderBy(filtersWithOrderBy) {
|
||||
const filtersForAPI = searchTicketsUtils.prepareFiltersForAPI(filtersWithOrderBy);
|
||||
const filtersForAPI = searchTicketsUtils.getFiltersForAPI(filtersWithOrderBy);
|
||||
const currentPath = window.location.pathname;
|
||||
const urlQuery = searchTicketsUtils.getFiltersForURL({
|
||||
filters: filtersForAPI,
|
||||
|
@ -101,7 +101,7 @@ export default {
|
||||
};
|
||||
},
|
||||
|
||||
initSession() {
|
||||
checkSession() {
|
||||
return {
|
||||
type: 'CHECK_SESSION',
|
||||
payload: new Promise((resolve, reject) => {
|
||||
|
@ -39,10 +39,10 @@ class ArticlesList extends React.Component {
|
||||
const { errored, loading } = this.props;
|
||||
|
||||
if(errored) {
|
||||
return <Message type="error">{i18n('ERROR_RETRIEVING_ARTICLES')}</Message>;
|
||||
return <Message showCloseButton={false} type="error">{i18n('ERROR_RETRIEVING_ARTICLES')}</Message>;
|
||||
}
|
||||
|
||||
return loading ? <Loading /> : this.renderContent();
|
||||
return loading ? <Loading className="articles-list__loading" backgrounded size="large"/> : this.renderContent();
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
|
@ -5,6 +5,11 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__loading {
|
||||
min-width: 300px;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
&__add-topic-button {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -1,6 +1,4 @@
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import DropDown from 'core-components/drop-down';
|
||||
import Icon from 'core-components/icon';
|
||||
|
@ -3,7 +3,6 @@ import {connect} from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import languageList from 'data/language-list';
|
||||
import i18n from 'lib-app/i18n';
|
||||
import DropDown from 'core-components/drop-down';
|
||||
|
||||
const languageCodes = Object.keys(languageList);
|
||||
|
@ -11,7 +11,7 @@ class ModalContainer extends React.Component {
|
||||
static openModal(
|
||||
content,
|
||||
options={noPadding: false, outsideClick: false, closeButton: {showCloseButton: false, whiteColor: false}}
|
||||
) {
|
||||
) {
|
||||
store.dispatch(
|
||||
ModalActions.openModal({
|
||||
content,
|
||||
|
41
client/src/app-components/page-size-dropdown.js
Normal file
41
client/src/app-components/page-size-dropdown.js
Normal file
@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
|
||||
import DropDown from 'core-components/drop-down';
|
||||
import i18n from 'lib-app/i18n';
|
||||
|
||||
class PageSizeDropdown extends React.Component {
|
||||
static propTypes = {
|
||||
value: React.PropTypes.number,
|
||||
onChange: React.PropTypes.func,
|
||||
pages: React.PropTypes.array
|
||||
}
|
||||
|
||||
state = {
|
||||
selectedIndex: 1
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<DropDown {...this.props} onChange={this.onChange.bind(this)} items={this.getPages()} selectedIndex={this.state.selectedIndex} />
|
||||
)
|
||||
}
|
||||
|
||||
getPages() {
|
||||
return this.props.pages.map((page) => {
|
||||
return {content: `${page} / ${i18n('TICKETS')}`}
|
||||
});
|
||||
}
|
||||
|
||||
onChange(event) {
|
||||
this.setState({
|
||||
selectedIndex: event.index
|
||||
})
|
||||
if(this.props.onChange) {
|
||||
this.props.onChange({
|
||||
pageSize: this.props.pages[event.index]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default PageSizeDropdown;
|
@ -24,13 +24,19 @@ class PasswordRecovery extends React.Component {
|
||||
renderLogo: false
|
||||
};
|
||||
|
||||
state = {
|
||||
showRecoverSentMessage: true
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!prevProps.recoverSent && this.props.recoverSent) {
|
||||
this.setState({showRecoverSentMessage : true});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
renderLogo,
|
||||
formProps,
|
||||
onBackToLoginClick,
|
||||
style
|
||||
} = this.props;
|
||||
const { renderLogo, formProps, onBackToLoginClick, style } = this.props;
|
||||
|
||||
return (
|
||||
<Widget style={style} className={this.getClass()} title={!renderLogo ? i18n('RECOVER_PASSWORD') : ''}>
|
||||
{this.renderLogo()}
|
||||
@ -68,22 +74,29 @@ class PasswordRecovery extends React.Component {
|
||||
}
|
||||
|
||||
renderRecoverStatus() {
|
||||
let status = null;
|
||||
|
||||
if (this.props.recoverSent) {
|
||||
status = (
|
||||
<Message className="password-recovery__message" type="info" leftAligned>
|
||||
{i18n('RECOVER_SENT')}
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
|
||||
return status;
|
||||
return (
|
||||
this.props.recoverSent ?
|
||||
<Message
|
||||
showMessage={this.state.showRecoverSentMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showRecoverSentMessage")}
|
||||
className="password-recovery__message"
|
||||
type="info"
|
||||
leftAligned>
|
||||
{i18n('RECOVER_SENT')}
|
||||
</Message> :
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
focusEmail() {
|
||||
this.refs.email.focus();
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default PasswordRecovery;
|
||||
|
@ -16,7 +16,7 @@ class PopupMessage extends React.Component {
|
||||
static open(props) {
|
||||
ModalContainer.openModal(
|
||||
<PopupMessage {...props} />,
|
||||
{noPadding: true, outsideClick: true}
|
||||
{noPadding: true, outsideClick: true, closeButton: {showCloseButton: false, whiteColor: false}}
|
||||
);
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ class PopupMessage extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="popup-message">
|
||||
<Message {...this.props} className="popup-message__message" />
|
||||
<Message {...this.props} showCloseButton={false} className="popup-message__message" />
|
||||
<Button
|
||||
className="popup-message__close-button"
|
||||
iconName="times"
|
||||
|
19
client/src/app-components/session-expired-modal.js
Normal file
19
client/src/app-components/session-expired-modal.js
Normal file
@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
import _ from "lodash";
|
||||
|
||||
import i18n from "lib-app/i18n";
|
||||
|
||||
import Header from "core-components/header";
|
||||
|
||||
class SessionExpiredModal extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Header
|
||||
title={i18n("SESSION_EXPIRED")}
|
||||
description={i18n("SESSION_EXPIRED_DESCRIPTION")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SessionExpiredModal;
|
@ -31,7 +31,9 @@ class TicketEvent extends React.Component {
|
||||
private: React.PropTypes.string,
|
||||
edited: React.PropTypes.bool,
|
||||
edit: React.PropTypes.bool,
|
||||
onToggleEdit: React.PropTypes.func
|
||||
onToggleEdit: React.PropTypes.func,
|
||||
isLastComment: React.PropTypes.bool,
|
||||
isTicketClosed: React.PropTypes.bool
|
||||
};
|
||||
|
||||
state = {
|
||||
@ -92,12 +94,7 @@ class TicketEvent extends React.Component {
|
||||
}
|
||||
|
||||
renderComment() {
|
||||
const {
|
||||
author,
|
||||
date,
|
||||
edit,
|
||||
file
|
||||
} = this.props;
|
||||
const { author, date, edit, file } = this.props;
|
||||
const customFields = (author && author.customfields) || [];
|
||||
|
||||
return (
|
||||
@ -142,10 +139,13 @@ class TicketEvent extends React.Component {
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
const { content, author, userId, userStaff, isLastComment, isTicketClosed } = this.props;
|
||||
const { id, staff } = author;
|
||||
|
||||
return (
|
||||
<div className="ticket-event__comment-content ql-editor">
|
||||
<div dangerouslySetInnerHTML={{__html: this.props.content}}></div>
|
||||
{((this.props.author.id == this.props.userId && this.props.author.staff == this.props.userStaff) || this.props.userStaff) ? this.renderEditIcon() : null}
|
||||
<div className="ticket-event__comment-content ql-editor">
|
||||
<div dangerouslySetInnerHTML={{__html: content}}></div>
|
||||
{(id == userId && staff == userStaff && isLastComment && !isTicketClosed) ? this.renderEditIcon() : null }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ import Checkbox from 'core-components/checkbox';
|
||||
import Tag from 'core-components/tag';
|
||||
import Icon from 'core-components/icon';
|
||||
import Message from 'core-components/message';
|
||||
import history from 'lib-app/history';
|
||||
import PageSizeDropdown from './page-size-dropdown';
|
||||
|
||||
class TicketList extends React.Component {
|
||||
static propTypes = {
|
||||
@ -30,7 +32,8 @@ class TicketList extends React.Component {
|
||||
]),
|
||||
closedTicketsShown: React.PropTypes.bool,
|
||||
onClosedTicketsShownChange: React.PropTypes.func,
|
||||
onDepartmentChange: React.PropTypes.func
|
||||
onDepartmentChange: React.PropTypes.func,
|
||||
showPageSizeDropdown: React.PropTypes.bool
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@ -40,7 +43,8 @@ class TicketList extends React.Component {
|
||||
departments: [],
|
||||
ticketPath: '/dashboard/ticket/',
|
||||
type: 'primary',
|
||||
closedTicketsShown: false
|
||||
closedTicketsShown: false,
|
||||
showPageSizeDropdown: true
|
||||
};
|
||||
|
||||
state = {
|
||||
@ -48,25 +52,32 @@ class TicketList extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { type, showDepartmentDropdown, onClosedTicketsShownChange } = this.props;
|
||||
const { type, showDepartmentDropdown, onClosedTicketsShownChange, showPageSizeDropdown } = this.props;
|
||||
const pages = [5, 10, 20, 50];
|
||||
|
||||
return (
|
||||
<div className="ticket-list">
|
||||
<div className="ticket-list__filters">
|
||||
{(type === 'primary') ? this.renderMessage() : null}
|
||||
{
|
||||
((type === 'secondary') && showDepartmentDropdown) ?
|
||||
this.renderDepartmentsDropDown() :
|
||||
<div className="ticket-list__main-filters">
|
||||
{(type === 'primary') ? this.renderMessage() : null}
|
||||
{
|
||||
((type === 'secondary') && showDepartmentDropdown) ?
|
||||
this.renderDepartmentsDropDown() :
|
||||
null
|
||||
}
|
||||
{onClosedTicketsShownChange ? this.renderFilterCheckbox() : null}
|
||||
</div>
|
||||
{
|
||||
showPageSizeDropdown ?
|
||||
<PageSizeDropdown className="ticket-list__page-dropdown" pages={pages} onChange={(event) => this.pageSizeChange(event)} /> :
|
||||
null
|
||||
}
|
||||
{onClosedTicketsShownChange ? this.renderFilterCheckbox() : null}
|
||||
</div>
|
||||
<Table {...this.getTableProps()} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
renderFilterCheckbox() {
|
||||
return (
|
||||
<Checkbox
|
||||
@ -90,14 +101,34 @@ class TicketList extends React.Component {
|
||||
renderMessage() {
|
||||
switch (queryString.parse(window.location.search)["message"]) {
|
||||
case 'success':
|
||||
return <Message className="create-ticket-form__message" type="success">{i18n('TICKET_SENT')}</Message>
|
||||
return (
|
||||
<Message
|
||||
onCloseMessage={this.onCloseMessage}
|
||||
className="create-ticket-form__message"
|
||||
type="success">
|
||||
{i18n('TICKET_SENT')}
|
||||
</Message>
|
||||
);
|
||||
case 'fail':
|
||||
return <Message className="create-ticket-form__message" type="error">{i18n('TICKET_SENT_ERROR')}</Message>;
|
||||
return (
|
||||
<Message
|
||||
onCloseMessage={this.onCloseMessage}
|
||||
className="create-ticket-form__message"
|
||||
type="error">
|
||||
{i18n('TICKET_SENT_ERROR')}
|
||||
</Message>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
pageSizeChange(event) {
|
||||
const { onPageSizeChange } = this.props;
|
||||
|
||||
onPageSizeChange && onPageSizeChange(event.pageSize);
|
||||
}
|
||||
|
||||
getDepartmentDropdownProps() {
|
||||
const { departments, onDepartmentChange } = this.props;
|
||||
|
||||
@ -123,7 +154,7 @@ class TicketList extends React.Component {
|
||||
loading,
|
||||
headers: this.getTableHeaders(),
|
||||
rows: this.getTableRows(),
|
||||
pageSize: 10,
|
||||
pageSize: this.state.tickets,
|
||||
page,
|
||||
pages,
|
||||
onPageChange
|
||||
@ -226,7 +257,7 @@ class TicketList extends React.Component {
|
||||
}
|
||||
|
||||
getTableRows() {
|
||||
return this.getTickets().map(this.gerTicketTableObject.bind(this));
|
||||
return this.getTickets().map(this.getTicketTableObject.bind(this));
|
||||
}
|
||||
|
||||
getTickets() {
|
||||
@ -240,7 +271,7 @@ class TicketList extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
gerTicketTableObject(ticket) {
|
||||
getTicketTableObject(ticket) {
|
||||
const { date, title, ticketNumber, closed, tags, department, author } = ticket;
|
||||
const dateTodayWithOutHoursAndMinutes = DateTransformer.getDateToday();
|
||||
const ticketDateWithOutHoursAndMinutes = Math.floor(DateTransformer.UTCDateToLocalNumericDate(JSON.stringify(date*1)) / 10000);
|
||||
@ -248,7 +279,7 @@ class TicketList extends React.Component {
|
||||
const ticketDate = (
|
||||
((dateTodayWithOutHoursAndMinutes - ticketDateWithOutHoursAndMinutes) > 1) ?
|
||||
stringTicketLocalDateFormat :
|
||||
`${(dateTodayWithOutHoursAndMinutes - ticketDateWithOutHoursAndMinutes) ? "Yesterday" : "Today"} at ${stringTicketLocalDateFormat.slice(-5)}`
|
||||
`${(dateTodayWithOutHoursAndMinutes - ticketDateWithOutHoursAndMinutes) ? i18n("YESTERDAY_AT") : i18n("TODAY_AT")} ${stringTicketLocalDateFormat.slice(-5)}`
|
||||
);
|
||||
let titleText = (this.isTicketUnread(ticket)) ? title + ' (1)' : title;
|
||||
|
||||
@ -293,6 +324,10 @@ class TicketList extends React.Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onCloseMessage() {
|
||||
history.push(window.location.pathname);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect((store) => {
|
||||
|
@ -7,16 +7,31 @@
|
||||
}
|
||||
|
||||
&__filters {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 25px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&__main-filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__department-selector {
|
||||
display: inline-block;
|
||||
margin-right: 25px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__page-dropdown {
|
||||
display: inline-block;
|
||||
margin-right: 25px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__checkbox {
|
||||
display: inline-block;
|
||||
}
|
||||
@ -48,3 +63,7 @@
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.create-ticket-form__message {
|
||||
width: 100%;
|
||||
}
|
@ -8,6 +8,7 @@ import i18n from 'lib-app/i18n';
|
||||
import API from 'lib-app/api-call';
|
||||
import history from 'lib-app/history';
|
||||
import searchTicketsUtils from 'lib-app/search-tickets-utils';
|
||||
import ticketUtils from 'lib-app/ticket-utils';
|
||||
|
||||
import Form from 'core-components/form';
|
||||
import SubmitButton from 'core-components/submit-button';
|
||||
@ -34,7 +35,8 @@ class TicketQueryFilters extends React.Component {
|
||||
formState,
|
||||
filters,
|
||||
showFilters,
|
||||
ticketQueryListState
|
||||
ticketQueryListState,
|
||||
staffList
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@ -47,22 +49,15 @@ class TicketQueryFilters extends React.Component {
|
||||
<div className="ticket-query-filters__search-box">
|
||||
<FormField name="query" field="search-box" fieldProps={{onSearch: this.onSubmitListConfig.bind(this)}} />
|
||||
</div>
|
||||
<div className="ticket-query-filters__first-row">
|
||||
<div className="ticket-query-filters__second-row">
|
||||
<FormField
|
||||
label={i18n('DATE')}
|
||||
name="dateRange"
|
||||
field="date-range"
|
||||
fieldProps={{defaultValue: formState.dateRange}} />
|
||||
<FormField
|
||||
label={i18n('STATUS')}
|
||||
name="closed"
|
||||
label={i18n('PERIOD')}
|
||||
name="period"
|
||||
field="select"
|
||||
fieldProps={{
|
||||
items: this.getStatusItems(),
|
||||
className: 'ticket-query-filters__status-drop-down'
|
||||
items: [{content: i18n('LAST_7_DAYS')}, {content: i18n('LAST_30_DAYS')}, {content: i18n('LAST_90_DAYS')}, {content: i18n('LAST_365_DAYS')}],
|
||||
className: 'ticket-query-filters__drop-down'
|
||||
}} />
|
||||
</div>
|
||||
<div className="ticket-query-filters__second-row">
|
||||
<FormField
|
||||
label={i18n('DEPARTMENTS')}
|
||||
name="departments"
|
||||
@ -72,9 +67,17 @@ class TicketQueryFilters extends React.Component {
|
||||
label={i18n('OWNER')}
|
||||
name="owners"
|
||||
field="autocomplete"
|
||||
fieldProps={{items: this.getStaffList()}} />
|
||||
fieldProps={{items: ticketUtils.getStaffList({staffList}, 'toAutocomplete')}} />
|
||||
</div>
|
||||
<div className="ticket-query-filters__third-row">
|
||||
<FormField
|
||||
label={i18n('STATUS')}
|
||||
name="closed"
|
||||
field="select"
|
||||
fieldProps={{
|
||||
items: this.getStatusItems(),
|
||||
className: 'ticket-query-filters__drop-down'
|
||||
}} />
|
||||
<FormField
|
||||
label={i18n('TAGS')}
|
||||
name="tags"
|
||||
@ -131,8 +134,8 @@ class TicketQueryFilters extends React.Component {
|
||||
id: author.id*1,
|
||||
profilePic: author.profilePic,
|
||||
isStaff: author.isStaff * 1,
|
||||
content: author.profilePic !== undefined ? this.renderStaffOption(author) : author.name,
|
||||
contentOnSelected: author.profilePic !== undefined ? this.renderStaffSelected(author) : author.name
|
||||
content: author.profilePic !== undefined ? ticketUtils.renderStaffOption(author) : author.name,
|
||||
contentOnSelected: author.profilePic !== undefined ? ticketUtils.renderStaffSelected(author) : author.name
|
||||
}});
|
||||
});
|
||||
}
|
||||
@ -159,29 +162,9 @@ class TicketQueryFilters extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderStaffOption(staff) {
|
||||
return (
|
||||
<div className="ticket-query-filters__staff-option" key={`staff-option-${staff.id}`}>
|
||||
<img className="ticket-query-filters__staff-option__profile-pic" src={this.getStaffProfilePic(staff)}/>
|
||||
<span className="ticket-query-filters__staff-option__name">{staff.name}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderStaffSelected(staff) {
|
||||
return (
|
||||
<div className="ticket-query-filters__staff-selected" key={`staff-selected-${staff.id}`}>
|
||||
<img className="ticket-query-filters__staff-selected__profile-pic" src={this.getStaffProfilePic(staff)}/>
|
||||
<span className="ticket-query-filters__staff-selected__name">{staff.name}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
addTag(tag) {
|
||||
const { formState } = this.props;
|
||||
let selectedTagsId = formState.tags.concat(this.tagsNametoTagsId(this.getSelectedTagsName([tag])));
|
||||
|
||||
this.onChangeFormState({...formState, tags: selectedTagsId});
|
||||
this.onChangeFormState({...formState, tags: [...formState.tags, tag]});
|
||||
}
|
||||
|
||||
autorsComparer(autorList, autorSelectedList) {
|
||||
@ -212,18 +195,23 @@ class TicketQueryFilters extends React.Component {
|
||||
let selectedDepartments = [];
|
||||
|
||||
if(selectedDepartmentsId !== undefined) {
|
||||
let departments = this.getDepartmentsItems();
|
||||
selectedDepartments = departments.filter(item => _.includes(selectedDepartmentsId, item.id));
|
||||
selectedDepartments = selectedDepartmentsId.map(
|
||||
(departmentId) => this.getDepartmentsItems().find(_department => (_department.id === departmentId))
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
return selectedDepartments;
|
||||
}
|
||||
|
||||
getSelectedStaffs(selectedStaffsId) {
|
||||
const { staffList } = this.props;
|
||||
let selectedStaffs = [];
|
||||
|
||||
if(selectedStaffsId !== undefined) {
|
||||
let staffs = this.getStaffList();
|
||||
selectedStaffs = staffs.filter(staff => _.includes(selectedStaffsId, staff.id));
|
||||
selectedStaffs = selectedStaffsId.map(
|
||||
(staffId) => ticketUtils.getStaffList({staffList}, 'toAutocomplete').find(_staff => (_staff.id === staffId))
|
||||
);
|
||||
}
|
||||
|
||||
return selectedStaffs;
|
||||
@ -233,33 +221,14 @@ class TicketQueryFilters extends React.Component {
|
||||
let selectedTagsName = [];
|
||||
|
||||
if(selectedTagsId !== undefined) {
|
||||
let tagList = this.getTags();
|
||||
let selectedTags = tagList.filter(item => _.includes(selectedTagsId, item.id));
|
||||
selectedTagsName = selectedTags.map(tag => tag.name);
|
||||
selectedTagsName = selectedTagsId.map(
|
||||
(tagId) => (this.getTags().find(_tag => (_tag.id === tagId)) || {}).name
|
||||
);
|
||||
}
|
||||
|
||||
return selectedTagsName;
|
||||
}
|
||||
|
||||
getStaffList() {
|
||||
const { staffList, } = this.props;
|
||||
let newStaffList = staffList.map(staff => {
|
||||
return {
|
||||
id: JSON.parse(staff.id),
|
||||
name: staff.name.toLowerCase(),
|
||||
color: 'gray',
|
||||
contentOnSelected: this.renderStaffSelected(staff),
|
||||
content: this.renderStaffOption(staff),
|
||||
}
|
||||
});
|
||||
|
||||
return newStaffList;
|
||||
}
|
||||
|
||||
getStaffProfilePic(staff) {
|
||||
return staff.profilePic ? API.getFileLink(staff.profilePic) : (API.getURL() + '/images/profile.png');
|
||||
}
|
||||
|
||||
getStatusItems() {
|
||||
let items = [
|
||||
{id: 0, name: 'Any', content: i18n('ANY')},
|
||||
@ -298,8 +267,8 @@ class TicketQueryFilters extends React.Component {
|
||||
true
|
||||
);
|
||||
|
||||
if(formEdited && formState.dateRange.valid) {
|
||||
const filtersForAPI = searchTicketsUtils.prepareFiltersForAPI(listConfigWithCompleteAuthorsList.filters);
|
||||
if(formEdited) {
|
||||
const filtersForAPI = searchTicketsUtils.getFiltersForAPI(listConfigWithCompleteAuthorsList.filters);
|
||||
const currentPath = window.location.pathname;
|
||||
const urlQuery = searchTicketsUtils.getFiltersForURL({
|
||||
filters: filtersForAPI,
|
||||
@ -312,24 +281,23 @@ class TicketQueryFilters extends React.Component {
|
||||
|
||||
removeTag(tag) {
|
||||
const { formState } = this.props;
|
||||
let tagListName = formState.tags;
|
||||
let newTagList = tagListName.filter(item => item !== tag);
|
||||
let selectedTags = this.tagsNametoTagsId(this.getSelectedTagsName(newTagList));
|
||||
|
||||
this.onChangeFormState({...formState, tags: selectedTags});
|
||||
this.onChangeFormState({...formState, tags: formState.tags.filter(item => item !== tag)});
|
||||
}
|
||||
|
||||
tagsNametoTagsId(selectedTagsName) {
|
||||
let tagList = this.getTags();
|
||||
let selectedTags = tagList.filter(item => _.includes(selectedTagsName, item.name));
|
||||
let selectedTagsId = selectedTags.map(tag => tag.id);
|
||||
let selectedTagsId = [];
|
||||
|
||||
if (selectedTagsName != undefined) {
|
||||
selectedTagsId = selectedTagsName.map(
|
||||
(tagName) => (this.getTags().find(_tag => (_tag.name === tagName)) || {}).id
|
||||
);
|
||||
}
|
||||
|
||||
return selectedTagsId;
|
||||
}
|
||||
|
||||
onChangeForm(data) {
|
||||
const newStartDate = data.dateRange.startDate ? data.dateRange.startDate : searchTicketsUtils.getDefaultLocalStartDate();
|
||||
const newEndDate = data.dateRange.endDate ? data.dateRange.endDate : searchTicketsUtils.getDefaultlocalEndDate();
|
||||
const departmentsId = data.departments.map(department => department.id);
|
||||
const staffsId = data.owners.map(staff => staff.id);
|
||||
const tagsName = this.tagsNametoTagsId(data.tags);
|
||||
@ -341,11 +309,6 @@ class TicketQueryFilters extends React.Component {
|
||||
owners: staffsId,
|
||||
departments: departmentsId,
|
||||
authors: authors,
|
||||
dateRange: {
|
||||
...data.dateRange,
|
||||
startDate: newStartDate,
|
||||
endDate: newEndDate
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -366,8 +329,8 @@ class TicketQueryFilters extends React.Component {
|
||||
id: author.id*1,
|
||||
isStaff: author.isStaff*1,
|
||||
profilePic: author.profilePic,
|
||||
content: author.profilePic !== undefined ? this.renderStaffOption(author) : author.name,
|
||||
contentOnSelected: author.profilePic !== undefined ? this.renderStaffSelected(author) : author.name
|
||||
content: author.profilePic !== undefined ? ticketUtils.renderStaffOption(author) : author.name,
|
||||
contentOnSelected: author.profilePic !== undefined ? ticketUtils.renderStaffSelected(author) : author.name
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&__status-drop-down > .drop-down__current-item {
|
||||
&__drop-down > .drop-down__current-item {
|
||||
background-color: $very-light-grey;
|
||||
|
||||
&:focus {
|
||||
|
@ -8,6 +8,8 @@ import TicketList from 'app-components/ticket-list';
|
||||
import Message from 'core-components/message';
|
||||
import searchFiltersActions from '../actions/search-filters-actions';
|
||||
import queryString from 'query-string';
|
||||
import searchTicketsUtils from 'lib-app/search-tickets-utils';
|
||||
import history from 'lib-app/history';
|
||||
|
||||
class TicketQueryList extends React.Component {
|
||||
|
||||
@ -21,13 +23,9 @@ class TicketQueryList extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
(this.state.error) ?
|
||||
<Message type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> :
|
||||
<TicketList {...this.getTicketListProps()}/>
|
||||
}
|
||||
</div>
|
||||
this.state.error ?
|
||||
<Message showCloseButton={false} type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> :
|
||||
<TicketList {...this.getTicketListProps()} />
|
||||
);
|
||||
}
|
||||
|
||||
@ -69,6 +67,7 @@ class TicketQueryList extends React.Component {
|
||||
orderBy: filters.orderBy ? JSON.parse(filters.orderBy) : filters.orderBy,
|
||||
showOrderArrows: true,
|
||||
onChangeOrderBy: onChangeOrderBy,
|
||||
showPageSizeDropdown: false
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import SessionStore from 'lib-app/session-store';
|
||||
import MentionsParser from 'lib-app/mentions-parser';
|
||||
import history from 'lib-app/history';
|
||||
import searchTicketsUtils from 'lib-app/search-tickets-utils';
|
||||
import ticketUtils from 'lib-app/ticket-utils';
|
||||
|
||||
import TicketEvent from 'app-components/ticket-event';
|
||||
import AreYouSure from 'app-components/are-you-sure';
|
||||
@ -71,6 +72,7 @@ class TicketViewer extends React.Component {
|
||||
editTags: false,
|
||||
editOwner: false,
|
||||
editDepartment: false,
|
||||
showTicketCommentErrorMessage: true
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@ -83,35 +85,61 @@ class TicketViewer extends React.Component {
|
||||
|
||||
render() {
|
||||
const { ticket, userStaff, userId, editable, allowAttachments, assignmentAllowed } = this.props;
|
||||
const { editTitle, loading, edit, editId } = this.state;
|
||||
const { closed, author, content, date, edited, file, events} = ticket;
|
||||
const showResponseField = (!closed && (editable || !assignmentAllowed));
|
||||
const lastComment = events.map(
|
||||
(event, index) => {
|
||||
return {...event, index}}
|
||||
).filter(
|
||||
(event) => event.type === "COMMENT"
|
||||
).at(-1);
|
||||
|
||||
const eventsWithModifiedComments = events.map(
|
||||
(event, index) => {
|
||||
return {...event, isLastComment: lastComment && index === lastComment.index && event.type === "COMMENT"};
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="ticket-viewer">
|
||||
{this.state.editTitle ? this.renderEditableTitle() : this.renderTitleHeader()}
|
||||
{editTitle ? this.renderEditableTitle() : this.renderTitleHeader()}
|
||||
{editable ? this.renderEditableHeaders() : this.renderHeaders()}
|
||||
<div className="ticket-viewer__content">
|
||||
<TicketEvent
|
||||
loading={this.state.loading}
|
||||
loading={loading}
|
||||
type="COMMENT"
|
||||
author={ticket.author}
|
||||
content={userStaff ? MentionsParser.parse(ticket.content) : ticket.content}
|
||||
isLastComment={!events.filter(event => event.type === "COMMENT").length}
|
||||
author={author}
|
||||
isTicketClosed={closed}
|
||||
content={userStaff ? MentionsParser.parse(content) : content}
|
||||
userStaff={userStaff}
|
||||
userId={userId}
|
||||
date={ticket.date}
|
||||
date={date}
|
||||
onEdit={this.onEdit.bind(this,0)}
|
||||
edited={ticket.edited}
|
||||
file={ticket.file}
|
||||
edit={this.state.edit && this.state.editId == 0}
|
||||
edited={edited}
|
||||
file={file}
|
||||
edit={edit && editId == 0}
|
||||
onToggleEdit={this.onToggleEdit.bind(this, 0)}
|
||||
allowAttachments={allowAttachments} />
|
||||
</div>
|
||||
<div className="ticket-viewer__comments">
|
||||
{ticket.events && ticket.events.map(this.renderTicketEvent.bind(this))}
|
||||
{eventsWithModifiedComments && eventsWithModifiedComments.map(this.renderTicketEvent.bind(this, closed))}
|
||||
</div>
|
||||
{(!ticket.closed && (editable || !assignmentAllowed)) ? this.renderResponseField() : (this.showDeleteButton()) ? this.renderDeleteTicketButton() : null}
|
||||
{showResponseField ? this.renderResponseField() : this.renderReopenCloseButtons()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderReopenCloseButtons() {
|
||||
return(
|
||||
<div className="ticket-viewer__reopen-close-buttons">
|
||||
{this.renderReopenTicketButton()}
|
||||
{this.showDeleteButton() ? this.renderDeleteTicketButton() : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderTitleHeader() {
|
||||
const {ticket, userStaff, userId} = this.props;
|
||||
const {ticketNumber, title, author, editedTitle, language} = ticket;
|
||||
@ -141,11 +169,11 @@ class TicketViewer extends React.Component {
|
||||
onChange={(e) => this.setState({newTitle: e.target.value})} />
|
||||
</div>
|
||||
<div className="ticket-viewer__edit-title__buttons">
|
||||
<Button disabled={this.state.editTitleLoading} type='primary' size="medium" onClick={() => this.setState({editTitle: false, newTitle: this.props.ticket.title})}>
|
||||
{this.state.editTitleLoading ? <Loading /> : <Icon name="times" />}
|
||||
<Button className="ticket-viewer__edit-title__button" disabled={this.state.editTitleLoading} type='primary' size="medium" onClick={() => this.setState({editTitle: false, newTitle: this.props.ticket.title})}>
|
||||
{this.state.editTitleLoading ? <Loading /> : <Icon name="times" size="large" />}
|
||||
</Button>
|
||||
<Button disabled={this.state.editTitleLoading} type='secondary' size="medium" onClick={this.changeTitle.bind(this)}>
|
||||
{this.state.editTitleLoading ? <Loading /> : <Icon name="check" />}
|
||||
<Button className="ticket-viewer__edit-title__button" disabled={this.state.editTitleLoading} type='secondary' size="medium" onClick={this.changeTitle.bind(this)}>
|
||||
{this.state.editTitleLoading ? <Loading /> : <Icon name="check" size="large" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@ -238,7 +266,7 @@ class TicketViewer extends React.Component {
|
||||
onRemoveClick={this.removeTag.bind(this)}
|
||||
onTagSelected={this.addTag.bind(this)}
|
||||
loading={this.state.tagSelectorLoading} />
|
||||
{this.renderCancelButton("Tags")}
|
||||
{this.renderCancelButton("Tags", "CLOSE")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -246,16 +274,20 @@ class TicketViewer extends React.Component {
|
||||
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')}
|
||||
</Button> :
|
||||
this.renderCloseTicketButton()}
|
||||
{this.renderCancelButton("Status", "CANCEL")}
|
||||
{this.props.ticket.closed ? this.renderReopenTicketButton() : this.renderCloseTicketButton()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderReopenTicketButton() {
|
||||
return (
|
||||
<Button type='secondary' size="medium" onClick={this.onReopenClick.bind(this)}>
|
||||
{i18n('RE_OPEN')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
renderHeaders() {
|
||||
const ticket = this.props.ticket;
|
||||
|
||||
@ -317,7 +349,7 @@ class TicketViewer extends React.Component {
|
||||
if(assignmentAllowed && ticket.owner) {
|
||||
ownerNode = (
|
||||
<a className="ticket-viewer__info-owner-name" href={this.searchTickets(filtersOnlyWithOwner)}>
|
||||
{ticket.owner.name}
|
||||
{ticketUtils.renderStaffSelected(ticket.owner)}
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
@ -344,7 +376,7 @@ class TicketViewer extends React.Component {
|
||||
className="ticket-viewer__editable-dropdown" items={items}
|
||||
selectedIndex={selectedIndex}
|
||||
onChange={this.onAssignmentChange.bind(this)} />
|
||||
{this.renderCancelButton("Owner")}
|
||||
{this.renderCancelButton("Owner", "CANCEL")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -360,7 +392,7 @@ class TicketViewer extends React.Component {
|
||||
departments={departments}
|
||||
selectedIndex={_.findIndex(departments, {id: ticket.department.id})}
|
||||
onChange={this.onDepartmentDropdownChanged.bind(this)} />
|
||||
{this.renderCancelButton("Department")}
|
||||
{this.renderCancelButton("Department", "CANCEL")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -387,26 +419,30 @@ class TicketViewer extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
renderCancelButton(option) {
|
||||
return <Button type='link' size="medium" onClick={() => this.setState({["edit"+option]: false})}>{i18n('CLOSE')}</Button>
|
||||
renderCancelButton(option, type) {
|
||||
return <Button type='link' size="medium" onClick={() => this.setState({["edit"+option]: false})}>{i18n(type)}</Button>
|
||||
}
|
||||
|
||||
renderTicketEvent(options, index) {
|
||||
renderTicketEvent(isTicketClosed, ticketEventObject, index) {
|
||||
const { userStaff, ticket, userId, allowAttachments } = this.props;
|
||||
const { edit, editId } = this.state;
|
||||
const { content, author, id} = ticketEventObject;
|
||||
|
||||
if(userStaff && typeof options.content === 'string') {
|
||||
options.content = MentionsParser.parse(options.content);
|
||||
if(userStaff && typeof content === 'string') {
|
||||
ticketEventObject.content = MentionsParser.parse(content);
|
||||
}
|
||||
|
||||
return (
|
||||
<TicketEvent
|
||||
{...options}
|
||||
author={(!_.isEmpty(options.author)) ? options.author : ticket.author}
|
||||
{...ticketEventObject}
|
||||
isLastComment={ticketEventObject.isLastComment}
|
||||
author={(!_.isEmpty(author)) ? author : ticket.author}
|
||||
userStaff={userStaff}
|
||||
isTicketClosed={isTicketClosed}
|
||||
userId={userId}
|
||||
onEdit={this.onEdit.bind(this, options.id)}
|
||||
edit={this.state.edit && this.state.editId == options.id}
|
||||
onToggleEdit={this.onToggleEdit.bind(this, options.id)}
|
||||
onEdit={this.onEdit.bind(this, id)}
|
||||
edit={edit && editId == id}
|
||||
onToggleEdit={this.onToggleEdit.bind(this, id)}
|
||||
key={index}
|
||||
allowAttachments={allowAttachments} />
|
||||
);
|
||||
@ -427,14 +463,15 @@ class TicketViewer extends React.Component {
|
||||
</div>
|
||||
<div className="ticket-viewer__response-field row">
|
||||
<FormField name="content" validation="TEXT_AREA" required field="textarea" fieldProps={{allowImages: allowAttachments}} />
|
||||
<div className="ticket-viewer__response-buttons">
|
||||
{allowAttachments ? <FormField name="file" field="file" /> : null}
|
||||
<SubmitButton type="secondary">{i18n('RESPOND_TICKET')}</SubmitButton>
|
||||
</div>
|
||||
<div className="ticket-viewer__buttons-column">
|
||||
<div className="ticket-viewer__buttons-row">
|
||||
{(this.showDeleteButton()) ? this.renderDeleteTicketButton() : null}
|
||||
{this.renderCloseTicketButton()}
|
||||
<div className="ticket-viewer__response-container">
|
||||
<div className="ticket-viewer__response-buttons">
|
||||
{allowAttachments ? <FormField name="file" field="file" /> : null}
|
||||
<SubmitButton type="secondary">{i18n('RESPOND_TICKET')}</SubmitButton>
|
||||
</div>
|
||||
<div className="ticket-viewer__buttons-column">
|
||||
<div className="ticket-viewer__buttons-row">
|
||||
{this.renderCloseTicketButton()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -494,8 +531,16 @@ class TicketViewer extends React.Component {
|
||||
}
|
||||
|
||||
renderCommentError() {
|
||||
const { showTicketCommentErrorMessage } = this.state;
|
||||
|
||||
return (
|
||||
<Message className="ticket-viewer__message" type="error">{i18n('TICKET_COMMENT_ERROR')}</Message>
|
||||
<Message
|
||||
showMessage={showTicketCommentErrorMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showTicketCommentErrorMessage")}
|
||||
className="ticket-viewer__message"
|
||||
type="error">
|
||||
{i18n('TICKET_COMMENT_ERROR')}
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
|
||||
@ -636,7 +681,7 @@ class TicketViewer extends React.Component {
|
||||
}
|
||||
}).then((result) => {
|
||||
this.onTicketModification(result);
|
||||
history.push('/admin/panel/tickets/my-tickets/');
|
||||
history.push(history.goBack());
|
||||
});
|
||||
}
|
||||
|
||||
@ -722,16 +767,16 @@ class TicketViewer extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
onEdit(ticketeventid,{content}) {
|
||||
onEdit(ticketeventid, {content}) {
|
||||
this.setState({
|
||||
loading: true
|
||||
});
|
||||
const data = {};
|
||||
|
||||
if(ticketeventid){
|
||||
data.ticketEventId = ticketeventid
|
||||
}else{
|
||||
data.ticketNumber = this.props.ticket.ticketNumber
|
||||
if(ticketeventid) {
|
||||
data.ticketEventId = ticketeventid;
|
||||
} else {
|
||||
data.ticketNumber = this.props.ticket.ticketNumber;
|
||||
}
|
||||
|
||||
API.call({
|
||||
@ -740,7 +785,7 @@ class TicketViewer extends React.Component {
|
||||
data,
|
||||
TextEditor.getContentFormData(content)
|
||||
)
|
||||
}).then(this.onEditCommentSuccess.bind(this), this.onFailCommentFail.bind(this));
|
||||
}).then(this.onEditCommentSuccess.bind(this), this.onEditCommentFail.bind(this));
|
||||
}
|
||||
|
||||
onEditCommentSuccess() {
|
||||
@ -754,10 +799,11 @@ class TicketViewer extends React.Component {
|
||||
this.onTicketModification();
|
||||
}
|
||||
|
||||
onFailCommentFail() {
|
||||
onEditCommentFail() {
|
||||
this.setState({
|
||||
loading: false,
|
||||
commentError: true
|
||||
commentError: true,
|
||||
showTicketCommentErrorMessage: true
|
||||
});
|
||||
}
|
||||
|
||||
@ -780,6 +826,7 @@ class TicketViewer extends React.Component {
|
||||
loading: false,
|
||||
commentValue: TextEditor.createEmpty(),
|
||||
commentError: false,
|
||||
commentFile: null,
|
||||
commentEdited: false
|
||||
});
|
||||
|
||||
@ -789,7 +836,8 @@ class TicketViewer extends React.Component {
|
||||
onCommentFail() {
|
||||
this.setState({
|
||||
loading: false,
|
||||
commentError: true
|
||||
commentError: true,
|
||||
showTicketCommentErrorMessage: true
|
||||
});
|
||||
}
|
||||
|
||||
@ -800,37 +848,19 @@ class TicketViewer extends React.Component {
|
||||
}
|
||||
|
||||
getStaffAssignmentItems() {
|
||||
const { userDepartments, userId, ticket } = this.props;
|
||||
const { staffMembers, ticket } = this.props;
|
||||
let staffAssignmentItems = [
|
||||
{content: i18n('NONE'), contentOnSelected: i18n('NONE'), id: 0}
|
||||
];
|
||||
|
||||
if(_.some(userDepartments, {id: ticket.department.id})) {
|
||||
staffAssignmentItems.push({
|
||||
content: i18n('ASSIGN_TO_ME'),
|
||||
contentOnSelected: this.getCurrentStaff().name,
|
||||
id: userId
|
||||
});
|
||||
}
|
||||
|
||||
staffAssignmentItems = staffAssignmentItems.concat(
|
||||
_.map(
|
||||
this.getStaffList(),
|
||||
({id, name}) => ({content: name, contentOnSelected: name, id: id*1})
|
||||
ticketUtils.getStaffList({staffList: staffMembers, ticket}, 'toDropDown').map(
|
||||
({id, content}) => ({content, id: id*1})
|
||||
)
|
||||
);
|
||||
|
||||
return staffAssignmentItems;
|
||||
}
|
||||
|
||||
getStaffList() {
|
||||
const { userId, staffMembers, ticket } = this.props;
|
||||
|
||||
return _.filter(staffMembers, ({id, departments}) => {
|
||||
return (id != userId) && _.some(departments, {id: ticket.department.id});
|
||||
})
|
||||
}
|
||||
|
||||
getCurrentStaff() {
|
||||
const { userId, staffMembers, ticket } = this.props;
|
||||
|
||||
@ -843,18 +873,24 @@ class TicketViewer extends React.Component {
|
||||
|
||||
showDeleteButton() {
|
||||
const { ticket, userLevel, userId, userStaff } = this.props;
|
||||
const { owner, author, closed } = ticket || {};
|
||||
const { staff, id } = author || {};
|
||||
|
||||
if(!ticket.owner) {
|
||||
if(!owner) {
|
||||
if(userLevel === 3) return true;
|
||||
if(userId == ticket.author.id*1) {
|
||||
if((userStaff && ticket.author.staff) || (!userStaff && !ticket.author.staff)){
|
||||
return true;
|
||||
}
|
||||
if(userId == id*1) {
|
||||
return (userStaff && staff && closed);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default connect((store) => {
|
||||
|
@ -33,6 +33,9 @@
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
width: 250px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
&__edited-title-text {
|
||||
@ -77,7 +80,10 @@
|
||||
margin-right: 6px;
|
||||
|
||||
.input__text {
|
||||
height: 25px;
|
||||
height: 30px;
|
||||
text-align: center;
|
||||
padding-top: 12px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,6 +94,14 @@
|
||||
align-items: center;
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
&__edit-title__button {
|
||||
width: 50px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__number {
|
||||
color: white;
|
||||
margin-right: 30px;
|
||||
@ -108,7 +122,7 @@
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
background-color: $light-grey;
|
||||
padding: 0 15px 30px 15px;
|
||||
padding: 10px 15px 30px 15px;
|
||||
|
||||
&-container {
|
||||
display: flex;
|
||||
@ -168,7 +182,15 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__reopen-close-buttons {
|
||||
width: 230px;
|
||||
display: flex;
|
||||
align-content: left;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__response {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
position: relative;
|
||||
|
||||
@ -206,16 +228,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
&-buttons {
|
||||
&-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
}
|
||||
&-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
width: 50%;
|
||||
min-width: 50%;
|
||||
}
|
||||
|
||||
&__delete-button {
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1151px) {
|
||||
|
@ -124,13 +124,15 @@ class TopicViewer extends React.Component {
|
||||
}
|
||||
|
||||
renderEditModal() {
|
||||
let props = {
|
||||
topicId: this.props.id,
|
||||
onChange: this.props.onChange,
|
||||
const {id, onChange, name, icon, iconColor} = this.props;
|
||||
|
||||
const props = {
|
||||
topicId: id,
|
||||
onChange,
|
||||
defaultValues: {
|
||||
title: this.props.name,
|
||||
icon: this.props.icon,
|
||||
iconColor: this.props.iconColor,
|
||||
title: name,
|
||||
icon,
|
||||
color: iconColor,
|
||||
private: this.props.private * 1
|
||||
}
|
||||
};
|
||||
|
@ -16,6 +16,9 @@ import Message from 'core-components/message';
|
||||
import Widget from 'core-components/widget';
|
||||
import WidgetTransition from 'core-components/widget-transition';
|
||||
|
||||
import Captcha from 'app/main/captcha';
|
||||
|
||||
const MAX_FREE_LOGIN_ATTEMPTS = 3;
|
||||
class AdminLoginPage extends React.Component {
|
||||
|
||||
state = {
|
||||
@ -24,11 +27,14 @@ class AdminLoginPage extends React.Component {
|
||||
recoverFormErrors: {},
|
||||
recoverSent: false,
|
||||
loadingLogin: false,
|
||||
loadingRecover: false
|
||||
loadingRecover: false,
|
||||
showRecoverSentMessage: true,
|
||||
showEmailOrPassordErrorMessage: true
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!prevProps.session.failed && this.props.session.failed) {
|
||||
this.setState({showEmailOrPassordErrorMessage : true});
|
||||
this.refs.loginForm.refs.password.focus();
|
||||
}
|
||||
}
|
||||
@ -72,6 +78,7 @@ class AdminLoginPage extends React.Component {
|
||||
className="admin-login-page__login-form-container__login-form__fields__remember"
|
||||
field="checkbox" />
|
||||
</div>
|
||||
{this.props.session.loginAttempts > MAX_FREE_LOGIN_ATTEMPTS ? this.renderLoginCaptcha() : null}
|
||||
<div className="admin-login-page__login-form-container__login-form__submit-button">
|
||||
<SubmitButton>{i18n('LOG_IN')}</SubmitButton>
|
||||
</div>
|
||||
@ -87,6 +94,14 @@ class AdminLoginPage extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderLoginCaptcha() {
|
||||
return(
|
||||
<div className={`main-home-page__${this.props.sitekey ? "captcha" : "no-captcha"}`}>
|
||||
<Captcha ref="captcha" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderPasswordRecovery() {
|
||||
return (
|
||||
<div className="admin-login-page__recovery-form-container">
|
||||
@ -96,31 +111,34 @@ class AdminLoginPage extends React.Component {
|
||||
}
|
||||
|
||||
renderRecoverStatus() {
|
||||
let status = null;
|
||||
const { showRecoverSentMessage, recoverSent } = this.state;
|
||||
|
||||
if (this.state.recoverSent) {
|
||||
status = (
|
||||
<Message className="admin-login-page__message" type="info" leftAligned>
|
||||
{i18n('RECOVER_SENT')}
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
|
||||
return status;
|
||||
return (
|
||||
recoverSent ?
|
||||
<Message
|
||||
showMessage={showRecoverSentMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showRecoverSentMessage")}
|
||||
className="admin-login-page__message"
|
||||
type="info"
|
||||
leftAligned>
|
||||
{i18n('RECOVER_SENT')}
|
||||
</Message> :
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
renderErrorStatus() {
|
||||
let status = null;
|
||||
|
||||
if (this.props.session.failed) {
|
||||
status = (
|
||||
<Message className="admin-login-page__error" type="error">
|
||||
{i18n('EMAIL_OR_PASSWORD')}
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
|
||||
return status;
|
||||
return (
|
||||
this.props.session.failed ?
|
||||
<Message
|
||||
showMessage={this.state.showEmailOrPassordErrorMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showEmailOrPassordErrorMessage")}
|
||||
className="admin-login-page__error"
|
||||
type="error">
|
||||
{i18n('EMAIL_OR_PASSWORD')}
|
||||
</Message> :
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
getLoginFormProps() {
|
||||
@ -135,10 +153,7 @@ class AdminLoginPage extends React.Component {
|
||||
}
|
||||
|
||||
getRecoverFormProps() {
|
||||
const {
|
||||
loadingRecover,
|
||||
recoverFormErrors
|
||||
} = this.state;
|
||||
const { loadingRecover, recoverFormErrors } = this.state;
|
||||
|
||||
return {
|
||||
loading: loadingRecover,
|
||||
@ -210,7 +225,8 @@ class AdminLoginPage extends React.Component {
|
||||
onRecoverPasswordSent() {
|
||||
this.setState({
|
||||
loadingRecover: false,
|
||||
recoverSent: true
|
||||
recoverSent: true,
|
||||
showRecoverSentMessage: true
|
||||
});
|
||||
}
|
||||
|
||||
@ -224,10 +240,17 @@ class AdminLoginPage extends React.Component {
|
||||
this.refs.recoverForm.refs.email.focus();
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default connect((store) => {
|
||||
return {
|
||||
session: store.session
|
||||
session: store.session,
|
||||
sitekey: store.config.reCaptchaKey
|
||||
};
|
||||
})(AdminLoginPage);
|
||||
|
@ -31,4 +31,10 @@
|
||||
&__error {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
&__captcha {
|
||||
margin: 10px auto 20px;
|
||||
height: 78px;
|
||||
width: 304px;
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import Form from 'core-components/form';
|
||||
import FormField from 'core-components/form-field';
|
||||
import SubmitButton from 'core-components/submit-button';
|
||||
import TextEditor from 'core-components/text-editor';
|
||||
import Icon from 'core-components/icon';
|
||||
|
||||
class AdminPanelViewArticle extends React.Component {
|
||||
|
||||
@ -65,23 +66,22 @@ class AdminPanelViewArticle extends React.Component {
|
||||
renderArticlePreview(article) {
|
||||
return (
|
||||
<div className="admin-panel-view-article__content">
|
||||
<div className="admin-panel-view-article__edit-buttons">
|
||||
<Button size="medium" onClick={this.onDeleteClick.bind(this, article)}>
|
||||
{i18n('DELETE')}
|
||||
</Button>
|
||||
<Button className="admin-panel-view-article__edit-button" size="medium" onClick={this.onEditClick.bind(this, article)} type="tertiary">
|
||||
{i18n('EDIT')}
|
||||
</Button>
|
||||
<div className="admin-panel-view-article__header-wrapper">
|
||||
<Header title={article.title} />
|
||||
<div className="admin-panel-view-article__header-buttons">
|
||||
<span onClick={this.onEditClick.bind(this, article)}>
|
||||
<Icon className="admin-panel-view-article__edit-icon" name="pencil" />
|
||||
</span>
|
||||
<span onClick={this.onDeleteClick.bind(this, article)} >
|
||||
<Icon className="admin-panel-view-article__edit-icon" name="trash" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="admin-panel-view-article__article">
|
||||
<Header title={article.title}/>
|
||||
|
||||
<div className="admin-panel-view-article__article-content ql-editor">
|
||||
<div dangerouslySetInnerHTML={{__html: MentionsParser.parse(article.content)}}/>
|
||||
</div>
|
||||
<div className="admin-panel-view-article__last-edited">
|
||||
{i18n('LAST_EDITED_IN', {date: DateTransformer.transformToString(article.lastEdited)})}
|
||||
</div>
|
||||
<div className="admin-panel-view-article__article-content ql-editor">
|
||||
<div dangerouslySetInnerHTML={{__html: MentionsParser.parse(article.content)}}/>
|
||||
</div>
|
||||
<div className="admin-panel-view-article__last-edited">
|
||||
{i18n('LAST_EDITED_IN', {date: DateTransformer.transformToString(article.lastEdited)})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -4,17 +4,25 @@
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
&__edit-buttons {
|
||||
&__header-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
&__header-buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 200px;
|
||||
margin-bottom: 20px;
|
||||
width: 50px;
|
||||
margin-bottom: 5px;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
&__edit-button {
|
||||
margin-right: 20px;
|
||||
&__edit-icon {
|
||||
color: $grey;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__last-edited {
|
||||
|
@ -2,6 +2,10 @@
|
||||
|
||||
&__menu {
|
||||
margin: 0 auto 20px auto;
|
||||
width: 300px;
|
||||
min-width: 300px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
@ -1,14 +1,12 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Bar, HorizontalBar } from 'react-chartjs-2';
|
||||
|
||||
import date from 'lib-app/date';
|
||||
import API from 'lib-app/api-call';
|
||||
import i18n from 'lib-app/i18n';
|
||||
import StatCard from 'app-components/stat-card';
|
||||
import statsUtils from 'lib-app/stats-utils';
|
||||
import date from 'lib-app/date';
|
||||
|
||||
import Header from 'core-components/header';
|
||||
import Tooltip from 'core-components/tooltip';
|
||||
import Form from 'core-components/form';
|
||||
import FormField from 'core-components/form-field';
|
||||
import Icon from 'core-components/icon';
|
||||
@ -21,7 +19,7 @@ class AdminPanelStats extends React.Component {
|
||||
state = {
|
||||
loading: true,
|
||||
rawForm: {
|
||||
dateRange: this.getInitialDateRange(),
|
||||
period: 0,
|
||||
departments: [],
|
||||
owners: [],
|
||||
tags: []
|
||||
@ -29,29 +27,19 @@ class AdminPanelStats extends React.Component {
|
||||
ticketData: {}
|
||||
};
|
||||
|
||||
getInitialDateRange() {
|
||||
let firstDayOfMonth = new Date();
|
||||
firstDayOfMonth.setDate(1);
|
||||
firstDayOfMonth.setHours(0);
|
||||
firstDayOfMonth.setMinutes(0);
|
||||
let todayAtNight = new Date();
|
||||
todayAtNight.setHours(23);
|
||||
todayAtNight.setMinutes(59);
|
||||
return {
|
||||
startDate: date.getFullDate(firstDayOfMonth),
|
||||
endDate: date.getFullDate(todayAtNight)
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.retrieveStats();
|
||||
statsUtils.retrieveStats({
|
||||
rawForm: this.getFormWithDateRange(this.state.rawForm),
|
||||
tags: this.props.tags
|
||||
}).then(({data}) => {
|
||||
this.setState({ticketData: data, loading: false});
|
||||
}).catch((error) => {
|
||||
if (showLogs) console.error('ERROR: ', error);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
loading,
|
||||
rawForm
|
||||
} = this.state;
|
||||
const { loading, rawForm, ticketData } = this.state;
|
||||
|
||||
return (
|
||||
<div className="admin-panel-stats">
|
||||
@ -60,7 +48,7 @@ class AdminPanelStats extends React.Component {
|
||||
<div className="admin-panel-stats__form__container">
|
||||
<div className="admin-panel-stats__form__container__row">
|
||||
<div className="admin-panel-stats__form__container__col">
|
||||
<FormField name="dateRange" label={i18n('DATE')} field="date-range" fieldProps={{defaultValue: rawForm.dateRange}} />
|
||||
<FormField name="period" label={i18n('DATE')} field="select" fieldProps={{size: 'large', items: [{content: i18n('LAST_7_DAYS')}, {content: i18n('LAST_30_DAYS')}, {content: i18n('LAST_90_DAYS')}, {content: i18n('LAST_365_DAYS')}]}} />
|
||||
<FormField name="tags" label={i18n('TAGS')} field="tag-selector" fieldProps={{items: this.getTagItems()}} />
|
||||
</div>
|
||||
<div className="admin-panel-stats__form__container__col">
|
||||
@ -90,98 +78,20 @@ class AdminPanelStats extends React.Component {
|
||||
<span className="separator" />
|
||||
</div>
|
||||
</div>
|
||||
{loading ? <div className="admin-panel-stats__loading"><Loading backgrounded size="large" /></div> : this.renderStatistics()}
|
||||
{
|
||||
loading ?
|
||||
<div className="admin-panel-stats__loading"><Loading backgrounded size="large" /></div> :
|
||||
statsUtils.renderStatistics({showStatCards: true, showStatsByHours: true, showStatsByDays: true, ticketData})
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderStatistics() {
|
||||
const primaryBlueWithTransparency = (alpha) => `rgba(32, 184, 197, ${alpha})`;
|
||||
const ticketsByHoursChartData = {
|
||||
labels: Array.from(Array(24).keys()),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Created Tickets by Hour',
|
||||
backgroundColor: primaryBlueWithTransparency(0.2),
|
||||
borderColor: primaryBlueWithTransparency(1),
|
||||
borderWidth: 1,
|
||||
hoverBackgroundColor: primaryBlueWithTransparency(0.4),
|
||||
hoverBorderColor: primaryBlueWithTransparency(1),
|
||||
data: this.state.ticketData.created_by_hour
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const primaryGreenWithTransparency = (alpha) => `rgba(130, 202, 156, ${alpha})`;
|
||||
const ticketsByWeekdayChartData = {
|
||||
labels: [
|
||||
i18n('MONDAY'),
|
||||
i18n('TUESDAY'),
|
||||
i18n('WEDNESDAY'),
|
||||
i18n('THURSDAY'),
|
||||
i18n('FRIDAY'),
|
||||
i18n('SATURDAY'),
|
||||
i18n('SUNDAY')
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Created Tickets by Weekday',
|
||||
backgroundColor: primaryGreenWithTransparency(0.2),
|
||||
borderColor: primaryGreenWithTransparency(1),
|
||||
borderWidth: 1,
|
||||
hoverBackgroundColor: primaryGreenWithTransparency(0.4),
|
||||
hoverBorderColor: primaryGreenWithTransparency(1),
|
||||
data: this.state.ticketData.created_by_weekday
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.renderStatCards()}
|
||||
<Bar
|
||||
options={this.getStatsOptions('y')}
|
||||
data={ticketsByHoursChartData}
|
||||
legend={{onClick: null}} /> {/* Weird, but if you only set the legend here, it changes that of the HorizontalBar next too*/}
|
||||
<HorizontalBar
|
||||
options={this.getStatsOptions('x')}
|
||||
data={ticketsByWeekdayChartData}
|
||||
legend={{onClick: null}} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderStatCards() {
|
||||
const {created, open, closed, instant, reopened} = this.state.ticketData;
|
||||
|
||||
return (
|
||||
<div className="admin-panel-stats__card-list">
|
||||
<StatCard label={i18n('CREATED')} description={i18n('CREATED_DESCRIPTION')} value={created} isPercentage={false} />
|
||||
<StatCard label={i18n('OPEN')} description={i18n('OPEN_DESCRIPTION')} value={open} isPercentage={false} />
|
||||
<StatCard label={i18n('CLOSED')} description={i18n('CLOSED_DESCRIPTION')} value={closed} isPercentage={false} />
|
||||
<StatCard label={i18n('INSTANT')} description={i18n('INSTANT_DESCRIPTION')} value={100*instant / closed} isPercentage={true} />
|
||||
<StatCard label={i18n('REOPENED')} description={i18n('REOPENED_DESCRIPTION')} value={100*reopened / created} isPercentage={true} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
getStatsOptions(axis) {
|
||||
return {
|
||||
scales: {
|
||||
[`${axis}Axes`]: [{
|
||||
ticks: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clearFormValues(event) {
|
||||
event.preventDefault();
|
||||
this.setState({
|
||||
rawForm: {
|
||||
dateRange: this.getInitialDateRange(),
|
||||
period: 0,
|
||||
departments: [],
|
||||
owners: [],
|
||||
tags: []
|
||||
@ -199,10 +109,6 @@ class AdminPanelStats extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
getSelectedTagIds() {
|
||||
return this.props.tags.filter(tag => _.includes(this.state.rawForm.tags, tag.name)).map(tag => tag.id);
|
||||
}
|
||||
|
||||
getStaffItems() {
|
||||
const getStaffProfilePic = (staff) => {
|
||||
return staff.profilePic ? API.getFileLink(staff.profilePic) : (API.getURL() + '/images/profile.png');
|
||||
@ -252,30 +158,31 @@ class AdminPanelStats extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
retrieveStats() {
|
||||
const { rawForm } = this.state;
|
||||
const { startDate, endDate } = rawForm.dateRange;
|
||||
API.call({
|
||||
path: '/system/get-stats',
|
||||
data: {
|
||||
dateRange: "[" + startDate.toString() + "," + endDate.toString() + "]",
|
||||
departments: "[" + rawForm.departments.map(department => department.id) + "]",
|
||||
owners: "[" + rawForm.owners.map(owner => owner.id) + "]",
|
||||
tags: "[" + this.getSelectedTagIds() + "]"
|
||||
}
|
||||
}).then(({data}) => {
|
||||
this.setState({ticketData: data, loading: false});
|
||||
}).catch((error) => {
|
||||
if (showLogs) console.error('ERROR: ', error);
|
||||
})
|
||||
}
|
||||
|
||||
onFormChange(newFormState) {
|
||||
this.setState({rawForm: newFormState});
|
||||
}
|
||||
|
||||
onFormSubmit() {
|
||||
this.retrieveStats();
|
||||
statsUtils.retrieveStats({
|
||||
rawForm: this.getFormWithDateRange(this.state.rawForm),
|
||||
tags: this.props.tags
|
||||
}).then(({data}) => {
|
||||
this.setState({ticketData: data, loading: false});
|
||||
}).catch((error) => {
|
||||
if (showLogs) console.error('ERROR: ', error);
|
||||
});
|
||||
}
|
||||
|
||||
getFormWithDateRange(form) {
|
||||
const {startDate, endDate} = statsUtils.getDateRangeFromPeriod(form.period);
|
||||
|
||||
return {
|
||||
...form,
|
||||
dateRange: {
|
||||
startDate,
|
||||
endDate
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,9 @@ class AdminPanelAdvancedSettings extends React.Component {
|
||||
messageContent: '',
|
||||
selectedAPIKey: -1,
|
||||
APIKeys: [],
|
||||
error: ''
|
||||
error: '',
|
||||
showMessage: true,
|
||||
showAPIKeyMessage: true
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@ -36,7 +38,7 @@ class AdminPanelAdvancedSettings extends React.Component {
|
||||
|
||||
render() {
|
||||
const { config } = this.props;
|
||||
const { messageType, error, selectedAPIKey } = this.state;
|
||||
const { messageType, error, selectedAPIKey, showAPIKeyMessage } = this.state;
|
||||
|
||||
return (
|
||||
<div className="admin-panel-advanced-settings">
|
||||
@ -88,12 +90,21 @@ class AdminPanelAdvancedSettings extends React.Component {
|
||||
<span className="separator" />
|
||||
</div>
|
||||
<div className="col-md-12 admin-panel-advanced-settings__api-keys">
|
||||
<div className="col-md-12 admin-panel-advanced-settings__api-keys-title">{i18n('REGISTRATION_API_KEYS')}</div>
|
||||
<div className="col-md-12 admin-panel-advanced-settings__api-keys-title">{i18n('API_KEYS')}</div>
|
||||
<div className="col-md-4">
|
||||
<Listing {...this.getListingProps()} />
|
||||
</div>
|
||||
<div className="col-md-8 admin-panel-advanced-settings__api-keys__container">
|
||||
{error ? <Message type="error">{i18n(error)}</Message> : ((selectedAPIKey === -1) ? this.renderNoKey() : this.renderKey())}
|
||||
{
|
||||
error ?
|
||||
<Message
|
||||
showMessage={showAPIKeyMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showAPIKeyMessage")}
|
||||
type="error">
|
||||
{i18n(error)}
|
||||
</Message> :
|
||||
((selectedAPIKey === -1) ? this.renderNoKey() : this.renderKey())
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -102,11 +113,16 @@ class AdminPanelAdvancedSettings extends React.Component {
|
||||
}
|
||||
|
||||
renderMessage() {
|
||||
const { messageType, messageTitle, messageContent } = this.state;
|
||||
const { messageType, messageTitle, messageContent, showMessage } = this.state;
|
||||
|
||||
return (
|
||||
<Message className="admin-panel-advanced-settings__message" type={messageType} title={messageTitle}>
|
||||
{messageContent}
|
||||
<Message
|
||||
showMessage={showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
|
||||
className="admin-panel-advanced-settings__message"
|
||||
type={messageType}
|
||||
title={messageTitle}>
|
||||
{messageContent}
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
@ -154,7 +170,7 @@ class AdminPanelAdvancedSettings extends React.Component {
|
||||
|
||||
getListingProps() {
|
||||
return {
|
||||
title: i18n('REGISTRATION_API_KEYS'),
|
||||
title: i18n('API_KEYS'),
|
||||
enableAddNew: true,
|
||||
items: this.state.APIKeys.map((item) => {
|
||||
return {
|
||||
@ -270,10 +286,11 @@ class AdminPanelAdvancedSettings extends React.Component {
|
||||
this.setState({
|
||||
messageType: 'success',
|
||||
messageTitle: null,
|
||||
showMessage: true,
|
||||
messageContent: config['mandatory-login'] ? i18n('MANDATORY_LOGIN_DISABLED') : i18n('MANDATORY_LOGIN_ENABLED')
|
||||
});
|
||||
dispatch(ConfigActions.updateData());
|
||||
}).catch(() => this.setState({messageType: 'error', messageTitle: null, messageContent: i18n('ERROR_UPDATING_SETTINGS')}));
|
||||
}).catch(() => this.setState({messageType: 'error', showMessage: true, messageTitle: null, messageContent: i18n('ERROR_UPDATING_SETTINGS')}));
|
||||
}
|
||||
|
||||
onAreYouSureRegistrationOk(password) {
|
||||
@ -287,11 +304,12 @@ class AdminPanelAdvancedSettings extends React.Component {
|
||||
}).then(() => {
|
||||
this.setState({
|
||||
messageType: 'success',
|
||||
showMessage: true,
|
||||
messageTitle: null,
|
||||
messageContent: config['registration'] ? i18n('REGISTRATION_DISABLED') : i18n('REGISTRATION_ENABLED')
|
||||
});
|
||||
dispatch(ConfigActions.updateData());
|
||||
}).catch(() => this.setState({messageType: 'error', messageTitle: null, messageContent: i18n('ERROR_UPDATING_SETTINGS')}));
|
||||
}).catch(() => this.setState({messageType: 'error', showMessage: true, messageTitle: null, messageContent: i18n('ERROR_UPDATING_SETTINGS')}));
|
||||
}
|
||||
|
||||
onImportCSV(event) {
|
||||
@ -303,23 +321,31 @@ class AdminPanelAdvancedSettings extends React.Component {
|
||||
path: '/system/csv-import',
|
||||
dataAsForm: true,
|
||||
data: {
|
||||
file: file,
|
||||
password: password
|
||||
file,
|
||||
password
|
||||
}
|
||||
})
|
||||
.then((result) => this.setState({
|
||||
messageType: 'success',
|
||||
messageType: 'success',
|
||||
showMessage: true,
|
||||
messageTitle: i18n('SUCCESS_IMPORTING_CSV_DESCRIPTION'),
|
||||
messageContent: (result.data.length) ? (
|
||||
<div>
|
||||
{i18n('ERRORS_FOUND')}
|
||||
<ul>
|
||||
{result.data.map((error) => <li>{error}</li>)}
|
||||
{result.data.map((error, index) => <li key={`csv-file__key-${index}`} >{error}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
) : null
|
||||
}))
|
||||
.catch(() => this.setState({messageType: 'error', messageTitle: null, messageContent: i18n('INVALID_FILE')}));
|
||||
.catch((error) => {
|
||||
this.setState({
|
||||
messageType: 'error',
|
||||
showMessage: true,
|
||||
messageTitle: null,
|
||||
messageContent: i18n(error.message)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
onBackupDatabase() {
|
||||
@ -347,12 +373,17 @@ class AdminPanelAdvancedSettings extends React.Component {
|
||||
data: {
|
||||
password: password
|
||||
}
|
||||
}).then(() => this.setState({messageType: 'success', messageTitle: null, messageContent: i18n('SUCCESS_DELETING_ALL_USERS')}
|
||||
)).catch(() => this.setState({messageType: 'error', messageTitle: null, messageContent: i18n('ERROR_DELETING_ALL_USERS')}));
|
||||
}).then(() => this.setState({messageType: 'success', showMessage: true, messageTitle: null, messageContent: i18n('SUCCESS_DELETING_ALL_USERS')}
|
||||
)).catch(() => this.setState({messageType: 'error', showMessage: true, messageTitle: null, messageContent: i18n('ERROR_DELETING_ALL_USERS')}));
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default connect((store) => {
|
||||
return {
|
||||
config: store.config
|
||||
|
@ -65,12 +65,12 @@ class AdminPanelCustomTagsModal extends React.Component {
|
||||
<FormField name="name" label={i18n('NAME')} fieldProps={{size: 'large'}} required={nameRequired} />
|
||||
<FormField name="color" label={i18n('COLOR')} decorator={ColorSelector} />
|
||||
<div className='admin-panel-custom-tags-modal__actions'>
|
||||
<Button onClick={this.onDiscardClick.bind(this)} size="small">
|
||||
{i18n('CANCEL')}
|
||||
</Button>
|
||||
<SubmitButton type="secondary" size="small">
|
||||
{i18n('SAVE')}
|
||||
</SubmitButton>
|
||||
<Button onClick={this.onDiscardClick.bind(this)} size="small">
|
||||
{i18n('CANCEL')}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
&__actions{
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ class AdminPanelEmailSettings extends React.Component {
|
||||
<div className="admin-panel-email-settings__image-container">
|
||||
<FormField className="admin-panel-email-settings__image-header-url"
|
||||
label={i18n('IMAGE_HEADER_URL')} name="headerImage" required
|
||||
infoMessage={i18n('IMAGE_HEADER_URL_DESCRIPTION')}
|
||||
infoMessage={i18n('IMAGE_HEADER_DESCRIPTION')}
|
||||
fieldProps={{size: 'large'}} />
|
||||
<SubmitButton className="admin-panel-email-settings__image-header-submit" type="secondary"
|
||||
size="small">{i18n('SAVE')}</SubmitButton>
|
||||
@ -169,7 +169,7 @@ class AdminPanelEmailSettings extends React.Component {
|
||||
</SubmitButton>
|
||||
</div>
|
||||
</Form>
|
||||
<Message className="admin-panel-email-settings__imap-message" type="info">
|
||||
<Message showCloseButton={false} className="admin-panel-email-settings__imap-message" type="info">
|
||||
{i18n('IMAP_POLLING_DESCRIPTION', {url: `${apiRoot}/system/email-polling`})}
|
||||
</Message>
|
||||
</div>
|
||||
@ -179,39 +179,61 @@ class AdminPanelEmailSettings extends React.Component {
|
||||
}
|
||||
|
||||
renderForm() {
|
||||
const {
|
||||
form,
|
||||
language,
|
||||
selectedIndex,
|
||||
edited
|
||||
} = this.state;
|
||||
const { form, language, selectedIndex, edited } = this.state;
|
||||
const { template, text2, text3} = form;
|
||||
|
||||
return (
|
||||
<div className="col-md-9">
|
||||
<FormField label={i18n('LANGUAGE')} decorator={LanguageSelector} value={language}
|
||||
onChange={event => this.onItemChange(selectedIndex, event.target.value)}
|
||||
fieldProps={{
|
||||
type: 'allowed',
|
||||
type: 'supported',
|
||||
size: 'medium'
|
||||
}} />
|
||||
<Form {...this.getFormProps()}>
|
||||
<div className="row">
|
||||
<div className="col-md-7">
|
||||
<FormField label={i18n('SUBJECT')} name="subject" validation="TITLE" required
|
||||
fieldProps={{size: 'large'}} />
|
||||
<FormField
|
||||
fieldProps={{size: 'large'}}
|
||||
label={i18n('SUBJECT')}
|
||||
name="subject"
|
||||
validation="TITLE"
|
||||
required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FormField key="text1" label={i18n('TEXT') + '1'} name="text1" validation="TEXT_AREA" required
|
||||
decorator={'textarea'}
|
||||
fieldProps={{className: 'admin-panel-email-settings__text-area'}} />
|
||||
{(form.text2) ?
|
||||
<FormField key="text2" label={i18n('TEXT') + '2'} name="text2" validation="TEXT_AREA" required
|
||||
decorator={'textarea'}
|
||||
fieldProps={{className: 'admin-panel-email-settings__text-area'}} /> : null}
|
||||
{(form.text3) ?
|
||||
<FormField key="text3" label={i18n('TEXT') + '3'} name="text3" validation="TEXT_AREA" required
|
||||
decorator={'textarea'}
|
||||
fieldProps={{className: 'admin-panel-email-settings__text-area'}} /> : null}
|
||||
<FormField
|
||||
fieldProps={{className: 'admin-panel-email-settings__text-area'}}
|
||||
label={i18n('TEXT') + '1'}
|
||||
key="text1"
|
||||
name="text1"
|
||||
validation="TEXT_AREA"
|
||||
required
|
||||
decorator={'textarea'} />
|
||||
{
|
||||
(text2 || text2 === "") ?
|
||||
<FormField
|
||||
fieldProps={{className: 'admin-panel-email-settings__text-area'}}
|
||||
label={i18n('TEXT') + '2'}
|
||||
key="text2"
|
||||
name="text2"
|
||||
validation="TEXT_AREA"
|
||||
required
|
||||
decorator={'textarea'} /> :
|
||||
null
|
||||
}
|
||||
{
|
||||
((text3 || text3 === "") && (template !== "USER_PASSWORD" && template !== "USER_EMAIL")) ?
|
||||
<FormField
|
||||
fieldProps={{className: 'admin-panel-email-settings__text-area'}}
|
||||
label={i18n('TEXT') + '3'}
|
||||
key="text3"
|
||||
name="text3"
|
||||
validation={(template !== "USER_PASSWORD" && template !== "USER_EMAIL") ? "TEXT_AREA" : ""}
|
||||
required={(template !== "USER_PASSWORD" && template !== "USER_EMAIL")}
|
||||
decorator={'textarea'} /> :
|
||||
null
|
||||
}
|
||||
|
||||
<div className="admin-panel-email-settings__actions">
|
||||
<div className="admin-panel-email-settings__optional-buttons">
|
||||
@ -223,11 +245,7 @@ class AdminPanelEmailSettings extends React.Component {
|
||||
{edited ? this.renderDiscardButton() : null}
|
||||
</div>
|
||||
<div className="admin-panel-email-settings__save-button">
|
||||
<SubmitButton
|
||||
key="submit-email-template"
|
||||
type="secondary"
|
||||
size="small"
|
||||
onClick={(e) => {e.preventDefault(); this.onFormSubmit(form);}}>
|
||||
<SubmitButton key="submit-email-template" type="secondary" size="small">
|
||||
{i18n('SAVE')}
|
||||
</SubmitButton>
|
||||
</div>
|
||||
@ -257,16 +275,19 @@ class AdminPanelEmailSettings extends React.Component {
|
||||
}
|
||||
|
||||
getFormProps() {
|
||||
const { form, errors, loadingForm } = this.state;
|
||||
|
||||
return {
|
||||
values: this.state.form,
|
||||
errors: this.state.errors,
|
||||
loading: this.state.loadingForm,
|
||||
values: form,
|
||||
errors,
|
||||
loading: loadingForm,
|
||||
onChange: (form) => {
|
||||
this.setState({form, edited: true})
|
||||
},
|
||||
onValidateErrors: (errors) => {
|
||||
this.setState({errors})
|
||||
},
|
||||
onSubmit: this.onFormSubmit.bind(this, form)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,8 @@ class AdminPanelSystemPreferences extends React.Component {
|
||||
message: null,
|
||||
values: {
|
||||
maintenance: false,
|
||||
}
|
||||
},
|
||||
showMessage: true
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@ -113,11 +114,29 @@ class AdminPanelSystemPreferences extends React.Component {
|
||||
}
|
||||
|
||||
renderMessage() {
|
||||
switch (this.state.message) {
|
||||
const { message, showMessage } = this.state;
|
||||
|
||||
switch (message) {
|
||||
case 'success':
|
||||
return <Message className="admin-panel-system-preferences__message" type="success">{i18n('SETTINGS_UPDATED')}</Message>;
|
||||
return (
|
||||
<Message
|
||||
showMessage={showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
|
||||
className="admin-panel-system-preferences__message"
|
||||
type="success">
|
||||
{i18n('SETTINGS_UPDATED')}
|
||||
</Message>
|
||||
);
|
||||
case 'fail':
|
||||
return <Message className="admin-panel-system-preferences__message" type="error">{i18n('ERROR_UPDATING_SETTINGS')}</Message>;
|
||||
return (
|
||||
<Message
|
||||
showMessage={showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
|
||||
className="admin-panel-system-preferences__message"
|
||||
type="error">
|
||||
{i18n('ERROR_UPDATING_SETTINGS')}
|
||||
</Message>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@ -126,13 +145,14 @@ class AdminPanelSystemPreferences extends React.Component {
|
||||
onFormChange(form) {
|
||||
const { language, supportedLanguages, allowedLanguages } = form;
|
||||
const languageIndex = _.indexOf(languageKeys, language);
|
||||
const updatedSupportedLanguages = _.filter(supportedLanguages, (supportedIndex) => _.includes(allowedLanguages, supportedIndex));
|
||||
|
||||
this.setState({
|
||||
values: _.extend({}, form, {
|
||||
language: _.includes(supportedLanguages, languageIndex) ? language : languageKeys[supportedLanguages[0]],
|
||||
supportedLanguages: _.filter(supportedLanguages, (supportedIndex) => _.includes(allowedLanguages, supportedIndex))
|
||||
}),
|
||||
message: null
|
||||
values: _.extend({}, form, {
|
||||
language: _.includes(updatedSupportedLanguages, languageIndex) ? language : languageKeys[updatedSupportedLanguages[0]],
|
||||
supportedLanguages: updatedSupportedLanguages
|
||||
}),
|
||||
message: null
|
||||
});
|
||||
}
|
||||
|
||||
@ -154,14 +174,15 @@ class AdminPanelSystemPreferences extends React.Component {
|
||||
'allowedLanguages': JSON.stringify(form.allowedLanguages.map(index => languageKeys[index])),
|
||||
'supportedLanguages': JSON.stringify(form.supportedLanguages.map(index => languageKeys[index]))
|
||||
}
|
||||
}).then(this.onSubmitSuccess.bind(this)).catch(() => this.setState({loading: false, message: 'fail'}));
|
||||
}).then(this.onSubmitSuccess.bind(this)).catch(() => this.setState({loading: false, message: 'fail', showMessage: true}));
|
||||
}
|
||||
|
||||
onSubmitSuccess() {
|
||||
this.recoverSettings();
|
||||
this.setState({
|
||||
message: 'success',
|
||||
loading: false
|
||||
loading: false,
|
||||
showMessage: true
|
||||
});
|
||||
}
|
||||
|
||||
@ -201,7 +222,8 @@ class AdminPanelSystemPreferences extends React.Component {
|
||||
|
||||
onRecoverSettingsFail() {
|
||||
this.setState({
|
||||
message: 'error'
|
||||
message: 'error',
|
||||
showMessage: true
|
||||
});
|
||||
}
|
||||
|
||||
@ -210,6 +232,12 @@ class AdminPanelSystemPreferences extends React.Component {
|
||||
this.setState({loading: true});
|
||||
this.recoverSettings();
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default AdminPanelSystemPreferences;
|
||||
|
@ -43,6 +43,9 @@ class AdminPanelDepartments extends React.Component {
|
||||
editedAddDepartmentForm: false,
|
||||
editedDefaultDepartmentForm: false,
|
||||
errorMessage: null,
|
||||
showErrorMessage: true,
|
||||
showSuccessMessage: true,
|
||||
showDefaultDepartmentErrorMessage: true,
|
||||
errors: {},
|
||||
defaultDepartmentError: null,
|
||||
form: {
|
||||
@ -55,7 +58,7 @@ class AdminPanelDepartments extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { errorMessage, formLoading, selectedIndex } = this.state;
|
||||
const { errorMessage, formLoading, selectedIndex, showErrorMessage } = this.state;
|
||||
|
||||
return (
|
||||
<div className="admin-panel-departments">
|
||||
@ -65,7 +68,13 @@ class AdminPanelDepartments extends React.Component {
|
||||
<Listing {...this.getListingProps()} />
|
||||
</div>
|
||||
<div className="col-md-8">
|
||||
{errorMessage ? <Message type="error">{i18n(errorMessage)}</Message> : null}
|
||||
{
|
||||
errorMessage ?
|
||||
<Message showMessage={showErrorMessage} onCloseMessage={this.onCloseMessage.bind(this, "showErrorMessage")} type="error">
|
||||
{i18n(errorMessage)}
|
||||
</Message> :
|
||||
null
|
||||
}
|
||||
<Form {...this.getFormProps()}>
|
||||
<div className="admin-panel-departments__container">
|
||||
<FormField className="admin-panel-departments__container__name" label={i18n('NAME')} name="name" validation="NAME" required fieldProps={{size: 'large'}} />
|
||||
@ -98,15 +107,19 @@ class AdminPanelDepartments extends React.Component {
|
||||
}
|
||||
|
||||
renderDefaultDepartmentForm() {
|
||||
const { defaultDepartmentError, formLoading } = this.state;
|
||||
const { defaultDepartmentError, formLoading, showSuccessMessage, showDefaultDepartmentErrorMessage } = this.state;
|
||||
|
||||
return (
|
||||
<div className="admin-panel-departments__default-departments-container">
|
||||
<span className="separator" />
|
||||
{(defaultDepartmentError !== null) ?
|
||||
((!defaultDepartmentError) ?
|
||||
<Message type="success">{i18n('SETTINGS_UPDATED')}</Message> :
|
||||
<Message type="error">{i18n(defaultDepartmentError)}</Message>) :
|
||||
<Message showMessage={showSuccessMessage} onCloseMessage={this.onCloseMessage.bind(this, "showSuccessMessage")} type="success">
|
||||
{i18n('SETTINGS_UPDATED')}
|
||||
</Message> :
|
||||
<Message showMessage={showDefaultDepartmentErrorMessage} onCloseMessage={this.onCloseMessage.bind(this, "showDefaultDepartmentErrorMessage")} type="error">
|
||||
{i18n(defaultDepartmentError)}
|
||||
</Message>) :
|
||||
null}
|
||||
<Form {...this.getDefaultDepartmentFormProps()} className="admin-panel-departments__default-departments-container__form">
|
||||
<div className="admin-panel-departments__default-departments-container__form__fields" >
|
||||
@ -234,6 +247,12 @@ class AdminPanelDepartments extends React.Component {
|
||||
};
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
|
||||
onItemChange(index) {
|
||||
if(this.state.editedAddDepartmentForm) {
|
||||
AreYouSure.openModal(i18n('WILL_LOSE_CHANGES'), this.updateForm.bind(this, index));
|
||||
@ -255,8 +274,8 @@ class AdminPanelDepartments extends React.Component {
|
||||
}
|
||||
}).then(() => {
|
||||
this.retrieveDepartments(true);
|
||||
this.setState({formLoading: false, errorMessage: false, defaultDepartmentError: false});
|
||||
}).catch(result => this.setState({formLoading: false, defaultDepartmentError: result.message}));
|
||||
this.setState({formLoading: false, errorMessage: false, defaultDepartmentError: false, showSuccessMessage: true});
|
||||
}).catch(result => this.setState({formLoading: false, defaultDepartmentError: result.message, showDefaultDepartmentErrorMessage: true}));
|
||||
}
|
||||
|
||||
onFormSubmit(form) {
|
||||
@ -273,7 +292,7 @@ class AdminPanelDepartments extends React.Component {
|
||||
}).then(() => {
|
||||
this.setState({formLoading: false, errorMessage: false, defaultDepartmentError: null});
|
||||
this.retrieveDepartments();
|
||||
}).catch(result => this.setState({formLoading: false, errorMessage: result.message, defaultDepartmentError: null}));
|
||||
}).catch(result => this.setState({formLoading: false, errorMessage: result.message, showErrorMessage: true, defaultDepartmentError: null}));
|
||||
} else {
|
||||
API.call({
|
||||
path: '/system/add-department',
|
||||
@ -285,9 +304,9 @@ class AdminPanelDepartments extends React.Component {
|
||||
this.setState({formLoading: false,errorMessage: false, defaultDepartmentError: null});
|
||||
this.retrieveDepartments();
|
||||
this.onItemChange(-1);
|
||||
}).catch(() => {
|
||||
}).catch(result => {
|
||||
this.onItemChange.bind(this, -1);
|
||||
this.setState({formLoading: false, defaultDepartmentError: null});
|
||||
this.setState({formLoading: false, errorMessage: result.message, showErrorMessage: true, defaultDepartmentError: null});
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -316,7 +335,7 @@ class AdminPanelDepartments extends React.Component {
|
||||
this.onItemChange(-1);
|
||||
this.setState({defaultDepartmentError: null});
|
||||
})
|
||||
.catch(result => this.setState({errorMessage: result.message, defaultDepartmentError: null}));
|
||||
.catch(result => this.setState({errorMessage: result.message, showErrorMessage: true, defaultDepartmentError: null}));
|
||||
}
|
||||
|
||||
updateForm(index) {
|
||||
|
@ -50,7 +50,7 @@ class AdminPanelStaffMembers extends React.Component {
|
||||
<Icon name="user-plus" className="" /> {i18n('INVITE_STAFF')}
|
||||
</Button>
|
||||
</div>
|
||||
{(this.props.loading) ? <Loading backgrounded /> : <PeopleList list={this.getStaffList()} page={this.state.page} onPageSelect={(index) => this.setState({page: index+1})} />}
|
||||
{(this.props.loading) ? <Loading className="admin-panel-staff-members__loading" backgrounded size="large"/> : <PeopleList list={this.getStaffList()} page={this.state.page} onPageSelect={(index) => this.setState({page: index+1})} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -27,6 +27,11 @@
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
&__loading {
|
||||
min-width: 300px;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 415px) {
|
||||
.admin-panel-staff-members {
|
||||
&__drowpdown {
|
||||
|
@ -10,7 +10,6 @@ import API from 'lib-app/api-call';
|
||||
import SessionStore from 'lib-app/session-store';
|
||||
import TicketList from 'app-components/ticket-list';
|
||||
import AreYouSure from 'app-components/are-you-sure';
|
||||
// import Stats from 'app-components/stats';
|
||||
|
||||
import Form from 'core-components/form';
|
||||
import FormField from 'core-components/form-field';
|
||||
@ -19,6 +18,7 @@ import Message from 'core-components/message';
|
||||
import Button from 'core-components/button';
|
||||
import Icon from 'core-components/icon';
|
||||
import Loading from 'core-components/loading';
|
||||
import statsUtils from 'lib-app/stats-utils';
|
||||
|
||||
const INITIAL_API_VALUE = {
|
||||
page: 1,
|
||||
@ -52,30 +52,41 @@ class StaffEditor extends React.Component {
|
||||
department: undefined,
|
||||
departments: this.getUserDepartments(),
|
||||
closedTicketsShown: false,
|
||||
sendEmailOnNewTicket: this.props.sendEmailOnNewTicket
|
||||
sendEmailOnNewTicket: this.props.sendEmailOnNewTicket,
|
||||
loadingReInviteStaff: false,
|
||||
reInviteStaff: "",
|
||||
loadingStats: true,
|
||||
showMessage: true,
|
||||
showReInviteStaffMessage: true,
|
||||
rawForm: {
|
||||
departments: [],
|
||||
owners: [{id: this.props.staffId}],
|
||||
tags: []
|
||||
},
|
||||
ticketData: {},
|
||||
ticketListLoading: false
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.retrieveStaffMembers();
|
||||
this.retrieveTicketsAssigned(INITIAL_API_VALUE);
|
||||
statsUtils.retrieveStats({
|
||||
rawForm: this.state.rawForm
|
||||
}).then(({data}) => {
|
||||
this.setState({
|
||||
ticketData: data,
|
||||
loadingStats: false
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (showLogs) console.error('ERROR: ', error);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
name,
|
||||
level,
|
||||
profilePic,
|
||||
myAccount,
|
||||
staffId,
|
||||
staffList,
|
||||
userId
|
||||
} = this.props;
|
||||
const {
|
||||
message,
|
||||
tickets,
|
||||
loadingPicture,
|
||||
email
|
||||
} = this.state;
|
||||
console.log('State: ', this.state.rawForm);
|
||||
|
||||
const { name, level, profilePic, myAccount, staffId, staffList, userId } = this.props;
|
||||
const { message, tickets, loadingPicture, email } = this.state;
|
||||
const myData = _.filter(staffList, {id: `${staffId}`})[0];
|
||||
|
||||
return (
|
||||
@ -144,6 +155,8 @@ class StaffEditor extends React.Component {
|
||||
<div className="col-md-8">
|
||||
<div className="staff-editor__activity">
|
||||
<div className="staff-editor__activity-title">{i18n('ACTIVITY')}</div>
|
||||
{myData.lastLogin ? null : this.renderReInviteStaffButton()}
|
||||
{this.renderReInviteStaffMessage()}
|
||||
{this.renderStaffStats()}
|
||||
</div>
|
||||
</div>
|
||||
@ -154,9 +167,66 @@ class StaffEditor extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderReInviteStaffButton () {
|
||||
const inviteStaffButtonContent = <div><Icon name="user-plus" /> {i18n('INVITE_STAFF')}</div>;
|
||||
|
||||
return (
|
||||
<div className="staff-editor__staff-invitation-content">
|
||||
{i18n('USER_UNLOGGED_IN')}
|
||||
<Button onClick={this.onReInviteStaffButton.bind(this)} size="medium" type="secondary" className="staff-editor__staff-invitation-button" disabled={this.state.loadingReInviteStaff}>
|
||||
{this.state.loadingReInviteStaff ? <Loading /> : inviteStaffButtonContent}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderReInviteStaffMessage() {
|
||||
const { reInviteStaff, showReInviteStaffMessage } = this.state;
|
||||
|
||||
if (reInviteStaff) {
|
||||
return (
|
||||
<Message
|
||||
showMessage={showReInviteStaffMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showReInviteStaffMessage")}
|
||||
className="staff-editor__staff-invitation-message"
|
||||
type={reInviteStaff}
|
||||
leftAligned>
|
||||
{(reInviteStaff === "success") ? i18n('RESEND_STAFF_INVITATION_SUCCESS') : i18n('RESEND_STAFF_INVITATION_FAIL')}
|
||||
</Message>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
onReInviteStaffButton() {
|
||||
this.setState({
|
||||
loadingReInviteStaff: true
|
||||
})
|
||||
|
||||
API.call({
|
||||
path: '/staff/resend-invite-staff',
|
||||
data: {
|
||||
email: this.props.email
|
||||
}
|
||||
}).then(() => {
|
||||
this.setState({
|
||||
loadingReInviteStaff: false,
|
||||
reInviteStaff: 'success',
|
||||
showReInviteStaffMessage: true
|
||||
})
|
||||
}).catch(() => {
|
||||
this.setState({
|
||||
loadingReInviteStaff: false,
|
||||
reInviteStaff: 'error',
|
||||
showReInviteStaffMessage: true
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
renderMessage() {
|
||||
const { message } = this.state;
|
||||
let messageType = (message === 'FAIL') ? 'error' : 'success';
|
||||
const { message, showMessage } = this.state;
|
||||
const messageType = (message === 'FAIL') ? 'error' : 'success';
|
||||
let _message = null;
|
||||
|
||||
switch (message) {
|
||||
@ -180,7 +250,15 @@ class StaffEditor extends React.Component {
|
||||
break;
|
||||
}
|
||||
|
||||
return <Message className="staff-editor__message" type={messageType}>{i18n(_message)}</Message>;
|
||||
return (
|
||||
<Message
|
||||
showMessage={showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
|
||||
className="staff-editor__message"
|
||||
type={messageType}>
|
||||
{i18n(_message)}
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
|
||||
renderSendEmailOnNewTicketForm() {
|
||||
@ -221,7 +299,7 @@ class StaffEditor extends React.Component {
|
||||
|
||||
renderDepartmentsInfo() {
|
||||
const { departments } = this.state;
|
||||
const departmentsAssigned = this.getDepartments().filter((_department, index) => departments.includes(index))
|
||||
const departmentsAssigned = this.getDepartments().filter((_department, index) => departments.includes(index));
|
||||
|
||||
return (
|
||||
<Form values={{departments: Array.from({length: departmentsAssigned.length}, (value, index) => index)}}>
|
||||
@ -231,11 +309,17 @@ class StaffEditor extends React.Component {
|
||||
}
|
||||
|
||||
renderStaffStats() {
|
||||
// return (
|
||||
// <Stats staffId={this.props.staffId} type="staff" />
|
||||
// );
|
||||
const { loadingStats, ticketData } = this.state;
|
||||
|
||||
return null;
|
||||
return (
|
||||
<div className="admin-panel-stats">
|
||||
{
|
||||
loadingStats ?
|
||||
<Loading className="admin-panel-stats__loading" backgrounded size="large" /> :
|
||||
statsUtils.renderStatistics({showStatCards: true, showStatsByHours: true, ticketData})
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderTickets() {
|
||||
@ -244,7 +328,10 @@ class StaffEditor extends React.Component {
|
||||
<span className="separator" />
|
||||
<div className="staff-editor__tickets">
|
||||
<div className="staff-editor__tickets-title">{i18n('TICKETS_ASSIGNED')}</div>
|
||||
<TicketList {...this.getTicketListProps()} />
|
||||
{this.state.ticketListLoading ?
|
||||
<Loading className="staff-editor__ticketlist-loading" backgrounded size="large"/> :
|
||||
<TicketList {...this.getTicketListProps()} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -276,16 +363,8 @@ class StaffEditor extends React.Component {
|
||||
}
|
||||
|
||||
getTicketListProps() {
|
||||
const {
|
||||
staffId,
|
||||
departments
|
||||
} = this.props;
|
||||
const {
|
||||
tickets,
|
||||
page,
|
||||
pages,
|
||||
closedTicketsShown
|
||||
} = this.state;
|
||||
const { staffId, departments } = this.props;
|
||||
const { tickets, page, pages, closedTicketsShown } = this.state;
|
||||
|
||||
return {
|
||||
type: 'secondary',
|
||||
@ -311,6 +390,7 @@ class StaffEditor extends React.Component {
|
||||
departmentIndexes.push(index);
|
||||
}
|
||||
});
|
||||
|
||||
return departmentIndexes;
|
||||
}
|
||||
|
||||
@ -344,11 +424,12 @@ class StaffEditor extends React.Component {
|
||||
}
|
||||
|
||||
onSubmit(eventType, form) {
|
||||
const {
|
||||
myAccount,
|
||||
staffId,
|
||||
onChange
|
||||
} = this.props;
|
||||
this.setState({
|
||||
loadingStats: true,
|
||||
ticketListLoading: true
|
||||
});
|
||||
|
||||
const { myAccount, staffId, onChange } = this.props;
|
||||
let departments;
|
||||
|
||||
if(form.departments) {
|
||||
@ -369,21 +450,33 @@ class StaffEditor extends React.Component {
|
||||
}
|
||||
}).then(() => {
|
||||
this.retrieveStaffMembers();
|
||||
window.scrollTo(0,0);
|
||||
this.setState({message: eventType});
|
||||
window.scrollTo(0,250);
|
||||
this.setState({
|
||||
message: eventType,
|
||||
showMessage: true,
|
||||
ticketListLoading: false
|
||||
});
|
||||
|
||||
statsUtils.retrieveStats({
|
||||
rawForm: this.state.rawForm
|
||||
}).then(({data}) => {
|
||||
this.setState({ticketData: data, loadingStats: false});
|
||||
}).catch((error) => {
|
||||
if (showLogs) console.error('ERROR: ', error);
|
||||
this.setState({loadingStats: false});
|
||||
});
|
||||
|
||||
this.retrieveTicketsAssigned({page: 1});
|
||||
onChange && onChange();
|
||||
}).catch(() => {
|
||||
window.scrollTo(0,0);
|
||||
this.setState({message: 'FAIL'});
|
||||
window.scrollTo(0,250);
|
||||
this.setState({message: 'FAIL', loadingStats: false, showMessage: true});
|
||||
});
|
||||
}
|
||||
|
||||
onDeleteClick() {
|
||||
const {
|
||||
staffId,
|
||||
onDelete
|
||||
} = this.props;
|
||||
const { staffId, onDelete } = this.props;
|
||||
|
||||
return API.call({
|
||||
path: '/staff/delete',
|
||||
data: {
|
||||
@ -391,16 +484,13 @@ class StaffEditor extends React.Component {
|
||||
}
|
||||
}).then(onDelete).catch(() => {
|
||||
window.scrollTo(0,0);
|
||||
this.setState({message: 'FAIL'});
|
||||
this.setState({message: 'FAIL', showMessage: true});
|
||||
});
|
||||
}
|
||||
|
||||
onProfilePicChange(event) {
|
||||
const {
|
||||
myAcount,
|
||||
staffId,
|
||||
onChange
|
||||
} = this.props;
|
||||
const { myAcount, staffId, onChange } = this.props;
|
||||
|
||||
this.setState({
|
||||
loadingPicture: true
|
||||
});
|
||||
@ -417,10 +507,11 @@ class StaffEditor extends React.Component {
|
||||
loadingPicture: false
|
||||
});
|
||||
|
||||
this.retrieveStaffMembers();
|
||||
onChange && onChange();
|
||||
}).catch(() => {
|
||||
window.scrollTo(0,0);
|
||||
this.setState({message: 'FAIL', loadingPicture: false});
|
||||
this.setState({message: 'FAIL', loadingPicture: false, showMessage: true});
|
||||
});
|
||||
}
|
||||
|
||||
@ -466,10 +557,7 @@ class StaffEditor extends React.Component {
|
||||
}
|
||||
|
||||
onClosedTicketsShownChange() {
|
||||
const {
|
||||
department,
|
||||
closedTicketsShown
|
||||
} = this.state;
|
||||
const { department, closedTicketsShown } = this.state;
|
||||
const newClosedValue = !closedTicketsShown;
|
||||
|
||||
this.setState({
|
||||
@ -492,6 +580,12 @@ class StaffEditor extends React.Component {
|
||||
department: newDepartmentFilter ? `[${newDepartmentFilter}]` : undefined
|
||||
}
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default connect((store) => {
|
||||
|
@ -174,6 +174,15 @@
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
&__ticketlist-loading {
|
||||
min-height: 361px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: $grey;
|
||||
}
|
||||
|
||||
&__separator {
|
||||
margin: 3px 0;
|
||||
}
|
||||
@ -213,11 +222,23 @@
|
||||
}
|
||||
|
||||
&__activity {
|
||||
|
||||
&-title {
|
||||
margin-bottom: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
&__staff {
|
||||
&-invitation-content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
&-invitation-button {
|
||||
min-width: 180px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,8 @@ class AdminPanelCustomResponses extends React.Component {
|
||||
title: '',
|
||||
content: TextEditor.createEmpty(),
|
||||
language: this.props.language
|
||||
}
|
||||
},
|
||||
showErrorMessage: true
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@ -103,8 +104,16 @@ class AdminPanelCustomResponses extends React.Component {
|
||||
);
|
||||
}
|
||||
renderErrorMessage() {
|
||||
const { showErrorMessage, error } = this.state;
|
||||
|
||||
return(
|
||||
<Message className="admin-panel-custom-responses__message" type="error">{i18n(this.state.error)}</Message>
|
||||
<Message
|
||||
showMessage={showErrorMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showErrorMessage")}
|
||||
className="admin-panel-custom-responses__message"
|
||||
type="error">
|
||||
{i18n(error)}
|
||||
</Message>
|
||||
)
|
||||
}
|
||||
renderOptionalButtons() {
|
||||
@ -202,7 +211,7 @@ class AdminPanelCustomResponses extends React.Component {
|
||||
this.onItemChange(-1);
|
||||
}).catch((e) => {
|
||||
this.onItemChange.bind(this, -1)
|
||||
this.setState({error: e.message, formLoading:false});
|
||||
this.setState({error: e.message, formLoading:false, showErrorMessage: true});
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -267,6 +276,12 @@ class AdminPanelCustomResponses extends React.Component {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default connect((store) => {
|
||||
|
@ -26,17 +26,18 @@ class AdminPanelMyTickets extends React.Component {
|
||||
state = {
|
||||
closedTicketsShown: false,
|
||||
departmentId: null,
|
||||
pageSize: 10
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.retrieveMyTickets();
|
||||
this.retrieveMyTickets({});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="admin-panel-my-tickets">
|
||||
<Header title={i18n('MY_TICKETS')} description={i18n('MY_TICKETS_DESCRIPTION')} />
|
||||
{(this.props.error) ? <Message type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> : <TicketList {...this.getProps()} />}
|
||||
{(this.props.error) ? <Message showCloseButton={false} type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> : <TicketList {...this.getProps()} />}
|
||||
<div style={{textAlign: 'right', marginTop: 10}}>
|
||||
<Button onClick={this.onCreateTicket.bind(this)} type="secondary" size="medium">
|
||||
<Icon size="sm" name="plus" /> {i18n('CREATE_TICKET')}
|
||||
@ -68,10 +69,14 @@ class AdminPanelMyTickets extends React.Component {
|
||||
onClosedTicketsShownChange: this.onClosedTicketsShownChange.bind(this),
|
||||
pages,
|
||||
page,
|
||||
onPageChange: event => this.retrieveMyTickets(event.target.value),
|
||||
onPageChange: event => this.retrieveMyTickets({page: event.target.value}),
|
||||
onDepartmentChange: departmentId => {
|
||||
this.setState({departmentId});
|
||||
this.retrieveMyTickets(1, closedTicketsShown, departmentId);
|
||||
this.setState({departmentId})
|
||||
this.retrieveMyTickets({page: 1, departmentId});
|
||||
},
|
||||
onPageSizeChange: pageSize => {
|
||||
this.setState({pageSize});
|
||||
this.retrieveMyTickets({page: 1, pageSize});
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -81,7 +86,7 @@ class AdminPanelMyTickets extends React.Component {
|
||||
return {
|
||||
closedTicketsShown: !state.closedTicketsShown
|
||||
};
|
||||
}, () => this.retrieveMyTickets());
|
||||
}, () => this.retrieveMyTickets({}));
|
||||
}
|
||||
|
||||
onCreateTicket() {
|
||||
@ -100,11 +105,11 @@ class AdminPanelMyTickets extends React.Component {
|
||||
|
||||
onCreateTicketSuccess() {
|
||||
ModalContainer.closeModal();
|
||||
this.retrieveMyTickets();
|
||||
this.retrieveMyTickets({});
|
||||
}
|
||||
|
||||
retrieveMyTickets(page = this.props.page, closed = this.state.closedTicketsShown, departmentId = this.state.departmentId) {
|
||||
this.props.dispatch(AdminDataAction.retrieveMyTickets(page, closed * 1, departmentId));
|
||||
retrieveMyTickets({page = this.props.page, closed = this.state.closedTicketsShown, departmentId = this.state.departmentId, pageSize = this.state.pageSize}) {
|
||||
this.props.dispatch(AdminDataAction.retrieveMyTickets({page, closed: closed * 1, departmentId, pageSize}));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,8 @@
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
bottom: 35px;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,10 +20,11 @@ class AdminPanelNewTickets extends React.Component {
|
||||
|
||||
state = {
|
||||
departmentId: null,
|
||||
pageSize: 10
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.retrieveNewTickets();
|
||||
this.retrieveNewTickets({});
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -31,8 +32,8 @@ class AdminPanelNewTickets extends React.Component {
|
||||
return (
|
||||
<div className="admin-panel-new-tickets">
|
||||
<Header title={i18n('NEW_TICKETS')} description={i18n('NEW_TICKETS_DESCRIPTION')} />
|
||||
{(noDepartments) ? <Message className="admin-panel-new-tickets__department-warning" type="warning">{i18n('NO_DEPARTMENT_ASSIGNED')}</Message> : null}
|
||||
{(this.props.error) ? <Message type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> : <TicketList {...this.getProps()}/>}
|
||||
{(noDepartments) ? <Message showCloseButton={false} className="admin-panel-new-tickets__department-warning" type="warning">{i18n('NO_DEPARTMENT_ASSIGNED')}</Message> : null}
|
||||
{(this.props.error) ? <Message showCloseButton={false} type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> : <TicketList {...this.getProps()} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -47,16 +48,20 @@ class AdminPanelNewTickets extends React.Component {
|
||||
ticketPath: '/admin/panel/tickets/view-ticket/',
|
||||
page: this.props.page,
|
||||
pages: this.props.pages,
|
||||
onPageChange: event => this.retrieveNewTickets(event.target.value),
|
||||
onPageChange: event => this.retrieveNewTickets({page: event.target.value}),
|
||||
onDepartmentChange: departmentId => {
|
||||
this.setState({departmentId});
|
||||
this.retrieveNewTickets(1, departmentId);
|
||||
this.retrieveNewTickets({page: 1, departmentId});
|
||||
},
|
||||
onPageSizeChange: pageSize => {
|
||||
this.setState({pageSize});
|
||||
this.retrieveNewTickets({page: 1, pageSize});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
retrieveNewTickets(page = this.props.page, departmentId = this.state.departmentId) {
|
||||
this.props.dispatch(AdminDataAction.retrieveNewTickets(page, departmentId));
|
||||
retrieveNewTickets({page = this.props.page, departmentId = this.state.departmentId, pageSize = this.state.pageSize }) {
|
||||
this.props.dispatch(AdminDataAction.retrieveNewTickets({page, departmentId, pageSize}));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ export function updateSearchTicketsFromURL() {
|
||||
const currentSearchParams = queryString.parse(currentSearch);
|
||||
const showFilters = (currentSearch !== SEARCH_TICKETS_INITIAL_QUERY) && currentSearchParams.custom;
|
||||
|
||||
if((showFilters !== undefined) && currentSearchParams.useInitialValues) store.dispatch(searchFiltersActions.changeShowFilters(!showFilters));
|
||||
if(showFilters !== undefined && currentSearchParams.useInitialValues) store.dispatch(searchFiltersActions.changeShowFilters(!showFilters));
|
||||
|
||||
store.dispatch(searchFiltersActions.changeFilters(listConfig));
|
||||
store.dispatch(searchFiltersActions.retrieveSearchTickets(
|
||||
@ -38,7 +38,8 @@ export function updateSearchTicketsFromURL() {
|
||||
...store.getState().searchFilters.ticketQueryListState,
|
||||
page: (currentSearchParams.page || INITIAL_PAGE)*1
|
||||
},
|
||||
searchTicketsUtils.prepareFiltersForAPI(listConfig.filters)
|
||||
searchTicketsUtils.getFiltersForAPI(listConfig.filters),
|
||||
currentSearchParams.pageSize
|
||||
));
|
||||
});
|
||||
}
|
||||
@ -49,7 +50,8 @@ updateSearchTicketsFromURL();
|
||||
class AdminPanelSearchTickets extends React.Component {
|
||||
|
||||
render() {
|
||||
const { listConfig } = this.props;
|
||||
const { listConfig, error } = this.props;
|
||||
|
||||
return (
|
||||
<div className="admin-panel-search-tickets">
|
||||
<div className="admin-panel-search-tickets__container">
|
||||
@ -67,10 +69,9 @@ class AdminPanelSearchTickets extends React.Component {
|
||||
</div>
|
||||
<TicketQueryFilters />
|
||||
{
|
||||
(this.props.error) ?
|
||||
<Message type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> :
|
||||
<TicketQueryList
|
||||
onChangeOrderBy={this.onChangeOrderBy.bind(this)} />
|
||||
error ?
|
||||
<Message showCloseButton={false} type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> :
|
||||
<TicketQueryList onChangeOrderBy={this.onChangeOrderBy.bind(this)} />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
@ -21,7 +21,9 @@ class AdminPanelBanUsers extends React.Component {
|
||||
listError: false,
|
||||
addBanStatus: 'none',
|
||||
emails: [],
|
||||
filteredEmails: []
|
||||
filteredEmails: [],
|
||||
showMessage: true,
|
||||
showListErrorMessage: true
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@ -29,10 +31,18 @@ class AdminPanelBanUsers extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { listError, showListErrorMessage } = this.state;
|
||||
|
||||
return (
|
||||
<div className="admin-panel-ban-users row">
|
||||
<Header title={i18n('BAN_USERS')} description={i18n('BAN_USERS_DESCRIPTION')} />
|
||||
{(this.state.listError) ? <Message type="error">{i18n('ERROR_RETRIEVING_BAN_LIST')}</Message> : this.renderContent()}
|
||||
{
|
||||
listError ?
|
||||
<Message showMessage={showListErrorMessage} onCloseMessage={this.onCloseMessage.bind(this, "showListErrorMessage")} type="error">
|
||||
{i18n('ERROR_RETRIEVING_BAN_LIST')}
|
||||
</Message> :
|
||||
this.renderContent()
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -59,11 +69,29 @@ class AdminPanelBanUsers extends React.Component {
|
||||
}
|
||||
|
||||
renderMessage() {
|
||||
switch (this.state.addBanStatus) {
|
||||
const { addBanStatus, showMessage } = this.state;
|
||||
|
||||
switch (addBanStatus) {
|
||||
case 'success':
|
||||
return <Message className="admin-panel-ban-users__form-message" type="success">{i18n('EMAIL_BANNED_SUCCESSFULLY')}</Message>;
|
||||
return (
|
||||
<Message
|
||||
showMessage={showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
|
||||
className="admin-panel-ban-users__form-message"
|
||||
type="success">
|
||||
{i18n('EMAIL_BANNED_SUCCESSFULLY')}
|
||||
</Message>
|
||||
);
|
||||
case 'fail':
|
||||
return <Message className="admin-panel-ban-users__form-message" type="error">{i18n('ERROR_BANNING_EMAIL')}</Message>;
|
||||
return (
|
||||
<Message
|
||||
showMessage={showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
|
||||
className="admin-panel-ban-users__form-message"
|
||||
type="error">
|
||||
{i18n('ERROR_BANNING_EMAIL')}
|
||||
</Message>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@ -119,10 +147,11 @@ class AdminPanelBanUsers extends React.Component {
|
||||
}
|
||||
}).then(() => {
|
||||
this.setState({
|
||||
addBanStatus: 'success'
|
||||
addBanStatus: 'success',
|
||||
showMessage: true
|
||||
});
|
||||
this.retrieveEmails();
|
||||
}).catch(() => this.setState({addBanStatus: 'fail', loadingForm: false}));
|
||||
}).catch(() => this.setState({addBanStatus: 'fail', loadingForm: false, showMessage: true}));
|
||||
}
|
||||
|
||||
onUnBanClick(email) {
|
||||
@ -150,10 +179,17 @@ class AdminPanelBanUsers extends React.Component {
|
||||
filteredEmails: result.data
|
||||
})).catch(() => this.setState({
|
||||
listError: true,
|
||||
showListErrorMessage: true,
|
||||
loadingList: false,
|
||||
loadingForm: false
|
||||
}));
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default AdminPanelBanUsers;
|
||||
|
@ -21,16 +21,16 @@ class AdminPanelCustomFieldForm extends React.Component {
|
||||
state = {
|
||||
loading: false,
|
||||
error: null,
|
||||
addForm: {},
|
||||
addForm: {
|
||||
name: "",
|
||||
description: ""
|
||||
},
|
||||
addFormOptions: [],
|
||||
showErrorMessage: true
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
loading,
|
||||
addForm,
|
||||
error
|
||||
} = this.state;
|
||||
const { loading, addForm, error } = this.state;
|
||||
|
||||
return (
|
||||
<div className="admin-panel-custom-field-form">
|
||||
@ -41,7 +41,8 @@ class AdminPanelCustomFieldForm extends React.Component {
|
||||
loading={loading}
|
||||
values={addForm}
|
||||
onChange={this.onAddFormChange.bind(this)}
|
||||
onSubmit={this.onSubmit.bind(this)}>
|
||||
onSubmit={this.onSubmit.bind(this)}
|
||||
onKeyDown={(event) => { if(event.key == 'Enter') event.preventDefault()}}>
|
||||
<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/>
|
||||
@ -58,9 +59,11 @@ class AdminPanelCustomFieldForm extends React.Component {
|
||||
}
|
||||
|
||||
renderErrorMessage() {
|
||||
const { error, showErrorMessage } = this.state;
|
||||
|
||||
return (
|
||||
<Message type="error">
|
||||
{this.state.error}
|
||||
<Message showMessage={showErrorMessage} onCloseMessage={this.onCloseMessage.bind(this, "showErrorMessage")} type="error">
|
||||
{i18n(error)}
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
@ -84,9 +87,14 @@ class AdminPanelCustomFieldForm extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
|
||||
onAddOptionClick(event) {
|
||||
event.preventDefault();
|
||||
|
||||
let addFormOptions = _.clone(this.state.addFormOptions);
|
||||
|
||||
addFormOptions.push("");
|
||||
@ -127,7 +135,7 @@ class AdminPanelCustomFieldForm extends React.Component {
|
||||
this.setState({loading: false, message: null});
|
||||
if(this.props.onChange) this.props.onChange();
|
||||
})
|
||||
.catch(result => this.setState({loading: false, error: result.message}));
|
||||
.catch(result => this.setState({loading: false, error: result.message, showErrorMessage: true}));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,5 +26,6 @@
|
||||
&__buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
@ -15,39 +15,53 @@ import Icon from 'core-components/icon';
|
||||
import ModalContainer from 'app-components/modal-container';
|
||||
import InviteUserWidget from 'app/admin/panel/users/invite-user-widget';
|
||||
|
||||
const DEFAULT_USERS_PARAMS = {
|
||||
page: 1,
|
||||
orderBy: 'id',
|
||||
desc: true,
|
||||
search: ''
|
||||
}
|
||||
|
||||
class AdminPanelListUsers extends React.Component {
|
||||
state = {
|
||||
loading: true,
|
||||
users: [],
|
||||
orderBy: 'id',
|
||||
desc: true,
|
||||
usersParams: DEFAULT_USERS_PARAMS,
|
||||
error: false,
|
||||
page: 1,
|
||||
pages: 1
|
||||
pages: 1,
|
||||
showMessage: true
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.retrieveUsers({
|
||||
page: 1,
|
||||
orderBy: 'id',
|
||||
desc: true,
|
||||
search: ''
|
||||
});
|
||||
this.retrieveUsers(DEFAULT_USERS_PARAMS);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="admin-panel-list-users">
|
||||
<Header title={i18n('LIST_USERS')} description={i18n('LIST_USERS_DESCRIPTION')} />
|
||||
{(this.state.error) ? <Message type="error">{i18n('ERROR_RETRIEVING_USERS')}</Message> : this.renderTableAndInviteButton()}
|
||||
{(this.state.error) ? <Message showCloseButton={false} type="error">{i18n('ERROR_RETRIEVING_USERS')}</Message> : this.renderTableAndInviteButton()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderTableAndInviteButton() {
|
||||
const { message, showMessage } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SearchBox className="admin-panel-list-users__search-box" placeholder={i18n('SEARCH_USERS')} onSearch={this.onSearch.bind(this)} />
|
||||
{
|
||||
(message === 'success') ?
|
||||
<Message
|
||||
showMessage={showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
|
||||
className="admin-panel-list-users__success-message"
|
||||
type="success">
|
||||
{i18n('INVITE_USER_SUCCESS')}
|
||||
</Message> :
|
||||
null
|
||||
}
|
||||
<Table {...this.getTableProps()} />
|
||||
<div style={{textAlign: 'right', marginTop: 10}}>
|
||||
<Button onClick={this.onInviteUser.bind(this)} type="secondary" size="medium">
|
||||
@ -59,14 +73,16 @@ class AdminPanelListUsers extends React.Component {
|
||||
}
|
||||
|
||||
getTableProps() {
|
||||
const {loading, users, usersParams, pages } = this.state;
|
||||
|
||||
return {
|
||||
className: 'admin-panel-list-users__table',
|
||||
loading: this.state.loading,
|
||||
loading,
|
||||
headers: this.getTableHeaders(),
|
||||
rows: this.state.users.map(this.getUserRow.bind(this)),
|
||||
rows: users.map(this.getUserRow.bind(this)),
|
||||
pageSize: 10,
|
||||
page: this.state.page,
|
||||
pages: this.state.pages,
|
||||
page: usersParams.page,
|
||||
pages,
|
||||
onPageChange: this.onPageChange.bind(this)
|
||||
};
|
||||
}
|
||||
@ -129,44 +145,57 @@ class AdminPanelListUsers extends React.Component {
|
||||
}
|
||||
|
||||
onSearch(query) {
|
||||
this.retrieveUsers({
|
||||
page: 1,
|
||||
orderBy: 'id',
|
||||
desc: true,
|
||||
const newUsersParams = {
|
||||
...this.state.usersParams,
|
||||
page: DEFAULT_USERS_PARAMS.page,
|
||||
search: query
|
||||
}
|
||||
|
||||
this.retrieveUsers(newUsersParams);
|
||||
|
||||
this.setState({
|
||||
usersParams: newUsersParams
|
||||
});
|
||||
}
|
||||
|
||||
onPageChange(event) {
|
||||
const {
|
||||
orderBy,
|
||||
desc,
|
||||
search
|
||||
} = this.state;
|
||||
|
||||
this.retrieveUsers({
|
||||
const newUsersParams = {
|
||||
...this.state.usersParams,
|
||||
page: event.target.value,
|
||||
orderBy,
|
||||
desc,
|
||||
search
|
||||
}
|
||||
|
||||
this.retrieveUsers(newUsersParams);
|
||||
|
||||
this.setState({
|
||||
usersParams: newUsersParams
|
||||
});
|
||||
}
|
||||
|
||||
orderByTickets(desc) {
|
||||
this.retrieveUsers({
|
||||
page: 1,
|
||||
const newUsersParams = {
|
||||
...this.state.usersParams,
|
||||
orderBy: 'tickets',
|
||||
desc: desc,
|
||||
search: this.state.search
|
||||
desc: desc
|
||||
}
|
||||
|
||||
this.retrieveUsers(newUsersParams);
|
||||
|
||||
this.setState({
|
||||
usersParams: newUsersParams
|
||||
});
|
||||
}
|
||||
|
||||
orderById(desc) {
|
||||
this.retrieveUsers({
|
||||
page: 1,
|
||||
const newUsersParams = {
|
||||
...this.state.usersParams,
|
||||
orderBy: 'id',
|
||||
desc: desc,
|
||||
search: this.state.search
|
||||
desc: desc
|
||||
}
|
||||
|
||||
this.retrieveUsers(newUsersParams);
|
||||
|
||||
this.setState({
|
||||
usersParams: newUsersParams
|
||||
});
|
||||
}
|
||||
|
||||
@ -184,21 +213,43 @@ class AdminPanelListUsers extends React.Component {
|
||||
onInviteUser(user) {
|
||||
ModalContainer.openModal(
|
||||
<div className="admin-panel-list-users__invite-user-form">
|
||||
<InviteUserWidget onSuccess={this.onInviteUserSuccess.bind(this)} />
|
||||
</div>
|
||||
<InviteUserWidget
|
||||
onSuccess={this.onInviteUserSuccess.bind(this)}
|
||||
onChangeMessage={this.onChangeMessage.bind(this)} />
|
||||
</div>,
|
||||
{
|
||||
closeButton: {
|
||||
showCloseButton: true
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onChangeMessage(message) {
|
||||
this.setState({
|
||||
message,
|
||||
showMessage: true
|
||||
});
|
||||
}
|
||||
|
||||
onInviteUserSuccess() {
|
||||
ModalContainer.closeModal();
|
||||
|
||||
this.retrieveUsers(DEFAULT_USERS_PARAMS);
|
||||
}
|
||||
|
||||
onUsersRetrieved(result) {
|
||||
const { page, pages, users, orderBy, desc } = result.data;
|
||||
|
||||
this.setState({
|
||||
page: result.data.page * 1,
|
||||
pages: result.data.pages * 1,
|
||||
users: result.data.users,
|
||||
orderBy: result.data.orderBy,
|
||||
desc: (result.data.desc*1),
|
||||
usersParams: {
|
||||
...this.state.usersParams,
|
||||
page: page*1,
|
||||
orderBy: orderBy,
|
||||
desc: desc*1,
|
||||
},
|
||||
pages: pages*1,
|
||||
users: users,
|
||||
error: false,
|
||||
loading: false
|
||||
});
|
||||
@ -210,6 +261,12 @@ class AdminPanelListUsers extends React.Component {
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default connect((store) => {
|
||||
|
@ -22,7 +22,15 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__success-message {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
&__add-user-form {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
&__invite-user-form {
|
||||
min-width: 700px;
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,8 @@ class AdminPanelViewUser extends React.Component {
|
||||
loading: true,
|
||||
disabled: false,
|
||||
userList: [],
|
||||
message: ''
|
||||
message: '',
|
||||
showMessage: true
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@ -51,7 +52,7 @@ class AdminPanelViewUser extends React.Component {
|
||||
renderInvalid() {
|
||||
return (
|
||||
<div className="admin-panel-view-user__invalid">
|
||||
<Message type="error">{i18n('INVALID_USER')}</Message>
|
||||
<Message showCloseButton={false} type="error">{i18n('INVALID_USER')}</Message>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -92,7 +93,10 @@ class AdminPanelViewUser extends React.Component {
|
||||
</div>
|
||||
<span className="separator" />
|
||||
<div className="admin-panel-view-user">
|
||||
<div className="admin-panel-view-user__supervised-users-header">{i18n('SUPERVISED_USER')}</div>
|
||||
<div className="admin-panel-view-user__supervised-users-container">
|
||||
<div className="admin-panel-view-user__supervised-users-header">{i18n('SUPERVISED_USER')}</div>
|
||||
<InfoTooltip className="admin-panel-view-user__info-tooltip" text={i18n('SUPERVISED_USER_INFORMATION')}/>
|
||||
</div>
|
||||
<div className="admin-panel-view-user__supervised-users-content">
|
||||
<Autocomplete
|
||||
onChange={this.onChangeValues.bind(this)}
|
||||
@ -111,7 +115,10 @@ class AdminPanelViewUser extends React.Component {
|
||||
</div>
|
||||
<span className="separator" />
|
||||
<div className="admin-panel-view-user__tickets">
|
||||
<div className="admin-panel-view-user__tickets-title">{i18n('TICKETS')}</div>
|
||||
<div className="admin-panel-view-user__tickets-info-container">
|
||||
<div className="admin-panel-view-user__tickets-title">{i18n('TICKETS')}</div>
|
||||
<InfoTooltip className="admin-panel-view-user__info-tooltip" text={i18n('TICKETS_INFORMATION')}/>
|
||||
</div>
|
||||
<TicketList {...this.getTicketListProps()} />
|
||||
</div>
|
||||
</div>
|
||||
@ -119,22 +126,32 @@ class AdminPanelViewUser extends React.Component {
|
||||
}
|
||||
|
||||
renderSupervisedUserMessage(){
|
||||
const { message } = this.state;
|
||||
const { message, showMessage } = this.state;
|
||||
|
||||
if(message) {
|
||||
if(message != 'success') {
|
||||
if(message !== 'success') {
|
||||
return (
|
||||
<div className="admin-panel-view-user__supervised-users-message">
|
||||
<Message type="error">{i18n(message)}</Message>
|
||||
</div>
|
||||
<Message
|
||||
showMessage={showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
|
||||
className="admin-panel-view-user__supervised-users-message"
|
||||
type="error">
|
||||
{i18n(message)}
|
||||
</Message>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className= "admin-panel-view-user__supervised-users-message">
|
||||
<Message type="success">{i18n('SUPERVISED_USERS_UPDATED')}</Message>
|
||||
</div>
|
||||
<Message
|
||||
showMessage={showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
|
||||
className="admin-panel-view-user__supervised-users-message"
|
||||
type="success">
|
||||
{i18n('SUPERVISED_USERS_UPDATED')}
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,12 +173,14 @@ class AdminPanelViewUser extends React.Component {
|
||||
}).then(r => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
message: 'success'
|
||||
message: 'success',
|
||||
showMessage: true
|
||||
})
|
||||
}).catch((r) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
message: r.message
|
||||
message: r.message,
|
||||
showMessage: true
|
||||
})
|
||||
});
|
||||
}
|
||||
@ -368,6 +387,12 @@ class AdminPanelViewUser extends React.Component {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default connect((store) => {
|
||||
|
@ -30,10 +30,19 @@
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
&__supervised-users-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
&__action-button {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
&__tickets-info-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__tickets-title {
|
||||
font-size: $font-size--md;
|
||||
margin-bottom: 20px;
|
||||
@ -64,4 +73,7 @@
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
&__info-tooltip{
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
@ -14,12 +14,14 @@ import Widget from 'core-components/widget';
|
||||
import Header from 'core-components/header';
|
||||
import Button from 'core-components/button';
|
||||
import ModalContainer from 'app-components/modal-container';
|
||||
import Loading from 'core-components/loading';
|
||||
|
||||
class InviteUserWidget extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
onSuccess: React.PropTypes.func,
|
||||
className: React.PropTypes.string
|
||||
className: React.PropTypes.string,
|
||||
onChangeMessage: React.PropTypes.func
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
@ -28,7 +30,8 @@ class InviteUserWidget extends React.Component {
|
||||
this.state = {
|
||||
loading: false,
|
||||
email: null,
|
||||
customFields: null
|
||||
customFields: null,
|
||||
showMessage: true
|
||||
};
|
||||
}
|
||||
|
||||
@ -36,32 +39,45 @@ class InviteUserWidget extends React.Component {
|
||||
API.call({
|
||||
path: '/system/get-custom-fields',
|
||||
data: {}
|
||||
})
|
||||
.then(result => this.setState({customFields: result.data}));
|
||||
}).then(result => {
|
||||
this.setState({
|
||||
customFields: result.data
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if(!this.state.customFields) return null;
|
||||
if(!this.state.customFields) return this.renderLoading();
|
||||
|
||||
return (
|
||||
<Widget className={this.getClass()}>
|
||||
<Header title={i18n('INVITE_USER')} description={i18n('INVITE_USER_VIEW_DESCRIPTION')} />
|
||||
<Form {...this.getFormProps()}>
|
||||
<div className="invite-user-widget__inputs">
|
||||
<FormField {...this.getInputProps()} label={i18n('FULL_NAME')} name="name" validation="NAME" required />
|
||||
<FormField {...this.getInputProps()} label={i18n('EMAIL')} name="email" validation="EMAIL" required />
|
||||
{this.state.customFields.map(this.renderCustomField.bind(this))}
|
||||
</div>
|
||||
<div className="invite-user-widget__captcha">
|
||||
<Captcha ref="captcha" />
|
||||
</div>
|
||||
<div className="invite-user-widget__buttons-container">
|
||||
<Button onClick={(e) => {e.preventDefault(); ModalContainer.closeModal();}} type="link">{i18n('CANCEL')}</Button>
|
||||
<SubmitButton type="secondary">{i18n('INVITE_USER')}</SubmitButton>
|
||||
</div>
|
||||
</Form>
|
||||
{this.renderMessage()}
|
||||
</Widget>
|
||||
<div className="invite-user-widget__modal-wrapper">
|
||||
<Widget className={this.getClass()}>
|
||||
<Header title={i18n('INVITE_USER')} description={i18n('INVITE_USER_VIEW_DESCRIPTION')} />
|
||||
<Form {...this.getFormProps()}>
|
||||
<div className="invite-user-widget__inputs">
|
||||
<FormField {...this.getInputProps()} label={i18n('FULL_NAME')} name="name" validation="NAME" required />
|
||||
<FormField {...this.getInputProps()} label={i18n('EMAIL')} name="email" validation="EMAIL" required />
|
||||
{this.state.customFields.map(this.renderCustomField.bind(this))}
|
||||
</div>
|
||||
<div className="invite-user-widget__captcha">
|
||||
<Captcha ref="captcha" />
|
||||
</div>
|
||||
<div className="invite-user-widget__buttons-container">
|
||||
<SubmitButton type="secondary">{i18n('INVITE_USER')}</SubmitButton>
|
||||
<Button onClick={(e) => {e.preventDefault(); ModalContainer.closeModal();}} type="link">{i18n('CANCEL')}</Button>
|
||||
</div>
|
||||
</Form>
|
||||
{this.renderMessage()}
|
||||
</Widget>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderLoading() {
|
||||
return (
|
||||
<div className="invite-user-widget__loading">
|
||||
<Loading backgrounded size="large" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -91,11 +107,29 @@ class InviteUserWidget extends React.Component {
|
||||
}
|
||||
|
||||
renderMessage() {
|
||||
switch (this.state.message) {
|
||||
case 'success':
|
||||
return <Message className="invite-user-widget__success-message" type="success">{i18n('INVITE_USER_SUCCESS')}</Message>;
|
||||
const { message, showMessage } = this.state;
|
||||
|
||||
switch (message) {
|
||||
case 'success': // TODO Remove this message case
|
||||
return (
|
||||
<Message
|
||||
showMessage={showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
|
||||
className="invite-user-widget__success-message"
|
||||
type="success">
|
||||
{i18n('INVITE_USER_SUCCESS')}
|
||||
</Message>
|
||||
);
|
||||
case 'fail':
|
||||
return <Message className="invite-user-widget__error-message" type="error">{i18n('EMAIL_EXISTS')}</Message>;
|
||||
return (
|
||||
<Message
|
||||
showMessage={showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
|
||||
className="invite-user-widget__error-message"
|
||||
type="error">
|
||||
{i18n('EMAIL_EXISTS')}
|
||||
</Message>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@ -153,16 +187,35 @@ class InviteUserWidget extends React.Component {
|
||||
}
|
||||
|
||||
onInviteUserSuccess() {
|
||||
const { onSuccess, onChangeMessage } = this.props;
|
||||
const message = 'success';
|
||||
|
||||
this.setState({
|
||||
loading: false,
|
||||
message: 'success'
|
||||
message,
|
||||
showMessage: true
|
||||
});
|
||||
|
||||
onChangeMessage && onChangeMessage(message);
|
||||
onSuccess && onSuccess();
|
||||
}
|
||||
|
||||
onInviteUserFail() {
|
||||
const { onChangeMessage } = this.props;
|
||||
const message = 'fail';
|
||||
|
||||
this.setState({
|
||||
loading: false,
|
||||
message: 'fail'
|
||||
message,
|
||||
showMessage: true
|
||||
});
|
||||
|
||||
onChangeMessage && onChangeMessage(message);
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@
|
||||
|
||||
&__buttons-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
@ -27,4 +27,18 @@
|
||||
&__error-message {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
&__modal-wrapper {
|
||||
min-width: 700px;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
&__loading {
|
||||
min-height: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: $grey;
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ class InstallCompleted extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="install-completed">
|
||||
<Message title={i18n('INSTALLATION_COMPLETED_TITLE')} type="success">
|
||||
<Message showCloseButton={false} title={i18n('INSTALLATION_COMPLETED_TITLE')} type="success">
|
||||
{i18n('INSTALLATION_COMPLETED_DESCRIPTION')}
|
||||
</Message>
|
||||
</div>
|
||||
|
@ -17,6 +17,7 @@ class InstallStep3Database extends React.Component {
|
||||
state = {
|
||||
loading: false,
|
||||
error: false,
|
||||
showErrorMessage: true,
|
||||
errorMessage: ''
|
||||
};
|
||||
|
||||
@ -42,17 +43,19 @@ class InstallStep3Database extends React.Component {
|
||||
}
|
||||
|
||||
renderMessage() {
|
||||
let message = null;
|
||||
const { error, errorMessage, showErrorMessage } = this.state;
|
||||
|
||||
if(this.state.error) {
|
||||
message = (
|
||||
<Message className="install-step-3__message" type="error">
|
||||
{i18n('ERROR_UPDATING_SETTINGS')}: {this.state.errorMessage}
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
|
||||
return message;
|
||||
return (
|
||||
error ?
|
||||
<Message
|
||||
showMessage={showErrorMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showErrorMessage")}
|
||||
className="install-step-3__message"
|
||||
type="error">
|
||||
{i18n('ERROR_UPDATING_SETTINGS')}: {errorMessage}
|
||||
</Message> :
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
onPreviousClick(event) {
|
||||
@ -72,10 +75,17 @@ class InstallStep3Database extends React.Component {
|
||||
.catch(({message}) => this.setState({
|
||||
loading: false,
|
||||
error: true,
|
||||
showErrorMessage: true,
|
||||
errorMessage: message
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default InstallStep3Database;
|
||||
|
@ -20,6 +20,7 @@ class InstallStep5Settings extends React.Component {
|
||||
loading: false,
|
||||
form: {},
|
||||
error: false,
|
||||
showErrorMessage: true,
|
||||
errorMessage: ''
|
||||
};
|
||||
|
||||
@ -65,17 +66,19 @@ class InstallStep5Settings extends React.Component {
|
||||
}
|
||||
|
||||
renderMessage() {
|
||||
let message = null;
|
||||
const { error, errorMessage, showErrorMessage } = this.state;
|
||||
|
||||
if(this.state.error) {
|
||||
message = (
|
||||
<Message className="install-step-5__message" type="error">
|
||||
{i18n('ERROR_UPDATING_SETTINGS')}: {this.state.errorMessage}
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
|
||||
return message;
|
||||
return (
|
||||
error ?
|
||||
<Message
|
||||
showMessage={showErrorMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showErrorMessage")}
|
||||
className="install-step-5__message"
|
||||
type="error">
|
||||
{i18n('ERROR_UPDATING_SETTINGS')}: {errorMessage}
|
||||
</Message> :
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
onTestSMTPClick(event) {
|
||||
@ -125,10 +128,17 @@ class InstallStep5Settings extends React.Component {
|
||||
.catch(({message}) => this.setState({
|
||||
loading: false,
|
||||
error: true,
|
||||
showErrorMessage: true,
|
||||
errorMessage: message
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default connect((store) => {
|
||||
|
@ -15,6 +15,7 @@ class InstallStep6Admin extends React.Component {
|
||||
state = {
|
||||
loading: false,
|
||||
error: false,
|
||||
showErrorMessage: true,
|
||||
errorMessage: ''
|
||||
};
|
||||
|
||||
@ -36,17 +37,19 @@ class InstallStep6Admin extends React.Component {
|
||||
}
|
||||
|
||||
renderMessage() {
|
||||
let message = null;
|
||||
const { error, errorMessage, showErrorMessage } = this.state;
|
||||
|
||||
if(this.state.error) {
|
||||
message = (
|
||||
<Message className="install-step-6_message" type="error">
|
||||
{i18n('ERROR_UPDATING_SETTINGS')}: {this.state.errorMessage}
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
|
||||
return message;
|
||||
return (
|
||||
error ?
|
||||
<Message
|
||||
showMessage={showErrorMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showErrorMessage")}
|
||||
className="install-step-6_message"
|
||||
type="error">
|
||||
{i18n('ERROR_UPDATING_SETTINGS')}: {errorMessage}
|
||||
</Message> :
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
onSubmit(form) {
|
||||
@ -61,10 +64,17 @@ class InstallStep6Admin extends React.Component {
|
||||
.catch(({message}) => this.setState({
|
||||
loading: false,
|
||||
error: true,
|
||||
showErrorMessage: true,
|
||||
errorMessage: message
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default InstallStep6Admin;
|
||||
export default InstallStep6Admin;
|
||||
|
@ -17,6 +17,12 @@ import FormField from 'core-components/form-field';
|
||||
import SubmitButton from 'core-components/submit-button';
|
||||
import Message from 'core-components/message';
|
||||
|
||||
const DEFAULT_CREATE_TICKET_FORM_VALUE = {
|
||||
title: '',
|
||||
email: '',
|
||||
name: ''
|
||||
};
|
||||
|
||||
class CreateTicketForm extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
@ -34,23 +40,16 @@ class CreateTicketForm extends React.Component {
|
||||
loading: false,
|
||||
message: null,
|
||||
form: {
|
||||
title: '',
|
||||
...DEFAULT_CREATE_TICKET_FORM_VALUE,
|
||||
content: TextEditor.createEmpty(),
|
||||
departmentIndex: getPublicDepartmentIndexFromDepartmentId(this.props.defaultDepartmentId, SessionStore.getDepartments()),
|
||||
email: '',
|
||||
name: '',
|
||||
language: this.props.language
|
||||
}
|
||||
},
|
||||
showMessage: true
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
userLogged,
|
||||
isDefaultDepartmentLocked,
|
||||
isStaff,
|
||||
onlyOneSupportedLanguage,
|
||||
allowAttachments
|
||||
} = this.props;
|
||||
const { userLogged, isDefaultDepartmentLocked, isStaff, onlyOneSupportedLanguage, allowAttachments } = this.props;
|
||||
|
||||
return (
|
||||
<div className="create-ticket-form">
|
||||
@ -79,10 +78,14 @@ class CreateTicketForm extends React.Component {
|
||||
fieldProps={{allowImages: allowAttachments}}
|
||||
required
|
||||
field="textarea" />
|
||||
<div className="create-ticket-form__buttons-container">
|
||||
{allowAttachments ? this.renderFileUpload() : null}
|
||||
{(!userLogged) ? this.renderCaptcha() : null}
|
||||
<SubmitButton type="secondary">{i18n('CREATE_TICKET')}</SubmitButton>
|
||||
<div className="create-ticket-form__container">
|
||||
<div className={`create-ticket-form__buttons-container${allowAttachments ? "" : "-without-allow-attachments"}`}>
|
||||
{allowAttachments ? this.renderFileUpload() : null}
|
||||
<SubmitButton type="secondary">{i18n('CREATE_TICKET')}</SubmitButton>
|
||||
</div>
|
||||
<div className="create-ticket-form__captcha-container">
|
||||
{(!userLogged) ? this.renderCaptcha() : null}
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
{this.renderMessage()}
|
||||
@ -116,21 +119,38 @@ class CreateTicketForm extends React.Component {
|
||||
}
|
||||
|
||||
renderMessage() {
|
||||
switch (this.state.message) {
|
||||
case 'success':
|
||||
return <Message className="create-ticket-form__message" type="success">{i18n('TICKET_SENT')}</Message>;
|
||||
const { message, showMessage } = this.state;
|
||||
|
||||
switch (message) {
|
||||
case 'success': // TODO Remove this message case
|
||||
return (
|
||||
this.props.userLogged ?
|
||||
<Message
|
||||
showMessage={showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
|
||||
className="create-ticket-form__message"
|
||||
type="success">
|
||||
{i18n('TICKET_SENT')}
|
||||
</Message> :
|
||||
null
|
||||
);
|
||||
case 'fail':
|
||||
return <Message className="create-ticket-form__message" type="error">{i18n('TICKET_SENT_ERROR')}</Message>;
|
||||
return (
|
||||
<Message
|
||||
showMessage={showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
|
||||
className="create-ticket-form__message"
|
||||
type="error">
|
||||
{i18n('TICKET_SENT_ERROR')}
|
||||
</Message>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
getFormProps() {
|
||||
const {
|
||||
loading,
|
||||
form
|
||||
} = this.state;
|
||||
const { loading, form } = this.state;
|
||||
|
||||
return {
|
||||
loading,
|
||||
@ -161,29 +181,48 @@ class CreateTicketForm extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onTicketSuccess(email, result) {
|
||||
const { onSuccess } = this.props;
|
||||
onTicketSuccess() {
|
||||
const { onSuccess, userLogged, language } = this.props;
|
||||
const { form } = this.state;
|
||||
const message = 'success';
|
||||
|
||||
this.setState(
|
||||
{
|
||||
loading: false,
|
||||
message
|
||||
message,
|
||||
showMessage: true,
|
||||
form: !userLogged ?
|
||||
{
|
||||
...form,
|
||||
...DEFAULT_CREATE_TICKET_FORM_VALUE,
|
||||
content: TextEditor.createEmpty(),
|
||||
departmentIndex: getPublicDepartmentIndexFromDepartmentId(this.props.defaultDepartmentId, SessionStore.getDepartments()),
|
||||
language
|
||||
} :
|
||||
form
|
||||
},
|
||||
() => {onSuccess && onSuccess(result, email, message);}
|
||||
() => {onSuccess && onSuccess(message);}
|
||||
);
|
||||
}
|
||||
|
||||
onTicketFail() {
|
||||
this.setState({
|
||||
loading: false,
|
||||
message: 'fail'
|
||||
message: 'fail',
|
||||
showMessage: true
|
||||
});
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default connect((store) => {
|
||||
const { language, supportedLanguages } = store.config;
|
||||
|
||||
return {
|
||||
language: _.includes(supportedLanguages, language) ? language : supportedLanguages[0],
|
||||
onlyOneSupportedLanguage: supportedLanguages.length == 1 ? true : false,
|
||||
|
@ -8,11 +8,29 @@
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
&__container {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
&__buttons-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
z-index: 999;
|
||||
|
||||
&-without-allow-attachments {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
}
|
||||
}
|
||||
|
||||
&__captcha-container {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
&__captcha {
|
||||
|
@ -1,12 +1,15 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import {connect} from 'react-redux';
|
||||
import history from 'lib-app/history';
|
||||
import queryString from 'query-string';
|
||||
|
||||
import history from 'lib-app/history';
|
||||
import i18n from 'lib-app/i18n';
|
||||
|
||||
import SessionActions from 'actions/session-actions';
|
||||
import CreateTicketForm from 'app/main/dashboard/dashboard-create-ticket/create-ticket-form';
|
||||
|
||||
import Widget from 'core-components/widget';
|
||||
import Message from 'core-components/message';
|
||||
|
||||
class DashboardCreateTicketPage extends React.Component {
|
||||
|
||||
@ -14,6 +17,10 @@ class DashboardCreateTicketPage extends React.Component {
|
||||
userSystemEnabled: React.PropTypes.bool
|
||||
};
|
||||
|
||||
state = {
|
||||
showMessage: !!queryString.parse(window.location.search)["message"]
|
||||
};
|
||||
|
||||
render() {
|
||||
let Wrapper = 'div';
|
||||
|
||||
@ -23,21 +30,26 @@ class DashboardCreateTicketPage extends React.Component {
|
||||
|
||||
return (
|
||||
<div className={this.getClass()}>
|
||||
<Wrapper>
|
||||
<CreateTicketForm
|
||||
userLogged={(this.props.location.pathname !== '/create-ticket')}
|
||||
onSuccess={this.onCreateTicketSuccess.bind(this)}/>
|
||||
<Wrapper className="dashboard-create-ticket-page__container">
|
||||
<Message // TODO Remove this message
|
||||
showMessage={this.state.showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this)}
|
||||
className="dashboard-create-ticket-page__message"
|
||||
type="success">
|
||||
{i18n('TICKET_NUMBER_SENT')}
|
||||
</Message>
|
||||
<div className={this.getCreateTicketFormClass()}>
|
||||
<CreateTicketForm
|
||||
userLogged={(this.props.location.pathname !== '/create-ticket')}
|
||||
onSuccess={this.onCreateTicketSuccess.bind(this)} />
|
||||
</div>
|
||||
</Wrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
onCreateTicketSuccess(result, email, message) {
|
||||
if((this.props.location.pathname !== '/create-ticket')) {
|
||||
history.push(`/dashboard?message=${message}`);
|
||||
} else {
|
||||
setTimeout(() => {history.push('/check-ticket/' + result.data.ticketNumber + '/' + email)}, 1000);
|
||||
}
|
||||
onCreateTicketSuccess(message) {
|
||||
history.push(`${(this.props.location.pathname !== '/create-ticket') ? "/dashboard" : "/"}?message=${message}`);
|
||||
}
|
||||
|
||||
getClass() {
|
||||
@ -49,6 +61,21 @@ class DashboardCreateTicketPage extends React.Component {
|
||||
|
||||
return classNames(classes);
|
||||
}
|
||||
|
||||
getCreateTicketFormClass() {
|
||||
let classes = {
|
||||
'dashboard-create-ticket-page__create-ticket-form': true,
|
||||
'dashboard-create-ticket-page__create-ticket-form__hidden': !!queryString.parse(window.location.search)["message"]
|
||||
};
|
||||
|
||||
return classNames(classes);
|
||||
}
|
||||
|
||||
onCloseMessage() {
|
||||
this.setState({
|
||||
showMessage: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default connect((store) => {
|
||||
|
@ -7,4 +7,18 @@
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__create-ticket-form__hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__message {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,8 @@ class DashboardEditProfilePage extends React.Component {
|
||||
customFields: [],
|
||||
customFieldsFrom: {},
|
||||
loadingCustomFields: false,
|
||||
showChangeEmailMessage: true,
|
||||
showChangePasswordMessage: true
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@ -115,12 +117,7 @@ class DashboardEditProfilePage extends React.Component {
|
||||
}
|
||||
|
||||
renderCustomField(customField, key) {
|
||||
const {
|
||||
type,
|
||||
name,
|
||||
description,
|
||||
options
|
||||
} = customField;
|
||||
const { type, name, description, options } = customField;
|
||||
|
||||
if(type === 'text') {
|
||||
return (
|
||||
@ -140,22 +137,58 @@ class DashboardEditProfilePage extends React.Component {
|
||||
}
|
||||
|
||||
renderMessageEmail() {
|
||||
switch (this.state.messageEmail) {
|
||||
const { messageEmail, showChangeEmailMessage } = this.state;
|
||||
|
||||
switch (messageEmail) {
|
||||
case 'success':
|
||||
return <Message className="edit-profile-page__message" type="success">{i18n('EMAIL_CHANGED')}</Message>;
|
||||
return (
|
||||
<Message
|
||||
showMessage={showChangeEmailMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showChangeEmailMessage")}
|
||||
className="edit-profile-page__message"
|
||||
type="success">
|
||||
{i18n('EMAIL_CHANGED')}
|
||||
</Message>
|
||||
);
|
||||
case 'fail':
|
||||
return <Message className="edit-profile-page__message" type="error">{i18n('EMAIL_EXISTS')}</Message>;
|
||||
return (
|
||||
<Message
|
||||
showMessage={showChangeEmailMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showChangeEmailMessage")}
|
||||
className="edit-profile-page__message"
|
||||
type="error">
|
||||
{i18n('EMAIL_EXISTS')}
|
||||
</Message>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
renderMessagePass() {
|
||||
switch (this.state.messagePass) {
|
||||
const { messagePass, showChangePasswordMessage } = this.state;
|
||||
|
||||
switch (messagePass) {
|
||||
case 'success':
|
||||
return <Message className="edit-profile-page__message" type="success">{i18n('PASSWORD_CHANGED')}</Message>;
|
||||
return (
|
||||
<Message
|
||||
showMessage={showChangePasswordMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showChangePasswordMessage")}
|
||||
className="edit-profile-page__message"
|
||||
type="success">
|
||||
{i18n('PASSWORD_CHANGED')}
|
||||
</Message>
|
||||
);
|
||||
case 'fail':
|
||||
return <Message className="edit-profile-page__message" type="error">{i18n('OLD_PASSWORD_INCORRECT')}</Message>;
|
||||
return (
|
||||
<Message
|
||||
showMessage={showChangePasswordMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showChangePasswordMessage")}
|
||||
className="edit-profile-page__message"
|
||||
type="error">
|
||||
{i18n('OLD_PASSWORD_INCORRECT')}
|
||||
</Message>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@ -213,12 +246,14 @@ class DashboardEditProfilePage extends React.Component {
|
||||
}).then(function () {
|
||||
this.setState({
|
||||
loadingEmail: false,
|
||||
messageEmail: "success"
|
||||
messageEmail: "success",
|
||||
showChangeEmailMessage: true
|
||||
});
|
||||
}.bind(this)).catch(function (){
|
||||
this.setState({
|
||||
loadingEmail: false,
|
||||
messageEmail: 'fail'
|
||||
messageEmail: 'fail',
|
||||
showChangeEmailMessage: true
|
||||
})
|
||||
}.bind(this));
|
||||
}
|
||||
@ -237,12 +272,14 @@ class DashboardEditProfilePage extends React.Component {
|
||||
}).then(function () {
|
||||
this.setState({
|
||||
loadingPass: false,
|
||||
messagePass: "success"
|
||||
messagePass: "success",
|
||||
showChangePasswordMessage: true
|
||||
});
|
||||
}.bind(this)).catch(function (){
|
||||
this.setState({
|
||||
loadingPass: false,
|
||||
messagePass: 'fail'
|
||||
messagePass: 'fail',
|
||||
showChangePasswordMessage: true
|
||||
})
|
||||
}.bind(this));
|
||||
}
|
||||
@ -277,6 +314,12 @@ class DashboardEditProfilePage extends React.Component {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default connect((store) => {
|
||||
|
@ -33,7 +33,8 @@ class DashboardListTicketsPage extends React.Component {
|
||||
ownTickets: true
|
||||
},
|
||||
message: '',
|
||||
loading: false
|
||||
loading: false,
|
||||
showMessage: true
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@ -42,16 +43,8 @@ class DashboardListTicketsPage extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
userUsers
|
||||
} = this.props;
|
||||
const {
|
||||
loading,
|
||||
page,
|
||||
pages,
|
||||
tickets,
|
||||
message
|
||||
} = this.state;
|
||||
const { userUsers } = this.props;
|
||||
const { loading, page, pages, tickets, message, showMessage } = this.state;
|
||||
|
||||
return (
|
||||
<div className="dashboard-ticket-list">
|
||||
@ -63,8 +56,18 @@ class DashboardListTicketsPage extends React.Component {
|
||||
page={page}
|
||||
pages={pages}
|
||||
tickets={tickets}
|
||||
showPageSizeDropdown={false}
|
||||
type={userUsers.length ? "secondary" : "primary"} />
|
||||
{message ? <Message type="error" >{i18n(message)}</Message> : null}
|
||||
{
|
||||
message ?
|
||||
<Message
|
||||
showMessage={showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
|
||||
type="error">
|
||||
{i18n(message)}
|
||||
</Message> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -130,11 +133,18 @@ class DashboardListTicketsPage extends React.Component {
|
||||
this.setState({
|
||||
tickets: [],
|
||||
message: r.message,
|
||||
showMessage: true,
|
||||
loading: false
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -16,6 +16,7 @@ class DashboardTicketPage extends React.Component {
|
||||
state = {
|
||||
error: null,
|
||||
ticket: null,
|
||||
showErrorMessage: true
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@ -23,7 +24,7 @@ class DashboardTicketPage extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {ticket, error} = this.state;
|
||||
const { ticket, error } = this.state;
|
||||
|
||||
return (
|
||||
<div className="dashboard-ticket-page">
|
||||
@ -33,11 +34,11 @@ class DashboardTicketPage extends React.Component {
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
const {ticket, error} = this.state;
|
||||
const { ticket, error, showErrorMessage } = this.state;
|
||||
|
||||
if(error) {
|
||||
return (
|
||||
<Message type="error">
|
||||
<Message showMessage={showErrorMessage} onCloseMessage={this.onCloseMessage.bind(this, "showErrorMessage")} type="error">
|
||||
{i18n(error)}
|
||||
</Message>
|
||||
);
|
||||
@ -71,12 +72,18 @@ class DashboardTicketPage extends React.Component {
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(result => this.setState({error: result.message}));
|
||||
.catch(result => this.setState({error: result.message, showErrorMessage: true}));
|
||||
}
|
||||
|
||||
retrieveUserData() {
|
||||
store.dispatch(SessionActions.getUserData());
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default DashboardTicketPage;
|
||||
|
@ -17,6 +17,14 @@ import FormField from 'core-components/form-field';
|
||||
import Widget from 'core-components/widget';
|
||||
import WidgetTransition from 'core-components/widget-transition';
|
||||
import Message from 'core-components/message';
|
||||
import Loading from 'core-components/loading';
|
||||
|
||||
import Captcha from 'app/main/captcha';
|
||||
|
||||
const UNVERIFIED_USER_STEP = 0;
|
||||
const LOADING_STEP = 1;
|
||||
const REQUEST_RESULT_STEP = 2;
|
||||
const MAX_FREE_LOGIN_ATTEMPTS = 3;
|
||||
|
||||
class MainHomePageLoginWidget extends React.Component {
|
||||
|
||||
@ -26,11 +34,17 @@ class MainHomePageLoginWidget extends React.Component {
|
||||
recoverFormErrors: {},
|
||||
recoverSent: false,
|
||||
loadingLogin: false,
|
||||
loadingRecover: false
|
||||
loadingRecover: false,
|
||||
reSendEMailVerificationLoading: false,
|
||||
reSendEmailVerificationStep: UNVERIFIED_USER_STEP,
|
||||
reSendEmailVerificationMessage: "",
|
||||
showRecoverSentMessage: true,
|
||||
showReSendEmailVerificationMessage: true
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!prevProps.session.failed && this.props.session.failed) {
|
||||
this.setState({showReSendEmailVerificationMessage : true});
|
||||
this.refs.loginForm.refs.password.focus();
|
||||
}
|
||||
}
|
||||
@ -53,17 +67,70 @@ class MainHomePageLoginWidget extends React.Component {
|
||||
<FormField placeholder={i18n('PASSWORD_LOWERCASE')} name="password" className="login-widget__input" required fieldProps={{password: true}}/>
|
||||
<FormField name="remember" label={i18n('REMEMBER_ME')} className="login-widget__input" field="checkbox"/>
|
||||
</div>
|
||||
{this.props.session.loginAttempts > MAX_FREE_LOGIN_ATTEMPTS ? this.renderLoginCaptcha() : null}
|
||||
<div className="login-widget__submit-button">
|
||||
<SubmitButton type="primary">{i18n('LOG_IN')}</SubmitButton>
|
||||
</div>
|
||||
</Form>
|
||||
<Button className="login-widget__forgot-password" type="link" onClick={this.onForgotPasswordClick.bind(this)} onMouseDown={(event) => {event.preventDefault()}}>
|
||||
{i18n('FORGOT_PASSWORD')}
|
||||
</Button>
|
||||
<div className="main-home-page__link-buttons-container">
|
||||
<Button className="login-widget__forgot-password" type="link" onClick={this.onForgotPasswordClick.bind(this)} onMouseDown={(event) => {event.preventDefault()}}>
|
||||
{i18n('FORGOT_PASSWORD')}
|
||||
</Button>
|
||||
{this.renderReSendEmailVerificationSection()}
|
||||
</div>
|
||||
</Widget>
|
||||
);
|
||||
}
|
||||
|
||||
renderLoginCaptcha() {
|
||||
return(
|
||||
<div className={`main-home-page__${this.props.sitekey ? "captcha" : "no-captcha"}`}>
|
||||
<Captcha ref="captcha" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderReSendEmailVerificationSection() {
|
||||
const { reSendEmailVerificationMessage,reSendEmailVerificationStep, showReSendEmailVerificationMessage } = this.state;
|
||||
|
||||
if(this.props.session.failMessage === 'UNVERIFIED_USER') {
|
||||
switch (reSendEmailVerificationStep) {
|
||||
case UNVERIFIED_USER_STEP:
|
||||
return (
|
||||
<Button className="login-widget__resend-verification-token" type="link" onClick={this.onReSendEmailVerificationClick.bind(this)}>
|
||||
{i18n('RESEND_EMAIL_VERIFICATION')}
|
||||
</Button>
|
||||
);
|
||||
|
||||
case LOADING_STEP:
|
||||
return <Loading className="login-widget__loading" />;
|
||||
|
||||
case REQUEST_RESULT_STEP:
|
||||
return (
|
||||
(reSendEmailVerificationMessage === "success") ?
|
||||
<Message
|
||||
showMessage={showReSendEmailVerificationMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showReSendEmailVerificationMessage")}
|
||||
className="login-widget__resend-email-verification-success"
|
||||
type="success"
|
||||
leftAligned>
|
||||
{i18n('RESEND_EMAIL_VERIFICATION_SUCCESS')}
|
||||
</Message> :
|
||||
<Message
|
||||
showMessage={showReSendEmailVerificationMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showReSendEmailVerificationMessage")}
|
||||
className="login-widget__resend-email-verification-fail"
|
||||
type="error"
|
||||
leftAligned>
|
||||
{i18n('RESEND_EMAIL_VERIFICATION_FAIL')}
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
renderPasswordRecovery() {
|
||||
return (
|
||||
<PasswordRecovery ref="passwordRecovery" recoverSent={this.state.recoverSent} formProps={this.getRecoverFormProps()} onBackToLoginClick={this.onBackToLoginClick.bind(this)}/>
|
||||
@ -71,17 +138,20 @@ class MainHomePageLoginWidget extends React.Component {
|
||||
}
|
||||
|
||||
renderRecoverStatus() {
|
||||
let status = null;
|
||||
const { recoverSent, showRecoverSentMessage} = this.state;
|
||||
|
||||
if (this.state.recoverSent) {
|
||||
status = (
|
||||
<Message className="login-widget__message" type="info" leftAligned>
|
||||
{i18n('RECOVER_SENT')}
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
|
||||
return status;
|
||||
return (
|
||||
recoverSent ?
|
||||
<Message
|
||||
showMessage={showRecoverSentMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showRecoverSentMessage")}
|
||||
className="login-widget__message"
|
||||
type="info"
|
||||
leftAligned>
|
||||
{i18n('RECOVER_SENT')}
|
||||
</Message> :
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
getLoginFormProps() {
|
||||
@ -108,7 +178,6 @@ class MainHomePageLoginWidget extends React.Component {
|
||||
|
||||
getLoginFormErrors() {
|
||||
let errors = _.extend({}, this.state.loginFormErrors);
|
||||
|
||||
if (this.props.session.failed) {
|
||||
if (this.props.session.failMessage === 'INVALID_CREDENTIALS') {
|
||||
errors.password = i18n('ERROR_PASSWORD');
|
||||
@ -123,6 +192,10 @@ class MainHomePageLoginWidget extends React.Component {
|
||||
}
|
||||
|
||||
onLoginFormSubmit(formState) {
|
||||
this.setState({
|
||||
reSendEmailVerificationStep: UNVERIFIED_USER_STEP,
|
||||
email: formState.email
|
||||
})
|
||||
this.props.dispatch(SessionActions.login(formState));
|
||||
}
|
||||
|
||||
@ -166,7 +239,8 @@ class MainHomePageLoginWidget extends React.Component {
|
||||
onRecoverPasswordSent() {
|
||||
this.setState({
|
||||
loadingRecover: false,
|
||||
recoverSent: true
|
||||
recoverSent: true,
|
||||
showRecoverSentMessage: true
|
||||
});
|
||||
}
|
||||
|
||||
@ -178,11 +252,41 @@ class MainHomePageLoginWidget extends React.Component {
|
||||
}
|
||||
}, () => this.refs.passwordRecovery.focusEmail());
|
||||
}
|
||||
|
||||
onReSendEmailVerificationClick() {
|
||||
this.setState({
|
||||
reSendEmailVerificationStep: LOADING_STEP,
|
||||
})
|
||||
|
||||
API.call({
|
||||
path: '/user/resend-email-token',
|
||||
data: {
|
||||
email: this.state.email
|
||||
}
|
||||
}).then(() => {
|
||||
this.setState({
|
||||
reSendEmailVerificationStep: REQUEST_RESULT_STEP,
|
||||
reSendEmailVerificationMessage: 'success'
|
||||
})
|
||||
}).catch(() => {
|
||||
this.setState({
|
||||
reSendEmailVerificationStep: REQUEST_RESULT_STEP,
|
||||
reSendEmailVerificationMessage: 'error'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default connect((store) => {
|
||||
return {
|
||||
session: store.session
|
||||
session: store.session,
|
||||
sitekey: store.config.reCaptchaKey
|
||||
};
|
||||
})(MainHomePageLoginWidget);
|
||||
})(MainHomePageLoginWidget);
|
@ -18,6 +18,24 @@
|
||||
&__message {
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
&__loading > span{
|
||||
border-top: 1.1em solid rgba($dark-grey, 0.2);
|
||||
border-right: 1.1em solid rgba($dark-grey, 0.2);
|
||||
border-bottom: 1.1em solid rgba($dark-grey, 0.2);
|
||||
border-left: 1.1em solid $dark-grey;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.main-home-page__link-buttons-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.main-home-page__widget {
|
||||
min-height: 391px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 379px) {
|
||||
|
@ -7,30 +7,45 @@ import Widget from 'core-components/widget';
|
||||
import Card from 'core-components/card';
|
||||
import i18n from 'lib-app/i18n';
|
||||
import Header from 'core-components/header';
|
||||
import queryString from 'query-string';
|
||||
import Message from 'core-components/message';
|
||||
|
||||
class MainHomePagePortal extends React.Component {
|
||||
static propTypes = {
|
||||
type: React.PropTypes.oneOf(['default', 'complete'])
|
||||
};
|
||||
|
||||
state = {
|
||||
showMessage: !!queryString.parse(window.location.search)["message"]
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Widget className={classNames('main-home-page-portal', this.props.className)}>
|
||||
<div className="main-home-page-portal__title">
|
||||
<Header title={this.props.title || i18n('SUPPORT_CENTER')} description={i18n('SUPPORT_CENTER_DESCRIPTION')} />
|
||||
</div>
|
||||
<div className="main-home-page-portal__cards">
|
||||
<div className="main-home-page-portal__card col-md-4">
|
||||
<Card {...this.getTicketsCardProps()}/>
|
||||
<div className="main-home-page-portal__message">
|
||||
<Message
|
||||
showMessage={this.state.showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this)}
|
||||
className="dashboard-create-ticket-page__message"
|
||||
type="success">
|
||||
{i18n('TICKET_NUMBER_SENT')}
|
||||
</Message>
|
||||
<Widget className={classNames('main-home-page-portal', this.props.className)}>
|
||||
<div className="main-home-page-portal__title">
|
||||
<Header title={this.props.title || i18n('SUPPORT_CENTER')} description={i18n('SUPPORT_CENTER_DESCRIPTION')} />
|
||||
</div>
|
||||
<div className="main-home-page-portal__card col-md-4">
|
||||
<Card {...((this.props.type === 'complete') ? this.getViewTicketCardProps() : this.getAccountCardProps())} />
|
||||
<div className="main-home-page-portal__cards">
|
||||
<div className="main-home-page-portal__card col-md-4">
|
||||
<Card {...this.getTicketsCardProps()}/>
|
||||
</div>
|
||||
<div className="main-home-page-portal__card col-md-4">
|
||||
<Card {...((this.props.type === 'complete') ? this.getViewTicketCardProps() : this.getAccountCardProps())} />
|
||||
</div>
|
||||
<div className="main-home-page-portal__card col-md-4">
|
||||
<Card {...this.getArticlesCardProps()} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="main-home-page-portal__card col-md-4">
|
||||
<Card {...this.getArticlesCardProps()} />
|
||||
</div>
|
||||
</div>
|
||||
</Widget>
|
||||
</Widget>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -75,6 +90,12 @@ class MainHomePagePortal extends React.Component {
|
||||
onButtonClick: () => history.push('/check-ticket')
|
||||
};
|
||||
}
|
||||
|
||||
onCloseMessage() {
|
||||
this.setState({
|
||||
showMessage: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default connect((store) => {
|
||||
|
@ -9,12 +9,19 @@ import MainHomePagePortal from 'app/main/main-home/main-home-page-portal';
|
||||
import Message from 'core-components/message';
|
||||
|
||||
class MainHomePage extends React.Component {
|
||||
|
||||
|
||||
state = {
|
||||
showMessage: true
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!prevProps.session && this.props.session) {
|
||||
this.setState({showMessage : true});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
config,
|
||||
loginForm
|
||||
} = this.props;
|
||||
const { config, loginForm } = this.props;
|
||||
return (
|
||||
<div className="main-home-page row">
|
||||
{this.renderMessage()}
|
||||
@ -50,16 +57,26 @@ class MainHomePage extends React.Component {
|
||||
|
||||
renderSuccess() {
|
||||
return (
|
||||
<Message title={i18n('VERIFY_SUCCESS')} type="success" className="main-home-page__message">
|
||||
{i18n('VERIFY_SUCCESS_DESCRIPTION')}
|
||||
<Message
|
||||
showMessage={this.state.showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this)}
|
||||
title={i18n('VERIFY_SUCCESS')}
|
||||
type="success"
|
||||
className="main-home-page__message">
|
||||
{i18n('VERIFY_SUCCESS_DESCRIPTION')}
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
|
||||
renderFailed() {
|
||||
return (
|
||||
<Message title={i18n('VERIFY_FAILED')} type="error" className="main-home-page__message">
|
||||
{i18n('VERIFY_FAILED_DESCRIPTION')}
|
||||
<Message
|
||||
showMessage={this.state.showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this)}
|
||||
title={i18n('VERIFY_FAILED')}
|
||||
type="error"
|
||||
className="main-home-page__message">
|
||||
{i18n('VERIFY_FAILED_DESCRIPTION')}
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
@ -85,6 +102,12 @@ class MainHomePage extends React.Component {
|
||||
|
||||
return classNames(classes);
|
||||
}
|
||||
|
||||
onCloseMessage() {
|
||||
this.setState({
|
||||
showMessage: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default connect((store) => {
|
||||
|
@ -10,4 +10,10 @@
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
&__captcha {
|
||||
margin: 10px auto 20px;
|
||||
height: 78px;
|
||||
width: 304px;
|
||||
}
|
||||
}
|
@ -20,8 +20,6 @@ class MainLayoutFooter extends React.Component {
|
||||
return (
|
||||
<div className="main-layout-footer__extra-links">
|
||||
<a className="main-layout-footer__extra-link" href="http://www.opensupports.com/documentation/" target="_blank">Documentation</a>
|
||||
<span> | </span>
|
||||
<a className="main-layout-footer__extra-link" href="http://www.opensupports.com/download/#donation" target="_blank">Donate</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -13,12 +13,24 @@ class MainLayoutHeader extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="main-layout-header">
|
||||
{this.renderAccessLinks()}
|
||||
<LanguageSelector {...this.getLanguageSelectorProps()} />
|
||||
{this.renderHeaderOptions()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
renderHeaderOptions(){
|
||||
let result = null;
|
||||
|
||||
if(!this.props.config['maintenance-mode']){
|
||||
result = (<div>
|
||||
{this.renderAccessLinks()}
|
||||
<LanguageSelector {...this.getLanguageSelectorProps()} />
|
||||
</div>)
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
renderAccessLinks() {
|
||||
const {
|
||||
session,
|
||||
|
@ -8,7 +8,7 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
min-height: 40px;
|
||||
&__user-name {
|
||||
color: $primary-red;
|
||||
}
|
||||
|
@ -8,8 +8,12 @@
|
||||
border-radius: 4px;
|
||||
transition: max-height 0.15s ease-out;
|
||||
|
||||
.main-layout-header__languages > .drop-down__current-item {
|
||||
min-height: unset;
|
||||
}
|
||||
|
||||
&--content {
|
||||
min-height: 400px;
|
||||
min-height: 453px;
|
||||
padding: 20px;
|
||||
|
||||
@media screen and (min-width: 993px) and (max-width: 1032px) {
|
||||
|
@ -23,7 +23,8 @@ class MainRecoverPasswordPage extends React.Component {
|
||||
|
||||
this.state = {
|
||||
recoverStatus: 'waiting',
|
||||
loading: false
|
||||
loading: false,
|
||||
showMessage: true
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,11 +48,27 @@ class MainRecoverPasswordPage extends React.Component {
|
||||
}
|
||||
|
||||
renderRecoverStatus() {
|
||||
switch (this.state.recoverStatus) {
|
||||
const { recoverStatus, showMessage } = this.state;
|
||||
|
||||
switch (recoverStatus) {
|
||||
case 'valid':
|
||||
return <Message type="success">{i18n('VALID_RECOVER')}</Message>;
|
||||
return (
|
||||
<Message
|
||||
showMessage={showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
|
||||
type="success">
|
||||
{i18n('VALID_RECOVER')}
|
||||
</Message>
|
||||
);
|
||||
case 'invalid':
|
||||
return <Message type="error">{i18n('INVALID_RECOVER')}</Message>;
|
||||
return (
|
||||
<Message
|
||||
showMessage={showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
|
||||
type="error">
|
||||
{i18n('INVALID_RECOVER')}
|
||||
</Message>
|
||||
);
|
||||
case 'waiting':
|
||||
return null;
|
||||
}
|
||||
@ -78,6 +95,7 @@ class MainRecoverPasswordPage extends React.Component {
|
||||
setTimeout(() => {history.push((response.data.staff*1) ? '/admin' : '/')}, 2000);
|
||||
this.setState({
|
||||
recoverStatus: 'valid',
|
||||
showMessage: true,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
@ -85,9 +103,16 @@ class MainRecoverPasswordPage extends React.Component {
|
||||
onPasswordRecoverFail() {
|
||||
this.setState({
|
||||
recoverStatus: 'invalid',
|
||||
showMessage: true,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default MainRecoverPasswordPage;
|
||||
|
@ -5,6 +5,7 @@ import classNames from 'classnames';
|
||||
import i18n from 'lib-app/i18n';
|
||||
import API from 'lib-app/api-call';
|
||||
import history from 'lib-app/history';
|
||||
import SessionStore from 'lib-app/session-store';
|
||||
|
||||
import Captcha from 'app/main/captcha';
|
||||
import SubmitButton from 'core-components/submit-button';
|
||||
@ -26,16 +27,29 @@ class MainSignUpWidget extends React.Component {
|
||||
this.state = {
|
||||
loading: false,
|
||||
email: null,
|
||||
customFields: null
|
||||
customFields: null,
|
||||
showMessage: true,
|
||||
message: null
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
API.call({
|
||||
path: '/system/get-custom-fields',
|
||||
data: {}
|
||||
})
|
||||
.then(result => this.setState({customFields: result.data}));
|
||||
if(!SessionStore.getItem('customFields')) {
|
||||
API.call({
|
||||
path: '/system/get-custom-fields',
|
||||
data: {}
|
||||
})
|
||||
.then(result => {
|
||||
SessionStore.storeCustomField(result.data);
|
||||
this.setState({
|
||||
customFields: result.data
|
||||
});
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
customFields: SessionStore.getCustomFields()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -89,11 +103,27 @@ class MainSignUpWidget extends React.Component {
|
||||
}
|
||||
|
||||
renderMessage() {
|
||||
switch (this.state.message) {
|
||||
const { message, showMessage } = this.state;
|
||||
|
||||
switch (message) {
|
||||
case 'success':
|
||||
return <Message type="success">{i18n('SIGNUP_SUCCESS')}</Message>;
|
||||
return (
|
||||
<Message
|
||||
showMessage={showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
|
||||
type="success">
|
||||
{i18n('SIGNUP_SUCCESS')}
|
||||
</Message>
|
||||
);
|
||||
case 'fail':
|
||||
return <Message type="error">{i18n('EMAIL_EXISTS')}</Message>;
|
||||
return (
|
||||
<Message
|
||||
showMessage={showMessage}
|
||||
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
|
||||
type="error">
|
||||
{i18n('EMAIL_EXISTS')}
|
||||
</Message>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@ -153,16 +183,24 @@ class MainSignUpWidget extends React.Component {
|
||||
onSignupSuccess() {
|
||||
this.setState({
|
||||
loading: false,
|
||||
message: 'success'
|
||||
message: 'success',
|
||||
showMessage: true
|
||||
}, () => {
|
||||
setTimeout(() => {history.push('/check-ticket')}, 2000);
|
||||
setTimeout(() => {history.push('/')}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
onSignupFail() {
|
||||
this.setState({
|
||||
loading: false,
|
||||
message: 'fail'
|
||||
message: 'fail',
|
||||
showMessage: true
|
||||
});
|
||||
}
|
||||
|
||||
onCloseMessage(showMessage) {
|
||||
this.setState({
|
||||
[showMessage]: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
opensupports_version = '4.9.0';
|
||||
opensupports_version = '4.11.0';
|
||||
root = 'http://localhost:3000';
|
||||
apiRoot = 'http://localhost:3000/api';
|
||||
globalIndexPath = '';
|
||||
|
@ -10,6 +10,7 @@
|
||||
background-color: $very-light-grey;
|
||||
border: 1px solid $grey;
|
||||
min-height: 38px;
|
||||
display: block;
|
||||
|
||||
&:focus-within {
|
||||
outline: none;
|
||||
|
@ -12,6 +12,10 @@
|
||||
border-radius: 4px 4px 0 0;
|
||||
color: $primary-black;
|
||||
padding: 6px;
|
||||
min-height: 38px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
|
@ -39,10 +39,4 @@
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
&_select {
|
||||
.form-field__label {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,13 @@ class Form extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState(this.getInitialFormAndValidations());
|
||||
this.setState(this.getInitialFormAndValidations(this.props.children));
|
||||
}
|
||||
|
||||
componentDidUpdate(nextProps) {
|
||||
if(nextProps.values != this.props.values) {
|
||||
this.setState(this.getInitialFormAndValidations(nextProps.children));
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -135,12 +141,11 @@ class Form extends React.Component {
|
||||
return newErrors;
|
||||
}
|
||||
|
||||
getInitialFormAndValidations() {
|
||||
getInitialFormAndValidations(children) {
|
||||
let form = {};
|
||||
let validations = {};
|
||||
|
||||
reactDFS(this.props.children, (child) => {
|
||||
|
||||
reactDFS(children, (child) => {
|
||||
if (this.isValidField(child)) {
|
||||
form[child.props.name] = child.props.value || FormField.getDefaultValue(child.props.field);
|
||||
|
||||
|
@ -10,19 +10,24 @@ class Message extends React.Component {
|
||||
title: React.PropTypes.string,
|
||||
children: React.PropTypes.node,
|
||||
leftAligned: React.PropTypes.bool,
|
||||
onCloseMessage: React.PropTypes.func,
|
||||
type: React.PropTypes.oneOf(['success', 'error', 'info', 'warning'])
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
type: 'info',
|
||||
leftAligned: false
|
||||
leftAligned: false,
|
||||
showCloseButton: true,
|
||||
showMessage: true
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Motion {...this.getAnimationProps()}>
|
||||
{this.renderMessage.bind(this)}
|
||||
</Motion>
|
||||
this.props.showMessage ?
|
||||
<Motion {...this.getAnimationProps()}>
|
||||
{this.renderMessage.bind(this)}
|
||||
</Motion> :
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
@ -38,26 +43,35 @@ class Message extends React.Component {
|
||||
}
|
||||
|
||||
renderMessage(style) {
|
||||
return this.renderMessageContent(style);
|
||||
}
|
||||
|
||||
renderMessageContent(style) {
|
||||
const { children, title, showCloseButton } = this.props;
|
||||
|
||||
return (
|
||||
<div className={this.getClass()} style={style} aria-live="assertive">
|
||||
<Icon className="message__icon" name={this.getIconName()} size={this.getIconSize()} />
|
||||
<div className="message__title">{this.props.title}</div>
|
||||
<div className="message__content">{this.props.children}</div>
|
||||
<div className="message__title">{title}</div>
|
||||
<div className="message__content">{children}</div>
|
||||
{showCloseButton ? this.renderCloseButton() : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
getClass() {
|
||||
const { type, title, leftAligned, className } = this.props
|
||||
|
||||
let classes = {
|
||||
'message': true,
|
||||
'message_success': (this.props.type === 'success'),
|
||||
'message_error': (this.props.type === 'error'),
|
||||
'message_info': (this.props.type === 'info'),
|
||||
'message_warning': (this.props.type === 'warning'),
|
||||
'message_with-title': (this.props.title),
|
||||
'message_left-aligned': (this.props.leftAligned),
|
||||
'message_success': (type === 'success'),
|
||||
'message_error': (type === 'error'),
|
||||
'message_info': (type === 'info'),
|
||||
'message_warning': (type === 'warning'),
|
||||
'message_with-title': title,
|
||||
'message_left-aligned': leftAligned,
|
||||
|
||||
[this.props.className]: (this.props.className)
|
||||
[className]: className
|
||||
};
|
||||
|
||||
return classNames(classes);
|
||||
@ -77,6 +91,20 @@ class Message extends React.Component {
|
||||
getIconSize() {
|
||||
return (this.props.title) ? '2x' : 'lg';
|
||||
}
|
||||
|
||||
renderCloseButton() {
|
||||
return (
|
||||
<span className="message__close-icon" onClick={this.onCloseMessage.bind(this)}>
|
||||
<Icon name="times" size="1x"/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
onCloseMessage() {
|
||||
const { onCloseMessage } = this.props
|
||||
|
||||
onCloseMessage && onCloseMessage();
|
||||
}
|
||||
}
|
||||
|
||||
export default Message;
|
||||
|
@ -111,4 +111,11 @@
|
||||
padding-left: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
&__close-icon {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,9 @@
|
||||
z-index: 1000;
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
margin: auto;
|
||||
background-color: white;
|
||||
|
@ -18,7 +18,7 @@ class TagSelector extends React.Component {
|
||||
|
||||
render() {
|
||||
const items = this.props.items.map(tag => ({...tag, content: this.renderTagOption(tag)}));
|
||||
const values = items.filter(item => _.includes(this.props.values, item.name));
|
||||
const values = this.props.values.map((tagName) => items.find(_tag => (_tag.name === tagName)));
|
||||
|
||||
return (
|
||||
<div className="tag-selector">
|
||||
|
@ -2,11 +2,12 @@ import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import ReactQuill, { Quill } from 'react-quill';
|
||||
import ImageResize from 'quill-image-resize-module-react';
|
||||
|
||||
import MagicUrl from 'quill-magic-url';
|
||||
import {isIE} from 'lib-core/navigator';
|
||||
import Base64ImageParser from 'lib-core/base64-image-parser';
|
||||
|
||||
Quill.register('modules/ImageResize', ImageResize);
|
||||
Quill.register('modules/magicUrl', MagicUrl)
|
||||
|
||||
class TextEditor extends React.Component {
|
||||
static propTypes = {
|
||||
@ -113,6 +114,7 @@ class TextEditor extends React.Component {
|
||||
],
|
||||
},
|
||||
ImageResize: {parchment: Quill.import('parchment')},
|
||||
magicUrl: true
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
display: inline-block;
|
||||
|
||||
&--widget {
|
||||
-webkit-perspective: 0;
|
||||
-webkit-backface-visibility: hidden;
|
||||
-webkit-transform: translate3d(0,0,0);
|
||||
visibility: visible;
|
||||
|
@ -102,6 +102,7 @@ export default {
|
||||
'CHANGE_EMAIL': 'Mudar o e-mail',
|
||||
'CHANGE_PASSWORD': 'Mudar a senha',
|
||||
'NAME': 'Nome',
|
||||
'APPLY': 'Aplicar',
|
||||
'SIGNUP_DATE': 'Data de inscrição',
|
||||
'CLEAR': 'Claro',
|
||||
'SEARCH': 'Procurar',
|
||||
@ -121,6 +122,7 @@ export default {
|
||||
'NO_RESULTS': 'Nenhum resultado',
|
||||
'DELETE_AND_BAN': 'Excluir e banir',
|
||||
'STAFF_LEVEL': 'Nível Pessoal',
|
||||
'TICKETS_ASSIGNED': 'Ingressos atribuídos',
|
||||
'ASSIGNED': 'Atribuído',
|
||||
'ASSIGNED_TICKETS': '{tickets} chamados atribuídos',
|
||||
'CLOSED_TICKETS': '{tickets} chamados fechados',
|
||||
@ -133,9 +135,10 @@ export default {
|
||||
'LEVEL_3': 'Nível 3 (Chamados + Artigos + Equipe)',
|
||||
'LEVEL_1_DESCRIPTION': 'Só pode responder aos chamados e gerenciar os usuários',
|
||||
'LEVEL_2_DESCRIPTION': 'Pode fazer o que o nível 1 faz e pode criar ou editar artigos e pode criar respostas customizadas.',
|
||||
'LEVEL_3_DESCRIPTION': 'Pode fazero que o nível 2 faz e pode criar ou editar membros da equipe e pode gerenciar todo o sistema.',
|
||||
'LEVEL_3_DESCRIPTION': 'Pode fazer o que o nível 2 faz e pode criar ou editar membros da equipe e pode gerenciar todo o sistema.',
|
||||
'UPDATE_EMAIL': 'Atualizar e-mail',
|
||||
'UPDATE_PASSWORD': 'Atualizar senha',
|
||||
'UPDATE_CUSTOM_FIELDS': 'Atualize campos personalizados',
|
||||
'UPDATE_LEVEL': 'Nível de atualização',
|
||||
'UPDATE_DEPARTMENTS': 'Atualizar departamentos',
|
||||
'EDIT_STAFF': 'Editar membro da equipe',
|
||||
@ -189,7 +192,7 @@ export default {
|
||||
'BACKUP_DATABASE': 'Backup do banco de dados',
|
||||
'DELETE_ALL_USERS': 'Excluir todos os usuários',
|
||||
'PLEASE_CONFIRM_PASSWORD': 'Confirme sua senha para fazer essas alterações',
|
||||
'REGISTRATION_API_KEYS': 'API de registro',
|
||||
'API_KEYS': 'Chaves API',
|
||||
'NAME_OF_KEY': 'Nome da chave',
|
||||
'KEY': 'Chave',
|
||||
'ADD_API_KEY': 'Adicionar chave de API',
|
||||
@ -228,11 +231,22 @@ export default {
|
||||
'OPTIONS': 'Opções',
|
||||
'FIELD_DESCRIPTION': 'Descrição do campo (opcional)',
|
||||
'DESCRIPTION_ADD_CUSTOM_TAG': 'aqui você pode adicionar uma nova tag personalizada',
|
||||
'PERMISSIONS': 'Permissões',
|
||||
'TICKET_CREATION_PERMISSION': 'Permitir a criação de bilhetes',
|
||||
'TICKET_CHECK_PERMISSION': 'Permitir verificar o bilheteiro',
|
||||
'TICKET_NUMBER_RETURN_PERMISSION': 'Permitir retorno do número do ticket',
|
||||
'USER_CREATION_PERMISSION': 'Permitir a criação do usuário',
|
||||
'DESCRIPTION_EDIT_CUSTOM_TAG': 'aqui você pode editar uma marca personalizada',
|
||||
'CUSTOM_FIELDS': 'Os campos personalizados',
|
||||
|
||||
'CHART_CREATE_TICKET': 'Chamados criados',
|
||||
'CHART_CLOSE': 'Chamados fechados',
|
||||
'RESEND_EMAIL_VERIFICATION': 'Reenviar a verificação por email.',
|
||||
'RESEND_EMAIL_VERIFICATION_SUCCESS': 'O correio foi enviado com sucesso',
|
||||
'RESEND_EMAIL_VERIFICATION_FAIL': 'ocorreu um erro',
|
||||
'USER_UNLOGGED_IN': 'Este usuário nunca foi logado antes',
|
||||
'RESEND_STAFF_INVITATION_SUCCESS': 'O convite foi enviado com sucesso',
|
||||
'RESEND_STAFF_INVITATION_FAIL': 'O convite não pôde ser enviado',
|
||||
'CHART_SIGNUP': 'Inscrições',
|
||||
'CHART_COMMENT': 'Respondidos',
|
||||
'CHART_ASSIGN': 'Atribuídos',
|
||||
@ -284,6 +298,7 @@ export default {
|
||||
'DATABASE_NAME': 'Nome do banco de dados MySQL',
|
||||
'DATABASE_USER': 'Usuário do MySQL',
|
||||
'DATABASE_PASSWORD': 'Senha do MySQL',
|
||||
'INSTALLATION_COMPLETED_TITLE': 'Instalação completa',
|
||||
'ADMIN_NAME': 'Nome da conta de administrador',
|
||||
'ADMIN_EMAIL': 'E-mail da conta de administrador',
|
||||
'ADMIN_PASSWORD': 'Senha da conta de administrador',
|
||||
@ -368,6 +383,7 @@ export default {
|
||||
'INVITE_USER_VIEW_DESCRIPTION': 'Aqui você pode convidar um usuário para se juntar nosso sistema de suporte, ele só precisará fornecer sua senha para criar um novo usuário.',
|
||||
'ERROR_NAME': 'Nome inválido',
|
||||
'INVITE_STAFF_DESCRIPTION': 'Aqui você pode convidar membros do pessoal para suas equipes.',
|
||||
'TICKETS_INFORMATION' : 'Ingressos de departamentos que você não atribuiu não ficarão visíveis.',
|
||||
'ERROR_TITLE': 'Título inválido',
|
||||
'ERROR_EMAIL': 'E-mail inválido',
|
||||
'ERROR_CONTENT_SHORT': 'Conteúdo muito curto',
|
||||
@ -393,6 +409,7 @@ export default {
|
||||
'ERRORS_FOUND': 'Erros encontrados',
|
||||
'ERROR_IMAGE_SIZE': 'Nenhuma imagem pode ter um tamanho maior que {size} MB',
|
||||
'USER_DISABLED': 'Esta conta está desativada.',
|
||||
'NAME_ALREADY_USED': 'Nome já usado',
|
||||
'INVALID_SYNTAX': 'Sintaxe inválida.',
|
||||
'DEPARTMENT_PRIVATE_TICKETS': 'Este departamento tem chamados criados por não funcionários e não pode ser privado',
|
||||
'CURRENTLY_UNAVAILABLE': 'Atualmente indisponivel',
|
||||
@ -404,10 +421,12 @@ export default {
|
||||
'INVALID_SUPERVISED_USERS': 'usuários supervisionadas inválidos',
|
||||
'INVALID_DEFAULT_DEPARTMENT': 'escolhido departamento padrão é inválido',
|
||||
'TICKET_SENT': 'O chamado foi aberto com sucesso.',
|
||||
'TICKET_NUMBER_SENT': 'O tíquete foi criado com sucesso e um e-mail com o número do tíquete foi enviado.',
|
||||
'SUPERVISOR_CAN_NOT_SUPERVISE_HIMSELF': 'Supervisor não pode fiscalizar a si mesmo',
|
||||
'VALID_RECOVER': 'Senha recuperada com êxito',
|
||||
'EMAIL_EXISTS': 'e-mail já existe',
|
||||
'INVITE_USER_SUCCESS': 'Você convidou um novo usuário com sucesso em nosso sistema de suporte',
|
||||
'WILL_DELETE_CUSTOM_TAG': 'A tag personalizada será excluída.',
|
||||
'ARE_YOU_SURE': 'Você tem certeza?',
|
||||
'EMAIL_WILL_CHANGE': 'O e-mail atual será alterado',
|
||||
'PASSWORD_WILL_CHANGE': 'A senha atual será alterada',
|
||||
@ -426,6 +445,8 @@ export default {
|
||||
'WILL_DELETE_STAFF': 'Este membro da equipe será excluído e todos os seus chamados serão desatribuídos.',
|
||||
'WILL_RECOVER_EMAIL_TEMPLATE': 'Este modelo de e-mail será recuperado para seu valor padrão neste idioma.',
|
||||
'SUCCESS_IMPORTING_CSV_DESCRIPTION': 'O arquivo CSV foi importado com êxito',
|
||||
'TODAY_AT': 'Hoje às',
|
||||
'YESTERDAY_AT': 'Ontem às',
|
||||
'SUCCESS_DELETING_ALL_USERS': 'Os usuários foram excluídos com êxito',
|
||||
'SUCCESSFUL_CONNECTION': 'Conexão bem sucedida',
|
||||
'UNSUCCESSFUL_CONNECTION': 'Conexão sem sucesso',
|
||||
@ -456,5 +477,24 @@ export default {
|
||||
'TEST_SMTP_CONNECTION': 'Testar conexão SMTP',
|
||||
'SERVER_ERROR': 'Não é possível se conectar ao servidor.',
|
||||
'EMAIL_SERVER_ADDRESS': 'Endereço do servidor de email',
|
||||
'EMAIL_SERVER_ADDRESS_DESCRIPTION': 'Endereço onde os e-mails serão recebidos e enviados'
|
||||
'EMAIL_SERVER_ADDRESS_DESCRIPTION': 'Endereço onde os e-mails serão recebidos e enviados',
|
||||
'CREATED': 'Criada',
|
||||
'CREATED_DESCRIPTION': 'Created tickets during the selected time range',
|
||||
'OPEN': 'Open',
|
||||
'OPEN_DESCRIPTION': 'Tíquetes criados durante o intervalo de tempo selecionado',
|
||||
'CLOSED_DESCRIPTION': 'Tíquetes fechados criados durante o intervalo de tempo selecionado',
|
||||
'INSTANT': 'Instante',
|
||||
'INSTANT_DESCRIPTION': 'Porcentagem de tíquetes fechados após uma única resposta da equipe sobre o total de tíquetes fechados',
|
||||
'REOPENED': 'Reaberta',
|
||||
'REOPENED_DESCRIPTION': 'Porcentagem de tíquetes reabertos sobre o total de tíquetes criados',
|
||||
'MONDAY': 'Segunda-feira',
|
||||
'TUESDAY': 'Terça-feira',
|
||||
'WEDNESDAY': 'quarta-feira',
|
||||
'THURSDAY': 'quinta-feira',
|
||||
'FRIDAY': 'sexta-feira',
|
||||
'SATURDAY': 'sábado',
|
||||
'SUNDAY': 'Domigo',
|
||||
'TICKET_NUMBER_SENT': 'O tíquete foi criado com sucesso e um e-mail com o número do tíquete foi enviado.',
|
||||
'TICKETS_INFORMATION' : 'Ingressos de departamentos que você não atribuiu não ficarão visíveis.',
|
||||
'API_KEYS': 'Chaves API'
|
||||
};
|
||||
|
@ -102,6 +102,7 @@ export default {
|
||||
'CHANGE_EMAIL': '更改電子郵件',
|
||||
'CHANGE_PASSWORD': '更改密碼',
|
||||
'NAME': '名稱',
|
||||
'APPLY': '申请',
|
||||
'SIGNUP_DATE': '註冊日期',
|
||||
'CLEAR': '明确',
|
||||
'SEARCH': '搜索',
|
||||
@ -121,6 +122,7 @@ export default {
|
||||
'NO_RESULTS': '沒有結果',
|
||||
'DELETE_AND_BAN': '刪除和禁止',
|
||||
'STAFF_LEVEL': '員工級別',
|
||||
'TICKETS_ASSIGNED': '分配的门票',
|
||||
'ASSIGNED': '分配',
|
||||
'ASSIGNED_TICKETS': '{ticket}分配的票',
|
||||
'CLOSED_TICKETS': '{ticket}已關閉的門票',
|
||||
@ -136,6 +138,7 @@ export default {
|
||||
'LEVEL_3_DESCRIPTION': '可以做每一級2,可以創建或編輯員工,並可以管理整個系統。',
|
||||
'UPDATE_EMAIL': '更新電子郵件',
|
||||
'UPDATE_PASSWORD': '更新密碼',
|
||||
'UPDATE_CUSTOM_FIELDS': '更新自定义字段',
|
||||
'UPDATE_LEVEL': '更新級別',
|
||||
'UPDATE_DEPARTMENTS': '更新部門',
|
||||
'EDIT_STAFF': '編輯員工',
|
||||
@ -189,7 +192,6 @@ export default {
|
||||
'BACKUP_DATABASE': '備份數據庫',
|
||||
'DELETE_ALL_USERS': '刪除所有用戶',
|
||||
'PLEASE_CONFIRM_PASSWORD': '請確認您的密碼進行更改',
|
||||
'REGISTRATION_API_KEYS': '註冊API密鑰',
|
||||
'NAME_OF_KEY': '密鑰名稱',
|
||||
'KEY': '鍵',
|
||||
'ADD_API_KEY': '添加API密鑰',
|
||||
@ -229,11 +231,22 @@ export default {
|
||||
'FIELD_DESCRIPTION': '字段描述(可选)',
|
||||
'DESCRIPTION_ADD_CUSTOM_TAG': '在这里,您可以添加新的自定义标记',
|
||||
'DESCRIPTION_EDIT_CUSTOM_TAG': '在这里你可以编辑自定义标签',
|
||||
'PERMISSIONS': '权限',
|
||||
'TICKET_CREATION_PERMISSION': '允许票务创造',
|
||||
'TICKET_CHECK_PERMISSION': '允许票务检查',
|
||||
'TICKET_NUMBER_RETURN_PERMISSION': '允许票号返回',
|
||||
'USER_CREATION_PERMISSION': '允许用户创建',
|
||||
'CUSTOM_FIELDS': '自定义字段',
|
||||
|
||||
'CHART_CREATE_TICKET': '已創建門票',
|
||||
'CHART_CLOSE': '門票已關閉',
|
||||
'CHART_SIGNUP': '註冊',
|
||||
'RESEND_EMAIL_VERIFICATION': '重新发送电子邮件验证',
|
||||
'RESEND_EMAIL_VERIFICATION_SUCCESS': '邮件已成功发送',
|
||||
'RESEND_EMAIL_VERIFICATION_FAIL': '发生了错误',
|
||||
'USER_UNLOGGED_IN': '此用户以前从未登录过',
|
||||
'RESEND_STAFF_INVITATION_SUCCESS': '邀请成功发送',
|
||||
'RESEND_STAFF_INVITATION_FAIL': '无法发送邀请',
|
||||
'CHART_COMMENT': '回复',
|
||||
'CHART_ASSIGN': '分配',
|
||||
|
||||
@ -285,6 +298,7 @@ export default {
|
||||
'DATABASE_USER': 'MySQL用戶',
|
||||
'DATABASE_PASSWORD': 'MySQL密碼',
|
||||
'ADMIN_NAME': '管理員帳戶名稱',
|
||||
'INSTALLATION_COMPLETED_TITLE': '安装完成',
|
||||
'ADMIN_EMAIL': '管理帳戶電子郵件',
|
||||
'ADMIN_PASSWORD': '管理員帳號密碼',
|
||||
'ADMIN_PASSWORD_DESCRIPTION': '請記住這個密碼。需要訪問管理面板。您可以稍後更改。',
|
||||
@ -395,6 +409,7 @@ export default {
|
||||
'USER_DISABLED': '此帐户已被停用。',
|
||||
'INVALID_SYNTAX': '无效的语法。',
|
||||
'DEPARTMENT_PRIVATE_TICKETS': '这个部门有非工作人员创建的门票,不能是私人的',
|
||||
'NAME_ALREADY_USED': '已使用的名称',
|
||||
'CURRENTLY_UNAVAILABLE': '当前不可用',
|
||||
'CAN_NOT_DELETE_DEFAULT_DEPARTMENT': '默认部门不能被删除',
|
||||
|
||||
@ -411,6 +426,7 @@ export default {
|
||||
'ARE_YOU_SURE': '你確定?',
|
||||
'EMAIL_WILL_CHANGE': '當前的電子郵件將被更改',
|
||||
'PASSWORD_WILL_CHANGE': '當前密碼將被更改',
|
||||
'WILL_DELETE_CUSTOM_TAG': '自定义标记将被删除。',
|
||||
'EMAIL_CHANGED': '電子郵件已成功更改',
|
||||
'PASSWORD_CHANGED': '密碼已成功更改',
|
||||
'OLD_PASSWORD_INCORRECT': '舊密碼不正確',
|
||||
@ -429,6 +445,8 @@ export default {
|
||||
'SUCCESS_DELETING_ALL_USERS': '用戶已成功刪除',
|
||||
'SUCCESSFUL_CONNECTION': '成功连接',
|
||||
'UNSUCCESSFUL_CONNECTION': '连接不成功',
|
||||
'TODAY_AT': '今天',
|
||||
'YESTERDAY_AT': '昨天',
|
||||
'SERVER_CREDENTIALS_WORKING': '服务器凭据正常工作',
|
||||
'DELETE_CUSTOM_FIELD_SURE': '某些用户可能正在使用此字段。你确定你要删除吗?',
|
||||
|
||||
@ -456,5 +474,25 @@ export default {
|
||||
'TEST_SMTP_CONNECTION': '测试SMTP连接',
|
||||
'SERVER_ERROR': '无法连接到服务器。',
|
||||
'EMAIL_SERVER_ADDRESS': '电邮服务器地址',
|
||||
'EMAIL_SERVER_ADDRESS_DESCRIPTION': '地址将收到和发送邮件'
|
||||
'EMAIL_SERVER_ADDRESS_DESCRIPTION': '地址将收到和发送邮件',
|
||||
/////////////////////
|
||||
'CREATED': '已创建',
|
||||
'CREATED_DESCRIPTION': '在选定时间范围内创建的工单',
|
||||
'OPEN': '打开',
|
||||
'OPEN_DESCRIPTION': '在选定时间范围内创建的未结票',
|
||||
'CLOSED_DESCRIPTION': '在选定时间范围内创建的已关闭工单',
|
||||
'INSTANT': '立即的',
|
||||
'INSTANT_DESCRIPTION': '单个工作人员回复后关闭的门票占关闭的门票总数的百分比',
|
||||
'REOPENED': '重新开放',
|
||||
'REOPENED_DESCRIPTION': '重新打开的工单占创建的工单总数的百分比',
|
||||
'MONDAY': '周一',
|
||||
'TUESDAY': '周二',
|
||||
'WEDNESDAY': '周三',
|
||||
'THURSDAY': '周四',
|
||||
'FRIDAY': '星期五',
|
||||
'SATURDAY': '周六',
|
||||
'SUNDAY': '星期日',
|
||||
'TICKET_NUMBER_SENT': '已成功创建票证,并已发送带有票证编号的电子邮件。',
|
||||
'TICKETS_INFORMATION' : '来自您未分配部门的工单将不可见。',
|
||||
'API_KEYS': 'API 密钥'
|
||||
};
|
||||
|
@ -8,8 +8,8 @@ export default {
|
||||
'DEFAULT': 'Standard',
|
||||
'PASSWORD': 'Passwort',
|
||||
'REPEAT_PASSWORD': 'Passwort wiederholen',
|
||||
'LOG_IN': 'Einloggen',
|
||||
'SIGN_UP': 'Anmelden',
|
||||
'LOG_IN': 'Anmelden',
|
||||
'SIGN_UP': 'Registrieren',
|
||||
'FORGOT_PASSWORD': 'Passwort vergessen',
|
||||
'RECOVER_PASSWORD': 'Passwort wiederherstellen',
|
||||
'SET_UP_PASSWORD': 'Richten Sie Ihr Passwort',
|
||||
@ -102,6 +102,7 @@ export default {
|
||||
'CHANGE_EMAIL': 'E-Mail ändern',
|
||||
'CHANGE_PASSWORD': 'Passwort ändern',
|
||||
'NAME': 'Name',
|
||||
'APPLY': 'Sich bewerben',
|
||||
'SIGNUP_DATE': 'Anmeldedatum',
|
||||
'CLEAR': 'klar',
|
||||
'SEARCH': 'Suche',
|
||||
@ -121,6 +122,7 @@ export default {
|
||||
'NO_RESULTS': 'Keine Ergebnisse',
|
||||
'DELETE_AND_BAN': 'Löschen und blockieren',
|
||||
'STAFF_LEVEL': 'Mitarbeiter-Ebene',
|
||||
'TICKETS_ASSIGNED': 'Tickets zugewiesen',
|
||||
'ASSIGNED': 'Zugewiesen',
|
||||
'ASSIGNED_TICKETS': '{tickets} zugeordnete Tickets',
|
||||
'CLOSED_TICKETS': '{tickets} geschlossene Tickets',
|
||||
@ -136,6 +138,7 @@ export default {
|
||||
'LEVEL_3_DESCRIPTION': 'Kann alles aus Stufe 2, kann Mitarbeiter erstellen oder bearbeiten und kann das gesamte System verwalten.',
|
||||
'UPDATE_EMAIL': 'E-Mail aktualisieren',
|
||||
'UPDATE_PASSWORD': 'Kennwort aktualisieren',
|
||||
'UPDATE_CUSTOM_FIELDS': 'Benutzerdefinierte Felder aktualisieren.',
|
||||
'UPDATE_LEVEL': 'Stufe aktualisieren',
|
||||
'UPDATE_DEPARTMENTS': 'Abteilungen aktualisieren',
|
||||
'EDIT_STAFF': 'Mitarbeiter bearbeiten',
|
||||
@ -189,7 +192,6 @@ export default {
|
||||
'BACKUP_DATABASE': 'Sicherungsdatenbank',
|
||||
'DELETE_ALL_USERS': 'Alle Benutzer löschen',
|
||||
'PLEASE_CONFIRM_PASSWORD': 'Bitte bestätigen Sie Ihr Passwort, um diese Änderungen vorzunehmen!',
|
||||
'REGISTRATION_API_KEYS': 'Registrierungs-API Schlüssel',
|
||||
'NAME_OF_KEY': 'Name des Schlüssels',
|
||||
'KEY': 'Schlüssel',
|
||||
'ADD_API_KEY': 'API-Schlüssel hinzufügen',
|
||||
@ -207,10 +209,10 @@ export default {
|
||||
'HIMSELF': 'selbst',
|
||||
'SUPERVISED_USERS_UPDATED': 'Betreute Nutzer aktualisiert',
|
||||
'ADD_USER': 'Benutzer hinzufügen',
|
||||
'INVITE_USER': 'laden Sie Benutzer',
|
||||
'INVITE_STAFF': 'einladen Personal',
|
||||
'INVITE_USER': 'Benutzer einladen',
|
||||
'INVITE_STAFF': 'Personal einladen',
|
||||
'UPLOAD_FILE': 'Datei hochladen',
|
||||
'PRIVATE': 'Privatgelände',
|
||||
'PRIVATE': 'Privat',
|
||||
'ENABLE_USER': 'Benutzer aktivieren',
|
||||
'DISABLE_USER': 'Benutzer deaktivieren',
|
||||
'SHOW_CLOSED_TICKETS': 'Geschlossene Tickets anzeigen',
|
||||
@ -229,11 +231,22 @@ export default {
|
||||
'FIELD_DESCRIPTION': 'Feldbeschreibung (optional)',
|
||||
'DESCRIPTION_ADD_CUSTOM_TAG': 'Hier können Sie ein neues benutzerdefiniertes Tag hinzufügen',
|
||||
'DESCRIPTION_EDIT_CUSTOM_TAG': 'Hier können Sie einen benutzerdefinierten Tag bearbeiten',
|
||||
'PERMISSIONS': 'Berechtigungen',
|
||||
'TICKET_CREATION_PERMISSION': 'Erlauben Sie die Kartenkreation',
|
||||
'TICKET_CHECK_PERMISSION': 'Ticketcheck zulassen',
|
||||
'TICKET_NUMBER_RETURN_PERMISSION': 'Erlauben Sie die Ticketnummer zurück',
|
||||
'USER_CREATION_PERMISSION': 'Benutzerkreibung zulassen',
|
||||
'CUSTOM_FIELDS': 'Benutzerdefinierte Felder',
|
||||
|
||||
'CHART_CREATE_TICKET': 'Tickets erstellt',
|
||||
'CHART_CLOSE': 'Tickets geschlossen',
|
||||
'CHART_SIGNUP': 'Anmeldungen',
|
||||
'RESEND_EMAIL_VERIFICATION': 'E-Mail-Überprüfung erneut senden',
|
||||
'RESEND_EMAIL_VERIFICATION_SUCCESS': 'Die Mail wurde erfolgreich gesendet',
|
||||
'RESEND_EMAIL_VERIFICATION_FAIL': 'Ein Fehler ist aufgetreten',
|
||||
'USER_UNLOGGED_IN': 'Dieser Benutzer hat sich noch nie angemeldet',
|
||||
'RESEND_STAFF_INVITATION_SUCCESS': 'Die Einladung wurde erfolgreich gesendet',
|
||||
'RESEND_STAFF_INVITATION_FAIL': 'Die Einladung konnte nicht gesendet werden',
|
||||
'CHART_COMMENT': 'Antworten',
|
||||
'CHART_ASSIGN': 'Tickets zugewiesen',
|
||||
|
||||
@ -285,6 +298,7 @@ export default {
|
||||
'DATABASE_USER': 'MySQL Benutzer',
|
||||
'DATABASE_PASSWORD': 'MySQL Passwort',
|
||||
'ADMIN_NAME': 'Admin Kontoname',
|
||||
'INSTALLATION_COMPLETED_TITLE': 'Installation abgeschlossen',
|
||||
'ADMIN_EMAIL': 'Admin-Konto E-Mail',
|
||||
'ADMIN_PASSWORD': 'Admin-Konto Passwort',
|
||||
'ADMIN_PASSWORD_DESCRIPTION': 'Bitte merken Sie sich dieses Passwort. Es ist für den Zugriff auf das Admin-Panel erforderlich. Sie können es später ändern.',
|
||||
@ -395,6 +409,7 @@ export default {
|
||||
'USER_DISABLED': 'Dieser Account ist deaktiviert.',
|
||||
'INVALID_SYNTAX': 'Ungültiger Satzbau.',
|
||||
'DEPARTMENT_PRIVATE_TICKETS': 'Diese Abteilung hat Tickets, die von Nicht-Mitarbeitern erstellt wurden, und kann nicht privat sein',
|
||||
'NAME_ALREADY_USED': 'Name bereits benutzt',
|
||||
'CURRENTLY_UNAVAILABLE': 'momentan nicht verfügbar',
|
||||
'CAN_NOT_DELETE_DEFAULT_DEPARTMENT': 'Standard-Abteilung kann nicht gelöscht werden',
|
||||
|
||||
@ -411,6 +426,7 @@ export default {
|
||||
'ARE_YOU_SURE': 'Sind Sie sicher?',
|
||||
'EMAIL_WILL_CHANGE': 'Die aktuelle E-Mail-Adresse wird geändert.',
|
||||
'PASSWORD_WILL_CHANGE': 'Das aktuelle Passwort wird geändert.',
|
||||
'WILL_DELETE_CUSTOM_TAG': 'Das benutzerdefinierte Tag wird gelöscht.',
|
||||
'EMAIL_CHANGED': 'Die E-Mail-Adresse wurde erfolgreich geändert.',
|
||||
'PASSWORD_CHANGED': 'Das Passwort wurde erfolgreich geändert.',
|
||||
'OLD_PASSWORD_INCORRECT': 'Das alte Passwort ist falsch.',
|
||||
@ -429,6 +445,8 @@ export default {
|
||||
'SUCCESS_DELETING_ALL_USERS': 'Die Benutzer wurden erfolgreich gelöscht.',
|
||||
'SUCCESSFUL_CONNECTION': 'Erfolgreiche Verbindung',
|
||||
'UNSUCCESSFUL_CONNECTION': 'Verbindung fehlgeschlagen',
|
||||
'TODAY_AT': 'Heute um',
|
||||
'YESTERDAY_AT': 'Gestern um',
|
||||
'SERVER_CREDENTIALS_WORKING': 'Server-Anmeldeinformationen funktionieren ordnungsgemäß',
|
||||
'DELETE_CUSTOM_FIELD_SURE': 'Einige Benutzer verwenden dieses Feld möglicherweise. Möchten Sie es wirklich löschen?',
|
||||
|
||||
@ -451,10 +469,29 @@ export default {
|
||||
'LEFT_EMPTY_DATABASE': 'Leer lassen für automatische Datenbankerstellung.',
|
||||
'REMEMBER_ME': 'Merken',
|
||||
'EMAIL_LOWERCASE': 'E-Mail',
|
||||
'DEFAULT_PORT': 'Leave leer für 3306 als Standard',
|
||||
'DEFAULT_PORT': 'Für 3306 als Standard leer lassen',
|
||||
'PASSWORD_LOWERCASE': 'Passwort',
|
||||
'TEST_SMTP_CONNECTION': 'SMTP Verbindung testen',
|
||||
'SERVER_ERROR': 'Kann nicht mit dem Server verbinden.',
|
||||
'EMAIL_SERVER_ADDRESS': 'E-Mail-Serveradresse',
|
||||
'EMAIL_SERVER_ADDRESS_DESCRIPTION': 'Adresse, an die Mails gesendet und gesendet werden'
|
||||
'EMAIL_SERVER_ADDRESS_DESCRIPTION': 'Adresse, an die Mails empfangen und gesendet werden',
|
||||
'CREATED': 'Erstellt',
|
||||
'CREATED_DESCRIPTION': 'Erstellte Tickets im ausgewählten Zeitraum',
|
||||
'OPEN': 'Offen',
|
||||
'OPEN_DESCRIPTION': 'Offene Tickets, die im ausgewählten Zeitraum erstellt wurden',
|
||||
'CLOSED_DESCRIPTION': 'Geschlossene Tickets, die im ausgewählten Zeitraum erstellt wurden',
|
||||
'INSTANT': 'Sofortig',
|
||||
'INSTANT_DESCRIPTION': 'Percentage of tickets closed after a single staff reply over the total of tickets closed',
|
||||
'REOPENED': 'Wieder geöffnet',
|
||||
'REOPENED_DESCRIPTION': 'Prozentsatz der wiedereröffneten Tickets an der Gesamtzahl der erstellten Tickets',
|
||||
'MONDAY': 'Montag',
|
||||
'TUESDAY': 'Dienstag',
|
||||
'WEDNESDAY': 'Mittwoch',
|
||||
'THURSDAY': 'Donnerstag',
|
||||
'FRIDAY': 'Freitag',
|
||||
'SATURDAY': 'Samstag',
|
||||
'SUNDAY': 'Sonntag',
|
||||
'TICKET_NUMBER_SENT': 'Ticket wurde erfolgreich erstellt und eine E-Mail mit der Ticketnummer wurde gesendet.',
|
||||
'TICKETS_INFORMATION' : 'Tickets von Abteilungen, die Sie nicht zugewiesen haben, werden nicht angezeigt.',
|
||||
'API_KEYS': 'API-Schlüssel'
|
||||
};
|
||||
|
@ -27,6 +27,7 @@ export default {
|
||||
'TICKET_LIST': 'Ticket List',
|
||||
'SUPPORT_CENTER': 'Support Center',
|
||||
'SUPERVISED_USER': 'Supervised users',
|
||||
'SUPERVISED_USER_INFORMATION': 'Allows to see tickets from another user',
|
||||
'DEPARTMENT': 'Department',
|
||||
'DEFAULT_DEPARTMENT': 'Default Department',
|
||||
'AUTHOR': 'Author',
|
||||
@ -77,6 +78,7 @@ export default {
|
||||
'OWNER': 'Owner',
|
||||
'OWNED': 'Owned',
|
||||
'STATUS': 'Status',
|
||||
'PERIOD': 'Period',
|
||||
'NONE': 'None',
|
||||
'ANY': 'Any',
|
||||
'OPENED': 'Opened',
|
||||
@ -190,7 +192,7 @@ export default {
|
||||
'BACKUP_DATABASE': 'Backup database',
|
||||
'DELETE_ALL_USERS': 'Delete all users',
|
||||
'PLEASE_CONFIRM_PASSWORD': 'Please confirm your password to make these changes',
|
||||
'REGISTRATION_API_KEYS': 'Registration API keys',
|
||||
'API_KEYS': 'API keys',
|
||||
'NAME_OF_KEY': 'Name of key',
|
||||
'KEY': 'Key',
|
||||
'ADD_API_KEY': 'Add API Key',
|
||||
@ -224,6 +226,7 @@ export default {
|
||||
'NEW_CUSTOM_FIELD': 'New Custom field',
|
||||
'TYPE': 'Type',
|
||||
'SELECT_INPUT': 'Select input',
|
||||
'TEXT': 'Text',
|
||||
'TEXT_INPUT': 'Text input',
|
||||
'OPTION': 'Option {index}',
|
||||
'OPTIONS': 'Options',
|
||||
@ -241,6 +244,13 @@ export default {
|
||||
'CHART_SIGNUP': 'Signups',
|
||||
'CHART_COMMENT': 'Replies',
|
||||
'CHART_ASSIGN': 'Assigned',
|
||||
'RESEND_EMAIL_VERIFICATION': 'Resend e-mail verification',
|
||||
'RESEND_EMAIL_VERIFICATION_SUCCESS': 'The mail was sent successfully',
|
||||
'RESEND_EMAIL_VERIFICATION_FAIL': 'An error has occurred',
|
||||
'USER_UNLOGGED_IN': 'This user has never logged in before',
|
||||
'RESEND_STAFF_INVITATION_SUCCESS': 'The invitation was sent successfully',
|
||||
'RESEND_STAFF_INVITATION_FAIL': 'The invitation could not be sent',
|
||||
'SESSION_EXPIRED': 'Session expired',
|
||||
|
||||
//ACTIVITIES
|
||||
'ACTIVITY_COMMENT': 'commented ticket',
|
||||
@ -362,6 +372,8 @@ export default {
|
||||
'CUSTOM_FIELDS_DESCRIPTION': 'Custom fields are defined additional fields the users are able to fill to provide more information about them.',
|
||||
'INVITE_USER_VIEW_DESCRIPTION': 'Here you can invite an user to join our support system, he will just need to provide his password to create a new user.',
|
||||
'INVITE_STAFF_DESCRIPTION': 'Here you can invite staff members to your teams.',
|
||||
'TICKETS_INFORMATION' : 'Tickets from departments you don’t have assigned won’t be visible.',
|
||||
'SESSION_EXPIRED_DESCRIPTION': 'Your session has timed out. Please log in again.',
|
||||
|
||||
//ERRORS
|
||||
'EMAIL_OR_PASSWORD': 'Email or password invalid',
|
||||
@ -403,11 +415,16 @@ export default {
|
||||
'INVALID_SUPERVISED_USERS': 'Invalid supervised users',
|
||||
'SUPERVISOR_CAN_NOT_SUPERVISE_HIMSELF': 'Supervisor can not supervise himself',
|
||||
'NAME_ALREADY_USED': 'Name already used',
|
||||
'INVALID_PAGE_SIZE': 'Invalid page size',
|
||||
'INVALID_CUSTOM_FIELD_TYPE': 'Invalid custom field type',
|
||||
'INVALID_CUSTOM_FIELD_OPTIONS': 'Invalid custom field options',
|
||||
'CUSTOM_FIELD_ALREADY_EXISTS': 'Custom field already exists',
|
||||
|
||||
//MESSAGES
|
||||
'SIGNUP_SUCCESS': 'You have registered successfully in our support system.',
|
||||
'INVITE_USER_SUCCESS': 'You have invited a new user successfully in our support system',
|
||||
'TICKET_SENT': 'Ticket has been created successfully.',
|
||||
'TICKET_NUMBER_SENT': 'Ticket has been created successfully and an email with the ticket number has been sent.',
|
||||
'VALID_RECOVER': 'Password recovered successfully',
|
||||
'EMAIL_EXISTS': 'Email already exists',
|
||||
'ARE_YOU_SURE': 'Confirm action',
|
||||
@ -437,6 +454,8 @@ export default {
|
||||
|
||||
'TITLE_EDITED': '(title edited)',
|
||||
'COMMENT_EDITED': '(comment edited)',
|
||||
'TODAY_AT': 'Today at',
|
||||
'YESTERDAY_AT': 'Yesterday at',
|
||||
'LAST_7_DAYS': 'Last 7 days',
|
||||
'LAST_30_DAYS': 'Last 30 days',
|
||||
'LAST_90_DAYS': 'Last 90 days',
|
||||
@ -464,7 +483,6 @@ export default {
|
||||
'CREATED_DESCRIPTION': 'Created tickets during the selected time range',
|
||||
'OPEN': 'Open',
|
||||
'OPEN_DESCRIPTION': 'Open tickets created during the selected time range',
|
||||
'CLOSED': 'Closed',
|
||||
'CLOSED_DESCRIPTION': 'Closed tickets created during the selected time range',
|
||||
'INSTANT': 'Instant',
|
||||
'INSTANT_DESCRIPTION': 'Percentage of tickets closed after a single staff reply over the total of tickets closed',
|
||||
|
@ -1,7 +1,7 @@
|
||||
export default {
|
||||
'WELCOME': 'Bienvenido',
|
||||
'TICKETS': 'Tickets',
|
||||
'ARTICLES': 'Artículo',
|
||||
'ARTICLES': 'Artículos',
|
||||
'ACCOUNT': 'Cuenta',
|
||||
'SUBMIT': 'Enviar',
|
||||
'EMAIL': 'Email',
|
||||
@ -28,9 +28,9 @@ export default {
|
||||
'SUPPORT_CENTER': 'Centro de Soporte',
|
||||
'SUPERVISED_USER': 'Los usuarios supervisados',
|
||||
'DEPARTMENT': 'Departamento',
|
||||
'DEFAULT_DEPARTMENT': 'Por defecto Departamento',
|
||||
'DEFAULT_DEPARTMENT': 'Departamento predeterminado',
|
||||
'AUTHOR': 'Autor',
|
||||
'AUTHORS': 'autores',
|
||||
'AUTHORS': 'Autores',
|
||||
'DATE': 'Fecha',
|
||||
'RESPOND': 'Responder',
|
||||
'RESPOND_TICKET': 'Responder ticket',
|
||||
@ -52,14 +52,14 @@ export default {
|
||||
'NEW_TICKETS': 'Nuevos Tickets',
|
||||
'ALL_TICKETS': 'Todos los Tickets',
|
||||
'CUSTOM_RESPONSES': 'Respuestas Personalizadas',
|
||||
'SEARCH_TICKETS': 'Buscar entradas',
|
||||
'SEARCH_TICKETS': 'Buscar tickets.',
|
||||
'CUSTOM_LIST': 'Lista personalizada',
|
||||
'CUSTOM_TAGS': 'Etiquetas personalizadas',
|
||||
'LIST_USERS': 'Lista de Usuarios',
|
||||
'TAGS': 'Etiquetas',
|
||||
'BAN_USERS': 'Bloquear Usuarios',
|
||||
'LIST_ARTICLES': 'Lista de Artículos',
|
||||
'STAFF_MEMBERS': 'Staff Members',
|
||||
'STAFF_MEMBERS': 'Miembros del staff',
|
||||
'DEPARTMENTS': 'Departamentos',
|
||||
'SYSTEM_PREFERENCES': 'Preferencias del Sistema',
|
||||
'ADVANCED_SETTINGS': 'Opciones Avanzadas',
|
||||
@ -82,7 +82,7 @@ export default {
|
||||
'OPENED': 'Abierto',
|
||||
'CLOSED': 'Cerrado',
|
||||
'CLOSE': 'Cerrar',
|
||||
'ANY': 'Alguna',
|
||||
'ANY': 'Cualquiera',
|
||||
'RE_OPEN': 'Reabrir',
|
||||
'ASSIGN_TO_ME': 'Asignar',
|
||||
'UN_ASSIGN': 'Desasignar',
|
||||
@ -102,8 +102,9 @@ export default {
|
||||
'CHANGE_EMAIL': 'Cambiar email',
|
||||
'CHANGE_PASSWORD': 'Cambiar contraseña',
|
||||
'NAME': 'Nombre',
|
||||
'APPLY': 'Aplicar',
|
||||
'SIGNUP_DATE': 'Fecha de registro',
|
||||
'CLEAR': 'Claro',
|
||||
'CLEAR': 'Limpiar',
|
||||
'SEARCH': 'Buscar',
|
||||
'SEARCH_USERS': 'Buscar usuarios...',
|
||||
'SEARCH_EMAIL': 'Buscar email...',
|
||||
@ -121,6 +122,7 @@ export default {
|
||||
'NO_RESULTS': 'No hay resultados',
|
||||
'DELETE_AND_BAN': 'Borrar y bloquear',
|
||||
'STAFF_LEVEL': 'Nivel de Staff',
|
||||
'TICKETS_ASSIGNED': 'Tickets asignados',
|
||||
'ASSIGNED': 'Asignado',
|
||||
'ASSIGNED_TICKETS': '{tickets} Tickets Asignados',
|
||||
'CLOSED_TICKETS': '{tickets} Tickets Cerrados',
|
||||
@ -131,13 +133,14 @@ export default {
|
||||
'LEVEL_1': 'Nivel 1 (Tickets)',
|
||||
'LEVEL_2': 'Nivel 2 (Tickets + Artículos)',
|
||||
'LEVEL_3': 'Nivel 3 (Tickets + Artículos + Staff)',
|
||||
'LEVEL_1_DESCRIPTION': 'sólo puede responder tickets y administrar usuarios.',
|
||||
'LEVEL_2_DESCRIPTION': 'puede hacer lo mismo que el Nivel 1, y además, crear o editar artículos y crear respuestas personalizadas.',
|
||||
'LEVEL_3_DESCRIPTION': 'Puede hacer lo mismo que el Nivel 2, y además, crear o editar miembros del personal y puede gestionar todo el sistema.',
|
||||
'LEVEL_1_DESCRIPTION': 'Sólo puede responder tickets y administrar usuarios.',
|
||||
'LEVEL_2_DESCRIPTION': 'Puede hacer lo mismo que el Nivel 1, y además, crear o editar artículos y crear respuestas personalizadas.',
|
||||
'LEVEL_3_DESCRIPTION': 'Puede hacer lo mismo que el nivel 2, además, crear o editar miembros del staff y gestionar todo el sistema.',
|
||||
'UPDATE_EMAIL': 'Actualizar correo electrónico',
|
||||
'UPDATE_PASSWORD': 'Actualizar contraseña',
|
||||
'UPDATE_CUSTOM_FIELDS': 'Actualizar campos personalizados',
|
||||
'UPDATE_LEVEL': 'Actualizar nivel',
|
||||
'UPDATE_DEPARTMENTS': 'Actializar departamentos',
|
||||
'UPDATE_DEPARTMENTS': 'Actualizar departamentos',
|
||||
'EDIT_STAFF': 'Editar miembro del staff',
|
||||
'ADD_DEPARTMENT': 'Añadir departamento',
|
||||
'UPDATE_DEPARTMENT': 'Actualizar Departamento',
|
||||
@ -182,14 +185,14 @@ export default {
|
||||
'ALL_NOTIFICATIONS': 'Todas las notificaciones',
|
||||
'VERIFY_SUCCESS': 'Usuario verificado',
|
||||
'VERIFY_FAILED': 'No se pudo verificar',
|
||||
'ENABLE_MANDATORY_LOGIN': 'entrada obligatoria para los clientes',
|
||||
'ENABLE_MANDATORY_LOGIN': 'Logueo obligatorio para clientes',
|
||||
'ENABLE_USER_SYSTEM': 'Usar sistema de usuarios para clientes',
|
||||
'ENABLE_USER_REGISTRATION': 'Habilitar registro de usuarios',
|
||||
'INCLUDE_USERS_VIA_CSV': 'Incluir usuarios por archivo CSV',
|
||||
'BACKUP_DATABASE': 'Backup database',
|
||||
'API_KEYS': 'Claves API',
|
||||
'DELETE_ALL_USERS': 'Borrar todos los usuarios',
|
||||
'PLEASE_CONFIRM_PASSWORD': 'Por favor, confirme su contraseña para hacer estos cambios',
|
||||
'REGISTRATION_API_KEYS': 'Registro de API keys',
|
||||
'NAME_OF_KEY': 'Nombre de la clave',
|
||||
'KEY': 'Clave',
|
||||
'ADD_API_KEY': 'Añadir nueva clave',
|
||||
@ -204,21 +207,21 @@ export default {
|
||||
'STAFF_UPDATED': 'Miembro de Staff actualizado',
|
||||
'UPDATE': 'Actualizar',
|
||||
'NEVER': 'Nunca',
|
||||
'HIMSELF': 'si mismo',
|
||||
'SUPERVISED_USERS_UPDATED': 'Los usuarios supervisados actualizan',
|
||||
'HIMSELF': 'Si mismo',
|
||||
'SUPERVISED_USERS_UPDATED': 'Los usuarios supervisados han sido actualizados.',
|
||||
'ADD_USER': 'Añadir un usuario',
|
||||
'INVITE_USER': 'invitar usuario',
|
||||
'INVITE_STAFF': 'invitar staff',
|
||||
'INVITE_USER': 'Invitar usuario',
|
||||
'INVITE_STAFF': 'Invitar staff',
|
||||
'UPLOAD_FILE': 'Subir archivo',
|
||||
'PRIVATE': 'privado',
|
||||
'PRIVATE': 'Privado',
|
||||
'ENABLE_USER': 'Habilitar usuario',
|
||||
'DISABLE_USER': 'Deshabilitar usuario',
|
||||
'SHOW_CLOSED_TICKETS': 'Mostrar Tickets Cerrados',
|
||||
'LOCKED': 'bloqueado',
|
||||
'LOCKED': 'Bloqueado',
|
||||
'IMAGE_HEADER_URL': 'URL del encabezado de la imagen',
|
||||
'IMAGE_HEADER_DESCRIPTION': 'Imagen que se utilizará como encabezado del correo electrónico.',
|
||||
'EMAIL_SETTINGS': 'Ajustes del correo electrónico',
|
||||
'SHOW_MY_TICKETS': 'Mostrar mis entradas',
|
||||
'SHOW_MY_TICKETS': 'Mostrar mis tickets.',
|
||||
'ADDITIONAL_FIELDS': 'Campos Adicionales',
|
||||
'NEW_CUSTOM_FIELD': 'Nuevo campo personalizado',
|
||||
'TYPE': 'Tipo',
|
||||
@ -228,44 +231,55 @@ export default {
|
||||
'OPTIONS': 'Opciones',
|
||||
'FIELD_DESCRIPTION': 'Descripción del campo (opcional)',
|
||||
'DESCRIPTION_ADD_CUSTOM_TAG': 'Aquí puede agregar una nueva etiqueta personalizada',
|
||||
'DESCRIPTION_EDIT_CUSTOM_TAG': 'aquí se puede editar una etiqueta personalizada',
|
||||
'PERMISSIONS': 'Permisos',
|
||||
'TICKET_CREATION_PERMISSION': 'Permitir la creación de tickets',
|
||||
'TICKET_CHECK_PERMISSION': 'Permitir la revision del ticket',
|
||||
'TICKET_NUMBER_RETURN_PERMISSION': 'Permitir la devolución del número de ticket',
|
||||
'USER_CREATION_PERMISSION': 'Permitir la creación del usuario',
|
||||
'DESCRIPTION_EDIT_CUSTOM_TAG': 'Aquí se puede editar una etiqueta personalizada',
|
||||
'CUSTOM_FIELDS': 'Campos Personalizados',
|
||||
|
||||
'CHART_CREATE_TICKET': 'Tickets creados',
|
||||
'CHART_CLOSE': 'Tickets cerrados',
|
||||
'RESEND_EMAIL_VERIFICATION': 'Reenviar la verificación de correo electrónico',
|
||||
'RESEND_EMAIL_VERIFICATION_SUCCESS': 'El mail fue enviado correctamente',
|
||||
'RESEND_EMAIL_VERIFICATION_FAIL': 'Se ha producido un error',
|
||||
'USER_UNLOGGED_IN': 'Este usuario nunca ha iniciado sesión antes',
|
||||
'RESEND_STAFF_INVITATION_SUCCESS': 'La invitación fue enviada exitosamente.',
|
||||
'RESEND_STAFF_INVITATION_FAIL': 'La invitación no pudo ser enviada.',
|
||||
'CHART_SIGNUP': 'Registros',
|
||||
'CHART_COMMENT': 'Respuestas',
|
||||
'CHART_ASSIGN': 'Asignaciones',
|
||||
|
||||
//ACTIVITIES
|
||||
'ACTIVITY_COMMENT': 'comentó el ticket',
|
||||
'ACTIVITY_ASSIGN': 'se asignó el ticket',
|
||||
'ACTIVITY_UN_ASSIGN': 'se desasignó el ticket',
|
||||
'ACTIVITY_CLOSE': 'cerró el ticket',
|
||||
'ACTIVITY_CREATE_TICKET': 'creó el ticket',
|
||||
'ACTIVITY_RE_OPEN': 'reabrió el ticket',
|
||||
'ACTIVITY_DEPARTMENT_CHANGED': 'cambió el departamento del ticket',
|
||||
'ACTIVITY_EDIT_COMMENT': 'editó un comentario del ticket',
|
||||
'ACTIVITY_COMMENT': 'Comentó el ticket',
|
||||
'ACTIVITY_ASSIGN': 'Se asignó el ticket',
|
||||
'ACTIVITY_UN_ASSIGN': 'Se desasignó el ticket',
|
||||
'ACTIVITY_CLOSE': 'Cerró el ticket',
|
||||
'ACTIVITY_CREATE_TICKET': 'Creó el ticket',
|
||||
'ACTIVITY_RE_OPEN': 'Reabrió el ticket',
|
||||
'ACTIVITY_DEPARTMENT_CHANGED': 'Cambió el departamento del ticket',
|
||||
'ACTIVITY_EDIT_COMMENT': 'Editó un comentario del ticket',
|
||||
|
||||
'ACTIVITY_EDIT_SETTINGS': 'editó las preferencias',
|
||||
'ACTIVITY_SIGNUP': 'se registró',
|
||||
'ACTIVITY_EDIT_TITLE': 'editó el titulo del ticket',
|
||||
'ACTIVITY_ADD_TOPIC': 'añadió el tema',
|
||||
'ACTIVITY_ADD_ARTICLE': 'añadió el articulo',
|
||||
'ACTIVITY_DELETE_TOPIC': 'eliminó el tema',
|
||||
'ACTIVITY_DELETE_ARTICLE': 'eliminó el artículo',
|
||||
'ACTIVITY_EDIT_ARTICLE': 'editó el artículo',
|
||||
'ACTIVITY_INVITE': 'usuario invitado',
|
||||
'ACTIVITY_ADD_STAFF': 'añadió el staff',
|
||||
'ACTIVITY_ADD_DEPARTMENT': 'añadió el departamento',
|
||||
'ACTIVITY_DELETE_DEPARTMENT': 'borró el departamento',
|
||||
'ACTIVITY_EDIT_DEPARTMENT': 'editó el departamento',
|
||||
'ACTIVITY_ADD_CUSTOM_RESPONSE': 'añadió una respuesta personalizada',
|
||||
'ACTIVITY_DELETE_CUSTOM_RESPONSE': 'borró una respuesta personalizada',
|
||||
'ACTIVITY_EDIT_CUSTOM_RESPONSE': 'editó una respuesta personalizada',
|
||||
'ACTIVITY_BAN_USER': 'bloqueó el usuario',
|
||||
'ACTIVITY_DELETE_USER': 'borró el usuario',
|
||||
'ACTIVITY_UN_BAN_USER': 'desbloqueó el usuario',
|
||||
'ACTIVITY_EDIT_SETTINGS': 'Editó las preferencias',
|
||||
'ACTIVITY_SIGNUP': 'Se registró',
|
||||
'ACTIVITY_EDIT_TITLE': 'Editó el titulo del ticket',
|
||||
'ACTIVITY_ADD_TOPIC': 'Añadió el tema',
|
||||
'ACTIVITY_ADD_ARTICLE': 'Añadió el articulo',
|
||||
'ACTIVITY_DELETE_TOPIC': 'Eliminó el tema',
|
||||
'ACTIVITY_DELETE_ARTICLE': 'Eliminó el artículo',
|
||||
'ACTIVITY_EDIT_ARTICLE': 'Editó el artículo',
|
||||
'ACTIVITY_INVITE': 'Usuario invitado',
|
||||
'ACTIVITY_ADD_STAFF': 'Añadió el staff',
|
||||
'ACTIVITY_ADD_DEPARTMENT': 'Añadió el departamento',
|
||||
'ACTIVITY_DELETE_DEPARTMENT': 'Borró el departamento',
|
||||
'ACTIVITY_EDIT_DEPARTMENT': 'Editó el departamento',
|
||||
'ACTIVITY_ADD_CUSTOM_RESPONSE': 'Añadió una respuesta personalizada',
|
||||
'ACTIVITY_DELETE_CUSTOM_RESPONSE': 'Borró una respuesta personalizada',
|
||||
'ACTIVITY_EDIT_CUSTOM_RESPONSE': 'Editó una respuesta personalizada',
|
||||
'ACTIVITY_BAN_USER': 'Bloqueó el usuario',
|
||||
'ACTIVITY_DELETE_USER': 'Borró el usuario',
|
||||
'ACTIVITY_UN_BAN_USER': 'Desbloqueó el usuario',
|
||||
|
||||
'SERVER_REQUIREMENTS': 'Requisitos del servidor',
|
||||
'DATABASE_CONFIGURATION': 'Configuracion de la base de datos',
|
||||
@ -284,6 +298,7 @@ export default {
|
||||
'DATABASE_NAME': 'Nombre de la base de datos MySQL',
|
||||
'DATABASE_USER': 'Usuario MySQL',
|
||||
'DATABASE_PASSWORD': 'Contraseña MySQL',
|
||||
'INSTALLATION_COMPLETED_TITLE': 'instalación completa',
|
||||
'ADMIN_NAME': 'Nombre de la cuenta admin',
|
||||
'ADMIN_EMAIL': 'Email de la cuenta admin',
|
||||
'ADMIN_PASSWORD': 'Contraseña de la cuenta admin',
|
||||
@ -334,8 +349,8 @@ export default {
|
||||
'SYSTEM_PREFERENCES_DESCRIPTION': 'Aquí puede editar las preferencias del sistema.',
|
||||
'VERIFY_SUCCESS_DESCRIPTION': 'Su usuario ha sido verificado correctamente. Puede iniciar sesión ahora.',
|
||||
'VERIFY_FAILED_DESCRIPTION': 'No se pudo hacer la verificación.',
|
||||
'MANDATORY_LOGIN_DISABLED': 'entrada obligatoria ha sido desactivado',
|
||||
'STATISTICS_DESCRIPTION': 'Aquí puede ver estadisticas relacionadas con tickets y registros.',
|
||||
'MANDATORY_LOGIN_DISABLED': 'El logueo obligatorio ha sido desactivado',
|
||||
'STATISTICS_DESCRIPTION': 'Aquí puede ver estadisticas relacionadas con tickets y logueos.',
|
||||
'ADVANCED_SETTINGS_DESCRIPTION': 'Aquí puede cambiar la configuración avanzada de su sistema. Tenga cuidado, los cambios que realice no se pueden revertir.',
|
||||
'USER_SYSTEM_DISABLED': 'Se ha inhabilitado el sistema de usuarios',
|
||||
'USER_SYSTEM_ENABLED': 'Se ha habilitado el sistema de usuarios',
|
||||
@ -347,14 +362,14 @@ export default {
|
||||
'EDIT_PROFILE_VIEW_DESCRIPTION': 'Aquí puede editar su usuario cambiando el correo electrónico o la contraseña.',
|
||||
'ENABLE_USER_SYSTEM_DESCRIPTION': 'Habilitar/Deshabilitar el uso de un sistema de usuario. Si lo deshabilita, todos los usuarios serán eliminados pero los tickets serán guardados. Si lo habilita, se crearán los usuarios de los tickets existentes.',
|
||||
'CSV_DESCRIPTION': 'El archivo CSV debe tener 3 columnas: correo electrónico, contraseña, nombre. No hay límite en el recuento de filas. Se creará un usuario por fila en el archivo.',
|
||||
'SMTP_SERVER_DESCRIPTION': 'La configuracion de SMTP permite que la applicacion mande emails. Si no es configurado, ningún mail sera enviado OpenSupports.',
|
||||
'SMTP_SERVER_DESCRIPTION': 'La configuración de SMTP permite que la aplicación envíe emails. Si no es configurado, ningún mail será enviado por OpenSupports.',
|
||||
'IMAP_SERVER_DESCRIPTION': 'La configuración del servidor IMAP permite que la aplicación cree tickets de los correos electrónicos enviados a un buzón.',
|
||||
'ENABLE_USER_DESCRIPTION': 'Esta acción permite al usuario iniciar sesión y crear tickets.',
|
||||
'LOCK_DEPARTMENT_DESCRIPTION': 'Permiten a los usuarios crear entradas sólo en el departamento por defecto',
|
||||
'LOCK_DEPARTMENT_DESCRIPTION': 'Permitir a los usuarios crear tickets solo en el departamento predeterminado.',
|
||||
'DISABLE_USER_DESCRIPTION': 'El usuario estará deshabilitado y no podrá iniciar sesión y crear tickets.',
|
||||
'PRIVATE_RESPONSE_DESCRIPTION': 'Esta respuesta solo será vista por los miembros del personal.',
|
||||
'PRIVATE_RESPONSE_DESCRIPTION': 'Esta respuesta solo será vista por los miembros del staff.',
|
||||
'PRIVATE_TOPIC_DESCRIPTION': 'Este tema solo será visto por los miembros del personal.',
|
||||
'PRIVATE_DEPARTMENT_DESCRIPTION': 'Este departamento solo será visto por miembros del personal.',
|
||||
'PRIVATE_DEPARTMENT_DESCRIPTION': 'Este departamento solo será visto por miembros del staff.',
|
||||
'EMAIL_SETTINGS_DESCRIPTION': 'Aquí puede editar la configuración para recibir y enviar correos electrónicos a sus clientes.',
|
||||
'IMAP_POLLING_DESCRIPTION': 'La verificación de la bandeja de entrada no se hará automáticamente por OpenSupports. Debe realizar solicitudes POST periódicamente a esta URL para procesar los correos electrónicos: {url}',
|
||||
'NEW_CUSTOM_FIELD_DESCRIPTION': 'Aquí puede crear un campo personalizado para un usuario, puede ser un cuadro de texto en blanco o un conjunto fijo de opciones.',
|
||||
@ -367,7 +382,7 @@ export default {
|
||||
'ERROR_PASSWORD': 'Contraseña Incorrecta',
|
||||
'INVITE_USER_VIEW_DESCRIPTION': 'Aquí se puede invitar a un usuario a nuestro sistema de soporte, que sólo tendrá que proporcionar su contraseña para crear una cuenta nueva.',
|
||||
'ERROR_NAME': 'Nombre Incorrecto',
|
||||
'INVITE_STAFF_DESCRIPTION': 'Aquí se puede invitar a los miembros del personal a sus equipos.',
|
||||
'INVITE_STAFF_DESCRIPTION': 'Aquí puedes invitar a alguien para que sea parte del staff.',
|
||||
'ERROR_TITLE': 'Título incorrecto',
|
||||
'ERROR_EMAIL': 'Email inválido',
|
||||
'ERROR_CONTENT_SHORT': 'Contenido demasiado corto',
|
||||
@ -382,38 +397,40 @@ export default {
|
||||
'ERROR_RETRIEVING_BAN_LIST': 'Se ha producido un error al intentar recuperar la lista de correos electrónicos bloqueados.',
|
||||
'ERROR_BANNING_EMAIL': 'Se ha producido un error al intentar bloquear el email.',
|
||||
'INVALID_NAME': 'nombre inválido',
|
||||
'ERROR_RETRIEVING_ARTICLES': 'Se ha producido un error al intentar recuperar los artículos.',
|
||||
'ERROR_RETRIEVING_ARTICLES': 'Se ha producido un error al intentar mostrar los artículos.',
|
||||
'ERROR_LIST': 'Seleccione al menos uno',
|
||||
'INVALID_TITLE': 'título inválido',
|
||||
'INVALID_TITLE': 'Título inválido',
|
||||
'ERROR_URL': 'URL incorrecta',
|
||||
'UNVERIFIED_EMAIL': 'El email no esta verificado aún',
|
||||
'ERROR_UPDATING_SETTINGS': 'Un error ocurrió mientras se intentaban actualizar las preferencias',
|
||||
'UNVERIFIED_EMAIL': 'El email aún no ha sido verificado',
|
||||
'ERROR_UPDATING_SETTINGS': 'Un error ocurrió mientras se intentaban actualizar las configuraciones.',
|
||||
'INVALID_EMAIL_OR_TICKET_NUMBER': 'Email o numero de ticket inválido',
|
||||
'INVALID_FILE': 'Archivo inválido',
|
||||
'ERRORS_FOUND': 'Se encontraron errores',
|
||||
'ERROR_IMAGE_SIZE': 'Ninguna imagen puede tener un tamaño superior a {size} MB',
|
||||
'USER_DISABLED': 'Esta cuenta está deshabilitada.',
|
||||
'INVALID_SYNTAX': 'Sintaxis inválida.',
|
||||
'DEPARTMENT_PRIVATE_TICKETS': 'Este departamento tiene entradas creadas por personal no administrativo y no puede ser privado.',
|
||||
'CURRENTLY_UNAVAILABLE': 'actualmente no disponible',
|
||||
'CAN_NOT_DELETE_DEFAULT_DEPARTMENT': 'departamento por defecto no se puede eliminar',
|
||||
'NAME_ALREADY_USED': 'Nombre en uso',
|
||||
'DEPARTMENT_PRIVATE_TICKETS': 'Este departamento tiene tickets creados por usuarios, no puede ser privado.',
|
||||
'CURRENTLY_UNAVAILABLE': 'Actualmente no disponible',
|
||||
'CAN_NOT_DELETE_DEFAULT_DEPARTMENT': 'No se puede eliminar el departamento predeterminado.',
|
||||
'DEFAULT_DEPARTMENT_CAN_NOT_BE_PRIVATE': 'El departamento predeterminado no puede ser privado.',
|
||||
|
||||
'DEFAULT_DEPARTMENT_CAN_NOT_BE_PRIVATE': 'departamento por defecto no puede ser privada',
|
||||
//MESSAGES
|
||||
'SIGNUP_SUCCESS': 'Se ha registrado con éxito en nuestro sistema de soporte.',
|
||||
'INVALID_SUPERVISED_USERS': 'usuarios supervisados no válidos',
|
||||
'INVALID_DEFAULT_DEPARTMENT': 'departamento elegido por defecto no es válido',
|
||||
'INVALID_SUPERVISED_USERS': 'Usuarios supervisados inválidos.',
|
||||
'INVALID_DEFAULT_DEPARTMENT': 'El departamento predeterminado elegido no es válido.',
|
||||
'TICKET_SENT': 'El ticket se ha creado correctamente.',
|
||||
'SUPERVISOR_CAN_NOT_SUPERVISE_HIMSELF': 'Supervisor no puede supervisar el propio',
|
||||
'VALID_RECOVER': 'La contraseña se recuperó correctamente',
|
||||
'EMAIL_EXISTS': 'El email ya existe',
|
||||
'INVITE_USER_SUCCESS': 'Has invitado a un nuevo usuario con éxito en nuestro sistema de soporte',
|
||||
'SUPERVISOR_CAN_NOT_SUPERVISE_HIMSELF': 'El supervisor no puede supervisarse a sí mismo.',
|
||||
'VALID_RECOVER': 'La contraseña se recuperó correctamente.',
|
||||
'EMAIL_EXISTS': 'El email ya existe.',
|
||||
'INVITE_USER_SUCCESS': 'Has invitado a un nuevo usuario con éxito en nuestro sistema de soporte.',
|
||||
'ARE_YOU_SURE': '¿Estás seguro?',
|
||||
'EMAIL_WILL_CHANGE': 'El correo electrónico actual se cambiará',
|
||||
'PASSWORD_WILL_CHANGE': 'Se cambiará la contraseña actual',
|
||||
'EMAIL_CHANGED': 'Se ha cambiado correctamente el correo electrónico',
|
||||
'PASSWORD_CHANGED': 'La contraseña ha sido cambiada correctamente',
|
||||
'OLD_PASSWORD_INCORRECT': 'La antigua contraseña es incorrecta',
|
||||
'EMAIL_WILL_CHANGE': 'El correo electrónico actual se cambiará.',
|
||||
'WILL_DELETE_CUSTOM_TAG': 'La etiqueta personalizada será eliminada.',
|
||||
'PASSWORD_WILL_CHANGE': 'Se cambiará la contraseña actual.',
|
||||
'EMAIL_CHANGED': 'Se ha cambiado correctamente el correo electrónico.',
|
||||
'PASSWORD_CHANGED': 'La contraseña ha sido cambiada correctamente.',
|
||||
'OLD_PASSWORD_INCORRECT': 'La antigua contraseña es incorrecta.',
|
||||
'WILL_LOSE_CHANGES': 'No has guardado. Los cambios se perderán.',
|
||||
'WILL_DELETE_CUSTOM_RESPONSE': 'La respuesta personalizada se eliminará.',
|
||||
'WILL_DELETE_DEPARTMENT': 'Se eliminará el departamento. Todos los tickets serán transferidos al departamento seleccionado.',
|
||||
@ -428,6 +445,8 @@ export default {
|
||||
'SUCCESS_IMPORTING_CSV_DESCRIPTION': 'El archivo CSV se ha importado correctamente',
|
||||
'SUCCESS_DELETING_ALL_USERS': 'Los usuarios se han eliminado correctamente',
|
||||
'SUCCESSFUL_CONNECTION': 'Conexión exitosa',
|
||||
'TODAY_AT': 'Hoy a las',
|
||||
'YESTERDAY_AT': 'Ayer a las',
|
||||
'UNSUCCESSFUL_CONNECTION': 'Conexión fallida',
|
||||
'SERVER_CREDENTIALS_WORKING': 'Las credenciales del servidor están funcionando correctamente',
|
||||
'DELETE_CUSTOM_FIELD_SURE': 'Algunos usuarios pueden estar usando este campo. ¿Seguro que quiere borrarlo?',
|
||||
@ -440,21 +459,42 @@ export default {
|
||||
'LAST_365_DAYS': 'Últimos 365 dias',
|
||||
|
||||
'TEST': 'Prueba',
|
||||
'ACTIVITY_COMMENT_THIS': 'comentó este ticket',
|
||||
'ACTIVITY_ASSIGN_THIS': 'se asignó este ticket para',
|
||||
'ACTIVITY_UN_ASSIGN_THIS': 'se desasignó este ticket para',
|
||||
'ACTIVITY_CLOSE_THIS': 'cerró este ticket',
|
||||
'ACTIVITY_CREATE_TICKET_THIS': 'creó este ticket',
|
||||
'ACTIVITY_RE_OPEN_THIS': 'reabrió este ticket',
|
||||
'ACTIVITY_DEPARTMENT_CHANGED_THIS': 'cambió el departamento de este ticket a ',
|
||||
'DATE_PREFIX': 'el día',
|
||||
'ACTIVITY_COMMENT_THIS': 'Comentó este ticket',
|
||||
'ACTIVITY_ASSIGN_THIS': 'Se asignó este ticket a.',
|
||||
'ACTIVITY_UN_ASSIGN_THIS': 'Se desasignó este ticket a.',
|
||||
'ACTIVITY_CLOSE_THIS': 'Cerró este ticket',
|
||||
'ACTIVITY_CREATE_TICKET_THIS': 'Creó este ticket',
|
||||
'ACTIVITY_RE_OPEN_THIS': 'Reabrió este ticket',
|
||||
'ACTIVITY_DEPARTMENT_CHANGED_THIS': 'Cambió el departamento de este ticket a ',
|
||||
'DATE_PREFIX': 'El día',
|
||||
'LEFT_EMPTY_DATABASE': 'Dejar vacío para la creación automática de bases de datos',
|
||||
'REMEMBER_ME': 'Recordarme',
|
||||
'EMAIL_LOWERCASE': 'email',
|
||||
'EMAIL_LOWERCASE': 'Email',
|
||||
'DEFAULT_PORT': 'Deje en blanco para 3306 por defecto',
|
||||
'PASSWORD_LOWERCASE': 'contraseña',
|
||||
'PASSWORD_LOWERCASE': 'Contraseña',
|
||||
'TEST_SMTP_CONNECTION': 'Probar conexion de SMTP',
|
||||
'SERVER_ERROR': 'No es posible conectar con el servidor.',
|
||||
'EMAIL_SERVER_ADDRESS': 'Dirección del servidor de correo electrónico',
|
||||
'EMAIL_SERVER_ADDRESS_DESCRIPTION': 'Dirección donde se recibirán y enviarán los correos.'
|
||||
'EMAIL_SERVER_ADDRESS_DESCRIPTION': 'Dirección donde se recibirán y enviarán los correos.',
|
||||
'CREATED': 'Creado',
|
||||
'REOPENED': 'Reabierto',
|
||||
'CREATED_DESCRIPTION': 'Tickets creados durante el intervalo de tiempo seleccionado',
|
||||
'OPEN': 'Abierto',
|
||||
'OPEN_DESCRIPTION': 'Tickets abiertos creados durante el intervalo de tiempo seleccionado',
|
||||
'CLOSED_DESCRIPTION': 'Tickets cerrados creados durante el intervalo de tiempo seleccionado',
|
||||
'INSTANT': 'Instante',
|
||||
'INSTANT_DESCRIPTION': 'Porcentaje de tickets cerrados despues de que un solo staff responda sobre el total de tickets cerrados',
|
||||
'REOPENED': 'Reabierto',
|
||||
'SUNDAY': 'domingo',
|
||||
'REOPENED_DESCRIPTION': 'Porcentaje de tickers que fueron reabiertos sobre el total de tickets creados',
|
||||
'MONDAY': 'Lunes',
|
||||
'TUESDAY': 'Martes',
|
||||
'WEDNESDAY': 'Miercoles',
|
||||
'THURSDAY': 'Jueves',
|
||||
'FRIDAY': 'Viernes',
|
||||
'SATURDAY': 'Sabado',
|
||||
'SUNDAY': 'Domingo',
|
||||
'TICKET_NUMBER_SENT': 'El ticket han sido creado satisfactoriamente y un email con el numero de ticket ha sido enviado.',
|
||||
'TICKETS_INFORMATION' : 'Los tickets de departamentos que no tengas asignados no serán visible.',
|
||||
'API_KEYS': 'API keys'
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user