mirror of
https://github.com/opensupports/opensupports.git
synced 2025-07-31 01:35:15 +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
|
aws-cli: circleci/aws-cli@1.0.0
|
||||||
jobs:
|
jobs:
|
||||||
install_composer_packages:
|
install_composer_packages:
|
||||||
executor: php/default
|
docker:
|
||||||
|
- image: 'cimg/base:edge'
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
|
- php/install-php:
|
||||||
|
version: '7.0'
|
||||||
- php/install-composer
|
- php/install-composer
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Install php extensions
|
name: Install php extensions
|
||||||
command: |
|
command: |
|
||||||
sudo add-apt-repository ppa:ondrej/php
|
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:
|
- php/install-packages:
|
||||||
app-dir: server/
|
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 'Circle-Token: ${CIRCLE_API_USER_TOKEN}' \
|
||||||
--header 'content-type: application/json' \
|
--header 'content-type: application/json' \
|
||||||
--data '{"branch":"master","parameters":{"server_to_deploy": "dev3"}}'
|
--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:
|
build-release-bundles:
|
||||||
$(eval UPGRADE_ZIP="opensupports_v$(VERSION)_update.zip")
|
$(eval UPGRADE_ZIP="opensupports_v$(VERSION)_update.zip")
|
||||||
./build.sh
|
./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.
|
OpenSupports is a simple and beautiful open source ticket system. <br />
|
||||||
Please, visit our website for more information: [http://www.opensupports.com/](http://www.opensupports.com/)
|
<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
|
</div>
|
||||||
* PHP 5.6+
|
|
||||||
* MySQL 4.1+
|
|
||||||
|
|
||||||
## Development
|
## 🌱 About the Project
|
||||||
Here is a guide of how to set up the development environment in OpenSupports.
|
|
||||||
|
|
||||||
### Getting up and running FRONT-END (client folder)
|
### What Customers See
|
||||||
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.
|
|
||||||
|
|
||||||
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
|
OpenSupports is a simple and beautiful open source ticket system.
|
||||||
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)
|
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.
|
||||||
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
|
Self-hosted, or [hosted by us](https://www.opensupports.com/pricing/), API-driven, and ready to be deployed on your own domain.
|
||||||
- `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/`
|
## 🧐 Stay Up-to-Date
|
||||||
Also, there's a *phpmyadmin* instance running on `http://localhost:6060/`,
|
|
||||||
you can access with the username `root` and empty password
|
|
||||||
|
|
||||||
##### Building
|
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.
|
||||||
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
|
## 💪🏼 Features
|
||||||
|
|
||||||
1. Go to tests folder: `cd opensupports/tests`
|
Check out our [most important features](https://opensupports.com/features) at our website.
|
||||||
2. Run `make build` to install ruby container and its required dependencies
|
|
||||||
|
|
||||||
- `make run` for running tests (database will be cleared)
|
Are we missing something? [Suggest an improvement](https://github.com/opensupports/opensupports/issues/new)!
|
||||||
- `make clear` for clearing database
|
|
||||||
|
|
||||||
##### BACKEND FAKE SMTP SERVER
|
## 🛠 Install
|
||||||
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:
|
OpenSupports can be hosted on your own servers, or [hosted by us](https://www.opensupports.com/pricing/).
|
||||||
|
|
||||||
`sudo apt-get install default-jre`
|
There are multiple benefits to having the system hosted by its creators, including official support into any problem you might encounter.
|
||||||
`sudo apt-get install default-jdk`
|
|
||||||
|
|
||||||
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",
|
"name": "OpenSupports",
|
||||||
"version": "4.9.0",
|
"version": "4.11.0",
|
||||||
"author": "Ivan Diaz <contact@opensupports.com>",
|
"author": "Ivan Diaz <contact@opensupports.com>",
|
||||||
"description": "Open source ticket system made with PHP and ReactJS",
|
"description": "Open source ticket system made with PHP and ReactJS",
|
||||||
"repository": {
|
"repository": {
|
||||||
@ -9,11 +9,11 @@
|
|||||||
},
|
},
|
||||||
"private": false,
|
"private": false,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^0.12.x",
|
"node": "^11.15.x",
|
||||||
"npm": "^2.1.x"
|
"npm": "^6.7.x"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"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",
|
"start-fixtures": "webpack-dev-server --env.FIXTURES=1",
|
||||||
"build": "./node_modules/.bin/rimraf build && NODE_ENV=production ./node_modules/.bin/webpack -p --devtool none",
|
"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"
|
"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",
|
"axios-mock-adapter": "^1.15.0",
|
||||||
"babel-loader": "^8.0.6",
|
"babel-loader": "^8.0.6",
|
||||||
"babel-plugin-add-module-exports": "^1.0.2",
|
"babel-plugin-add-module-exports": "^1.0.2",
|
||||||
"browser-sync": "^2.7.13",
|
"browser-sync": "^2.27.5",
|
||||||
"chai": "^3.5.0",
|
"chai": "^3.5.0",
|
||||||
"copy-webpack-plugin": "^5.0.3",
|
"copy-webpack-plugin": "^5.0.3",
|
||||||
"css-loader": "^3.0.0",
|
"css-loader": "^3.0.0",
|
||||||
@ -74,12 +74,13 @@
|
|||||||
"html-to-text": "^4.0.0",
|
"html-to-text": "^4.0.0",
|
||||||
"keycode": "^2.1.4",
|
"keycode": "^2.1.4",
|
||||||
"localStorage": "^1.0.3",
|
"localStorage": "^1.0.3",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.21",
|
||||||
"messageformat": "^0.2.2",
|
"messageformat": "^0.2.2",
|
||||||
"moment": "^2.27.0",
|
"moment": "^2.27.0",
|
||||||
"qs": "^6.5.2",
|
"qs": "^6.5.2",
|
||||||
"query-string": "^6.12.1",
|
"query-string": "^6.12.1",
|
||||||
"quill-image-resize-module-react": "^3.0.0",
|
"quill-image-resize-module-react": "^3.0.0",
|
||||||
|
"quill-magic-url": "^4.1.3",
|
||||||
"random-string": "^0.2.0",
|
"random-string": "^0.2.0",
|
||||||
"react": "^15.4.2",
|
"react": "^15.4.2",
|
||||||
"react-chartjs-2": "^2.10.0",
|
"react-chartjs-2": "^2.10.0",
|
||||||
@ -94,4 +95,4 @@
|
|||||||
"redux": "^3.5.2",
|
"redux": "^3.5.2",
|
||||||
"redux-promise-middleware": "^3.3.2"
|
"redux-promise-middleware": "^3.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export default {
|
export default {
|
||||||
login: stub(),
|
login: stub(),
|
||||||
logout: stub(),
|
logout: stub(),
|
||||||
initSession: stub()
|
checkSession: stub()
|
||||||
};
|
};
|
@ -88,7 +88,7 @@
|
|||||||
// });
|
// });
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// describe('initSession action', function () {
|
// describe('checkSession action', function () {
|
||||||
// beforeEach(function () {
|
// beforeEach(function () {
|
||||||
// APICallMock.call.returns({
|
// APICallMock.call.returns({
|
||||||
// then: function (resolve) {
|
// 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(storeMock.dispatch).to.have.been.calledWith({type: 'SESSION_CHECKED'});
|
||||||
// expect(APICallMock.call).to.have.been.calledWith({
|
// expect(APICallMock.call).to.have.been.calledWith({
|
||||||
// path: '/user/check-session',
|
// 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 () {
|
// it('should return CHECK_SESSION and dispatch LOGOUT_FULFILLED if session is not active and no remember data', function () {
|
||||||
// sessionStoreMock.isRememberDataExpired.returns(true);
|
// 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(storeMock.dispatch).to.have.been.calledWith({type: 'LOGOUT_FULFILLED'});
|
||||||
// expect(APICallMock.call).to.have.been.calledWith({
|
// expect(APICallMock.call).to.have.been.calledWith({
|
||||||
// path: '/user/check-session',
|
// 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 () {
|
// it('should return CHECK_SESSION and dispatch LOGIN_AUTO if session is not active but remember data exists', function () {
|
||||||
// sessionStoreMock.isRememberDataExpired.returns(false);
|
// 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(storeMock.dispatch).to.not.have.been.calledWith({type: 'LOGOUT_FULFILLED'});
|
||||||
// expect(APICallMock.call).to.have.been.calledWith({
|
// expect(APICallMock.call).to.have.been.calledWith({
|
||||||
// path: '/user/check-session',
|
// 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 {
|
return {
|
||||||
type: 'MY_TICKETS',
|
type: 'MY_TICKETS',
|
||||||
payload: API.call({
|
payload: API.call({
|
||||||
path: '/staff/get-tickets',
|
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 {
|
return {
|
||||||
type: 'NEW_TICKETS',
|
type: 'NEW_TICKETS',
|
||||||
payload: API.call({
|
payload: API.call({
|
||||||
path: '/staff/get-new-tickets',
|
path: '/staff/get-new-tickets',
|
||||||
data: {page, departmentId}
|
data: {page, departmentId, pageSize}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -9,14 +9,15 @@ export default {
|
|||||||
payload: {}
|
payload: {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
retrieveSearchTickets(ticketQueryListState, filters = {}) {
|
retrieveSearchTickets(ticketQueryListState, filters = {}, pageSize = 10) {
|
||||||
return {
|
return {
|
||||||
type: 'SEARCH_TICKETS',
|
type: 'SEARCH_TICKETS',
|
||||||
payload: API.call({
|
payload: API.call({
|
||||||
path: '/ticket/search',
|
path: '/ticket/search',
|
||||||
data: {
|
data: {
|
||||||
...filters,
|
...filters,
|
||||||
page: ticketQueryListState.page
|
page: ticketQueryListState.page,
|
||||||
|
pageSize
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -28,7 +29,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
changeFilters(listConfig) {
|
changeFilters(listConfig) {
|
||||||
const filtersForAPI = searchTicketsUtils.prepareFiltersForAPI(listConfig.filters);
|
const filtersForAPI = searchTicketsUtils.getFiltersForAPI(listConfig.filters);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'SEARCH_FILTERS_CHANGE_FILTERS',
|
type: 'SEARCH_FILTERS_CHANGE_FILTERS',
|
||||||
@ -48,7 +49,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
changePage(filtersWithPage) {
|
changePage(filtersWithPage) {
|
||||||
const filtersForAPI = searchTicketsUtils.prepareFiltersForAPI(filtersWithPage);
|
const filtersForAPI = searchTicketsUtils.getFiltersForAPI(filtersWithPage);
|
||||||
const currentPath = window.location.pathname;
|
const currentPath = window.location.pathname;
|
||||||
const urlQuery = searchTicketsUtils.getFiltersForURL({
|
const urlQuery = searchTicketsUtils.getFiltersForURL({
|
||||||
filters: filtersForAPI,
|
filters: filtersForAPI,
|
||||||
@ -63,7 +64,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
changeOrderBy(filtersWithOrderBy) {
|
changeOrderBy(filtersWithOrderBy) {
|
||||||
const filtersForAPI = searchTicketsUtils.prepareFiltersForAPI(filtersWithOrderBy);
|
const filtersForAPI = searchTicketsUtils.getFiltersForAPI(filtersWithOrderBy);
|
||||||
const currentPath = window.location.pathname;
|
const currentPath = window.location.pathname;
|
||||||
const urlQuery = searchTicketsUtils.getFiltersForURL({
|
const urlQuery = searchTicketsUtils.getFiltersForURL({
|
||||||
filters: filtersForAPI,
|
filters: filtersForAPI,
|
||||||
|
@ -101,7 +101,7 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
initSession() {
|
checkSession() {
|
||||||
return {
|
return {
|
||||||
type: 'CHECK_SESSION',
|
type: 'CHECK_SESSION',
|
||||||
payload: new Promise((resolve, reject) => {
|
payload: new Promise((resolve, reject) => {
|
||||||
|
@ -39,10 +39,10 @@ class ArticlesList extends React.Component {
|
|||||||
const { errored, loading } = this.props;
|
const { errored, loading } = this.props;
|
||||||
|
|
||||||
if(errored) {
|
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() {
|
renderContent() {
|
||||||
|
@ -5,6 +5,11 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__loading {
|
||||||
|
min-width: 300px;
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
&__add-topic-button {
|
&__add-topic-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {connect} from 'react-redux';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
import DropDown from 'core-components/drop-down';
|
import DropDown from 'core-components/drop-down';
|
||||||
import Icon from 'core-components/icon';
|
import Icon from 'core-components/icon';
|
||||||
|
@ -3,7 +3,6 @@ import {connect} from 'react-redux';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import languageList from 'data/language-list';
|
import languageList from 'data/language-list';
|
||||||
import i18n from 'lib-app/i18n';
|
|
||||||
import DropDown from 'core-components/drop-down';
|
import DropDown from 'core-components/drop-down';
|
||||||
|
|
||||||
const languageCodes = Object.keys(languageList);
|
const languageCodes = Object.keys(languageList);
|
||||||
|
@ -11,7 +11,7 @@ class ModalContainer extends React.Component {
|
|||||||
static openModal(
|
static openModal(
|
||||||
content,
|
content,
|
||||||
options={noPadding: false, outsideClick: false, closeButton: {showCloseButton: false, whiteColor: false}}
|
options={noPadding: false, outsideClick: false, closeButton: {showCloseButton: false, whiteColor: false}}
|
||||||
) {
|
) {
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
ModalActions.openModal({
|
ModalActions.openModal({
|
||||||
content,
|
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
|
renderLogo: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
showRecoverSentMessage: true
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (!prevProps.recoverSent && this.props.recoverSent) {
|
||||||
|
this.setState({showRecoverSentMessage : true});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { renderLogo, formProps, onBackToLoginClick, style } = this.props;
|
||||||
renderLogo,
|
|
||||||
formProps,
|
|
||||||
onBackToLoginClick,
|
|
||||||
style
|
|
||||||
} = this.props;
|
|
||||||
return (
|
return (
|
||||||
<Widget style={style} className={this.getClass()} title={!renderLogo ? i18n('RECOVER_PASSWORD') : ''}>
|
<Widget style={style} className={this.getClass()} title={!renderLogo ? i18n('RECOVER_PASSWORD') : ''}>
|
||||||
{this.renderLogo()}
|
{this.renderLogo()}
|
||||||
@ -68,22 +74,29 @@ class PasswordRecovery extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderRecoverStatus() {
|
renderRecoverStatus() {
|
||||||
let status = null;
|
return (
|
||||||
|
this.props.recoverSent ?
|
||||||
if (this.props.recoverSent) {
|
<Message
|
||||||
status = (
|
showMessage={this.state.showRecoverSentMessage}
|
||||||
<Message className="password-recovery__message" type="info" leftAligned>
|
onCloseMessage={this.onCloseMessage.bind(this, "showRecoverSentMessage")}
|
||||||
{i18n('RECOVER_SENT')}
|
className="password-recovery__message"
|
||||||
</Message>
|
type="info"
|
||||||
);
|
leftAligned>
|
||||||
}
|
{i18n('RECOVER_SENT')}
|
||||||
|
</Message> :
|
||||||
return status;
|
null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
focusEmail() {
|
focusEmail() {
|
||||||
this.refs.email.focus();
|
this.refs.email.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCloseMessage(showMessage) {
|
||||||
|
this.setState({
|
||||||
|
[showMessage]: false
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PasswordRecovery;
|
export default PasswordRecovery;
|
||||||
|
@ -16,7 +16,7 @@ class PopupMessage extends React.Component {
|
|||||||
static open(props) {
|
static open(props) {
|
||||||
ModalContainer.openModal(
|
ModalContainer.openModal(
|
||||||
<PopupMessage {...props} />,
|
<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() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="popup-message">
|
<div className="popup-message">
|
||||||
<Message {...this.props} className="popup-message__message" />
|
<Message {...this.props} showCloseButton={false} className="popup-message__message" />
|
||||||
<Button
|
<Button
|
||||||
className="popup-message__close-button"
|
className="popup-message__close-button"
|
||||||
iconName="times"
|
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,
|
private: React.PropTypes.string,
|
||||||
edited: React.PropTypes.bool,
|
edited: React.PropTypes.bool,
|
||||||
edit: React.PropTypes.bool,
|
edit: React.PropTypes.bool,
|
||||||
onToggleEdit: React.PropTypes.func
|
onToggleEdit: React.PropTypes.func,
|
||||||
|
isLastComment: React.PropTypes.bool,
|
||||||
|
isTicketClosed: React.PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -92,12 +94,7 @@ class TicketEvent extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderComment() {
|
renderComment() {
|
||||||
const {
|
const { author, date, edit, file } = this.props;
|
||||||
author,
|
|
||||||
date,
|
|
||||||
edit,
|
|
||||||
file
|
|
||||||
} = this.props;
|
|
||||||
const customFields = (author && author.customfields) || [];
|
const customFields = (author && author.customfields) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -142,10 +139,13 @@ class TicketEvent extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderContent() {
|
renderContent() {
|
||||||
|
const { content, author, userId, userStaff, isLastComment, isTicketClosed } = this.props;
|
||||||
|
const { id, staff } = author;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ticket-event__comment-content ql-editor">
|
<div className="ticket-event__comment-content ql-editor">
|
||||||
<div dangerouslySetInnerHTML={{__html: this.props.content}}></div>
|
<div dangerouslySetInnerHTML={{__html: content}}></div>
|
||||||
{((this.props.author.id == this.props.userId && this.props.author.staff == this.props.userStaff) || this.props.userStaff) ? this.renderEditIcon() : null}
|
{(id == userId && staff == userStaff && isLastComment && !isTicketClosed) ? this.renderEditIcon() : null }
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@ import Checkbox from 'core-components/checkbox';
|
|||||||
import Tag from 'core-components/tag';
|
import Tag from 'core-components/tag';
|
||||||
import Icon from 'core-components/icon';
|
import Icon from 'core-components/icon';
|
||||||
import Message from 'core-components/message';
|
import Message from 'core-components/message';
|
||||||
|
import history from 'lib-app/history';
|
||||||
|
import PageSizeDropdown from './page-size-dropdown';
|
||||||
|
|
||||||
class TicketList extends React.Component {
|
class TicketList extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -30,7 +32,8 @@ class TicketList extends React.Component {
|
|||||||
]),
|
]),
|
||||||
closedTicketsShown: React.PropTypes.bool,
|
closedTicketsShown: React.PropTypes.bool,
|
||||||
onClosedTicketsShownChange: React.PropTypes.func,
|
onClosedTicketsShownChange: React.PropTypes.func,
|
||||||
onDepartmentChange: React.PropTypes.func
|
onDepartmentChange: React.PropTypes.func,
|
||||||
|
showPageSizeDropdown: React.PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@ -40,7 +43,8 @@ class TicketList extends React.Component {
|
|||||||
departments: [],
|
departments: [],
|
||||||
ticketPath: '/dashboard/ticket/',
|
ticketPath: '/dashboard/ticket/',
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
closedTicketsShown: false
|
closedTicketsShown: false,
|
||||||
|
showPageSizeDropdown: true
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -48,25 +52,32 @@ class TicketList extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { type, showDepartmentDropdown, onClosedTicketsShownChange } = this.props;
|
const { type, showDepartmentDropdown, onClosedTicketsShownChange, showPageSizeDropdown } = this.props;
|
||||||
|
const pages = [5, 10, 20, 50];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ticket-list">
|
<div className="ticket-list">
|
||||||
<div className="ticket-list__filters">
|
<div className="ticket-list__filters">
|
||||||
{(type === 'primary') ? this.renderMessage() : null}
|
<div className="ticket-list__main-filters">
|
||||||
{
|
{(type === 'primary') ? this.renderMessage() : null}
|
||||||
((type === 'secondary') && showDepartmentDropdown) ?
|
{
|
||||||
this.renderDepartmentsDropDown() :
|
((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
|
null
|
||||||
}
|
}
|
||||||
{onClosedTicketsShownChange ? this.renderFilterCheckbox() : null}
|
|
||||||
</div>
|
</div>
|
||||||
<Table {...this.getTableProps()} />
|
<Table {...this.getTableProps()} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
renderFilterCheckbox() {
|
renderFilterCheckbox() {
|
||||||
return (
|
return (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@ -90,14 +101,34 @@ class TicketList extends React.Component {
|
|||||||
renderMessage() {
|
renderMessage() {
|
||||||
switch (queryString.parse(window.location.search)["message"]) {
|
switch (queryString.parse(window.location.search)["message"]) {
|
||||||
case 'success':
|
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':
|
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:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pageSizeChange(event) {
|
||||||
|
const { onPageSizeChange } = this.props;
|
||||||
|
|
||||||
|
onPageSizeChange && onPageSizeChange(event.pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
getDepartmentDropdownProps() {
|
getDepartmentDropdownProps() {
|
||||||
const { departments, onDepartmentChange } = this.props;
|
const { departments, onDepartmentChange } = this.props;
|
||||||
|
|
||||||
@ -123,7 +154,7 @@ class TicketList extends React.Component {
|
|||||||
loading,
|
loading,
|
||||||
headers: this.getTableHeaders(),
|
headers: this.getTableHeaders(),
|
||||||
rows: this.getTableRows(),
|
rows: this.getTableRows(),
|
||||||
pageSize: 10,
|
pageSize: this.state.tickets,
|
||||||
page,
|
page,
|
||||||
pages,
|
pages,
|
||||||
onPageChange
|
onPageChange
|
||||||
@ -226,7 +257,7 @@ class TicketList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTableRows() {
|
getTableRows() {
|
||||||
return this.getTickets().map(this.gerTicketTableObject.bind(this));
|
return this.getTickets().map(this.getTicketTableObject.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
getTickets() {
|
getTickets() {
|
||||||
@ -240,7 +271,7 @@ class TicketList extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
gerTicketTableObject(ticket) {
|
getTicketTableObject(ticket) {
|
||||||
const { date, title, ticketNumber, closed, tags, department, author } = ticket;
|
const { date, title, ticketNumber, closed, tags, department, author } = ticket;
|
||||||
const dateTodayWithOutHoursAndMinutes = DateTransformer.getDateToday();
|
const dateTodayWithOutHoursAndMinutes = DateTransformer.getDateToday();
|
||||||
const ticketDateWithOutHoursAndMinutes = Math.floor(DateTransformer.UTCDateToLocalNumericDate(JSON.stringify(date*1)) / 10000);
|
const ticketDateWithOutHoursAndMinutes = Math.floor(DateTransformer.UTCDateToLocalNumericDate(JSON.stringify(date*1)) / 10000);
|
||||||
@ -248,7 +279,7 @@ class TicketList extends React.Component {
|
|||||||
const ticketDate = (
|
const ticketDate = (
|
||||||
((dateTodayWithOutHoursAndMinutes - ticketDateWithOutHoursAndMinutes) > 1) ?
|
((dateTodayWithOutHoursAndMinutes - ticketDateWithOutHoursAndMinutes) > 1) ?
|
||||||
stringTicketLocalDateFormat :
|
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;
|
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) => {
|
export default connect((store) => {
|
||||||
|
@ -7,16 +7,31 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__filters {
|
&__filters {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__main-filters {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
&__department-selector {
|
&__department-selector {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 25px;
|
margin-right: 25px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__page-dropdown {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 25px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
&__checkbox {
|
&__checkbox {
|
||||||
display: inline-block;
|
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 API from 'lib-app/api-call';
|
||||||
import history from 'lib-app/history';
|
import history from 'lib-app/history';
|
||||||
import searchTicketsUtils from 'lib-app/search-tickets-utils';
|
import searchTicketsUtils from 'lib-app/search-tickets-utils';
|
||||||
|
import ticketUtils from 'lib-app/ticket-utils';
|
||||||
|
|
||||||
import Form from 'core-components/form';
|
import Form from 'core-components/form';
|
||||||
import SubmitButton from 'core-components/submit-button';
|
import SubmitButton from 'core-components/submit-button';
|
||||||
@ -34,7 +35,8 @@ class TicketQueryFilters extends React.Component {
|
|||||||
formState,
|
formState,
|
||||||
filters,
|
filters,
|
||||||
showFilters,
|
showFilters,
|
||||||
ticketQueryListState
|
ticketQueryListState,
|
||||||
|
staffList
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -47,22 +49,15 @@ class TicketQueryFilters extends React.Component {
|
|||||||
<div className="ticket-query-filters__search-box">
|
<div className="ticket-query-filters__search-box">
|
||||||
<FormField name="query" field="search-box" fieldProps={{onSearch: this.onSubmitListConfig.bind(this)}} />
|
<FormField name="query" field="search-box" fieldProps={{onSearch: this.onSubmitListConfig.bind(this)}} />
|
||||||
</div>
|
</div>
|
||||||
<div className="ticket-query-filters__first-row">
|
<div className="ticket-query-filters__second-row">
|
||||||
<FormField
|
<FormField
|
||||||
label={i18n('DATE')}
|
label={i18n('PERIOD')}
|
||||||
name="dateRange"
|
name="period"
|
||||||
field="date-range"
|
|
||||||
fieldProps={{defaultValue: formState.dateRange}} />
|
|
||||||
<FormField
|
|
||||||
label={i18n('STATUS')}
|
|
||||||
name="closed"
|
|
||||||
field="select"
|
field="select"
|
||||||
fieldProps={{
|
fieldProps={{
|
||||||
items: this.getStatusItems(),
|
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__status-drop-down'
|
className: 'ticket-query-filters__drop-down'
|
||||||
}} />
|
}} />
|
||||||
</div>
|
|
||||||
<div className="ticket-query-filters__second-row">
|
|
||||||
<FormField
|
<FormField
|
||||||
label={i18n('DEPARTMENTS')}
|
label={i18n('DEPARTMENTS')}
|
||||||
name="departments"
|
name="departments"
|
||||||
@ -72,9 +67,17 @@ class TicketQueryFilters extends React.Component {
|
|||||||
label={i18n('OWNER')}
|
label={i18n('OWNER')}
|
||||||
name="owners"
|
name="owners"
|
||||||
field="autocomplete"
|
field="autocomplete"
|
||||||
fieldProps={{items: this.getStaffList()}} />
|
fieldProps={{items: ticketUtils.getStaffList({staffList}, 'toAutocomplete')}} />
|
||||||
</div>
|
</div>
|
||||||
<div className="ticket-query-filters__third-row">
|
<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
|
<FormField
|
||||||
label={i18n('TAGS')}
|
label={i18n('TAGS')}
|
||||||
name="tags"
|
name="tags"
|
||||||
@ -131,8 +134,8 @@ class TicketQueryFilters extends React.Component {
|
|||||||
id: author.id*1,
|
id: author.id*1,
|
||||||
profilePic: author.profilePic,
|
profilePic: author.profilePic,
|
||||||
isStaff: author.isStaff * 1,
|
isStaff: author.isStaff * 1,
|
||||||
content: author.profilePic !== undefined ? this.renderStaffOption(author) : author.name,
|
content: author.profilePic !== undefined ? ticketUtils.renderStaffOption(author) : author.name,
|
||||||
contentOnSelected: author.profilePic !== undefined ? this.renderStaffSelected(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) {
|
addTag(tag) {
|
||||||
const { formState } = this.props;
|
const { formState } = this.props;
|
||||||
let selectedTagsId = formState.tags.concat(this.tagsNametoTagsId(this.getSelectedTagsName([tag])));
|
this.onChangeFormState({...formState, tags: [...formState.tags, tag]});
|
||||||
|
|
||||||
this.onChangeFormState({...formState, tags: selectedTagsId});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
autorsComparer(autorList, autorSelectedList) {
|
autorsComparer(autorList, autorSelectedList) {
|
||||||
@ -212,18 +195,23 @@ class TicketQueryFilters extends React.Component {
|
|||||||
let selectedDepartments = [];
|
let selectedDepartments = [];
|
||||||
|
|
||||||
if(selectedDepartmentsId !== undefined) {
|
if(selectedDepartmentsId !== undefined) {
|
||||||
let departments = this.getDepartmentsItems();
|
selectedDepartments = selectedDepartmentsId.map(
|
||||||
selectedDepartments = departments.filter(item => _.includes(selectedDepartmentsId, item.id));
|
(departmentId) => this.getDepartmentsItems().find(_department => (_department.id === departmentId))
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return selectedDepartments;
|
return selectedDepartments;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelectedStaffs(selectedStaffsId) {
|
getSelectedStaffs(selectedStaffsId) {
|
||||||
|
const { staffList } = this.props;
|
||||||
let selectedStaffs = [];
|
let selectedStaffs = [];
|
||||||
|
|
||||||
if(selectedStaffsId !== undefined) {
|
if(selectedStaffsId !== undefined) {
|
||||||
let staffs = this.getStaffList();
|
selectedStaffs = selectedStaffsId.map(
|
||||||
selectedStaffs = staffs.filter(staff => _.includes(selectedStaffsId, staff.id));
|
(staffId) => ticketUtils.getStaffList({staffList}, 'toAutocomplete').find(_staff => (_staff.id === staffId))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return selectedStaffs;
|
return selectedStaffs;
|
||||||
@ -233,33 +221,14 @@ class TicketQueryFilters extends React.Component {
|
|||||||
let selectedTagsName = [];
|
let selectedTagsName = [];
|
||||||
|
|
||||||
if(selectedTagsId !== undefined) {
|
if(selectedTagsId !== undefined) {
|
||||||
let tagList = this.getTags();
|
selectedTagsName = selectedTagsId.map(
|
||||||
let selectedTags = tagList.filter(item => _.includes(selectedTagsId, item.id));
|
(tagId) => (this.getTags().find(_tag => (_tag.id === tagId)) || {}).name
|
||||||
selectedTagsName = selectedTags.map(tag => tag.name);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return selectedTagsName;
|
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() {
|
getStatusItems() {
|
||||||
let items = [
|
let items = [
|
||||||
{id: 0, name: 'Any', content: i18n('ANY')},
|
{id: 0, name: 'Any', content: i18n('ANY')},
|
||||||
@ -298,8 +267,8 @@ class TicketQueryFilters extends React.Component {
|
|||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
if(formEdited && formState.dateRange.valid) {
|
if(formEdited) {
|
||||||
const filtersForAPI = searchTicketsUtils.prepareFiltersForAPI(listConfigWithCompleteAuthorsList.filters);
|
const filtersForAPI = searchTicketsUtils.getFiltersForAPI(listConfigWithCompleteAuthorsList.filters);
|
||||||
const currentPath = window.location.pathname;
|
const currentPath = window.location.pathname;
|
||||||
const urlQuery = searchTicketsUtils.getFiltersForURL({
|
const urlQuery = searchTicketsUtils.getFiltersForURL({
|
||||||
filters: filtersForAPI,
|
filters: filtersForAPI,
|
||||||
@ -312,24 +281,23 @@ class TicketQueryFilters extends React.Component {
|
|||||||
|
|
||||||
removeTag(tag) {
|
removeTag(tag) {
|
||||||
const { formState } = this.props;
|
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) {
|
tagsNametoTagsId(selectedTagsName) {
|
||||||
let tagList = this.getTags();
|
let selectedTagsId = [];
|
||||||
let selectedTags = tagList.filter(item => _.includes(selectedTagsName, item.name));
|
|
||||||
let selectedTagsId = selectedTags.map(tag => tag.id);
|
if (selectedTagsName != undefined) {
|
||||||
|
selectedTagsId = selectedTagsName.map(
|
||||||
|
(tagName) => (this.getTags().find(_tag => (_tag.name === tagName)) || {}).id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return selectedTagsId;
|
return selectedTagsId;
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeForm(data) {
|
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 departmentsId = data.departments.map(department => department.id);
|
||||||
const staffsId = data.owners.map(staff => staff.id);
|
const staffsId = data.owners.map(staff => staff.id);
|
||||||
const tagsName = this.tagsNametoTagsId(data.tags);
|
const tagsName = this.tagsNametoTagsId(data.tags);
|
||||||
@ -341,11 +309,6 @@ class TicketQueryFilters extends React.Component {
|
|||||||
owners: staffsId,
|
owners: staffsId,
|
||||||
departments: departmentsId,
|
departments: departmentsId,
|
||||||
authors: authors,
|
authors: authors,
|
||||||
dateRange: {
|
|
||||||
...data.dateRange,
|
|
||||||
startDate: newStartDate,
|
|
||||||
endDate: newEndDate
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,8 +329,8 @@ class TicketQueryFilters extends React.Component {
|
|||||||
id: author.id*1,
|
id: author.id*1,
|
||||||
isStaff: author.isStaff*1,
|
isStaff: author.isStaff*1,
|
||||||
profilePic: author.profilePic,
|
profilePic: author.profilePic,
|
||||||
content: author.profilePic !== undefined ? this.renderStaffOption(author) : author.name,
|
content: author.profilePic !== undefined ? ticketUtils.renderStaffOption(author) : author.name,
|
||||||
contentOnSelected: author.profilePic !== undefined ? this.renderStaffSelected(author) : author.name
|
contentOnSelected: author.profilePic !== undefined ? ticketUtils.renderStaffSelected(author) : author.name
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__status-drop-down > .drop-down__current-item {
|
&__drop-down > .drop-down__current-item {
|
||||||
background-color: $very-light-grey;
|
background-color: $very-light-grey;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
|
@ -8,6 +8,8 @@ import TicketList from 'app-components/ticket-list';
|
|||||||
import Message from 'core-components/message';
|
import Message from 'core-components/message';
|
||||||
import searchFiltersActions from '../actions/search-filters-actions';
|
import searchFiltersActions from '../actions/search-filters-actions';
|
||||||
import queryString from 'query-string';
|
import queryString from 'query-string';
|
||||||
|
import searchTicketsUtils from 'lib-app/search-tickets-utils';
|
||||||
|
import history from 'lib-app/history';
|
||||||
|
|
||||||
class TicketQueryList extends React.Component {
|
class TicketQueryList extends React.Component {
|
||||||
|
|
||||||
@ -21,13 +23,9 @@ class TicketQueryList extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
this.state.error ?
|
||||||
{
|
<Message showCloseButton={false} type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> :
|
||||||
(this.state.error) ?
|
<TicketList {...this.getTicketListProps()} />
|
||||||
<Message type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> :
|
|
||||||
<TicketList {...this.getTicketListProps()}/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,6 +67,7 @@ class TicketQueryList extends React.Component {
|
|||||||
orderBy: filters.orderBy ? JSON.parse(filters.orderBy) : filters.orderBy,
|
orderBy: filters.orderBy ? JSON.parse(filters.orderBy) : filters.orderBy,
|
||||||
showOrderArrows: true,
|
showOrderArrows: true,
|
||||||
onChangeOrderBy: onChangeOrderBy,
|
onChangeOrderBy: onChangeOrderBy,
|
||||||
|
showPageSizeDropdown: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import SessionStore from 'lib-app/session-store';
|
|||||||
import MentionsParser from 'lib-app/mentions-parser';
|
import MentionsParser from 'lib-app/mentions-parser';
|
||||||
import history from 'lib-app/history';
|
import history from 'lib-app/history';
|
||||||
import searchTicketsUtils from 'lib-app/search-tickets-utils';
|
import searchTicketsUtils from 'lib-app/search-tickets-utils';
|
||||||
|
import ticketUtils from 'lib-app/ticket-utils';
|
||||||
|
|
||||||
import TicketEvent from 'app-components/ticket-event';
|
import TicketEvent from 'app-components/ticket-event';
|
||||||
import AreYouSure from 'app-components/are-you-sure';
|
import AreYouSure from 'app-components/are-you-sure';
|
||||||
@ -71,6 +72,7 @@ class TicketViewer extends React.Component {
|
|||||||
editTags: false,
|
editTags: false,
|
||||||
editOwner: false,
|
editOwner: false,
|
||||||
editDepartment: false,
|
editDepartment: false,
|
||||||
|
showTicketCommentErrorMessage: true
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -83,35 +85,61 @@ class TicketViewer extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { ticket, userStaff, userId, editable, allowAttachments, assignmentAllowed } = this.props;
|
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 (
|
return (
|
||||||
<div className="ticket-viewer">
|
<div className="ticket-viewer">
|
||||||
{this.state.editTitle ? this.renderEditableTitle() : this.renderTitleHeader()}
|
{editTitle ? this.renderEditableTitle() : this.renderTitleHeader()}
|
||||||
{editable ? this.renderEditableHeaders() : this.renderHeaders()}
|
{editable ? this.renderEditableHeaders() : this.renderHeaders()}
|
||||||
<div className="ticket-viewer__content">
|
<div className="ticket-viewer__content">
|
||||||
<TicketEvent
|
<TicketEvent
|
||||||
loading={this.state.loading}
|
loading={loading}
|
||||||
type="COMMENT"
|
type="COMMENT"
|
||||||
author={ticket.author}
|
isLastComment={!events.filter(event => event.type === "COMMENT").length}
|
||||||
content={userStaff ? MentionsParser.parse(ticket.content) : ticket.content}
|
author={author}
|
||||||
|
isTicketClosed={closed}
|
||||||
|
content={userStaff ? MentionsParser.parse(content) : content}
|
||||||
userStaff={userStaff}
|
userStaff={userStaff}
|
||||||
userId={userId}
|
userId={userId}
|
||||||
date={ticket.date}
|
date={date}
|
||||||
onEdit={this.onEdit.bind(this,0)}
|
onEdit={this.onEdit.bind(this,0)}
|
||||||
edited={ticket.edited}
|
edited={edited}
|
||||||
file={ticket.file}
|
file={file}
|
||||||
edit={this.state.edit && this.state.editId == 0}
|
edit={edit && editId == 0}
|
||||||
onToggleEdit={this.onToggleEdit.bind(this, 0)}
|
onToggleEdit={this.onToggleEdit.bind(this, 0)}
|
||||||
allowAttachments={allowAttachments} />
|
allowAttachments={allowAttachments} />
|
||||||
</div>
|
</div>
|
||||||
<div className="ticket-viewer__comments">
|
<div className="ticket-viewer__comments">
|
||||||
{ticket.events && ticket.events.map(this.renderTicketEvent.bind(this))}
|
{eventsWithModifiedComments && eventsWithModifiedComments.map(this.renderTicketEvent.bind(this, closed))}
|
||||||
</div>
|
</div>
|
||||||
{(!ticket.closed && (editable || !assignmentAllowed)) ? this.renderResponseField() : (this.showDeleteButton()) ? this.renderDeleteTicketButton() : null}
|
{showResponseField ? this.renderResponseField() : this.renderReopenCloseButtons()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderReopenCloseButtons() {
|
||||||
|
return(
|
||||||
|
<div className="ticket-viewer__reopen-close-buttons">
|
||||||
|
{this.renderReopenTicketButton()}
|
||||||
|
{this.showDeleteButton() ? this.renderDeleteTicketButton() : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
renderTitleHeader() {
|
renderTitleHeader() {
|
||||||
const {ticket, userStaff, userId} = this.props;
|
const {ticket, userStaff, userId} = this.props;
|
||||||
const {ticketNumber, title, author, editedTitle, language} = ticket;
|
const {ticketNumber, title, author, editedTitle, language} = ticket;
|
||||||
@ -141,11 +169,11 @@ class TicketViewer extends React.Component {
|
|||||||
onChange={(e) => this.setState({newTitle: e.target.value})} />
|
onChange={(e) => this.setState({newTitle: e.target.value})} />
|
||||||
</div>
|
</div>
|
||||||
<div className="ticket-viewer__edit-title__buttons">
|
<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})}>
|
<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" />}
|
{this.state.editTitleLoading ? <Loading /> : <Icon name="times" size="large" />}
|
||||||
</Button>
|
</Button>
|
||||||
<Button disabled={this.state.editTitleLoading} type='secondary' size="medium" onClick={this.changeTitle.bind(this)}>
|
<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" />}
|
{this.state.editTitleLoading ? <Loading /> : <Icon name="check" size="large" />}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -238,7 +266,7 @@ class TicketViewer extends React.Component {
|
|||||||
onRemoveClick={this.removeTag.bind(this)}
|
onRemoveClick={this.removeTag.bind(this)}
|
||||||
onTagSelected={this.addTag.bind(this)}
|
onTagSelected={this.addTag.bind(this)}
|
||||||
loading={this.state.tagSelectorLoading} />
|
loading={this.state.tagSelectorLoading} />
|
||||||
{this.renderCancelButton("Tags")}
|
{this.renderCancelButton("Tags", "CLOSE")}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -246,16 +274,20 @@ class TicketViewer extends React.Component {
|
|||||||
renderEditStatus() {
|
renderEditStatus() {
|
||||||
return (
|
return (
|
||||||
<div className="ticket-viewer__edit-status__buttons">
|
<div className="ticket-viewer__edit-status__buttons">
|
||||||
{this.renderCancelButton("Status")}
|
{this.renderCancelButton("Status", "CANCEL")}
|
||||||
{this.props.ticket.closed ?
|
{this.props.ticket.closed ? this.renderReopenTicketButton() : this.renderCloseTicketButton()}
|
||||||
<Button type='secondary' size="medium" onClick={this.onReopenClick.bind(this)}>
|
|
||||||
{i18n('RE_OPEN')}
|
|
||||||
</Button> :
|
|
||||||
this.renderCloseTicketButton()}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderReopenTicketButton() {
|
||||||
|
return (
|
||||||
|
<Button type='secondary' size="medium" onClick={this.onReopenClick.bind(this)}>
|
||||||
|
{i18n('RE_OPEN')}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderHeaders() {
|
renderHeaders() {
|
||||||
const ticket = this.props.ticket;
|
const ticket = this.props.ticket;
|
||||||
|
|
||||||
@ -317,7 +349,7 @@ class TicketViewer extends React.Component {
|
|||||||
if(assignmentAllowed && ticket.owner) {
|
if(assignmentAllowed && ticket.owner) {
|
||||||
ownerNode = (
|
ownerNode = (
|
||||||
<a className="ticket-viewer__info-owner-name" href={this.searchTickets(filtersOnlyWithOwner)}>
|
<a className="ticket-viewer__info-owner-name" href={this.searchTickets(filtersOnlyWithOwner)}>
|
||||||
{ticket.owner.name}
|
{ticketUtils.renderStaffSelected(ticket.owner)}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -344,7 +376,7 @@ class TicketViewer extends React.Component {
|
|||||||
className="ticket-viewer__editable-dropdown" items={items}
|
className="ticket-viewer__editable-dropdown" items={items}
|
||||||
selectedIndex={selectedIndex}
|
selectedIndex={selectedIndex}
|
||||||
onChange={this.onAssignmentChange.bind(this)} />
|
onChange={this.onAssignmentChange.bind(this)} />
|
||||||
{this.renderCancelButton("Owner")}
|
{this.renderCancelButton("Owner", "CANCEL")}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -360,7 +392,7 @@ class TicketViewer extends React.Component {
|
|||||||
departments={departments}
|
departments={departments}
|
||||||
selectedIndex={_.findIndex(departments, {id: ticket.department.id})}
|
selectedIndex={_.findIndex(departments, {id: ticket.department.id})}
|
||||||
onChange={this.onDepartmentDropdownChanged.bind(this)} />
|
onChange={this.onDepartmentDropdownChanged.bind(this)} />
|
||||||
{this.renderCancelButton("Department")}
|
{this.renderCancelButton("Department", "CANCEL")}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -387,26 +419,30 @@ class TicketViewer extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCancelButton(option) {
|
renderCancelButton(option, type) {
|
||||||
return <Button type='link' size="medium" onClick={() => this.setState({["edit"+option]: false})}>{i18n('CLOSE')}</Button>
|
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 { userStaff, ticket, userId, allowAttachments } = this.props;
|
||||||
|
const { edit, editId } = this.state;
|
||||||
|
const { content, author, id} = ticketEventObject;
|
||||||
|
|
||||||
if(userStaff && typeof options.content === 'string') {
|
if(userStaff && typeof content === 'string') {
|
||||||
options.content = MentionsParser.parse(options.content);
|
ticketEventObject.content = MentionsParser.parse(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TicketEvent
|
<TicketEvent
|
||||||
{...options}
|
{...ticketEventObject}
|
||||||
author={(!_.isEmpty(options.author)) ? options.author : ticket.author}
|
isLastComment={ticketEventObject.isLastComment}
|
||||||
|
author={(!_.isEmpty(author)) ? author : ticket.author}
|
||||||
userStaff={userStaff}
|
userStaff={userStaff}
|
||||||
|
isTicketClosed={isTicketClosed}
|
||||||
userId={userId}
|
userId={userId}
|
||||||
onEdit={this.onEdit.bind(this, options.id)}
|
onEdit={this.onEdit.bind(this, id)}
|
||||||
edit={this.state.edit && this.state.editId == options.id}
|
edit={edit && editId == id}
|
||||||
onToggleEdit={this.onToggleEdit.bind(this, options.id)}
|
onToggleEdit={this.onToggleEdit.bind(this, id)}
|
||||||
key={index}
|
key={index}
|
||||||
allowAttachments={allowAttachments} />
|
allowAttachments={allowAttachments} />
|
||||||
);
|
);
|
||||||
@ -427,14 +463,15 @@ class TicketViewer extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
<div className="ticket-viewer__response-field row">
|
<div className="ticket-viewer__response-field row">
|
||||||
<FormField name="content" validation="TEXT_AREA" required field="textarea" fieldProps={{allowImages: allowAttachments}} />
|
<FormField name="content" validation="TEXT_AREA" required field="textarea" fieldProps={{allowImages: allowAttachments}} />
|
||||||
<div className="ticket-viewer__response-buttons">
|
<div className="ticket-viewer__response-container">
|
||||||
{allowAttachments ? <FormField name="file" field="file" /> : null}
|
<div className="ticket-viewer__response-buttons">
|
||||||
<SubmitButton type="secondary">{i18n('RESPOND_TICKET')}</SubmitButton>
|
{allowAttachments ? <FormField name="file" field="file" /> : null}
|
||||||
</div>
|
<SubmitButton type="secondary">{i18n('RESPOND_TICKET')}</SubmitButton>
|
||||||
<div className="ticket-viewer__buttons-column">
|
</div>
|
||||||
<div className="ticket-viewer__buttons-row">
|
<div className="ticket-viewer__buttons-column">
|
||||||
{(this.showDeleteButton()) ? this.renderDeleteTicketButton() : null}
|
<div className="ticket-viewer__buttons-row">
|
||||||
{this.renderCloseTicketButton()}
|
{this.renderCloseTicketButton()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -494,8 +531,16 @@ class TicketViewer extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderCommentError() {
|
renderCommentError() {
|
||||||
|
const { showTicketCommentErrorMessage } = this.state;
|
||||||
|
|
||||||
return (
|
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) => {
|
}).then((result) => {
|
||||||
this.onTicketModification(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({
|
this.setState({
|
||||||
loading: true
|
loading: true
|
||||||
});
|
});
|
||||||
const data = {};
|
const data = {};
|
||||||
|
|
||||||
if(ticketeventid){
|
if(ticketeventid) {
|
||||||
data.ticketEventId = ticketeventid
|
data.ticketEventId = ticketeventid;
|
||||||
}else{
|
} else {
|
||||||
data.ticketNumber = this.props.ticket.ticketNumber
|
data.ticketNumber = this.props.ticket.ticketNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
API.call({
|
API.call({
|
||||||
@ -740,7 +785,7 @@ class TicketViewer extends React.Component {
|
|||||||
data,
|
data,
|
||||||
TextEditor.getContentFormData(content)
|
TextEditor.getContentFormData(content)
|
||||||
)
|
)
|
||||||
}).then(this.onEditCommentSuccess.bind(this), this.onFailCommentFail.bind(this));
|
}).then(this.onEditCommentSuccess.bind(this), this.onEditCommentFail.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
onEditCommentSuccess() {
|
onEditCommentSuccess() {
|
||||||
@ -754,10 +799,11 @@ class TicketViewer extends React.Component {
|
|||||||
this.onTicketModification();
|
this.onTicketModification();
|
||||||
}
|
}
|
||||||
|
|
||||||
onFailCommentFail() {
|
onEditCommentFail() {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
commentError: true
|
commentError: true,
|
||||||
|
showTicketCommentErrorMessage: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -780,6 +826,7 @@ class TicketViewer extends React.Component {
|
|||||||
loading: false,
|
loading: false,
|
||||||
commentValue: TextEditor.createEmpty(),
|
commentValue: TextEditor.createEmpty(),
|
||||||
commentError: false,
|
commentError: false,
|
||||||
|
commentFile: null,
|
||||||
commentEdited: false
|
commentEdited: false
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -789,7 +836,8 @@ class TicketViewer extends React.Component {
|
|||||||
onCommentFail() {
|
onCommentFail() {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
commentError: true
|
commentError: true,
|
||||||
|
showTicketCommentErrorMessage: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -800,37 +848,19 @@ class TicketViewer extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getStaffAssignmentItems() {
|
getStaffAssignmentItems() {
|
||||||
const { userDepartments, userId, ticket } = this.props;
|
const { staffMembers, ticket } = this.props;
|
||||||
let staffAssignmentItems = [
|
let staffAssignmentItems = [
|
||||||
{content: i18n('NONE'), contentOnSelected: i18n('NONE'), id: 0}
|
{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(
|
staffAssignmentItems = staffAssignmentItems.concat(
|
||||||
_.map(
|
ticketUtils.getStaffList({staffList: staffMembers, ticket}, 'toDropDown').map(
|
||||||
this.getStaffList(),
|
({id, content}) => ({content, id: id*1})
|
||||||
({id, name}) => ({content: name, contentOnSelected: name, id: id*1})
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
return staffAssignmentItems;
|
return staffAssignmentItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
getStaffList() {
|
|
||||||
const { userId, staffMembers, ticket } = this.props;
|
|
||||||
|
|
||||||
return _.filter(staffMembers, ({id, departments}) => {
|
|
||||||
return (id != userId) && _.some(departments, {id: ticket.department.id});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
getCurrentStaff() {
|
getCurrentStaff() {
|
||||||
const { userId, staffMembers, ticket } = this.props;
|
const { userId, staffMembers, ticket } = this.props;
|
||||||
|
|
||||||
@ -843,18 +873,24 @@ class TicketViewer extends React.Component {
|
|||||||
|
|
||||||
showDeleteButton() {
|
showDeleteButton() {
|
||||||
const { ticket, userLevel, userId, userStaff } = this.props;
|
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(userLevel === 3) return true;
|
||||||
if(userId == ticket.author.id*1) {
|
if(userId == id*1) {
|
||||||
if((userStaff && ticket.author.staff) || (!userStaff && !ticket.author.staff)){
|
return (userStaff && staff && closed);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCloseMessage(showMessage) {
|
||||||
|
this.setState({
|
||||||
|
[showMessage]: false
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect((store) => {
|
export default connect((store) => {
|
||||||
|
@ -33,6 +33,9 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
width: 250px;
|
width: 250px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__edited-title-text {
|
&__edited-title-text {
|
||||||
@ -77,7 +80,10 @@
|
|||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
|
|
||||||
.input__text {
|
.input__text {
|
||||||
height: 25px;
|
height: 30px;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,6 +94,14 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
width: 160px;
|
width: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__edit-title__button {
|
||||||
|
width: 50px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
&__number {
|
&__number {
|
||||||
color: white;
|
color: white;
|
||||||
margin-right: 30px;
|
margin-right: 30px;
|
||||||
@ -108,7 +122,7 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
background-color: $light-grey;
|
background-color: $light-grey;
|
||||||
padding: 0 15px 30px 15px;
|
padding: 10px 15px 30px 15px;
|
||||||
|
|
||||||
&-container {
|
&-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -168,7 +182,15 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__reopen-close-buttons {
|
||||||
|
width: 230px;
|
||||||
|
display: flex;
|
||||||
|
align-content: left;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
&__response {
|
&__response {
|
||||||
|
width: 100%;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
@ -206,16 +228,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-buttons {
|
&-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
position: relative;
|
||||||
justify-content: space-between;
|
flex-direction: row-reverse;
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
&-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) {
|
@media screen and (max-width: 1151px) {
|
||||||
|
@ -124,13 +124,15 @@ class TopicViewer extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderEditModal() {
|
renderEditModal() {
|
||||||
let props = {
|
const {id, onChange, name, icon, iconColor} = this.props;
|
||||||
topicId: this.props.id,
|
|
||||||
onChange: this.props.onChange,
|
const props = {
|
||||||
|
topicId: id,
|
||||||
|
onChange,
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
title: this.props.name,
|
title: name,
|
||||||
icon: this.props.icon,
|
icon,
|
||||||
iconColor: this.props.iconColor,
|
color: iconColor,
|
||||||
private: this.props.private * 1
|
private: this.props.private * 1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -16,6 +16,9 @@ import Message from 'core-components/message';
|
|||||||
import Widget from 'core-components/widget';
|
import Widget from 'core-components/widget';
|
||||||
import WidgetTransition from 'core-components/widget-transition';
|
import WidgetTransition from 'core-components/widget-transition';
|
||||||
|
|
||||||
|
import Captcha from 'app/main/captcha';
|
||||||
|
|
||||||
|
const MAX_FREE_LOGIN_ATTEMPTS = 3;
|
||||||
class AdminLoginPage extends React.Component {
|
class AdminLoginPage extends React.Component {
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -24,11 +27,14 @@ class AdminLoginPage extends React.Component {
|
|||||||
recoverFormErrors: {},
|
recoverFormErrors: {},
|
||||||
recoverSent: false,
|
recoverSent: false,
|
||||||
loadingLogin: false,
|
loadingLogin: false,
|
||||||
loadingRecover: false
|
loadingRecover: false,
|
||||||
|
showRecoverSentMessage: true,
|
||||||
|
showEmailOrPassordErrorMessage: true
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (!prevProps.session.failed && this.props.session.failed) {
|
if (!prevProps.session.failed && this.props.session.failed) {
|
||||||
|
this.setState({showEmailOrPassordErrorMessage : true});
|
||||||
this.refs.loginForm.refs.password.focus();
|
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"
|
className="admin-login-page__login-form-container__login-form__fields__remember"
|
||||||
field="checkbox" />
|
field="checkbox" />
|
||||||
</div>
|
</div>
|
||||||
|
{this.props.session.loginAttempts > MAX_FREE_LOGIN_ATTEMPTS ? this.renderLoginCaptcha() : null}
|
||||||
<div className="admin-login-page__login-form-container__login-form__submit-button">
|
<div className="admin-login-page__login-form-container__login-form__submit-button">
|
||||||
<SubmitButton>{i18n('LOG_IN')}</SubmitButton>
|
<SubmitButton>{i18n('LOG_IN')}</SubmitButton>
|
||||||
</div>
|
</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() {
|
renderPasswordRecovery() {
|
||||||
return (
|
return (
|
||||||
<div className="admin-login-page__recovery-form-container">
|
<div className="admin-login-page__recovery-form-container">
|
||||||
@ -96,31 +111,34 @@ class AdminLoginPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderRecoverStatus() {
|
renderRecoverStatus() {
|
||||||
let status = null;
|
const { showRecoverSentMessage, recoverSent } = this.state;
|
||||||
|
|
||||||
if (this.state.recoverSent) {
|
return (
|
||||||
status = (
|
recoverSent ?
|
||||||
<Message className="admin-login-page__message" type="info" leftAligned>
|
<Message
|
||||||
{i18n('RECOVER_SENT')}
|
showMessage={showRecoverSentMessage}
|
||||||
</Message>
|
onCloseMessage={this.onCloseMessage.bind(this, "showRecoverSentMessage")}
|
||||||
);
|
className="admin-login-page__message"
|
||||||
}
|
type="info"
|
||||||
|
leftAligned>
|
||||||
return status;
|
{i18n('RECOVER_SENT')}
|
||||||
|
</Message> :
|
||||||
|
null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderErrorStatus() {
|
renderErrorStatus() {
|
||||||
let status = null;
|
return (
|
||||||
|
this.props.session.failed ?
|
||||||
if (this.props.session.failed) {
|
<Message
|
||||||
status = (
|
showMessage={this.state.showEmailOrPassordErrorMessage}
|
||||||
<Message className="admin-login-page__error" type="error">
|
onCloseMessage={this.onCloseMessage.bind(this, "showEmailOrPassordErrorMessage")}
|
||||||
{i18n('EMAIL_OR_PASSWORD')}
|
className="admin-login-page__error"
|
||||||
</Message>
|
type="error">
|
||||||
);
|
{i18n('EMAIL_OR_PASSWORD')}
|
||||||
}
|
</Message> :
|
||||||
|
null
|
||||||
return status;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getLoginFormProps() {
|
getLoginFormProps() {
|
||||||
@ -135,10 +153,7 @@ class AdminLoginPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getRecoverFormProps() {
|
getRecoverFormProps() {
|
||||||
const {
|
const { loadingRecover, recoverFormErrors } = this.state;
|
||||||
loadingRecover,
|
|
||||||
recoverFormErrors
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loading: loadingRecover,
|
loading: loadingRecover,
|
||||||
@ -210,7 +225,8 @@ class AdminLoginPage extends React.Component {
|
|||||||
onRecoverPasswordSent() {
|
onRecoverPasswordSent() {
|
||||||
this.setState({
|
this.setState({
|
||||||
loadingRecover: false,
|
loadingRecover: false,
|
||||||
recoverSent: true
|
recoverSent: true,
|
||||||
|
showRecoverSentMessage: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,10 +240,17 @@ class AdminLoginPage extends React.Component {
|
|||||||
this.refs.recoverForm.refs.email.focus();
|
this.refs.recoverForm.refs.email.focus();
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCloseMessage(showMessage) {
|
||||||
|
this.setState({
|
||||||
|
[showMessage]: false
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect((store) => {
|
export default connect((store) => {
|
||||||
return {
|
return {
|
||||||
session: store.session
|
session: store.session,
|
||||||
|
sitekey: store.config.reCaptchaKey
|
||||||
};
|
};
|
||||||
})(AdminLoginPage);
|
})(AdminLoginPage);
|
||||||
|
@ -31,4 +31,10 @@
|
|||||||
&__error {
|
&__error {
|
||||||
margin-top: 30px;
|
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 FormField from 'core-components/form-field';
|
||||||
import SubmitButton from 'core-components/submit-button';
|
import SubmitButton from 'core-components/submit-button';
|
||||||
import TextEditor from 'core-components/text-editor';
|
import TextEditor from 'core-components/text-editor';
|
||||||
|
import Icon from 'core-components/icon';
|
||||||
|
|
||||||
class AdminPanelViewArticle extends React.Component {
|
class AdminPanelViewArticle extends React.Component {
|
||||||
|
|
||||||
@ -65,23 +66,22 @@ class AdminPanelViewArticle extends React.Component {
|
|||||||
renderArticlePreview(article) {
|
renderArticlePreview(article) {
|
||||||
return (
|
return (
|
||||||
<div className="admin-panel-view-article__content">
|
<div className="admin-panel-view-article__content">
|
||||||
<div className="admin-panel-view-article__edit-buttons">
|
<div className="admin-panel-view-article__header-wrapper">
|
||||||
<Button size="medium" onClick={this.onDeleteClick.bind(this, article)}>
|
<Header title={article.title} />
|
||||||
{i18n('DELETE')}
|
<div className="admin-panel-view-article__header-buttons">
|
||||||
</Button>
|
<span onClick={this.onEditClick.bind(this, article)}>
|
||||||
<Button className="admin-panel-view-article__edit-button" size="medium" onClick={this.onEditClick.bind(this, article)} type="tertiary">
|
<Icon className="admin-panel-view-article__edit-icon" name="pencil" />
|
||||||
{i18n('EDIT')}
|
</span>
|
||||||
</Button>
|
<span onClick={this.onDeleteClick.bind(this, article)} >
|
||||||
|
<Icon className="admin-panel-view-article__edit-icon" name="trash" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="admin-panel-view-article__article">
|
<div className="admin-panel-view-article__article-content ql-editor">
|
||||||
<Header title={article.title}/>
|
<div dangerouslySetInnerHTML={{__html: MentionsParser.parse(article.content)}}/>
|
||||||
|
</div>
|
||||||
<div className="admin-panel-view-article__article-content ql-editor">
|
<div className="admin-panel-view-article__last-edited">
|
||||||
<div dangerouslySetInnerHTML={{__html: MentionsParser.parse(article.content)}}/>
|
{i18n('LAST_EDITED_IN', {date: DateTransformer.transformToString(article.lastEdited)})}
|
||||||
</div>
|
|
||||||
<div className="admin-panel-view-article__last-edited">
|
|
||||||
{i18n('LAST_EDITED_IN', {date: DateTransformer.transformToString(article.lastEdited)})}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -4,17 +4,25 @@
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__edit-buttons {
|
&__header-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 200px;
|
width: 50px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 5px;
|
||||||
|
margin-left: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__edit-button {
|
&__edit-icon {
|
||||||
margin-right: 20px;
|
color: $grey;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__last-edited {
|
&__last-edited {
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
&__menu {
|
&__menu {
|
||||||
margin: 0 auto 20px auto;
|
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 React from 'react';
|
||||||
import { connect } from 'react-redux';
|
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 API from 'lib-app/api-call';
|
||||||
import i18n from 'lib-app/i18n';
|
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 Header from 'core-components/header';
|
||||||
import Tooltip from 'core-components/tooltip';
|
|
||||||
import Form from 'core-components/form';
|
import Form from 'core-components/form';
|
||||||
import FormField from 'core-components/form-field';
|
import FormField from 'core-components/form-field';
|
||||||
import Icon from 'core-components/icon';
|
import Icon from 'core-components/icon';
|
||||||
@ -21,7 +19,7 @@ class AdminPanelStats extends React.Component {
|
|||||||
state = {
|
state = {
|
||||||
loading: true,
|
loading: true,
|
||||||
rawForm: {
|
rawForm: {
|
||||||
dateRange: this.getInitialDateRange(),
|
period: 0,
|
||||||
departments: [],
|
departments: [],
|
||||||
owners: [],
|
owners: [],
|
||||||
tags: []
|
tags: []
|
||||||
@ -29,29 +27,19 @@ class AdminPanelStats extends React.Component {
|
|||||||
ticketData: {}
|
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() {
|
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() {
|
render() {
|
||||||
const {
|
const { loading, rawForm, ticketData } = this.state;
|
||||||
loading,
|
|
||||||
rawForm
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="admin-panel-stats">
|
<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">
|
||||||
<div className="admin-panel-stats__form__container__row">
|
<div className="admin-panel-stats__form__container__row">
|
||||||
<div className="admin-panel-stats__form__container__col">
|
<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()}} />
|
<FormField name="tags" label={i18n('TAGS')} field="tag-selector" fieldProps={{items: this.getTagItems()}} />
|
||||||
</div>
|
</div>
|
||||||
<div className="admin-panel-stats__form__container__col">
|
<div className="admin-panel-stats__form__container__col">
|
||||||
@ -90,98 +78,20 @@ class AdminPanelStats extends React.Component {
|
|||||||
<span className="separator" />
|
<span className="separator" />
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</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) {
|
clearFormValues(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.setState({
|
this.setState({
|
||||||
rawForm: {
|
rawForm: {
|
||||||
dateRange: this.getInitialDateRange(),
|
period: 0,
|
||||||
departments: [],
|
departments: [],
|
||||||
owners: [],
|
owners: [],
|
||||||
tags: []
|
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() {
|
getStaffItems() {
|
||||||
const getStaffProfilePic = (staff) => {
|
const getStaffProfilePic = (staff) => {
|
||||||
return staff.profilePic ? API.getFileLink(staff.profilePic) : (API.getURL() + '/images/profile.png');
|
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) {
|
onFormChange(newFormState) {
|
||||||
this.setState({rawForm: newFormState});
|
this.setState({rawForm: newFormState});
|
||||||
}
|
}
|
||||||
|
|
||||||
onFormSubmit() {
|
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: '',
|
messageContent: '',
|
||||||
selectedAPIKey: -1,
|
selectedAPIKey: -1,
|
||||||
APIKeys: [],
|
APIKeys: [],
|
||||||
error: ''
|
error: '',
|
||||||
|
showMessage: true,
|
||||||
|
showAPIKeyMessage: true
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -36,7 +38,7 @@ class AdminPanelAdvancedSettings extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { config } = this.props;
|
const { config } = this.props;
|
||||||
const { messageType, error, selectedAPIKey } = this.state;
|
const { messageType, error, selectedAPIKey, showAPIKeyMessage } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="admin-panel-advanced-settings">
|
<div className="admin-panel-advanced-settings">
|
||||||
@ -88,12 +90,21 @@ class AdminPanelAdvancedSettings extends React.Component {
|
|||||||
<span className="separator" />
|
<span className="separator" />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-12 admin-panel-advanced-settings__api-keys">
|
<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">
|
<div className="col-md-4">
|
||||||
<Listing {...this.getListingProps()} />
|
<Listing {...this.getListingProps()} />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-8 admin-panel-advanced-settings__api-keys__container">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -102,11 +113,16 @@ class AdminPanelAdvancedSettings extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderMessage() {
|
renderMessage() {
|
||||||
const { messageType, messageTitle, messageContent } = this.state;
|
const { messageType, messageTitle, messageContent, showMessage } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Message className="admin-panel-advanced-settings__message" type={messageType} title={messageTitle}>
|
<Message
|
||||||
{messageContent}
|
showMessage={showMessage}
|
||||||
|
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
|
||||||
|
className="admin-panel-advanced-settings__message"
|
||||||
|
type={messageType}
|
||||||
|
title={messageTitle}>
|
||||||
|
{messageContent}
|
||||||
</Message>
|
</Message>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -154,7 +170,7 @@ class AdminPanelAdvancedSettings extends React.Component {
|
|||||||
|
|
||||||
getListingProps() {
|
getListingProps() {
|
||||||
return {
|
return {
|
||||||
title: i18n('REGISTRATION_API_KEYS'),
|
title: i18n('API_KEYS'),
|
||||||
enableAddNew: true,
|
enableAddNew: true,
|
||||||
items: this.state.APIKeys.map((item) => {
|
items: this.state.APIKeys.map((item) => {
|
||||||
return {
|
return {
|
||||||
@ -270,10 +286,11 @@ class AdminPanelAdvancedSettings extends React.Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
messageType: 'success',
|
messageType: 'success',
|
||||||
messageTitle: null,
|
messageTitle: null,
|
||||||
|
showMessage: true,
|
||||||
messageContent: config['mandatory-login'] ? i18n('MANDATORY_LOGIN_DISABLED') : i18n('MANDATORY_LOGIN_ENABLED')
|
messageContent: config['mandatory-login'] ? i18n('MANDATORY_LOGIN_DISABLED') : i18n('MANDATORY_LOGIN_ENABLED')
|
||||||
});
|
});
|
||||||
dispatch(ConfigActions.updateData());
|
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) {
|
onAreYouSureRegistrationOk(password) {
|
||||||
@ -287,11 +304,12 @@ class AdminPanelAdvancedSettings extends React.Component {
|
|||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
messageType: 'success',
|
messageType: 'success',
|
||||||
|
showMessage: true,
|
||||||
messageTitle: null,
|
messageTitle: null,
|
||||||
messageContent: config['registration'] ? i18n('REGISTRATION_DISABLED') : i18n('REGISTRATION_ENABLED')
|
messageContent: config['registration'] ? i18n('REGISTRATION_DISABLED') : i18n('REGISTRATION_ENABLED')
|
||||||
});
|
});
|
||||||
dispatch(ConfigActions.updateData());
|
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) {
|
onImportCSV(event) {
|
||||||
@ -303,23 +321,31 @@ class AdminPanelAdvancedSettings extends React.Component {
|
|||||||
path: '/system/csv-import',
|
path: '/system/csv-import',
|
||||||
dataAsForm: true,
|
dataAsForm: true,
|
||||||
data: {
|
data: {
|
||||||
file: file,
|
file,
|
||||||
password: password
|
password
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((result) => this.setState({
|
.then((result) => this.setState({
|
||||||
messageType: 'success',
|
messageType: 'success',
|
||||||
|
showMessage: true,
|
||||||
messageTitle: i18n('SUCCESS_IMPORTING_CSV_DESCRIPTION'),
|
messageTitle: i18n('SUCCESS_IMPORTING_CSV_DESCRIPTION'),
|
||||||
messageContent: (result.data.length) ? (
|
messageContent: (result.data.length) ? (
|
||||||
<div>
|
<div>
|
||||||
{i18n('ERRORS_FOUND')}
|
{i18n('ERRORS_FOUND')}
|
||||||
<ul>
|
<ul>
|
||||||
{result.data.map((error) => <li>{error}</li>)}
|
{result.data.map((error, index) => <li key={`csv-file__key-${index}`} >{error}</li>)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
) : null
|
) : 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() {
|
onBackupDatabase() {
|
||||||
@ -347,12 +373,17 @@ class AdminPanelAdvancedSettings extends React.Component {
|
|||||||
data: {
|
data: {
|
||||||
password: password
|
password: password
|
||||||
}
|
}
|
||||||
}).then(() => this.setState({messageType: 'success', messageTitle: null, messageContent: i18n('SUCCESS_DELETING_ALL_USERS')}
|
}).then(() => this.setState({messageType: 'success', showMessage: true, messageTitle: null, messageContent: i18n('SUCCESS_DELETING_ALL_USERS')}
|
||||||
)).catch(() => this.setState({messageType: 'error', messageTitle: null, messageContent: i18n('ERROR_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) => {
|
export default connect((store) => {
|
||||||
return {
|
return {
|
||||||
config: store.config
|
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="name" label={i18n('NAME')} fieldProps={{size: 'large'}} required={nameRequired} />
|
||||||
<FormField name="color" label={i18n('COLOR')} decorator={ColorSelector} />
|
<FormField name="color" label={i18n('COLOR')} decorator={ColorSelector} />
|
||||||
<div className='admin-panel-custom-tags-modal__actions'>
|
<div className='admin-panel-custom-tags-modal__actions'>
|
||||||
<Button onClick={this.onDiscardClick.bind(this)} size="small">
|
|
||||||
{i18n('CANCEL')}
|
|
||||||
</Button>
|
|
||||||
<SubmitButton type="secondary" size="small">
|
<SubmitButton type="secondary" size="small">
|
||||||
{i18n('SAVE')}
|
{i18n('SAVE')}
|
||||||
</SubmitButton>
|
</SubmitButton>
|
||||||
|
<Button onClick={this.onDiscardClick.bind(this)} size="small">
|
||||||
|
{i18n('CANCEL')}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
&__actions{
|
&__actions{
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ class AdminPanelEmailSettings extends React.Component {
|
|||||||
<div className="admin-panel-email-settings__image-container">
|
<div className="admin-panel-email-settings__image-container">
|
||||||
<FormField className="admin-panel-email-settings__image-header-url"
|
<FormField className="admin-panel-email-settings__image-header-url"
|
||||||
label={i18n('IMAGE_HEADER_URL')} name="headerImage" required
|
label={i18n('IMAGE_HEADER_URL')} name="headerImage" required
|
||||||
infoMessage={i18n('IMAGE_HEADER_URL_DESCRIPTION')}
|
infoMessage={i18n('IMAGE_HEADER_DESCRIPTION')}
|
||||||
fieldProps={{size: 'large'}} />
|
fieldProps={{size: 'large'}} />
|
||||||
<SubmitButton className="admin-panel-email-settings__image-header-submit" type="secondary"
|
<SubmitButton className="admin-panel-email-settings__image-header-submit" type="secondary"
|
||||||
size="small">{i18n('SAVE')}</SubmitButton>
|
size="small">{i18n('SAVE')}</SubmitButton>
|
||||||
@ -169,7 +169,7 @@ class AdminPanelEmailSettings extends React.Component {
|
|||||||
</SubmitButton>
|
</SubmitButton>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</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`})}
|
{i18n('IMAP_POLLING_DESCRIPTION', {url: `${apiRoot}/system/email-polling`})}
|
||||||
</Message>
|
</Message>
|
||||||
</div>
|
</div>
|
||||||
@ -179,39 +179,61 @@ class AdminPanelEmailSettings extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderForm() {
|
renderForm() {
|
||||||
const {
|
const { form, language, selectedIndex, edited } = this.state;
|
||||||
form,
|
const { template, text2, text3} = form;
|
||||||
language,
|
|
||||||
selectedIndex,
|
|
||||||
edited
|
|
||||||
} = this.state;
|
|
||||||
return (
|
return (
|
||||||
<div className="col-md-9">
|
<div className="col-md-9">
|
||||||
<FormField label={i18n('LANGUAGE')} decorator={LanguageSelector} value={language}
|
<FormField label={i18n('LANGUAGE')} decorator={LanguageSelector} value={language}
|
||||||
onChange={event => this.onItemChange(selectedIndex, event.target.value)}
|
onChange={event => this.onItemChange(selectedIndex, event.target.value)}
|
||||||
fieldProps={{
|
fieldProps={{
|
||||||
type: 'allowed',
|
type: 'supported',
|
||||||
size: 'medium'
|
size: 'medium'
|
||||||
}} />
|
}} />
|
||||||
<Form {...this.getFormProps()}>
|
<Form {...this.getFormProps()}>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-7">
|
<div className="col-md-7">
|
||||||
<FormField label={i18n('SUBJECT')} name="subject" validation="TITLE" required
|
<FormField
|
||||||
fieldProps={{size: 'large'}} />
|
fieldProps={{size: 'large'}}
|
||||||
|
label={i18n('SUBJECT')}
|
||||||
|
name="subject"
|
||||||
|
validation="TITLE"
|
||||||
|
required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FormField key="text1" label={i18n('TEXT') + '1'} name="text1" validation="TEXT_AREA" required
|
<FormField
|
||||||
decorator={'textarea'}
|
fieldProps={{className: 'admin-panel-email-settings__text-area'}}
|
||||||
fieldProps={{className: 'admin-panel-email-settings__text-area'}} />
|
label={i18n('TEXT') + '1'}
|
||||||
{(form.text2) ?
|
key="text1"
|
||||||
<FormField key="text2" label={i18n('TEXT') + '2'} name="text2" validation="TEXT_AREA" required
|
name="text1"
|
||||||
decorator={'textarea'}
|
validation="TEXT_AREA"
|
||||||
fieldProps={{className: 'admin-panel-email-settings__text-area'}} /> : null}
|
required
|
||||||
{(form.text3) ?
|
decorator={'textarea'} />
|
||||||
<FormField key="text3" label={i18n('TEXT') + '3'} name="text3" validation="TEXT_AREA" required
|
{
|
||||||
decorator={'textarea'}
|
(text2 || text2 === "") ?
|
||||||
fieldProps={{className: 'admin-panel-email-settings__text-area'}} /> : null}
|
<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__actions">
|
||||||
<div className="admin-panel-email-settings__optional-buttons">
|
<div className="admin-panel-email-settings__optional-buttons">
|
||||||
@ -223,11 +245,7 @@ class AdminPanelEmailSettings extends React.Component {
|
|||||||
{edited ? this.renderDiscardButton() : null}
|
{edited ? this.renderDiscardButton() : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="admin-panel-email-settings__save-button">
|
<div className="admin-panel-email-settings__save-button">
|
||||||
<SubmitButton
|
<SubmitButton key="submit-email-template" type="secondary" size="small">
|
||||||
key="submit-email-template"
|
|
||||||
type="secondary"
|
|
||||||
size="small"
|
|
||||||
onClick={(e) => {e.preventDefault(); this.onFormSubmit(form);}}>
|
|
||||||
{i18n('SAVE')}
|
{i18n('SAVE')}
|
||||||
</SubmitButton>
|
</SubmitButton>
|
||||||
</div>
|
</div>
|
||||||
@ -257,16 +275,19 @@ class AdminPanelEmailSettings extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getFormProps() {
|
getFormProps() {
|
||||||
|
const { form, errors, loadingForm } = this.state;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
values: this.state.form,
|
values: form,
|
||||||
errors: this.state.errors,
|
errors,
|
||||||
loading: this.state.loadingForm,
|
loading: loadingForm,
|
||||||
onChange: (form) => {
|
onChange: (form) => {
|
||||||
this.setState({form, edited: true})
|
this.setState({form, edited: true})
|
||||||
},
|
},
|
||||||
onValidateErrors: (errors) => {
|
onValidateErrors: (errors) => {
|
||||||
this.setState({errors})
|
this.setState({errors})
|
||||||
},
|
},
|
||||||
|
onSubmit: this.onFormSubmit.bind(this, form)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,8 @@ class AdminPanelSystemPreferences extends React.Component {
|
|||||||
message: null,
|
message: null,
|
||||||
values: {
|
values: {
|
||||||
maintenance: false,
|
maintenance: false,
|
||||||
}
|
},
|
||||||
|
showMessage: true
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -113,11 +114,29 @@ class AdminPanelSystemPreferences extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderMessage() {
|
renderMessage() {
|
||||||
switch (this.state.message) {
|
const { message, showMessage } = this.state;
|
||||||
|
|
||||||
|
switch (message) {
|
||||||
case 'success':
|
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':
|
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:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -126,13 +145,14 @@ class AdminPanelSystemPreferences extends React.Component {
|
|||||||
onFormChange(form) {
|
onFormChange(form) {
|
||||||
const { language, supportedLanguages, allowedLanguages } = form;
|
const { language, supportedLanguages, allowedLanguages } = form;
|
||||||
const languageIndex = _.indexOf(languageKeys, language);
|
const languageIndex = _.indexOf(languageKeys, language);
|
||||||
|
const updatedSupportedLanguages = _.filter(supportedLanguages, (supportedIndex) => _.includes(allowedLanguages, supportedIndex));
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
values: _.extend({}, form, {
|
values: _.extend({}, form, {
|
||||||
language: _.includes(supportedLanguages, languageIndex) ? language : languageKeys[supportedLanguages[0]],
|
language: _.includes(updatedSupportedLanguages, languageIndex) ? language : languageKeys[updatedSupportedLanguages[0]],
|
||||||
supportedLanguages: _.filter(supportedLanguages, (supportedIndex) => _.includes(allowedLanguages, supportedIndex))
|
supportedLanguages: updatedSupportedLanguages
|
||||||
}),
|
}),
|
||||||
message: null
|
message: null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,14 +174,15 @@ class AdminPanelSystemPreferences extends React.Component {
|
|||||||
'allowedLanguages': JSON.stringify(form.allowedLanguages.map(index => languageKeys[index])),
|
'allowedLanguages': JSON.stringify(form.allowedLanguages.map(index => languageKeys[index])),
|
||||||
'supportedLanguages': JSON.stringify(form.supportedLanguages.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() {
|
onSubmitSuccess() {
|
||||||
this.recoverSettings();
|
this.recoverSettings();
|
||||||
this.setState({
|
this.setState({
|
||||||
message: 'success',
|
message: 'success',
|
||||||
loading: false
|
loading: false,
|
||||||
|
showMessage: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +222,8 @@ class AdminPanelSystemPreferences extends React.Component {
|
|||||||
|
|
||||||
onRecoverSettingsFail() {
|
onRecoverSettingsFail() {
|
||||||
this.setState({
|
this.setState({
|
||||||
message: 'error'
|
message: 'error',
|
||||||
|
showMessage: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,6 +232,12 @@ class AdminPanelSystemPreferences extends React.Component {
|
|||||||
this.setState({loading: true});
|
this.setState({loading: true});
|
||||||
this.recoverSettings();
|
this.recoverSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCloseMessage(showMessage) {
|
||||||
|
this.setState({
|
||||||
|
[showMessage]: false
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AdminPanelSystemPreferences;
|
export default AdminPanelSystemPreferences;
|
||||||
|
@ -43,6 +43,9 @@ class AdminPanelDepartments extends React.Component {
|
|||||||
editedAddDepartmentForm: false,
|
editedAddDepartmentForm: false,
|
||||||
editedDefaultDepartmentForm: false,
|
editedDefaultDepartmentForm: false,
|
||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
|
showErrorMessage: true,
|
||||||
|
showSuccessMessage: true,
|
||||||
|
showDefaultDepartmentErrorMessage: true,
|
||||||
errors: {},
|
errors: {},
|
||||||
defaultDepartmentError: null,
|
defaultDepartmentError: null,
|
||||||
form: {
|
form: {
|
||||||
@ -55,7 +58,7 @@ class AdminPanelDepartments extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { errorMessage, formLoading, selectedIndex } = this.state;
|
const { errorMessage, formLoading, selectedIndex, showErrorMessage } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="admin-panel-departments">
|
<div className="admin-panel-departments">
|
||||||
@ -65,7 +68,13 @@ class AdminPanelDepartments extends React.Component {
|
|||||||
<Listing {...this.getListingProps()} />
|
<Listing {...this.getListingProps()} />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-8">
|
<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()}>
|
<Form {...this.getFormProps()}>
|
||||||
<div className="admin-panel-departments__container">
|
<div className="admin-panel-departments__container">
|
||||||
<FormField className="admin-panel-departments__container__name" label={i18n('NAME')} name="name" validation="NAME" required fieldProps={{size: 'large'}} />
|
<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() {
|
renderDefaultDepartmentForm() {
|
||||||
const { defaultDepartmentError, formLoading } = this.state;
|
const { defaultDepartmentError, formLoading, showSuccessMessage, showDefaultDepartmentErrorMessage } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="admin-panel-departments__default-departments-container">
|
<div className="admin-panel-departments__default-departments-container">
|
||||||
<span className="separator" />
|
<span className="separator" />
|
||||||
{(defaultDepartmentError !== null) ?
|
{(defaultDepartmentError !== null) ?
|
||||||
((!defaultDepartmentError) ?
|
((!defaultDepartmentError) ?
|
||||||
<Message type="success">{i18n('SETTINGS_UPDATED')}</Message> :
|
<Message showMessage={showSuccessMessage} onCloseMessage={this.onCloseMessage.bind(this, "showSuccessMessage")} type="success">
|
||||||
<Message type="error">{i18n(defaultDepartmentError)}</Message>) :
|
{i18n('SETTINGS_UPDATED')}
|
||||||
|
</Message> :
|
||||||
|
<Message showMessage={showDefaultDepartmentErrorMessage} onCloseMessage={this.onCloseMessage.bind(this, "showDefaultDepartmentErrorMessage")} type="error">
|
||||||
|
{i18n(defaultDepartmentError)}
|
||||||
|
</Message>) :
|
||||||
null}
|
null}
|
||||||
<Form {...this.getDefaultDepartmentFormProps()} className="admin-panel-departments__default-departments-container__form">
|
<Form {...this.getDefaultDepartmentFormProps()} className="admin-panel-departments__default-departments-container__form">
|
||||||
<div className="admin-panel-departments__default-departments-container__form__fields" >
|
<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) {
|
onItemChange(index) {
|
||||||
if(this.state.editedAddDepartmentForm) {
|
if(this.state.editedAddDepartmentForm) {
|
||||||
AreYouSure.openModal(i18n('WILL_LOSE_CHANGES'), this.updateForm.bind(this, index));
|
AreYouSure.openModal(i18n('WILL_LOSE_CHANGES'), this.updateForm.bind(this, index));
|
||||||
@ -255,8 +274,8 @@ class AdminPanelDepartments extends React.Component {
|
|||||||
}
|
}
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.retrieveDepartments(true);
|
this.retrieveDepartments(true);
|
||||||
this.setState({formLoading: false, errorMessage: false, defaultDepartmentError: false});
|
this.setState({formLoading: false, errorMessage: false, defaultDepartmentError: false, showSuccessMessage: true});
|
||||||
}).catch(result => this.setState({formLoading: false, defaultDepartmentError: result.message}));
|
}).catch(result => this.setState({formLoading: false, defaultDepartmentError: result.message, showDefaultDepartmentErrorMessage: true}));
|
||||||
}
|
}
|
||||||
|
|
||||||
onFormSubmit(form) {
|
onFormSubmit(form) {
|
||||||
@ -273,7 +292,7 @@ class AdminPanelDepartments extends React.Component {
|
|||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.setState({formLoading: false, errorMessage: false, defaultDepartmentError: null});
|
this.setState({formLoading: false, errorMessage: false, defaultDepartmentError: null});
|
||||||
this.retrieveDepartments();
|
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 {
|
} else {
|
||||||
API.call({
|
API.call({
|
||||||
path: '/system/add-department',
|
path: '/system/add-department',
|
||||||
@ -285,9 +304,9 @@ class AdminPanelDepartments extends React.Component {
|
|||||||
this.setState({formLoading: false,errorMessage: false, defaultDepartmentError: null});
|
this.setState({formLoading: false,errorMessage: false, defaultDepartmentError: null});
|
||||||
this.retrieveDepartments();
|
this.retrieveDepartments();
|
||||||
this.onItemChange(-1);
|
this.onItemChange(-1);
|
||||||
}).catch(() => {
|
}).catch(result => {
|
||||||
this.onItemChange.bind(this, -1);
|
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.onItemChange(-1);
|
||||||
this.setState({defaultDepartmentError: null});
|
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) {
|
updateForm(index) {
|
||||||
|
@ -50,7 +50,7 @@ class AdminPanelStaffMembers extends React.Component {
|
|||||||
<Icon name="user-plus" className="" /> {i18n('INVITE_STAFF')}
|
<Icon name="user-plus" className="" /> {i18n('INVITE_STAFF')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,11 @@
|
|||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__loading {
|
||||||
|
min-width: 300px;
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 415px) {
|
@media screen and (max-width: 415px) {
|
||||||
.admin-panel-staff-members {
|
.admin-panel-staff-members {
|
||||||
&__drowpdown {
|
&__drowpdown {
|
||||||
|
@ -10,7 +10,6 @@ import API from 'lib-app/api-call';
|
|||||||
import SessionStore from 'lib-app/session-store';
|
import SessionStore from 'lib-app/session-store';
|
||||||
import TicketList from 'app-components/ticket-list';
|
import TicketList from 'app-components/ticket-list';
|
||||||
import AreYouSure from 'app-components/are-you-sure';
|
import AreYouSure from 'app-components/are-you-sure';
|
||||||
// import Stats from 'app-components/stats';
|
|
||||||
|
|
||||||
import Form from 'core-components/form';
|
import Form from 'core-components/form';
|
||||||
import FormField from 'core-components/form-field';
|
import FormField from 'core-components/form-field';
|
||||||
@ -19,6 +18,7 @@ import Message from 'core-components/message';
|
|||||||
import Button from 'core-components/button';
|
import Button from 'core-components/button';
|
||||||
import Icon from 'core-components/icon';
|
import Icon from 'core-components/icon';
|
||||||
import Loading from 'core-components/loading';
|
import Loading from 'core-components/loading';
|
||||||
|
import statsUtils from 'lib-app/stats-utils';
|
||||||
|
|
||||||
const INITIAL_API_VALUE = {
|
const INITIAL_API_VALUE = {
|
||||||
page: 1,
|
page: 1,
|
||||||
@ -52,30 +52,41 @@ class StaffEditor extends React.Component {
|
|||||||
department: undefined,
|
department: undefined,
|
||||||
departments: this.getUserDepartments(),
|
departments: this.getUserDepartments(),
|
||||||
closedTicketsShown: false,
|
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() {
|
componentDidMount() {
|
||||||
this.retrieveStaffMembers();
|
this.retrieveStaffMembers();
|
||||||
this.retrieveTicketsAssigned(INITIAL_API_VALUE);
|
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() {
|
render() {
|
||||||
const {
|
console.log('State: ', this.state.rawForm);
|
||||||
name,
|
|
||||||
level,
|
const { name, level, profilePic, myAccount, staffId, staffList, userId } = this.props;
|
||||||
profilePic,
|
const { message, tickets, loadingPicture, email } = this.state;
|
||||||
myAccount,
|
|
||||||
staffId,
|
|
||||||
staffList,
|
|
||||||
userId
|
|
||||||
} = this.props;
|
|
||||||
const {
|
|
||||||
message,
|
|
||||||
tickets,
|
|
||||||
loadingPicture,
|
|
||||||
email
|
|
||||||
} = this.state;
|
|
||||||
const myData = _.filter(staffList, {id: `${staffId}`})[0];
|
const myData = _.filter(staffList, {id: `${staffId}`})[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -144,6 +155,8 @@ class StaffEditor extends React.Component {
|
|||||||
<div className="col-md-8">
|
<div className="col-md-8">
|
||||||
<div className="staff-editor__activity">
|
<div className="staff-editor__activity">
|
||||||
<div className="staff-editor__activity-title">{i18n('ACTIVITY')}</div>
|
<div className="staff-editor__activity-title">{i18n('ACTIVITY')}</div>
|
||||||
|
{myData.lastLogin ? null : this.renderReInviteStaffButton()}
|
||||||
|
{this.renderReInviteStaffMessage()}
|
||||||
{this.renderStaffStats()}
|
{this.renderStaffStats()}
|
||||||
</div>
|
</div>
|
||||||
</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() {
|
renderMessage() {
|
||||||
const { message } = this.state;
|
const { message, showMessage } = this.state;
|
||||||
let messageType = (message === 'FAIL') ? 'error' : 'success';
|
const messageType = (message === 'FAIL') ? 'error' : 'success';
|
||||||
let _message = null;
|
let _message = null;
|
||||||
|
|
||||||
switch (message) {
|
switch (message) {
|
||||||
@ -180,7 +250,15 @@ class StaffEditor extends React.Component {
|
|||||||
break;
|
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() {
|
renderSendEmailOnNewTicketForm() {
|
||||||
@ -221,7 +299,7 @@ class StaffEditor extends React.Component {
|
|||||||
|
|
||||||
renderDepartmentsInfo() {
|
renderDepartmentsInfo() {
|
||||||
const { departments } = this.state;
|
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 (
|
return (
|
||||||
<Form values={{departments: Array.from({length: departmentsAssigned.length}, (value, index) => index)}}>
|
<Form values={{departments: Array.from({length: departmentsAssigned.length}, (value, index) => index)}}>
|
||||||
@ -231,11 +309,17 @@ class StaffEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderStaffStats() {
|
renderStaffStats() {
|
||||||
// return (
|
const { loadingStats, ticketData } = this.state;
|
||||||
// <Stats staffId={this.props.staffId} type="staff" />
|
|
||||||
// );
|
|
||||||
|
|
||||||
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() {
|
renderTickets() {
|
||||||
@ -244,7 +328,10 @@ class StaffEditor extends React.Component {
|
|||||||
<span className="separator" />
|
<span className="separator" />
|
||||||
<div className="staff-editor__tickets">
|
<div className="staff-editor__tickets">
|
||||||
<div className="staff-editor__tickets-title">{i18n('TICKETS_ASSIGNED')}</div>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -276,16 +363,8 @@ class StaffEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTicketListProps() {
|
getTicketListProps() {
|
||||||
const {
|
const { staffId, departments } = this.props;
|
||||||
staffId,
|
const { tickets, page, pages, closedTicketsShown } = this.state;
|
||||||
departments
|
|
||||||
} = this.props;
|
|
||||||
const {
|
|
||||||
tickets,
|
|
||||||
page,
|
|
||||||
pages,
|
|
||||||
closedTicketsShown
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'secondary',
|
type: 'secondary',
|
||||||
@ -311,6 +390,7 @@ class StaffEditor extends React.Component {
|
|||||||
departmentIndexes.push(index);
|
departmentIndexes.push(index);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return departmentIndexes;
|
return departmentIndexes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,11 +424,12 @@ class StaffEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSubmit(eventType, form) {
|
onSubmit(eventType, form) {
|
||||||
const {
|
this.setState({
|
||||||
myAccount,
|
loadingStats: true,
|
||||||
staffId,
|
ticketListLoading: true
|
||||||
onChange
|
});
|
||||||
} = this.props;
|
|
||||||
|
const { myAccount, staffId, onChange } = this.props;
|
||||||
let departments;
|
let departments;
|
||||||
|
|
||||||
if(form.departments) {
|
if(form.departments) {
|
||||||
@ -369,21 +450,33 @@ class StaffEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.retrieveStaffMembers();
|
this.retrieveStaffMembers();
|
||||||
window.scrollTo(0,0);
|
window.scrollTo(0,250);
|
||||||
this.setState({message: eventType});
|
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();
|
onChange && onChange();
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
window.scrollTo(0,0);
|
window.scrollTo(0,250);
|
||||||
this.setState({message: 'FAIL'});
|
this.setState({message: 'FAIL', loadingStats: false, showMessage: true});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onDeleteClick() {
|
onDeleteClick() {
|
||||||
const {
|
const { staffId, onDelete } = this.props;
|
||||||
staffId,
|
|
||||||
onDelete
|
|
||||||
} = this.props;
|
|
||||||
return API.call({
|
return API.call({
|
||||||
path: '/staff/delete',
|
path: '/staff/delete',
|
||||||
data: {
|
data: {
|
||||||
@ -391,16 +484,13 @@ class StaffEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}).then(onDelete).catch(() => {
|
}).then(onDelete).catch(() => {
|
||||||
window.scrollTo(0,0);
|
window.scrollTo(0,0);
|
||||||
this.setState({message: 'FAIL'});
|
this.setState({message: 'FAIL', showMessage: true});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onProfilePicChange(event) {
|
onProfilePicChange(event) {
|
||||||
const {
|
const { myAcount, staffId, onChange } = this.props;
|
||||||
myAcount,
|
|
||||||
staffId,
|
|
||||||
onChange
|
|
||||||
} = this.props;
|
|
||||||
this.setState({
|
this.setState({
|
||||||
loadingPicture: true
|
loadingPicture: true
|
||||||
});
|
});
|
||||||
@ -417,10 +507,11 @@ class StaffEditor extends React.Component {
|
|||||||
loadingPicture: false
|
loadingPicture: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.retrieveStaffMembers();
|
||||||
onChange && onChange();
|
onChange && onChange();
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
window.scrollTo(0,0);
|
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() {
|
onClosedTicketsShownChange() {
|
||||||
const {
|
const { department, closedTicketsShown } = this.state;
|
||||||
department,
|
|
||||||
closedTicketsShown
|
|
||||||
} = this.state;
|
|
||||||
const newClosedValue = !closedTicketsShown;
|
const newClosedValue = !closedTicketsShown;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -492,6 +580,12 @@ class StaffEditor extends React.Component {
|
|||||||
department: newDepartmentFilter ? `[${newDepartmentFilter}]` : undefined
|
department: newDepartmentFilter ? `[${newDepartmentFilter}]` : undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCloseMessage(showMessage) {
|
||||||
|
this.setState({
|
||||||
|
[showMessage]: false
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect((store) => {
|
export default connect((store) => {
|
||||||
|
@ -174,6 +174,15 @@
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__ticketlist-loading {
|
||||||
|
min-height: 361px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: $grey;
|
||||||
|
}
|
||||||
|
|
||||||
&__separator {
|
&__separator {
|
||||||
margin: 3px 0;
|
margin: 3px 0;
|
||||||
}
|
}
|
||||||
@ -213,11 +222,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__activity {
|
&__activity {
|
||||||
|
|
||||||
&-title {
|
&-title {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
text-align: left;
|
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: '',
|
title: '',
|
||||||
content: TextEditor.createEmpty(),
|
content: TextEditor.createEmpty(),
|
||||||
language: this.props.language
|
language: this.props.language
|
||||||
}
|
},
|
||||||
|
showErrorMessage: true
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -103,8 +104,16 @@ class AdminPanelCustomResponses extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
renderErrorMessage() {
|
renderErrorMessage() {
|
||||||
|
const { showErrorMessage, error } = this.state;
|
||||||
|
|
||||||
return(
|
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() {
|
renderOptionalButtons() {
|
||||||
@ -202,7 +211,7 @@ class AdminPanelCustomResponses extends React.Component {
|
|||||||
this.onItemChange(-1);
|
this.onItemChange(-1);
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
this.onItemChange.bind(this, -1)
|
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) => {
|
export default connect((store) => {
|
||||||
|
@ -26,17 +26,18 @@ class AdminPanelMyTickets extends React.Component {
|
|||||||
state = {
|
state = {
|
||||||
closedTicketsShown: false,
|
closedTicketsShown: false,
|
||||||
departmentId: null,
|
departmentId: null,
|
||||||
|
pageSize: 10
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.retrieveMyTickets();
|
this.retrieveMyTickets({});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="admin-panel-my-tickets">
|
<div className="admin-panel-my-tickets">
|
||||||
<Header title={i18n('MY_TICKETS')} description={i18n('MY_TICKETS_DESCRIPTION')} />
|
<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}}>
|
<div style={{textAlign: 'right', marginTop: 10}}>
|
||||||
<Button onClick={this.onCreateTicket.bind(this)} type="secondary" size="medium">
|
<Button onClick={this.onCreateTicket.bind(this)} type="secondary" size="medium">
|
||||||
<Icon size="sm" name="plus" /> {i18n('CREATE_TICKET')}
|
<Icon size="sm" name="plus" /> {i18n('CREATE_TICKET')}
|
||||||
@ -68,10 +69,14 @@ class AdminPanelMyTickets extends React.Component {
|
|||||||
onClosedTicketsShownChange: this.onClosedTicketsShownChange.bind(this),
|
onClosedTicketsShownChange: this.onClosedTicketsShownChange.bind(this),
|
||||||
pages,
|
pages,
|
||||||
page,
|
page,
|
||||||
onPageChange: event => this.retrieveMyTickets(event.target.value),
|
onPageChange: event => this.retrieveMyTickets({page: event.target.value}),
|
||||||
onDepartmentChange: departmentId => {
|
onDepartmentChange: departmentId => {
|
||||||
this.setState({departmentId});
|
this.setState({departmentId})
|
||||||
this.retrieveMyTickets(1, closedTicketsShown, 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 {
|
return {
|
||||||
closedTicketsShown: !state.closedTicketsShown
|
closedTicketsShown: !state.closedTicketsShown
|
||||||
};
|
};
|
||||||
}, () => this.retrieveMyTickets());
|
}, () => this.retrieveMyTickets({}));
|
||||||
}
|
}
|
||||||
|
|
||||||
onCreateTicket() {
|
onCreateTicket() {
|
||||||
@ -100,11 +105,11 @@ class AdminPanelMyTickets extends React.Component {
|
|||||||
|
|
||||||
onCreateTicketSuccess() {
|
onCreateTicketSuccess() {
|
||||||
ModalContainer.closeModal();
|
ModalContainer.closeModal();
|
||||||
this.retrieveMyTickets();
|
this.retrieveMyTickets({});
|
||||||
}
|
}
|
||||||
|
|
||||||
retrieveMyTickets(page = this.props.page, closed = this.state.closedTicketsShown, departmentId = this.state.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 * 1, departmentId));
|
this.props.dispatch(AdminDataAction.retrieveMyTickets({page, closed: closed * 1, departmentId, pageSize}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
bottom: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,11 @@ class AdminPanelNewTickets extends React.Component {
|
|||||||
|
|
||||||
state = {
|
state = {
|
||||||
departmentId: null,
|
departmentId: null,
|
||||||
|
pageSize: 10
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.retrieveNewTickets();
|
this.retrieveNewTickets({});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -31,8 +32,8 @@ class AdminPanelNewTickets extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div className="admin-panel-new-tickets">
|
<div className="admin-panel-new-tickets">
|
||||||
<Header title={i18n('NEW_TICKETS')} description={i18n('NEW_TICKETS_DESCRIPTION')} />
|
<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}
|
{(noDepartments) ? <Message showCloseButton={false} 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()}/>}
|
{(this.props.error) ? <Message showCloseButton={false} type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> : <TicketList {...this.getProps()} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -47,16 +48,20 @@ class AdminPanelNewTickets extends React.Component {
|
|||||||
ticketPath: '/admin/panel/tickets/view-ticket/',
|
ticketPath: '/admin/panel/tickets/view-ticket/',
|
||||||
page: this.props.page,
|
page: this.props.page,
|
||||||
pages: this.props.pages,
|
pages: this.props.pages,
|
||||||
onPageChange: event => this.retrieveNewTickets(event.target.value),
|
onPageChange: event => this.retrieveNewTickets({page: event.target.value}),
|
||||||
onDepartmentChange: departmentId => {
|
onDepartmentChange: departmentId => {
|
||||||
this.setState({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) {
|
retrieveNewTickets({page = this.props.page, departmentId = this.state.departmentId, pageSize = this.state.pageSize }) {
|
||||||
this.props.dispatch(AdminDataAction.retrieveNewTickets(page, departmentId));
|
this.props.dispatch(AdminDataAction.retrieveNewTickets({page, departmentId, pageSize}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ export function updateSearchTicketsFromURL() {
|
|||||||
const currentSearchParams = queryString.parse(currentSearch);
|
const currentSearchParams = queryString.parse(currentSearch);
|
||||||
const showFilters = (currentSearch !== SEARCH_TICKETS_INITIAL_QUERY) && currentSearchParams.custom;
|
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.changeFilters(listConfig));
|
||||||
store.dispatch(searchFiltersActions.retrieveSearchTickets(
|
store.dispatch(searchFiltersActions.retrieveSearchTickets(
|
||||||
@ -38,7 +38,8 @@ export function updateSearchTicketsFromURL() {
|
|||||||
...store.getState().searchFilters.ticketQueryListState,
|
...store.getState().searchFilters.ticketQueryListState,
|
||||||
page: (currentSearchParams.page || INITIAL_PAGE)*1
|
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 {
|
class AdminPanelSearchTickets extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { listConfig } = this.props;
|
const { listConfig, error } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="admin-panel-search-tickets">
|
<div className="admin-panel-search-tickets">
|
||||||
<div className="admin-panel-search-tickets__container">
|
<div className="admin-panel-search-tickets__container">
|
||||||
@ -67,10 +69,9 @@ class AdminPanelSearchTickets extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
<TicketQueryFilters />
|
<TicketQueryFilters />
|
||||||
{
|
{
|
||||||
(this.props.error) ?
|
error ?
|
||||||
<Message type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> :
|
<Message showCloseButton={false} type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> :
|
||||||
<TicketQueryList
|
<TicketQueryList onChangeOrderBy={this.onChangeOrderBy.bind(this)} />
|
||||||
onChangeOrderBy={this.onChangeOrderBy.bind(this)} />
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -21,7 +21,9 @@ class AdminPanelBanUsers extends React.Component {
|
|||||||
listError: false,
|
listError: false,
|
||||||
addBanStatus: 'none',
|
addBanStatus: 'none',
|
||||||
emails: [],
|
emails: [],
|
||||||
filteredEmails: []
|
filteredEmails: [],
|
||||||
|
showMessage: true,
|
||||||
|
showListErrorMessage: true
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -29,10 +31,18 @@ class AdminPanelBanUsers extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { listError, showListErrorMessage } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="admin-panel-ban-users row">
|
<div className="admin-panel-ban-users row">
|
||||||
<Header title={i18n('BAN_USERS')} description={i18n('BAN_USERS_DESCRIPTION')} />
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -59,11 +69,29 @@ class AdminPanelBanUsers extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderMessage() {
|
renderMessage() {
|
||||||
switch (this.state.addBanStatus) {
|
const { addBanStatus, showMessage } = this.state;
|
||||||
|
|
||||||
|
switch (addBanStatus) {
|
||||||
case 'success':
|
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':
|
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:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -119,10 +147,11 @@ class AdminPanelBanUsers extends React.Component {
|
|||||||
}
|
}
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
addBanStatus: 'success'
|
addBanStatus: 'success',
|
||||||
|
showMessage: true
|
||||||
});
|
});
|
||||||
this.retrieveEmails();
|
this.retrieveEmails();
|
||||||
}).catch(() => this.setState({addBanStatus: 'fail', loadingForm: false}));
|
}).catch(() => this.setState({addBanStatus: 'fail', loadingForm: false, showMessage: true}));
|
||||||
}
|
}
|
||||||
|
|
||||||
onUnBanClick(email) {
|
onUnBanClick(email) {
|
||||||
@ -150,10 +179,17 @@ class AdminPanelBanUsers extends React.Component {
|
|||||||
filteredEmails: result.data
|
filteredEmails: result.data
|
||||||
})).catch(() => this.setState({
|
})).catch(() => this.setState({
|
||||||
listError: true,
|
listError: true,
|
||||||
|
showListErrorMessage: true,
|
||||||
loadingList: false,
|
loadingList: false,
|
||||||
loadingForm: false
|
loadingForm: false
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCloseMessage(showMessage) {
|
||||||
|
this.setState({
|
||||||
|
[showMessage]: false
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AdminPanelBanUsers;
|
export default AdminPanelBanUsers;
|
||||||
|
@ -21,16 +21,16 @@ class AdminPanelCustomFieldForm extends React.Component {
|
|||||||
state = {
|
state = {
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
addForm: {},
|
addForm: {
|
||||||
|
name: "",
|
||||||
|
description: ""
|
||||||
|
},
|
||||||
addFormOptions: [],
|
addFormOptions: [],
|
||||||
|
showErrorMessage: true
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { loading, addForm, error } = this.state;
|
||||||
loading,
|
|
||||||
addForm,
|
|
||||||
error
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="admin-panel-custom-field-form">
|
<div className="admin-panel-custom-field-form">
|
||||||
@ -41,7 +41,8 @@ class AdminPanelCustomFieldForm extends React.Component {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
values={addForm}
|
values={addForm}
|
||||||
onChange={this.onAddFormChange.bind(this)}
|
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="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="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/>
|
<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() {
|
renderErrorMessage() {
|
||||||
|
const { error, showErrorMessage } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Message type="error">
|
<Message showMessage={showErrorMessage} onCloseMessage={this.onCloseMessage.bind(this, "showErrorMessage")} type="error">
|
||||||
{this.state.error}
|
{i18n(error)}
|
||||||
</Message>
|
</Message>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -84,9 +87,14 @@ class AdminPanelCustomFieldForm extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCloseMessage(showMessage) {
|
||||||
|
this.setState({
|
||||||
|
[showMessage]: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onAddOptionClick(event) {
|
onAddOptionClick(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
let addFormOptions = _.clone(this.state.addFormOptions);
|
let addFormOptions = _.clone(this.state.addFormOptions);
|
||||||
|
|
||||||
addFormOptions.push("");
|
addFormOptions.push("");
|
||||||
@ -127,7 +135,7 @@ class AdminPanelCustomFieldForm extends React.Component {
|
|||||||
this.setState({loading: false, message: null});
|
this.setState({loading: false, message: null});
|
||||||
if(this.props.onChange) this.props.onChange();
|
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 {
|
&__buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
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 ModalContainer from 'app-components/modal-container';
|
||||||
import InviteUserWidget from 'app/admin/panel/users/invite-user-widget';
|
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 {
|
class AdminPanelListUsers extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
loading: true,
|
loading: true,
|
||||||
users: [],
|
users: [],
|
||||||
orderBy: 'id',
|
usersParams: DEFAULT_USERS_PARAMS,
|
||||||
desc: true,
|
|
||||||
error: false,
|
error: false,
|
||||||
page: 1,
|
pages: 1,
|
||||||
pages: 1
|
showMessage: true
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.retrieveUsers({
|
this.retrieveUsers(DEFAULT_USERS_PARAMS);
|
||||||
page: 1,
|
|
||||||
orderBy: 'id',
|
|
||||||
desc: true,
|
|
||||||
search: ''
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="admin-panel-list-users">
|
<div className="admin-panel-list-users">
|
||||||
<Header title={i18n('LIST_USERS')} description={i18n('LIST_USERS_DESCRIPTION')} />
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTableAndInviteButton() {
|
renderTableAndInviteButton() {
|
||||||
|
const { message, showMessage } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SearchBox className="admin-panel-list-users__search-box" placeholder={i18n('SEARCH_USERS')} onSearch={this.onSearch.bind(this)} />
|
<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()} />
|
<Table {...this.getTableProps()} />
|
||||||
<div style={{textAlign: 'right', marginTop: 10}}>
|
<div style={{textAlign: 'right', marginTop: 10}}>
|
||||||
<Button onClick={this.onInviteUser.bind(this)} type="secondary" size="medium">
|
<Button onClick={this.onInviteUser.bind(this)} type="secondary" size="medium">
|
||||||
@ -59,14 +73,16 @@ class AdminPanelListUsers extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTableProps() {
|
getTableProps() {
|
||||||
|
const {loading, users, usersParams, pages } = this.state;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
className: 'admin-panel-list-users__table',
|
className: 'admin-panel-list-users__table',
|
||||||
loading: this.state.loading,
|
loading,
|
||||||
headers: this.getTableHeaders(),
|
headers: this.getTableHeaders(),
|
||||||
rows: this.state.users.map(this.getUserRow.bind(this)),
|
rows: users.map(this.getUserRow.bind(this)),
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
page: this.state.page,
|
page: usersParams.page,
|
||||||
pages: this.state.pages,
|
pages,
|
||||||
onPageChange: this.onPageChange.bind(this)
|
onPageChange: this.onPageChange.bind(this)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -129,44 +145,57 @@ class AdminPanelListUsers extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSearch(query) {
|
onSearch(query) {
|
||||||
this.retrieveUsers({
|
const newUsersParams = {
|
||||||
page: 1,
|
...this.state.usersParams,
|
||||||
orderBy: 'id',
|
page: DEFAULT_USERS_PARAMS.page,
|
||||||
desc: true,
|
|
||||||
search: query
|
search: query
|
||||||
|
}
|
||||||
|
|
||||||
|
this.retrieveUsers(newUsersParams);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
usersParams: newUsersParams
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onPageChange(event) {
|
onPageChange(event) {
|
||||||
const {
|
const newUsersParams = {
|
||||||
orderBy,
|
...this.state.usersParams,
|
||||||
desc,
|
|
||||||
search
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
this.retrieveUsers({
|
|
||||||
page: event.target.value,
|
page: event.target.value,
|
||||||
orderBy,
|
}
|
||||||
desc,
|
|
||||||
search
|
this.retrieveUsers(newUsersParams);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
usersParams: newUsersParams
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
orderByTickets(desc) {
|
orderByTickets(desc) {
|
||||||
this.retrieveUsers({
|
const newUsersParams = {
|
||||||
page: 1,
|
...this.state.usersParams,
|
||||||
orderBy: 'tickets',
|
orderBy: 'tickets',
|
||||||
desc: desc,
|
desc: desc
|
||||||
search: this.state.search
|
}
|
||||||
|
|
||||||
|
this.retrieveUsers(newUsersParams);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
usersParams: newUsersParams
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
orderById(desc) {
|
orderById(desc) {
|
||||||
this.retrieveUsers({
|
const newUsersParams = {
|
||||||
page: 1,
|
...this.state.usersParams,
|
||||||
orderBy: 'id',
|
orderBy: 'id',
|
||||||
desc: desc,
|
desc: desc
|
||||||
search: this.state.search
|
}
|
||||||
|
|
||||||
|
this.retrieveUsers(newUsersParams);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
usersParams: newUsersParams
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,21 +213,43 @@ class AdminPanelListUsers extends React.Component {
|
|||||||
onInviteUser(user) {
|
onInviteUser(user) {
|
||||||
ModalContainer.openModal(
|
ModalContainer.openModal(
|
||||||
<div className="admin-panel-list-users__invite-user-form">
|
<div className="admin-panel-list-users__invite-user-form">
|
||||||
<InviteUserWidget onSuccess={this.onInviteUserSuccess.bind(this)} />
|
<InviteUserWidget
|
||||||
</div>
|
onSuccess={this.onInviteUserSuccess.bind(this)}
|
||||||
|
onChangeMessage={this.onChangeMessage.bind(this)} />
|
||||||
|
</div>,
|
||||||
|
{
|
||||||
|
closeButton: {
|
||||||
|
showCloseButton: true
|
||||||
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChangeMessage(message) {
|
||||||
|
this.setState({
|
||||||
|
message,
|
||||||
|
showMessage: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onInviteUserSuccess() {
|
onInviteUserSuccess() {
|
||||||
ModalContainer.closeModal();
|
ModalContainer.closeModal();
|
||||||
|
|
||||||
|
this.retrieveUsers(DEFAULT_USERS_PARAMS);
|
||||||
}
|
}
|
||||||
|
|
||||||
onUsersRetrieved(result) {
|
onUsersRetrieved(result) {
|
||||||
|
const { page, pages, users, orderBy, desc } = result.data;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
page: result.data.page * 1,
|
usersParams: {
|
||||||
pages: result.data.pages * 1,
|
...this.state.usersParams,
|
||||||
users: result.data.users,
|
page: page*1,
|
||||||
orderBy: result.data.orderBy,
|
orderBy: orderBy,
|
||||||
desc: (result.data.desc*1),
|
desc: desc*1,
|
||||||
|
},
|
||||||
|
pages: pages*1,
|
||||||
|
users: users,
|
||||||
error: false,
|
error: false,
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
@ -210,6 +261,12 @@ class AdminPanelListUsers extends React.Component {
|
|||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCloseMessage(showMessage) {
|
||||||
|
this.setState({
|
||||||
|
[showMessage]: false
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect((store) => {
|
export default connect((store) => {
|
||||||
|
@ -22,7 +22,15 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__success-message {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
&__add-user-form {
|
&__add-user-form {
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__invite-user-form {
|
||||||
|
min-width: 700px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,8 @@ class AdminPanelViewUser extends React.Component {
|
|||||||
loading: true,
|
loading: true,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
userList: [],
|
userList: [],
|
||||||
message: ''
|
message: '',
|
||||||
|
showMessage: true
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -51,7 +52,7 @@ class AdminPanelViewUser extends React.Component {
|
|||||||
renderInvalid() {
|
renderInvalid() {
|
||||||
return (
|
return (
|
||||||
<div className="admin-panel-view-user__invalid">
|
<div className="admin-panel-view-user__invalid">
|
||||||
<Message type="error">{i18n('INVALID_USER')}</Message>
|
<Message showCloseButton={false} type="error">{i18n('INVALID_USER')}</Message>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -92,7 +93,10 @@ class AdminPanelViewUser extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
<span className="separator" />
|
<span className="separator" />
|
||||||
<div className="admin-panel-view-user">
|
<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">
|
<div className="admin-panel-view-user__supervised-users-content">
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
onChange={this.onChangeValues.bind(this)}
|
onChange={this.onChangeValues.bind(this)}
|
||||||
@ -111,7 +115,10 @@ class AdminPanelViewUser extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
<span className="separator" />
|
<span className="separator" />
|
||||||
<div className="admin-panel-view-user__tickets">
|
<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()} />
|
<TicketList {...this.getTicketListProps()} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -119,22 +126,32 @@ class AdminPanelViewUser extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderSupervisedUserMessage(){
|
renderSupervisedUserMessage(){
|
||||||
const { message } = this.state;
|
const { message, showMessage } = this.state;
|
||||||
|
|
||||||
if(message) {
|
if(message) {
|
||||||
if(message != 'success') {
|
if(message !== 'success') {
|
||||||
return (
|
return (
|
||||||
<div className="admin-panel-view-user__supervised-users-message">
|
<Message
|
||||||
<Message type="error">{i18n(message)}</Message>
|
showMessage={showMessage}
|
||||||
</div>
|
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
|
||||||
|
className="admin-panel-view-user__supervised-users-message"
|
||||||
|
type="error">
|
||||||
|
{i18n(message)}
|
||||||
|
</Message>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div className= "admin-panel-view-user__supervised-users-message">
|
<Message
|
||||||
<Message type="success">{i18n('SUPERVISED_USERS_UPDATED')}</Message>
|
showMessage={showMessage}
|
||||||
</div>
|
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 => {
|
}).then(r => {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
message: 'success'
|
message: 'success',
|
||||||
|
showMessage: true
|
||||||
})
|
})
|
||||||
}).catch((r) => {
|
}).catch((r) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
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) => {
|
export default connect((store) => {
|
||||||
|
@ -30,10 +30,19 @@
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__supervised-users-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
&__action-button {
|
&__action-button {
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__tickets-info-container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
&__tickets-title {
|
&__tickets-title {
|
||||||
font-size: $font-size--md;
|
font-size: $font-size--md;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
@ -64,4 +73,7 @@
|
|||||||
margin-left: 15px;
|
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 Header from 'core-components/header';
|
||||||
import Button from 'core-components/button';
|
import Button from 'core-components/button';
|
||||||
import ModalContainer from 'app-components/modal-container';
|
import ModalContainer from 'app-components/modal-container';
|
||||||
|
import Loading from 'core-components/loading';
|
||||||
|
|
||||||
class InviteUserWidget extends React.Component {
|
class InviteUserWidget extends React.Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onSuccess: React.PropTypes.func,
|
onSuccess: React.PropTypes.func,
|
||||||
className: React.PropTypes.string
|
className: React.PropTypes.string,
|
||||||
|
onChangeMessage: React.PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -28,7 +30,8 @@ class InviteUserWidget extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
loading: false,
|
loading: false,
|
||||||
email: null,
|
email: null,
|
||||||
customFields: null
|
customFields: null,
|
||||||
|
showMessage: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,32 +39,45 @@ class InviteUserWidget extends React.Component {
|
|||||||
API.call({
|
API.call({
|
||||||
path: '/system/get-custom-fields',
|
path: '/system/get-custom-fields',
|
||||||
data: {}
|
data: {}
|
||||||
})
|
}).then(result => {
|
||||||
.then(result => this.setState({customFields: result.data}));
|
this.setState({
|
||||||
|
customFields: result.data
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if(!this.state.customFields) return null;
|
if(!this.state.customFields) return this.renderLoading();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Widget className={this.getClass()}>
|
<div className="invite-user-widget__modal-wrapper">
|
||||||
<Header title={i18n('INVITE_USER')} description={i18n('INVITE_USER_VIEW_DESCRIPTION')} />
|
<Widget className={this.getClass()}>
|
||||||
<Form {...this.getFormProps()}>
|
<Header title={i18n('INVITE_USER')} description={i18n('INVITE_USER_VIEW_DESCRIPTION')} />
|
||||||
<div className="invite-user-widget__inputs">
|
<Form {...this.getFormProps()}>
|
||||||
<FormField {...this.getInputProps()} label={i18n('FULL_NAME')} name="name" validation="NAME" required />
|
<div className="invite-user-widget__inputs">
|
||||||
<FormField {...this.getInputProps()} label={i18n('EMAIL')} name="email" validation="EMAIL" required />
|
<FormField {...this.getInputProps()} label={i18n('FULL_NAME')} name="name" validation="NAME" required />
|
||||||
{this.state.customFields.map(this.renderCustomField.bind(this))}
|
<FormField {...this.getInputProps()} label={i18n('EMAIL')} name="email" validation="EMAIL" required />
|
||||||
</div>
|
{this.state.customFields.map(this.renderCustomField.bind(this))}
|
||||||
<div className="invite-user-widget__captcha">
|
</div>
|
||||||
<Captcha ref="captcha" />
|
<div className="invite-user-widget__captcha">
|
||||||
</div>
|
<Captcha ref="captcha" />
|
||||||
<div className="invite-user-widget__buttons-container">
|
</div>
|
||||||
<Button onClick={(e) => {e.preventDefault(); ModalContainer.closeModal();}} type="link">{i18n('CANCEL')}</Button>
|
<div className="invite-user-widget__buttons-container">
|
||||||
<SubmitButton type="secondary">{i18n('INVITE_USER')}</SubmitButton>
|
<SubmitButton type="secondary">{i18n('INVITE_USER')}</SubmitButton>
|
||||||
</div>
|
<Button onClick={(e) => {e.preventDefault(); ModalContainer.closeModal();}} type="link">{i18n('CANCEL')}</Button>
|
||||||
</Form>
|
</div>
|
||||||
{this.renderMessage()}
|
</Form>
|
||||||
</Widget>
|
{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() {
|
renderMessage() {
|
||||||
switch (this.state.message) {
|
const { message, showMessage } = this.state;
|
||||||
case 'success':
|
|
||||||
return <Message className="invite-user-widget__success-message" type="success">{i18n('INVITE_USER_SUCCESS')}</Message>;
|
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':
|
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:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -153,16 +187,35 @@ class InviteUserWidget extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onInviteUserSuccess() {
|
onInviteUserSuccess() {
|
||||||
|
const { onSuccess, onChangeMessage } = this.props;
|
||||||
|
const message = 'success';
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
message: 'success'
|
message,
|
||||||
|
showMessage: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onChangeMessage && onChangeMessage(message);
|
||||||
|
onSuccess && onSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
onInviteUserFail() {
|
onInviteUserFail() {
|
||||||
|
const { onChangeMessage } = this.props;
|
||||||
|
const message = 'fail';
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
message: 'fail'
|
message,
|
||||||
|
showMessage: true
|
||||||
|
});
|
||||||
|
|
||||||
|
onChangeMessage && onChangeMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
onCloseMessage(showMessage) {
|
||||||
|
this.setState({
|
||||||
|
[showMessage]: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
&__buttons-container {
|
&__buttons-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row-reverse;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
@ -27,4 +27,18 @@
|
|||||||
&__error-message {
|
&__error-message {
|
||||||
margin-top: 20px;
|
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() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="install-completed">
|
<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')}
|
{i18n('INSTALLATION_COMPLETED_DESCRIPTION')}
|
||||||
</Message>
|
</Message>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,6 +17,7 @@ class InstallStep3Database extends React.Component {
|
|||||||
state = {
|
state = {
|
||||||
loading: false,
|
loading: false,
|
||||||
error: false,
|
error: false,
|
||||||
|
showErrorMessage: true,
|
||||||
errorMessage: ''
|
errorMessage: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -42,17 +43,19 @@ class InstallStep3Database extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderMessage() {
|
renderMessage() {
|
||||||
let message = null;
|
const { error, errorMessage, showErrorMessage } = this.state;
|
||||||
|
|
||||||
if(this.state.error) {
|
return (
|
||||||
message = (
|
error ?
|
||||||
<Message className="install-step-3__message" type="error">
|
<Message
|
||||||
{i18n('ERROR_UPDATING_SETTINGS')}: {this.state.errorMessage}
|
showMessage={showErrorMessage}
|
||||||
</Message>
|
onCloseMessage={this.onCloseMessage.bind(this, "showErrorMessage")}
|
||||||
);
|
className="install-step-3__message"
|
||||||
}
|
type="error">
|
||||||
|
{i18n('ERROR_UPDATING_SETTINGS')}: {errorMessage}
|
||||||
return message;
|
</Message> :
|
||||||
|
null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onPreviousClick(event) {
|
onPreviousClick(event) {
|
||||||
@ -72,10 +75,17 @@ class InstallStep3Database extends React.Component {
|
|||||||
.catch(({message}) => this.setState({
|
.catch(({message}) => this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
error: true,
|
error: true,
|
||||||
|
showErrorMessage: true,
|
||||||
errorMessage: message
|
errorMessage: message
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCloseMessage(showMessage) {
|
||||||
|
this.setState({
|
||||||
|
[showMessage]: false
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default InstallStep3Database;
|
export default InstallStep3Database;
|
||||||
|
@ -20,6 +20,7 @@ class InstallStep5Settings extends React.Component {
|
|||||||
loading: false,
|
loading: false,
|
||||||
form: {},
|
form: {},
|
||||||
error: false,
|
error: false,
|
||||||
|
showErrorMessage: true,
|
||||||
errorMessage: ''
|
errorMessage: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -65,17 +66,19 @@ class InstallStep5Settings extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderMessage() {
|
renderMessage() {
|
||||||
let message = null;
|
const { error, errorMessage, showErrorMessage } = this.state;
|
||||||
|
|
||||||
if(this.state.error) {
|
return (
|
||||||
message = (
|
error ?
|
||||||
<Message className="install-step-5__message" type="error">
|
<Message
|
||||||
{i18n('ERROR_UPDATING_SETTINGS')}: {this.state.errorMessage}
|
showMessage={showErrorMessage}
|
||||||
</Message>
|
onCloseMessage={this.onCloseMessage.bind(this, "showErrorMessage")}
|
||||||
);
|
className="install-step-5__message"
|
||||||
}
|
type="error">
|
||||||
|
{i18n('ERROR_UPDATING_SETTINGS')}: {errorMessage}
|
||||||
return message;
|
</Message> :
|
||||||
|
null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onTestSMTPClick(event) {
|
onTestSMTPClick(event) {
|
||||||
@ -125,10 +128,17 @@ class InstallStep5Settings extends React.Component {
|
|||||||
.catch(({message}) => this.setState({
|
.catch(({message}) => this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
error: true,
|
error: true,
|
||||||
|
showErrorMessage: true,
|
||||||
errorMessage: message
|
errorMessage: message
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCloseMessage(showMessage) {
|
||||||
|
this.setState({
|
||||||
|
[showMessage]: false
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect((store) => {
|
export default connect((store) => {
|
||||||
|
@ -15,6 +15,7 @@ class InstallStep6Admin extends React.Component {
|
|||||||
state = {
|
state = {
|
||||||
loading: false,
|
loading: false,
|
||||||
error: false,
|
error: false,
|
||||||
|
showErrorMessage: true,
|
||||||
errorMessage: ''
|
errorMessage: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -36,17 +37,19 @@ class InstallStep6Admin extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderMessage() {
|
renderMessage() {
|
||||||
let message = null;
|
const { error, errorMessage, showErrorMessage } = this.state;
|
||||||
|
|
||||||
if(this.state.error) {
|
return (
|
||||||
message = (
|
error ?
|
||||||
<Message className="install-step-6_message" type="error">
|
<Message
|
||||||
{i18n('ERROR_UPDATING_SETTINGS')}: {this.state.errorMessage}
|
showMessage={showErrorMessage}
|
||||||
</Message>
|
onCloseMessage={this.onCloseMessage.bind(this, "showErrorMessage")}
|
||||||
);
|
className="install-step-6_message"
|
||||||
}
|
type="error">
|
||||||
|
{i18n('ERROR_UPDATING_SETTINGS')}: {errorMessage}
|
||||||
return message;
|
</Message> :
|
||||||
|
null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit(form) {
|
onSubmit(form) {
|
||||||
@ -61,10 +64,17 @@ class InstallStep6Admin extends React.Component {
|
|||||||
.catch(({message}) => this.setState({
|
.catch(({message}) => this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
error: true,
|
error: true,
|
||||||
|
showErrorMessage: true,
|
||||||
errorMessage: message
|
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 SubmitButton from 'core-components/submit-button';
|
||||||
import Message from 'core-components/message';
|
import Message from 'core-components/message';
|
||||||
|
|
||||||
|
const DEFAULT_CREATE_TICKET_FORM_VALUE = {
|
||||||
|
title: '',
|
||||||
|
email: '',
|
||||||
|
name: ''
|
||||||
|
};
|
||||||
|
|
||||||
class CreateTicketForm extends React.Component {
|
class CreateTicketForm extends React.Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -34,23 +40,16 @@ class CreateTicketForm extends React.Component {
|
|||||||
loading: false,
|
loading: false,
|
||||||
message: null,
|
message: null,
|
||||||
form: {
|
form: {
|
||||||
title: '',
|
...DEFAULT_CREATE_TICKET_FORM_VALUE,
|
||||||
content: TextEditor.createEmpty(),
|
content: TextEditor.createEmpty(),
|
||||||
departmentIndex: getPublicDepartmentIndexFromDepartmentId(this.props.defaultDepartmentId, SessionStore.getDepartments()),
|
departmentIndex: getPublicDepartmentIndexFromDepartmentId(this.props.defaultDepartmentId, SessionStore.getDepartments()),
|
||||||
email: '',
|
|
||||||
name: '',
|
|
||||||
language: this.props.language
|
language: this.props.language
|
||||||
}
|
},
|
||||||
|
showMessage: true
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { userLogged, isDefaultDepartmentLocked, isStaff, onlyOneSupportedLanguage, allowAttachments } = this.props;
|
||||||
userLogged,
|
|
||||||
isDefaultDepartmentLocked,
|
|
||||||
isStaff,
|
|
||||||
onlyOneSupportedLanguage,
|
|
||||||
allowAttachments
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="create-ticket-form">
|
<div className="create-ticket-form">
|
||||||
@ -79,10 +78,14 @@ class CreateTicketForm extends React.Component {
|
|||||||
fieldProps={{allowImages: allowAttachments}}
|
fieldProps={{allowImages: allowAttachments}}
|
||||||
required
|
required
|
||||||
field="textarea" />
|
field="textarea" />
|
||||||
<div className="create-ticket-form__buttons-container">
|
<div className="create-ticket-form__container">
|
||||||
{allowAttachments ? this.renderFileUpload() : null}
|
<div className={`create-ticket-form__buttons-container${allowAttachments ? "" : "-without-allow-attachments"}`}>
|
||||||
{(!userLogged) ? this.renderCaptcha() : null}
|
{allowAttachments ? this.renderFileUpload() : null}
|
||||||
<SubmitButton type="secondary">{i18n('CREATE_TICKET')}</SubmitButton>
|
<SubmitButton type="secondary">{i18n('CREATE_TICKET')}</SubmitButton>
|
||||||
|
</div>
|
||||||
|
<div className="create-ticket-form__captcha-container">
|
||||||
|
{(!userLogged) ? this.renderCaptcha() : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
{this.renderMessage()}
|
{this.renderMessage()}
|
||||||
@ -116,21 +119,38 @@ class CreateTicketForm extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderMessage() {
|
renderMessage() {
|
||||||
switch (this.state.message) {
|
const { message, showMessage } = this.state;
|
||||||
case 'success':
|
|
||||||
return <Message className="create-ticket-form__message" type="success">{i18n('TICKET_SENT')}</Message>;
|
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':
|
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:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getFormProps() {
|
getFormProps() {
|
||||||
const {
|
const { loading, form } = this.state;
|
||||||
loading,
|
|
||||||
form
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loading,
|
loading,
|
||||||
@ -161,29 +181,48 @@ class CreateTicketForm extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onTicketSuccess(email, result) {
|
onTicketSuccess() {
|
||||||
const { onSuccess } = this.props;
|
const { onSuccess, userLogged, language } = this.props;
|
||||||
|
const { form } = this.state;
|
||||||
const message = 'success';
|
const message = 'success';
|
||||||
|
|
||||||
this.setState(
|
this.setState(
|
||||||
{
|
{
|
||||||
loading: false,
|
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() {
|
onTicketFail() {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
message: 'fail'
|
message: 'fail',
|
||||||
|
showMessage: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onCloseMessage(showMessage) {
|
||||||
|
this.setState({
|
||||||
|
[showMessage]: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect((store) => {
|
export default connect((store) => {
|
||||||
const { language, supportedLanguages } = store.config;
|
const { language, supportedLanguages } = store.config;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
language: _.includes(supportedLanguages, language) ? language : supportedLanguages[0],
|
language: _.includes(supportedLanguages, language) ? language : supportedLanguages[0],
|
||||||
onlyOneSupportedLanguage: supportedLanguages.length == 1 ? true : false,
|
onlyOneSupportedLanguage: supportedLanguages.length == 1 ? true : false,
|
||||||
|
@ -8,11 +8,29 @@
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
&__buttons-container {
|
&__buttons-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: center;
|
||||||
align-items: 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 {
|
&__captcha {
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {connect} from 'react-redux';
|
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 CreateTicketForm from 'app/main/dashboard/dashboard-create-ticket/create-ticket-form';
|
||||||
|
|
||||||
import Widget from 'core-components/widget';
|
import Widget from 'core-components/widget';
|
||||||
|
import Message from 'core-components/message';
|
||||||
|
|
||||||
class DashboardCreateTicketPage extends React.Component {
|
class DashboardCreateTicketPage extends React.Component {
|
||||||
|
|
||||||
@ -14,6 +17,10 @@ class DashboardCreateTicketPage extends React.Component {
|
|||||||
userSystemEnabled: React.PropTypes.bool
|
userSystemEnabled: React.PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
showMessage: !!queryString.parse(window.location.search)["message"]
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let Wrapper = 'div';
|
let Wrapper = 'div';
|
||||||
|
|
||||||
@ -23,21 +30,26 @@ class DashboardCreateTicketPage extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={this.getClass()}>
|
<div className={this.getClass()}>
|
||||||
<Wrapper>
|
<Wrapper className="dashboard-create-ticket-page__container">
|
||||||
<CreateTicketForm
|
<Message // TODO Remove this message
|
||||||
userLogged={(this.props.location.pathname !== '/create-ticket')}
|
showMessage={this.state.showMessage}
|
||||||
onSuccess={this.onCreateTicketSuccess.bind(this)}/>
|
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>
|
</Wrapper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onCreateTicketSuccess(result, email, message) {
|
onCreateTicketSuccess(message) {
|
||||||
if((this.props.location.pathname !== '/create-ticket')) {
|
history.push(`${(this.props.location.pathname !== '/create-ticket') ? "/dashboard" : "/"}?message=${message}`);
|
||||||
history.push(`/dashboard?message=${message}`);
|
|
||||||
} else {
|
|
||||||
setTimeout(() => {history.push('/check-ticket/' + result.data.ticketNumber + '/' + email)}, 1000);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getClass() {
|
getClass() {
|
||||||
@ -49,6 +61,21 @@ class DashboardCreateTicketPage extends React.Component {
|
|||||||
|
|
||||||
return classNames(classes);
|
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) => {
|
export default connect((store) => {
|
||||||
|
@ -7,4 +7,18 @@
|
|||||||
float: none;
|
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: [],
|
customFields: [],
|
||||||
customFieldsFrom: {},
|
customFieldsFrom: {},
|
||||||
loadingCustomFields: false,
|
loadingCustomFields: false,
|
||||||
|
showChangeEmailMessage: true,
|
||||||
|
showChangePasswordMessage: true
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -115,12 +117,7 @@ class DashboardEditProfilePage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderCustomField(customField, key) {
|
renderCustomField(customField, key) {
|
||||||
const {
|
const { type, name, description, options } = customField;
|
||||||
type,
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
options
|
|
||||||
} = customField;
|
|
||||||
|
|
||||||
if(type === 'text') {
|
if(type === 'text') {
|
||||||
return (
|
return (
|
||||||
@ -140,22 +137,58 @@ class DashboardEditProfilePage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderMessageEmail() {
|
renderMessageEmail() {
|
||||||
switch (this.state.messageEmail) {
|
const { messageEmail, showChangeEmailMessage } = this.state;
|
||||||
|
|
||||||
|
switch (messageEmail) {
|
||||||
case 'success':
|
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':
|
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:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMessagePass() {
|
renderMessagePass() {
|
||||||
switch (this.state.messagePass) {
|
const { messagePass, showChangePasswordMessage } = this.state;
|
||||||
|
|
||||||
|
switch (messagePass) {
|
||||||
case 'success':
|
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':
|
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:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -213,12 +246,14 @@ class DashboardEditProfilePage extends React.Component {
|
|||||||
}).then(function () {
|
}).then(function () {
|
||||||
this.setState({
|
this.setState({
|
||||||
loadingEmail: false,
|
loadingEmail: false,
|
||||||
messageEmail: "success"
|
messageEmail: "success",
|
||||||
|
showChangeEmailMessage: true
|
||||||
});
|
});
|
||||||
}.bind(this)).catch(function (){
|
}.bind(this)).catch(function (){
|
||||||
this.setState({
|
this.setState({
|
||||||
loadingEmail: false,
|
loadingEmail: false,
|
||||||
messageEmail: 'fail'
|
messageEmail: 'fail',
|
||||||
|
showChangeEmailMessage: true
|
||||||
})
|
})
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}
|
}
|
||||||
@ -237,12 +272,14 @@ class DashboardEditProfilePage extends React.Component {
|
|||||||
}).then(function () {
|
}).then(function () {
|
||||||
this.setState({
|
this.setState({
|
||||||
loadingPass: false,
|
loadingPass: false,
|
||||||
messagePass: "success"
|
messagePass: "success",
|
||||||
|
showChangePasswordMessage: true
|
||||||
});
|
});
|
||||||
}.bind(this)).catch(function (){
|
}.bind(this)).catch(function (){
|
||||||
this.setState({
|
this.setState({
|
||||||
loadingPass: false,
|
loadingPass: false,
|
||||||
messagePass: 'fail'
|
messagePass: 'fail',
|
||||||
|
showChangePasswordMessage: true
|
||||||
})
|
})
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}
|
}
|
||||||
@ -277,6 +314,12 @@ class DashboardEditProfilePage extends React.Component {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCloseMessage(showMessage) {
|
||||||
|
this.setState({
|
||||||
|
[showMessage]: false
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect((store) => {
|
export default connect((store) => {
|
||||||
|
@ -33,7 +33,8 @@ class DashboardListTicketsPage extends React.Component {
|
|||||||
ownTickets: true
|
ownTickets: true
|
||||||
},
|
},
|
||||||
message: '',
|
message: '',
|
||||||
loading: false
|
loading: false,
|
||||||
|
showMessage: true
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -42,16 +43,8 @@ class DashboardListTicketsPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { userUsers } = this.props;
|
||||||
userUsers
|
const { loading, page, pages, tickets, message, showMessage } = this.state;
|
||||||
} = this.props;
|
|
||||||
const {
|
|
||||||
loading,
|
|
||||||
page,
|
|
||||||
pages,
|
|
||||||
tickets,
|
|
||||||
message
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dashboard-ticket-list">
|
<div className="dashboard-ticket-list">
|
||||||
@ -63,8 +56,18 @@ class DashboardListTicketsPage extends React.Component {
|
|||||||
page={page}
|
page={page}
|
||||||
pages={pages}
|
pages={pages}
|
||||||
tickets={tickets}
|
tickets={tickets}
|
||||||
|
showPageSizeDropdown={false}
|
||||||
type={userUsers.length ? "secondary" : "primary"} />
|
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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -130,11 +133,18 @@ class DashboardListTicketsPage extends React.Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
tickets: [],
|
tickets: [],
|
||||||
message: r.message,
|
message: r.message,
|
||||||
|
showMessage: true,
|
||||||
loading: false
|
loading: false
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCloseMessage(showMessage) {
|
||||||
|
this.setState({
|
||||||
|
[showMessage]: false
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ class DashboardTicketPage extends React.Component {
|
|||||||
state = {
|
state = {
|
||||||
error: null,
|
error: null,
|
||||||
ticket: null,
|
ticket: null,
|
||||||
|
showErrorMessage: true
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -23,7 +24,7 @@ class DashboardTicketPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {ticket, error} = this.state;
|
const { ticket, error } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dashboard-ticket-page">
|
<div className="dashboard-ticket-page">
|
||||||
@ -33,11 +34,11 @@ class DashboardTicketPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderContent() {
|
renderContent() {
|
||||||
const {ticket, error} = this.state;
|
const { ticket, error, showErrorMessage } = this.state;
|
||||||
|
|
||||||
if(error) {
|
if(error) {
|
||||||
return (
|
return (
|
||||||
<Message type="error">
|
<Message showMessage={showErrorMessage} onCloseMessage={this.onCloseMessage.bind(this, "showErrorMessage")} type="error">
|
||||||
{i18n(error)}
|
{i18n(error)}
|
||||||
</Message>
|
</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() {
|
retrieveUserData() {
|
||||||
store.dispatch(SessionActions.getUserData());
|
store.dispatch(SessionActions.getUserData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCloseMessage(showMessage) {
|
||||||
|
this.setState({
|
||||||
|
[showMessage]: false
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DashboardTicketPage;
|
export default DashboardTicketPage;
|
||||||
|
@ -17,6 +17,14 @@ import FormField from 'core-components/form-field';
|
|||||||
import Widget from 'core-components/widget';
|
import Widget from 'core-components/widget';
|
||||||
import WidgetTransition from 'core-components/widget-transition';
|
import WidgetTransition from 'core-components/widget-transition';
|
||||||
import Message from 'core-components/message';
|
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 {
|
class MainHomePageLoginWidget extends React.Component {
|
||||||
|
|
||||||
@ -26,11 +34,17 @@ class MainHomePageLoginWidget extends React.Component {
|
|||||||
recoverFormErrors: {},
|
recoverFormErrors: {},
|
||||||
recoverSent: false,
|
recoverSent: false,
|
||||||
loadingLogin: false,
|
loadingLogin: false,
|
||||||
loadingRecover: false
|
loadingRecover: false,
|
||||||
|
reSendEMailVerificationLoading: false,
|
||||||
|
reSendEmailVerificationStep: UNVERIFIED_USER_STEP,
|
||||||
|
reSendEmailVerificationMessage: "",
|
||||||
|
showRecoverSentMessage: true,
|
||||||
|
showReSendEmailVerificationMessage: true
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (!prevProps.session.failed && this.props.session.failed) {
|
if (!prevProps.session.failed && this.props.session.failed) {
|
||||||
|
this.setState({showReSendEmailVerificationMessage : true});
|
||||||
this.refs.loginForm.refs.password.focus();
|
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 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"/>
|
<FormField name="remember" label={i18n('REMEMBER_ME')} className="login-widget__input" field="checkbox"/>
|
||||||
</div>
|
</div>
|
||||||
|
{this.props.session.loginAttempts > MAX_FREE_LOGIN_ATTEMPTS ? this.renderLoginCaptcha() : null}
|
||||||
<div className="login-widget__submit-button">
|
<div className="login-widget__submit-button">
|
||||||
<SubmitButton type="primary">{i18n('LOG_IN')}</SubmitButton>
|
<SubmitButton type="primary">{i18n('LOG_IN')}</SubmitButton>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
<Button className="login-widget__forgot-password" type="link" onClick={this.onForgotPasswordClick.bind(this)} onMouseDown={(event) => {event.preventDefault()}}>
|
<div className="main-home-page__link-buttons-container">
|
||||||
{i18n('FORGOT_PASSWORD')}
|
<Button className="login-widget__forgot-password" type="link" onClick={this.onForgotPasswordClick.bind(this)} onMouseDown={(event) => {event.preventDefault()}}>
|
||||||
</Button>
|
{i18n('FORGOT_PASSWORD')}
|
||||||
|
</Button>
|
||||||
|
{this.renderReSendEmailVerificationSection()}
|
||||||
|
</div>
|
||||||
</Widget>
|
</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() {
|
renderPasswordRecovery() {
|
||||||
return (
|
return (
|
||||||
<PasswordRecovery ref="passwordRecovery" recoverSent={this.state.recoverSent} formProps={this.getRecoverFormProps()} onBackToLoginClick={this.onBackToLoginClick.bind(this)}/>
|
<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() {
|
renderRecoverStatus() {
|
||||||
let status = null;
|
const { recoverSent, showRecoverSentMessage} = this.state;
|
||||||
|
|
||||||
if (this.state.recoverSent) {
|
return (
|
||||||
status = (
|
recoverSent ?
|
||||||
<Message className="login-widget__message" type="info" leftAligned>
|
<Message
|
||||||
{i18n('RECOVER_SENT')}
|
showMessage={showRecoverSentMessage}
|
||||||
</Message>
|
onCloseMessage={this.onCloseMessage.bind(this, "showRecoverSentMessage")}
|
||||||
);
|
className="login-widget__message"
|
||||||
}
|
type="info"
|
||||||
|
leftAligned>
|
||||||
return status;
|
{i18n('RECOVER_SENT')}
|
||||||
|
</Message> :
|
||||||
|
null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getLoginFormProps() {
|
getLoginFormProps() {
|
||||||
@ -108,7 +178,6 @@ class MainHomePageLoginWidget extends React.Component {
|
|||||||
|
|
||||||
getLoginFormErrors() {
|
getLoginFormErrors() {
|
||||||
let errors = _.extend({}, this.state.loginFormErrors);
|
let errors = _.extend({}, this.state.loginFormErrors);
|
||||||
|
|
||||||
if (this.props.session.failed) {
|
if (this.props.session.failed) {
|
||||||
if (this.props.session.failMessage === 'INVALID_CREDENTIALS') {
|
if (this.props.session.failMessage === 'INVALID_CREDENTIALS') {
|
||||||
errors.password = i18n('ERROR_PASSWORD');
|
errors.password = i18n('ERROR_PASSWORD');
|
||||||
@ -123,6 +192,10 @@ class MainHomePageLoginWidget extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onLoginFormSubmit(formState) {
|
onLoginFormSubmit(formState) {
|
||||||
|
this.setState({
|
||||||
|
reSendEmailVerificationStep: UNVERIFIED_USER_STEP,
|
||||||
|
email: formState.email
|
||||||
|
})
|
||||||
this.props.dispatch(SessionActions.login(formState));
|
this.props.dispatch(SessionActions.login(formState));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +239,8 @@ class MainHomePageLoginWidget extends React.Component {
|
|||||||
onRecoverPasswordSent() {
|
onRecoverPasswordSent() {
|
||||||
this.setState({
|
this.setState({
|
||||||
loadingRecover: false,
|
loadingRecover: false,
|
||||||
recoverSent: true
|
recoverSent: true,
|
||||||
|
showRecoverSentMessage: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,11 +252,41 @@ class MainHomePageLoginWidget extends React.Component {
|
|||||||
}
|
}
|
||||||
}, () => this.refs.passwordRecovery.focusEmail());
|
}, () => 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) => {
|
export default connect((store) => {
|
||||||
return {
|
return {
|
||||||
session: store.session
|
session: store.session,
|
||||||
|
sitekey: store.config.reCaptchaKey
|
||||||
};
|
};
|
||||||
})(MainHomePageLoginWidget);
|
})(MainHomePageLoginWidget);
|
@ -18,6 +18,24 @@
|
|||||||
&__message {
|
&__message {
|
||||||
margin-top: 18px;
|
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) {
|
@media screen and (min-width: 379px) {
|
||||||
|
@ -7,30 +7,45 @@ import Widget from 'core-components/widget';
|
|||||||
import Card from 'core-components/card';
|
import Card from 'core-components/card';
|
||||||
import i18n from 'lib-app/i18n';
|
import i18n from 'lib-app/i18n';
|
||||||
import Header from 'core-components/header';
|
import Header from 'core-components/header';
|
||||||
|
import queryString from 'query-string';
|
||||||
|
import Message from 'core-components/message';
|
||||||
|
|
||||||
class MainHomePagePortal extends React.Component {
|
class MainHomePagePortal extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
type: React.PropTypes.oneOf(['default', 'complete'])
|
type: React.PropTypes.oneOf(['default', 'complete'])
|
||||||
};
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
showMessage: !!queryString.parse(window.location.search)["message"]
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Widget className={classNames('main-home-page-portal', this.props.className)}>
|
<div className="main-home-page-portal__message">
|
||||||
<div className="main-home-page-portal__title">
|
<Message
|
||||||
<Header title={this.props.title || i18n('SUPPORT_CENTER')} description={i18n('SUPPORT_CENTER_DESCRIPTION')} />
|
showMessage={this.state.showMessage}
|
||||||
</div>
|
onCloseMessage={this.onCloseMessage.bind(this)}
|
||||||
<div className="main-home-page-portal__cards">
|
className="dashboard-create-ticket-page__message"
|
||||||
<div className="main-home-page-portal__card col-md-4">
|
type="success">
|
||||||
<Card {...this.getTicketsCardProps()}/>
|
{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>
|
||||||
<div className="main-home-page-portal__card col-md-4">
|
<div className="main-home-page-portal__cards">
|
||||||
<Card {...((this.props.type === 'complete') ? this.getViewTicketCardProps() : this.getAccountCardProps())} />
|
<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>
|
||||||
<div className="main-home-page-portal__card col-md-4">
|
</Widget>
|
||||||
<Card {...this.getArticlesCardProps()} />
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Widget>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,6 +90,12 @@ class MainHomePagePortal extends React.Component {
|
|||||||
onButtonClick: () => history.push('/check-ticket')
|
onButtonClick: () => history.push('/check-ticket')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCloseMessage() {
|
||||||
|
this.setState({
|
||||||
|
showMessage: false
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect((store) => {
|
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';
|
import Message from 'core-components/message';
|
||||||
|
|
||||||
class MainHomePage extends React.Component {
|
class MainHomePage extends React.Component {
|
||||||
|
|
||||||
|
state = {
|
||||||
|
showMessage: true
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (!prevProps.session && this.props.session) {
|
||||||
|
this.setState({showMessage : true});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { config, loginForm } = this.props;
|
||||||
config,
|
|
||||||
loginForm
|
|
||||||
} = this.props;
|
|
||||||
return (
|
return (
|
||||||
<div className="main-home-page row">
|
<div className="main-home-page row">
|
||||||
{this.renderMessage()}
|
{this.renderMessage()}
|
||||||
@ -50,16 +57,26 @@ class MainHomePage extends React.Component {
|
|||||||
|
|
||||||
renderSuccess() {
|
renderSuccess() {
|
||||||
return (
|
return (
|
||||||
<Message title={i18n('VERIFY_SUCCESS')} type="success" className="main-home-page__message">
|
<Message
|
||||||
{i18n('VERIFY_SUCCESS_DESCRIPTION')}
|
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>
|
</Message>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFailed() {
|
renderFailed() {
|
||||||
return (
|
return (
|
||||||
<Message title={i18n('VERIFY_FAILED')} type="error" className="main-home-page__message">
|
<Message
|
||||||
{i18n('VERIFY_FAILED_DESCRIPTION')}
|
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>
|
</Message>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -85,6 +102,12 @@ class MainHomePage extends React.Component {
|
|||||||
|
|
||||||
return classNames(classes);
|
return classNames(classes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCloseMessage() {
|
||||||
|
this.setState({
|
||||||
|
showMessage: false
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect((store) => {
|
export default connect((store) => {
|
||||||
|
@ -10,4 +10,10 @@
|
|||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__captcha {
|
||||||
|
margin: 10px auto 20px;
|
||||||
|
height: 78px;
|
||||||
|
width: 304px;
|
||||||
|
}
|
||||||
}
|
}
|
@ -20,8 +20,6 @@ class MainLayoutFooter extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div className="main-layout-footer__extra-links">
|
<div className="main-layout-footer__extra-links">
|
||||||
<a className="main-layout-footer__extra-link" href="http://www.opensupports.com/documentation/" target="_blank">Documentation</a>
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -13,12 +13,24 @@ class MainLayoutHeader extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="main-layout-header">
|
<div className="main-layout-header">
|
||||||
{this.renderAccessLinks()}
|
{this.renderHeaderOptions()}
|
||||||
<LanguageSelector {...this.getLanguageSelectorProps()} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderHeaderOptions(){
|
||||||
|
let result = null;
|
||||||
|
|
||||||
|
if(!this.props.config['maintenance-mode']){
|
||||||
|
result = (<div>
|
||||||
|
{this.renderAccessLinks()}
|
||||||
|
<LanguageSelector {...this.getLanguageSelectorProps()} />
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
renderAccessLinks() {
|
renderAccessLinks() {
|
||||||
const {
|
const {
|
||||||
session,
|
session,
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
min-height: 40px;
|
||||||
&__user-name {
|
&__user-name {
|
||||||
color: $primary-red;
|
color: $primary-red;
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,12 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: max-height 0.15s ease-out;
|
transition: max-height 0.15s ease-out;
|
||||||
|
|
||||||
|
.main-layout-header__languages > .drop-down__current-item {
|
||||||
|
min-height: unset;
|
||||||
|
}
|
||||||
|
|
||||||
&--content {
|
&--content {
|
||||||
min-height: 400px;
|
min-height: 453px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|
||||||
@media screen and (min-width: 993px) and (max-width: 1032px) {
|
@media screen and (min-width: 993px) and (max-width: 1032px) {
|
||||||
|
@ -23,7 +23,8 @@ class MainRecoverPasswordPage extends React.Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
recoverStatus: 'waiting',
|
recoverStatus: 'waiting',
|
||||||
loading: false
|
loading: false,
|
||||||
|
showMessage: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,11 +48,27 @@ class MainRecoverPasswordPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderRecoverStatus() {
|
renderRecoverStatus() {
|
||||||
switch (this.state.recoverStatus) {
|
const { recoverStatus, showMessage } = this.state;
|
||||||
|
|
||||||
|
switch (recoverStatus) {
|
||||||
case 'valid':
|
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':
|
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':
|
case 'waiting':
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -78,6 +95,7 @@ class MainRecoverPasswordPage extends React.Component {
|
|||||||
setTimeout(() => {history.push((response.data.staff*1) ? '/admin' : '/')}, 2000);
|
setTimeout(() => {history.push((response.data.staff*1) ? '/admin' : '/')}, 2000);
|
||||||
this.setState({
|
this.setState({
|
||||||
recoverStatus: 'valid',
|
recoverStatus: 'valid',
|
||||||
|
showMessage: true,
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -85,9 +103,16 @@ class MainRecoverPasswordPage extends React.Component {
|
|||||||
onPasswordRecoverFail() {
|
onPasswordRecoverFail() {
|
||||||
this.setState({
|
this.setState({
|
||||||
recoverStatus: 'invalid',
|
recoverStatus: 'invalid',
|
||||||
|
showMessage: true,
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCloseMessage(showMessage) {
|
||||||
|
this.setState({
|
||||||
|
[showMessage]: false
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MainRecoverPasswordPage;
|
export default MainRecoverPasswordPage;
|
||||||
|
@ -5,6 +5,7 @@ import classNames from 'classnames';
|
|||||||
import i18n from 'lib-app/i18n';
|
import i18n from 'lib-app/i18n';
|
||||||
import API from 'lib-app/api-call';
|
import API from 'lib-app/api-call';
|
||||||
import history from 'lib-app/history';
|
import history from 'lib-app/history';
|
||||||
|
import SessionStore from 'lib-app/session-store';
|
||||||
|
|
||||||
import Captcha from 'app/main/captcha';
|
import Captcha from 'app/main/captcha';
|
||||||
import SubmitButton from 'core-components/submit-button';
|
import SubmitButton from 'core-components/submit-button';
|
||||||
@ -26,16 +27,29 @@ class MainSignUpWidget extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
loading: false,
|
loading: false,
|
||||||
email: null,
|
email: null,
|
||||||
customFields: null
|
customFields: null,
|
||||||
|
showMessage: true,
|
||||||
|
message: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
API.call({
|
if(!SessionStore.getItem('customFields')) {
|
||||||
path: '/system/get-custom-fields',
|
API.call({
|
||||||
data: {}
|
path: '/system/get-custom-fields',
|
||||||
})
|
data: {}
|
||||||
.then(result => this.setState({customFields: result.data}));
|
})
|
||||||
|
.then(result => {
|
||||||
|
SessionStore.storeCustomField(result.data);
|
||||||
|
this.setState({
|
||||||
|
customFields: result.data
|
||||||
|
});
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
customFields: SessionStore.getCustomFields()
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -89,11 +103,27 @@ class MainSignUpWidget extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderMessage() {
|
renderMessage() {
|
||||||
switch (this.state.message) {
|
const { message, showMessage } = this.state;
|
||||||
|
|
||||||
|
switch (message) {
|
||||||
case 'success':
|
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':
|
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:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -153,16 +183,24 @@ class MainSignUpWidget extends React.Component {
|
|||||||
onSignupSuccess() {
|
onSignupSuccess() {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
message: 'success'
|
message: 'success',
|
||||||
|
showMessage: true
|
||||||
}, () => {
|
}, () => {
|
||||||
setTimeout(() => {history.push('/check-ticket')}, 2000);
|
setTimeout(() => {history.push('/')}, 2000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onSignupFail() {
|
onSignupFail() {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
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';
|
root = 'http://localhost:3000';
|
||||||
apiRoot = 'http://localhost:3000/api';
|
apiRoot = 'http://localhost:3000/api';
|
||||||
globalIndexPath = '';
|
globalIndexPath = '';
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
background-color: $very-light-grey;
|
background-color: $very-light-grey;
|
||||||
border: 1px solid $grey;
|
border: 1px solid $grey;
|
||||||
min-height: 38px;
|
min-height: 38px;
|
||||||
|
display: block;
|
||||||
|
|
||||||
&:focus-within {
|
&:focus-within {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
@ -12,6 +12,10 @@
|
|||||||
border-radius: 4px 4px 0 0;
|
border-radius: 4px 4px 0 0;
|
||||||
color: $primary-black;
|
color: $primary-black;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
|
min-height: 38px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
@ -39,10 +39,4 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&_select {
|
|
||||||
.form-field__label {
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,13 @@ class Form extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
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() {
|
render() {
|
||||||
@ -135,12 +141,11 @@ class Form extends React.Component {
|
|||||||
return newErrors;
|
return newErrors;
|
||||||
}
|
}
|
||||||
|
|
||||||
getInitialFormAndValidations() {
|
getInitialFormAndValidations(children) {
|
||||||
let form = {};
|
let form = {};
|
||||||
let validations = {};
|
let validations = {};
|
||||||
|
|
||||||
reactDFS(this.props.children, (child) => {
|
reactDFS(children, (child) => {
|
||||||
|
|
||||||
if (this.isValidField(child)) {
|
if (this.isValidField(child)) {
|
||||||
form[child.props.name] = child.props.value || FormField.getDefaultValue(child.props.field);
|
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,
|
title: React.PropTypes.string,
|
||||||
children: React.PropTypes.node,
|
children: React.PropTypes.node,
|
||||||
leftAligned: React.PropTypes.bool,
|
leftAligned: React.PropTypes.bool,
|
||||||
|
onCloseMessage: React.PropTypes.func,
|
||||||
type: React.PropTypes.oneOf(['success', 'error', 'info', 'warning'])
|
type: React.PropTypes.oneOf(['success', 'error', 'info', 'warning'])
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
type: 'info',
|
type: 'info',
|
||||||
leftAligned: false
|
leftAligned: false,
|
||||||
|
showCloseButton: true,
|
||||||
|
showMessage: true
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Motion {...this.getAnimationProps()}>
|
this.props.showMessage ?
|
||||||
{this.renderMessage.bind(this)}
|
<Motion {...this.getAnimationProps()}>
|
||||||
</Motion>
|
{this.renderMessage.bind(this)}
|
||||||
|
</Motion> :
|
||||||
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,26 +43,35 @@ class Message extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderMessage(style) {
|
renderMessage(style) {
|
||||||
|
return this.renderMessageContent(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMessageContent(style) {
|
||||||
|
const { children, title, showCloseButton } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={this.getClass()} style={style} aria-live="assertive">
|
<div className={this.getClass()} style={style} aria-live="assertive">
|
||||||
<Icon className="message__icon" name={this.getIconName()} size={this.getIconSize()} />
|
<Icon className="message__icon" name={this.getIconName()} size={this.getIconSize()} />
|
||||||
<div className="message__title">{this.props.title}</div>
|
<div className="message__title">{title}</div>
|
||||||
<div className="message__content">{this.props.children}</div>
|
<div className="message__content">{children}</div>
|
||||||
|
{showCloseButton ? this.renderCloseButton() : null}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
getClass() {
|
getClass() {
|
||||||
|
const { type, title, leftAligned, className } = this.props
|
||||||
|
|
||||||
let classes = {
|
let classes = {
|
||||||
'message': true,
|
'message': true,
|
||||||
'message_success': (this.props.type === 'success'),
|
'message_success': (type === 'success'),
|
||||||
'message_error': (this.props.type === 'error'),
|
'message_error': (type === 'error'),
|
||||||
'message_info': (this.props.type === 'info'),
|
'message_info': (type === 'info'),
|
||||||
'message_warning': (this.props.type === 'warning'),
|
'message_warning': (type === 'warning'),
|
||||||
'message_with-title': (this.props.title),
|
'message_with-title': title,
|
||||||
'message_left-aligned': (this.props.leftAligned),
|
'message_left-aligned': leftAligned,
|
||||||
|
|
||||||
[this.props.className]: (this.props.className)
|
[className]: className
|
||||||
};
|
};
|
||||||
|
|
||||||
return classNames(classes);
|
return classNames(classes);
|
||||||
@ -77,6 +91,20 @@ class Message extends React.Component {
|
|||||||
getIconSize() {
|
getIconSize() {
|
||||||
return (this.props.title) ? '2x' : 'lg';
|
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;
|
export default Message;
|
||||||
|
@ -111,4 +111,11 @@
|
|||||||
padding-left: 28px;
|
padding-left: 28px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__close-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,9 @@
|
|||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
|
@ -18,7 +18,7 @@ class TagSelector extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const items = this.props.items.map(tag => ({...tag, content: this.renderTagOption(tag)}));
|
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 (
|
return (
|
||||||
<div className="tag-selector">
|
<div className="tag-selector">
|
||||||
|
@ -2,11 +2,12 @@ import React from 'react';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import ReactQuill, { Quill } from 'react-quill';
|
import ReactQuill, { Quill } from 'react-quill';
|
||||||
import ImageResize from 'quill-image-resize-module-react';
|
import ImageResize from 'quill-image-resize-module-react';
|
||||||
|
import MagicUrl from 'quill-magic-url';
|
||||||
import {isIE} from 'lib-core/navigator';
|
import {isIE} from 'lib-core/navigator';
|
||||||
import Base64ImageParser from 'lib-core/base64-image-parser';
|
import Base64ImageParser from 'lib-core/base64-image-parser';
|
||||||
|
|
||||||
Quill.register('modules/ImageResize', ImageResize);
|
Quill.register('modules/ImageResize', ImageResize);
|
||||||
|
Quill.register('modules/magicUrl', MagicUrl)
|
||||||
|
|
||||||
class TextEditor extends React.Component {
|
class TextEditor extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -113,6 +114,7 @@ class TextEditor extends React.Component {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
ImageResize: {parchment: Quill.import('parchment')},
|
ImageResize: {parchment: Quill.import('parchment')},
|
||||||
|
magicUrl: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
&--widget {
|
&--widget {
|
||||||
-webkit-perspective: 0;
|
|
||||||
-webkit-backface-visibility: hidden;
|
-webkit-backface-visibility: hidden;
|
||||||
-webkit-transform: translate3d(0,0,0);
|
-webkit-transform: translate3d(0,0,0);
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
|
@ -102,6 +102,7 @@ export default {
|
|||||||
'CHANGE_EMAIL': 'Mudar o e-mail',
|
'CHANGE_EMAIL': 'Mudar o e-mail',
|
||||||
'CHANGE_PASSWORD': 'Mudar a senha',
|
'CHANGE_PASSWORD': 'Mudar a senha',
|
||||||
'NAME': 'Nome',
|
'NAME': 'Nome',
|
||||||
|
'APPLY': 'Aplicar',
|
||||||
'SIGNUP_DATE': 'Data de inscrição',
|
'SIGNUP_DATE': 'Data de inscrição',
|
||||||
'CLEAR': 'Claro',
|
'CLEAR': 'Claro',
|
||||||
'SEARCH': 'Procurar',
|
'SEARCH': 'Procurar',
|
||||||
@ -121,6 +122,7 @@ export default {
|
|||||||
'NO_RESULTS': 'Nenhum resultado',
|
'NO_RESULTS': 'Nenhum resultado',
|
||||||
'DELETE_AND_BAN': 'Excluir e banir',
|
'DELETE_AND_BAN': 'Excluir e banir',
|
||||||
'STAFF_LEVEL': 'Nível Pessoal',
|
'STAFF_LEVEL': 'Nível Pessoal',
|
||||||
|
'TICKETS_ASSIGNED': 'Ingressos atribuídos',
|
||||||
'ASSIGNED': 'Atribuído',
|
'ASSIGNED': 'Atribuído',
|
||||||
'ASSIGNED_TICKETS': '{tickets} chamados atribuídos',
|
'ASSIGNED_TICKETS': '{tickets} chamados atribuídos',
|
||||||
'CLOSED_TICKETS': '{tickets} chamados fechados',
|
'CLOSED_TICKETS': '{tickets} chamados fechados',
|
||||||
@ -133,9 +135,10 @@ export default {
|
|||||||
'LEVEL_3': 'Nível 3 (Chamados + Artigos + Equipe)',
|
'LEVEL_3': 'Nível 3 (Chamados + Artigos + Equipe)',
|
||||||
'LEVEL_1_DESCRIPTION': 'Só pode responder aos chamados e gerenciar os usuários',
|
'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_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_EMAIL': 'Atualizar e-mail',
|
||||||
'UPDATE_PASSWORD': 'Atualizar senha',
|
'UPDATE_PASSWORD': 'Atualizar senha',
|
||||||
|
'UPDATE_CUSTOM_FIELDS': 'Atualize campos personalizados',
|
||||||
'UPDATE_LEVEL': 'Nível de atualização',
|
'UPDATE_LEVEL': 'Nível de atualização',
|
||||||
'UPDATE_DEPARTMENTS': 'Atualizar departamentos',
|
'UPDATE_DEPARTMENTS': 'Atualizar departamentos',
|
||||||
'EDIT_STAFF': 'Editar membro da equipe',
|
'EDIT_STAFF': 'Editar membro da equipe',
|
||||||
@ -189,7 +192,7 @@ export default {
|
|||||||
'BACKUP_DATABASE': 'Backup do banco de dados',
|
'BACKUP_DATABASE': 'Backup do banco de dados',
|
||||||
'DELETE_ALL_USERS': 'Excluir todos os usuários',
|
'DELETE_ALL_USERS': 'Excluir todos os usuários',
|
||||||
'PLEASE_CONFIRM_PASSWORD': 'Confirme sua senha para fazer essas alterações',
|
'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',
|
'NAME_OF_KEY': 'Nome da chave',
|
||||||
'KEY': 'Chave',
|
'KEY': 'Chave',
|
||||||
'ADD_API_KEY': 'Adicionar chave de API',
|
'ADD_API_KEY': 'Adicionar chave de API',
|
||||||
@ -228,11 +231,22 @@ export default {
|
|||||||
'OPTIONS': 'Opções',
|
'OPTIONS': 'Opções',
|
||||||
'FIELD_DESCRIPTION': 'Descrição do campo (opcional)',
|
'FIELD_DESCRIPTION': 'Descrição do campo (opcional)',
|
||||||
'DESCRIPTION_ADD_CUSTOM_TAG': 'aqui você pode adicionar uma nova tag personalizada',
|
'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',
|
'DESCRIPTION_EDIT_CUSTOM_TAG': 'aqui você pode editar uma marca personalizada',
|
||||||
'CUSTOM_FIELDS': 'Os campos personalizados',
|
'CUSTOM_FIELDS': 'Os campos personalizados',
|
||||||
|
|
||||||
'CHART_CREATE_TICKET': 'Chamados criados',
|
'CHART_CREATE_TICKET': 'Chamados criados',
|
||||||
'CHART_CLOSE': 'Chamados fechados',
|
'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_SIGNUP': 'Inscrições',
|
||||||
'CHART_COMMENT': 'Respondidos',
|
'CHART_COMMENT': 'Respondidos',
|
||||||
'CHART_ASSIGN': 'Atribuídos',
|
'CHART_ASSIGN': 'Atribuídos',
|
||||||
@ -284,6 +298,7 @@ export default {
|
|||||||
'DATABASE_NAME': 'Nome do banco de dados MySQL',
|
'DATABASE_NAME': 'Nome do banco de dados MySQL',
|
||||||
'DATABASE_USER': 'Usuário do MySQL',
|
'DATABASE_USER': 'Usuário do MySQL',
|
||||||
'DATABASE_PASSWORD': 'Senha do MySQL',
|
'DATABASE_PASSWORD': 'Senha do MySQL',
|
||||||
|
'INSTALLATION_COMPLETED_TITLE': 'Instalação completa',
|
||||||
'ADMIN_NAME': 'Nome da conta de administrador',
|
'ADMIN_NAME': 'Nome da conta de administrador',
|
||||||
'ADMIN_EMAIL': 'E-mail da conta de administrador',
|
'ADMIN_EMAIL': 'E-mail da conta de administrador',
|
||||||
'ADMIN_PASSWORD': 'Senha 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.',
|
'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',
|
'ERROR_NAME': 'Nome inválido',
|
||||||
'INVITE_STAFF_DESCRIPTION': 'Aqui você pode convidar membros do pessoal para suas equipes.',
|
'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_TITLE': 'Título inválido',
|
||||||
'ERROR_EMAIL': 'E-mail inválido',
|
'ERROR_EMAIL': 'E-mail inválido',
|
||||||
'ERROR_CONTENT_SHORT': 'Conteúdo muito curto',
|
'ERROR_CONTENT_SHORT': 'Conteúdo muito curto',
|
||||||
@ -393,6 +409,7 @@ export default {
|
|||||||
'ERRORS_FOUND': 'Erros encontrados',
|
'ERRORS_FOUND': 'Erros encontrados',
|
||||||
'ERROR_IMAGE_SIZE': 'Nenhuma imagem pode ter um tamanho maior que {size} MB',
|
'ERROR_IMAGE_SIZE': 'Nenhuma imagem pode ter um tamanho maior que {size} MB',
|
||||||
'USER_DISABLED': 'Esta conta está desativada.',
|
'USER_DISABLED': 'Esta conta está desativada.',
|
||||||
|
'NAME_ALREADY_USED': 'Nome já usado',
|
||||||
'INVALID_SYNTAX': 'Sintaxe inválida.',
|
'INVALID_SYNTAX': 'Sintaxe inválida.',
|
||||||
'DEPARTMENT_PRIVATE_TICKETS': 'Este departamento tem chamados criados por não funcionários e não pode ser privado',
|
'DEPARTMENT_PRIVATE_TICKETS': 'Este departamento tem chamados criados por não funcionários e não pode ser privado',
|
||||||
'CURRENTLY_UNAVAILABLE': 'Atualmente indisponivel',
|
'CURRENTLY_UNAVAILABLE': 'Atualmente indisponivel',
|
||||||
@ -404,10 +421,12 @@ export default {
|
|||||||
'INVALID_SUPERVISED_USERS': 'usuários supervisionadas inválidos',
|
'INVALID_SUPERVISED_USERS': 'usuários supervisionadas inválidos',
|
||||||
'INVALID_DEFAULT_DEPARTMENT': 'escolhido departamento padrão é inválido',
|
'INVALID_DEFAULT_DEPARTMENT': 'escolhido departamento padrão é inválido',
|
||||||
'TICKET_SENT': 'O chamado foi aberto com sucesso.',
|
'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',
|
'SUPERVISOR_CAN_NOT_SUPERVISE_HIMSELF': 'Supervisor não pode fiscalizar a si mesmo',
|
||||||
'VALID_RECOVER': 'Senha recuperada com êxito',
|
'VALID_RECOVER': 'Senha recuperada com êxito',
|
||||||
'EMAIL_EXISTS': 'e-mail já existe',
|
'EMAIL_EXISTS': 'e-mail já existe',
|
||||||
'INVITE_USER_SUCCESS': 'Você convidou um novo usuário com sucesso em nosso sistema de suporte',
|
'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?',
|
'ARE_YOU_SURE': 'Você tem certeza?',
|
||||||
'EMAIL_WILL_CHANGE': 'O e-mail atual será alterado',
|
'EMAIL_WILL_CHANGE': 'O e-mail atual será alterado',
|
||||||
'PASSWORD_WILL_CHANGE': 'A senha atual será alterada',
|
'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_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.',
|
'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',
|
'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',
|
'SUCCESS_DELETING_ALL_USERS': 'Os usuários foram excluídos com êxito',
|
||||||
'SUCCESSFUL_CONNECTION': 'Conexão bem sucedida',
|
'SUCCESSFUL_CONNECTION': 'Conexão bem sucedida',
|
||||||
'UNSUCCESSFUL_CONNECTION': 'Conexão sem sucesso',
|
'UNSUCCESSFUL_CONNECTION': 'Conexão sem sucesso',
|
||||||
@ -456,5 +477,24 @@ export default {
|
|||||||
'TEST_SMTP_CONNECTION': 'Testar conexão SMTP',
|
'TEST_SMTP_CONNECTION': 'Testar conexão SMTP',
|
||||||
'SERVER_ERROR': 'Não é possível se conectar ao servidor.',
|
'SERVER_ERROR': 'Não é possível se conectar ao servidor.',
|
||||||
'EMAIL_SERVER_ADDRESS': 'Endereço do servidor de email',
|
'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_EMAIL': '更改電子郵件',
|
||||||
'CHANGE_PASSWORD': '更改密碼',
|
'CHANGE_PASSWORD': '更改密碼',
|
||||||
'NAME': '名稱',
|
'NAME': '名稱',
|
||||||
|
'APPLY': '申请',
|
||||||
'SIGNUP_DATE': '註冊日期',
|
'SIGNUP_DATE': '註冊日期',
|
||||||
'CLEAR': '明确',
|
'CLEAR': '明确',
|
||||||
'SEARCH': '搜索',
|
'SEARCH': '搜索',
|
||||||
@ -121,6 +122,7 @@ export default {
|
|||||||
'NO_RESULTS': '沒有結果',
|
'NO_RESULTS': '沒有結果',
|
||||||
'DELETE_AND_BAN': '刪除和禁止',
|
'DELETE_AND_BAN': '刪除和禁止',
|
||||||
'STAFF_LEVEL': '員工級別',
|
'STAFF_LEVEL': '員工級別',
|
||||||
|
'TICKETS_ASSIGNED': '分配的门票',
|
||||||
'ASSIGNED': '分配',
|
'ASSIGNED': '分配',
|
||||||
'ASSIGNED_TICKETS': '{ticket}分配的票',
|
'ASSIGNED_TICKETS': '{ticket}分配的票',
|
||||||
'CLOSED_TICKETS': '{ticket}已關閉的門票',
|
'CLOSED_TICKETS': '{ticket}已關閉的門票',
|
||||||
@ -136,6 +138,7 @@ export default {
|
|||||||
'LEVEL_3_DESCRIPTION': '可以做每一級2,可以創建或編輯員工,並可以管理整個系統。',
|
'LEVEL_3_DESCRIPTION': '可以做每一級2,可以創建或編輯員工,並可以管理整個系統。',
|
||||||
'UPDATE_EMAIL': '更新電子郵件',
|
'UPDATE_EMAIL': '更新電子郵件',
|
||||||
'UPDATE_PASSWORD': '更新密碼',
|
'UPDATE_PASSWORD': '更新密碼',
|
||||||
|
'UPDATE_CUSTOM_FIELDS': '更新自定义字段',
|
||||||
'UPDATE_LEVEL': '更新級別',
|
'UPDATE_LEVEL': '更新級別',
|
||||||
'UPDATE_DEPARTMENTS': '更新部門',
|
'UPDATE_DEPARTMENTS': '更新部門',
|
||||||
'EDIT_STAFF': '編輯員工',
|
'EDIT_STAFF': '編輯員工',
|
||||||
@ -189,7 +192,6 @@ export default {
|
|||||||
'BACKUP_DATABASE': '備份數據庫',
|
'BACKUP_DATABASE': '備份數據庫',
|
||||||
'DELETE_ALL_USERS': '刪除所有用戶',
|
'DELETE_ALL_USERS': '刪除所有用戶',
|
||||||
'PLEASE_CONFIRM_PASSWORD': '請確認您的密碼進行更改',
|
'PLEASE_CONFIRM_PASSWORD': '請確認您的密碼進行更改',
|
||||||
'REGISTRATION_API_KEYS': '註冊API密鑰',
|
|
||||||
'NAME_OF_KEY': '密鑰名稱',
|
'NAME_OF_KEY': '密鑰名稱',
|
||||||
'KEY': '鍵',
|
'KEY': '鍵',
|
||||||
'ADD_API_KEY': '添加API密鑰',
|
'ADD_API_KEY': '添加API密鑰',
|
||||||
@ -229,11 +231,22 @@ export default {
|
|||||||
'FIELD_DESCRIPTION': '字段描述(可选)',
|
'FIELD_DESCRIPTION': '字段描述(可选)',
|
||||||
'DESCRIPTION_ADD_CUSTOM_TAG': '在这里,您可以添加新的自定义标记',
|
'DESCRIPTION_ADD_CUSTOM_TAG': '在这里,您可以添加新的自定义标记',
|
||||||
'DESCRIPTION_EDIT_CUSTOM_TAG': '在这里你可以编辑自定义标签',
|
'DESCRIPTION_EDIT_CUSTOM_TAG': '在这里你可以编辑自定义标签',
|
||||||
|
'PERMISSIONS': '权限',
|
||||||
|
'TICKET_CREATION_PERMISSION': '允许票务创造',
|
||||||
|
'TICKET_CHECK_PERMISSION': '允许票务检查',
|
||||||
|
'TICKET_NUMBER_RETURN_PERMISSION': '允许票号返回',
|
||||||
|
'USER_CREATION_PERMISSION': '允许用户创建',
|
||||||
'CUSTOM_FIELDS': '自定义字段',
|
'CUSTOM_FIELDS': '自定义字段',
|
||||||
|
|
||||||
'CHART_CREATE_TICKET': '已創建門票',
|
'CHART_CREATE_TICKET': '已創建門票',
|
||||||
'CHART_CLOSE': '門票已關閉',
|
'CHART_CLOSE': '門票已關閉',
|
||||||
'CHART_SIGNUP': '註冊',
|
'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_COMMENT': '回复',
|
||||||
'CHART_ASSIGN': '分配',
|
'CHART_ASSIGN': '分配',
|
||||||
|
|
||||||
@ -285,6 +298,7 @@ export default {
|
|||||||
'DATABASE_USER': 'MySQL用戶',
|
'DATABASE_USER': 'MySQL用戶',
|
||||||
'DATABASE_PASSWORD': 'MySQL密碼',
|
'DATABASE_PASSWORD': 'MySQL密碼',
|
||||||
'ADMIN_NAME': '管理員帳戶名稱',
|
'ADMIN_NAME': '管理員帳戶名稱',
|
||||||
|
'INSTALLATION_COMPLETED_TITLE': '安装完成',
|
||||||
'ADMIN_EMAIL': '管理帳戶電子郵件',
|
'ADMIN_EMAIL': '管理帳戶電子郵件',
|
||||||
'ADMIN_PASSWORD': '管理員帳號密碼',
|
'ADMIN_PASSWORD': '管理員帳號密碼',
|
||||||
'ADMIN_PASSWORD_DESCRIPTION': '請記住這個密碼。需要訪問管理面板。您可以稍後更改。',
|
'ADMIN_PASSWORD_DESCRIPTION': '請記住這個密碼。需要訪問管理面板。您可以稍後更改。',
|
||||||
@ -395,6 +409,7 @@ export default {
|
|||||||
'USER_DISABLED': '此帐户已被停用。',
|
'USER_DISABLED': '此帐户已被停用。',
|
||||||
'INVALID_SYNTAX': '无效的语法。',
|
'INVALID_SYNTAX': '无效的语法。',
|
||||||
'DEPARTMENT_PRIVATE_TICKETS': '这个部门有非工作人员创建的门票,不能是私人的',
|
'DEPARTMENT_PRIVATE_TICKETS': '这个部门有非工作人员创建的门票,不能是私人的',
|
||||||
|
'NAME_ALREADY_USED': '已使用的名称',
|
||||||
'CURRENTLY_UNAVAILABLE': '当前不可用',
|
'CURRENTLY_UNAVAILABLE': '当前不可用',
|
||||||
'CAN_NOT_DELETE_DEFAULT_DEPARTMENT': '默认部门不能被删除',
|
'CAN_NOT_DELETE_DEFAULT_DEPARTMENT': '默认部门不能被删除',
|
||||||
|
|
||||||
@ -411,6 +426,7 @@ export default {
|
|||||||
'ARE_YOU_SURE': '你確定?',
|
'ARE_YOU_SURE': '你確定?',
|
||||||
'EMAIL_WILL_CHANGE': '當前的電子郵件將被更改',
|
'EMAIL_WILL_CHANGE': '當前的電子郵件將被更改',
|
||||||
'PASSWORD_WILL_CHANGE': '當前密碼將被更改',
|
'PASSWORD_WILL_CHANGE': '當前密碼將被更改',
|
||||||
|
'WILL_DELETE_CUSTOM_TAG': '自定义标记将被删除。',
|
||||||
'EMAIL_CHANGED': '電子郵件已成功更改',
|
'EMAIL_CHANGED': '電子郵件已成功更改',
|
||||||
'PASSWORD_CHANGED': '密碼已成功更改',
|
'PASSWORD_CHANGED': '密碼已成功更改',
|
||||||
'OLD_PASSWORD_INCORRECT': '舊密碼不正確',
|
'OLD_PASSWORD_INCORRECT': '舊密碼不正確',
|
||||||
@ -429,6 +445,8 @@ export default {
|
|||||||
'SUCCESS_DELETING_ALL_USERS': '用戶已成功刪除',
|
'SUCCESS_DELETING_ALL_USERS': '用戶已成功刪除',
|
||||||
'SUCCESSFUL_CONNECTION': '成功连接',
|
'SUCCESSFUL_CONNECTION': '成功连接',
|
||||||
'UNSUCCESSFUL_CONNECTION': '连接不成功',
|
'UNSUCCESSFUL_CONNECTION': '连接不成功',
|
||||||
|
'TODAY_AT': '今天',
|
||||||
|
'YESTERDAY_AT': '昨天',
|
||||||
'SERVER_CREDENTIALS_WORKING': '服务器凭据正常工作',
|
'SERVER_CREDENTIALS_WORKING': '服务器凭据正常工作',
|
||||||
'DELETE_CUSTOM_FIELD_SURE': '某些用户可能正在使用此字段。你确定你要删除吗?',
|
'DELETE_CUSTOM_FIELD_SURE': '某些用户可能正在使用此字段。你确定你要删除吗?',
|
||||||
|
|
||||||
@ -456,5 +474,25 @@ export default {
|
|||||||
'TEST_SMTP_CONNECTION': '测试SMTP连接',
|
'TEST_SMTP_CONNECTION': '测试SMTP连接',
|
||||||
'SERVER_ERROR': '无法连接到服务器。',
|
'SERVER_ERROR': '无法连接到服务器。',
|
||||||
'EMAIL_SERVER_ADDRESS': '电邮服务器地址',
|
'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',
|
'DEFAULT': 'Standard',
|
||||||
'PASSWORD': 'Passwort',
|
'PASSWORD': 'Passwort',
|
||||||
'REPEAT_PASSWORD': 'Passwort wiederholen',
|
'REPEAT_PASSWORD': 'Passwort wiederholen',
|
||||||
'LOG_IN': 'Einloggen',
|
'LOG_IN': 'Anmelden',
|
||||||
'SIGN_UP': 'Anmelden',
|
'SIGN_UP': 'Registrieren',
|
||||||
'FORGOT_PASSWORD': 'Passwort vergessen',
|
'FORGOT_PASSWORD': 'Passwort vergessen',
|
||||||
'RECOVER_PASSWORD': 'Passwort wiederherstellen',
|
'RECOVER_PASSWORD': 'Passwort wiederherstellen',
|
||||||
'SET_UP_PASSWORD': 'Richten Sie Ihr Passwort',
|
'SET_UP_PASSWORD': 'Richten Sie Ihr Passwort',
|
||||||
@ -102,6 +102,7 @@ export default {
|
|||||||
'CHANGE_EMAIL': 'E-Mail ändern',
|
'CHANGE_EMAIL': 'E-Mail ändern',
|
||||||
'CHANGE_PASSWORD': 'Passwort ändern',
|
'CHANGE_PASSWORD': 'Passwort ändern',
|
||||||
'NAME': 'Name',
|
'NAME': 'Name',
|
||||||
|
'APPLY': 'Sich bewerben',
|
||||||
'SIGNUP_DATE': 'Anmeldedatum',
|
'SIGNUP_DATE': 'Anmeldedatum',
|
||||||
'CLEAR': 'klar',
|
'CLEAR': 'klar',
|
||||||
'SEARCH': 'Suche',
|
'SEARCH': 'Suche',
|
||||||
@ -121,6 +122,7 @@ export default {
|
|||||||
'NO_RESULTS': 'Keine Ergebnisse',
|
'NO_RESULTS': 'Keine Ergebnisse',
|
||||||
'DELETE_AND_BAN': 'Löschen und blockieren',
|
'DELETE_AND_BAN': 'Löschen und blockieren',
|
||||||
'STAFF_LEVEL': 'Mitarbeiter-Ebene',
|
'STAFF_LEVEL': 'Mitarbeiter-Ebene',
|
||||||
|
'TICKETS_ASSIGNED': 'Tickets zugewiesen',
|
||||||
'ASSIGNED': 'Zugewiesen',
|
'ASSIGNED': 'Zugewiesen',
|
||||||
'ASSIGNED_TICKETS': '{tickets} zugeordnete Tickets',
|
'ASSIGNED_TICKETS': '{tickets} zugeordnete Tickets',
|
||||||
'CLOSED_TICKETS': '{tickets} geschlossene 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.',
|
'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_EMAIL': 'E-Mail aktualisieren',
|
||||||
'UPDATE_PASSWORD': 'Kennwort aktualisieren',
|
'UPDATE_PASSWORD': 'Kennwort aktualisieren',
|
||||||
|
'UPDATE_CUSTOM_FIELDS': 'Benutzerdefinierte Felder aktualisieren.',
|
||||||
'UPDATE_LEVEL': 'Stufe aktualisieren',
|
'UPDATE_LEVEL': 'Stufe aktualisieren',
|
||||||
'UPDATE_DEPARTMENTS': 'Abteilungen aktualisieren',
|
'UPDATE_DEPARTMENTS': 'Abteilungen aktualisieren',
|
||||||
'EDIT_STAFF': 'Mitarbeiter bearbeiten',
|
'EDIT_STAFF': 'Mitarbeiter bearbeiten',
|
||||||
@ -189,7 +192,6 @@ export default {
|
|||||||
'BACKUP_DATABASE': 'Sicherungsdatenbank',
|
'BACKUP_DATABASE': 'Sicherungsdatenbank',
|
||||||
'DELETE_ALL_USERS': 'Alle Benutzer löschen',
|
'DELETE_ALL_USERS': 'Alle Benutzer löschen',
|
||||||
'PLEASE_CONFIRM_PASSWORD': 'Bitte bestätigen Sie Ihr Passwort, um diese Änderungen vorzunehmen!',
|
'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',
|
'NAME_OF_KEY': 'Name des Schlüssels',
|
||||||
'KEY': 'Schlüssel',
|
'KEY': 'Schlüssel',
|
||||||
'ADD_API_KEY': 'API-Schlüssel hinzufügen',
|
'ADD_API_KEY': 'API-Schlüssel hinzufügen',
|
||||||
@ -207,10 +209,10 @@ export default {
|
|||||||
'HIMSELF': 'selbst',
|
'HIMSELF': 'selbst',
|
||||||
'SUPERVISED_USERS_UPDATED': 'Betreute Nutzer aktualisiert',
|
'SUPERVISED_USERS_UPDATED': 'Betreute Nutzer aktualisiert',
|
||||||
'ADD_USER': 'Benutzer hinzufügen',
|
'ADD_USER': 'Benutzer hinzufügen',
|
||||||
'INVITE_USER': 'laden Sie Benutzer',
|
'INVITE_USER': 'Benutzer einladen',
|
||||||
'INVITE_STAFF': 'einladen Personal',
|
'INVITE_STAFF': 'Personal einladen',
|
||||||
'UPLOAD_FILE': 'Datei hochladen',
|
'UPLOAD_FILE': 'Datei hochladen',
|
||||||
'PRIVATE': 'Privatgelände',
|
'PRIVATE': 'Privat',
|
||||||
'ENABLE_USER': 'Benutzer aktivieren',
|
'ENABLE_USER': 'Benutzer aktivieren',
|
||||||
'DISABLE_USER': 'Benutzer deaktivieren',
|
'DISABLE_USER': 'Benutzer deaktivieren',
|
||||||
'SHOW_CLOSED_TICKETS': 'Geschlossene Tickets anzeigen',
|
'SHOW_CLOSED_TICKETS': 'Geschlossene Tickets anzeigen',
|
||||||
@ -229,11 +231,22 @@ export default {
|
|||||||
'FIELD_DESCRIPTION': 'Feldbeschreibung (optional)',
|
'FIELD_DESCRIPTION': 'Feldbeschreibung (optional)',
|
||||||
'DESCRIPTION_ADD_CUSTOM_TAG': 'Hier können Sie ein neues benutzerdefiniertes Tag hinzufügen',
|
'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',
|
'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',
|
'CUSTOM_FIELDS': 'Benutzerdefinierte Felder',
|
||||||
|
|
||||||
'CHART_CREATE_TICKET': 'Tickets erstellt',
|
'CHART_CREATE_TICKET': 'Tickets erstellt',
|
||||||
'CHART_CLOSE': 'Tickets geschlossen',
|
'CHART_CLOSE': 'Tickets geschlossen',
|
||||||
'CHART_SIGNUP': 'Anmeldungen',
|
'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_COMMENT': 'Antworten',
|
||||||
'CHART_ASSIGN': 'Tickets zugewiesen',
|
'CHART_ASSIGN': 'Tickets zugewiesen',
|
||||||
|
|
||||||
@ -285,6 +298,7 @@ export default {
|
|||||||
'DATABASE_USER': 'MySQL Benutzer',
|
'DATABASE_USER': 'MySQL Benutzer',
|
||||||
'DATABASE_PASSWORD': 'MySQL Passwort',
|
'DATABASE_PASSWORD': 'MySQL Passwort',
|
||||||
'ADMIN_NAME': 'Admin Kontoname',
|
'ADMIN_NAME': 'Admin Kontoname',
|
||||||
|
'INSTALLATION_COMPLETED_TITLE': 'Installation abgeschlossen',
|
||||||
'ADMIN_EMAIL': 'Admin-Konto E-Mail',
|
'ADMIN_EMAIL': 'Admin-Konto E-Mail',
|
||||||
'ADMIN_PASSWORD': 'Admin-Konto Passwort',
|
'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.',
|
'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.',
|
'USER_DISABLED': 'Dieser Account ist deaktiviert.',
|
||||||
'INVALID_SYNTAX': 'Ungültiger Satzbau.',
|
'INVALID_SYNTAX': 'Ungültiger Satzbau.',
|
||||||
'DEPARTMENT_PRIVATE_TICKETS': 'Diese Abteilung hat Tickets, die von Nicht-Mitarbeitern erstellt wurden, und kann nicht privat sein',
|
'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',
|
'CURRENTLY_UNAVAILABLE': 'momentan nicht verfügbar',
|
||||||
'CAN_NOT_DELETE_DEFAULT_DEPARTMENT': 'Standard-Abteilung kann nicht gelöscht werden',
|
'CAN_NOT_DELETE_DEFAULT_DEPARTMENT': 'Standard-Abteilung kann nicht gelöscht werden',
|
||||||
|
|
||||||
@ -411,6 +426,7 @@ export default {
|
|||||||
'ARE_YOU_SURE': 'Sind Sie sicher?',
|
'ARE_YOU_SURE': 'Sind Sie sicher?',
|
||||||
'EMAIL_WILL_CHANGE': 'Die aktuelle E-Mail-Adresse wird geändert.',
|
'EMAIL_WILL_CHANGE': 'Die aktuelle E-Mail-Adresse wird geändert.',
|
||||||
'PASSWORD_WILL_CHANGE': 'Das aktuelle Passwort 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.',
|
'EMAIL_CHANGED': 'Die E-Mail-Adresse wurde erfolgreich geändert.',
|
||||||
'PASSWORD_CHANGED': 'Das Passwort wurde erfolgreich geändert.',
|
'PASSWORD_CHANGED': 'Das Passwort wurde erfolgreich geändert.',
|
||||||
'OLD_PASSWORD_INCORRECT': 'Das alte Passwort ist falsch.',
|
'OLD_PASSWORD_INCORRECT': 'Das alte Passwort ist falsch.',
|
||||||
@ -429,6 +445,8 @@ export default {
|
|||||||
'SUCCESS_DELETING_ALL_USERS': 'Die Benutzer wurden erfolgreich gelöscht.',
|
'SUCCESS_DELETING_ALL_USERS': 'Die Benutzer wurden erfolgreich gelöscht.',
|
||||||
'SUCCESSFUL_CONNECTION': 'Erfolgreiche Verbindung',
|
'SUCCESSFUL_CONNECTION': 'Erfolgreiche Verbindung',
|
||||||
'UNSUCCESSFUL_CONNECTION': 'Verbindung fehlgeschlagen',
|
'UNSUCCESSFUL_CONNECTION': 'Verbindung fehlgeschlagen',
|
||||||
|
'TODAY_AT': 'Heute um',
|
||||||
|
'YESTERDAY_AT': 'Gestern um',
|
||||||
'SERVER_CREDENTIALS_WORKING': 'Server-Anmeldeinformationen funktionieren ordnungsgemäß',
|
'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?',
|
'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.',
|
'LEFT_EMPTY_DATABASE': 'Leer lassen für automatische Datenbankerstellung.',
|
||||||
'REMEMBER_ME': 'Merken',
|
'REMEMBER_ME': 'Merken',
|
||||||
'EMAIL_LOWERCASE': 'E-Mail',
|
'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',
|
'PASSWORD_LOWERCASE': 'Passwort',
|
||||||
'TEST_SMTP_CONNECTION': 'SMTP Verbindung testen',
|
'TEST_SMTP_CONNECTION': 'SMTP Verbindung testen',
|
||||||
'SERVER_ERROR': 'Kann nicht mit dem Server verbinden.',
|
'SERVER_ERROR': 'Kann nicht mit dem Server verbinden.',
|
||||||
'EMAIL_SERVER_ADDRESS': 'E-Mail-Serveradresse',
|
'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',
|
'TICKET_LIST': 'Ticket List',
|
||||||
'SUPPORT_CENTER': 'Support Center',
|
'SUPPORT_CENTER': 'Support Center',
|
||||||
'SUPERVISED_USER': 'Supervised users',
|
'SUPERVISED_USER': 'Supervised users',
|
||||||
|
'SUPERVISED_USER_INFORMATION': 'Allows to see tickets from another user',
|
||||||
'DEPARTMENT': 'Department',
|
'DEPARTMENT': 'Department',
|
||||||
'DEFAULT_DEPARTMENT': 'Default Department',
|
'DEFAULT_DEPARTMENT': 'Default Department',
|
||||||
'AUTHOR': 'Author',
|
'AUTHOR': 'Author',
|
||||||
@ -77,6 +78,7 @@ export default {
|
|||||||
'OWNER': 'Owner',
|
'OWNER': 'Owner',
|
||||||
'OWNED': 'Owned',
|
'OWNED': 'Owned',
|
||||||
'STATUS': 'Status',
|
'STATUS': 'Status',
|
||||||
|
'PERIOD': 'Period',
|
||||||
'NONE': 'None',
|
'NONE': 'None',
|
||||||
'ANY': 'Any',
|
'ANY': 'Any',
|
||||||
'OPENED': 'Opened',
|
'OPENED': 'Opened',
|
||||||
@ -190,7 +192,7 @@ export default {
|
|||||||
'BACKUP_DATABASE': 'Backup database',
|
'BACKUP_DATABASE': 'Backup database',
|
||||||
'DELETE_ALL_USERS': 'Delete all users',
|
'DELETE_ALL_USERS': 'Delete all users',
|
||||||
'PLEASE_CONFIRM_PASSWORD': 'Please confirm your password to make these changes',
|
'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',
|
'NAME_OF_KEY': 'Name of key',
|
||||||
'KEY': 'Key',
|
'KEY': 'Key',
|
||||||
'ADD_API_KEY': 'Add API Key',
|
'ADD_API_KEY': 'Add API Key',
|
||||||
@ -224,6 +226,7 @@ export default {
|
|||||||
'NEW_CUSTOM_FIELD': 'New Custom field',
|
'NEW_CUSTOM_FIELD': 'New Custom field',
|
||||||
'TYPE': 'Type',
|
'TYPE': 'Type',
|
||||||
'SELECT_INPUT': 'Select input',
|
'SELECT_INPUT': 'Select input',
|
||||||
|
'TEXT': 'Text',
|
||||||
'TEXT_INPUT': 'Text input',
|
'TEXT_INPUT': 'Text input',
|
||||||
'OPTION': 'Option {index}',
|
'OPTION': 'Option {index}',
|
||||||
'OPTIONS': 'Options',
|
'OPTIONS': 'Options',
|
||||||
@ -241,6 +244,13 @@ export default {
|
|||||||
'CHART_SIGNUP': 'Signups',
|
'CHART_SIGNUP': 'Signups',
|
||||||
'CHART_COMMENT': 'Replies',
|
'CHART_COMMENT': 'Replies',
|
||||||
'CHART_ASSIGN': 'Assigned',
|
'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
|
//ACTIVITIES
|
||||||
'ACTIVITY_COMMENT': 'commented ticket',
|
'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.',
|
'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_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.',
|
'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
|
//ERRORS
|
||||||
'EMAIL_OR_PASSWORD': 'Email or password invalid',
|
'EMAIL_OR_PASSWORD': 'Email or password invalid',
|
||||||
@ -403,11 +415,16 @@ export default {
|
|||||||
'INVALID_SUPERVISED_USERS': 'Invalid supervised users',
|
'INVALID_SUPERVISED_USERS': 'Invalid supervised users',
|
||||||
'SUPERVISOR_CAN_NOT_SUPERVISE_HIMSELF': 'Supervisor can not supervise himself',
|
'SUPERVISOR_CAN_NOT_SUPERVISE_HIMSELF': 'Supervisor can not supervise himself',
|
||||||
'NAME_ALREADY_USED': 'Name already used',
|
'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
|
//MESSAGES
|
||||||
'SIGNUP_SUCCESS': 'You have registered successfully in our support system.',
|
'SIGNUP_SUCCESS': 'You have registered successfully in our support system.',
|
||||||
'INVITE_USER_SUCCESS': 'You have invited a new user 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_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',
|
'VALID_RECOVER': 'Password recovered successfully',
|
||||||
'EMAIL_EXISTS': 'Email already exists',
|
'EMAIL_EXISTS': 'Email already exists',
|
||||||
'ARE_YOU_SURE': 'Confirm action',
|
'ARE_YOU_SURE': 'Confirm action',
|
||||||
@ -437,6 +454,8 @@ export default {
|
|||||||
|
|
||||||
'TITLE_EDITED': '(title edited)',
|
'TITLE_EDITED': '(title edited)',
|
||||||
'COMMENT_EDITED': '(comment edited)',
|
'COMMENT_EDITED': '(comment edited)',
|
||||||
|
'TODAY_AT': 'Today at',
|
||||||
|
'YESTERDAY_AT': 'Yesterday at',
|
||||||
'LAST_7_DAYS': 'Last 7 days',
|
'LAST_7_DAYS': 'Last 7 days',
|
||||||
'LAST_30_DAYS': 'Last 30 days',
|
'LAST_30_DAYS': 'Last 30 days',
|
||||||
'LAST_90_DAYS': 'Last 90 days',
|
'LAST_90_DAYS': 'Last 90 days',
|
||||||
@ -464,7 +483,6 @@ export default {
|
|||||||
'CREATED_DESCRIPTION': 'Created tickets during the selected time range',
|
'CREATED_DESCRIPTION': 'Created tickets during the selected time range',
|
||||||
'OPEN': 'Open',
|
'OPEN': 'Open',
|
||||||
'OPEN_DESCRIPTION': 'Open tickets created during the selected time range',
|
'OPEN_DESCRIPTION': 'Open tickets created during the selected time range',
|
||||||
'CLOSED': 'Closed',
|
|
||||||
'CLOSED_DESCRIPTION': 'Closed tickets created during the selected time range',
|
'CLOSED_DESCRIPTION': 'Closed tickets created during the selected time range',
|
||||||
'INSTANT': 'Instant',
|
'INSTANT': 'Instant',
|
||||||
'INSTANT_DESCRIPTION': 'Percentage of tickets closed after a single staff reply over the total of tickets closed',
|
'INSTANT_DESCRIPTION': 'Percentage of tickets closed after a single staff reply over the total of tickets closed',
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
'WELCOME': 'Bienvenido',
|
'WELCOME': 'Bienvenido',
|
||||||
'TICKETS': 'Tickets',
|
'TICKETS': 'Tickets',
|
||||||
'ARTICLES': 'Artículo',
|
'ARTICLES': 'Artículos',
|
||||||
'ACCOUNT': 'Cuenta',
|
'ACCOUNT': 'Cuenta',
|
||||||
'SUBMIT': 'Enviar',
|
'SUBMIT': 'Enviar',
|
||||||
'EMAIL': 'Email',
|
'EMAIL': 'Email',
|
||||||
@ -28,9 +28,9 @@ export default {
|
|||||||
'SUPPORT_CENTER': 'Centro de Soporte',
|
'SUPPORT_CENTER': 'Centro de Soporte',
|
||||||
'SUPERVISED_USER': 'Los usuarios supervisados',
|
'SUPERVISED_USER': 'Los usuarios supervisados',
|
||||||
'DEPARTMENT': 'Departamento',
|
'DEPARTMENT': 'Departamento',
|
||||||
'DEFAULT_DEPARTMENT': 'Por defecto Departamento',
|
'DEFAULT_DEPARTMENT': 'Departamento predeterminado',
|
||||||
'AUTHOR': 'Autor',
|
'AUTHOR': 'Autor',
|
||||||
'AUTHORS': 'autores',
|
'AUTHORS': 'Autores',
|
||||||
'DATE': 'Fecha',
|
'DATE': 'Fecha',
|
||||||
'RESPOND': 'Responder',
|
'RESPOND': 'Responder',
|
||||||
'RESPOND_TICKET': 'Responder ticket',
|
'RESPOND_TICKET': 'Responder ticket',
|
||||||
@ -52,14 +52,14 @@ export default {
|
|||||||
'NEW_TICKETS': 'Nuevos Tickets',
|
'NEW_TICKETS': 'Nuevos Tickets',
|
||||||
'ALL_TICKETS': 'Todos los Tickets',
|
'ALL_TICKETS': 'Todos los Tickets',
|
||||||
'CUSTOM_RESPONSES': 'Respuestas Personalizadas',
|
'CUSTOM_RESPONSES': 'Respuestas Personalizadas',
|
||||||
'SEARCH_TICKETS': 'Buscar entradas',
|
'SEARCH_TICKETS': 'Buscar tickets.',
|
||||||
'CUSTOM_LIST': 'Lista personalizada',
|
'CUSTOM_LIST': 'Lista personalizada',
|
||||||
'CUSTOM_TAGS': 'Etiquetas personalizadas',
|
'CUSTOM_TAGS': 'Etiquetas personalizadas',
|
||||||
'LIST_USERS': 'Lista de Usuarios',
|
'LIST_USERS': 'Lista de Usuarios',
|
||||||
'TAGS': 'Etiquetas',
|
'TAGS': 'Etiquetas',
|
||||||
'BAN_USERS': 'Bloquear Usuarios',
|
'BAN_USERS': 'Bloquear Usuarios',
|
||||||
'LIST_ARTICLES': 'Lista de Artículos',
|
'LIST_ARTICLES': 'Lista de Artículos',
|
||||||
'STAFF_MEMBERS': 'Staff Members',
|
'STAFF_MEMBERS': 'Miembros del staff',
|
||||||
'DEPARTMENTS': 'Departamentos',
|
'DEPARTMENTS': 'Departamentos',
|
||||||
'SYSTEM_PREFERENCES': 'Preferencias del Sistema',
|
'SYSTEM_PREFERENCES': 'Preferencias del Sistema',
|
||||||
'ADVANCED_SETTINGS': 'Opciones Avanzadas',
|
'ADVANCED_SETTINGS': 'Opciones Avanzadas',
|
||||||
@ -82,7 +82,7 @@ export default {
|
|||||||
'OPENED': 'Abierto',
|
'OPENED': 'Abierto',
|
||||||
'CLOSED': 'Cerrado',
|
'CLOSED': 'Cerrado',
|
||||||
'CLOSE': 'Cerrar',
|
'CLOSE': 'Cerrar',
|
||||||
'ANY': 'Alguna',
|
'ANY': 'Cualquiera',
|
||||||
'RE_OPEN': 'Reabrir',
|
'RE_OPEN': 'Reabrir',
|
||||||
'ASSIGN_TO_ME': 'Asignar',
|
'ASSIGN_TO_ME': 'Asignar',
|
||||||
'UN_ASSIGN': 'Desasignar',
|
'UN_ASSIGN': 'Desasignar',
|
||||||
@ -102,8 +102,9 @@ export default {
|
|||||||
'CHANGE_EMAIL': 'Cambiar email',
|
'CHANGE_EMAIL': 'Cambiar email',
|
||||||
'CHANGE_PASSWORD': 'Cambiar contraseña',
|
'CHANGE_PASSWORD': 'Cambiar contraseña',
|
||||||
'NAME': 'Nombre',
|
'NAME': 'Nombre',
|
||||||
|
'APPLY': 'Aplicar',
|
||||||
'SIGNUP_DATE': 'Fecha de registro',
|
'SIGNUP_DATE': 'Fecha de registro',
|
||||||
'CLEAR': 'Claro',
|
'CLEAR': 'Limpiar',
|
||||||
'SEARCH': 'Buscar',
|
'SEARCH': 'Buscar',
|
||||||
'SEARCH_USERS': 'Buscar usuarios...',
|
'SEARCH_USERS': 'Buscar usuarios...',
|
||||||
'SEARCH_EMAIL': 'Buscar email...',
|
'SEARCH_EMAIL': 'Buscar email...',
|
||||||
@ -121,6 +122,7 @@ export default {
|
|||||||
'NO_RESULTS': 'No hay resultados',
|
'NO_RESULTS': 'No hay resultados',
|
||||||
'DELETE_AND_BAN': 'Borrar y bloquear',
|
'DELETE_AND_BAN': 'Borrar y bloquear',
|
||||||
'STAFF_LEVEL': 'Nivel de Staff',
|
'STAFF_LEVEL': 'Nivel de Staff',
|
||||||
|
'TICKETS_ASSIGNED': 'Tickets asignados',
|
||||||
'ASSIGNED': 'Asignado',
|
'ASSIGNED': 'Asignado',
|
||||||
'ASSIGNED_TICKETS': '{tickets} Tickets Asignados',
|
'ASSIGNED_TICKETS': '{tickets} Tickets Asignados',
|
||||||
'CLOSED_TICKETS': '{tickets} Tickets Cerrados',
|
'CLOSED_TICKETS': '{tickets} Tickets Cerrados',
|
||||||
@ -131,13 +133,14 @@ export default {
|
|||||||
'LEVEL_1': 'Nivel 1 (Tickets)',
|
'LEVEL_1': 'Nivel 1 (Tickets)',
|
||||||
'LEVEL_2': 'Nivel 2 (Tickets + Artículos)',
|
'LEVEL_2': 'Nivel 2 (Tickets + Artículos)',
|
||||||
'LEVEL_3': 'Nivel 3 (Tickets + Artículos + Staff)',
|
'LEVEL_3': 'Nivel 3 (Tickets + Artículos + Staff)',
|
||||||
'LEVEL_1_DESCRIPTION': 'sólo puede responder tickets y administrar usuarios.',
|
'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_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_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_EMAIL': 'Actualizar correo electrónico',
|
||||||
'UPDATE_PASSWORD': 'Actualizar contraseña',
|
'UPDATE_PASSWORD': 'Actualizar contraseña',
|
||||||
|
'UPDATE_CUSTOM_FIELDS': 'Actualizar campos personalizados',
|
||||||
'UPDATE_LEVEL': 'Actualizar nivel',
|
'UPDATE_LEVEL': 'Actualizar nivel',
|
||||||
'UPDATE_DEPARTMENTS': 'Actializar departamentos',
|
'UPDATE_DEPARTMENTS': 'Actualizar departamentos',
|
||||||
'EDIT_STAFF': 'Editar miembro del staff',
|
'EDIT_STAFF': 'Editar miembro del staff',
|
||||||
'ADD_DEPARTMENT': 'Añadir departamento',
|
'ADD_DEPARTMENT': 'Añadir departamento',
|
||||||
'UPDATE_DEPARTMENT': 'Actualizar Departamento',
|
'UPDATE_DEPARTMENT': 'Actualizar Departamento',
|
||||||
@ -182,14 +185,14 @@ export default {
|
|||||||
'ALL_NOTIFICATIONS': 'Todas las notificaciones',
|
'ALL_NOTIFICATIONS': 'Todas las notificaciones',
|
||||||
'VERIFY_SUCCESS': 'Usuario verificado',
|
'VERIFY_SUCCESS': 'Usuario verificado',
|
||||||
'VERIFY_FAILED': 'No se pudo verificar',
|
'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_SYSTEM': 'Usar sistema de usuarios para clientes',
|
||||||
'ENABLE_USER_REGISTRATION': 'Habilitar registro de usuarios',
|
'ENABLE_USER_REGISTRATION': 'Habilitar registro de usuarios',
|
||||||
'INCLUDE_USERS_VIA_CSV': 'Incluir usuarios por archivo CSV',
|
'INCLUDE_USERS_VIA_CSV': 'Incluir usuarios por archivo CSV',
|
||||||
'BACKUP_DATABASE': 'Backup database',
|
'BACKUP_DATABASE': 'Backup database',
|
||||||
|
'API_KEYS': 'Claves API',
|
||||||
'DELETE_ALL_USERS': 'Borrar todos los usuarios',
|
'DELETE_ALL_USERS': 'Borrar todos los usuarios',
|
||||||
'PLEASE_CONFIRM_PASSWORD': 'Por favor, confirme su contraseña para hacer estos cambios',
|
'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',
|
'NAME_OF_KEY': 'Nombre de la clave',
|
||||||
'KEY': 'Clave',
|
'KEY': 'Clave',
|
||||||
'ADD_API_KEY': 'Añadir nueva clave',
|
'ADD_API_KEY': 'Añadir nueva clave',
|
||||||
@ -204,21 +207,21 @@ export default {
|
|||||||
'STAFF_UPDATED': 'Miembro de Staff actualizado',
|
'STAFF_UPDATED': 'Miembro de Staff actualizado',
|
||||||
'UPDATE': 'Actualizar',
|
'UPDATE': 'Actualizar',
|
||||||
'NEVER': 'Nunca',
|
'NEVER': 'Nunca',
|
||||||
'HIMSELF': 'si mismo',
|
'HIMSELF': 'Si mismo',
|
||||||
'SUPERVISED_USERS_UPDATED': 'Los usuarios supervisados actualizan',
|
'SUPERVISED_USERS_UPDATED': 'Los usuarios supervisados han sido actualizados.',
|
||||||
'ADD_USER': 'Añadir un usuario',
|
'ADD_USER': 'Añadir un usuario',
|
||||||
'INVITE_USER': 'invitar usuario',
|
'INVITE_USER': 'Invitar usuario',
|
||||||
'INVITE_STAFF': 'invitar staff',
|
'INVITE_STAFF': 'Invitar staff',
|
||||||
'UPLOAD_FILE': 'Subir archivo',
|
'UPLOAD_FILE': 'Subir archivo',
|
||||||
'PRIVATE': 'privado',
|
'PRIVATE': 'Privado',
|
||||||
'ENABLE_USER': 'Habilitar usuario',
|
'ENABLE_USER': 'Habilitar usuario',
|
||||||
'DISABLE_USER': 'Deshabilitar usuario',
|
'DISABLE_USER': 'Deshabilitar usuario',
|
||||||
'SHOW_CLOSED_TICKETS': 'Mostrar Tickets Cerrados',
|
'SHOW_CLOSED_TICKETS': 'Mostrar Tickets Cerrados',
|
||||||
'LOCKED': 'bloqueado',
|
'LOCKED': 'Bloqueado',
|
||||||
'IMAGE_HEADER_URL': 'URL del encabezado de la imagen',
|
'IMAGE_HEADER_URL': 'URL del encabezado de la imagen',
|
||||||
'IMAGE_HEADER_DESCRIPTION': 'Imagen que se utilizará como encabezado del correo electrónico.',
|
'IMAGE_HEADER_DESCRIPTION': 'Imagen que se utilizará como encabezado del correo electrónico.',
|
||||||
'EMAIL_SETTINGS': 'Ajustes 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',
|
'ADDITIONAL_FIELDS': 'Campos Adicionales',
|
||||||
'NEW_CUSTOM_FIELD': 'Nuevo campo personalizado',
|
'NEW_CUSTOM_FIELD': 'Nuevo campo personalizado',
|
||||||
'TYPE': 'Tipo',
|
'TYPE': 'Tipo',
|
||||||
@ -228,44 +231,55 @@ export default {
|
|||||||
'OPTIONS': 'Opciones',
|
'OPTIONS': 'Opciones',
|
||||||
'FIELD_DESCRIPTION': 'Descripción del campo (opcional)',
|
'FIELD_DESCRIPTION': 'Descripción del campo (opcional)',
|
||||||
'DESCRIPTION_ADD_CUSTOM_TAG': 'Aquí puede agregar una nueva etiqueta personalizada',
|
'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',
|
'CUSTOM_FIELDS': 'Campos Personalizados',
|
||||||
|
|
||||||
'CHART_CREATE_TICKET': 'Tickets creados',
|
'CHART_CREATE_TICKET': 'Tickets creados',
|
||||||
'CHART_CLOSE': 'Tickets cerrados',
|
'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_SIGNUP': 'Registros',
|
||||||
'CHART_COMMENT': 'Respuestas',
|
'CHART_COMMENT': 'Respuestas',
|
||||||
'CHART_ASSIGN': 'Asignaciones',
|
'CHART_ASSIGN': 'Asignaciones',
|
||||||
|
|
||||||
//ACTIVITIES
|
//ACTIVITIES
|
||||||
'ACTIVITY_COMMENT': 'comentó el ticket',
|
'ACTIVITY_COMMENT': 'Comentó el ticket',
|
||||||
'ACTIVITY_ASSIGN': 'se asignó el ticket',
|
'ACTIVITY_ASSIGN': 'Se asignó el ticket',
|
||||||
'ACTIVITY_UN_ASSIGN': 'se desasignó el ticket',
|
'ACTIVITY_UN_ASSIGN': 'Se desasignó el ticket',
|
||||||
'ACTIVITY_CLOSE': 'cerró el ticket',
|
'ACTIVITY_CLOSE': 'Cerró el ticket',
|
||||||
'ACTIVITY_CREATE_TICKET': 'creó el ticket',
|
'ACTIVITY_CREATE_TICKET': 'Creó el ticket',
|
||||||
'ACTIVITY_RE_OPEN': 'reabrió el ticket',
|
'ACTIVITY_RE_OPEN': 'Reabrió el ticket',
|
||||||
'ACTIVITY_DEPARTMENT_CHANGED': 'cambió el departamento del ticket',
|
'ACTIVITY_DEPARTMENT_CHANGED': 'Cambió el departamento del ticket',
|
||||||
'ACTIVITY_EDIT_COMMENT': 'editó un comentario del ticket',
|
'ACTIVITY_EDIT_COMMENT': 'Editó un comentario del ticket',
|
||||||
|
|
||||||
'ACTIVITY_EDIT_SETTINGS': 'editó las preferencias',
|
'ACTIVITY_EDIT_SETTINGS': 'Editó las preferencias',
|
||||||
'ACTIVITY_SIGNUP': 'se registró',
|
'ACTIVITY_SIGNUP': 'Se registró',
|
||||||
'ACTIVITY_EDIT_TITLE': 'editó el titulo del ticket',
|
'ACTIVITY_EDIT_TITLE': 'Editó el titulo del ticket',
|
||||||
'ACTIVITY_ADD_TOPIC': 'añadió el tema',
|
'ACTIVITY_ADD_TOPIC': 'Añadió el tema',
|
||||||
'ACTIVITY_ADD_ARTICLE': 'añadió el articulo',
|
'ACTIVITY_ADD_ARTICLE': 'Añadió el articulo',
|
||||||
'ACTIVITY_DELETE_TOPIC': 'eliminó el tema',
|
'ACTIVITY_DELETE_TOPIC': 'Eliminó el tema',
|
||||||
'ACTIVITY_DELETE_ARTICLE': 'eliminó el artículo',
|
'ACTIVITY_DELETE_ARTICLE': 'Eliminó el artículo',
|
||||||
'ACTIVITY_EDIT_ARTICLE': 'editó el artículo',
|
'ACTIVITY_EDIT_ARTICLE': 'Editó el artículo',
|
||||||
'ACTIVITY_INVITE': 'usuario invitado',
|
'ACTIVITY_INVITE': 'Usuario invitado',
|
||||||
'ACTIVITY_ADD_STAFF': 'añadió el staff',
|
'ACTIVITY_ADD_STAFF': 'Añadió el staff',
|
||||||
'ACTIVITY_ADD_DEPARTMENT': 'añadió el departamento',
|
'ACTIVITY_ADD_DEPARTMENT': 'Añadió el departamento',
|
||||||
'ACTIVITY_DELETE_DEPARTMENT': 'borró el departamento',
|
'ACTIVITY_DELETE_DEPARTMENT': 'Borró el departamento',
|
||||||
'ACTIVITY_EDIT_DEPARTMENT': 'editó el departamento',
|
'ACTIVITY_EDIT_DEPARTMENT': 'Editó el departamento',
|
||||||
'ACTIVITY_ADD_CUSTOM_RESPONSE': 'añadió una respuesta personalizada',
|
'ACTIVITY_ADD_CUSTOM_RESPONSE': 'Añadió una respuesta personalizada',
|
||||||
'ACTIVITY_DELETE_CUSTOM_RESPONSE': 'borró una respuesta personalizada',
|
'ACTIVITY_DELETE_CUSTOM_RESPONSE': 'Borró una respuesta personalizada',
|
||||||
'ACTIVITY_EDIT_CUSTOM_RESPONSE': 'editó una respuesta personalizada',
|
'ACTIVITY_EDIT_CUSTOM_RESPONSE': 'Editó una respuesta personalizada',
|
||||||
'ACTIVITY_BAN_USER': 'bloqueó el usuario',
|
'ACTIVITY_BAN_USER': 'Bloqueó el usuario',
|
||||||
'ACTIVITY_DELETE_USER': 'borró el usuario',
|
'ACTIVITY_DELETE_USER': 'Borró el usuario',
|
||||||
'ACTIVITY_UN_BAN_USER': 'desbloqueó el usuario',
|
'ACTIVITY_UN_BAN_USER': 'Desbloqueó el usuario',
|
||||||
|
|
||||||
'SERVER_REQUIREMENTS': 'Requisitos del servidor',
|
'SERVER_REQUIREMENTS': 'Requisitos del servidor',
|
||||||
'DATABASE_CONFIGURATION': 'Configuracion de la base de datos',
|
'DATABASE_CONFIGURATION': 'Configuracion de la base de datos',
|
||||||
@ -284,6 +298,7 @@ export default {
|
|||||||
'DATABASE_NAME': 'Nombre de la base de datos MySQL',
|
'DATABASE_NAME': 'Nombre de la base de datos MySQL',
|
||||||
'DATABASE_USER': 'Usuario MySQL',
|
'DATABASE_USER': 'Usuario MySQL',
|
||||||
'DATABASE_PASSWORD': 'Contraseña MySQL',
|
'DATABASE_PASSWORD': 'Contraseña MySQL',
|
||||||
|
'INSTALLATION_COMPLETED_TITLE': 'instalación completa',
|
||||||
'ADMIN_NAME': 'Nombre de la cuenta admin',
|
'ADMIN_NAME': 'Nombre de la cuenta admin',
|
||||||
'ADMIN_EMAIL': 'Email de la cuenta admin',
|
'ADMIN_EMAIL': 'Email de la cuenta admin',
|
||||||
'ADMIN_PASSWORD': 'Contraseña 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.',
|
'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_SUCCESS_DESCRIPTION': 'Su usuario ha sido verificado correctamente. Puede iniciar sesión ahora.',
|
||||||
'VERIFY_FAILED_DESCRIPTION': 'No se pudo hacer la verificación.',
|
'VERIFY_FAILED_DESCRIPTION': 'No se pudo hacer la verificación.',
|
||||||
'MANDATORY_LOGIN_DISABLED': 'entrada obligatoria ha sido desactivado',
|
'MANDATORY_LOGIN_DISABLED': 'El logueo obligatorio ha sido desactivado',
|
||||||
'STATISTICS_DESCRIPTION': 'Aquí puede ver estadisticas relacionadas con tickets y registros.',
|
'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.',
|
'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_DISABLED': 'Se ha inhabilitado el sistema de usuarios',
|
||||||
'USER_SYSTEM_ENABLED': 'Se ha habilitado 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.',
|
'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.',
|
'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.',
|
'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.',
|
'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.',
|
'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.',
|
'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_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.',
|
'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}',
|
'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.',
|
'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',
|
'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.',
|
'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',
|
'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_TITLE': 'Título incorrecto',
|
||||||
'ERROR_EMAIL': 'Email inválido',
|
'ERROR_EMAIL': 'Email inválido',
|
||||||
'ERROR_CONTENT_SHORT': 'Contenido demasiado corto',
|
'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_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.',
|
'ERROR_BANNING_EMAIL': 'Se ha producido un error al intentar bloquear el email.',
|
||||||
'INVALID_NAME': 'nombre inválido',
|
'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',
|
'ERROR_LIST': 'Seleccione al menos uno',
|
||||||
'INVALID_TITLE': 'título inválido',
|
'INVALID_TITLE': 'Título inválido',
|
||||||
'ERROR_URL': 'URL incorrecta',
|
'ERROR_URL': 'URL incorrecta',
|
||||||
'UNVERIFIED_EMAIL': 'El email no esta verificado aún',
|
'UNVERIFIED_EMAIL': 'El email aún no ha sido verificado',
|
||||||
'ERROR_UPDATING_SETTINGS': 'Un error ocurrió mientras se intentaban actualizar las preferencias',
|
'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_EMAIL_OR_TICKET_NUMBER': 'Email o numero de ticket inválido',
|
||||||
'INVALID_FILE': 'Archivo inválido',
|
'INVALID_FILE': 'Archivo inválido',
|
||||||
'ERRORS_FOUND': 'Se encontraron errores',
|
'ERRORS_FOUND': 'Se encontraron errores',
|
||||||
'ERROR_IMAGE_SIZE': 'Ninguna imagen puede tener un tamaño superior a {size} MB',
|
'ERROR_IMAGE_SIZE': 'Ninguna imagen puede tener un tamaño superior a {size} MB',
|
||||||
'USER_DISABLED': 'Esta cuenta está deshabilitada.',
|
'USER_DISABLED': 'Esta cuenta está deshabilitada.',
|
||||||
'INVALID_SYNTAX': 'Sintaxis inválida.',
|
'INVALID_SYNTAX': 'Sintaxis inválida.',
|
||||||
'DEPARTMENT_PRIVATE_TICKETS': 'Este departamento tiene entradas creadas por personal no administrativo y no puede ser privado.',
|
'NAME_ALREADY_USED': 'Nombre en uso',
|
||||||
'CURRENTLY_UNAVAILABLE': 'actualmente no disponible',
|
'DEPARTMENT_PRIVATE_TICKETS': 'Este departamento tiene tickets creados por usuarios, no puede ser privado.',
|
||||||
'CAN_NOT_DELETE_DEFAULT_DEPARTMENT': 'departamento por defecto no se puede eliminar',
|
'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
|
//MESSAGES
|
||||||
'SIGNUP_SUCCESS': 'Se ha registrado con éxito en nuestro sistema de soporte.',
|
'SIGNUP_SUCCESS': 'Se ha registrado con éxito en nuestro sistema de soporte.',
|
||||||
'INVALID_SUPERVISED_USERS': 'usuarios supervisados no válidos',
|
'INVALID_SUPERVISED_USERS': 'Usuarios supervisados inválidos.',
|
||||||
'INVALID_DEFAULT_DEPARTMENT': 'departamento elegido por defecto no es válido',
|
'INVALID_DEFAULT_DEPARTMENT': 'El departamento predeterminado elegido no es válido.',
|
||||||
'TICKET_SENT': 'El ticket se ha creado correctamente.',
|
'TICKET_SENT': 'El ticket se ha creado correctamente.',
|
||||||
'SUPERVISOR_CAN_NOT_SUPERVISE_HIMSELF': 'Supervisor no puede supervisar el propio',
|
'SUPERVISOR_CAN_NOT_SUPERVISE_HIMSELF': 'El supervisor no puede supervisarse a sí mismo.',
|
||||||
'VALID_RECOVER': 'La contraseña se recuperó correctamente',
|
'VALID_RECOVER': 'La contraseña se recuperó correctamente.',
|
||||||
'EMAIL_EXISTS': 'El email ya existe',
|
'EMAIL_EXISTS': 'El email ya existe.',
|
||||||
'INVITE_USER_SUCCESS': 'Has invitado a un nuevo usuario con éxito en nuestro sistema de soporte',
|
'INVITE_USER_SUCCESS': 'Has invitado a un nuevo usuario con éxito en nuestro sistema de soporte.',
|
||||||
'ARE_YOU_SURE': '¿Estás seguro?',
|
'ARE_YOU_SURE': '¿Estás seguro?',
|
||||||
'EMAIL_WILL_CHANGE': 'El correo electrónico actual se cambiará',
|
'EMAIL_WILL_CHANGE': 'El correo electrónico actual se cambiará.',
|
||||||
'PASSWORD_WILL_CHANGE': 'Se cambiará la contraseña actual',
|
'WILL_DELETE_CUSTOM_TAG': 'La etiqueta personalizada será eliminada.',
|
||||||
'EMAIL_CHANGED': 'Se ha cambiado correctamente el correo electrónico',
|
'PASSWORD_WILL_CHANGE': 'Se cambiará la contraseña actual.',
|
||||||
'PASSWORD_CHANGED': 'La contraseña ha sido cambiada correctamente',
|
'EMAIL_CHANGED': 'Se ha cambiado correctamente el correo electrónico.',
|
||||||
'OLD_PASSWORD_INCORRECT': 'La antigua contraseña es incorrecta',
|
'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_LOSE_CHANGES': 'No has guardado. Los cambios se perderán.',
|
||||||
'WILL_DELETE_CUSTOM_RESPONSE': 'La respuesta personalizada se eliminará.',
|
'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.',
|
'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_IMPORTING_CSV_DESCRIPTION': 'El archivo CSV se ha importado correctamente',
|
||||||
'SUCCESS_DELETING_ALL_USERS': 'Los usuarios se han eliminado correctamente',
|
'SUCCESS_DELETING_ALL_USERS': 'Los usuarios se han eliminado correctamente',
|
||||||
'SUCCESSFUL_CONNECTION': 'Conexión exitosa',
|
'SUCCESSFUL_CONNECTION': 'Conexión exitosa',
|
||||||
|
'TODAY_AT': 'Hoy a las',
|
||||||
|
'YESTERDAY_AT': 'Ayer a las',
|
||||||
'UNSUCCESSFUL_CONNECTION': 'Conexión fallida',
|
'UNSUCCESSFUL_CONNECTION': 'Conexión fallida',
|
||||||
'SERVER_CREDENTIALS_WORKING': 'Las credenciales del servidor están funcionando correctamente',
|
'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?',
|
'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',
|
'LAST_365_DAYS': 'Últimos 365 dias',
|
||||||
|
|
||||||
'TEST': 'Prueba',
|
'TEST': 'Prueba',
|
||||||
'ACTIVITY_COMMENT_THIS': 'comentó este ticket',
|
'ACTIVITY_COMMENT_THIS': 'Comentó este ticket',
|
||||||
'ACTIVITY_ASSIGN_THIS': 'se asignó este ticket para',
|
'ACTIVITY_ASSIGN_THIS': 'Se asignó este ticket a.',
|
||||||
'ACTIVITY_UN_ASSIGN_THIS': 'se desasignó este ticket para',
|
'ACTIVITY_UN_ASSIGN_THIS': 'Se desasignó este ticket a.',
|
||||||
'ACTIVITY_CLOSE_THIS': 'cerró este ticket',
|
'ACTIVITY_CLOSE_THIS': 'Cerró este ticket',
|
||||||
'ACTIVITY_CREATE_TICKET_THIS': 'creó este ticket',
|
'ACTIVITY_CREATE_TICKET_THIS': 'Creó este ticket',
|
||||||
'ACTIVITY_RE_OPEN_THIS': 'reabrió este ticket',
|
'ACTIVITY_RE_OPEN_THIS': 'Reabrió este ticket',
|
||||||
'ACTIVITY_DEPARTMENT_CHANGED_THIS': 'cambió el departamento de este ticket a ',
|
'ACTIVITY_DEPARTMENT_CHANGED_THIS': 'Cambió el departamento de este ticket a ',
|
||||||
'DATE_PREFIX': 'el día',
|
'DATE_PREFIX': 'El día',
|
||||||
'LEFT_EMPTY_DATABASE': 'Dejar vacío para la creación automática de bases de datos',
|
'LEFT_EMPTY_DATABASE': 'Dejar vacío para la creación automática de bases de datos',
|
||||||
'REMEMBER_ME': 'Recordarme',
|
'REMEMBER_ME': 'Recordarme',
|
||||||
'EMAIL_LOWERCASE': 'email',
|
'EMAIL_LOWERCASE': 'Email',
|
||||||
'DEFAULT_PORT': 'Deje en blanco para 3306 por defecto',
|
'DEFAULT_PORT': 'Deje en blanco para 3306 por defecto',
|
||||||
'PASSWORD_LOWERCASE': 'contraseña',
|
'PASSWORD_LOWERCASE': 'Contraseña',
|
||||||
'TEST_SMTP_CONNECTION': 'Probar conexion de SMTP',
|
'TEST_SMTP_CONNECTION': 'Probar conexion de SMTP',
|
||||||
'SERVER_ERROR': 'No es posible conectar con el servidor.',
|
'SERVER_ERROR': 'No es posible conectar con el servidor.',
|
||||||
'EMAIL_SERVER_ADDRESS': 'Dirección del servidor de correo electrónico',
|
'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