Compare commits

..

839 Commits

Author SHA1 Message Date
Maximiliano Redigonda
20720ca4f9
[DEV-371] Improve README (#1219) 2022-06-10 18:03:30 -03:00
Maxi Redigonda
40016635a8 [DEV-367] Use FileDownloader instead of fopen 2022-06-03 16:42:57 -03:00
Maxi Redigonda
68b2d2bf63 [DEV-365] Remove page size selector from ticket list in user view 2022-06-02 18:26:51 -03:00
Maximiliano Redigonda
36c5f3264b
[DEV-143] Handle expired sessions (#1192) 2022-06-02 11:39:31 -03:00
Maximiliano Redigonda
82fd54ffd9
[DEV-108] Change component to select date ranges (#1216)
Update console for better API request/response logs

Add some logs

Remove dateRange and make period select work

Fix package files

Fix stats path

WIP

Fix stats for single staff members

Remove logger
2022-06-01 07:08:36 -03:00
Maximiliano Redigonda
8c732f5dda
[DEV-190] Fix tests in Github Actions (#1217)
* Silly test

* Add better debugging, try starting service

* Try stopping and re-starting the mysql service, the old way

* Check status before running MySQL command

* Now that indeed mysql is not started, make it start

* [DEV-190] Try removing background-running of make run

* Ignore mysql service when not running at the beginning

* [DEV-190] Mount folder with instructions on how to initialize

* [DEV-190] Go back to using normal make run

* Adding -v command to mount volume with init scripts

* Remove SSH terminal to Github Actions instance

* Remove enabling of MySQL

* Remove unnecessary parts from Makefile
2022-06-01 07:00:00 -03:00
Joel Elias Méndez
b620baf5ed
[DEV-268] Fix long loading time in homepage (#1206) 2022-05-30 16:04:32 -03:00
LautaroCesso
d3b47eaa73
[DEV-262] Fix i18n in email header url description (#1212) 2022-05-24 10:20:25 -03:00
LautaroCesso
90e7923aec
[DEV-300] Fix error in upload file ticket comment (#1211) 2022-05-16 17:54:32 -03:00
LautaroCesso
0ecf88237f
[DEV-340] Refactor edit ticket comment no permission validations (#1207)
* Refactor edit ticket comment no permission validations

* Refactor edit comment path
2022-05-16 17:36:06 -03:00
Maximiliano Redigonda
713a5b5ee1
Try to start server before truncate db (#1208) 2022-05-16 17:32:50 -03:00
LautaroCesso
b578a26225
[DEV-339] Fix DropDown style (#1205)
* Fix DropDown style

* xd
2022-05-16 15:07:47 -03:00
Joel Elias Méndez
93fa9e12a3
[DEV-332] Create ruby tests for the new page size parameter (#1196) 2022-05-16 13:45:12 -03:00
Maximiliano Redigonda
3720baf370
[DEV-190] Fix tests (#1204)
* Add sudo

* Try with mysql instead of mysqld
2022-05-11 10:39:50 -03:00
Maximiliano Redigonda
b1ef69c60c
Revert "Fix undefined error (#1129)" (#1203)
This reverts commit a5da776d2734f7aae1091d084f0eea978edb57ff.
2022-05-09 19:19:54 -03:00
Maximiliano Redigonda
615f42a91b
[DEV-190] Fix tests (mysql socket error, initialize mysqld first) (#1202)
* [DEV-190] Fix tests v2

* Try changing to 127.0.0.1

* Try maybe 0.0.0.0 ?

* Try forcing tcp protocol

* Test with equal symbol

* Try with starting the service first

* Add command to start mysqld to both install actions in Makefile

* Remove mounting volume to check who's the culprit
2022-05-05 15:19:16 -03:00
Maximiliano Redigonda
39f8a601db
[DEV-190] Fix tests (#1201)
* [DEV-190] Add extra setup step before running tests

* 🤦
2022-05-04 12:14:03 -03:00
LautaroCesso
c82aaa001e
[DEV-318] Add edit comment tests (#1194)
* WIP Fix ruby tests

* WIP

* WIP

* Add edit ticket comment test
2022-05-04 11:30:37 -03:00
Maximiliano Redigonda
04923b0e9d
[DEV-190] Migrate tests from Travis CI to GitHub Actions (#1198)
* [DEV-315] Create docker routine for frontend that works on Mac

* Add make option to run bash inside container

* [DEV-190] Migrate tests from Travis CI to Github Actions

* Make install step not interactive

* Run build steps before trying to run tests

* Setup vendor permissions prior to running tests

* Add command to setup permissions in files folder too

* Test tests failing

* Move setup vendor permissions into make install, corrects tests

* Revert "Move setup vendor permissions into make install, corrects tests"

This reverts commit 8092cad24cbf73664905e86a188bb1ab79ee9377.

* Revert "Test tests failing"

This reverts commit 57fd123c559be6fd8eb9d5501e426f22f9647a8c.
2022-05-04 11:06:52 -03:00
Maximiliano Redigonda
5e7f39df05
[DEV-315] Create docker routine for frontend that works on Mac (#1175)
* [DEV-315] Create docker routine for frontend that works on Mac

* Add make option to run bash inside container
2022-05-04 11:06:17 -03:00
Maximiliano Redigonda
e190710252
[DEV-299] Fix missing validations (#1195) 2022-05-02 17:41:16 -03:00
Maximiliano Redigonda
a74d6ab4d7
[DEV-328] Fix array offset on null in /user/login (#1185) 2022-05-02 16:48:14 -03:00
Daniele Scasciafratte
a5da776d27
Fix undefined error (#1129) 2022-04-29 16:55:32 -03:00
Guillermo Giuliana
41d7aa5406
[DEV-302] Fix custom filed error message padding (#1190)
* add en translation

* add margin

* add i18n to error

* add minor changes

* add en translations
2022-04-25 18:16:07 -03:00
Guillermo Giuliana
f2adb160be
remove header options when maintenance-mode is on (#1189) 2022-04-25 17:55:35 -03:00
LautaroCesso
62bd70cc3b
[DEV-318] Make staffs able to edit their own content (#1187)
* Fix edit ticket comment permissions

* wip

* Rename ticketEventToArray variable
2022-04-25 12:43:25 -03:00
LautaroCesso
0f6c64674e
Fix ruby tests (#1188)
* Fix ruby tests

* Add pageSize default value
2022-04-25 12:38:44 -03:00
LautaroCesso
861f7dc254
[DEV-214] Fix email template validation (#1136)
* Fix email template validation

* Fix bug with default email templates

* Use template codes instead of selected index in text3 email template field

* Fix bug in no email template forms

* Use componentDidUpdate method
2022-04-19 19:28:51 -03:00
Joel Elias Méndez
83b8e8094b
[DEV-195] Fix php documentation (#1134)
* fix php documentation

* add descriptions to backend docs

* add missing php documentation

* correct some documentation details
2022-04-19 13:18:23 -03:00
Joel Elias Méndez
81cc579d57
[DEV-260] Make modal in CSV import actually check if the password entered is correct (#1155)
* check for correct password when uploading csv file

* allow to import only csv files

* add validation for password in csv import
2022-04-19 13:18:04 -03:00
Joel Elias Méndez
b3c8819d83
[DEV-279] Make ticket table show more tickets overview (#1164)
* make ticket table show more tickets overview

* allow user to choose ticket quantity

* add styles

* fix New Tickets section dropdown

* add commented changes

* correct some code issues
2022-04-19 13:17:48 -03:00
Joel Elias Méndez
86cad910ec
[DEV-222] Search fails when staff has no departments assigned (#1172)
* commented pr

* fix no department assigned issue

* undo irrelevants changes
2022-04-19 13:16:31 -03:00
Guillermo Giuliana
f890fdc2d3
[DEV-161] Add apikey to ticket comment path (#1173)
* update ticket comment path

* update validations

* remove useless line
2022-03-29 13:48:46 -03:00
Maximiliano Redigonda
c64eb9930b
[DEV-258] Add log commands for PHP debugging (#1167) 2022-03-25 19:03:09 -03:00
LautaroCesso
74870c632a
[DEV-290] Fix file attachment in SaaS instances (#1171)
* Update db schema

* Set all AUTO_INCREMENT to 1
2022-03-25 17:25:52 -03:00
Maximiliano Redigonda
e0738f1c88
[DEV-270] Increase token size in file names (#1165) 2022-03-16 09:19:12 -03:00
Maximiliano Redigonda
545f88236d
Changes the order of most calls to assertEquals, minor change in DataStore (#868)
* Changes the order of most calls to assertEquals

* Minor changes in datastore
2022-02-24 09:32:27 -03:00
Joel Elias Méndez
2e5bfa611e
[DEV-216] Fix stat charts in my account (showing only assigned departments) (#1157) 2022-02-23 15:08:51 -03:00
Nícolas Castillo
784d470ba5
Fixed e-mail reply polling (#1154) 2022-02-21 14:12:04 -03:00
LautaroCesso
54a5a9803d
[DEV-239] Remove unexpected error in upload csv file (#1151)
* Remove unexpected error in upload csv file

* Fix bug when render csv file error list

* csv files without eof line work OK

* [DEV-239] Prettify code
2022-02-18 18:33:34 -03:00
Joel Elias Méndez
d5552c0f73
[DEV-215] Email template infinite loading (#1153) 2022-02-15 17:39:00 -03:00
Joel Elias Méndez
daf1db847c
[DEV-236] Modify edit ticket name padding and buttons (#1147) 2022-02-14 14:52:05 -03:00
Joel Elias Méndez
2df12aa5e3
[DEV-234] Ticket status should say ''cancel'' instead of "close" (#1144)
* change button from close to cancel

* add type of closing button
2022-02-09 13:47:21 -03:00
Joel Elias Méndez
c80c026617
[DEV-228] Last activity menu style breaks in some languages (#1145) 2022-02-09 13:45:39 -03:00
Joel Elias Méndez
9cf71dcf66
[DEV-224] Error in edit topic icon color (#1143) 2022-02-08 14:01:43 -03:00
Joel Elias Méndez
a264d384a1
[DEV-130] add current staff to assign list (#1089)
* add current staff to assign list

* create a unique library

* add owner pic to dropdown

* update staffmember list when profilepic changes

* Improve coding

Co-authored-by: LautaroCesso <lautaro_cesso@hotmail.com>
2022-02-08 13:58:21 -03:00
Joel Elias Méndez
d90b7d48c3
[DEV-226] fix modal behavior when pressing enter (#1139) 2022-02-04 12:22:52 -03:00
Joel Elias Méndez
527843f00f
[DEV-217] update tickets when changing departments (#1137)
* update tickets when changing departments

* add loading to ticketlist
2022-02-03 17:19:22 -03:00
kirbypls
3e3a95f518
Add dev4 (#1142) 2022-02-03 03:18:43 -03:00
Guillermo Giuliana
639d40ddb0
[DEV-194] Change same department of a ticket bug (#1135)
* add logic into change-departmetn path

* add ruby tests
2022-01-28 14:22:07 -03:00
Joel Elias Méndez
fbee7275d5
[DEV-128] add padding to ticket-viewer-info (#1133) 2022-01-27 14:53:23 -03:00
Joel Elias Méndez
7df190ea01
[DEV-207] Edit article buttons (#1128)
* edit article buttons

* delete blank spaces

* change article button style

* align buttons

* center header and edit buttons

* fix height of article title

* fix article classnames

* change styles classnames

* improve coding
2022-01-19 12:41:22 -03:00
Joel Elias Méndez
1228d593d0
remove donate link for customers (#1132) 2022-01-19 12:10:40 -03:00
Joel Elias Méndez
c1a7befbed
[DEV-213] add supervised user tooltip (#1131)
* add supervised user tooltip

* change translation
2022-01-19 11:59:52 -03:00
Joel Elias Méndez
6e195a9109
add some loadings (#1127) 2022-01-14 22:19:19 -03:00
LautaroCesso
bf7c1ba8f9
[DEV-198] Change by id order filter (#1108)
* Change by id order in tag selector and tag filter

* Change by id order in departments filter

* Change by id order in owner filter

* Fix requested changes
2022-01-12 21:39:00 -03:00
LautaroCesso
8b4b73402e
Change 4.10.0 to 4.11.0 (#1126) 2022-01-04 13:24:06 -03:00
LautaroCesso
4cf446483d
Update language files (#1125)
* Update language files

* update spanish translations

Co-authored-by: Guillermo Giuliana <guillermogiuliana@hotmail.com>
2022-01-04 13:23:42 -03:00
Joel Elias Méndez
ea6bc7f436
Fix create ticket button issue in create-ticket-form (#1124) 2021-12-30 14:59:37 -03:00
Joel Elias Méndez
37209ef3fc
[DEV-206] Fix bug in login widget transition (#1120)
* fix widget height issue

* Fix bug in login widget transition

Co-authored-by: LautaroCesso <lautaro_cesso@hotmail.com>
2021-12-30 14:57:26 -03:00
Joel Elias Méndez
83e75cc572
fix button issue (#1123) 2021-12-29 17:16:49 -03:00
LautaroCesso
9291aa66a4
[DEV-197] Message bug (#1113)
* Fix message bug

* Add controlled pattern to message component

* Fix bug in admin panel custom fields form message

* Fix bug in dashboard ticket page message

* Fix bug in staff editor messages

* Fix bug in ticket viewer messages

* Fix bug in admin login page messages

* Fix bug in password recovery messages

* Fix bug in main home page login widget messages

* Fix bug in main signup widget messages

* Fix bug in admin panel custom responses message

* Fix bug in admin panel email settings message

* Fix bug in admin panel ban users message

* Fix bug in admin panel ban users message

* Fix bug in main recover password page messages

* Fix bug in dashboard list tickets page message

* Fix bug in dashboard edit profile page messages

* Fix bug in create ticket form messages

* Fix bug in invite user widget messages

* Fix bug in create ticket form messages

* Fix bug in admin panel list users messages

* Fix bug in admin panel system preferences messages

* Fix bug in admin panel advanced settings messages

* Fix bug in install step 3 database message

* Fix bug in install step 5 settings message

* Fix bug in install step 6 admin message

* Fix bug in install completed message

* Fix bug in ticket query list message

* Fix bug in articles list message

* Fix bug in admin pane search tickets message

* Fix bug in admin panel my tickets message

* Fix bug in admin panel new tickets messages

* Fix bug in ticket list messages

* Fix bug in main home page portal message

* Fix bug in dashboard create ticket page message

* Fix bug in main home page messages
2021-12-28 14:03:53 -03:00
Guillermo Giuliana
b9f5f7fcf1
[DEV-205] Users/Staffs should not be able to change the email for one already used by another user/staff (#1121)
* add verification of email on staffs

* add email verification users

* fix inviteStaff ruby test function

* add edit staff ruby tests

* add edit user ruby tests

* update other ruby tests
2021-12-28 00:26:55 -03:00
Guillermo Giuliana
fe1dd1bd48
[DEV-199] Can't put a department in private (#1115)
* add correct validation of repeated name use

* update department create ruby script

* add new edit department ruby tests

* change parameter name of create department ruby scripts

* verify change of private state while editing department ruby tests
2021-12-17 14:10:57 -03:00
Joel Elias Méndez
5dd6b7acdc
[DEV-133] align close button (#1111)
* align close button

* align create ticket buttons

* remove padding excess

* fix align buttons issues

* remove padding excess
2021-12-16 18:03:14 -03:00
Joel Elias Méndez
4bd8df1d5e
[DEV-188] add loading (#1117)
* fix coding issues

* improve coding

* add loading when updating my departments
2021-12-15 15:17:57 -03:00
Joel Elias Méndez
8d7b178fa1
[DEV-151] Invite user modal needs a loading (#1085)
* add loading to modal

* fix coding issues

* fix min-height measures
2021-12-14 15:31:35 -03:00
Guillermo Giuliana
237801e9ed
Revert "add last files (#1110)" (#1114)
This reverts commit 950439bf4776168e7e075c7b944efa7323dfa7e6.
2021-12-13 16:32:53 -03:00
Guillermo Giuliana
950439bf47
add last files (#1110) 2021-12-13 09:57:20 -03:00
LautaroCesso
6cb538616d
Fix message bug (#1109) 2021-12-09 15:23:56 -03:00
Joel Elias Méndez
156b285344
align response actions (#1106) 2021-12-03 12:19:41 -03:00
LautaroCesso
b39e4c2a5f
Fix edit ticket comment (#1107)
* Fix edit ticket comment

* Add some docs comments

* Change some test names
2021-12-02 21:35:37 -03:00
LautaroCesso
5a1b558a6d
Fix some article bugs (#1102) 2021-12-01 20:00:36 -03:00
Guillermo Giuliana
4b9a55b334
[DEV-155] add captcha into login (#1090)
* fix apidoc

* part 1

* pt2

* add classname and css
2021-11-30 01:37:24 -03:00
LautaroCesso
e7daf76274
Fix ticket viewer bug (#1105)
* Fix ticket viewer bug

* Fix ticket viewer bug
2021-11-29 19:49:39 -03:00
LautaroCesso
402af565a9
[DEV-162] Fix ticket comment issues (#1081)
* Fix bug in UI

* Fix bug in backend

* Fix some issues

* Improve coding

* Improve coding
2021-11-26 17:37:33 -03:00
Guillermo Giuliana
f5e9b2602c
[DEV-113] change ticket date according to selected language (#1086)
* fix apidoc

* update tickets date according selected language

* sync language abbreviations

* fix today/yesterday ticket date & hindi bug

* erase useless comments
2021-11-25 17:55:51 -03:00
LautaroCesso
f1d746f9f9
Fix bug in edit email templates (#1101) 2021-11-25 13:57:22 -03:00
Ivan Diaz
c0fc7933ed Use php7.0 for circleci 2021-11-25 11:29:06 +01:00
Ivan Diaz
f31586874e Use old composer file 2021-11-25 11:18:31 +01:00
LautaroCesso
d2c126aad9
Update composer lock (#1100) 2021-11-24 17:28:55 -03:00
LautaroCesso
ffd09b841f
Revert "Update composer lock (#1097)" (#1099)
This reverts commit e23468d3bebd8a19038d70d748d79128f7b72da0.
2021-11-24 17:09:28 -03:00
LautaroCesso
e23468d3be
Update composer lock (#1097)
* update composer lock

* update composer lock
2021-11-24 15:27:16 -03:00
LautaroCesso
84f36e89dc
Fix language settings bug (#1091) 2021-11-24 15:17:38 -03:00
kirbypls
9715cdc9a1
Revert "update composer lock (#1095)" (#1096)
This reverts commit b39dca642b106ad5f21d0520d5e5df0c582041e7.
2021-11-24 15:13:08 -03:00
LautaroCesso
b39dca642b
update composer lock (#1095) 2021-11-24 15:12:02 -03:00
kirbypls
6f6acc925d
Revert "Update package lock by guillegui (#1093)" (#1094)
This reverts commit d2e6dc2fbe5db288cb41160928a189ff0b9f7efd.
2021-11-24 14:28:00 -03:00
Guillermo Giuliana
d2e6dc2fbe
Update package lock by guillegui (#1093) 2021-11-24 14:26:04 -03:00
Guillermo Giuliana
0df57af11e
[DEV-187] Fix duplicated-department-names bug (#1083)
* fix apidoc

* add new custom validation

* add ruby tests

* add frontend error

* take out the ternary
2021-11-24 14:21:16 -03:00
Guillermo Giuliana
8400a1caf0
[DEV-112] Autodetect links into tickets (#1084)
* fix apidoc

* add magicurl editor
2021-11-21 04:01:01 -03:00
Joel Elias Méndez
f045c08b2e
[DEV-147] Add resend user-staff invitation (#1069)
* add resend user-staff invitation

* fix width issue

* fix styles issues

* fix code blank spaces

* improve coding

* improve coding
2021-11-19 14:22:35 -03:00
kirbypls
645e64532b
Update stats-utils.js 2021-11-17 14:33:35 -03:00
Joel Elias Méndez
b9f935df5f
[DEV-153] Fix activity (#1080)
* fix spanish translations

* fix activity

* fix activity

* improve coding

* improve coding

* improve coding

* improve coding
2021-11-17 13:48:37 -03:00
LautaroCesso
edddc8d2c5
[DEV-163] Fix custom fields issue (#1079)
* Fix custom fields issue

* Add custom fields options validation

* Add not blank option validation

* Fix description length
2021-11-17 13:31:39 -03:00
Joel Elias Méndez
eaeaaa647e
[DEV-140] Fix spanish translations (#1074)
* fix spanish translations

* add translations

* add translations

* add more translation
2021-11-16 17:33:22 -03:00
LautaroCesso
d7ccff1a5a
[DEV-26] Update length validations (#1075)
* Update length validations

* Fix language validations

* Remove unnecessary import

* Delete some semicolons
2021-11-11 17:17:39 -03:00
LautaroCesso
c5f1aa2b92
Update font awesome version (#1078) 2021-11-10 14:18:41 -03:00
Guillermo Giuliana
9ed4caf202
[DEV-160] Add user permission into logout path (#1073)
* fix apidoc

* add user verification into logout.php and update ruby tests
2021-11-05 17:06:08 -03:00
LautaroCesso
018863ab3e
Fix email test button (#1076) 2021-11-05 14:44:31 -03:00
Joel Elias Méndez
7015e21966
redirect home after a user signs up (#1070) 2021-11-02 17:39:53 -03:00
Joel Elias Méndez
7cdb6d3603
[DEV-149] add close button to messages (#1071)
* add close button to messages

* improve coding
2021-11-02 17:39:34 -03:00
LautaroCesso
1836849fa5
Delete unused modal (#1072) 2021-11-02 16:33:56 -03:00
Ivan Diaz
9f9e1dbd91
Remove textContent check for non-textarea elements (#1066) 2021-10-21 12:09:13 -03:00
Ivan Diaz
60b1b5eec5
add x-frame-options for cleint php page 2021-10-21 12:07:33 -03:00
Guillermo Giuliana
7b4427d3e3
add php header (#1064) 2021-10-21 11:50:02 -03:00
Joel Elias Méndez
09150d6940
fix message issue (#1063) 2021-10-20 14:10:09 -03:00
Guillermo Giuliana
02cf8f0da3
Update php cookies security (#1056)
* set php cookies

* update edit title doc

* add session renerate id to session createSession function
2021-10-18 22:06:32 -03:00
Guillermo Giuliana
e15bd15f07
Updates to 4.10.0 (#1061)
* change 4.9 to 4.10

* new translations
2021-10-18 22:06:20 -03:00
Guillermo Giuliana
c657d8291f
add controller request secure param (#1060) 2021-10-18 22:05:54 -03:00
Joel Elias Méndez
b2e43430b1
Add email status (#1058) 2021-10-15 15:43:55 -03:00
Joel Elias Méndez
8c17d22ab3
Fix frontend issues (#1055)
* fix login widget bugs

* fix ticket issues

* fix path name
2021-10-14 20:29:57 -03:00
Guillermo Giuliana
143776febe
update api documentation (#971) 2021-10-14 17:57:02 -03:00
Justman10000
6536050fdd
Translation corrected (#1021)
* Corrected translation

* I forgot

* I forgot too

* You already know that Anmelden and Einloggen are the same thing?
2021-10-14 17:52:30 -03:00
Joel Elias Méndez
9a4374d371
[DEV-114] Add "resend verification email" in frontend (#1047)
* resend-verification-token

* force resend email verification

* delete wrong codelines

* improve code

* fix loading
2021-10-14 14:07:23 -03:00
Joel Elias Méndez
b8be664809
fix-response-width (#1054) 2021-10-14 13:29:12 -03:00
Ivan Diaz
e9a1a2e5be
Create SECURITY.md 2021-10-14 02:52:38 -03:00
Joel Elias Méndez
0f976ebde9
Add reopen option after a ticket is closed (#1041) 2021-10-10 15:42:36 -03:00
Guillermo Giuliana
c64f1f1ea6
[DEV-146] resend verification email backend (#1049)
* add path resend signup token

* add ruby tests
2021-10-08 19:07:14 -03:00
Guillermo Giuliana
5d4fe0250b
[DEV-148] Resend invitation backend (#1050)
* add resend-staff-invite path

* add resend-user-invite path

* add departments verification staff invite and ruby test

* add user invite ruby tests

* add resend invite paths and ruby tests
2021-10-08 19:04:25 -03:00
Joel Elias Méndez
af15d0116d
After delete a ticket redirect to previous page (#1044) 2021-10-08 16:09:39 -03:00
Joel Elias Méndez
6ccb389492
[DEV-129] Redirect after ticket is created without user system (#1045)
* Redirect to home/dashboard after ticket is created without user system

* fix min-height in main-home-page-portal component

* Move success messagge

* querystring check

* fix delete ticket button visibility
2021-10-08 16:09:09 -03:00
Joel Elias Méndez
ae076de88f
[DEV-104] - Redirect to home/dashboard after ticket is created without user system (#1037)
* Redirect to home/dashboard after ticket is created without user system

* fix min-height in main-home-page-portal component

* Move success messagge

* querystring check
2021-09-27 19:00:43 -03:00
Joel Elias Méndez
59fb9eaef3
[DEV-129] Remove "Delete ticket" from response form (#1038)
* Delete ticket button for logged user

* define variables
2021-09-27 11:16:25 -03:00
Joel Elias Méndez
ffe7ef8e0b
Change Registration Api Keys name to Api Keys (#1039) 2021-09-27 10:05:23 -03:00
Guillermo Giuliana
27e86c934c
[DEV-131] Fix delete ticket bug (#1035)
* verify ticket author-user before reduce amount of tickets created

* add ruby test

* change geting tickets by id in ruby tests
2021-09-22 18:20:19 -03:00
Ivan Diaz
064f00388a
Fix undefined property check for user 2021-09-18 23:07:09 -03:00
Joel Elias Méndez
fc93ad4c00
[DEV-109] Add Info tooltip warning about tickets not visible (#1031)
* Info tooltip added

* delete blank spaces
2021-09-16 10:50:21 -03:00
Guillermo Giuliana
c22a1cdbb3
[DEV-95] add prevent default when pressing down Enter key (#1033)
* add prevent default when pressing down Enter key

* delete listener and add onkeydown prop
2021-09-15 14:21:19 -03:00
Ivan Diaz
89cb4c18fd Update composer.lock 2021-08-30 00:23:15 -03:00
LautaroCesso
167d7927db
Clear create ticket form after create a ticket with mandatory login disabled (#966)
* Clear create ticket form after create a ticket with mandatory login disabled.

* WIP
2021-08-29 22:12:41 -03:00
LautaroCesso
55c89d58cc
Fix style diff in create ticket form without attachment file button (#974) 2021-08-29 22:11:22 -03:00
LautaroCesso
0bcc775944
Delete date warning (#975) 2021-08-29 22:09:11 -03:00
LautaroCesso
e6441179c9
Fix bug in ticket count in admin panel list users (#967) 2021-01-19 17:15:43 -03:00
LautaroCesso
45a59e0b20
Close panel when inviting user (#960)
* Close panel when inviting user

* Move success invite user message to admin panel list user
2021-01-12 17:39:16 -03:00
Ivan Diaz
9f7b11413c Fix readme version, fix frontend tests 2021-01-08 16:31:06 -03:00
Guillermo Giuliana
e554bb64d1
Release 4.9.0 (#965)
Co-authored-by: CircleCI-BOT <ivan@opensupports.com>
2021-01-08 16:27:24 -03:00
Ivan Diaz
0088332562 Use english as default i18n in case it breaks 2021-01-07 20:53:28 -03:00
Guillermo Giuliana
42c5dd7210
Add updating script 4.9.0 (#964)
* Add updating script 4.9.0

* Update 4.9.0.php
2021-01-07 19:33:20 -03:00
J0WI
1a9e1a852c
Only erase all mails on success (#894) 2021-01-05 21:47:33 -03:00
Guillermo Giuliana
49dc1ab56c
DEV-86 (#959) 2021-01-05 21:02:35 -03:00
dependabot[bot]
1ea4509e4f
Bump axios from 0.18.1 to 0.21.1 in /client (#962)
Bumps [axios](https://github.com/axios/axios) from 0.18.1 to 0.21.1.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v0.21.1/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.18.1...v0.21.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-05 19:58:23 -03:00
Maximiliano Redigonda
c5d6068e97
Add statistics (#904)
* Adds first_closed_at and last_closed_at to Ticket

* Fixes ticket isClosed function

* Adds reopened column to Ticket table

* Adds stats path

* Adds stats for instant tickets

* Adds basic connection with frontend

* Creates cards to display ticketData

* Adds tooltips with descriptions and i18n

* Adds date range filter to backend

* Adds DateRange filter on frontend

* Documents and better structures code

* Makes $dateRange local

* Adds departments filter on backend

* Adds stats path to menu

* Adds first UI for departments filter in stats

* Implements departments filter on frontend

* Improves styling by adding bootstrap rows

* Improves structure of dynamics queries

* Adds tags filter on backend

* Adding TagFilter for statistics WIP

* Adds missing `id` to propTypes TagSelector

* Removes console.warns

* Adapts form to pass tagnames as value as FormField

* Sends tags to API too

* Makes tag-selector change form with tagnames only

* Fixes tag-selector from ticket-viewer

* Removes console.warn

* Removes logs

* Adds owner filter on backend

* Connects owners frontend with backend for stats

* Style changes for date-selector

* Adds tickets by hours stat to /system/stats path

* Adds chart for tickets created on each hour

* Adds better wrap for ticketdata cards

* Adds getAverageFirstReply to backend stats path

* Adds getNumberOfCreatedTicketsByWeekday to backend

* Adds created tickets by weekday chart

* Disables clicking on the legend to toggle data

* Adds base functions for efficiency stats

* Adds getAverageFirstClosed to backend stats

* Adds getAverageLastClosed to backend stats

* Adds table, filters, and groupBy variables to queries

* Adds response structure with mocks

* Adds totalOwners and totalDepartments

* Adds SQL queries to get department/staff hops of a ticket

* Changes incorrect name

* Rolls back addition of near useless function

* Improves tag array management from redux store

* Fix bug in autocomplete filters.

* Sets default date range to current month.

improves date.js.

* Adds i18n

* wip

* Add media query in admin-panel-stats.scss

* Updates date handling in search-ticket-utils

* Makes tooltip open on hover of the entire block

* Fix date range mobile style.

* Add Loading

* Add submit button and clear button in admin panel stats

* Adds tests for stats and comments old ones

* Add test for stats after a ticket has been created

* Makes default dateRange for stats go to the end of the day

* Factors out function to create ticket and adds test

* Adds instant ticket test

* Adds reopened test

* Commit to save technique to test created_by_hour but is prohibitively slow.

* Updates test of created_by_hour to be more lightweight

* Adds test for created_by_weekday

* Fixes default date and renames a function

* Fixes hover bug by extracting card-stat to its own component

* Fix drawbacks with previous change in style - mobile

Co-authored-by: LautaroCesso <lautaro_cesso@hotmail.com>

* Set up 0 as a minimum number for bar chart

* Moves styles from stats cards to the component

* Removes old /system/get-stats path

* Changes name from /system/stats to /system/get-stats

* Restore getCurrentDate in date transformer

Co-authored-by: LautaroCesso <lautaro_cesso@hotmail.com>
Co-authored-by: Ivan Diaz <ivan@opensupports.com>
2020-12-27 16:22:55 -03:00
LautaroCesso
51eee4ed7b
Paginate ticket list in admin panel view user (#944)
Co-authored-by: Ivan Diaz <ivan@opensupports.com>
2020-12-26 19:13:59 -03:00
LautaroCesso
1e0e0134a3
Fix style bug in ticket viewer (#947)
* fix style bug in ticket viewer

* Fix style in invite staff modal

Co-authored-by: Ivan Diaz <ivan@opensupports.com>
2020-12-26 18:25:32 -03:00
LautaroCesso
bc9023b8a6
Auto discard change before delete custom response (#946) 2020-12-26 18:09:22 -03:00
LautaroCesso
534bf3624a
Fix bug custom fields default values undefined (#945)
* Fix bug in selected custom fields in admin panel invite user

* WIP

* Fix style

* WIP
2020-12-26 17:39:51 -03:00
LautaroCesso
a7cb7f376c
Fix bug with discard department change (#942)
* Fix bug with discard department change

* WIP

* WIP

Co-authored-by: Ivan Diaz <ivan@opensupports.com>
2020-12-26 17:39:18 -03:00
Ivan Diaz
c3088406da
Fix ticket date style (#958)
* Fix ticket date style in table componenet

* WIP

* WIP

* Add shortcat date in ticket list

Co-authored-by: LautaroCesso <lautaro_cesso@hotmail.com>
2020-12-26 16:29:30 -03:00
Ivan Diaz
ea8d0719eb
Fix composer for 5.6 (#956)
* Fix composer lock for 5.6 support

* Fix ticketlist test render error
2020-12-24 20:05:28 -03:00
Ivan Diaz
e4a7fe8783 Fix release automation 2020-12-06 20:58:44 -03:00
Ivan Diaz
ca54d19bd9 Fix deploy build 2020-11-26 21:03:26 -03:00
Ivan Diaz
5416ef4009 Add automated release 2020-11-24 00:49:17 -03:00
LautaroCesso
af3d95cf4d
Styles standardization (#934)
* WIP tag create and tag edit pop up

* WIP admin panel email settings

* WIP admin panel viwe article, article list, edit topic, add article

* WIP admin panel list users and invite user widget

* WIP admin panel view user

* WIP admin panel custom fields and admin panel custom field form

* WIP fix plus icon position and type

* WIP admin panel custom responses

* WIP admin panle my tickets and create ticket form

* change check ticket button to green color

* WIP dashboard edit profile page

* Change icon in invite user button

* WIP
2020-11-19 17:37:59 -03:00
Ivan Diaz
3dd76f214d
Remove recaptchakey leftover from test (#937) 2020-11-18 20:52:05 -03:00
LautaroCesso
16435925b6
Group params of open modal function (#935)
* Agrupate params of open modal function

* Add white color prop in modal componenet
2020-11-18 11:47:38 -03:00
Ivan Diaz
ea273970d1
Changing a few texts (#761)
the Spanish version was a mixture of formal, informal and it also had grammatical mistakes. The German version had a few mistakes like 'Fahrkartenummer' which actually means "bus/train ticket number".
2020-11-15 10:51:03 -03:00
Ivan Diaz
b9b21ef950 Add request deployment 2020-11-13 17:32:56 -03:00
LautaroCesso
bb1f5d0ade
Fix bug with departments info in staff editor component (#931) 2020-11-13 14:41:59 -03:00
LautaroCesso
15f765cf85
Fix bug in ticket list in my account and view staff (#921)
* wip

* Delete ticket of staff/get.php response

* Move paginate functions to staff editor component

* wip

* fix assignedTickets and closedTickets values in staff/get-all

* add show closed tickets checkbox in admin panel my account

* WIP

* Add initial api value

* Fix staff/get-all test

* WIP
2020-11-13 14:23:14 -03:00
LautaroCesso
9feb7d6cd4
Add cancel button in api key pop up (#929)
* Add cancel button in api key pop up

* Change space between add api key form buttons

* Add render close button param in open modal container function

* WIP
2020-11-13 14:05:11 -03:00
LautaroCesso
94926f90e6
Fix styles (#916)
* Fix mobile style in admin panle email settings

* Fix style in ticket query filters

* fix style in mobile in ticket query filters
2020-11-13 13:54:54 -03:00
LautaroCesso
b02acfdf7b
Fix bug with private departments in default department feature (#930) 2020-11-12 15:12:15 -03:00
LautaroCesso
560f231e51
Add close ticket button at the end of ticket viewer (#932)
* Add close ticket button at the end of ticket viewer

* WIP

* standardization
2020-11-12 14:13:52 -03:00
Ivan Diaz
7fb7be3860 Add circleci config 2020-10-29 18:30:32 -03:00
Guillermo Giuliana
b8944a3f04
New apikeys permissions (#869)
* back-end  y ruby test

* ruby test

* frontend part

* delete unused component

* resolve relevant  github maxi comments

* delete .catch of getAllkeys

* fix github ivan comments

* add ruby test and minor changes

* search ruby test

* fix name checkticketpermission
2020-10-14 15:08:14 -03:00
LautaroCesso
a64c9f2255
Fix error in searchbox in tickets/search-tickets (#891) 2020-10-02 02:17:22 -03:00
LautaroCesso
994a39ad6d
Add more descriptive error messages when in delete staff. (#892) 2020-09-30 17:35:32 -03:00
Guillermo Giuliana
b73d6d534d
Fix ticket seach query when you are looking for an own ticket that is not in an owned department (#897)
* update departments filter

* update php test
2020-09-29 01:14:06 -03:00
Guillermo Giuliana
d4cdbab203
Indicate it's myAccount when editing staff (#884)
* add myaccount prop

* take out ternary

* user constand insted this.state
2020-09-05 15:37:30 -03:00
Maximiliano Redigonda
80a9a958a8
Revert "Google social login" (#886) 2020-08-28 14:13:23 -03:00
Maximiliano Redigonda
9125944bc3
Revert "Adds Google platform.js script to index.php (#882)" (#885)
This reverts commit d3638787e65ef6afe65ecd2a8e58219ae1c0f84e.
2020-08-28 14:12:10 -03:00
Guillermo Giuliana
c00720d6a2
Minor GitHub bugs (#806)
* text-validation to edit article content

* shows only own department on my account

* add moment and update date

* Update date transformer

* Delete time zone setting.

* Use local date in date range component and utc date in date range filter.

* Fix github comment.

* Fix merge 'Fix filter show bug in ticket search when ordering'.

* Fix merge 'Fix filter show bug in ticket search when ordering' second part.

* Fix getDefaultUTCEndDate function.

Co-authored-by: Ivan Diaz <ivan@opensupports.com>
Co-authored-by: LautaroCesso <lautaro_cesso@hotmail.com>
2020-08-28 07:13:34 -03:00
LautaroCesso
5184c31907
Fix error in edit button for ticket owner. (#872)
* Retrieve staff members after edit staffs.

* Delete retun after throw new RequestException in staff/edit.php

* Resolve github Ivan comments.

* wip
2020-08-28 06:27:04 -03:00
LautaroCesso
817240e0b4
Add loading in Autocomplete when dropdown list is updating. (#863)
* Add loading in Autocomplete when dropdown list is updating.

* Fix error with set state in autocomplete.

* Resolve github Ivan comments.
2020-08-28 05:46:19 -03:00
LautaroCesso
52eae4d242
Thorough ruby tests /ticket/search path (#870)
* Add ticket/search test (ruby)

* Resolve github maxi comments.

* Resolve github maxi comments second part.

* wip

* Rename some test.

* Add query filter test.

* Add test combining multiple parameters.

* wip

* comment query test in search.rb

* comment query test in search.rb second part.
2020-08-28 05:19:47 -03:00
Maximiliano Redigonda
d3638787e6
Adds Google platform.js script to index.php (#882) 2020-08-25 16:08:38 -03:00
Maximiliano Redigonda
68c3975ea4
Merge pull request #860 from opensupports/optimize-ticket-search
Optimize ticket search
2020-08-24 09:40:04 -03:00
Maximiliano Redigonda
6e6d2d83e7
Merge pull request #881 from opensupports/google-social-login
Google social login
2020-08-24 09:34:29 -03:00
Maxi Redigonda
371f111706 Fixes frontend tests by mocking gapi object 2020-08-24 07:50:07 -03:00
Maxi Redigonda
bb89956e9a Fixes admin logout 2020-08-23 18:55:05 -03:00
Maxi Redigonda
a89a465ac9 Adds check to see if email was verified 2020-08-22 00:47:24 -03:00
Maxi Redigonda
9f915a3291 Removes workaround 2020-08-21 00:05:42 -03:00
Maxi Redigonda
0fd23ac6b8 Aligns Google login button 2020-08-20 23:53:44 -03:00
Maxi Redigonda
5021781d25 Implements Google auto signup
If the user doesn't have an account on the system, the first time they
log in with Google, an account will be created.
2020-08-20 23:45:09 -03:00
Maxi Redigonda
47f92569ef Fixes button not showing and re-login 2020-08-20 22:56:31 -03:00
LautaroCesso
c92ecf25dc
Transform staff/user Names Into Hyperlinks. (#875) 2020-08-20 19:15:54 -03:00
Maxi Redigonda
38136ade4d Moves renderGoogleButton out of render 2020-08-20 18:27:43 -03:00
Maxi Redigonda
da534e3018 Adds Google close session functionality 2020-08-20 16:40:15 -03:00
Maxi Redigonda
0518b0ac17 Adds branching for when social login user wasn't created 2020-08-20 16:23:30 -03:00
Maxi Redigonda
5d2e4fcb1f Merge branch 'master' of https://github.com/opensupports/opensupports into social-login 2020-08-20 14:53:39 -03:00
LautaroCesso
7a803dd7ff
Fix security bug, Ticket Number data filtered. (#879) 2020-08-19 23:45:47 -03:00
LautaroCesso
4077dac8c7
Remember me function for staffs (#866)
* fix warning in checbox in form field.

* Add remember me function for staffs.

* Add staff instance in session cookie.

* Add result data staff in get user data in auto login.

* Fix remember me function for user.

* Fix login test rb and add remember me function test in login rb.

* Resolve github maxi comments.
2020-08-19 23:33:40 -03:00
Maxi Redigonda
c4e211518c WIP - Stub to log in with Google 2020-08-19 17:33:38 -03:00
Maxi Redigonda
56ca30a7c3 WIP - validates google token and gets its data 2020-08-19 16:17:44 -03:00
Maxi Redigonda
fad3dce646 Adds google/apiclient to composer json 2020-08-19 16:17:44 -03:00
Maxi Redigonda
deb590ceee Places Google Login button inside login widget 2020-08-19 16:17:44 -03:00
LautaroCesso
e36b984b23 Resolve github maxi comments. 2020-08-18 23:48:06 -03:00
LautaroCesso
e397d45c53 Merge branch 'master' into remember-me-function-for-staffs 2020-08-13 15:57:31 -03:00
LautaroCesso
e4a9366b07 Fix login test rb and add remember me function test in login rb. 2020-08-12 20:32:17 -03:00
LautaroCesso
bbbc845fe7 Fix remember me function for user. 2020-08-12 16:02:39 -03:00
LautaroCesso
a46140381b Add result data staff in get user data in auto login. 2020-08-11 23:51:41 -03:00
LautaroCesso
d72aec3976 Add staff instance in session cookie. 2020-08-11 23:35:10 -03:00
LautaroCesso
1c5d156723
Delete non existent step 7 instalation (#862)
* Delete non existent step 7 instalation

* Add INSTALLATION_COMPLETED_TITLE i18n and delete INSTALLATION_COMPLETED.
2020-08-07 17:29:24 -03:00
Maximiliano Redigonda
f72f2ac074
Merge pull request #867 from opensupports/fix-system-get-logs
Fixes /system/get-logs by adding getFetchAs
2020-08-07 17:25:25 -03:00
Maxi Redigonda
01a494ac23 Fixes /system/get-logs by adding getFetchAs 2020-08-05 16:34:27 -03:00
Maxi Redigonda
4fd9db8651 Assumes all tables exist, updates tests.
An internal function has now a new signature, it receives a query,
and now tests correctly reflect that.
2020-08-03 19:39:48 -03:00
LautaroCesso
01718cf92b Merge branch 'master' into remember-me-function-for-staffs 2020-08-03 12:58:28 -03:00
LautaroCesso
96990c8c04 Add remember me function for staffs. 2020-08-03 12:54:15 -03:00
LautaroCesso
bb9873be4f fix warning in checbox in form field. 2020-08-03 12:52:12 -03:00
Maximiliano Redigonda
4970b18c2d
Merge pull request #861 from chsjr1996/patch-1
Update br.js translation
2020-07-31 19:52:35 -03:00
Carlos H
92e222f10d
Update br.js
Translate remaining "tickets" words
2020-07-31 14:52:49 -03:00
Maximiliano Redigonda
76b7e2c6e7
Merge pull request #859 from opensupports/forbid-get-supervised-tickets-path-to-staffs
Forbids call to /get-supervised-tickets from staff
2020-07-31 12:03:14 -03:00
Maximiliano Redigonda
38c90e2c07
Merge pull request #850 from opensupports/avoid-manual-parsing-of-query-strings-using-library
Avoid manual parsing of query string using queryString library
2020-07-31 12:02:26 -03:00
LautaroCesso
938e25b2fa
Add edit buttons for fields in ticket viewer (#835)
* Fix style color in autocomplete component and dropdown in ticket search and ticket viewer.

* Change style of ticket status edit in ticket viewer.

* Redirect to ticket search when author name was clicked in ticket viewer

* Add i18n ASSIGNED_TO_ME.

* Add content on selected in dropdown component.

* Add styles in edit tags in ticket viewers

* Add edit owner and delete i18n ASSIGNED_TO_ME.

* Redirect to ticket search when owner name was clicked in ticket viewer

* Add style in edit department in ticket viewer.

* Add nav links in owner and author in ticket viewer.
2020-07-30 22:30:48 -03:00
LautaroCesso
1ecf619892
fix autocomplete bug (#855) 2020-07-30 22:13:14 -03:00
LautaroCesso
57b5ea8820
Autocomplete supervised users should not show supervisor user as option (#856)
* Fix bug in supervised user

* Add some line break in admin panel view user.
2020-07-30 21:59:54 -03:00
LautaroCesso
7866880152
Add authors column in dashboard list ticket page. (#857) 2020-07-30 21:52:43 -03:00
LautaroCesso
6d938bead4
Take out remember data of important inputs. (#858) 2020-07-30 21:42:08 -03:00
Maxi Redigonda
5b16393659 Optimizes query to avoid information_schema (-2s)
This actually also corrects a bug (previous to this, the query would
find any table with the correct name in any database).
2020-07-30 15:36:28 -03:00
Carlos H
e3b57bd106
Update br.js
The 'ingressos' word in BR does not represent a "support ticket", but represent a "ticket" of events or places like movie theater, soccer match, etc.

The correct word is already used in this file is "chamados".
2020-07-30 09:26:45 -03:00
Maxi Redigonda
2bf6b2c23f Centralizes calls to expensive functions (-2.5 s). 2020-07-29 15:50:05 -03:00
Maxi Redigonda
384f7c93d7 Avoids redundant call saving 2.5 seconds 2020-07-29 15:36:18 -03:00
Maxi Redigonda
785e2d8ac5 Forbids call to /get-supervised-tickets from staff 2020-07-28 16:36:40 -03:00
Maxi Redigonda
47ea47f971 Avoid manual parsing of query string using queryString library as in the rest of the code. 2020-07-28 15:56:19 -03:00
Guillermo Giuliana
c4a2c48eae
and create tags instead priority script 4.8 (#849)
* and create tags instead priority script 4.8

* change names request ticket ids

* add tag model documentation

* .
2020-07-22 13:24:40 -03:00
Guillermo Giuliana
791e0969e9
add script and change 4.7.0 to 4.8.0 (#848)
* add script and change 4.7.0 to 4.8

* change end of line 4.8.0 script

* Delete main.py
2020-07-22 07:32:18 -03:00
LautaroCesso
302a29db41
Fix filter show bug in ticket search when ordering (#829)
* Fix bug in admin panel search tickets page first part.

* fix show filters in search ticket

* fix a ternary in admin panel search ticket.

* fix a ternary in admin panel search ticket second part.

* Add loading in ticket query filters buttons.

* fix bug in search ticket when ordering.

* Fix error with retrieve staff members in admin panel search tickets.

* get all staff members after staff login.

* Move history listen to index.js and add staff members in local storage.

* Rename className in ticket query filters

* Rename currentSearchObject to currentSearchParams.

* Fix change show tickets in reducer and admin panel search tickets.

* fix get filters for url function in search utils.

* add some tabulations in search tickets utils.

* fix error with initial url in ticket search

* Add empty line at the end of ticket-query-filters.js
2020-07-21 23:07:05 -03:00
Guillermo Giuliana
2e37c35b41
languages (#846) 2020-07-21 22:50:19 -03:00
LautaroCesso
03df5725f7
Fix bugs in change ticket department. (#828)
* fix change department in ticket viewer (FE).

* fix bug change departmet. (BE)

* fix unassigned ticket for any level staff can use it (BE) and add ruby test

* fix ticket department change to un assign ticket correctly (BE) and add ruby test

* fix an error with author id in ticket viewer

* Use get departments for transder in change department params.

* Rename test in change department rb.
2020-07-21 11:04:24 -03:00
Guillermo Giuliana
9634fdfeb0
Add loading list ticket page (#847)
* add loading list ticket page

* add space to prop

* ,
2020-07-21 09:40:05 -03:00
Guillermo Giuliana
5a62ce10b7
and set 4.7.0 ticket model correct (#845) 2020-07-21 08:40:04 -03:00
Guillermo Giuliana
cd73606852
Super user feature (#841)
* add ruby tests

* update ruby test

* backend part

* add get-supervised-ticket path and ruby tests

* update search-authors path and custom validation to get-supervised-tickets

* add supervised users components and css

* add supervised-users messages

* resolve minor bugs get super ticket, ruby tests and change file name

* add supervisor options on user panel

* change supervisor structure

* add pagination dashboard supervisor tickets

* change error name

* add error to path edit-supervised-list

* fix github comments

* resolve github comment backend

* add minor changes dashboard list tickets page
2020-07-21 06:52:34 -03:00
LautaroCesso
507be7bff2
Fix bug in assing ticket dropdown (#840)
* fix assing ticket dropdown bug in ticket viewer.

* add tabulations in render tag
2020-07-16 19:11:07 -03:00
Ivan Diaz
f8342ffa16
Hotfix email polling not working (#839) 2020-07-14 20:34:01 -03:00
LautaroCesso
79527c690d
Restore ticket title state when cancel edit ticket title (#838) 2020-07-14 20:07:29 -03:00
LautaroCesso
14d81cff24
Fix style in edit ticket title and add loading logic. (#827) 2020-07-14 19:19:51 -03:00
LautaroCesso
62497d1263
Delete ticket priorities. (#822)
* Delete ticket priorities.

* Fix style in autocomplete component.

* Fix style in tag selector component.

* Fix style in ticket query filters.

* Fix style in ticket viewer.

* Delete commented lines.
2020-07-06 13:39:38 -03:00
LautaroCesso
57434e2ef7
Fix loading in AreYouSure component. (#826)
* Fix loading in AreYouSure component.

* Fix are you sure loading in some panels and components.

* Delete ticket admin panel custom tags.
2020-07-06 12:16:44 -03:00
LautaroCesso
f5c96f814a
Fix some warnings and errors. (#807)
* Fix error with case sensitive in autocomplete component.

* Fix error in autocomplete test.

* Minor syle change in Tag Selector component.

* Delete ticket/admin-panel-custom-tags and fix some bugs with settings/admin-panel-custom-tags.

* Fix some bugs part 1.

* Replace query2 for queryAtBeginning in search test.

* Fix warning with checkbox value

* fix warnings in admin panel email settings.

* Delete admin panel all tickets page.

* Rename some classNames in admin panel search tickets.

* fix some erros.
2020-07-01 21:01:33 -03:00
LautaroCesso
d3f95bde05
Fix mobile style. (#810)
* Fix header style.

* Fix check ticket mobile style.

* Fix footer style bug.

* Fix admin panel departments.

* Fix admin panel system preferences.

* Fix article list.

* Fix admin panel staff menbers.

* Fix admin panel ban users.

* fix admin panel advance settings.

* Fix ticket viewer first part.

* Fix ticket viewer second part.

* Fix  ticket viewer header.

* fix admin panel departments

* Fix admin panel system preferences second part.

* fix admin panel ban users second part.

* Fix full width.

* fix loading in admin panel email settings.

* Fix admin panel departments second part.
2020-06-25 15:25:24 -03:00
LautaroCesso
82d6f47eea
Fix ticket filters. (#820)
* Fix ticket filters.

* change search ticket title.

* fix search tickets utils.
2020-06-24 19:57:58 -03:00
LautaroCesso
3f68febdd7
Restore index.html (#819) 2020-06-24 18:56:37 -03:00
LautaroCesso
6d476d5d5d
Ticket filters (#795)
* Delete one line break in table.js

* Add status icon in ticket title

* Compare new filters with previous filters

* Add ticket query filters

* Add departments and priority filters.

* Minor style changes in TagSelector and Tag

* Add content on selected items Autocomplete component

* Delete id prop from autocomplete component

* Add DateSelector and DateRange component.

* Delete compare function and add arrow orderby icon.

* Add dateRange and orderBy filters.

* Add minors changes of tabulations

* Minors changes of funtion name and constant name

* Minors changes of funtion name and constant name part 2.

* Transform SearchBox in a controlled component.

* Add Autocomplete and DateRange component int FormField.

* Add preventDefault event in DropDown.

* Create some customs list.

* Add TagSelector in FormField.

* Add private icon in autocomplete departments filters and correct some errors.

* Delete consoleLogs.

* Add ANY and TAGS (i18n).

* Add search-filters reducer.

* Add CLEAR (i18n).

* Add clear form button in ticket search.

* Correrct titel error in ticket search.

* Correrct style submit button in ticket search.

* Submit form not change orderBy.

* Replace AdminPanelAllTicekts to AdminPanelSearchTicket.

* Correct error when query is empty string.

* Change form style.

* Add show filters button.

* Add minors style changes.

* Add autors filters first part.

* Add autors filters second part.

* Apply filters when enter is pressed in searchbox.

* Change prop name to node proptype in tag component.

* Change search filter actions name.

* Change name of render arrow icon in ticketList Component.

* Fix an error with Autocomplete component.

* Restore demo page and system.

* Update ticket list test.

* Fix an error with timeout in autocomplete test.

* Delete custom ticket list in index.html.

* rder the functions in search-filters-reducer.

* Fix store undefine in session-action-test.

* fix import store in session action test.

* Delete unlock icon in ticket list.

* Change style in autocomplete component.

* Change size of lock icon in ticket list.

* Fix error with blacklist in authors autocomplete.

* Comment session action test.

* Fix test in get-authors.rb and searchTest.php

* Add onChange in tag-selector

* Fix some bugs and autocomplete authors.

* Wip

* wip

* wip

* wip

* Fix authors bugs in orderBy filter.

* Delete some consoleLogs in autocomplete component.

* Fix bug with authors filters when clicked custom list menu.

* Fix highlight menu item bug.

* Fix error when custom tickets list is undefined.

* change double quote to single quote in search filters reducer

* Fix error with get authors path in admin panel ticket search

* Add page in ticket search filters

* Join componentDidMount from Ticket QueryFilter and TicketQuery List in AdminPanelSearchTicket and add retrieveSearchTickets action in seartch ticket reducer.

* rename search tickets api to search tickets utils

* correct import of searchFiltersActions

* move some functions to search tickets utils and date transformer

* Fix bug with component did mount when custom tickets list menu option was clicked

* Fix bug with component did mount and closed filters in url

* Fix bug with authors filters in url when did mount search tickets page

* Fix bug with closed urlFilters in search tickets did mount

* Fix bug when change authors in urlFilters

* Fix bug with authors filters in retrieveSearchTickets action

* Delete changeCustomListFilters action

* Fix error with dateRange filters in retrieveSearchTickets action

* Add page in url

* Rename filtesForAPI to filtersForAPI in search filters reducer.

* Update Autocomplete test

* Fix error with get-authors-test

* fix bug with double request when search ticket page was mounted with item selected menu and delete custom param in url when custom ticket list form was edited

* Add separator line between form filters and tickets table and delete console log

* doubles the animation speed of show filters

* Show form filters in search tickets

* delete console log in session action

* wip

* Fix error with ticket list when custom list change.

* Fix bug with hover of date range component.

* Fix Autocomplete test.

* Fix error with autocomplete owners in ticket query filters component.

Co-authored-by: Ivan Diaz <ivan@opensupports.com>
2020-06-24 16:45:55 -03:00
Guillermo Giuliana
7daa53e47d
set ticket author correctly (#817)
* set ticket author correctly

* x
2020-06-24 02:12:13 -03:00
Guillermo Giuliana
b0b3ed7238
Fix 4.7.0 update script (#813) 2020-06-19 14:37:15 -03:00
Ivan Diaz
c7af383ef2
Fix ticket session (#814) 2020-06-19 14:36:49 -03:00
Guillermo Giuliana
17e9dcf53b
fix multiple session handle ticket and update 4.7.0 update script (#812)
* fix multiple session handle ticket

* update 4.7.0 script
2020-06-18 07:08:20 -03:00
Guillermo Giuliana
91d2c203d3
languages update (#811)
* languages update

* add pl translation
2020-06-17 15:05:00 -03:00
Guillermo Giuliana
6a34c24d7d
update script and change 4.6 to 4.7 (#809)
* update script and change 4.6 to 4.7

* fix 4.7.0 script
2020-06-16 21:26:04 -03:00
LautaroCesso
ec51024f93
Add checkbox disabled style (#798)
* Add checkbox disabled style

* Change checkbox disabled color.

* Fix autocomplete test.
2020-06-16 12:52:03 -03:00
LautaroCesso
978a988035
Fix errors in admin panel departments. (#808) 2020-06-16 12:49:30 -03:00
Guillermo Giuliana
1e7a3e2f4a
Default department feature (#805)
* pt 2

* do not allow to delete default depto

* ticket craete changes and ruby tests

* set langauge to ticket  and ruby tests

* show departments/lenaguages dropdown logic frontend

* default department frontend

* fix github comments

* add logic edit-setting

* add settings to reducer

* resolve git hub comments logic frontend

* delete variables without use

* select default department dropdown

* fix ruby tabulation

* delete creaticketform console.log
2020-06-15 16:27:45 -03:00
LautaroCesso
21290c600f
Fix error with buttons in install steps (#800)
* Fix error with buttons in install steps

* Fix error with autocomplete test
2020-06-07 17:29:52 -03:00
LautaroCesso
13019ab446
Fix mobile style and bug with footer. (#785) 2020-06-07 17:29:19 -03:00
Ivan Diaz
09b7a049ed
Fix MailSender 2020-05-16 17:18:52 -03:00
Bernard Spil
6fd392bc15
Fix SMTP for servers that don't need auth (#783)
Currently this fails against servers that don't need or support AUTH but rely on other means to allow relaying email.
2020-05-13 18:30:47 -03:00
Ivan Diaz
1d19d5578b
Add gif support, fix styling issues, fix login test (#784) 2020-05-13 16:08:42 -03:00
Ivan Diaz
eca89ea9b4 Fix docker implementation 2020-05-13 02:51:51 -03:00
LautaroCesso
1f8a95b4da
Fix some warnings and errors. (#776)
* Fix some warnings and errors.

* Fix error with style in password recovery.
2020-05-12 20:22:53 -03:00
Guillermo Giuliana
92a96c276b
Mandatory Login BE and Ruby tests (#757)
* Mandatory Login BE and Ruby tests

* registration handle and remove user-system setting

* create specific paths to mandatory login changing

* BE logic not allow turn off mandatory login without registratrion

* fix github issues

* Delete config['user-system-enabled'].

* Add some tabulations.

* Create MandatoryLoginReducer.

* Replace 'user-system' to 'mandatory-login'.

* Replace user-system toggle to mandatory-login checbox.

* Add some button in the header.

* Change onChange function mandatory login name.

* Disabled checkbox when you should not change it.

* Delete consolelog and some irrelevant lines.

* Change name of mandatory login reducer.

* Change style button in install step 1

* Change style button in install step 2

* Fix loading bug in submmit button.

* Change style button in install step 5

* Change style button in install step 6

* Delete UserSystemEnabled in ticket viewer component.

* Delete UserSystemEnabled in some files.

* Delete onRetriveFail function in main view ticket page.

* Replace user-system-enabled to mandatory-login in some files.

* replace user-system-enabled to mandatory-login in install steps.

* Fix style in dashboard-[Ccreate-ticket-page and dashboard-list-article-page.

* Fix mandatory login issues

Co-authored-by: LautaroCesso <lautaro_cesso@hotmail.com>
Co-authored-by: Ivan Diaz <ivan@opensupports.com>
2020-05-12 19:22:51 -03:00
Guillermo Giuliana
923b9c47c0
Merge pull request #760 from guillegiu/aligned-ticket-content
Fix ticket contents aligment
2020-05-11 20:37:32 -03:00
LautaroCesso
3d06020d5d
Add loading in AreYouSure component. (#771) 2020-05-01 14:56:49 -03:00
Ivan Diaz
482167f765
Merge pull request #724 from LautaroCesso/lautaro
Add ticket create message.
2020-04-20 08:41:44 -03:00
LautaroCesso
a9a0d61a69 Change function to get values of URL. 2020-04-20 07:32:16 -03:00
Guillermo Giuliana
50f5dd24b9
Merge pull request #765 from guillegiu/correct_displaying_of_email_images
fix image displaying
2020-04-16 13:59:31 -03:00
LautaroCesso
212bf0b1d0 Merge branch 'master' of https://github.com/opensupports/opensupports into lautaro 2020-04-16 11:07:34 -03:00
Guillermo Giuliana
64cfd5a8f1 create get-image-paths function 2020-04-16 10:52:33 -03:00
Guillermo Giuliana
2aded07b56 fix image displaying 2020-04-16 10:07:28 -03:00
Alejandro Matos
f31d0aa377
Changing a few texts
the Spanish version was a mixture of formal, informal and it also had grammatical mistakes. The German version had a few mistakes like 'Fahrkartenummer' which actually means "bus/train ticket number".
2020-04-15 01:28:05 -05:00
Guillermo Giuliana
10adc62d90 Fix ticket contents aligments with css 2020-04-12 08:43:49 -03:00
Guillermo Giuliana
33bf2c42dd
Merge pull request #739 from guillegiu/master
Custom Validation and Ruby test - Get authors path
2020-03-03 17:01:48 -03:00
Guillermo Giuliana
5b75e5d732 allow empty query in get authors path, changes names 2020-03-02 19:08:19 -03:00
Guillermo Giuliana
c6c1a57c57 ruby tests get authors path 2020-03-02 17:19:14 -03:00
Guillermo Giuliana
c2ce516b4d add custom validation get authors path 2020-03-02 14:14:29 -03:00
Guillermo Giuliana
bd5db08ba5
Merge pull request #738 from guillegiu/master
Get Authors Path and fix search path query order
2020-03-02 12:02:09 -03:00
Guillermo Giuliana
55e88f2ac6 update search php test 2020-03-02 00:20:55 -03:00
Guillermo Giuliana
22b378e0ba fix string order on ticket search 2020-02-29 16:37:03 -03:00
Guillermo Giuliana
31b58308e8 get authors path 2020-02-29 14:21:36 -03:00
LautaroCesso
954fc27c54 Correct the message path. 2020-02-18 18:00:02 -03:00
LautaroCesso
57098c661e Add ticket create message. 2020-02-11 15:44:22 -03:00
Guillermo Giuliana
e947e72e09
Merge pull request #717 from guillegiu/master
change 4.6.0 to 4.6.1
2020-02-05 17:32:55 -03:00
Guillermo Giuliana
0a4484133b change 4.6.0 to 4.6.1 2020-02-05 17:15:32 -03:00
Guillermo Giuliana
5b1d3d8b50
Merge pull request #715 from guillegiu/master
Fix bugs for 4.6.1
2020-02-05 16:48:58 -03:00
Guillermo Giuliana
aedae876d6 increase length liming content, allowes title-articles with 1 caracted 2020-02-05 16:43:14 -03:00
Guillermo Giuliana
364aca247e allowes contents of 1 caracter length FE 2020-02-05 16:41:06 -03:00
Guillermo Giuliana
a2502bc1c6 add length limit contents 2020-02-05 15:43:25 -03:00
Guillermo Giuliana
dc278db845 shows real pages when you search tickets with a query 2020-02-04 19:11:37 -03:00
Guillermo Giuliana
d520b96932 fix ruby tests and change ticket search departmentvalid functions 2020-02-04 16:22:08 -03:00
Guillermo Giuliana
af0071dec2 show blank title/content error FE 2020-02-03 17:41:40 -03:00
Guillermo Giuliana
ca63c3d08b update names of departments, ticket search path 2020-02-03 17:38:58 -03:00
Guillermo Giuliana
74de20641f fix department filter of ticket search 2020-01-31 21:47:56 -03:00
Guillermo Giuliana
e44559618f Fix blank strings of titles and contents BE 2020-01-31 14:19:48 -03:00
Guillermo Giuliana
72a9b1ef0e fix custom ticket list 2020-01-31 14:15:07 -03:00
Guillermo Giuliana
c941a4792d
Merge pull request #708 from guillegiu/master
change 4.5.0 to  4.6.0 add tables creating into upgrade file
2020-01-25 14:48:54 -03:00
Guillermo Giuliana
2d75f87def change 4.5.0 to 4.6.0 add tables creating into upgrade file 2020-01-24 21:37:05 -03:00
Guillermo Giuliana
a06681085b
Merge pull request #707 from guillegiu/master
add table_schema verification to 4.6.0v upgrade
2020-01-24 18:10:24 -03:00
Guillermo Giuliana
f5ef43b020 add table_schema verification to 4.6.0v upgrade 2020-01-24 17:56:38 -03:00
Guillermo Giuliana
ae34631bca
Merge pull request #705 from guillegiu/master
add owner filter to ticketSearch BE
2020-01-24 16:34:09 -03:00
Guillermo Giuliana
f4b3ea2a65 seach valid staff to validownersid 2020-01-24 15:18:05 -03:00
Guillermo Giuliana
46ca19c2cc add owner filter to ticketSearch BE 2020-01-23 22:26:32 -03:00
Guillermo Giuliana
7ccadadb81
Merge pull request #704 from guillegiu/master
Update Lodash and fix minor bugs
2020-01-23 20:37:45 -03:00
Guillermo Giuliana
9cff8cd789 fix ticket search unitary tests 2020-01-23 19:39:22 -03:00
Guillermo Giuliana
b28e744ff7 change any lodash function 2020-01-23 16:54:17 -03:00
Guillermo Giuliana
c55d8ea964 update lodash 2020-01-23 16:52:16 -03:00
Guillermo Giuliana
b5639051f4 throw error when try to change title for a blank string 2020-01-23 16:51:26 -03:00
Ivan Diaz
03d9127a8b
Merge pull request #703 from guillegiu/master
V 4.6.0 update file
2020-01-23 16:48:19 -03:00
Guillermo Giuliana
23c750e6b9 fix priorities empty array problem 2020-01-23 16:48:18 -03:00
Guillermo Giuliana
fd8b431f55 update 4.6.0 2020-01-23 13:31:12 -03:00
Guillermo Giuliana
c862c0a6fa Merge branch 'master' of https://github.com/guillegiu/opensupports 2020-01-23 13:12:38 -03:00
Guillermo Giuliana
8a6099174f v4.6.0 update version 2020-01-23 12:54:10 -03:00
Guillermo Giuliana
a5078ddbc3
Merge pull request #695 from guillegiu/master
update languages and edit header title of custom ticket list
2020-01-23 12:53:18 -03:00
Guillermo Giuliana
e24ec402e4
Merge branch 'master' into master 2020-01-23 12:43:45 -03:00
Guillermo Giuliana
cd4e86c7bd
Merge pull request #642 from amatosg/patch-1
fixing issues with translations
2020-01-23 00:28:42 -03:00
Guillermo Giuliana
e6466b76ac
Merge pull request #690 from LautaroCesso/master
Add autocomplete component
2020-01-23 00:11:05 -03:00
LautaroCesso
a6c3cfeba9 Restore demo-page 2020-01-22 22:14:27 -03:00
LautaroCesso
ae4aa4ead9 Autocomplete component Finished 2020-01-22 22:07:39 -03:00
Guillermo Giuliana
a77432ab12
Merge pull request #699 from ivandiazwm/master
Add base schema population
2020-01-22 16:35:40 -03:00
Guillermo Giuliana
e7aed6979f fix minor error seting list search ticket feature 2020-01-22 16:27:01 -03:00
Ivan Diaz
71a1d434ef Add base schema population 2020-01-22 14:26:07 -03:00
Guillermo Giuliana
fe31163a8a fix ticket search when staff hasnt deparments and shows own tickets 2020-01-22 01:18:23 -03:00
LautaroCesso
e63809400c Add autocomplete test part three 2020-01-20 15:46:06 -03:00
Guillermo Giuliana
42f8be1870 fix articles aligment 2020-01-20 14:46:29 -03:00
LautaroCesso
118b7e97cf Add autocomplete test part two 2020-01-17 20:41:44 -03:00
LautaroCesso
0dd8e75241 Add autocomplete test 2020-01-16 14:26:01 -03:00
LautaroCesso
780aa64d19 Minors changes og line breack and spaces in tagSelector, autocomplete-dropdown and dropdown 2020-01-14 17:09:06 -03:00
Guillermo Giuliana
546fdb8ab5 update languages and edit header title of custom ticket list 2020-01-13 23:37:42 -03:00
LautaroCesso
179252303c
Merge branch 'master' into master 2020-01-13 22:08:25 -03:00
LautaroCesso
b267a29d06 Add loading, empty list message. (autocompplete component) 2020-01-13 22:00:51 -03:00
Guillermo Giuliana
059a6325e9
Merge pull request #686 from frasiek/patch-1
Update polish translation
2020-01-13 16:48:21 -03:00
Guillermo Giuliana
880caa0388
Merge pull request #688 from guillegiu/master
Edit Ticket Title feature
2020-01-12 04:00:43 -03:00
LautaroCesso
bbe66b4dea Added getItemListFromQuery autocomplete dropdown 2020-01-10 11:33:04 -03:00
Guillermo
8b32d5e86b . 2020-01-09 12:07:29 -03:00
LautaroCesso
8248ec4d03 Added autocomplete tagselector component 2020-01-08 18:11:26 -03:00
Guillermo
f3b70ce497 ticketviewer prop name fail 2020-01-08 16:19:18 -03:00
Guillermo
f87f809b15 simplification of code 2020-01-08 16:16:36 -03:00
LautaroCesso
6ff46163b3 Added itemsSelectedList default 2020-01-08 12:27:11 -03:00
Guillermo
7c1315c244 fix disable user system ruby test 2020-01-08 10:27:39 -03:00
Guillermo
0174233a24 edit title for system without users 2020-01-08 10:09:35 -03:00
LautaroCesso
5705115f73 Added search autocomplete input text. 2020-01-07 22:32:26 -03:00
Guillermo
943c910181 add minor changes 2020-01-07 18:48:59 -03:00
Guillermo
d56c46dba6 Merge branch 'master' of https://github.com/opensupports/opensupports
Conflicts:
	client/src/data/languages/en.js
2020-01-07 18:46:33 -03:00
Ivan Diaz
56b50596cb
Merge pull request #687 from ivandiazwm/create-ticket-api-key
Create ticket api key
2020-01-07 17:38:52 -03:00
Ivan Diaz
4141b31992
Merge pull request #682 from guillegiu/reditect-after-deleting
Reditect after deleting
2020-01-07 17:32:24 -03:00
Ivan Diaz
7b608a6d06 Fix disable-user-system test 2020-01-07 17:29:28 -03:00
Guillermo
eb9a970353 Edit ticket title feature 2020-01-07 17:11:52 -03:00
Ivan Diaz
9ca6632ee9 Fix APIKey mock 2020-01-07 15:09:06 -03:00
Ivan Diaz
19df5857b3 Merge branch 'create-ticket-api-key' of github.com:ivandiazwm/opensupports into create-ticket-api-key 2020-01-07 14:51:56 -03:00
Ivan Diaz
ec98767e25 Fix APIkey mock 2020-01-07 14:51:16 -03:00
Ivan Diaz
51e498f8c9 Fix add-api-key description comment 2020-01-07 14:44:18 -03:00
Ivan Diaz
4d18bc9aa6
Merge branch 'master' into create-ticket-api-key 2020-01-07 14:22:10 -03:00
LautaroCesso
b52a65497e Demopage does work 2020-01-06 12:52:13 -03:00
LautaroCesso
76cfbc8753 Minors changes of line break in tag select and dropdown 2020-01-06 12:48:25 -03:00
LautaroCesso
3435a8f2df Create autocomplete component first part 2020-01-06 12:38:32 -03:00
Michał Fraś
5f1d002339
Update polish translation
correct translations for closing and deleting tickets
2020-01-02 23:18:10 +01:00
Guillermo Giuliana
63313c4675
Merge pull request #685 from guillegiu/show-search-ticket-path
fix ticket search path showing
2020-01-02 10:17:16 -03:00
Guillermo
97d6bd6a36 fix ticket search path showing 2020-01-02 10:09:44 -03:00
Guillermo
af92a6bbf2 part 1 edit ticket title 2020-01-02 09:55:55 -03:00
Guillermo Giuliana
9f4a293107
Merge pull request #678 from guillegiu/master
Add ticket Search Path
2019-12-29 15:58:32 -03:00
Guillermo
cc9c51b856 fix date range validation on php 7.2 2019-12-29 15:48:57 -03:00
Guillermo
8be43bb7ea resolve mirrors errors 2019-12-28 17:50:11 -03:00
Guillermo
702b786202 search ruby test 2019-12-27 22:46:25 -03:00
Guillermo
d5f5d988fb redirect after deleting ticket 2019-12-27 22:07:40 -03:00
Guillermo
e64e8e65f1 update rudy test 2019-12-27 20:51:22 -03:00
Guillermo
9f84239c3e search ruby tests 2019-12-27 19:59:23 -03:00
Guillermo
d25da2c5ff add validation test 2019-12-27 19:50:50 -03:00
LautaroCesso
38c6295a7b
Improved the render in MainSignUpWidget (Issue #576). (#683)
* replace spaces for underscores in edit custom fields

* Create function getCustomFieldParamName

* Improved the render in MainSignUpWidget
2019-12-27 14:46:33 -03:00
Guillermo
f9e8a0abec add department test and escape query 2019-12-26 17:25:00 -03:00
LautaroCesso
843bd13281
Allows space in custom fields name (issue #598) (#681)
* replace spaces for underscores in edit custom fields

* Create function getCustomFieldParamName
2019-12-20 16:49:43 -03:00
Guillermo Giuliana
b9e4a55b91
Merge branch 'master' into master 2019-12-20 11:35:51 -03:00
Guillermo
0bd099d05a add support for custom ticket lists 2019-12-19 17:06:12 -03:00
Guillermo
c8634d457c order departments get all ruby test2 2019-12-17 17:28:02 -03:00
Guillermo
7de248e5b1 order departments get all ruby test 2019-12-17 17:02:46 -03:00
Guillermo
61ce46d5c7 change name Validation priorities 2019-12-17 15:25:45 -03:00
Guillermo
870f5fea46 puts get all ruby test 2019-12-17 11:05:22 -03:00
Guillermo Giuliana
2099af5f3a
Update LinearCongruentialGeneratorTest.php 2019-12-17 10:15:19 -03:00
Guillermo Giuliana
58be6127fb
Delete search.rb 2019-12-17 09:15:52 -03:00
Guillermo
bac138757e fix search php test 2019-12-17 09:04:49 -03:00
Guillermo
2678947f85 ticket search list frontend 2019-12-17 08:35:04 -03:00
Ivan Diaz
682eedba0a
Merge pull request #653 from mredigonda/invite-users-feature
Invite users feature
2019-12-16 23:32:22 -03:00
Ivan Diaz
1bbf4c49d7
Merge pull request #643 from gaetanfl/use-objet-instead-of-titre
Change TITLE translation from titre to objet
2019-12-09 09:02:41 -03:00
Maxi Redigonda
6c04e50ea8 Removes the USER section in the menu when the user system is disabled (previously, it was shown anyways and an error would pop up). Also, temporarily disables statistics until they are fully implemented. 2019-11-21 17:52:52 -03:00
Maxi Redigonda
33af34ab12 Corrects email notifications and unread status on ticket/comment. 2019-11-20 14:24:02 -03:00
Guillermo
5219b91388 add valid departments to staff search 2019-11-19 19:55:47 -03:00
Maxi Redigonda
ec88e02ad4 Removes duplicated code in wrapper component, and redirects users after signup. 2019-11-18 16:34:33 -03:00
Ivan Diaz
e8dab6922b Fix frontend to only use registration api keys 2019-11-16 17:21:38 -03:00
Ivan Diaz
01a0435f57 Add test for APIKeys 2019-11-16 17:16:53 -03:00
Ivan Diaz
7e1749dbd1 Add create ticket APIKey 2019-11-16 16:07:02 -03:00
Maxi Redigonda
44cab04157 Hides invite-user button when user system is disabled in admin panel 2019-11-15 19:14:12 -03:00
Maxi Redigonda
8ea9c6ee9b Fixes redirection when creating a ticket without user system enabled 2019-11-15 18:50:35 -03:00
Guillermo
57d3766c9d unit test for Search ticket 2019-11-14 17:17:45 -03:00
Maxi Redigonda
bc38411275 Merge branch 'master' of https://github.com/opensupports/opensupports into invite-users-feature 2019-11-14 15:11:37 -03:00
Maximiliano Redigonda
8aaacc5b2a
Merge pull request #656 from mredigonda/no-user-system-comment-bug
No user system comment bug - Fix notifications
2019-11-14 14:54:29 -03:00
Maxi Redigonda
995d302a49 Improves code according to Ivan's comments 2019-11-14 11:34:20 -03:00
Maximiliano Redigonda
c56be37340
Merge pull request #658 from mredigonda/update-translations
Fixes languages according to issue #619
2019-11-11 16:12:35 -03:00
Maxi Redigonda
545c910d5e Fixes languages according to issue 619 2019-11-11 12:55:14 -03:00
Guillermo Giuliana
b81c628faa Search ticket path backend 2019-11-10 16:14:30 -03:00
Maxi Redigonda
1d84aa488c Removes checks for mailsender since it should always be connected. 2019-11-08 20:39:28 -03:00
Maxi Redigonda
1c4bd7df17 Fix bug to allow staff members to recover their passwords even if user system is disabled. 2019-11-08 19:42:02 -03:00
Maxi Redigonda
5ab155f8ce Adds test to /system/get-logs 2019-11-08 11:06:47 -03:00
Maxi Redigonda
fa5300a731 Fix small issue, now all names of authors of logs are stored. 2019-11-08 10:57:25 -03:00
Maxi Redigonda
a883f4d430 Creates ticket session when an unlogged user creates a ticket.
Fixes runtime error when there is no author associated with a log.
2019-11-08 10:41:50 -03:00
Maxi Redigonda
bedf55d1ad Makes createLog function store authorName. 2019-11-08 10:08:14 -03:00
Maxi Redigonda
f5f78a549c Adds test to manage tickets when the user system is disabled 2019-11-07 17:43:47 -03:00
Maxi Redigonda
431ef44c8d Makes users able to comment on tickets when there is no user system, and fixes logs 2019-11-07 15:27:26 -03:00
Maxi Redigonda
e12857392b Merge branch 'master' of https://github.com/opensupports/opensupports into invite-users-feature 2019-10-31 21:05:34 -03:00
Maxi Redigonda
364f10f03a Removes outdated commented line adding the /staff/add controller 2019-10-31 16:15:16 -03:00
Maxi Redigonda
87e2984fa4 Removes /staff/add path, updates tests accordingly, and adds log when a staff member is invited 2019-10-31 16:13:04 -03:00
Maxi Redigonda
afa76ce059 Adds MailTexts for the new mail template USER_INVITE for all languages 2019-10-31 12:08:37 -03:00
Maxi Redigonda
643f3c81a9 Minor style changes 2019-10-29 18:23:20 -03:00
Maxi Redigonda
93ade9cf0f Implemented functionality to allow staff members invite other staffs. 2019-10-29 18:23:09 -03:00
Maxi Redigonda
18be18ebf8 Adds log for user invitations 2019-10-29 16:11:37 -03:00
Maxi Redigonda
7eae717b0c Removes ADD USER button, since its functionality is now replaced by the INVITE USER button 2019-10-29 14:39:39 -03:00
Maxi Redigonda
f3a0fbf7da Allows staffs to invite members even when user registration is disabled (the most likely use case) 2019-10-29 14:34:20 -03:00
Maxi Redigonda
d7e1edea01 Changes content displayed in recover-password when the user has been invited, and updates mail texts 2019-10-29 14:23:07 -03:00
Maxi Redigonda
5331c3363e First working version of invite users feature 2019-10-28 15:27:25 -03:00
Maxi Redigonda
aa7cefc959 Fixed PHP documentation for path get-all 2019-10-28 10:49:00 -03:00
Ivan Diaz
0bfb36afd6 Merge branch 'master' of github.com:opensupports/opensupports 2019-10-21 19:35:03 -03:00
Maxi Redigonda
f558e64afc First mock for an 'invite user' button in the list-user view 2019-10-21 17:44:49 -03:00
Gaëtan
fb80336583
Change TITLE translation from titre to objet
Titre is ambiguous in french and can be misinterpreted as job title instead of subject
2019-10-17 16:08:43 +02:00
Alejandro Matos
7e42cd97ea
fixing issues with translations
there was several inconsistencies using formal and informal second person (fixed to use only formal)
2019-10-16 01:41:11 -05:00
Ivan Diaz
a477941b18
Merge pull request #638 from mredigonda/fix-release-bugs
Fix various bugs pre-release 4.5.0
2019-10-14 16:52:28 -03:00
Maxi Redigonda
81a7300b14 Fixes tests to meet requirements on color format, and add new test to rule out for invalid formats 2019-10-14 11:17:16 -03:00
Maximiliano Redigonda
d9becc4e45
Merge pull request #639 from guillegiu/master
fix release v4.5.0
2019-10-14 10:06:03 -03:00
Maxi Redigonda
ec3b1ec32a Adds validation for colors when editing/creating tags, and creates the INVALID_COLOR error 2019-10-14 10:02:01 -03:00
Maxi Redigonda
22efd7ea93 Fix lack of sanitization for path ticket/add-custom-response 2019-10-10 19:48:33 -03:00
Guillermo Giuliana
d0a6dd06d8 fix Staff class,canManageTicket function to camelcase form.fix bug edit commment FE id ternary 2019-10-10 18:22:04 -03:00
Maxi Redigonda
9041c21b8b Fix lack of sanitization for path ticket/edit-comment 2019-10-10 16:03:44 -03:00
Guillermo Giuliana
4b2d27e757 fix ticket seen path and tests 2019-10-10 15:24:50 -03:00
Guillermo Giuliana
620cd6b876
Merge pull request #637 from guillegiu/master
release v4.5.0
2019-10-09 13:26:04 -03:00
Guillermo Giuliana
b2483f58c7 add migration script to V4.5.0 2019-10-08 20:20:05 -03:00
Guillermo Giuliana
efbe553076 release v4.5.0 2019-10-07 20:21:43 -03:00
Maxi Redigonda
f2421b19f0 Fixes build.sh 2019-10-07 19:05:32 -03:00
Ivan Diaz
b9104c4c8a Merge branch 'master' of github.com:opensupports/opensupports 2019-10-07 18:43:59 -03:00
Maximiliano Redigonda
bbf20bb1de
Merge pull request #636 from mredigonda/update-build-sh
Update build sh
2019-10-07 18:33:38 -03:00
Maxi Redigonda
a992cb3f31 Rewrote comments 2019-10-07 17:43:05 -03:00
Maxi Redigonda
89fa93bc56 Updates build.sh for the new release 2019-10-07 17:37:16 -03:00
Ivan Diaz
2ffb88c979
Use single build file (#635) 2019-10-07 14:50:10 -03:00
Ivan Diaz
c475d0e35c Use single build file 2019-10-07 14:47:28 -03:00
Maximiliano Redigonda
68281e0985
Merge pull request #631 from mredigonda/add-polish-language
Adds polish language and updates other languages automatically
2019-10-05 15:45:32 -03:00
Maxi Redigonda
eb055b1f79 Fix google translation with format problems 2019-10-05 15:39:54 -03:00
Maximiliano Redigonda
896802f793 call get settings path on user login (#633)
* fix ticketEventId submit

* staff allow manage ticket feature

* edit-comment log

* fix staff1/2 change own perfile pic

* Makes frontend allow ticket editing for any staff member

* Allows all staff members in charge of the department of a ticket to manage it (change its department, priority, comment on it, etc.)

* fix comments github pt1

* tests ruby

* fix

* Fix ruby tests

* add commenteed tests

* call get setttings path on user login
2019-10-05 15:31:46 -03:00
Guillermo Giuliana
201d6367de Merge branch 'master' of https://github.com/opensupports/opensupports 2019-10-05 14:44:22 -03:00
Guillermo Giuliana
8c35449ce4 call get setttings path on user login 2019-10-05 14:32:03 -03:00
Maxi Redigonda
ec0b50fd73 Fix typo 2019-10-05 13:35:45 -03:00
Maxi Redigonda
60de566446 Updated pl.js language file to follow the exact patern as that of en.js 2019-10-03 22:30:04 -03:00
Maxi Redigonda
d307b01832 Adds polish language and updates other languages automatically 2019-10-02 12:54:55 -03:00
Guillermo Giuliana
63ef66198a Remove special functionality of "ticket assignment", other minor fixes (#587)
* fix ticketEventId submit

* staff allow manage ticket feature

* edit-comment log

* fix staff1/2 change own perfile pic

* Makes frontend allow ticket editing for any staff member

* Allows all staff members in charge of the department of a ticket to manage it (change its department, priority, comment on it, etc.)

* fix comments github pt1

* tests ruby

* fix

* Fix ruby tests

* add commenteed tests
2019-10-01 16:40:30 -03:00
Guillermo Giuliana
875a45451a add commenteed tests 2019-10-01 14:52:24 -03:00
Guillermo Giuliana
fdfe3a0ed2 Fix ruby tests 2019-10-01 12:50:25 -03:00
Guillermo
97aa52e0db Merge branch 'master' of https://github.com/guillegiu/opensupports 2019-09-26 23:41:17 -03:00
Guillermo
5ff7d114e5 fix 2019-09-26 23:18:26 -03:00
Guillermo Giuliana
309440caf7
Merge pull request #9 from opensupports/master
Update master
2019-09-20 19:03:37 -03:00
Guillermo
844de1e10f tests ruby 2019-09-20 16:58:21 -03:00
Guillermo
c0f1f932c6 fix comments github pt1 2019-08-22 16:17:45 -03:00
Ivan Diaz
2e4817b144
Merge pull request #606 from ivandiazwm/master
Update readme
2019-08-13 19:56:19 +02:00
Ivan Diaz
5fe5ae7f32 Update readme 2019-08-13 19:55:44 +02:00
Ivan Diaz
514a278574
Merge pull request #600 from mredigonda/ui-tags-and-articles
UI articles
2019-08-11 13:40:04 +02:00
Ivan Diaz
051f2f567c
Merge pull request #604 from ivandiazwm/master
Fix font-awesome issue
2019-08-10 15:00:40 +02:00
Ivan Diaz
46db3bf27e Fix font-awesome issue 2019-08-10 14:59:12 +02:00
Ivan Diaz
fbfc32c4f8
Merge pull request #603 from ivandiazwm/master
Fix font-awesome issue
2019-08-10 14:47:00 +02:00
Ivan Diaz
a8859fa6a1 Fix font-awesome issue 2019-08-10 14:46:16 +02:00
Maxi Redigonda
3c82e87d08 Makes the fix more general to apply for the user version too 2019-08-05 22:11:31 -03:00
Maxi Redigonda
73f62d5463 Fixes UI for overflow of extremely long titles of articles and article content 2019-08-05 22:06:04 -03:00
Ivan Diaz
45f846c2d9
Merge pull request #594 from ivandiazwm/master
Implement webpack and fix department ticket filter
2019-08-05 23:04:45 +02:00
Ivan Diaz
b7b4161cd9 Fix mocha version 2019-08-05 22:14:13 +02:00
Ivan Diaz
0278da8942 Updates test implementation 2019-08-04 18:04:13 +02:00
Ivan Diaz
596aaf6a7f Add webpack publicPath 2019-07-26 21:47:55 +02:00
Ivan Diaz
b81a28114f [TR-22] - Fix department filter issue 2019-07-26 12:49:43 +02:00
Ivan Diaz
59413434b0 Implement webpack 2019-07-25 21:35:51 +02:00
Guillermo Giuliana
4c3049a4fa
Merge pull request #8 from mredigonda/patch-guillermo
Patch guillermo
2019-07-21 02:52:47 -03:00
Maxi Redigonda
c70e9a444d Allows all staff members in charge of the department of a ticket to manage it (change its department, priority, comment on it, etc.) 2019-07-11 19:42:09 -03:00
Maxi Redigonda
5073188d71 Makes frontend allow ticket editing for any staff member 2019-07-11 18:16:35 -03:00
Guillermo
36cfb1bcbe fix staff1/2 change own perfile pic 2019-07-07 22:06:29 -03:00
Guillermo
1106cd89b8 edit-comment log 2019-07-05 17:56:03 -03:00
Guillermo
53e88a78f7 staff allow manage ticket feature 2019-07-04 20:22:38 -03:00
Guillermo
b495b83a93 fix ticketEventId submit 2019-07-02 16:55:10 -03:00
Ivan Diaz
74f26fb3f5
Merge pull request #569 from guillegiu/master
Edit tags feature
2019-07-01 23:06:55 -03:00
Guillermo
69d0c58172 fix test ruby 2019-07-01 20:38:34 -03:00
Guillermo
94a8cd4431 edit comment ticket feature 2019-06-26 22:04:56 -03:00
Guillermo
37ab1c5817 edit stags feature 2019-06-13 03:23:55 -03:00
Ivan Diaz
6e0b7b7c80 Reduce SMTP timeout 2019-03-10 19:15:46 -03:00
Ivan Diaz
5e356da79a Fix custom fields not showing 2019-03-06 16:25:25 -03:00
Ivan Diaz
863a43c501
Merge pull request #483 from ivandiazwm/master
Realese 4.4.0 and disable stats
2019-03-06 13:47:49 -03:00
Ivan Diaz
28d50e7d24 Release v4.4.0 2019-03-06 12:50:25 -03:00
Ivan Diaz
0a5928f0bb Disable statistics 2019-03-06 12:41:53 -03:00
Ivan Diaz
f40e2228d5
Merge pull request #481 from ivandiazwm/master
Pre-release fixes
2019-03-03 16:04:22 -03:00
Ivan Diaz
3e94369f6b Fix languages key duplications 2019-03-03 15:11:46 -03:00
Ivan Diaz
600ea95b16 Merge remote-tracking branch 'origin/master'
Conflicts:
	client/src/data/languages/de.js
	client/src/data/languages/es.js
	client/src/data/languages/fr.js
	client/src/data/languages/gr.js
2019-03-03 13:27:40 -03:00
Ivan Diaz
a89973d366 Fix frontend tests with connect function 2019-03-03 13:24:07 -03:00
Ivan Diaz
f99767e884 Fix ticket not showing issue 2019-03-03 12:14:24 -03:00
Ivan Diaz
223a39ace9 Fix languages format 2019-03-03 11:53:32 -03:00
Ivan Diaz
19a083996c wip languages 2019-03-01 20:10:35 -03:00
Ivan Diaz
a355fc30da
Merge pull request #479 from ivandiazwm/guillermo-master
Guillermo master
2019-03-01 02:46:28 -03:00
Ivan Diaz
3918bdcd61 Merge branch 'master' of https://github.com/guillegiu/opensupports into guillermo-master 2019-03-01 02:43:24 -03:00
Ivan Diaz
5d3a805285 Fix styling of custom tags 2019-03-01 01:15:03 -03:00
Ivan Diaz
3b84142004
Merge pull request #478 from ivandiazwm/master
Bug fixing
2019-02-28 23:41:22 -03:00
Ivan Diaz
9cce0ccc84
Fix mocha version for travis ci 2019-02-28 23:39:02 -03:00
Ivan Diaz
30dcca3ea3
Fix variable name 2019-02-28 23:31:01 -03:00
Ivan Diaz
133d59e4a7
Remove debbungig console.log 2019-02-28 23:27:03 -03:00
7JuanCruz7
56f6aabd01
Merge pull request #436 from florisvdk/patch-1
Fix dutch translation
2019-02-28 23:14:48 -03:00
7JuanCruz7
2b0f71d158
Merge pull request #443 from dustynjcollins/master
Update en.js
2019-02-28 23:14:15 -03:00
7JuanCruz7
1f97a63983
Merge pull request #416 from kenlog/patch-1
Update it.js
2019-02-28 23:12:06 -03:00
Ivan Diaz
d96e8b44b1 create custom tag admin editor 2019-02-27 17:38:45 -03:00
Guillermo
36d09ec919 tag tests ruby 2019-02-25 13:34:22 -03:00
Guillermo Giuliana
1d3d18b345
Merge pull request #6 from ivandiazwm/guillermo-master
add-tag path
2019-02-24 22:32:37 -03:00
Ivan Diaz
5bf8ff94bc display tags in frontend 2019-02-22 18:26:06 -03:00
Ivan Diaz
fad0e8eafb remove tag path 2019-02-22 14:08:49 -03:00
Ivan Diaz
7092d19c27 Merge remote-tracking branch 'guillermo/master' into guillermo-master 2019-02-21 19:38:28 -03:00
Ivan Diaz
c970c9923f API path AddTag for tickets 2019-02-21 19:37:53 -03:00
Ivan Diaz
4627405242 Merge branch 'custom-tags' into guillermo-master
Conflicts:
	server/libs/validations/dataStoreId.php
	tests/init.rb
2019-02-21 17:32:10 -03:00
Ivan Diaz
4b80a9b397 Fix #460 issue: Unable to edit email templates with two text fields 2019-02-20 14:18:14 -03:00
Ivan Diaz
eed0fdce03 Fix frontend fixtures 2019-02-20 13:16:04 -03:00
Guillermo Giuliana
a43829c288
Merge pull request #5 from ivandiazwm/guillermo-master
tag selector styling
2019-02-20 11:10:25 -03:00
Ivan Diaz
956dac1600 User dropdown for tag selector 2019-02-20 03:03:29 -03:00
Ivan Diaz
e35e843a29 tag selector styling 2019-02-19 19:06:19 -03:00
Guillermo
ba5750a20d tag selector component pt1 2019-02-19 11:41:11 -03:00
Ivan Diaz
9a116c5c29
Merge pull request #458 from ivandiazwm/master
Custom field tests
2019-02-15 22:33:59 -03:00
Ivan Diaz
28f26d0956 Fix custom tags tests errors 2019-02-14 19:45:52 -03:00
Ivan Diaz
ef023d139b Fix custom field description 2019-02-13 20:17:19 -03:00
Ivan Diaz
f6f8262880 Add custom fields test 2019-02-13 19:21:59 -03:00
Ivan Diaz
673a92c196 Remove file uploading from email polling 2019-02-10 20:52:42 -03:00
Guillermo
fcf6b887db Merge branch 'master' of https://github.com/opensupports/opensupports into customFieldsTests 2019-02-05 17:13:24 -03:00
Ivan Diaz
a4c44fb9ab
Merge pull request #452 from ivandiazwm/master
Custom fields feature
2019-02-03 18:37:35 -02:00
Ivan Diaz
623a81b51d Add Custom Fields feature 2019-02-03 16:47:29 -03:00
dustynjcollins
8a195f829b
Update en.js
Spelling error, Typo 'for' changed to 'to'
2019-01-24 11:26:09 -05:00
Ivan Diaz
58c6f2e63f wip 2019-01-18 20:58:30 -03:00
Guillermo
57feafd453 frontend 1part 2019-01-15 20:44:21 -03:00
Ivan Diaz
0339ef366f
Merge pull request #439 from ivandiazwm/master
Email Polling feature
2019-01-15 16:15:52 -03:00
Ivan Diaz
08a6f7b8d0 Add email polling deletion 2019-01-12 04:49:25 -03:00
Ivan Diaz
2ceb73edd7 Remove email host values from ruby test 2019-01-12 04:43:02 -03:00
Ivan Diaz
466c37cfeb Fix email fields 2019-01-12 04:36:33 -03:00
Ivan Diaz
17e2dabeae Update installation email settings, add languages 2019-01-12 01:28:23 -03:00
Ivan Diaz
c7f489d988 Add fronted email configuration 2019-01-12 00:38:33 -03:00
Floris Van der krieken
5c03a29f3a
Fix dutch translation
There was a translation that was not dutch.
2019-01-02 20:11:43 +01:00
Ivan Diaz
429796aee8 Email feature - wip 2018-12-23 21:44:59 -03:00
Guillermo
a85386a162 Merge branch 'master' of https://github.com/opensupports/opensupports into feature146 2018-12-18 23:59:36 -03:00
Guillermo
234de7ed2c feature#146 backend 2018-12-12 01:23:58 -03:00
Valentino Pesce
31dd690cf2
Update it.js
Hi, I'm Italian mother tongue. I made some corrections. A little help, for a great job!
2018-12-04 19:35:39 +01:00
Ivan Diaz
7df893f18a [HOTFIX] - Fix installation issue 2018-12-02 15:49:47 -03:00
Ivan Diaz
004be8a55a
Merge pull request #407 from ivandiazwm/master
Fix private department issues
2018-11-29 18:53:08 -03:00
Ivan Diaz
0780863257 Fix private department issues 2018-11-29 18:52:34 -03:00
Ivan Diaz
c6435dbe6b
Merge pull request #406 from ivandiazwm/master
Fix mentions parser, add test
2018-11-29 15:39:19 -03:00
Ivan Diaz
a309314e16 Fix mentions parser, add test 2018-11-29 15:38:07 -03:00
Ivan Diaz
7b7dc334d8 Merge branch 'master' of github.com:opensupports/opensupports 2018-11-29 14:22:19 -03:00
Ivan Diaz
e8365fc24e Hide logs 2018-11-29 14:20:40 -03:00
Ivan Diaz
287c5afb2a
Merge pull request #405 from ivandiazwm/master
Prepare v4.3.2 release
2018-11-29 13:45:25 -03:00
Ivan Diaz
e47df8e6dc Prepare v4.3.2 release 2018-11-29 13:35:14 -03:00
Ivan Diaz
761c3e4792 Fix get-new-tickets empty list error 2018-11-29 00:31:00 -03:00
Ivan Diaz
60036873b0 Fix #297 safari issue 2018-11-29 00:21:20 -03:00
Ivan Diaz
7439afff63 Merge branch 'master' of github.com:opensupports/opensupports 2018-11-29 00:09:53 -03:00
Ivan Diaz
cc2fc2c295
Merge pull request #402 from guillegiu/master
features #340
2018-11-28 00:15:48 -03:00
Ivan Diaz
e0d4a46929
Merge pull request #383 from SamMousa/singleton-trait
Refactor singleton functionality into trait
2018-11-28 00:15:00 -03:00
Guillermo
aa795c3099 Revert "Delete edit-topic.php"
This reverts commit 80934ff3f85e891e69b7114c6b45483e8ed53397.
2018-11-26 22:47:39 -03:00
Guillermo Giuliana
80934ff3f8
Delete edit-topic.php 2018-11-26 17:08:56 -03:00
Guillermo
50818d162d Merge branch 'master' of https://github.com/guillegiu/opensupports 2018-11-26 17:02:55 -03:00
Guillermo
d2b6f1cc30 fix tst 2018-11-26 15:09:34 -03:00
Guillermo
1bc30240bd fix test 2018-11-26 02:43:37 -03:00
Ivan Diaz
cbdda1e1dc Merge branch 'master' of github.com:opensupports/opensupports 2018-11-25 18:39:35 -03:00
Ivan Diaz
de47dea0c7
Merge branch 'master' into master 2018-11-25 18:31:42 -03:00
Ivan Diaz
7fcfe6a283
Merge pull request #391 from guillegiu/articles-331
Articles 331
2018-11-25 18:20:05 -03:00
Guillermo
f9e74d8758 test 2018-11-24 23:04:12 -03:00
Guillermo
ba1d72d004 Merge branch 'master' of https://github.com/opensupports/opensupports into articles-331 2018-11-24 02:36:01 -03:00
Guillermo
6596e29d9c fix test 2018-11-23 22:44:40 -03:00
Guillermo Giuliana
80b6bcea8a
Merge branch 'master' into master 2018-11-23 21:36:40 -03:00
Guillermo
79569fcfde tests 2018-11-23 20:43:06 -03:00
Guillermo
b4dc92b8f1 features #340 2018-11-23 19:44:10 -03:00
Ivan Diaz
540a91ccd5
Merge pull request #401 from mredigonda/mentions-parser
Fixes mentions-parser to ignore alphabetic characters
2018-11-23 17:54:02 -03:00
Ivan Diaz
ca80e3ed1f Use custom exception on response 2018-11-22 16:00:40 -03:00
Ivan Diaz
fb3f1a9c16
Merge pull request #398 from Mte90/patch-1
image will fit the comment container
2018-11-22 15:51:44 -03:00
Maxi Redigonda
e28e1bbeb1 Fixes mentions-parser to ignore alphabetic characters 2018-11-20 20:10:35 -03:00
Ivan Diaz
ace895a4a2 Use own exception class 2018-11-20 19:41:21 -03:00
Daniele Scasciafratte
b2f9663001
image will fit the container 2018-11-19 17:41:23 +01:00
Sam Mousa
1a5f38f6de
Improved singleton implementation 2018-11-19 14:27:42 +01:00
Sam Mousa
5c88c8ac2a
Make constructor protected 2018-11-19 14:14:27 +01:00
Sam Mousa
245254fa57
Merge branch 'master' into singleton-trait 2018-11-19 14:04:44 +01:00
Ivan Diaz
4cfa152164
Merge pull request #395 from ivandiazwm/master
Fix login with rememberToken
2018-11-18 16:58:53 -03:00
Ivan Diaz
77a388e225 Merge branch 'master' of github.com:opensupports/opensupports 2018-11-17 13:21:09 -03:00
Ivan Diaz
54cc704dc6 fix tests 2018-11-17 13:20:47 -03:00
Ivan Diaz
7dd88a8f82
Merge pull request #389 from mredigonda/mentions-parser
Mentions parser
2018-11-17 13:13:48 -03:00
Maxi Redigonda
e1dffaef16 Parses ticket comments to create links for mentioned tickets 2018-11-17 12:30:52 -03:00
Maxi Redigonda
b56ff24a7d Parses text from the content of a ticket to add links to mentioned tickets 2018-11-17 12:21:07 -03:00
Ivan Diaz
4251e3b5e7 Fix login with rememberToken 2018-11-16 19:34:07 -03:00
Ivan Diaz
59d59b6a7e
Merge pull request #390 from ivandiazwm/master
Email Templates refactor / Ticket listing pagination
2018-11-16 17:45:01 -03:00
Ivan Diaz
048d18e3cb Add php7.2 to travis ci 2018-11-16 17:44:44 -03:00
Ivan Diaz
85eced56ff Fix mailtemplate tests 2018-11-16 17:24:43 -03:00
Ivan Diaz
a187e06ce3 Solve merge conflicts 2018-11-16 16:53:40 -03:00
Ivan Diaz
1533331e1d
Merge pull request #384 from SamMousa/use-composer-autoloader
Use composer autoloader for source files (except tests)
2018-11-16 15:44:37 -03:00
Ivan Diaz
a756ac7210 Remove LCG from file uploading 2018-11-16 15:12:15 -03:00
Guillermo
7d949c0719 minor changes feature #331 2018-11-15 23:52:46 -03:00
Guillermo
e855966561 Merge branch 'master' of https://github.com/guillegiu/opensupports 2018-11-15 20:53:10 -03:00
Guillermo
e9dfbfb97b wip 2018-11-15 20:51:44 -03:00
Ivan Diaz
e8848d898e Paginate all ticket list request, avoid returting too much data 2018-11-15 20:33:08 -03:00
Ivan Diaz
5f0996f243 solve merge conflics with languages 2018-11-15 17:09:10 -03:00
Ivan Diaz
221fee715d
Delete main.py 2018-11-15 16:33:02 -03:00
Ivan Diaz
4d2d0eac31
Merge pull request #342 from mredigonda/closed-tickets-filter
Adds a button to filter opened/closed tickets
2018-11-15 16:32:06 -03:00
Maxi Redigonda
0657857981 Deleting files uploaded by mistake :'( 2018-11-15 16:22:35 -03:00
Maxi Redigonda
886d372f38 Merge branch 'closed-tickets-filter' of https://github.com/mredigonda/opensupports into closed-tickets-filter 2018-11-15 16:20:13 -03:00
Ivan Diaz
c436365c07
Merge pull request #379 from ivandiazwm/v4.3.1
Fix ticket number collision #378
2018-11-15 16:19:11 -03:00
Ivan Diaz
aa86fd9763 Retrieve tickets individually for users 2018-11-15 16:15:46 -03:00
Ivan Diaz
a2e505c33d Fix custom responses #388 2018-11-15 12:53:23 -03:00
Ivan Diaz
9d67a19c35 Fix #388 email templates langauge selection 2018-11-15 10:39:11 -03:00
Maxi Redigonda
e8c1ffeea7 Adds parsing of mentions in articles from the admin panel 2018-11-15 10:05:39 -03:00
Ivan Diaz
8654433702 Add email template image header edition 2018-11-14 22:59:00 -03:00
Maxi Redigonda
3cc622876c Adds mentions-parser lib app, used to parse ticket numbers preceded by a hashtag and transform them to links 2018-11-14 20:39:42 -03:00
Ivan Diaz
614b5a1a67 Refactor mail templates frontend, update backend refactor 2018-11-14 19:58:32 -03:00
Ivan Diaz
e6b23c7f29 Refactor backend of mailtemplates, addd removal of last logs 2018-11-14 12:28:01 -03:00
Sam Mousa
13bd53b326
Removed some more includes; restore loading controller groups manually 2018-11-12 17:46:44 +01:00
Sam Mousa
f5d77bdd54
Add SingletonTrait to composers' autoloader 2018-11-12 17:18:19 +01:00
Sam Mousa
5d83143664
Use composer autoloader for source files (except tests) 2018-11-12 17:14:30 +01:00
Sam Mousa
9808ff5923
Refactor singleton functionality into trait 2018-11-12 16:58:56 +01:00
Ivan Diaz
b09ccdec02 Fix ticket number collision #378 2018-11-08 18:42:48 -03:00
Guillermo
6e8c3279da feature #331 2018-11-08 14:51:04 -03:00
Maxi Redigonda
917520601e Fixes order of tickets retrieved by get-all-tickets 2018-11-07 10:34:14 -03:00
Ivan Diaz
c70868d3fa
Merge pull request #370 from guillegiu/master
Feature #311
2018-11-02 00:14:42 -03:00
Guillermo
71689e021e Feature #311 2018-10-30 16:05:34 -03:00
Guillermo Giuliana
c850fb30a9
Merge pull request #4 from ivandiazwm/guillermo-master
Fix delete
2018-10-30 14:52:31 -03:00
Ivan Diaz
099dd5a5a0 Fix ticket delete test issues 2018-10-29 19:32:03 -03:00
Ivan Diaz
11c4401bfc Merge branch 'master' of github.com:guillegiu/opensupports into guillermo-master 2018-10-29 11:33:51 -03:00
Guillermo
b8bac44d43 s 2018-10-29 11:32:31 -03:00
Maxi Redigonda
5680660e5e Adds i18n for show_closed_tickets 2018-10-27 22:27:50 -03:00
Maxi Redigonda
b2fb45262b Adds i18n for show_closed_tickets 2018-10-27 22:25:29 -03:00
Maxi Redigonda
1edf282942 Revert "Deletes deprecated search-tickets path"
This reverts commit debe851546b95408e1039e5d3f0df961d9fa2e25.
2018-10-27 18:22:08 -03:00
Maxi Redigonda
debe851546 Deletes deprecated search-tickets path 2018-10-27 18:03:33 -03:00
Maxi Redigonda
d3fc148920 Adapts frontend to match new backend merged path get-all-tickets 2018-10-27 18:00:20 -03:00
Maxi Redigonda
ac11db5505 Merges search-tickets with get-all-tickets 2018-10-27 17:49:56 -03:00
Ivan Diaz
8605001c44 Merge branch 'master' of github.com:opensupports/opensupports 2018-10-26 13:12:23 -03:00
Ivan Diaz
9a40c63cf4 Fix recover password redirection bug 2018-10-26 12:58:01 -03:00
Ivan Diaz
2adc708045
Merge pull request #365 from ivandiazwm/master
Email template fix
2018-10-26 12:04:49 -03:00
Ivan Diaz
c3d1637615 #335 fix email template editing 2018-10-26 12:02:07 -03:00
Maxi Redigonda
5405f2b9ec Aligns dropdown and checkbox to the left 2018-10-22 11:26:28 -03:00
Ivan Diaz
b283748c42 upgrade build command 2018-10-20 16:21:50 -03:00
Guillermo
ceb2717bd2 feature #311 2018-10-18 22:30:06 -03:00
Maxi Redigonda
bd5fa9d520 Removes lodash dependency from my-tickets 2018-10-18 12:05:31 -03:00
Maxi Redigonda
c9ef8f1766 Adds 'include closed' checkbox to all-tickets view with no integration with the search bar 2018-10-18 12:02:39 -03:00
Maxi Redigonda
73b92bba86 Moves filtering functionality to backend and adapts frontend 2018-10-17 11:26:11 -03:00
Maxi Redigonda
a2d3908c4d Renamed showClosedTickets to closedTicketsShown and updated 2018-10-16 12:13:02 -03:00
Maxi Redigonda
4c52f6c6f5 Adds a button to filter opened/closed tickets to ticket-list and shows it under 'my-tickets' 2018-10-15 19:27:08 -03:00
Ivan Diaz
4111401518 Merge branch 'master' of github.com:opensupports/opensupports 2018-10-08 23:15:51 -03:00
Ivan Diaz
64b3820bd4 Fix blackquote 2018-10-08 23:15:34 -03:00
Ivan Diaz
8882b53b3b
Merge pull request #325 from ivandiazwm/master
Fix usery system disabled issues, prepare 4.3.0 upgrade
2018-10-06 16:09:30 -03:00
Ivan Diaz
60066bb9ca Fix user system disabled issues, add tests 2018-10-06 16:08:39 -03:00
Ivan Diaz
e33c97116a Add 4.3.0 upgrade script 2018-10-06 15:07:21 -03:00
Ivan Diaz
36b9c99243 Fix issues with no user system 2018-10-05 23:54:49 -03:00
Ivan Diaz
80589c9ac5 Add 4.3.0 version 2018-10-05 23:20:32 -03:00
Ivan Diaz
8181c51da9
Merge pull request #324 from ivandiazwm/master
Fix private response css
2018-10-05 22:57:14 -03:00
Ivan Diaz
becb41d3ce Add library for file uploading 2018-10-05 22:23:19 -03:00
Ivan Diaz
28839575a2 Fix translations 2018-10-05 14:58:33 -03:00
Ivan Diaz
b4232e5fd6 Prevent double image addition 2018-10-05 02:51:18 -03:00
Ivan Diaz
3f7dcc8d2c Fix php tests 2018-10-05 02:28:57 -03:00
Ivan Diaz
7740952956 Add clipboard image paste feature #168 2018-10-05 02:15:53 -03:00
Ivan Diaz
621c44e28c Fix private response css 2018-10-04 21:44:07 -03:00
Ivan Diaz
72a7200e77 Fix tests 2018-10-01 18:39:14 -03:00
Ivan Diaz
515e615418
Merge pull request #319 from guillegiu/master
feature #174
2018-10-01 17:23:26 -03:00
Ivan Diaz
303f71dab3
Merge branch 'master' into master 2018-10-01 17:22:43 -03:00
Ivan Diaz
78b30eea0a
Merge pull request #318 from ivandiazwm/master
Disable users #61
2018-09-28 14:09:21 -03:00
Ivan Diaz
56c23bf375 Disable users #61 2018-09-28 14:08:01 -03:00
Guillermo
b5c3d7ee23 feature #174 2018-09-27 21:19:34 -03:00
Ivan Diaz
158bb099e5
Merge pull request #313 from ivandiazwm/master
Replace text editor, add automated image uploading, fix max-size issue
2018-09-22 13:34:16 -03:00
Ivan Diaz
77e302f191 Fix text editor css 2018-09-21 23:50:53 -03:00
Ivan Diaz
c2148f48f9 update .travis.yml 2018-09-20 19:06:18 -03:00
Ivan Diaz
7dcc58f88e solve merge conflicts 2018-09-20 17:34:57 -03:00
Ivan Diaz
b4342167e4 Update API documentation 2018-09-20 17:19:47 -03:00
Ivan Diaz
285b62832a Add image file size validation 2018-09-20 16:23:54 -03:00
Ivan Diaz
4df3bab1ff Add test for image upload 2018-09-20 14:51:04 -03:00
Ivan Diaz
03778f0e95 Fix TextEditor, improve image uploading 2018-09-14 01:14:15 -03:00
Ivan Diaz
3b5d23b78b
Merge pull request #296 from guillegiu/master
fix bug #288 and add validatios
2018-09-11 18:09:20 -03:00
Guillermo
9d27912233 i18n close and upload file 2018-09-10 21:27:03 -03:00
Guillermo
1aa43bf32e some minor changes 2018-09-10 19:00:28 -03:00
Guillermo
28f5b624ca minnor error 2018-09-10 11:51:17 -03:00
Guillermo
89c42be18b fix minnor bugs 2018-09-08 15:52:51 -03:00
Guillermo
e43835b2bc fix bug #288 and add validatios 2018-09-05 01:21:21 -03:00
Ivan Diaz
20fbfa1e05
Merge pull request #284 from ivandiazwm/master
Add release 4.2.0
2018-08-19 19:18:49 -03:00
Ivan Diaz
e3a7f6c20a Merge branch 'master' of github.com:opensupports/opensupports
Conflicts:
	README.md
2018-08-19 19:17:37 -03:00
Ivan Diaz
5335f67746 Update version 4.2.0 2018-08-19 19:17:03 -03:00
Ivan Diaz
47375a0a2d
Update README.md 2018-08-16 20:15:04 -03:00
Ivan Diaz
1352587ec7
Merge pull request #280 from ivandiazwm/master
Add tests for password recovery
2018-08-15 23:43:39 -03:00
Ivan Diaz
2838a76438 Add tests for password recovery 2018-08-15 23:42:28 -03:00
Ivan Diaz
f052e7a568
Merge pull request #278 from ivandiazwm/maxi-fix
Bug fixing
2018-08-14 15:23:43 -03:00
Ivan Diaz
eef4c8622d Fix assigment issues, login widget issues, recover password issues. Emails always on lowercase. 2018-08-14 15:21:36 -03:00
Ivan Diaz
c994d6bc8b Solve merge conflicts 2018-08-13 14:34:02 -03:00
Ivan Diaz
b80a526b7c
Temporary fix 2018-07-30 20:51:28 -03:00
Ivan Diaz
51b086d63e
Merge pull request #257 from mredigonda/admin-recover-password-issue-161
Admin recover password issue 161
2018-07-31 01:46:37 +02:00
Ivan Diaz
8c6a5dee4a wip 2018-07-28 23:51:00 -03:00
Ivan Diaz
9c2b688ec4
Merge pull request #258 from ivandiazwm/master
Stat fixes, staff dropdown for assignment
2018-07-28 09:34:23 +02:00
Ivan Diaz
25117074a0 Fix issues related with #80 ticket assignment 2018-07-28 02:35:13 -03:00
Ivan Diaz
6383b52fd0 Merge branch 'master' of github.com:opensupports/opensupports 2018-07-27 20:36:56 -03:00
Ivan Diaz
dda8e28e93 Add staff dropdown for assignment 2018-07-27 20:36:35 -03:00
Ivan Diaz
2cfa455087
Merge pull request #254 from guillegiu/master
back-end structure and tests of feature #161 #80
2018-07-27 21:39:43 +02:00
Ivan Diaz
99ea7e0f8a Add warning when staff has no departments assigned 2018-07-26 21:35:19 -03:00
Maxi Redigonda
876fd8d52f Enhances password-recovery to show a logo if required, and adds password recovery to admin panel 2018-07-26 17:46:26 -03:00
Ivan Diaz
80ccccb951 Reduce stats to 30 days max 2018-07-26 17:28:27 -03:00
Maxi Redigonda
949334fa71 Removes old commented code 2018-07-26 16:03:25 -03:00
Maxi Redigonda
da3cf30192 Makes password-recovery act as a widget by injecting props 2018-07-26 15:51:21 -03:00
Maxi Redigonda
e60105cccc WIP - Creating PasswordRecovery component 2018-07-20 20:23:15 -03:00
Maxi Redigonda
c0e0ba0d77 Removes focus-on-changing-side machinery from home-page-login-widget, since widget-transition now does that job 2018-07-20 19:08:33 -03:00
Maxi Redigonda
edb8ba1de0 Makes widget-transition set focus when changing side to show 2018-07-20 18:56:19 -03:00
Guillermo
ce58e064cf resolve merge conflict 2018-07-20 18:31:26 -03:00
Guillermo
a093bb41a5 back-end structure and tests of feature #161 #80 2018-07-20 18:21:18 -03:00
Maxi Redigonda
e204b1877c Fixes redirection upon password recovery 2018-07-20 17:35:58 -03:00
Ivan Diaz
ac5b72f35d
Merge pull request #250 from ivandiazwm/master
Allow staff members to create tickets
2018-07-19 22:08:57 -03:00
Ivan Diaz
33c73988f2
Merge branch 'master' into master 2018-07-19 21:59:38 -03:00
Ivan Diaz
fe05cc5f2c
Merge pull request #251 from guillegiu/master
Add front-end esctruct  feature #101
2018-07-19 21:58:56 -03:00
Ivan Diaz
0b38addd58 Update ruby tests 2018-07-19 18:09:54 -03:00
Guillermo
305b94c567 add front-end esctruct feature #101 2018-07-18 21:38:12 -03:00
Ivan Diaz
cd2e1cd3df Add create ticket option for staff members 2018-07-17 23:18:51 -03:00
Ivan Diaz
0a5d444186 Mark as unread if author is not making the change 2018-07-17 02:04:00 -03:00
Ivan Diaz
050623713c Merge branch 'master' of github.com:opensupports/opensupports
Conflicts:
	tests/ticket/close.rb
2018-07-17 01:22:00 -03:00
Ivan Diaz
96868abd92 Allow tickets to be created by staff members #175 2018-07-17 01:17:49 -03:00
Ivan Diaz
92012d639b
Merge pull request #248 from guillegiu/master
Add  test of feature #101
2018-07-17 01:16:04 -03:00
Guillermo
52934bc473 fix some tests 2018-07-14 15:11:38 -03:00
Guillermo
787bd9a42b test of feature #101 2018-07-12 02:08:37 -03:00
Ivan Diaz
2c1e5f1a61 Add version upgrade for 4.1.3 2018-07-08 17:40:03 -03:00
Ivan Diaz
46da0c6719 Fix edit test 2018-07-06 17:05:38 -03:00
Ivan Diaz
428a126ab1 Merge branch 'master' of github.com:opensupports/opensupports 2018-07-06 16:07:15 -03:00
Ivan Diaz
254135dbf4 [HOTFIX] - Fix PHP 7.1 edit error 2018-07-06 16:06:51 -03:00
Ivan Diaz
07e68b0a49 Add v4.1.3 upgrade script 2018-07-05 22:58:41 -03:00
Ivan Diaz
c9520d8a1e
Update LICENSE 2018-07-05 07:24:20 -03:00
Ivan Diaz
352cfe0ca5 Fix image file stream 2018-07-02 23:31:47 -03:00
Ivan Diaz
0e0fbe324c
Merge pull request #237 from ivandiazwm/master
Add languages, fix languageselector issues.
2018-07-02 17:43:54 -03:00
Ivan Diaz
a63991ad2d Fix slash on windows server 2018-07-02 17:32:27 -03:00
Ivan Diaz
cd16026f31 Fix #238 - database backup not working 2018-07-02 16:05:46 -03:00
Maxi Redigonda
0a88bbccd6 add dutch language 2018-06-29 15:38:34 -03:00
Ivan Diaz
775d9a65c3 merge nl language 2018-06-29 13:38:44 -03:00
Ivan Diaz
e52d4ca151 Add nl lang 2018-06-29 13:36:04 -03:00
Ivan Diaz
24e9352d44 Merge branch 'master' of github.com:ivandiazwm/opensupports 2018-06-29 13:21:39 -03:00
Maxi Redigonda
dade0c995b add greek language 2018-06-29 13:19:52 -03:00
Ivan Diaz
e02c5e5021 Update package and fix language preferences 2018-06-29 13:18:51 -03:00
Maxi Redigonda
aafc20291f Merge branch 'master' of https://github.com/opensupports/opensupports 2018-06-29 12:11:29 -03:00
Ivan Diaz
e8c50339c1
Merge pull request #118 from paraskevasleivadaros/paraskevasleivadaros-patch-1
Greek Translation Pt. 2
2018-06-29 12:09:40 -03:00
Maxi Redigonda
a625971ec8 add portuguese Br language 2018-06-29 11:57:02 -03:00
Maxi Redigonda
8418eeb872 Merge remote-tracking branch 'github/revert-122-master' 2018-06-29 09:26:06 -03:00
Ivan Diaz
1497fae844
Merge pull request #198 from tkrameral/patch-3
Brazillian Portuguese translation file
2018-06-29 09:22:21 -03:00
Ivan Diaz
29d1880d57
Merge pull request #223 from ivandiazwm/master
Fix html parsing, frontend language selector issues
2018-06-13 21:28:44 -03:00
ivan
2e5c5ac9e0 Fix #75 Allow text alignment on text editor 2018-06-13 21:17:19 -03:00
ivan
7076d01a24 Fix #65 default language not set 2018-06-12 19:19:17 -03:00
ivan
c26faaa902 Fix default language issue on creating ticket 2018-06-12 18:52:59 -03:00
ivan
1b8444ae18 resolve merge conflicts package.json 2018-06-12 16:45:34 -03:00
Maximiliano Redigonda
d62b50dc23
Merge pull request #212 from mredigonda/remove-jquery
Replaces JQuery ajax requests with Axios requests
2018-06-11 21:53:26 -03:00
Maxi Redigonda
a58c36c9b4 Replaced mockjax with axios-mock-request 2018-06-11 21:00:43 -03:00
ivan
ad7051f2e0 Remove htmlentities, use html-to-text 2018-06-11 18:28:36 -03:00
Guillermo Giuliana
5f07b85975
Merge pull request #218 from ivandiazwm/master
Fix react editor, email links, smtp password being deleted
2018-06-06 02:40:49 -03:00
Guillermo Giuliana
38d7474ef5
Merge pull request #217 from guillegiu/master
Synchronize backend title lenght validator
2018-06-06 02:37:43 -03:00
ivan
7ec6f96879 Fix removal of smtp password when updating settings 2018-06-05 22:05:38 -03:00
ivan
a5555b2596 Fix email links 2018-06-05 21:52:22 -03:00
ivan
17bdcc66d5 WIP 2018-06-04 18:04:03 -03:00
Guillermo
9c8052439c Synchronize beckend title lenght validator 2018-06-02 02:10:31 -03:00
ivan
84f213454a Fix TextEditor CSS 2018-05-25 21:13:38 -03:00
Ivan Diaz
1903ea5757
Merge pull request #213 from ivandiazwm/master
[HOTFIX] - Email parsing
2018-05-18 16:54:16 -03:00
Ivan Diaz
7ec01b43c8
Merge pull request #178 from mredigonda/mred-2018-03-22
Fixes #177 by setting loading states in topic and article edits
2018-05-18 16:53:50 -03:00
ivan
5c81b67e7c [HOTFIX] - Email parsing 2018-05-18 16:52:50 -03:00
Ivan Diaz
423a366149
Merge pull request #210 from ivandiazwm/master
Update Mail templates
2018-05-18 16:20:14 -03:00
Maxi Redigonda
4fe9676318 Replaces JQuery ajax requests with Axios requests 2018-05-18 14:32:18 -03:00
ivan
7c52aa02bb UPdate Mail templates 2018-05-14 22:33:02 -03:00
Ivan Diaz
c295371f4d
Merge pull request #204 from guillegiu/master
Single file for email #66
2018-05-14 20:17:12 -03:00
Ivan Diaz
7be7f06754
Merge branch 'master' into master 2018-05-14 20:17:01 -03:00
Ivan Diaz
d0809ddc50
Merge pull request #205 from ivandiazwm/master
Fix image attachments issue, add build script
2018-05-14 19:43:54 -03:00
Guillermo
6471ca6b13 Single file for email #66 2018-05-11 21:50:05 -03:00
ivan
b3b20c443b Add build script 2018-05-10 21:00:33 -03:00
ivan
43ef3e4e19 [Fix issue #104] - Show image attachements on browser 2018-05-02 21:40:44 -03:00
Thiago Alves Cavalcante
b915f28a59
Brazillian Portuguese translation file
Translated and revised Brazillian Portuguese file
2018-05-01 19:04:48 -03:00
Ivan Diaz
ce3d2a249b
Merge pull request #183 from ivandiazwm/master
Add docker and make support
2018-04-26 21:47:55 -03:00
ivan
635ccea020 Use mysql port from env variable 2018-04-26 21:34:28 -03:00
ivan
d29f379ff6 Update dbPort ducumentation, set default port 2018-04-26 17:42:59 -03:00
Maximiliano Redigonda
d05a398ea8
Merge pull request #159 from mredigonda/gulp-sass-fix
Fixes gulp-sass crashes when saving sass files with syntactic errors
2018-04-24 09:57:07 -03:00
Maximiliano Redigonda
8bf8ed5223
Merge pull request #141 from mredigonda/issue-108
Fixes minimum size of titles.
2018-04-24 09:56:26 -03:00
ivan
43f8b6017a Fix test issues 2018-04-18 15:31:17 -03:00
ivan
f7ef552da1 Merge remote-tracking branch 'guillermo/master' 2018-04-12 22:23:26 -03:00
ivan
e573a5460d Change vendor chmod docker 2018-04-12 22:22:18 -03:00
ivan
909525d630 Fix docker issues 2018-04-11 22:10:28 -03:00
Ivan Diaz
b8740f3aaa Add database creation on install 2018-03-29 16:38:00 -03:00
Ivan Diaz
accc5ba7ae Add fakesmtp to make stop 2018-03-29 15:34:23 -03:00
Ivan Diaz
0c0e9c3e59 Add docker and make support 2018-03-27 23:30:18 -03:00
Maxi Redigonda
bc5968126d Sets loading state in form when an user edits a topic 2018-03-22 12:10:07 -03:00
Maxi Redigonda
23f9fb7833 Sets loading state in the form when the user presses SAVE 2018-03-22 11:57:33 -03:00
Guillermo
d0d6db77b7 Merge branch 'test' into guillermo/master 2018-03-12 19:45:12 -03:00
Guillermo
4ea1b925a9 wip 2018-03-12 19:31:54 -03:00
Guillermo
30fdb384f3 fix bug 115/166 2018-03-09 15:17:28 -03:00
Guillermo
d26d511ebd fix bug #115 2018-02-28 02:43:34 -03:00
Ivan Diaz
48e0aabeef
Merge pull request #163 from ivandiazwm/master
Add IE compatibility
2018-02-25 15:34:55 -03:00
Ivan Diaz
695149c497 Merge branch 'master' of github.com:opensupports/opensupports 2018-02-25 15:11:43 -03:00
Ivan Diaz
7134f82b5f Add IE compatibility 2018-02-25 12:33:38 -03:00
Maximiliano Redigonda
66de1f176d
Merge pull request #119 from mredigonda/master
Minor fixes
2018-02-20 16:10:45 -03:00
Maxi Redigonda
b54fadc75f Fixes step 5 instruction in backend api ruby testing 2018-02-20 10:18:42 -03:00
Maxi Redigonda
d19443d94d Fixes gulp-sass crashes when saving sass files with syntactic errors 2018-02-19 19:43:05 -03:00
Ivan Diaz
266e0b23f5
Merge pull request #137 from flokX/patch-1
Improve german translation
2018-02-10 11:32:14 -03:00
Maxi Redigonda
9e3b6ef47f Renames validation-factory to validator-factory and updates 2018-02-08 12:11:19 -03:00
Maxi Redigonda
5460e5ad49 Modifies the export of validations-factory, so that it now matches the import from form.js 2018-01-18 20:35:56 -03:00
Maxi Redigonda
39fd45dced Fixes minimum length in add article backend 2018-01-18 20:24:44 -03:00
Maxi Redigonda
f0e1afc1ac Fixes title validations 2018-01-18 20:11:49 -03:00
Ivan Diaz
2cabfe1887
Merge pull request #140 from ivandiazwm/master
Reintegrate older fixes
2018-01-18 17:42:00 -03:00
ivan
8e750dbad5 Revert "Revert "Fix #125 Maintenance mode""
This reverts commit a68aa76b2f830243a6052307440feeafe0595ddd.
2018-01-18 17:25:02 -03:00
ivan
8f8c481d58 Revert "Revert "fix bug #58""
This reverts commit 4b3ea709c82dd90ca9f8ea2f7f9f0015658eee6e.
2018-01-18 17:13:44 -03:00
Guillermo Giuliana
f211bd9cea
Merge pull request #136 from guillegiu/master
fix bug #83
2018-01-17 22:10:28 -03:00
Maxi Redigonda
e088114b17 Merge branch 'master' of https://github.com/opensupports/opensupports 2018-01-17 00:54:52 -03:00
Guillermo
f09f86f7b3 fix bug #95 2018-01-16 23:41:23 -03:00
Guillermo
a98d7ecdd1 Add htmlentities to department name 2018-01-16 20:23:32 -03:00
Florian
824aecd501
Improve german translation 2018-01-13 11:10:40 +01:00
Guillermo
3d416f82bd fix bug 83 2018-01-12 23:08:07 -03:00
ivan
f12dfc3754 realease version 4.1.2 2018-01-11 20:40:30 -03:00
ivan
a68aa76b2f Revert "Fix #125 Maintenance mode"
This reverts commit 0837754ffb1219c575bca9dbad3b80c640afa614.
2018-01-11 18:51:54 -03:00
ivan
2d83116520 [HOTFIX] - Fix check-requirements to check files folder correctly 2018-01-11 18:51:07 -03:00
ivan
f227c9a9fa Merge branch 'master' of https://github.com/opensupports/opensupports 2018-01-11 18:16:42 -03:00
Ivan Diaz
4b45a45a23
Update package.json 2018-01-08 18:16:57 -03:00
Ivan Diaz
feea28a159
Update README.md 2018-01-08 18:16:44 -03:00
Ivan Diaz
6f694e387c
Merge pull request #133 from opensupports/revert-122-master
Revert "fix bug #58"
2018-01-08 18:07:56 -03:00
Ivan Diaz
4b3ea709c8
Revert "fix bug #58" 2018-01-08 18:01:55 -03:00
Guillermo Giuliana
e1d4ae7be4
Merge pull request #122 from guillegiu/master
fix bug #58
2018-01-04 01:02:55 -03:00
Guillermo
02b6993615 Fix edit settings test 2018-01-04 00:57:45 -03:00
ivan
0837754ffb Fix #125 Maintenance mode 2018-01-03 20:08:04 -03:00
Maxi Redigonda
472dd6e8bb Reverts wrong renaming of a path 2017-12-31 00:25:36 -03:00
Guillermo
c002fdf00f fix bug #115 2017-12-29 18:40:59 -03:00
Guillermo
923571c5a2 fix bug #58 2017-12-29 17:50:46 -03:00
Guillermo Giuliana
9d4d1bb0b4
Merge pull request #3 from opensupports/master
update from official repo
2017-12-29 17:46:04 -03:00
Maxi Redigonda
fc05f9b905 Minor fix to en.js 2017-12-26 18:33:31 -03:00
Maxi Redigonda
193bc0ec04 changes os4-react to opensupports in docs 2017-12-26 12:43:31 -03:00
Maxi Redigonda
e3ca2c5e52 Fixes a typo and a path in the backend docs 2017-12-25 23:06:58 -03:00
Maxi Redigonda
4528c74022 Minor fixes to the README 2017-12-25 20:13:32 -03:00
Paraskevas Leivadaros
bb48dd234a
Add files via upload 2017-12-22 13:43:36 +02:00
Ivan Diaz
8e6d28fff8
Merge pull request #112 from ivandiazwm/master
Prepare 4.1.1
2017-12-13 19:28:33 -03:00
ivan
64986a70e1 update version docs 2017-12-06 20:58:28 -03:00
ivan
83b1a7c69b Fix version upgrade 2017-12-06 20:56:12 -03:00
ivan
a81e7320b7 Prepare 4.1.1 2017-11-30 18:06:09 -03:00
Ivan Diaz
49f80f642f
Merge pull request #91 from ivandiazwm/master
PHP 7.1, linear congruential generator
2017-11-07 13:13:34 -03:00
ivan
c3bf5e9fe5 use phpunit 5.7 2017-11-06 22:44:13 -03:00
ivan
1b48369e27 add test for linear congruential generator 2017-11-06 21:39:12 -03:00
ivan
3fabdfbe2f fix phpunit tests 2017-11-06 16:06:34 -03:00
ivan
aa533856d5 Ivan - Fix linear congruential generator 2017-10-31 18:31:32 -03:00
ivan
84b128326c Merge remote-tracking branch 'github/master' 2017-10-26 01:10:45 -03:00
ivan
911f457fbf Ivan - Remove unset($this), that task should be done by the garbage collector. It doesn't work for php 7.1 2017-10-26 01:10:04 -03:00
Ivan Diaz
5e0d42ea03 Merge pull request #82 from mredigonda/master
Max Red - Solves issue #40 about last login
2017-10-24 00:45:15 -03:00
Ivan Diaz
e9803caeb5 Merge pull request #67 from srishag/patch-1
corrected errors part-1
2017-10-24 00:45:06 -03:00
Maxi Redigonda
a93bf5f3e7 Max Red - Solves issue #40 about last login when the staff member has never logged in 2017-10-21 21:49:50 -03:00
srishti
052f4dc62a corrected errors part-1 2017-09-02 20:49:03 +08:00
Ivan Diaz
0c45fe9028 Merge pull request #63 from oneWaveAdrian/patch-1
Update de.js
2017-09-01 13:45:44 -03:00
Adrian
199a7f3339 Update de.js
adjusted several translations that looked weird to me while installing and first using the system. Still needs some updates - am happy to contribute
2017-09-01 10:36:56 +02:00
Ivan Diaz
abf9aba17b Merge pull request #47 from guillegiu/master
issue #46
2017-07-11 04:33:00 -03:00
Guillermo
0aeb04c642 Guillermo - Issue #46 2017-07-11 03:39:17 -03:00
Guillermo
ea2c750515 Guillermo - Issue #20 2017-07-11 02:57:19 -03:00
Ivan Diaz
33573550a0 Update README.md 2017-07-06 15:50:13 -03:00
751 changed files with 62096 additions and 46151 deletions

190
.circleci/config.yml Normal file
View File

@ -0,0 +1,190 @@
version: 2.1
orbs:
php: circleci/php@1.0.2
node: circleci/node@1.1.6
aws-cli: circleci/aws-cli@1.0.0
jobs:
install_composer_packages:
docker:
- image: 'cimg/base:edge'
steps:
- checkout
- php/install-php:
version: '7.0'
- php/install-composer
- run:
name: Install php extensions
command: |
sudo add-apt-repository ppa:ondrej/php
sudo apt update
sudo apt install php7.0-imap -y
sudo apt install php7.0-xml -y
sudo apt install zip unzip php7.0-zip php7.0-mbstring -y
- php/install-packages:
app-dir: server/
- persist_to_workspace:
root: .
paths:
- .
install_node_packages:
docker:
- image: circleci/node:11.15.0-stretch
steps:
- attach_workspace:
at: .
- restore_cache:
keys:
- node-cache-{{ checksum "client/package.json" }}
- run:
name: Install dependencies
command: |
sudo npm install -g npm@6.7.0
sudo npm install -g mocha@6.2.0
cd client && npm install
- save_cache:
paths:
- client/node_modules
key: node-cache-{{ checksum "client/package.json" }}
- persist_to_workspace:
root: .
paths:
- .
deploy_staging_files:
docker:
- image: circleci/node:11.15.0-stretch
environment:
- GIT_COMMIT_DESC: git log --format=oneline -n 1 $CIRCLE_SHA1
steps:
- attach_workspace:
at: .
- deploy:
name: Deploy staging files
command: |
if [ ! "$CIRCLE_BRANCH" = "master" ]; then exit 0; fi
if [[ "$GIT_COMMIT_DESC" = Release* ]]; then exit 0; fi
sudo apt update
sudo apt install -y lftp
make deploy-staging-files
make deploy-staging-population
add_release_commit:
docker:
- image: circleci/node:11.15.0-stretch
parameters:
version:
type: string
default: ""
steps:
- attach_workspace:
at: .
- add_ssh_keys:
fingerprints:
- "45:1e:cf:38:3f:9f:97:87:5b:b8:fd:e1:6c:71:11:41"
- run:
name: Commit new version
command: |
export VERSION=<< parameters.version >>
cd version_upgrades/release_script
npm i
npm run modify-files
cd ../..
ssh-keyscan -H github.com >> ~/.ssh/known_hosts
git config --global user.email "ivan@opensupports.com"
git config --global user.name "CircleCI-BOT"
git add .
git commit -m "Release $VERSION"
git checkout -b release-${VERSION}
git push origin release-${VERSION}
- persist_to_workspace:
root: .
paths:
- .
add_release_tag:
docker:
- image: circleci/node:11.15.0-stretch
parameters:
version:
type: string
default: ""
steps:
- attach_workspace:
at: .
- add_ssh_keys:
fingerprints:
- "45:1e:cf:38:3f:9f:97:87:5b:b8:fd:e1:6c:71:11:41"
- run:
name: Add Release tag
command: |
export VERSION=<< parameters.version >>
sudo apt-get update
sudo apt-get install lftp
make build-release-bundles
make upload-bundles
# make push-prerelease-tag
make populate-staging-release
- persist_to_workspace:
root: .
paths:
- .
parameters:
version:
type: string
default: ""
run_build:
type: boolean
default: true
workflows:
build:
when:
and:
- equal: [ master, << pipeline.git.branch >> ]
- << pipeline.parameters.run_build >>
jobs:
- install_composer_packages
- install_node_packages:
requires:
- install_composer_packages
- deploy_staging_files:
requires:
- install_node_packages
release:
when: << pipeline.parameters.version >>
jobs:
- install_composer_packages
- install_node_packages:
requires:
- install_composer_packages
- add_release_commit:
version: << pipeline.parameters.version >>
requires:
- install_node_packages
- add_release_tag:
version: << pipeline.parameters.version >>
requires:
- add_release_commit

14
.github/workflows/run-tests.yml vendored Normal file
View 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

14
.gitignore vendored
View File

@ -1,4 +1,12 @@
server/composer.lock
server/vendor
.idea
.jshintrc
.jshintrc
.DS_Store
server/vendor
server/files/
!server/files/.gitkeep
!server/files/.htaccess
server/.dbdata
server/.fakemail
server/apidoc
dist/
.env

View File

@ -3,20 +3,23 @@ language: php
php:
- '5.6'
- '7.0'
- '7.1'
- '7.2'
services:
- mysql
before_install:
- rvm use 2.2 --install --binary --fuzzy
- rvm use 2.3 --install --binary --fuzzy
- ruby --version
- mysql -e 'CREATE DATABASE development;'
- nvm install 4.4.7
- npm install -g npm@2.15.8
- npm install -g mocha
- nvm install 6.14.4
- npm install -g npm@6.1.0
- npm install -g mocha@6.2.0
- cd client
- npm install
- cd ../tests
- gem install bundler
- bundle install
- gem install bacon
- cd ../server
@ -27,5 +30,7 @@ before_install:
script:
- cd client
- npm test
- cd ../server
- ./run-tests.sh
- cd ../tests
- bacon init.rb

80
DEVELOPMENT.md Normal file
View 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.

53
LICENSE
View File

@ -619,56 +619,3 @@ Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

71
Makefile Normal file
View File

@ -0,0 +1,71 @@
#!make
-include .env
deploy-staging-files:
./build.sh
mv dist/opensupports_dev.zip .
make upload-bundles
deploy-staging-population:
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": "dev1"}}'
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": "dev2"}}'
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": "dev3"}}'
curl --request POST \
--url https://circleci.com/api/v2/project/github/opensupports/staging-population/pipeline \
--header 'Circle-Token: ${CIRCLE_API_USER_TOKEN}' \
--header 'content-type: application/json' \
--data '{"branch":"master","parameters":{"server_to_deploy": "dev4"}}'
build-release-bundles:
$(eval UPGRADE_ZIP="opensupports_v$(VERSION)_update.zip")
./build.sh
mv dist/opensupports_dev.zip .
cp opensupports_dev.zip ${UPGRADE_ZIP} && \
mv opensupports_dev.zip opensupports_v${VERSION}.zip && \
zip -d ${UPGRADE_ZIP} "api/config.php" && \
(( \
zip -r ${UPGRADE_ZIP} "version_upgrades/${VERSION}" && \
zip -r ${UPGRADE_ZIP} "version_upgrades/mysql_connect.php" \
) || true)
upload-bundles:
for file in *.zip ; do \
lftp -c "open -u $(FTP_USER),$(FTP_PASSWORD) $(FTP_HOST); set ssl:verify-certificate no; put -O /files/ $${file}"; \
done
push-prerelease-tag:
echo -e "Release v${VERSION}\n====\n" > log.txt && \
git log $(git describe --tags --abbrev=0 @^)..@ --pretty=format:'%s' >> log.txt
# ./version_upgrades/release_script/node_modules/.bin/github-release upload \
# --owner opensupports \
# --repo opensupports \
# --draft true\
# --tag "v$(VERSION)" \
# --release-name "Release v$(VERSION)" \
# --body "$(<log.txt)" \
# opensupports_v${VERSION}.zip opensupports_v${VERSION}_update.zip
populate-staging-release:
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": "westeros", "version_to_deploy": "${VERSION}"}}'
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": "senate", "version_to_deploy": "${VERSION}_update"}}'
deploy-staging-release: build-release-bundles upload-bundles populate-staging-release

115
README.md
View File

@ -1,106 +1,61 @@
![OpenSupports](http://www.opensupports.com/logo.png)
<div align="center">
[![Build Status](https://travis-ci.org/opensupports/opensupports.svg?branch=master)](https://travis-ci.org/opensupports/opensupports)
![OpenSupports](https://user-images.githubusercontent.com/25920622/172173126-f0a07319-0cc2-409b-aa22-120187fa4541.png)
OpenSupports is an open source ticket system built primarly with PHP and ReactJS.
Please, visit our website for more information: [http://www.opensupports.com/](http://www.opensupports.com/)
OpenSupports is a simple and beautiful open source ticket system. <br />
<a href="https://www.opensupports.com/"><strong>Learn more »</strong></a>
<br />
<p align="center">
<a href="https://www.opensupports.com/">Website</a>
<a href="https://docs.opensupports.com/">Docs</a>
<a href="https://opensupports.com/demo/">Demo</a>
<a href="https://www.opensupports.com/pricing/">Official Subscription</a>
</p>
## Requirements
* PHP 5.6+
* MySQL 4.1+
</div>
## Development
Here is a guide of how to set up the development environment in OpenSupports
## 🌱 About the Project
### Getting up and running FRONT-END (client folder)
0. update `sudo apt-get update`
1. Clone this repo
2. 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`
3. Install npm `sudo apt-get install npm`
4. Install gulp `sudo npm install -g gulp`
5. Go to repo `cd os4-react/client`
6. Install dependences `npm install`
7. Rebuild node-sass `npm rebuild node-sass`
8. Run `gulp dev`
9. Go to the main app: `http://localhost:3000/app` or the component demo `http://localhost:3000/demo`
10. Your browser will automatically be opened and directed to the browser-sync proxy address
12. Use `gulp dev --api` to disable fixtures and use the real php server api (it must be running at :8080).
### What Customers See
Now that `gulp dev` is running, the server is up as well and serving files from the `/build` directory. Any changes in the `/src` directory will be automatically processed by Gulp and the changes will be injected to any open browsers pointed at the proxy address.
![2022-06-08_10-32_demo](https://user-images.githubusercontent.com/25920622/172630004-988c914b-918e-455c-be48-11f96a00611e.gif)
### What Staff Members See
##### Production Task
![2022-06-08_10-32_demo_staff](https://user-images.githubusercontent.com/25920622/172867706-3669c7db-ef86-48df-92a9-8c2bfb19f622.gif)
Just as there is the `gulp dev` task for development, there is also a `gulp prod` 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.
## 🙌🏼 Ticket System for Absolutely Everyone
**Reminder:** Notice there is `index.html` and `index.php`. The firstone searches the backend server where `config.js` say it, the second one uses `/api` to find the server. If you want to run OpenSupports in a single server, then use `index.php`.
OpenSupports is a simple and beautiful open source ticket system.
#### Frontend Unit Testing
1. Do the steps described before
2. Install mocha "sudo npm install -g mocha"
3. Run `npm test` to run the tests
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.
### Getting up and running BACK-END (server folder)
Self-hosted, or [hosted by us](https://www.opensupports.com/pricing/), API-driven, and ready to be deployed on your own domain.
1. Clone this repo
2. [Install PHP 5.6](https://www.dev-metal.com/install-setup-php-5-6-ubuntu-14-04-lts/)
3. [Create MySQL Database](#markdown-header-create-mysql-database)
4. [Install composer](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-composer-on-ubuntu-14-04)
5. Go to `cd os4-react/api`
6. Run `composer install`
7. Run the server with `php -S localhost:8080`
## 🧐 Stay Up-to-Date
##### Create MySQL Database
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.
1. Install mysql-server
## 💪🏼 Features
Ubuntu
Check out our [most important features](https://opensupports.com/features) at our website.
`sudo apt-get install mysql-server`
Are we missing something? [Suggest an improvement](https://github.com/opensupports/opensupports/issues/new)!
Cent OS
## 🛠 Install
`sudo yum install mysql-server`
`/etc/init.d/mysqld start`
OpenSupports can be hosted on your own servers, or [hosted by us](https://www.opensupports.com/pricing/).
2. Access the mysql shell
There are multiple benefits to having the system hosted by its creators, including official support into any problem you might encounter.
`mysql -u root`
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. Create a new database
Check out our [installation guide](https://docs.opensupports.com/guides/installation/).
`CREATE DATABASE development;`
## 👨🏼‍💻 Development
4. Run the MySQL server
Are you a programmer? You can help us to fix bugs and build OpenSupports' features!
`sudo /etc/init.d/mysql start`
##### BACKEND API RUBY TESTING
Check out our [development guide](./DEVELOPMENT.md) to get your development environment up and running.
1. Install ruby `sudo apt-get install ruby-full`
2. Install mysql dev dependencies `sudo apt-get install libmysqlclient-dev libmysqlclient16 ruby-dev`
3. Install bundle `sudo gem install bundler`
4. Go to test folder `cd os4-react/tests`
5. Install project dependencies `sudo gem install bundler`
Test can run by using executing `run-tests.sh` file.
##### 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 jet
`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.
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
View 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.

37
build.sh Executable file
View File

@ -0,0 +1,37 @@
echo "1/3 Building frontend..."
cd client
npm run build
rm build/index.html
echo "2/3 Creating api folder..."
cd ../server
echo -n > config.php
mkdir files2
mv files/.htaccess files2
rm -rf files/
mv files2 files
cd ..
mkdir api
mv server/index.php api
mv server/.htaccess api
mv server/composer.json api
mv server/composer.lock api
mv server/controllers api
mv server/data api
mv server/libs api
mv server/models api
mv server/vendor api
mv server/files api
cp server/config.php api
chmod -R 755 .
cp client/src/index.php client/build
echo "3/3 Generating zip..."
cd client/build
zip opensupports_dev.zip index.php
zip -u opensupports_dev.zip .htaccess
zip -u opensupports_dev.zip bundle.js
zip -ur opensupports_dev.zip images
mv opensupports_dev.zip ../..
cd ../..
zip -ur opensupports_dev.zip api
mkdir dist
mv opensupports_dev.zip dist

View File

@ -1,3 +1,4 @@
{
"optional": ["es7.classProperties"]
}
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": ["@babel/plugin-proposal-class-properties", "add-module-exports"]
}

11
client/Makefile Normal file
View 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

View File

@ -1,31 +0,0 @@
'use strict';
module.exports = {
'serverport': 3000,
'scripts': {
'src': './src/*.js',
'dest': './build/js/'
},
'images': {
'src': './src/assets/images/**/*.{jpeg,jpg,png}',
'dest': './build/images/'
},
'styles': {
'src': './src/**/*.scss',
'dest': './build/css/'
},
'fonts': {
'src': './src/scss/font_awesome/fonts/*',
'dest': './build/fonts/'
},
'sourceDir': './src/',
'buildDir': './build/'
};

View File

@ -1,11 +0,0 @@
'use strict';
var fs = require('fs');
var onlyScripts = require('./util/script-filter');
var tasks = fs.readdirSync('./gulp/tasks/').filter(onlyScripts);
process.env.NODE_PATH = './src';
tasks.forEach(function(task) {
require('./tasks/' + task);
});

View File

@ -1,14 +0,0 @@
'use strict';
var config = require('../config');
var browserSync = require('browser-sync');
var gulp = require('gulp');
gulp.task('browserSync', function() {
browserSync({
proxy: 'localhost:' + config.serverport,
startPath: '/'
});
});

View File

@ -1,78 +0,0 @@
'use strict';
var gulp = require('gulp');
var gulpif = require('gulp-if');
var gutil = require('gulp-util');
var source = require('vinyl-source-stream');
var streamify = require('gulp-streamify');
var sourcemaps = require('gulp-sourcemaps');
var rename = require('gulp-rename');
var watchify = require('watchify');
var browserify = require('browserify');
var babelify = require('babelify');
var uglify = require('gulp-uglify');
var browserSync = require('browser-sync');
var debowerify = require('debowerify');
var handleErrors = require('../util/handle-errors');
var config = require('../config');
var util = require('gulp-util');
// Based on: http://blog.avisi.nl/2014/04/25/how-to-keep-a-fast-build-with-browserify-and-reactjs/
function buildScript(file, watch) {
var bundler = browserify({
entries: [config.sourceDir + file],
debug: !global.isProd,
insertGlobalVars: {
isProd: function () {
return (global.isProd) ? "'enabled'" : "'disabled'";
},
noFixtures: function() {
return (util.env['api']) ? "'enabled'" : "'disabled'";
}
},
cache: {},
packageCache: {},
fullPaths: false
});
if ( watch ) {
bundler = watchify(bundler);
bundler.on('update', rebundle);
}
bundler.transform(babelify, {'optional': ['es7.classProperties']});
bundler.transform(debowerify);
function rebundle() {
var stream = bundler.bundle();
gutil.log('Rebundle...');
return stream.on('error', handleErrors)
.pipe(source(file))
.pipe(gulpif(global.isProd, streamify(uglify())))
.pipe(streamify(rename({
basename: 'main'
})))
.pipe(gulpif(!global.isProd, sourcemaps.write('./')))
.pipe(gulp.dest(config.scripts.dest))
.pipe(gulpif(browserSync.active, browserSync.reload({ stream: true, once: true })));
}
return rebundle();
}
gulp.task('browserify', function() {
// Only run watchify if NOT production
return buildScript('index.js', !global.isProd);
});
gulp.task('config', function() {
return gulp.src(config.sourceDir + 'config.js')
.pipe(gulp.dest(config.scripts.dest))
});

View File

@ -1,11 +0,0 @@
'use strict';
var config = require('../config');
var gulp = require('gulp');
var del = require('del');
gulp.task('clean', function(cb) {
del([config.buildDir], cb);
});

View File

@ -1,10 +0,0 @@
'use strict';
var gulp = require('gulp');
var config = require('../config');
gulp.task('copyFonts', function() {
return gulp.src(config.fonts.src)
.pipe(gulp.dest(config.fonts.dest))
});

View File

@ -1,11 +0,0 @@
'use strict';
var gulp = require('gulp');
gulp.task('copyIcons', function() {
// Copy icons from root directory to build/
return gulp.src(['./*.png', './favicon.ico'])
.pipe(gulp.dest('build/'));
});

View File

@ -1,12 +0,0 @@
'use strict';
var gulp = require('gulp');
var config = require('../config');
gulp.task('copyIndex', function() {
gulp.src(config.sourceDir + 'index.html').pipe(gulp.dest(config.buildDir));
gulp.src(config.sourceDir + 'index.php').pipe(gulp.dest(config.buildDir));
gulp.src(config.sourceDir + '.htaccess').pipe(gulp.dest(config.buildDir));
});

View File

@ -1,10 +0,0 @@
'use strict';
var gulp = require('gulp');
//var config = require('../config');
gulp.task('deploy', ['prod'], function() {
// Deploy to hosting environment
});

View File

@ -1,15 +0,0 @@
'use strict';
var gulp = require('gulp');
var runSequence = require('run-sequence');
gulp.task('dev', ['clean'], function(callback) {
callback = callback || function() {};
global.isProd = false;
// Run all tasks once
return runSequence(['sass', 'imagemin', 'browserify', 'copyFonts', 'copyIndex', 'copyIcons', 'config'], 'watch', callback);
});

View File

@ -1,17 +0,0 @@
'use strict';
var gulp = require('gulp');
var gulpif = require('gulp-if');
var imagemin = require('gulp-imagemin');
var browserSync = require('browser-sync');
var config = require('../config');
gulp.task('imagemin', function() {
// Run imagemin task on all images
return gulp.src(config.images.src)
.pipe(gulpif(global.isProd, imagemin()))
.pipe(gulp.dest(config.images.dest))
.pipe(gulpif(browserSync.active, browserSync.reload({ stream: true, once: true })));
});

View File

@ -1,15 +0,0 @@
'use strict';
var gulp = require('gulp');
var runSequence = require('run-sequence');
gulp.task('prod', ['clean'], function(callback) {
process.env.NODE_ENV = 'production';
callback = callback || function() {};
global.isProd = true;
runSequence(['sass', 'imagemin', 'browserify', 'copyFonts', 'copyIndex', 'copyIcons'], callback);
});

View File

@ -1,26 +0,0 @@
'use strict';
var gulp = require('gulp');
var sass = require('gulp-sass');
var gulpif = require('gulp-if');
var browserSync = require('browser-sync');
var autoprefixer = require('gulp-autoprefixer');
var bulkSass = require('gulp-sass-bulk-import');
var handleErrors = require('../util/handle-errors');
var config = require('../config');
gulp.task('sass', function () {
return gulp.src(config.styles.src)
.pipe(bulkSass())
.pipe(sass({
sourceComments: global.isProd ? 'none' : 'map',
sourceMap: 'sass',
outputStyle: global.isProd ? 'compressed' : 'nested'
}))
.pipe(autoprefixer("last 2 versions", "> 1%", "ie 8"))
.on('error', handleErrors)
.pipe(gulp.dest(config.styles.dest))
.pipe(gulpif(browserSync.active, browserSync.reload({ stream: true })));
});

View File

@ -1,44 +0,0 @@
'use strict';
var config = require('../config');
var http = require('http');
var express = require('express');
var gulp = require('gulp');
var gutil = require('gulp-util');
var morgan = require('morgan');
var proxy = require('express-http-proxy');
gulp.task('server', function() {
var server = express();
// log all requests to the console
server.use(morgan('dev'));
server.use(express.static(config.buildDir));
// Proxy php server api
server.use('/api', proxy('http://localhost:8080', {
forwardPath: function(req, res) {
return require('url').parse(req.url).path;
}
}));
// Serve index.html for all routes to leave routing up to react-router
server.all('/*', function(req, res) {
res.sendFile('index.html', { root: 'build' });
});
// Start webserver if not already running
var s = http.createServer(server);
s.on('error', function(err){
if(err.code === 'EADDRINUSE'){
gutil.log('Development server is already started at port ' + config.serverport);
}
else {
throw err;
}
});
s.listen(config.serverport);
});

View File

@ -1,10 +0,0 @@
'use strict';
var gulp = require('gulp');
//var config = require('../config');
gulp.task('test', function() {
// Run all tests
});

View File

@ -1,13 +0,0 @@
'use strict';
var gulp = require('gulp');
var config = require('../config');
gulp.task('watch', ['browserSync', 'server'], function() {
// Scripts are automatically watched by Watchify inside Browserify task
gulp.watch(config.styles.src, ['sass']);
gulp.watch(config.images.src, ['imagemin']);
gulp.watch(config.sourceDir + 'index.html', ['copyIndex']);
});

View File

@ -1,27 +0,0 @@
'use strict';
var notify = require('gulp-notify');
module.exports = function(error) {
if( !global.isProd ) {
var args = Array.prototype.slice.call(arguments);
// Send error to notification center with gulp-notify
notify.onError({
title: 'Compile Error',
message: '<%= error.message %>'
}).apply(this, args);
// Keep gulp from hanging on this task
this.emit('end');
} else {
// Log the error and stop the process
// to prevent broken code from building
console.log(error);
process.exit(1);
}
};

View File

@ -1,9 +0,0 @@
'use strict';
var path = require('path');
// Filters out non .coffee and .js files. Prevents
// accidental inclusion of possible hidden files
module.exports = function(name) {
return /(\.(js|coffee)$)/i.test(path.extname(name));
};

View File

@ -1,5 +0,0 @@
'use strict';
global.isProd = false;
require('./gulp');

21698
client/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "OpenSupports",
"version": "4.1.0",
"version": "4.11.0",
"author": "Ivan Diaz <contact@opensupports.com>",
"description": "Open source ticket system made with PHP and ReactJS",
"repository": {
@ -9,68 +9,86 @@
},
"private": false,
"engines": {
"node": "^0.12.x",
"npm": "^2.1.x"
"node": "^11.15.x",
"npm": "^6.7.x"
},
"scripts": {
"test": "export NODE_PATH=src && mocha src/lib-test/preprocessor.js --compilers js:babel-core/register --recursive src/**/**/__tests__/*-test.js"
"start": "webpack-dev-server --display-reasons --display-error-details --history-api-fallback --progress --colors",
"start-fixtures": "webpack-dev-server --env.FIXTURES=1",
"build": "./node_modules/.bin/rimraf build && NODE_ENV=production ./node_modules/.bin/webpack -p --devtool none",
"test": "export NODE_PATH=src && mocha src/lib-test/preprocessor.js --require @babel/register --recursive src/**/**/__tests__/*-test.js"
},
"devDependencies": {
"babel-core": "^5.8.22",
"babel-plugin-transform-class-properties": "^6.11.5",
"babel-register": "^6.7.2",
"babelify": "^6.1.x",
"browser-sync": "^2.7.13",
"browserify": "^10.2.6",
"@babel/cli": "^7.5.5",
"@babel/core": "^7.5.5",
"@babel/node": "^7.5.5",
"@babel/plugin-proposal-class-properties": "^7.4.4",
"@babel/plugin-transform-modules-commonjs": "^7.5.0",
"@babel/preset-env": "^7.5.5",
"@babel/preset-react": "^7.0.0",
"@babel/register": "^7.5.5",
"axios-mock-adapter": "^1.15.0",
"babel-loader": "^8.0.6",
"babel-plugin-add-module-exports": "^1.0.2",
"browser-sync": "^2.27.5",
"chai": "^3.5.0",
"copy-webpack-plugin": "^5.0.3",
"css-loader": "^3.0.0",
"debowerify": "^1.3.1",
"del": "^1.2.0",
"eslint": "^5.16.0",
"eslint-loader": "^2.1.2",
"express": "^4.13.1",
"express-http-proxy": "^0.6.0",
"gulp": "^3.9.0",
"gulp-autoprefixer": "^2.3.1",
"gulp-connect-php": "0.0.5",
"gulp-if": "^1.2.5",
"gulp-imagemin": "^2.3.0",
"gulp-notify": "^2.2.0",
"gulp-rename": "^1.2.2",
"gulp-sass": "^2.0.4",
"gulp-sass-bulk-import": "^0.3.2",
"gulp-sourcemaps": "^1.5.2",
"gulp-streamify": "0.0.5",
"gulp-uglify": "^2.1.2",
"gulp-util": "^3.0.6",
"file-loader": "^4.0.0",
"html-webpack-plugin": "^3.2.0",
"humps": "^0.6.0",
"jquery-mockjax": "^2.1.0",
"jsdom": "^8.4.1",
"mocha": "^6.2.0",
"morgan": "^1.6.1",
"node-sass": "^4.12.0",
"nodemon": "^1.19.1",
"path": "^0.12.7",
"proxyquire": "^1.7.4",
"react-addons-test-utils": "^15.0.1",
"rimraf": "^2.6.3",
"run-sequence": "^1.1.1",
"sass-loader": "^7.1.0",
"sinon": "^1.17.3",
"sinon-chai": "^2.8.0",
"style-loader": "^0.23.1",
"stylelint-webpack-plugin": "^0.10.5",
"vinyl-source-stream": "^1.1.0",
"watchify": "^3.2.x"
"watchify": "^3.2.x",
"webpack": "^4.34.0",
"webpack-bundle-analyzer": "^3.3.2",
"webpack-cli": "^3.3.4",
"webpack-dev-server": "^3.7.1",
"webpack-import-glob": "^2.0.0"
},
"dependencies": {
"app-module-path": "^1.0.3",
"chart.js": "^2.4.0",
"axios": "^0.21.1",
"chart.js": "^2.9.3",
"classnames": "^2.2.5",
"draft-js": "^0.10.0",
"draft-js-export-html": "^0.5.2",
"history": "^3.0.0",
"jquery": "^2.1.4",
"html-to-text": "^4.0.0",
"keycode": "^2.1.4",
"localStorage": "^1.0.3",
"lodash": "^3.10.0",
"lodash": "^4.17.21",
"messageformat": "^0.2.2",
"moment": "^2.27.0",
"qs": "^6.5.2",
"query-string": "^6.12.1",
"quill-image-resize-module-react": "^3.0.0",
"quill-magic-url": "^4.1.3",
"random-string": "^0.2.0",
"react": "^15.4.2",
"react-chartjs-2": "^2.0.0",
"react-chartjs-2": "^2.10.0",
"react-document-title": "^1.0.2",
"react-dom": "^15.4.2",
"react-draft-wysiwyg": "^1.7.6",
"react-google-recaptcha": "^0.5.2",
"react-motion": "^0.4.7",
"react-quill": "^1.3.1",
"react-redux": "^4.4.5",
"react-router": "^3.0.2",
"react-router-redux": "^4.0.7",

View File

@ -1,17 +0,0 @@
'use strict';
var babel = require('babel-core');
module.exports = {
process: function(src, filename) {
// Ignore files other than .js, .es, .jsx or .es6
if (!babel.canCompile(filename)) {
return '';
}
// Ignore all files within node_modules
if (filename.indexOf('node_modules') === -1) {
return babel.transform(src, {filename: filename}).code;
}
return src;
}
};

View File

@ -1,5 +1,5 @@
export default {
login: stub(),
logout: stub(),
initSession: stub()
checkSession: stub()
};

View File

@ -1,161 +1,160 @@
const sessionStoreMock = require('lib-app/__mocks__/session-store-mock');
const APICallMock = require('lib-app/__mocks__/api-call-mock');
const storeMock = {
dispatch: stub()
};
// const sessionStoreMock = require('lib-app/__mocks__/session-store-mock');
// const APICallMock = require('lib-app/__mocks__/api-call-mock');
// const storeMock = require('app/__mocks__/store-mock');
const SessionActions = requireUnit('actions/session-actions', {
'lib-app/api-call': APICallMock,
'lib-app/session-store': sessionStoreMock,
'app/store': storeMock
});
// const SessionActions = requireUnit('actions/session-actions', {
// 'lib-app/api-call': APICallMock,
// 'lib-app/session-store': sessionStoreMock,
// 'app/store': storeMock
// });
describe('Session Actions,', function () {
// describe('Session Actions,', function () {
describe('login action', function () {
it('should return LOGIN with with a result promise', function () {
APICallMock.call.returns({
then: function (resolve) {
resolve({
data: {
userId: 14,
token: 'SOME_TOKEN'
}
});
}
});
// describe('login action', function () {
// it('should return LOGIN with with a result promise', function () {
// APICallMock.call.returns({
// then: function (resolve) {
// resolve({
// data: {
// userId: 14,
// token: 'SOME_TOKEN'
// }
// });
// }
// });
let loginData = {
email: 'SOME_EMAIL',
password: 'SOME_PASSWORD',
remember: false
};
// let loginData = {
// email: 'SOME_EMAIL',
// password: 'SOME_PASSWORD',
// remember: false
// };
expect(SessionActions.login(loginData).type).to.equal('LOGIN');
expect(storeMock.dispatch).to.have.been.calledWithMatch({type: 'USER_DATA'});
expect(APICallMock.call).to.have.been.calledWith({
path: '/user/get',
data: {
csrf_userid: 14,
csrf_token: 'SOME_TOKEN'
}
});
});
});
// expect(SessionActions.login(loginData).type).to.equal('LOGIN');
// expect(storeMock.dispatch).to.have.been.calledWithMatch({type: 'USER_DATA'});
// expect(APICallMock.call).to.have.been.calledWith({
// path: '/user/get',
// data: {
// csrf_userid: 14,
// csrf_token: 'SOME_TOKEN'
// }
// });
// });
// });
describe('autoLogin action', function () {
it('should return LOGIN_AUTO with remember data from sessionStore', function () {
APICallMock.call.returns({
then: function (resolve) {
resolve({
data: {
userId: 14
}
});
}
});
sessionStoreMock.getRememberData.returns({
token: 'SOME_TOKEN',
userId: 'SOME_ID',
expiration: 'SOME_EXPIRATION'
});
// describe('autoLogin action', function () {
// it('should return LOGIN_AUTO with remember data from sessionStore', function () {
// APICallMock.call.returns({
// then: function (resolve) {
// resolve({
// data: {
// userId: 14
// }
// });
// }
// });
// sessionStoreMock.getRememberData.returns({
// token: 'SOME_TOKEN',
// userId: 'SOME_ID',
// expiration: 'SOME_EXPIRATION'
// });
expect(SessionActions.autoLogin().type).to.equal('LOGIN_AUTO');
expect(storeMock.dispatch).to.have.been.calledWithMatch({type: 'USER_DATA'});
expect(APICallMock.call).to.have.been.calledWith({
path: '/user/login',
data: {
rememberToken: 'SOME_TOKEN',
userId: 'SOME_ID',
isAutomatic: true
}
});
});
});
// expect(SessionActions.autoLogin().type).to.equal('LOGIN_AUTO');
// expect(storeMock.dispatch).to.have.been.calledWithMatch({type: 'USER_DATA'});
// expect(APICallMock.call).to.have.been.calledWith({
// path: '/user/login',
// data: {
// rememberToken: 'SOME_TOKEN',
// userId: 'SOME_ID',
// remember: 1,
// isAutomatic: 1
// }
// });
// });
// });
describe('logout action', function () {
it('should return LOGOUT and call /user/logout', function () {
APICallMock.call.returns('API_RESULT');
APICallMock.call.reset();
// describe('logout action', function () {
// it('should return LOGOUT and call /user/logout', function () {
// APICallMock.call.returns('API_RESULT');
// APICallMock.call.reset();
expect(SessionActions.logout()).to.deep.equal({
type: 'LOGOUT',
payload: 'API_RESULT'
});
expect(APICallMock.call).to.have.been.calledWith({
path: '/user/logout',
data: {}
});
});
});
// expect(SessionActions.logout()).to.deep.equal({
// type: 'LOGOUT',
// payload: 'API_RESULT'
// });
// expect(APICallMock.call).to.have.been.calledWith({
// path: '/user/logout',
// data: {}
// });
// });
// });
describe('initSession action', function () {
beforeEach(function () {
APICallMock.call.returns({
then: function (resolve) {
resolve({
data: {
sessionActive: false
}
});
}
});
APICallMock.call.reset();
storeMock.dispatch.reset();
sessionStoreMock.isLoggedIn.returns(true)
});
// describe('checkSession action', function () {
// beforeEach(function () {
// APICallMock.call.returns({
// then: function (resolve) {
// resolve({
// data: {
// sessionActive: false
// }
// });
// }
// });
// APICallMock.call.reset();
// storeMock.dispatch.reset();
// sessionStoreMock.isLoggedIn.returns(true)
// });
after(function () {
APICallMock.call.returns(new Promise(function (resolve) {
resolve({
data: {
sessionActive: true
}
});
}));
});
// after(function () {
// APICallMock.call.returns(new Promise(function (resolve) {
// resolve({
// data: {
// sessionActive: true
// }
// });
// }));
// });
it('should return CHECK_SESSION and dispatch SESSION_CHECKED if session is active', function () {
APICallMock.call.returns({
then: function (resolve) {
resolve({
data: {
sessionActive: true
}
});
}
});
// it('should return CHECK_SESSION and dispatch SESSION_CHECKED if session is active', function () {
// APICallMock.call.returns({
// then: function (resolve) {
// resolve({
// data: {
// sessionActive: true
// }
// });
// }
// });
expect(SessionActions.initSession().type).to.equal('CHECK_SESSION');
expect(storeMock.dispatch).to.have.been.calledWith({type: 'SESSION_CHECKED'});
expect(APICallMock.call).to.have.been.calledWith({
path: '/user/check-session',
data: {}
});
});
// expect(SessionActions.checkSession().type).to.equal('CHECK_SESSION');
// expect(storeMock.dispatch).to.have.been.calledWith({type: 'SESSION_CHECKED'});
// expect(APICallMock.call).to.have.been.calledWith({
// path: '/user/check-session',
// data: {}
// });
// });
it('should return CHECK_SESSION and dispatch LOGOUT_FULFILLED if session is not active and no remember data', function () {
sessionStoreMock.isRememberDataExpired.returns(true);
// it('should return CHECK_SESSION and dispatch LOGOUT_FULFILLED if session is not active and no remember data', function () {
// sessionStoreMock.isRememberDataExpired.returns(true);
expect(SessionActions.initSession().type).to.equal('CHECK_SESSION');
expect(storeMock.dispatch).to.have.been.calledWith({type: 'LOGOUT_FULFILLED'});
expect(APICallMock.call).to.have.been.calledWith({
path: '/user/check-session',
data: {}
});
});
// expect(SessionActions.checkSession().type).to.equal('CHECK_SESSION');
// expect(storeMock.dispatch).to.have.been.calledWith({type: 'LOGOUT_FULFILLED'});
// expect(APICallMock.call).to.have.been.calledWith({
// path: '/user/check-session',
// data: {}
// });
// });
it('should return CHECK_SESSION and dispatch LOGIN_AUTO if session is not active but remember data exists', function () {
sessionStoreMock.isRememberDataExpired.returns(false);
// it('should return CHECK_SESSION and dispatch LOGIN_AUTO if session is not active but remember data exists', function () {
// sessionStoreMock.isRememberDataExpired.returns(false);
expect(SessionActions.initSession().type).to.equal('CHECK_SESSION');
expect(storeMock.dispatch).to.not.have.been.calledWith({type: 'LOGOUT_FULFILLED'});
expect(APICallMock.call).to.have.been.calledWith({
path: '/user/check-session',
data: {}
});
// expect(SessionActions.checkSession().type).to.equal('CHECK_SESSION');
// expect(storeMock.dispatch).to.not.have.been.calledWith({type: 'LOGOUT_FULFILLED'});
// expect(APICallMock.call).to.have.been.calledWith({
// path: '/user/check-session',
// data: {}
// });
expect(storeMock.dispatch).to.have.been.calledWith(SessionActions.autoLogin());
});
});
});
// expect(storeMock.dispatch).to.have.been.calledWith(SessionActions.autoLogin());
// });
// });
// });

View File

@ -1,7 +1,7 @@
import API from 'lib-app/api-call';
export default {
retrieveCustomResponses() {
return {
type: 'CUSTOM_RESPONSES',
@ -12,32 +12,42 @@ export default {
};
},
retrieveMyTickets() {
retrieveMyTickets({page, closed = 0, departmentId = 0, pageSize = 10}) {
return {
type: 'MY_TICKETS',
payload: API.call({
path: '/staff/get-tickets',
data: {}
data: {page, closed, departmentId, pageSize}
})
};
},
retrieveNewTickets() {
retrieveNewTickets({page, departmentId = 0, pageSize = 10}) {
return {
type: 'NEW_TICKETS',
payload: API.call({
path: '/staff/get-new-tickets',
data: {}
data: {page, departmentId, pageSize}
})
};
},
retrieveAllTickets(page = 1) {
retrieveAllTickets(page = 1, query = '', closed = 0) {
return {
type: 'ALL_TICKETS',
payload: API.call({
path: '/staff/get-all-tickets',
data: {page}
data: {page, query, closed}
})
};
},
retrieveStaffMembers() {
return {
type: 'STAFF_MEMBERS',
payload: API.call({
path: '/staff/get-all',
data: {}
})
};
},
@ -51,4 +61,4 @@ export default {
})
};
}
};
};

View File

@ -0,0 +1,15 @@
export default {
showLoginForm() {
return {
type: 'SHOW_LOGIN_FORM',
payload: true
};
},
hideLoginForm() {
return {
type: 'HIDE_LOGIN_FORM',
payload: false
};
}
};

View File

@ -0,0 +1,81 @@
import API from 'lib-app/api-call';
import searchTicketsUtils from 'lib-app/search-tickets-utils';
import history from 'lib-app/history';
export default {
setLoadingInTrue() {
return {
type: 'SEARCH_FILTERS_SET_LOADING_IN_TRUE',
payload: {}
}
},
retrieveSearchTickets(ticketQueryListState, filters = {}, pageSize = 10) {
return {
type: 'SEARCH_TICKETS',
payload: API.call({
path: '/ticket/search',
data: {
...filters,
page: ticketQueryListState.page,
pageSize
}
})
}
},
changeForm(form) {
return {
type: 'SEARCH_FILTERS_CHANGE_FORM',
payload: form
}
},
changeFilters(listConfig) {
const filtersForAPI = searchTicketsUtils.getFiltersForAPI(listConfig.filters);
return {
type: 'SEARCH_FILTERS_CHANGE_FILTERS',
payload: {...listConfig, filtersForAPI}
}
},
setDefaultFormValues() {
return {
type: 'SEARCH_FILTERS_SET_DEFAULT_FORM_VALUES',
payload: {}
}
},
changeShowFilters(showFilters) {
return {
type: 'SEARCH_FILTERS_CHANGE_SHOW_FILTERS',
payload: showFilters
}
},
changePage(filtersWithPage) {
const filtersForAPI = searchTicketsUtils.getFiltersForAPI(filtersWithPage);
const currentPath = window.location.pathname;
const urlQuery = searchTicketsUtils.getFiltersForURL({
filters: filtersForAPI,
shouldRemoveCustomParam: false,
shouldRemoveUseInitialValuesParam: true
});
urlQuery && history.push(`${currentPath}${urlQuery}`);
return {
type: 'SEARCH_FILTERS_CHANGE_PAGE',
payload: {...filtersWithPage, ...filtersForAPI}
}
},
changeOrderBy(filtersWithOrderBy) {
const filtersForAPI = searchTicketsUtils.getFiltersForAPI(filtersWithOrderBy);
const currentPath = window.location.pathname;
const urlQuery = searchTicketsUtils.getFiltersForURL({
filters: filtersForAPI,
shouldRemoveCustomParam: false,
shouldRemoveUseInitialValuesParam: true
});
urlQuery && history.push(`${currentPath}${urlQuery}`);
return {
type: 'SEARCH_FILTERS_CHANGE_ORDER_BY',
payload: {...filtersWithOrderBy, ...filtersForAPI}
}
},
};

View File

@ -1,5 +1,8 @@
import _ from 'lodash';
import API from 'lib-app/api-call';
import AdminDataActions from 'actions/admin-data-actions';
import ConfigActions from 'actions/config-actions';
import sessionStore from 'lib-app/session-store';
import store from 'app/store';
@ -12,13 +15,16 @@ export default {
let loginCall = () => {
API.call({
path: '/user/login',
data: loginData
data: _.extend(loginData, {remember: loginData.remember * 1})
}).then((result) => {
store.dispatch(this.getUserData(result.data.userId, result.data.token, result.data.staff)).then(() => {
if(result.data.staff) {
store.dispatch(AdminDataActions.retrieveCustomResponses());
}
});
store.dispatch(this.getUserData(result.data.userId, result.data.token, result.data.staff))
.then(() => store.dispatch(ConfigActions.updateData()))
.then(() => {
if(result.data.staff) {
store.dispatch(AdminDataActions.retrieveCustomResponses());
store.dispatch(AdminDataActions.retrieveStaffMembers());
}
});
resolve(result);
}).catch((result) => {
@ -48,11 +54,12 @@ export default {
data: {
userId: rememberData.userId,
rememberToken: rememberData.token,
isAutomatic: true
staff: rememberData.isStaff,
remember: 1,
}
}).then((result) => {
store.dispatch(this.getUserData(result.data.userId, result.data.token));
store.dispatch(this.getUserData(result.data.userId, result.data.token, result.data.staff));
return result;
})
};
@ -94,7 +101,7 @@ export default {
};
},
initSession() {
checkSession() {
return {
type: 'CHECK_SESSION',
payload: new Promise((resolve, reject) => {
@ -125,4 +132,4 @@ export default {
})
}
}
};
};

View File

@ -3,7 +3,7 @@ const TicketInfo = ReactMock();
const Table = ReactMock();
const Button = ReactMock();
const Tooltip = ReactMock();
const Dropdown = ReactMock();
const DepartmentDropdown = ReactMock();
const i18n = stub().returnsArg(0);
const TicketList = requireUnit('app-components/ticket-list', {
@ -11,8 +11,15 @@ const TicketList = requireUnit('app-components/ticket-list', {
'core-components/table': Table,
'core-components/button': Button,
'core-components/tooltip': Tooltip,
'core-components/drop-down': Dropdown,
'lib-app/i18n': i18n
'app-components/department-dropdown': DepartmentDropdown,
'lib-app/i18n': i18n,
'react-redux': {
connect: function() {
return function(param) {
return param;
}
}
},
});
describe('TicketList component', function () {
@ -28,11 +35,11 @@ describe('TicketList component', function () {
id: 1,
name: 'Sales Support'
},
priority: 'low',
author: {
id: 3,
name: 'Francisco Villegas'
}
},
tags: []
};
let list = _.range(5).map(() => ticket);
@ -50,71 +57,46 @@ describe('TicketList component', function () {
function renderTicketList(props = {}) {
ticketList = TestUtils.renderIntoDocument(
<TicketList tickets={tickets} {...props}/>
<TicketList tickets={tickets} {...props}></TicketList>
);
table = TestUtils.scryRenderedComponentsWithType(ticketList, Table);
dropdown = TestUtils.scryRenderedComponentsWithType(ticketList, Dropdown);
dropdown = TestUtils.scryRenderedComponentsWithType(ticketList, DepartmentDropdown);
}
it('should pass correct props to Table', function () {
renderTicketList();
expect(table[0].props.loading).to.equal(false);
expect(table[0].props.pageSize).to.equal(10);
expect(table[0].props.headers).to.deep.equal([
expect(table[0].props.headers[0]).to.deep.equal(
{
key: 'number',
value: i18n('NUMBER'),
className: 'ticket-list__number col-md-1'
},
});
expect(table[0].props.headers[1]).to.deep.equal(
{
key: 'title',
value: i18n('TITLE'),
className: 'ticket-list__title col-md-6'
},
});
expect(table[0].props.headers[2]).to.deep.equal(
{
key: 'department',
value: i18n('DEPARTMENT'),
className: 'ticket-list__department col-md-3'
},
{
key: 'date',
value: i18n('DATE'),
className: 'ticket-list__date col-md-2'
}
]);
});
expect(table[0].props.headers[3].key).to.equal('date');
expect(table[0].props.headers[3].value.props.children[0]).to.equal(i18n('DATE'));
expect(table[0].props.headers[3].value.props.children[1]).to.equal(null);
expect(table[0].props.headers[3].className).to.equal('ticket-list__date col-md-2');
});
it('should pass loading to Table', function () {
renderTicketList({loading: true});
expect(table[0].props.loading).to.equal(true);
});
it('should pass correct compare function to Table', function () {
let minCompare = table[0].props.comp;
let row1 = {
closed: false,
unread: false,
date: '20160405'
};
let row2 = {
closed: false,
unread: false,
date: '20160406'
};
expect(minCompare(row1, row2)).to.equal(1);
row1.unread = true;
expect(minCompare(row1, row2)).to.equal(-1);
row2.unread = true;
expect(minCompare(row1, row2)).to.equal(1);
row2.date = '20160401';
expect(minCompare(row1, row2)).to.equal(-1);
});
describe('when using secondary type', function () {
beforeEach(function () {
renderTicketList({
@ -127,45 +109,41 @@ describe('TicketList component', function () {
});
it('should pass correct props to Table', function () {
expect(table[0].props.headers).to.deep.equal([
expect(table[0].props.headers[0]).to.deep.equal(
{
key: 'number',
value: i18n('NUMBER'),
className: 'ticket-list__number col-md-1'
},
});
expect(table[0].props.headers[1]).to.deep.equal(
{
key: 'title',
value: i18n('TITLE'),
className: 'ticket-list__title col-md-4'
},
{
key: 'priority',
value: i18n('PRIORITY'),
className: 'ticket-list__priority col-md-1'
},
});
expect(table[0].props.headers[2]).to.deep.equal(
{
key: 'department',
value: i18n('DEPARTMENT'),
className: 'ticket-list__department col-md-2'
},
});
expect(table[0].props.headers[3]).to.deep.equal(
{
key: 'author',
value: i18n('AUTHOR'),
className: 'ticket-list__author col-md-2'
},
{
key: 'date',
value: i18n('DATE'),
className: 'ticket-list__date col-md-2'
}
]);
});
expect(table[0].props.headers[4].key).to.equal('date');
expect(table[0].props.headers[4].value.props.children[0]).to.equal(i18n('DATE'));
expect(table[0].props.headers[4].value.props.children[1]).to.equal(null);
expect(table[0].props.headers[4].className).to.equal('ticket-list__date col-md-2');
});
it('should pass correct props to dropdown', function () {
expect(dropdown[0].props.items).to.deep.equal([
{content: i18n('ALL_DEPARTMENTS')},
{content: 'Sales Support'},
{content: 'Tech Help'}
expect(dropdown[0].props.departments).to.deep.equal([
{name: i18n('ALL_DEPARTMENTS')},
{name: 'Sales Support', id: 1},
{name: 'Tech Help', id: 2}
]);
expect(dropdown[0].props.size).to.equal('medium');
});
@ -185,4 +163,4 @@ describe('TicketList component', function () {
expect(table[0].props.rows.length).to.equal(10);
});
});
});
});

View File

@ -18,10 +18,12 @@ class ActivityRow extends React.Component {
'CREATE_TICKET',
'RE_OPEN',
'DEPARTMENT_CHANGED',
'PRIORITY_CHANGED',
'EDIT_TITLE',
'EDIT_COMMENT',
'EDIT_SETTINGS',
'SIGNUP',
'INVITE',
'ADD_TOPIC',
'ADD_ARTICLE',
'DELETE_TOPIC',
@ -56,16 +58,16 @@ class ActivityRow extends React.Component {
'CREATE_TICKET',
'RE_OPEN',
'DEPARTMENT_CHANGED',
'PRIORITY_CHANGED'
'COMMENT_EDITED',
'EDIT_TITLE',
'EDIT_COMMENT',
];
return (
<div className="activity-row">
<Icon {...this.getIconProps()} className="activity-row__icon"/>
<span>
<Link className="activity-row__name-link" to={this.getNameLinkDestination()}>
{this.props.author.name}
</Link>
{this.renderAuthorName()}
</span>
<span className="activity-row__message"> {i18n('ACTIVITY_' + this.props.type)} </span>
{_.includes(ticketRelatedTypes, this.props.type) ? this.renderTicketNumber() : this.props.to}
@ -74,6 +76,18 @@ class ActivityRow extends React.Component {
);
}
renderAuthorName() {
let name = this.props.author.name;
if (this.props.author.id) {
name = <Link className="activity-row__name-link" to={this.getNameLinkDestination()}>
{this.props.author.name}
</Link>;
}
return name;
}
renderTicketNumber() {
let ticketNumber = (this.props.mode === 'staff') ? this.props.ticketNumber : this.props.to;
@ -99,10 +113,12 @@ class ActivityRow extends React.Component {
'CREATE_TICKET': 'ticket',
'RE_OPEN': 'unlock-alt',
'DEPARTMENT_CHANGED': 'exchange',
'PRIORITY_CHANGED': 'exclamation',
'EDIT_TITLE': 'edit',
'EDIT_COMMENT': 'edit',
'EDIT_SETTINGS': 'wrench',
'SIGNUP': 'user-plus',
'INVITE': 'user-plus',
'ADD_TOPIC': 'book',
'ADD_ARTICLE': 'book',
'DELETE_TOPIC': 'book',
@ -126,4 +142,4 @@ class ActivityRow extends React.Component {
}
}
export default ActivityRow;
export default ActivityRow;

View File

@ -5,8 +5,7 @@ import ModalContainer from 'app-components/modal-container';
import Button from 'core-components/button';
import Input from 'core-components/input';
import Icon from 'core-components/icon';
import Loading from 'core-components/loading'
class AreYouSure extends React.Component {
static propTypes = {
@ -24,13 +23,14 @@ class AreYouSure extends React.Component {
};
state = {
loading: false,
password: ''
};
static openModal(description, onYes, type = 'default') {
ModalContainer.openModal(
<AreYouSure description={description} onYes={onYes} type={type}/>,
true
<AreYouSure description={description} onYes={onYes} type={type} />,
{noPadding: true, closeButton: {showCloseButton: true, whiteColor: true}}
);
}
@ -39,28 +39,34 @@ class AreYouSure extends React.Component {
}
render() {
const { loading } = this.state;
const { description, type } = this.props;
return (
<div className="are-you-sure" role="dialog" aria-labelledby="are-you-sure__header" aria-describedby="are-you-sure__description">
<div className="are-you-sure__header" id="are-you-sure__header">
{i18n('ARE_YOU_SURE')}
</div>
<span className="are-you-sure__close-icon" onClick={this.onNo.bind(this)}>
<Icon name="times" size="2x"/>
</span>
<div className="are-you-sure__description" id="are-you-sure__description">
{this.props.description || (this.props.type === 'secure' && i18n('PLEASE_CONFIRM_PASSWORD'))}
{description || (type === 'secure' && i18n('PLEASE_CONFIRM_PASSWORD'))}
</div>
{(this.props.type === 'secure') ? this.renderPassword() : null}
{(type === 'secure') ? this.renderPassword() : null}
<span className="separator" />
<div className="are-you-sure__buttons">
<div className="are-you-sure__no-button">
<Button type="link" size="auto" onClick={this.onNo.bind(this)} tabIndex="2">
<Button disabled={loading} type="link" size="auto" onClick={this.onNo.bind(this)} tabIndex="2">
{i18n('CANCEL')}
</Button>
</div>
<div className="are-you-sure__yes-button">
<Button type="secondary" size="small" onClick={this.onYes.bind(this)} ref="yesButton" tabIndex="2">
{i18n('YES')}
<Button
type="secondary"
size="small"
onClick={this.onYes.bind(this)}
ref="yesButton"
tabIndex="2"
disabled={loading}>
{loading ? <Loading /> : i18n('YES')}
</Button>
</div>
</div>
@ -69,8 +75,20 @@ class AreYouSure extends React.Component {
}
renderPassword() {
const { password, loading } = this.state;
return (
<Input className="are-you-sure__password" password placeholder="password" name="password" ref="password" size="medium" value={this.state.password} onChange={this.onPasswordChange.bind(this)} onKeyDown={this.onInputKeyDown.bind(this)}/>
<Input
className="are-you-sure__password"
password
placeholder="password"
name="password"
ref="password"
size="medium"
value={password}
onChange={this.onPasswordChange.bind(this)}
onKeyDown={this.onInputKeyDown.bind(this)}
disabled={loading} />
);
}
@ -87,28 +105,59 @@ class AreYouSure extends React.Component {
}
onYes() {
if (this.props.type === 'secure' && !this.state.password) {
const { password } = this.state;
const { type, onYes } = this.props;
if(type === 'secure' && !password) {
this.refs.password.focus()
}
if (this.props.type === 'default' || this.state.password) {
this.closeModal();
if (this.props.onYes) {
this.props.onYes(this.state.password);
if(type === 'default' || password) {
if(onYes) {
const result = onYes(password);
if(this.isPromise(result)) {
this.setState({
loading: true
});
result
.then(() => {
this.setState({
loading: false
});
this.closeModal();
})
.catch(() => {
this.setState({
loading: false,
});
this.closeModal();
})
} else {
this.closeModal();
}
} else {
this.closeModal();
}
}
}
isPromise(object) {
if(Promise && Promise.resolve) {
return Promise.resolve(object) == object;
} else {
throw "Promise not supported in your environment"
}
}
onNo() {
this.closeModal();
}
closeModal() {
if (this.context.closeModal) {
this.context.closeModal();
}
const { closeModal } = this.context;
closeModal && closeModal();
}
}
export default AreYouSure;
export default AreYouSure;

View File

@ -24,12 +24,16 @@
&__buttons {
margin-top: 10px;
padding-bottom: 10px;
text-align: right;
width: 100%;
display: inline-flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
&__yes-button,
&__no-button {
display: inline-block;
display: block;
margin-right: 10px;
}

View File

@ -1,4 +1,6 @@
import React from 'react';
import _ from 'lodash';
import {connect} from 'react-redux';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
@ -9,48 +11,65 @@ import Form from 'core-components/form';
import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button';
import Button from 'core-components/button';
import TextEditor from 'core-components/text-editor';
class ArticleAddModal extends React.Component {
static propTypes = {
topicId: React.PropTypes.number.isRequired,
topicName: React.PropTypes.string.isRequired,
position: React.PropTypes.number.isRequired
position: React.PropTypes.number.isRequired,
allowAttachments: React.PropTypes.bool
};
state = {
loading: false
};
render() {
return (
<div className="article-add-modal">
<Header title={i18n('ADD_ARTICLE')} description={i18n('ADD_ARTICLE_DESCRIPTION', {category: this.props.topicName})} />
<Form onSubmit={this.onAddNewArticleFormSubmit.bind(this)}>
<Form onSubmit={this.onAddNewArticleFormSubmit.bind(this)} loading={this.state.loading}>
<FormField name="title" label={i18n('TITLE')} field="input" fieldProps={{size: 'large'}} validation="TITLE" required/>
<FormField name="content" label={i18n('CONTENT')} field="textarea" validation="TEXT_AREA" required/>
<SubmitButton type="secondary">{i18n('ADD_ARTICLE')}</SubmitButton>
<FormField name="content" label={i18n('CONTENT')} field="textarea" validation="TEXT_AREA" required fieldProps={{allowImages: this.props.allowAttachments}}/>
<Button className="article-add-modal__cancel-button" type="link" onClick={(event) => {
event.preventDefault();
ModalContainer.closeModal();
}}>{i18n('CANCEL')}</Button>
<SubmitButton className="article-add-modal__submit-button" type="secondary">{i18n('ADD_ARTICLE')}</SubmitButton>
</Form>
</div>
);
}
onAddNewArticleFormSubmit(form) {
this.setState({
loading: true
});
API.call({
path: '/article/add',
data: {
dataAsForm: true,
data: _.extend(TextEditor.getContentFormData(form.content), {
title: form.title,
content: form.content,
topicId: this.props.topicId,
position: this.props.position
}
})
}).then(() => {
ModalContainer.closeModal();
if(this.props.onChange) {
this.props.onChange();
}
}).catch(() => {
this.setState({
loading: false
});
});
}
}
export default ArticleAddModal;
export default connect((store) => {
return {
allowAttachments: store.config['allow-attachments']
};
})(ArticleAddModal);

View File

@ -2,7 +2,11 @@
width: 800px;
&__cancel-button {
float: right;
float: left;
margin-top: 15px;
}
&__submit-button {
float: right;
}
}

View File

@ -36,11 +36,13 @@ class ArticlesList extends React.Component {
}
render() {
if(this.props.errored) {
return <Message type="error">{i18n('ERROR_RETRIEVING_ARTICLES')}</Message>;
const { errored, loading } = this.props;
if(errored) {
return <Message showCloseButton={false} type="error">{i18n('ERROR_RETRIEVING_ARTICLES')}</Message>;
}
return (this.props.loading) ? <Loading /> : this.renderContent();
return loading ? <Loading className="articles-list__loading" backgrounded size="large"/> : this.renderContent();
}
renderContent() {
@ -53,17 +55,19 @@ class ArticlesList extends React.Component {
}
renderTopics() {
const { topics, editable, articlePath } = this.props;
return (
<div className="articles-list__topics">
{this.props.topics.map((topic, index) => {
{topics.map((topic, index) => {
return (
<div key={index}>
<TopicViewer
{...topic}
id={topic.id * 1}
editable={this.props.editable}
editable={editable}
onChange={this.retrieveArticles.bind(this)}
articlePath={this.props.articlePath} />
articlePath={articlePath} />
<span className="separator" />
</div>
);
@ -76,7 +80,7 @@ class ArticlesList extends React.Component {
return (
<div className="articles-list__add-topic-button">
<Button onClick={() => ModalContainer.openModal(<TopicEditModal addForm onChange={this.retrieveArticles.bind(this)} />)} type="secondary" className="articles-list__add">
<Icon name="plus-circle" size="2x" className="articles-list__add-icon"/> {i18n('ADD_TOPIC')}
<Icon name="plus" className="articles-list__add-icon" /> {i18n('ADD_TOPIC')}
</Button>
</div>
);
@ -88,9 +92,11 @@ class ArticlesList extends React.Component {
}
export default connect((store) => {
const { topics, errored, loading } = store.articles;
return {
topics: store.articles.topics,
errored: store.articles.errored,
loading: store.articles.loading
topics: topics.map((topic) => {return {...topic, private: topic.private === "1"}}),
errored,
loading
};
})(ArticlesList);

View File

@ -1,14 +1,19 @@
@import "../scss/vars";
.articles-list {
&__add {
position: relative;
}
&__add-icon {
position: absolute;
left: 10px;
margin-top: -4px;
&__loading {
min-width: 300px;
min-height: 300px;
}
}
&__add-topic-button {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
}

View File

@ -0,0 +1,41 @@
import React from 'react';
import DropDown from 'core-components/drop-down';
import Icon from 'core-components/icon';
class DepartmentDropdown extends React.Component {
static propTypes = {
value: React.PropTypes.number,
onChange: React.PropTypes.func,
departments: React.PropTypes.array
}
render() {
return <DropDown {...this.props} onChange={this.onChange.bind(this)} items={this.getDepartments()} />
}
getDepartments() {
let departments = this.props.departments.map((department) => {
if(department.private*1) {
return {content: <span>{department.name} <Icon name='user-secret'/></span>};
} else {
return {content: department.name};
}
});
return departments;
}
onChange(event) {
if(this.props.onChange) {
this.props.onChange({
index: event.index,
target: {
value: event.index
}
});
}
}
}
export default DepartmentDropdown;

View File

@ -3,7 +3,6 @@ import {connect} from 'react-redux';
import classNames from 'classnames';
import languageList from 'data/language-list';
import i18n from 'lib-app/i18n';
import DropDown from 'core-components/drop-down';
const languageCodes = Object.keys(languageList);

View File

@ -1,7 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import keyCode from 'keycode';
import classNames from 'classnames';
import store from 'app/store';
import ModalActions from 'actions/modal-actions';
@ -9,11 +8,14 @@ import Modal from 'core-components/modal';
class ModalContainer extends React.Component {
static openModal(content, noPadding) {
static openModal(
content,
options={noPadding: false, outsideClick: false, closeButton: {showCloseButton: false, whiteColor: false}}
) {
store.dispatch(
ModalActions.openModal({
content,
noPadding
options
})
);
}
@ -49,8 +51,16 @@ class ModalContainer extends React.Component {
}
renderModal() {
const { content, options } = this.props.modal;
const { noPadding, outsideClick, closeButton } = options;
return (
<Modal content={this.props.modal.content} noPadding={this.props.modal.noPadding}/>
<Modal
content={content}
noPadding={noPadding}
outsideClick={outsideClick}
onOutsideClick={this.closeModal.bind(this)}
closeButton={closeButton} />
);
}
@ -69,4 +79,4 @@ export default connect((store) => {
return {
modal: store.modal
};
})(ModalContainer);
})(ModalContainer);

View 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;

View File

@ -0,0 +1,102 @@
import React from 'react';
import classNames from 'classnames';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import Form from 'core-components/form';
import FormField from 'core-components/form-field';
import Widget from 'core-components/widget';
import Button from 'core-components/button';
import SubmitButton from 'core-components/submit-button';
import Message from 'core-components/message';
class PasswordRecovery extends React.Component {
static propTypes = {
recoverSent: React.PropTypes.bool,
formProps: React.PropTypes.object,
onBackToLoginClick: React.PropTypes.func,
renderLogo: React.PropTypes.bool
};
static defaultProps = {
renderLogo: false
};
state = {
showRecoverSentMessage: true
}
componentDidUpdate(prevProps) {
if (!prevProps.recoverSent && this.props.recoverSent) {
this.setState({showRecoverSentMessage : true});
}
}
render() {
const { renderLogo, formProps, onBackToLoginClick, style } = this.props;
return (
<Widget style={style} className={this.getClass()} title={!renderLogo ? i18n('RECOVER_PASSWORD') : ''}>
{this.renderLogo()}
<Form {...formProps}>
<div className="password-recovery__inputs">
<FormField ref="email" placeholder={i18n('EMAIL_LOWERCASE')} name="email" className="password-recovery__input" validation="EMAIL" required/>
</div>
<div className="password-recovery__submit-button">
<SubmitButton type="primary">{i18n('RECOVER_PASSWORD')}</SubmitButton>
</div>
</Form>
<Button className="password-recovery__forgot-password" type="link" onClick={onBackToLoginClick} onMouseDown={(event) => {event.preventDefault()}}>
{i18n('BACK_LOGIN_FORM')}
</Button>
{this.renderRecoverStatus()}
</Widget>
);
}
getClass() {
return classNames({
'password-recovery__content': true,
[this.props.className]: (this.props.className)
});
}
renderLogo() {
let logo = null;
if (this.props.renderLogo) {
logo = (<div className="password-recovery__image"><img width="100%" src={API.getURL() + '/images/logo.png'} alt="OpenSupports Login Panel"/></div>);
}
return logo;
}
renderRecoverStatus() {
return (
this.props.recoverSent ?
<Message
showMessage={this.state.showRecoverSentMessage}
onCloseMessage={this.onCloseMessage.bind(this, "showRecoverSentMessage")}
className="password-recovery__message"
type="info"
leftAligned>
{i18n('RECOVER_SENT')}
</Message> :
null
);
}
focusEmail() {
this.refs.email.focus();
}
onCloseMessage(showMessage) {
this.setState({
[showMessage]: false
});
}
}
export default PasswordRecovery;

View File

@ -0,0 +1,38 @@
.password-recovery {
&__inputs {
display: inline-block;
margin: 0 auto 20px;
text-align: left;
}
&__content {
margin: 0 auto;
padding: 40px;
}
&__forgot-password {
margin-top: 20px;
}
&__message {
margin-top: 18px;
}
&__image {
width: 365px;
margin-bottom: 30px;
}
}
@media screen and (max-width: 320px) {
.widget {
margin-left: -12px;
}
}
@media screen and (max-width: 992px) {
.password-recovery__content {
width: 100%;
}
}

View File

@ -93,7 +93,7 @@ class PeopleList extends React.Component {
</div>
<div className="people-list__item-block people-list__item-last-login">
<div>{i18n('LAST_LOGIN')}</div>
<div>{DateTransformer.transformToString(item.lastLogin)}</div>
<div>{item.lastLogin ? DateTransformer.transformToString(item.lastLogin) : i18n('NEVER')}</div>
</div>
</div>
) : null;

View File

@ -61,4 +61,28 @@
&__pagination {
}
}
@media screen and (max-width: 415px) {
.people-list {
&__item {
height: unset;
padding: unset;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-end;
&-profile-pic-wrapper {
top: 15px;
left: 15px;
}
&-block {
width: unset;
display: unset;
padding: 15px 10px 0 0;
}
}
}
}
}

View File

@ -0,0 +1,48 @@
import React from 'react';
import ModalContainer from 'app-components/modal-container';
import Button from 'core-components/button';
import Message from 'core-components/message';
class PopupMessage extends React.Component {
static propTypes = Message.propTypes;
static contextTypes = {
closeModal: React.PropTypes.func
};
static open(props) {
ModalContainer.openModal(
<PopupMessage {...props} />,
{noPadding: true, outsideClick: true, closeButton: {showCloseButton: false, whiteColor: false}}
);
}
componentDidMount() {
this.refs.closeButton && this.refs.closeButton.focus();
}
render() {
return (
<div className="popup-message">
<Message {...this.props} showCloseButton={false} className="popup-message__message" />
<Button
className="popup-message__close-button"
iconName="times"
type="clean"
ref="closeButton"
onClick={this.closeModal.bind(this)} />
</div>
);
}
closeModal() {
const { closeModal } = this.context;
closeModal && closeModal();
}
}
export default PopupMessage;

View File

@ -0,0 +1,17 @@
@import "../scss/vars";
.popup-message {
min-width: 500px;
position: relative;
&__close-button {
position: absolute;
top: 0;
right: 0;
color: $dark-grey;
&:focus {
outline: none;
color: $grey;
}
}
}

View 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;

View File

@ -0,0 +1,42 @@
import React from 'react';
import Tooltip from 'core-components/tooltip';
class StatCard extends React.Component {
static propTypes = {
label: React.PropTypes.string,
description: React.PropTypes.string,
value: React.PropTypes.number,
isPercentage: React.PropTypes.bool
};
state = {
showTooltip: false
};
render() {
const {
label,
description,
value,
isPercentage
} = this.props;
const displayValue = isNaN(value) ? "-" : (isPercentage ? value.toFixed(2) : value);
return (
<Tooltip content={description} show={this.state.showTooltip} >
<div className="stat-card" onMouseEnter={() => this.setState({showTooltip: true})} onMouseLeave={() => this.setState({showTooltip: false})}>
<div className="stat-card__wrapper">
{label}
<div className="stat-card__container">
{displayValue}{isPercentage && !isNaN(value) ? "%" : ""}
</div>
</div>
</div>
</Tooltip>
);
}
}
export default StatCard;

View File

@ -0,0 +1,22 @@
@import "../scss/vars";
.stat-card {
margin: 0 4px;
&__wrapper {
padding: 10px;
min-width: 160px;
margin: 8px auto;
transition: 0.3s;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
text-align: center;
&:hover {
box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
}
}
&__container {
font-size: $font-size--lg;
padding: 16px;
}
}

View File

@ -1,100 +0,0 @@
import React from 'react';
import {Line} from 'react-chartjs-2';
import i18n from 'lib-app/i18n';
class StatsChart extends React.Component {
static propTypes = {
strokes: React.PropTypes.arrayOf(React.PropTypes.shape({
name: React.PropTypes.string,
values: React.PropTypes.arrayOf(React.PropTypes.shape({
date: React.PropTypes.string,
value: React.PropTypes.number
}))
})),
period: React.PropTypes.number
};
render() {
return (
<div>
<Line data={this.getChartData()} options={this.getChartOptions()} width={800} height={400} redraw/>
</div>
);
}
getChartData() {
let labels = this.getLabels();
let color = {
'CLOSE': 'rgba(150, 20, 20, 0.6)',
'CREATE_TICKET': 'rgba(20, 150, 20, 0.6)',
'SIGNUP': 'rgba(20, 20, 150, 0.6)',
'COMMENT': 'rgba(20, 200, 200, 0.6)',
'ASSIGN': 'rgba(20, 150, 20, 0.6)'
};
let strokes = this.props.strokes.slice();
let datasets = strokes.map((stroke, index) => {
return {
label: i18n('CHART_' + stroke.name),
data: stroke.values.map((val) => val.value),
fill: false,
borderWidth: this.getBorderWidth(),
borderColor: color[stroke.name],
pointBorderColor: color[stroke.name],
pointRadius: 0,
pointHoverRadius: 3,
lineTension: 0.2,
pointHoverBackgroundColor: color[stroke.name],
hitRadius: this.hitRadius(index)
}
});
return {
labels: labels,
datasets: datasets
};
}
getBorderWidth() {
return (this.props.period <= 90) ? 3 : 2;
}
getLabels() {
let months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
let labels = [];
if (!this.props.strokes.length) {
labels = Array.from('x'.repeat(this.props.period));
}
else {
labels = this.props.strokes[0].values.map((item) => {
let idx = item.date.slice(4, 6) - 1;
return item.date.slice(6, 8) + ' ' + months[idx];
});
}
return labels;
}
hitRadius(index) {
if (this.props.period <= 7) return 20;
if (this.props.period <= 30) return 15;
if (this.props.period <= 90) return 10;
return 3;
}
getChartOptions() {
return {
legend: {
display: false
}
};
}
}
export default StatsChart;

View File

@ -1,199 +0,0 @@
import React from 'react';
import _ from 'lodash';
import classNames from 'classnames';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import DropDown from 'core-components/drop-down';
import ToggleList from 'core-components/toggle-list';
import StatsChart from 'app-components/stats-chart';
const generalStrokes = ['CREATE_TICKET', 'CLOSE', 'SIGNUP', 'COMMENT'];
const staffStrokes = ['ASSIGN', 'CLOSE'];
const ID = {
'CREATE_TICKET': 0,
'ASSIGN': 0,
'CLOSE': 1,
'SIGNUP': 2,
'COMMENT': 3
};
class Stats extends React.Component {
static propTypes = {
type: React.PropTypes.string,
staffId: React.PropTypes.number
};
state = {
stats: this.getDefaultStats(),
strokes: this.getStrokes().map((name) => {
return {
name: name,
values: []
}
}),
showed: [0],
period: 0
};
componentDidMount() {
this.retrieve(7);
}
render() {
return (
<div className={this.getClass()}>
<DropDown {...this.getDropDownProps()}/>
<ToggleList {...this.getToggleListProps()} />
<StatsChart {...this.getStatsChartProps()} />
</div>
);
}
getClass() {
let classes = {
'stats': true,
'stats_staff': this.props.type === 'staff'
};
return classNames(classes);
}
getToggleListProps() {
return {
values: this.state.showed,
className: 'stats__toggle-list',
onChange: this.onToggleListChange.bind(this),
type: this.props.type === 'general' ? 'default' : 'small',
items: this.getStrokes().map((name) => {
return {
className: 'stats__toggle-list_' + name,
content: (
<div className="stats__toggle-list-item">
<div className="stats__toggle-list-item-value">{this.state.stats[name]}</div>
<div className="stats__toggle-list-item-name">{i18n('CHART_' + name)}</div>
</div>
)
}
})
};
}
onToggleListChange(event) {
this.setState({
showed: event.target.value
});
}
getDropDownProps() {
return {
items: ['LAST_7_DAYS', 'LAST_30_DAYS', 'LAST_90_DAYS', 'LAST_365_DAYS'].map((name) => {
return {
content: i18n(name),
icon: ''
};
}),
onChange: this.onDropDownChange.bind(this),
className: 'stats__dropdown'
}
}
onDropDownChange(event) {
let val = [7, 30, 90, 365];
this.retrieve(val[event.index]);
}
getStatsChartProps() {
let showed = this.getShowedArray();
return {
period: this.state.period,
strokes: _.filter(this.state.strokes, (s, i) => showed[i])
};
}
retrieve(period) {
let periodName;
switch (period) {
case 30:
periodName = 'MONTH';
break;
case 90:
periodName = 'QUARTER';
break;
case 365:
periodName = 'YEAR';
break;
default:
periodName = 'WEEK';
}
API.call({
path: '/system/get-stats',
data: this.getApiCallData(periodName)
}).then(this.onRetrieveSuccess.bind(this));
}
onRetrieveSuccess(result) {
let newStats = this.getDefaultStats();
let newStrokes = this.getStrokes().map((name) => {
return {
name: name,
values: []
};
});
let realPeriod = result.data.length / this.getStrokes().length;
result.data.reverse().map((item) => {
newStats[item.type] += item.value * 1;
newStrokes[ ID[item.type] ].values.push({
date: item.date,
value: item.value * 1
});
});
this.setState({stats: newStats, strokes: newStrokes, period: realPeriod});
}
getShowedArray() {
let showed = this.getStrokes().map(() => false);
for (let i = 0; i < showed.length; i++) {
showed[this.state.showed[i]] = true;
}
return showed;
}
getStrokes() {
return this.props.type === 'general' ? generalStrokes : staffStrokes;
}
getDefaultStats() {
return this.props.type === 'general' ?
{
'CREATE_TICKET': 0,
'CLOSE': 0,
'SIGNUP': 0,
'COMMENT': 0
} :
{
'ASSIGN': 0,
'CLOSE': 0
};
}
getApiCallData(periodName) {
return this.props.type === 'general' ? {period: periodName} : {period: periodName, staffId: this.props.staffId};
}
}
export default Stats;

View File

@ -1,76 +0,0 @@
@import '../scss/vars';
.stats {
&__dropdown {
margin-left: auto;
margin-bottom: 20px;
}
&__toggle-list {
margin-bottom: 20px;
user-select: none;
&-item {
&-value {
font-size: $font-size--lg;
line-height: 80px;
}
&-name {
font-size: $font-size--md;
line-height: 20px;
display: inline-flex;
}
}
&_CREATE_TICKET.toggle-list__selected {
box-shadow: inset 0 -5px 0px 0px rgba(20, 150, 20, 0.6);
}
&_CLOSE.toggle-list__selected {
box-shadow: inset 0 -5px 0px 0px rgba(150, 20, 20, 0.6);
}
&_SIGNUP.toggle-list__selected {
box-shadow: inset 0 -5px 0px 0px rgba(20, 20, 150, 0.6);
}
&_COMMENT.toggle-list__selected {
box-shadow: inset 0 -5px 0px 0px rgba(20, 200, 200, 0.6);
}
&_ASSIGN.toggle-list__selected {
box-shadow: inset 0 -5px 0px 0px rgba(20, 150, 20, 0.6);
}
}
&_staff {
.stats__dropdown {
margin-left: auto;
margin-bottom: 20px;
float: left;
}
.stats__toggle-list {
margin-bottom: 20px;
float: right;
&-item {
&-value {
font-size: $font-size--md;
line-height: 40px;
}
&-name {
font-size: $font-size--sm;
line-height: 20px;
}
}
}
}
}

View File

@ -1,12 +1,20 @@
import React from 'react';
import classNames from 'classnames';
import {connect} from 'react-redux';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import DateTransformer from 'lib-core/date-transformer';
import Icon from 'core-components/icon';
import Tooltip from 'core-components/tooltip';
import Button from 'core-components/button';
import SubmitButton from 'core-components/submit-button';
import Form from 'core-components/form';
import FormField from 'core-components/form-field';
const VIEW_USER_PATH = "/admin/panel/users/view-user/";
const VIEW_STAFF_PATH = "/admin/panel/staff/view-staff/";
class TicketEvent extends React.Component {
static propTypes = {
type: React.PropTypes.oneOf([
@ -16,11 +24,20 @@ class TicketEvent extends React.Component {
'CLOSE',
'RE_OPEN',
'DEPARTMENT_CHANGED',
'PRIORITY_CHANGED'
]),
author: React.PropTypes.object,
content: React.PropTypes.string,
date: React.PropTypes.string
date: React.PropTypes.string,
private: React.PropTypes.string,
edited: React.PropTypes.bool,
edit: React.PropTypes.bool,
onToggleEdit: React.PropTypes.func,
isLastComment: React.PropTypes.bool,
isTicketClosed: React.PropTypes.bool
};
state = {
content: this.props.content
};
render() {
@ -71,42 +88,116 @@ class TicketEvent extends React.Component {
'CLOSE': this.renderClosed.bind(this),
'RE_OPEN': this.renderReOpened.bind(this),
'DEPARTMENT_CHANGED': this.renderDepartmentChange.bind(this),
'PRIORITY_CHANGED': this.renderPriorityChange.bind(this)
};
return renders[this.props.type]();
}
renderComment() {
const { author, date, edit, file } = this.props;
const customFields = (author && author.customfields) || [];
return (
<div className="ticket-event__comment">
<span className="ticket-event__comment-pointer" />
<div className="ticket-event__comment-author">
<span className="ticket-event__comment-author-name">{this.props.author.name}</span>
<span className="ticket-event__comment-author-type">({i18n((this.props.author.staff) ? 'STAFF' : 'CUSTOMER')})</span>
{this.renderCommentAuthor()}
<span className="ticket-event__comment-badge-container">
<span className="ticket-event__comment-badge">{i18n((author.staff) ? 'STAFF' : 'CUSTOMER')}</span>
</span>
{customFields.map(this.renderCustomFieldValue.bind(this))}
{(this.props.private*1) ? this.renderPrivateBadge() : null}
</div>
<div className="ticket-event__comment-date">{DateTransformer.transformToString(this.props.date)}</div>
<div className="ticket-event__comment-content" dangerouslySetInnerHTML={{__html: this.props.content}}></div>
{this.renderFileRow(this.props.file)}
<div className="ticket-event__comment-date">{DateTransformer.transformToString(date)}</div>
{!edit ? this.renderContent() : this.renderEditField()}
{this.renderFooter(file)}
</div>
);
}
renderCommentAuthor() {
const {
author,
level
} = this.props;
const commentAutorClass = "ticket-event__comment-author-name";
let commentAuthor;
if(level === "3") {
commentAuthor = (
<a className={commentAutorClass} href={((author.staff) ? VIEW_STAFF_PATH : VIEW_USER_PATH)+author.id}>
{author.name}
</a>
);
} else if(level && !author.staff) {
commentAuthor = <a className={commentAutorClass} href={VIEW_USER_PATH+author.id}>{author.name}</a>;
} else {
commentAuthor = <span className={commentAutorClass}>{author.name}</span>;
}
return commentAuthor;
}
renderContent() {
const { content, author, userId, userStaff, isLastComment, isTicketClosed } = this.props;
const { id, staff } = author;
return (
<div className="ticket-event__comment-content ql-editor">
<div dangerouslySetInnerHTML={{__html: content}}></div>
{(id == userId && staff == userStaff && isLastComment && !isTicketClosed) ? this.renderEditIcon() : null }
</div>
)
}
renderEditIcon() {
return (
<div className="ticket-event__comment-content__edit" >
<Icon name="pencil" onClick={this.props.onToggleEdit} />
</div>
)
}
renderEditField() {
return (
<Form loading={this.props.loading} values={{content:this.state.content}} onChange={(form) => {this.setState({content:form.content})}} onSubmit={this.props.onEdit}>
<FormField name="content" required field="textarea" validation="TEXT_AREA" fieldProps={{allowImages: this.props.allowAttachments}}/>
<div className="ticket-event__submit-edited-comment" >
<SubmitButton type="secondary" >{i18n('SUBMIT')}</SubmitButton>
<Button size="medium" onClick={this.props.onToggleEdit}>{i18n('CLOSE')}</Button>
</div>
</Form>
);
}
renderAssignment() {
let assignedTo = this.props.content;
let authorName = this.props.author.name;
if(!assignedTo || assignedTo == authorName) {
assignedTo = i18n('HIMSELF');
}
return (
<div className="ticket-event__circled">
<span className="ticket-event__circled-author">{this.props.author.name}</span>
<span className="ticket-event__circled-author">{authorName}</span>
<span className="ticket-event__circled-text"> {i18n('ACTIVITY_ASSIGN_THIS')}</span>
<span className="ticket-event__circled-text"> {assignedTo}</span>
<span className="ticket-event__circled-date"> {i18n('DATE_PREFIX')} {DateTransformer.transformToString(this.props.date)}</span>
</div>
)
}
renderUnAssignment() {
let unAssignedTo = this.props.content;
let authorName = this.props.author.name;
if(!unAssignedTo || unAssignedTo == authorName) {
unAssignedTo = i18n('HIMSELF');
}
return (
<div className="ticket-event__circled">
<span className="ticket-event__circled-author">{this.props.author.name}</span>
<span className="ticket-event__circled-author">{authorName}</span>
<span className="ticket-event__circled-text"> {i18n('ACTIVITY_UN_ASSIGN_THIS')}</span>
<span className="ticket-event__circled-text"> {unAssignedTo}</span>
<span className="ticket-event__circled-date"> {i18n('DATE_PREFIX')} {DateTransformer.transformToString(this.props.date)}</span>
</div>
)
@ -143,19 +234,19 @@ class TicketEvent extends React.Component {
);
}
renderPriorityChange() {
renderPrivateBadge() {
return (
<div className="ticket-event__circled">
<span className="ticket-event__circled-author">{this.props.author.name}</span>
<span className="ticket-event__circled-text"> {i18n('ACTIVITY_PRIORITY_CHANGED_THIS')}</span>
<span className="ticket-event__circled-indication"> {this.props.content}</span>
<span className="ticket-event__circled-date"> {i18n('DATE_PREFIX')} {DateTransformer.transformToString(this.props.date)}</span>
</div>
<span className="ticket-event__comment-badge-container">
<Tooltip content={i18n('PRIVATE_RESPONSE_DESCRIPTION')} openOnHover>
<span className="ticket-event__comment-badge">{i18n('PRIVATE')}</span>
</Tooltip>
</span>
);
}
renderFileRow(file) {
renderFooter(file) {
let node = null;
let edited = null;
if (file) {
node = <span> {this.getFileLink(file)} <Icon name="paperclip" /> </span>;
@ -163,11 +254,30 @@ class TicketEvent extends React.Component {
node = i18n('NO_ATTACHMENT');
}
if (this.props.edited && this.props.type === 'COMMENT') {
edited = i18n('COMMENT_EDITED');
}
return (
<div className="ticket-event__file">
{node}
<div className="ticket-event__items">
<div className="ticket-event__edited">
{edited}
</div>
<div className="ticket-event__file">
{node}
</div>
</div>
)
);
}
renderCustomFieldValue(customField) {
return (
<span className="ticket-event__comment-badge-container">
<span className="ticket-event__comment-badge">
{customField.customfield}: <span className="ticket-event__comment-badge-value">{customField.value}</span>
</span>
</span>
);
}
getClass() {
@ -178,7 +288,6 @@ class TicketEvent extends React.Component {
'CLOSE': true,
'RE_OPEN': true,
'DEPARTMENT_CHANGED': true,
'PRIORITY_CHANGED': true
};
const classes = {
'row': true,
@ -189,7 +298,7 @@ class TicketEvent extends React.Component {
'ticket-event_close': this.props.type === 'CLOSE',
'ticket-event_reopen': this.props.type === 'RE_OPEN',
'ticket-event_department': this.props.type === 'DEPARTMENT_CHANGED',
'ticket-event_priority': this.props.type === 'PRIORITY_CHANGED'
'ticket-event_private': this.props.private*1,
};
return classNames(classes);
@ -203,7 +312,6 @@ class TicketEvent extends React.Component {
'CLOSE': 'lock',
'RE_OPEN': 'unlock-alt',
'DEPARTMENT_CHANGED': 'exchange',
'PRIORITY_CHANGED': 'exclamation'
};
const iconSize = {
'COMMENT': '2x',
@ -212,7 +320,6 @@ class TicketEvent extends React.Component {
'CLOSE': 'lg',
'RE_OPEN': 'lg',
'DEPARTMENT_CHANGED': 'lg',
'PRIORITY_CHANGED': 'lg'
};
return {
@ -230,4 +337,7 @@ class TicketEvent extends React.Component {
}
}
export default TicketEvent;
export default connect((store) => {
return { level: store.session.userLevel };
})(TicketEvent);

View File

@ -47,7 +47,7 @@
&__comment {
position: relative;
word-break: break-all;
word-wrap: break-word;
&-pointer {
right: 100%;
@ -64,13 +64,19 @@
position: relative;
padding: 12px;
color: $primary-black;
}
&-type {
font-size: 10px;
padding-left: 10px;
color: $secondary-blue;
font-variant: small-caps;
}
&-badge-container {
margin-left: 12px;
}
&-badge {
font-size: 10.6px;
font-weight: bold;
color: $primary-blue;
background-color: $very-light-grey;
padding: 6px;
width: 100px;
}
&-date {
@ -78,6 +84,8 @@
border: 2px solid $light-grey;
border-bottom: none;
padding: 12px;
font-size: 12.5px;
font-family: helvetica;
background-color: $light-grey;
}
@ -86,27 +94,62 @@
background-color: white;
border: 2px solid $very-light-grey;
border-top: none;
padding: 20px 10px;
padding: 28px 10px;
text-align: left;
position:relative;
overflow-y: initial;
&:hover {
.ticket-event__comment-content__edit {
color: grey;
cursor:pointer;
}
}
img {
max-width:100%;
}
&__edit {
position:absolute;
top: 3px;
right: 9px;
align-self: right;
color:white;
}
}
}
&__submit-edited-comment {
display:flex;
align-items: center;
justify-content: space-between;
padding: 8px;
}
&__items {
background-color: $very-light-grey;
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px;
font-size: 12px;
}
&__file {
background-color: $very-light-grey;
cursor: pointer;
text-align: right;
padding: 5px 10px;
font-size: 12px;
}
&__edited{
font-style: italic;
}
&__comment-badge-value {
font-weight: normal;
}
&_staff {
.ticket-event__icon {
background-color: $primary-blue;
}
.ticket-event__comment-author-type {
color: $primary-blue;
}
}
&_circled {
@ -170,10 +213,17 @@
}
}
&_priority {
.ticket-event__icon {
padding-left: 11px;
padding-top: 5px;
&_private {
.ticket-event__comment-pointer {
border-right-color: $light-yellow;
}
.ticket-event__comment-date {
background-color: $light-yellow;
border-color: $light-yellow;
}
.ticket-event__staff-pic {
background-color: $light-yellow;
border-color: $light-yellow;
}
}
}
}

View File

@ -1,5 +1,6 @@
import React from 'react';
import _ from 'lodash';
import htmlToText from 'html-to-text';
import i18n from 'lib-app/i18n';
import Icon from 'core-components/icon';
@ -19,7 +20,7 @@ class TicketInfo extends React.Component {
</span>
</div>
<div className="ticket-info__description">
{this.props.ticket.content}
{htmlToText.fromString(this.props.ticket.content)}
</div>
<div className="ticket-info__author">
@ -34,14 +35,6 @@ class TicketInfo extends React.Component {
{(this.props.ticket.closed) ? 'closed' : 'open'}
</span>
</div>
<div className="ticket-info__properties__priority">
<span className="ticket-info__properties__label">
{i18n('PRIORITY')}:
</span>
<span className={this.getPriorityClass()}>
{this.props.ticket.priority}
</span>
</div>
<div className="ticket-info__properties__owner">
<span className="ticket-info__properties__label">
{i18n('OWNED')}:
@ -79,15 +72,6 @@ class TicketInfo extends React.Component {
}
}
getPriorityClass() {
let priorityClasses = {
'low': 'ticket-info__properties__badge-green',
'medium': 'ticket-info__properties__badge-blue',
'high': 'ticket-info__properties__badge-red'
};
return priorityClasses[this.props.ticket.priority];
}
}
export default TicketInfo;
export default TicketInfo;

View File

@ -3,6 +3,7 @@
.ticket-info {
width: 300px;
font-weight: normal;
text-align: left;
&__title {
color: $primary-black;
@ -35,7 +36,6 @@
&__status,
&__owner,
&__priority,
&__comments {
display: inline-block;
width: 50%;
@ -86,4 +86,4 @@
}
}
}
}
}

View File

@ -1,14 +1,22 @@
import React from 'react';
import _ from 'lodash';
import {connect} from 'react-redux';
import queryString from 'query-string';
import i18n from 'lib-app/i18n';
import DateTransformer from 'lib-core/date-transformer';
import TicketInfo from 'app-components/ticket-info';
import DepartmentDropdown from 'app-components/department-dropdown';
import Table from 'core-components/table';
import Button from 'core-components/button';
import Tooltip from 'core-components/tooltip';
import DropDown from 'core-components/drop-down';
import Checkbox from 'core-components/checkbox';
import Tag from 'core-components/tag';
import Icon from 'core-components/icon';
import Message from 'core-components/message';
import history from 'lib-app/history';
import PageSizeDropdown from './page-size-dropdown';
class TicketList extends React.Component {
static propTypes = {
@ -17,10 +25,15 @@ class TicketList extends React.Component {
ticketPath: React.PropTypes.string,
showDepartmentDropdown: React.PropTypes.bool,
tickets: React.PropTypes.arrayOf(React.PropTypes.object),
userId: React.PropTypes.number,
type: React.PropTypes.oneOf([
'primary',
'secondary'
])
]),
closedTicketsShown: React.PropTypes.bool,
onClosedTicketsShownChange: React.PropTypes.func,
onDepartmentChange: React.PropTypes.func,
showPageSizeDropdown: React.PropTypes.bool
};
static defaultProps = {
@ -29,7 +42,9 @@ class TicketList extends React.Component {
tickets: [],
departments: [],
ticketPath: '/dashboard/ticket/',
type: 'primary'
type: 'primary',
closedTicketsShown: false,
showPageSizeDropdown: true
};
state = {
@ -37,61 +52,129 @@ class TicketList extends React.Component {
};
render() {
const { type, showDepartmentDropdown, onClosedTicketsShownChange, showPageSizeDropdown } = this.props;
const pages = [5, 10, 20, 50];
return (
<div className="ticket-list">
{(this.props.type === 'secondary' && this.props.showDepartmentDropdown) ? this.renderDepartmentsDropDown() : null}
<div className="ticket-list__filters">
<div className="ticket-list__main-filters">
{(type === 'primary') ? this.renderMessage() : null}
{
((type === 'secondary') && showDepartmentDropdown) ?
this.renderDepartmentsDropDown() :
null
}
{onClosedTicketsShownChange ? this.renderFilterCheckbox() : null}
</div>
{
showPageSizeDropdown ?
<PageSizeDropdown className="ticket-list__page-dropdown" pages={pages} onChange={(event) => this.pageSizeChange(event)} /> :
null
}
</div>
<Table {...this.getTableProps()} />
</div>
);
}
renderFilterCheckbox() {
return (
<Checkbox
className="ticket-list__checkbox"
label={i18n("SHOW_CLOSED_TICKETS")}
value={this.props.closedTicketsShown}
onChange={this.props.onClosedTicketsShownChange}
wrapInLabel
/>
);
}
renderDepartmentsDropDown() {
return (
<div className="ticket-list__department-selector">
<DropDown {...this.getDepartmentDropdownProps()} />
<DepartmentDropdown {...this.getDepartmentDropdownProps()} />
</div>
);
}
renderMessage() {
switch (queryString.parse(window.location.search)["message"]) {
case 'success':
return (
<Message
onCloseMessage={this.onCloseMessage}
className="create-ticket-form__message"
type="success">
{i18n('TICKET_SENT')}
</Message>
);
case 'fail':
return (
<Message
onCloseMessage={this.onCloseMessage}
className="create-ticket-form__message"
type="error">
{i18n('TICKET_SENT_ERROR')}
</Message>
);
default:
return null;
}
}
pageSizeChange(event) {
const { onPageSizeChange } = this.props;
onPageSizeChange && onPageSizeChange(event.pageSize);
}
getDepartmentDropdownProps() {
const { departments, onDepartmentChange } = this.props;
return {
items: this.getDepartments(),
departments: this.getDepartments(),
onChange: (event) => {
const departmentId = event.index && departments[event.index - 1].id;
this.setState({
selectedDepartment: event.index && this.props.departments[event.index - 1].id
selectedDepartment: departmentId
});
onDepartmentChange && onDepartmentChange(departmentId || null);
},
size: 'medium'
};
}
getTableProps() {
const { loading, page, pages, onPageChange } = this.props;
return {
loading: this.props.loading,
loading,
headers: this.getTableHeaders(),
rows: this.getTableRows(),
pageSize: 10,
comp: this.compareFunction,
page: this.props.page,
pages: this.props.pages,
onPageChange: this.props.onPageChange
pageSize: this.state.tickets,
page,
pages,
onPageChange
};
}
getDepartments() {
let departments = this.props.departments.map((department) => {
return {content: department.name};
});
let departments = _.clone(this.props.departments);
departments.unshift({
content: i18n('ALL_DEPARTMENTS')
name: i18n('ALL_DEPARTMENTS')
});
return departments;
}
getTableHeaders() {
if (this.props.type == 'primary' ) {
const { type } = this.props;
if(type == 'primary' ) {
return [
{
key: 'number',
@ -110,11 +193,14 @@ class TicketList extends React.Component {
},
{
key: 'date',
value: i18n('DATE'),
value: <div>
{i18n('DATE')}
{this.renderSortArrow('date')}
</div>,
className: 'ticket-list__date col-md-2'
}
];
} else if (this.props.type == 'secondary') {
} else if(type == 'secondary') {
return [
{
key: 'number',
@ -126,11 +212,6 @@ class TicketList extends React.Component {
value: i18n('TITLE'),
className: 'ticket-list__title col-md-4'
},
{
key: 'priority',
value: i18n('PRIORITY'),
className: 'ticket-list__priority col-md-1'
},
{
key: 'department',
value: i18n('DEPARTMENT'),
@ -143,99 +224,114 @@ class TicketList extends React.Component {
},
{
key: 'date',
value: i18n('DATE'),
value: <div>
{i18n('DATE')}
{this.renderSortArrow('date')}
</div>,
className: 'ticket-list__date col-md-2'
}
];
}
}
renderSortArrow(header) {
const { orderBy, showOrderArrows, onChangeOrderBy } = this.props;
return (
showOrderArrows ?
<Icon
name={`arrow-${this.getIconName(header, orderBy)}`}
className="ticket-list__order-icon"
color={this.getIconColor(header, orderBy)}
onClick={() => onChangeOrderBy(header)} /> :
null
);
}
getIconName(header, orderBy) {
return (orderBy && orderBy.value === header && orderBy.asc) ? "up" : "down";
}
getIconColor(header, orderBy) {
return (orderBy && orderBy.value === header) ? "gray" : "white";
}
getTableRows() {
return this.getTickets().map(this.gerTicketTableObject.bind(this));
return this.getTickets().map(this.getTicketTableObject.bind(this));
}
getTickets() {
return (this.state.selectedDepartment) ? _.filter(this.props.tickets, (ticket) => {
return ticket.department.id == this.state.selectedDepartment
}) : this.props.tickets;
const { tickets } = this.props;
const { selectedDepartment } = this.state;
return (
(selectedDepartment) ?
_.filter(tickets, (ticket) => { return ticket.department.id == selectedDepartment}) :
tickets
);
}
gerTicketTableObject(ticket) {
let titleText = (this.isTicketUnread(ticket)) ? ticket.title + ' (1)' : ticket.title;
getTicketTableObject(ticket) {
const { date, title, ticketNumber, closed, tags, department, author } = ticket;
const dateTodayWithOutHoursAndMinutes = DateTransformer.getDateToday();
const ticketDateWithOutHoursAndMinutes = Math.floor(DateTransformer.UTCDateToLocalNumericDate(JSON.stringify(date*1)) / 10000);
const stringTicketLocalDateFormat = DateTransformer.transformToString(date, false, true);
const ticketDate = (
((dateTodayWithOutHoursAndMinutes - ticketDateWithOutHoursAndMinutes) > 1) ?
stringTicketLocalDateFormat :
`${(dateTodayWithOutHoursAndMinutes - ticketDateWithOutHoursAndMinutes) ? i18n("YESTERDAY_AT") : i18n("TODAY_AT")} ${stringTicketLocalDateFormat.slice(-5)}`
);
let titleText = (this.isTicketUnread(ticket)) ? title + ' (1)' : title;
return {
number: (
<Tooltip content={<TicketInfo ticket={ticket}/>} openOnHover>
{'#' + ticket.ticketNumber}
<Tooltip content={<TicketInfo ticket={ticket} />} openOnHover>
{'#' + ticketNumber}
</Tooltip>
),
title: (
<Button className="ticket-list__title-link" type="clean" route={{to: this.props.ticketPath + ticket.ticketNumber}}>
{titleText}
</Button>
<div>
{closed ? <Icon size="sm" name="lock" /> : null}
<Button className="ticket-list__title-link" type="clean" route={{to: this.props.ticketPath + ticketNumber}}>
{titleText}
</Button>
{(tags || []).map((tagName,index) => {
let tag = _.find(this.props.tags, {name:tagName});
return <Tag size='small' name={tag && tag.name} color={tag && tag.color} key={index} />
})}
</div>
),
priority: this.getTicketPriority(ticket.priority),
department: ticket.department.name,
author: ticket.author.name,
date: DateTransformer.transformToString(ticket.date, false),
department: department.name,
author: author.name,
date: ticketDate,
unread: this.isTicketUnread(ticket),
highlighted: this.isTicketUnread(ticket)
};
}
getTicketPriority(priority) {
if(priority == 'high'){
return (
<span className="ticket-list__priority-high">{i18n('HIGH')}</span>
);
}
if(priority == 'medium'){
return (
<span className="ticket-list__priority-medium">{i18n('MEDIUM')}</span>
);
}
if(priority == 'low'){
return (
<span className="ticket-list__priority-low">{i18n('LOW')}</span>
);
}
}
compareFunction(row1, row2) {
if (row1.closed == row2.closed) {
if (row1.unread == row2.unread) {
let s1 = row1.date;
let s2 = row2.date;
let y1 = s1.substring(0, 4);
let y2 = s2.substring(0, 4);
if (y1 == y2) {
let m1 = s1.substring(4, 6);
let m2 = s2.substring(4, 6);
if (m1 == m2) {
let d1 = s1.substring(6, 8);
let d2 = s2.substring(6, 8);
if (d1 == d2) {
return 0;
}
return d1 > d2 ? -1 : 1;
}
return m1 > m2 ? -1 : 1;
}
return y1 > y2 ? -1 : 1;
}
return row1.unread ? -1 : 1;
}
return row1.closed ? -1 : 1;
}
isTicketUnread(ticket) {
return (this.props.type === 'primary' && ticket.unread) || (this.props.type === 'secondary' && ticket.unreadStaff);
const { type, userId } = this.props;
const { unread, author, unreadStaff } = ticket;
if(type === 'primary') {
return unread;
} else if(type === 'secondary') {
if(author.id == userId && author.staff) {
return unread;
} else {
return unreadStaff;
}
}
}
onCloseMessage() {
history.push(window.location.pathname);
}
}
export default TicketList;
export default connect((store) => {
return {
tags: store.config['tags']
};
})(TicketList);

View File

@ -1,9 +1,39 @@
@import "../scss/vars";
.ticket-list {
&__order-icon {
padding-left: 5px;
font-size: 14px;
}
&__filters {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
text-align: left;
}
&__main-filters {
display: flex;
align-items: center;
width: 100%;
}
&__department-selector {
margin-bottom: 25px;
display: inline-block;
margin-right: 25px;
text-align: center;
}
&__page-dropdown {
display: inline-block;
margin-right: 25px;
text-align: center;
}
&__checkbox {
display: inline-block;
}
&__number {
@ -32,24 +62,8 @@
text-decoration: underline;
}
&__priority-low,
&__priority-medium,
&__priority-high {
display: inline-block;
border-radius: 10px;
width: 70px;
color: white;
}
}
&__priority-low {
background-color: $primary-green;
}
&__priority-medium {
background-color: $secondary-blue;
}
&__priority-high {
background-color: $primary-red;
}
.create-ticket-form__message {
width: 100%;
}

View File

@ -0,0 +1,350 @@
import React from 'react';
import _ from 'lodash';
import {connect} from 'react-redux';
import SearchFiltersActions from 'actions/search-filters-actions';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import history from 'lib-app/history';
import searchTicketsUtils from 'lib-app/search-tickets-utils';
import ticketUtils from 'lib-app/ticket-utils';
import Form from 'core-components/form';
import SubmitButton from 'core-components/submit-button';
import FormField from 'core-components/form-field';
import Icon from 'core-components/icon';
import Button from 'core-components/button';
import Loading from 'core-components/loading';
class TicketQueryFilters extends React.Component {
static propTypes = {
filters: React.PropTypes.shape({
query: React.PropTypes.string,
departments: React.PropTypes.string,
owners: React.PropTypes.string,
tags: React.PropTypes.string,
dateRange: React.PropTypes.string,
})
}
render() {
const {
formState,
filters,
showFilters,
ticketQueryListState,
staffList
} = this.props;
return (
<div className={"ticket-query-filters" + (showFilters ? "__open" : "") }>
<Form
loading={ticketQueryListState.loading}
values={this.getFormValue(formState)}
onChange={this.onChangeForm.bind(this)}
onSubmit={this.onSubmitListConfig.bind(this)}>
<div className="ticket-query-filters__search-box">
<FormField name="query" field="search-box" fieldProps={{onSearch: this.onSubmitListConfig.bind(this)}} />
</div>
<div className="ticket-query-filters__second-row">
<FormField
label={i18n('PERIOD')}
name="period"
field="select"
fieldProps={{
items: [{content: i18n('LAST_7_DAYS')}, {content: i18n('LAST_30_DAYS')}, {content: i18n('LAST_90_DAYS')}, {content: i18n('LAST_365_DAYS')}],
className: 'ticket-query-filters__drop-down'
}} />
<FormField
label={i18n('DEPARTMENTS')}
name="departments"
field="autocomplete"
fieldProps={{items: this.getDepartmentsItems()}} />
<FormField
label={i18n('OWNER')}
name="owners"
field="autocomplete"
fieldProps={{items: ticketUtils.getStaffList({staffList}, 'toAutocomplete')}} />
</div>
<div className="ticket-query-filters__third-row">
<FormField
label={i18n('STATUS')}
name="closed"
field="select"
fieldProps={{
items: this.getStatusItems(),
className: 'ticket-query-filters__drop-down'
}} />
<FormField
label={i18n('TAGS')}
name="tags"
field="tag-selector"
fieldProps={{
items: this.getTags(filters.tags),
onRemoveClick: this.removeTag.bind(this),
onTagSelected: this.addTag.bind(this)
}} />
<FormField
label={i18n('AUTHORS')}
name="authors"
field="autocomplete"
fieldProps={{
getItemListFromQuery: this.searchAuthors.bind(this),
comparerFunction: this.autorsComparer.bind(this)
}} />
</div>
<div className="ticket-query-filters__container">
<Button
className="ticket-query-filters__container__button ticket-query-filters__container__clear-button"
size= "medium"
disabled={ticketQueryListState.loading}
onClick={this.clearFormValues.bind(this)}>
{ticketQueryListState.loading ? <Loading /> : i18n('CLEAR')}
</Button>
<SubmitButton
className="ticket-query-filters__container__button ticket-query-filters__container__search-button"
type="secondary"
size= "medium">
{i18n('SEARCH')}
</SubmitButton>
</div>
</Form>
<span className="separator" />
</div>
);
}
searchAuthors(query, blacklist = []) {
blacklist = blacklist.map(item => {return {isStaff: item.isStaff, id: item.id}});
return API.call({
path: '/ticket/search-authors',
data: {
query: query,
blackList: JSON.stringify(blacklist)
}
}).then(r => {
return r.data.authors.map(author => {
return {
name: author.name,
color: "gray",
id: author.id*1,
profilePic: author.profilePic,
isStaff: author.isStaff * 1,
content: author.profilePic !== undefined ? ticketUtils.renderStaffOption(author) : author.name,
contentOnSelected: author.profilePic !== undefined ? ticketUtils.renderStaffSelected(author) : author.name
}});
});
}
renderDepartmentOption(department) {
return (
<div className="ticket-query-filters__department-option" key={`department-option-${department.id}`}>
{department.private*1 ?
<Icon className="ticket-query-filters__department-option__icon" name='user-secret'/> :
null}
<span className="ticket-query-filters__department-option__name">{department.name}</span>
</div>
);
}
renderDeparmentSelected(department) {
return (
<div className="ticket-query-filters__department-selected" key={`department-selected-${department.id}`}>
{department.private*1 ?
<Icon className="ticket-query-filters__department-selected__icon" name='user-secret'/> :
null}
<span className="ticket-query-filters__department-selected__name">{department.name}</span>
</div>
);
}
addTag(tag) {
const { formState } = this.props;
this.onChangeFormState({...formState, tags: [...formState.tags, tag]});
}
autorsComparer(autorList, autorSelectedList) {
return autorList.filter(item => !_.some(autorSelectedList, {id: item.id, isStaff: item.isStaff}));
}
clearFormValues(event) {
event.preventDefault();
this.props.dispatch(SearchFiltersActions.setDefaultFormValues());
}
getDepartmentsItems() {
const { departments, } = this.props;
let departmentsList = departments.map(department => {
return {
id: JSON.parse(department.id),
name: department.name.toLowerCase(),
color: 'gray',
contentOnSelected: this.renderDeparmentSelected(department),
content: this.renderDepartmentOption(department),
}
});
return departmentsList;
}
getSelectedDepartments(selectedDepartmentsId) {
let selectedDepartments = [];
if(selectedDepartmentsId !== undefined) {
selectedDepartments = selectedDepartmentsId.map(
(departmentId) => this.getDepartmentsItems().find(_department => (_department.id === departmentId))
);
}
return selectedDepartments;
}
getSelectedStaffs(selectedStaffsId) {
const { staffList } = this.props;
let selectedStaffs = [];
if(selectedStaffsId !== undefined) {
selectedStaffs = selectedStaffsId.map(
(staffId) => ticketUtils.getStaffList({staffList}, 'toAutocomplete').find(_staff => (_staff.id === staffId))
);
}
return selectedStaffs;
}
getSelectedTagsName(selectedTagsId) {
let selectedTagsName = [];
if(selectedTagsId !== undefined) {
selectedTagsName = selectedTagsId.map(
(tagId) => (this.getTags().find(_tag => (_tag.id === tagId)) || {}).name
);
}
return selectedTagsName;
}
getStatusItems() {
let items = [
{id: 0, name: 'Any', content: i18n('ANY')},
{id: 1, name: 'Opened', content: i18n('OPENED')},
{id: 2, name: 'Closed', content: i18n('CLOSED')},
];
return items;
}
getTags() {
const { tags, } = this.props;
let newTagList = tags.map(tag => {
return {
id: JSON.parse(tag.id),
name: tag.name,
color : tag.color
}
});
return newTagList;
}
onChangeFormState(formValues) {
this.props.dispatch(SearchFiltersActions.changeForm(formValues));
}
onSubmitListConfig() {
const {
formState,
filters,
formEdited,
} = this.props;
const listConfigWithCompleteAuthorsList = searchTicketsUtils.formValueToListConfig(
{...formState, orderBy: filters.orderBy, page: 1},
true
);
if(formEdited) {
const filtersForAPI = searchTicketsUtils.getFiltersForAPI(listConfigWithCompleteAuthorsList.filters);
const currentPath = window.location.pathname;
const urlQuery = searchTicketsUtils.getFiltersForURL({
filters: filtersForAPI,
shouldRemoveCustomParam: true,
shouldRemoveUseInitialValuesParam: true
});
urlQuery && history.push(`${currentPath}${urlQuery}`);
}
}
removeTag(tag) {
const { formState } = this.props;
this.onChangeFormState({...formState, tags: formState.tags.filter(item => item !== tag)});
}
tagsNametoTagsId(selectedTagsName) {
let selectedTagsId = [];
if (selectedTagsName != undefined) {
selectedTagsId = selectedTagsName.map(
(tagName) => (this.getTags().find(_tag => (_tag.name === tagName)) || {}).id
);
}
return selectedTagsId;
}
onChangeForm(data) {
const departmentsId = data.departments.map(department => department.id);
const staffsId = data.owners.map(staff => staff.id);
const tagsName = this.tagsNametoTagsId(data.tags);
const authors = data.authors.map(({name, id, isStaff, profilePic, color}) => ({name, id: id*1, isStaff, profilePic, color}));
this.onChangeFormState({
...data,
tags: tagsName,
owners: staffsId,
departments: departmentsId,
authors: authors,
});
}
getFormValue(form) {
return {
...form,
departments: this.getSelectedDepartments(form.departments),
owners: this.getSelectedStaffs(form.owners),
tags: this.getSelectedTagsName(form.tags),
authors: this.getAuthors(form.authors),
}
}
getAuthors(authors = []) {
return authors.map(author => ({
name: author.name,
color: "gray",
id: author.id*1,
isStaff: author.isStaff*1,
profilePic: author.profilePic,
content: author.profilePic !== undefined ? ticketUtils.renderStaffOption(author) : author.name,
contentOnSelected: author.profilePic !== undefined ? ticketUtils.renderStaffSelected(author) : author.name
}));
}
}
export default connect((store) => {
return {
tags: store.config.tags,
departments: store.config.departments,
staffList: store.adminData.staffMembers,
formState: store.searchFilters.form,
filters: store.searchFilters.listConfig.filters,
showFilters: store.searchFilters.showFilters,
formEdited: store.searchFilters.formEdited,
ticketQueryListState: store.searchFilters.ticketQueryListState,
};
})(TicketQueryFilters);

View File

@ -0,0 +1,118 @@
@import '../scss/vars';
.ticket-query-filters {
opacity: 0;
max-height: 0;
overflow-y: hidden;
transition: all 0.2s ease;
&__open {
max-height: 1000px;
opacity: 1;
transition: all 0.2s ease;
}
&__container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
&__button {
margin: 0 10px;
}
}
&__department-option {
&__name {
margin-left: 10px;
}
}
&__department-selected {
display: inline-block;
border-radius: 3px;
margin-left: 5px;
padding: 1px;
font-size: 13px;
cursor: default;
&__icon {
padding-right: 5px;
}
}
&__first-row {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 0 10%;
}
&__second-row,
&__third-row {
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: flex-start;
}
&__drop-down > .drop-down__current-item {
background-color: $very-light-grey;
&:focus {
background-color: $medium-grey;
}
}
&__search-box {
width: 100%;
padding: 0 50px;
}
&__staff-option {
&__profile-pic {
width: 25px;
height: 25px;
border-radius: 50%;
}
&__name {
margin-left: 10px;
}
}
&__staff-selected {
display: inline-block;
border-radius: 3px;
margin-left: 5px;
padding: 1px;
font-size: 13px;
cursor: default;
&__profile-pic {
width: 25px;
height: 25px;
border-radius: 50%;
margin-right: 5px;
}
}
&__title {
padding-bottom: 10px;
}
}
@media screen and (max-width: 992px) {
.ticket-query-filters {
&__first-row,
&__second-row,
&__third-row {
display: flex;
flex-direction: column;
align-items: flex-start;
padding: unset;
}
}
}

View File

@ -0,0 +1,82 @@
import React from 'react';
import _ from 'lodash';
import i18n from 'lib-app/i18n';
import {connect} from 'react-redux';
import TicketList from 'app-components/ticket-list';
import Message from 'core-components/message';
import searchFiltersActions from '../actions/search-filters-actions';
import queryString from 'query-string';
import searchTicketsUtils from 'lib-app/search-tickets-utils';
import history from 'lib-app/history';
class TicketQueryList extends React.Component {
state = {
tickets: [],
page: 1,
pages: 0,
error: null,
loading: true
};
render() {
return (
this.state.error ?
<Message showCloseButton={false} type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> :
<TicketList {...this.getTicketListProps()} />
);
}
onPageChange(event) {
const {
dispatch,
filters
} = this.props;
dispatch(searchFiltersActions.changePage({
...filters,
page: event.target.value
}));
}
getTicketListProps () {
const {
filters,
onChangeOrderBy,
userId,
ticketQueryListState
} = this.props;
const page = {
...ticketQueryListState,
...queryString.parse(window.location.search)
}.page*1;
return {
userId: userId,
ticketPath: '/admin/panel/tickets/view-ticket/',
tickets: ticketQueryListState.tickets,
page: page,
pages: ticketQueryListState.pages,
loading: ticketQueryListState.loading,
type: 'secondary',
showDepartmentDropdown: false,
closedTicketsShown: false,
onPageChange:this.onPageChange.bind(this),
orderBy: filters.orderBy ? JSON.parse(filters.orderBy) : filters.orderBy,
showOrderArrows: true,
onChangeOrderBy: onChangeOrderBy,
showPageSizeDropdown: false
};
}
}
export default connect((store) => {
return {
userId: store.session.userId*1,
filters: store.searchFilters.listConfig.filters,
ticketQueryListState: store.searchFilters.ticketQueryListState
};
})(TicketQueryList);

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,6 @@
@import "../scss/vars";
.ticket-viewer {
&__header {
background-color: $primary-blue;
border-top-right-radius: 4px;
@ -9,31 +8,162 @@
color: white;
font-size: 16px;
padding: 6px 0;
display: flex;
align-items:center;
justify-content:center;
&:hover {
.ticket-viewer__edit-title-icon {
color: $grey;
}
}
}
&__buttons-column {
padding-top: 10px;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
}
&__buttons-row {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-start;
width: 250px;
position: absolute;
left: 0;
bottom: 0;
}
&__edited-title-text {
font-style: italic;
font-size: 14px;
margin: 0 10px;
}
&__edit-icon {
right: 12px;
margin: 0 10px;
color: $light-grey;
&:hover {
cursor:pointer;
}
}
&__edit-title-icon {
color: $primary-blue;
right: 12px;
margin: 0 10px;
&:hover {
cursor:pointer;
}
}
&__edit-status__buttons {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-top: 10px;
}
&___input-edit-title {
color: black;
align-items:center;
justify-content: center;
margin-bottom: 6px;
margin-right: 6px;
.input__text {
height: 30px;
text-align: center;
padding-top: 12px;
border-radius: 5px;
}
}
&__edit-title__buttons {
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
width: 160px;
}
&__edit-title__button {
width: 50px;
height: 30px;
display: flex;
justify-content: center;
}
&__number {
color: white;
margin-right: 10px;
margin-right: 30px;
font-size: 14px;
}
&__title {
display: inline-block;
max-width: 375px;
}
&__flag {
margin-left: 10px;
margin-left: 30px;
}
&__info-row-header {
&__info {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-start;
background-color: $light-grey;
font-weight: bold;
}
padding: 10px 15px 30px 15px;
&__info-row-values {
background-color: $light-grey;
color: $secondary-blue;
padding-bottom: 10px;
&-container {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
min-width: 150px;
max-width: 250px;
}
&-container-editable {
display: flex;
flex-direction: row;
justify-content: center;
align-items: flex-start;
min-width: 300px;
&:hover {
.ticket-viewer__edit-icon {
color: $primary-blue;
}
}
&__column {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
min-width: 170px;
}
}
&-header {
font-weight: bold;
}
&-value {
color: $secondary-blue;
padding-bottom: 10px;
width: 100%;
}
}
&__editable-dropdown {
@ -52,7 +182,15 @@
position: relative;
}
&__reopen-close-buttons {
width: 230px;
display: flex;
align-content: left;
justify-content: space-between;
}
&__response {
width: 100%;
margin-top: 20px;
position: relative;
@ -71,10 +209,74 @@
text-align: left;
}
&-custom {
&-actions {
background-color: $very-light-grey;
display: flex;
justify-content: space-between;
}
&-custom {
padding: 20px 0 0 20px;
text-align: left;
}
&-private {
padding: 20px 20px 0 0;
&-info {
margin-left: 5px;
}
}
&-container {
display: flex;
position: relative;
flex-direction: row-reverse;
}
&-buttons {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-end;
width: 50%;
min-width: 50%;
}
}
}
@media screen and (max-width: 1151px) {
.ticket-viewer__info {
&-container {
width: 200px;
min-width: unset;
max-width: unset;
}
&-container-editable {
min-width: 250px;
max-width: 300px;
}
}
}
@media screen and (max-width: 992px) {
.ticket-viewer__info {
flex-direction: column;
justify-content: space-between;
align-items: center;
}
}
@media screen and (max-width: 571px) {
.ticket-viewer {
&__number, &__edit-title-icon {
margin: 0 5px;
}
&__flag {
padding: 0 10px;
}
}
}
}

View File

@ -10,6 +10,7 @@ import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button';
import IconSelector from 'core-components/icon-selector';
import ColorSelector from 'core-components/color-selector';
import InfoTooltip from 'core-components/info-tooltip';
class TopicEditModal extends React.Component {
@ -24,44 +25,57 @@ class TopicEditModal extends React.Component {
};
state = {
values: this.props.defaultValues || {title: '', icon: 'address-card', color: '#ff6900'}
values: this.props.defaultValues || {title: '', icon: 'address-card', color: '#ff6900', private: false},
loading: false
};
render() {
return (
<div className="topic-edit-modal">
<Header title={i18n((this.props.addForm) ? 'ADD_TOPIC' : 'EDIT_TOPIC')} description={i18n((this.props.addForm) ? 'ADD_TOPIC_DESCRIPTION' : 'EDIT_TOPIC_DESCRIPTION')} />
<Form values={this.state.values} onChange={this.onFormChange.bind(this)} onSubmit={this.onSubmit.bind(this)}>
<Form values={this.state.values} onChange={this.onFormChange.bind(this)} onSubmit={this.onSubmit.bind(this)} loading={this.state.loading}>
<FormField name="title" label={i18n('TITLE')} fieldProps={{size: 'large'}} validation="TITLE" required />
<FormField name="icon" className="topic-edit-modal__icon" label={i18n('ICON')} decorator={IconSelector} />
<FormField name="color" className="topic-edit-modal__color" label={i18n('COLOR')} decorator={ColorSelector} />
<SubmitButton className="topic-edit-modal__save-button" type="secondary" size="small">
{i18n('SAVE')}
</SubmitButton>
<Button className="topic-edit-modal__discard-button" onClick={this.onDiscardClick.bind(this)} size="small">
{i18n('CANCEL')}
</Button>
<FormField className="topic-edit-modal__private" label={i18n('PRIVATE')} name="private" field="checkbox" />
<InfoTooltip className="topic-edit-modal__private" text={i18n('PRIVATE_TOPIC_DESCRIPTION')} />
<div className="topic-edit-modal__buttons-container">
<Button className="topic-edit-modal__discard-button" onClick={this.onDiscardClick.bind(this)} size="small">
{i18n('CANCEL')}
</Button>
<SubmitButton className="topic-edit-modal__save-button" type="secondary" size="small">
{i18n('SAVE')}
</SubmitButton>
</div>
</Form>
</div>
);
}
onSubmit() {
this.setState({
loading: true
});
API.call({
path: (this.props.addForm) ? '/article/add-topic' : '/article/edit-topic',
data: {
topicId: this.props.topicId,
name: this.state.values['title'],
icon: this.state.values['icon'],
iconColor: this.state.values['color']
iconColor: this.state.values['color'],
private: this.state.values['private']*1
}
}).then(() => {
this.context.closeModal();
if(this.props.onChange) {
this.props.onChange();
}
}).catch(() => {
this.setState({
loading: false
});
});
}
@ -77,4 +91,4 @@ class TopicEditModal extends React.Component {
}
}
export default TopicEditModal;
export default TopicEditModal;

View File

@ -13,7 +13,21 @@
}
&__discard-button {
float: right;
&__buttons-container {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 20px 0 0 0;
}
}
&__discard-button {
float: left;
}
&__private {
display: inline-block;
padding-right: 10px;
}
}

View File

@ -22,7 +22,8 @@ class TopicViewer extends React.Component {
iconColor: React.PropTypes.string.isRequired,
articles: React.PropTypes.array.isRequired,
articlePath: React.PropTypes.string,
editable: React.PropTypes.bool
editable: React.PropTypes.bool,
private: React.PropTypes.bool
};
static defaultProps = {
@ -53,6 +54,8 @@ class TopicViewer extends React.Component {
<span className="topic-viewer__title">{this.props.name}</span>
{(this.props.editable) ? this.renderEditButton() : null}
{(this.props.editable) ? this.renderDeleteButton() : null}
{this.props.private*1 ? <Icon className="topic-viewer__private" name='user-secret' color='grey'/> : null}
</div>
<ul className="topic-viewer__list">
{this.state.articles.map(this.renderArticleItem.bind(this))}
@ -121,13 +124,16 @@ class TopicViewer extends React.Component {
}
renderEditModal() {
let props = {
topicId: this.props.id,
onChange: this.props.onChange,
const {id, onChange, name, icon, iconColor} = this.props;
const props = {
topicId: id,
onChange,
defaultValues: {
title: this.props.name,
icon: this.props.icon,
iconColor: this.props.iconColor
title: name,
icon,
color: iconColor,
private: this.props.private * 1
}
};
@ -148,7 +154,7 @@ class TopicViewer extends React.Component {
<ArticleAddModal {...props}/>
);
}
getArticleLinkProps(article) {
let classes = {
'topic-viewer__list-item-button': true,
@ -162,7 +168,7 @@ class TopicViewer extends React.Component {
}
onDeleteClick() {
API.call({
return API.call({
path: '/article/delete-topic',
data: {
topicId: this.props.id
@ -265,4 +271,4 @@ class TopicViewer extends React.Component {
}
}
export default TopicViewer;
export default TopicViewer;

View File

@ -25,6 +25,10 @@
margin-left: 15px;
}
&__private {
margin-left: 10px;
}
&__edit-icon {
color: $grey;
cursor: pointer;
@ -78,4 +82,4 @@
&__add-item {
color: $dark-grey;
}
}
}

View File

@ -50,7 +50,7 @@ class App extends React.Component {
'application': true,
'application_modal-opened': (this.props.modal.opened),
'application_full-width': (this.props.config.layout === 'full-width' && !_.includes(this.props.location.pathname, '/admin')),
'application_user-system': (this.props.config['user-system-enabled'])
'application_mandatory-login': (this.props.config['mandatory-login'])
};
return classNames(classes);
@ -64,12 +64,12 @@ class App extends React.Component {
loggedInStaff: !_.includes(props.location.pathname, '/admin/panel') && props.session.logged && props.session.staff,
loggedOutStaff: _.includes(props.location.pathname, '/admin/panel') && !props.session.logged
};
if(props.config['maintenance-mode'] === '1' && !_.includes(props.location.pathname, '/admin') && !_.includes(props.location.pathname, '/maintenance')) {
if(props.config['maintenance-mode'] && !_.includes(props.location.pathname, '/admin') && !_.includes(props.location.pathname, '/maintenance')) {
history.push('/maintenance');
}
if(props.config['maintenance-mode'] === '0' && _.includes(props.location.pathname, '/maintenance')) {
if(!props.config['maintenance-mode'] && _.includes(props.location.pathname, '/maintenance')) {
history.push('/');
}
@ -99,7 +99,7 @@ class App extends React.Component {
history.push('/');
}
if(props.config['user-system-enabled'] && _.includes(props.location.pathname, '/check-ticket')) {
if(props.config['mandatory-login'] && _.includes(props.location.pathname, '/check-ticket')) {
history.push('/');
}
@ -111,7 +111,7 @@ class App extends React.Component {
history.push('/admin');
}
if(isProd && _.includes(props.location.pathname, '/components-demo')) {
if(process.env.NODE_ENV === 'production' && _.includes(props.location.pathname, '/components-demo')) {
history.push('/');
}
}
@ -139,4 +139,4 @@ export default connect((store) => {
session: store.session,
routing: store.routing
};
})(App);
})(App);

View File

@ -32,13 +32,14 @@ import AdminPanelMyAccount from 'app/admin/panel/dashboard/admin-panel-my-accoun
import AdminPanelMyTickets from 'app/admin/panel/tickets/admin-panel-my-tickets';
import AdminPanelNewTickets from 'app/admin/panel/tickets/admin-panel-new-tickets';
import AdminPanelAllTickets from 'app/admin/panel/tickets/admin-panel-all-tickets';
import AdminPanelSearchTickets from 'app/admin/panel/tickets/admin-panel-search-tickets';
import AdminPanelViewTicket from 'app/admin/panel/tickets/admin-panel-view-ticket';
import AdminPanelCustomResponses from 'app/admin/panel/tickets/admin-panel-custom-responses';
import AdminPanelListUsers from 'app/admin/panel/users/admin-panel-list-users';
import AdminPanelViewUser from 'app/admin/panel/users/admin-panel-view-user';
import AdminPanelBanUsers from 'app/admin/panel/users/admin-panel-ban-users';
import AdminPanelCustomFields from 'app/admin/panel/users/admin-panel-custom-fields';
import AdminPanelListArticles from 'app/admin/panel/articles/admin-panel-list-articles';
import AdminPanelViewArticle from 'app/admin/panel/articles/admin-panel-view-article';
@ -49,7 +50,8 @@ import AdminPanelViewStaff from 'app/admin/panel/staff/admin-panel-view-staff';
import AdminPanelSystemPreferences from 'app/admin/panel/settings/admin-panel-system-preferences';
import AdminPanelAdvancedSettings from 'app/admin/panel/settings/admin-panel-advanced-settings';
import AdminPanelEmailTemplates from 'app/admin/panel/settings/admin-panel-email-templates';
import AdminPanelEmailSettings from 'app/admin/panel/settings/admin-panel-email-settings';
import AdminPanelCustomTags from 'app/admin/panel/settings/admin-panel-custom-tags';
// INSTALLATION
import InstallLayout from 'app/install/install-layout';
@ -59,7 +61,7 @@ import InstallStep3Database from 'app/install/install-step-3-database';
import InstallStep4UserSystem from 'app/install/install-step-4-user-system';
import InstallStep5Settings from 'app/install/install-step-5-settings';
import InstallStep6Admin from 'app/install/install-step-6-admin';
import InstallStep7Completed from 'app/install/install-step-7-completed';
import InstallCompleted from 'app/install/install-completed';
export default (
<Router history={history}>
@ -96,12 +98,12 @@ export default (
<Route path="step-4" component={InstallStep4UserSystem} />
<Route path="step-5" component={InstallStep5Settings} />
<Route path="step-6" component={InstallStep6Admin} />
<Route path="step-7" component={InstallStep7Completed} />
<Route path="completed" component={InstallCompleted} />
</Route>
<Route path="admin">
<IndexRoute component={AdminLoginPage} />
<Route path="panel" component={AdminPanelLayout}>
<IndexRedirect to="stats" />
<IndexRedirect to="activity" />
<Route path="stats" component={AdminPanelStats} />
<Route path="activity" component={AdminPanelActivity} />
<Route path="my-account" component={AdminPanelMyAccount} />
@ -110,7 +112,7 @@ export default (
<IndexRedirect to="my-tickets" />
<Route path="my-tickets" component={AdminPanelMyTickets} />
<Route path="new-tickets" component={AdminPanelNewTickets} />
<Route path="all-tickets" component={AdminPanelAllTickets} />
<Route path="search-tickets" component={AdminPanelSearchTickets} />
<Route path="custom-responses" component={AdminPanelCustomResponses} />
<Route path="view-ticket/:ticketNumber" component={AdminPanelViewTicket} />
</Route>
@ -120,6 +122,7 @@ export default (
<Route path="list-users" component={AdminPanelListUsers} />
<Route path="view-user/:userId" component={AdminPanelViewUser} />
<Route path="ban-users" component={AdminPanelBanUsers} />
<Route path="custom-fields" component={AdminPanelCustomFields} />
</Route>
<Route path="articles">
@ -139,7 +142,8 @@ export default (
<IndexRedirect to="system-preferences" />
<Route path="system-preferences" component={AdminPanelSystemPreferences} />
<Route path="advanced-settings" component={AdminPanelAdvancedSettings} />
<Route path="email-templates" component={AdminPanelEmailTemplates} />
<Route path="email-settings" component={AdminPanelEmailSettings} />
<Route path="custom-tags" component={AdminPanelCustomTags} />
</Route>
</Route>
</Route>

View File

@ -1,5 +1,5 @@
export default {
dispatch: stub(),
dispatch: stub().returns(new Promise(r => r())),
getState: stub().returns({
config: {},
session: {},

View File

@ -1,59 +1,256 @@
import React from 'react';
import _ from 'lodash';
import classNames from 'classnames';
import {connect} from 'react-redux';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import SessionActions from 'actions/session-actions';
import PasswordRecovery from 'app-components/password-recovery.js';
import Button from 'core-components/button';
import Form from 'core-components/form';
import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button';
import Message from 'core-components/message';
import Widget from 'core-components/widget';
import WidgetTransition from 'core-components/widget-transition';
import Captcha from 'app/main/captcha';
const MAX_FREE_LOGIN_ATTEMPTS = 3;
class AdminLoginPage extends React.Component {
state = {
sideToShow: 'front',
loginFormErrors: {},
recoverFormErrors: {},
recoverSent: false,
loadingLogin: false,
loadingRecover: false,
showRecoverSentMessage: true,
showEmailOrPassordErrorMessage: true
};
componentDidUpdate(prevProps) {
if (!prevProps.session.failed && this.props.session.failed) {
this.setState({showEmailOrPassordErrorMessage : true});
this.refs.loginForm.refs.password.focus();
}
}
render() {
return (
<div className="admin-login-page">
<WidgetTransition sideToShow={this.state.sideToShow} className={classNames('admin-login-page__container', this.props.className)}>
{this.renderLogin()}
{this.renderPasswordRecovery()}
</WidgetTransition>
</div>
);
}
renderLogin() {
return (
<div>
<Widget className="admin-login-page__content">
<div className="admin-login-page__image"><img width="100%" src={API.getURL() + '/images/logo.png'} alt="OpenSupports Admin Panel"/></div>
<div className="admin-login-page__login-form">
<Form onSubmit={this.onSubmit.bind(this)} loading={this.props.session.pending}>
<FormField name="email" label={i18n('EMAIL')} field="input" validation="EMAIL" fieldProps={{size:'large'}} required />
<FormField name="password" label={i18n('PASSWORD')} field="input" fieldProps={{password:true, size:'large'}} />
<SubmitButton>{i18n('LOG_IN')}</SubmitButton>
<div className="admin-login-page__login-form-container">
<Form {...this.getLoginFormProps()}>
<div className="admin-login-page__login-form-container__login-form__fields">
<FormField
name="email"
label={i18n('EMAIL')}
className="admin-login-page__login-form-container__login-form__fields__email"
field="input"
validation="EMAIL"
fieldProps={{size:'large'}}
required />
<FormField
name="password"
label={i18n('PASSWORD')}
className="admin-login-page__login-form-container__login-form__fields__password"
field="input"
fieldProps={{password:true, size:'large'}} />
<FormField
name="remember"
label={i18n('REMEMBER_ME')}
className="admin-login-page__login-form-container__login-form__fields__remember"
field="checkbox" />
</div>
{this.props.session.loginAttempts > MAX_FREE_LOGIN_ATTEMPTS ? this.renderLoginCaptcha() : null}
<div className="admin-login-page__login-form-container__login-form__submit-button">
<SubmitButton>{i18n('LOG_IN')}</SubmitButton>
</div>
</Form>
</div>
{this.renderMessage()}
{this.renderRecoverStatus()}
{this.renderErrorStatus()}
<Button className="login-widget__forgot-password" type="link" onClick={this.onForgotPasswordClick.bind(this)} onMouseDown={(event) => {event.preventDefault()}}>
{i18n('FORGOT_PASSWORD')}
</Button>
</Widget>
</div>
);
}
renderMessage() {
let message = null;
if(this.props.session.failed) {
message = (
<Message className="admin-login-page__error" type="error">
{i18n('EMAIL_OR_PASSWORD')}
</Message>
);
}
return message;
renderLoginCaptcha() {
return(
<div className={`main-home-page__${this.props.sitekey ? "captcha" : "no-captcha"}`}>
<Captcha ref="captcha" />
</div>
)
}
onSubmit(formState) {
renderPasswordRecovery() {
return (
<div className="admin-login-page__recovery-form-container">
<PasswordRecovery recoverSent={this.state.recoverSent} formProps={this.getRecoverFormProps()} onBackToLoginClick={this.onBackToLoginClick.bind(this)} renderLogo={true}/>
</div>
);
}
renderRecoverStatus() {
const { showRecoverSentMessage, recoverSent } = this.state;
return (
recoverSent ?
<Message
showMessage={showRecoverSentMessage}
onCloseMessage={this.onCloseMessage.bind(this, "showRecoverSentMessage")}
className="admin-login-page__message"
type="info"
leftAligned>
{i18n('RECOVER_SENT')}
</Message> :
null
);
}
renderErrorStatus() {
return (
this.props.session.failed ?
<Message
showMessage={this.state.showEmailOrPassordErrorMessage}
onCloseMessage={this.onCloseMessage.bind(this, "showEmailOrPassordErrorMessage")}
className="admin-login-page__error"
type="error">
{i18n('EMAIL_OR_PASSWORD')}
</Message> :
null
);
}
getLoginFormProps() {
return {
loading: this.props.session.pending,
className: 'admin-login-page__login-form-container__login-form',
ref: 'loginForm',
onSubmit: this.onLoginFormSubmit.bind(this),
errors: this.getLoginFormErrors(),
onValidateErrors: this.onLoginFormErrorsValidation.bind(this)
};
}
getRecoverFormProps() {
const { loadingRecover, recoverFormErrors } = this.state;
return {
loading: loadingRecover,
className: 'admin-login-page__recovery-form-container__recovery-form',
ref: 'recoverForm',
onSubmit: this.onForgotPasswordSubmit.bind(this),
errors: recoverFormErrors,
onValidateErrors: this.onRecoverFormErrorsValidation.bind(this)
};
}
getLoginFormErrors() {
let errors = _.extend({}, this.state.loginFormErrors);
if (this.props.session.failed) {
if (this.props.session.failMessage === 'INVALID_CREDENTIALS') {
errors.password = i18n('ERROR_PASSWORD');
} else if (this.props.session.failMessage === 'UNVERIFIED_USER') {
errors.email = i18n('UNVERIFIED_EMAIL');
}
}
return errors;
}
onLoginFormSubmit(formState) {
this.props.dispatch(SessionActions.login(_.extend({}, formState, {
staff: true
})));
}
onForgotPasswordSubmit(formState) {
this.setState({
loadingRecover: true,
recoverSent: false
});
API.call({
path: '/user/send-recover-password',
data: _.extend({}, formState, {staff: true})
}).then(this.onRecoverPasswordSent.bind(this)).catch(this.onRecoverPasswordFail.bind(this));
}
onLoginFormErrorsValidation(errors) {
this.setState({
loginFormErrors: errors
});
}
onRecoverFormErrorsValidation(errors) {
this.setState({
recoverFormErrors: errors
});
}
onForgotPasswordClick() {
this.setState({
sideToShow: 'back'
});
}
onBackToLoginClick() {
this.setState({
sideToShow: 'front',
recoverSent: false
});
}
onRecoverPasswordSent() {
this.setState({
loadingRecover: false,
recoverSent: true,
showRecoverSentMessage: true
});
}
onRecoverPasswordFail() {
this.setState({
loadingRecover: false,
recoverFormErrors: {
email: i18n('EMAIL_NOT_EXIST')
}
}, function () {
this.refs.recoverForm.refs.email.focus();
}.bind(this));
}
onCloseMessage(showMessage) {
this.setState({
[showMessage]: false
});
}
}
export default connect((store) => {
return {
session: store.session
session: store.session,
sitekey: store.config.reCaptchaKey
};
})(AdminLoginPage);

View File

@ -2,6 +2,12 @@
.admin-login-page {
display: flex;
margin: 0 auto;
width: 446px;
&__container {
height: 361px;
}
&__content {
margin: 0 auto;
@ -13,12 +19,22 @@
margin-bottom: 30px;
}
&__login-form {
&__login-form-container {
margin: 0 auto;
display: inline-block;
&__login-form__fields {
padding: 10px 0;
}
}
&__error {
margin-top: 30px;
}
}
&__captcha {
margin: 10px auto 20px;
height: 78px;
width: 304px;
}
}

View File

@ -1,4 +1,7 @@
import React from 'react';
import store from 'app/store';
import ConfigActions from 'actions/config-actions';
import MainLayout from 'app/main/main-layout';
import AdminPanelStaffWidget from 'app/admin/panel/admin-panel-staff-widget';
@ -8,6 +11,10 @@ import Widget from 'core-components/widget';
class AdminPanel extends React.Component {
componentDidMount() {
store.dispatch(ConfigActions.updateData());
}
render() {
return (
<MainLayout>
@ -22,7 +29,7 @@ class AdminPanel extends React.Component {
</div>
<div className="row">
<div className="col-md-12 admin-panel-layout__content">
<Widget>
<Widget className='admin-panel-layout__content__widget'>
{this.props.children}
</Widget>
</div>
@ -33,4 +40,4 @@ class AdminPanel extends React.Component {
}
}
export default AdminPanel;
export default AdminPanel;

View File

@ -4,4 +4,10 @@
&__header {
margin-bottom: 20px;
}
@media screen and (max-width: 415px) {
.admin-panel-layout__content__widget {
padding: 20px 5px;
}
}
}

View File

@ -2,10 +2,13 @@ import React from 'react';
import _ from 'lodash';
import {connect} from 'react-redux';
import {dispatch} from 'app/store';
import i18n from 'lib-app/i18n';
import Menu from 'core-components/menu';
import queryString from 'query-string';
const INITIAL_PAGE = 1;
class AdminPanelMenu extends React.Component {
static contextTypes = {
@ -70,15 +73,32 @@ class AdminPanelMenu extends React.Component {
onGroupItemClick(index) {
const group = this.getRoutes()[this.getGroupIndex()];
const item = group.items[index];
this.context.router.push(group.items[index].path);
this.context.router.push(item.path);
item.onItemClick && item.onItemClick();
}
getGroupItemIndex() {
const { location } = this.props;
const search = window.location.search;
const filtersInURL = queryString.parse(search);
const group = this.getRoutes()[this.getGroupIndex()];
const pathname = this.props.location.pathname;
const pathname = location.pathname + location.search;
const SEARCH_TICKETS_PATH = '/admin/panel/tickets/search-tickets';
return _.findIndex(group.items, {path: pathname});
return (
_.findIndex(
group.items,
(item) => {
if(location.pathname === SEARCH_TICKETS_PATH) {
const customTicketsListNumber = queryString.parse(item.path.slice(SEARCH_TICKETS_PATH.length)).custom;
return item.path.includes(SEARCH_TICKETS_PATH) && customTicketsListNumber === filtersInURL.custom;
}
return item.path === pathname;
}
)
);
}
getGroupIndex() {
@ -90,8 +110,24 @@ class AdminPanelMenu extends React.Component {
return (groupIndex === -1) ? 0 : groupIndex;
}
getCustomlists() {
if(window.customTicketList){
return window.customTicketList.map((item, index) => {
return {
name: item.title,
path: `/admin/panel/tickets/search-tickets?custom=${index}&page=${INITIAL_PAGE}&useInitialValues=true`,
level: 1,
}
})
} else {
return [];
}
}
getRoutes() {
return this.getItemsByFilteredByLevel([
const customLists = this.getCustomlists();
return this.getItemsByFilteredByLevel(_.without([
{
groupName: i18n('DASHBOARD'),
path: '/admin/panel',
@ -99,13 +135,13 @@ class AdminPanelMenu extends React.Component {
level: 1,
items: this.getItemsByFilteredByLevel([
{
name: i18n('STATISTICS'),
path: '/admin/panel/stats',
name: i18n('LAST_ACTIVITY'),
path: '/admin/panel/activity',
level: 1
},
{
name: i18n('LAST_ACTIVITY'),
path: '/admin/panel/activity',
name: i18n('STATISTICS'),
path: '/admin/panel/stats',
level: 1
}
])
@ -127,15 +163,16 @@ class AdminPanelMenu extends React.Component {
level: 1
},
{
name: i18n('ALL_TICKETS'),
path: '/admin/panel/tickets/all-tickets',
level: 1
name: i18n('SEARCH_TICKETS'),
path: `/admin/panel/tickets/search-tickets?page=${INITIAL_PAGE}&useInitialValues=true`,
level: 1,
},
{
name: i18n('CUSTOM_RESPONSES'),
path: '/admin/panel/tickets/custom-responses',
level: 2
}
},
...customLists
])
},
{
@ -153,6 +190,11 @@ class AdminPanelMenu extends React.Component {
name: i18n('BAN_USERS'),
path: '/admin/panel/users/ban-users',
level: 1
},
{
name: i18n('CUSTOM_FIELDS'),
path: '/admin/panel/users/custom-fields',
level: 1
}
])
},
@ -170,7 +212,6 @@ class AdminPanelMenu extends React.Component {
])
},
{
groupName: i18n('STAFF'),
path: '/admin/panel/staff',
icon: 'users',
@ -206,13 +247,18 @@ class AdminPanelMenu extends React.Component {
level: 3
},
{
name: i18n('EMAIL_TEMPLATES'),
path: '/admin/panel/settings/email-templates',
name: i18n('EMAIL_SETTINGS'),
path: '/admin/panel/settings/email-settings',
level: 3
},
{
name: i18n('CUSTOM_TAGS'),
path: '/admin/panel/settings/custom-tags',
level: 3
}
])
}
]);
], null));
}
getItemsByFilteredByLevel(items) {
@ -222,6 +268,8 @@ class AdminPanelMenu extends React.Component {
export default connect((store) => {
return {
level: store.session.userLevel
level: store.session.userLevel,
config: store.config,
searchFilters: store.searchFilters,
};
})(AdminPanelMenu);
})(AdminPanelMenu);

View File

@ -18,4 +18,4 @@ class AdminPanelListArticles extends React.Component {
}
}
export default AdminPanelListArticles;
export default AdminPanelListArticles;

View File

@ -3,4 +3,11 @@
&__list {
padding: 0 50px;
}
}
@media screen and (max-width: 415px) {
.admin-panel-list-articles__list {
padding: 0;
}
}
}

View File

@ -7,6 +7,7 @@ import ArticlesActions from 'actions/articles-actions';
import SessionStore from 'lib-app/session-store';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import MentionsParser from 'lib-app/mentions-parser';
import DateTransformer from 'lib-core/date-transformer';
import AreYouSure from 'app-components/are-you-sure';
@ -17,12 +18,14 @@ import Form from 'core-components/form';
import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button';
import TextEditor from 'core-components/text-editor';
import Icon from 'core-components/icon';
class AdminPanelViewArticle extends React.Component {
static propTypes = {
topics: React.PropTypes.array,
loading: React.PropTypes.bool
loading: React.PropTypes.bool,
allowAttachments: React.PropTypes.bool
};
static defaultProps = {
@ -63,23 +66,22 @@ class AdminPanelViewArticle extends React.Component {
renderArticlePreview(article) {
return (
<div className="admin-panel-view-article__content">
<div className="admin-panel-view-article__edit-buttons">
<Button className="admin-panel-view-article__edit-button" size="medium" onClick={this.onEditClick.bind(this, article)} type="tertiary">
{i18n('EDIT')}
</Button>
<Button size="medium" onClick={this.onDeleteClick.bind(this, article)}>
{i18n('DELETE')}
</Button>
<div className="admin-panel-view-article__header-wrapper">
<Header title={article.title} />
<div className="admin-panel-view-article__header-buttons">
<span onClick={this.onEditClick.bind(this, article)}>
<Icon className="admin-panel-view-article__edit-icon" name="pencil" />
</span>
<span onClick={this.onDeleteClick.bind(this, article)} >
<Icon className="admin-panel-view-article__edit-icon" name="trash" />
</span>
</div>
</div>
<div className="admin-panel-view-article__article">
<Header title={article.title}/>
<div className="admin-panel-view-article__article-content">
<div dangerouslySetInnerHTML={{__html: article.content}}/>
</div>
<div className="admin-panel-view-article__last-edited">
{i18n('LAST_EDITED_IN', {date: DateTransformer.transformToString(article.lastEdited)})}
</div>
<div className="admin-panel-view-article__article-content ql-editor">
<div dangerouslySetInnerHTML={{__html: MentionsParser.parse(article.content)}}/>
</div>
<div className="admin-panel-view-article__last-edited">
{i18n('LAST_EDITED_IN', {date: DateTransformer.transformToString(article.lastEdited)})}
</div>
</div>
);
@ -89,13 +91,13 @@ class AdminPanelViewArticle extends React.Component {
return (
<Form values={this.state.form} onChange={(form) => this.setState({form})} onSubmit={this.onFormSubmit.bind(this)}>
<div className="admin-panel-view-article__buttons">
<SubmitButton className="admin-panel-view-article__button" type="secondary" size="medium">{i18n('SAVE')}</SubmitButton>
<Button className="admin-panel-view-article__button" size="medium" onClick={this.onFormCancel.bind(this)}>
{i18n('CANCEL')}
</Button>
<SubmitButton className="admin-panel-view-article__button" type="secondary" size="medium">{i18n('SAVE')}</SubmitButton>
</div>
<FormField name="title" label={i18n('TITLE')} />
<FormField name="content" label={i18n('CONTENT')} field="textarea" />
<FormField name="content" label={i18n('CONTENT')} field="textarea" validation="TEXT_AREA" required fieldProps={{allowImages: this.props.allowAttachments}}/>
</Form>
);
}
@ -129,11 +131,11 @@ class AdminPanelViewArticle extends React.Component {
onFormSubmit(form) {
API.call({
path: '/article/edit',
data: {
dataAsForm: true,
data: _.extend(TextEditor.getContentFormData(form.content), {
articleId: this.findArticle().id,
title: form.title,
content: form.content
}
title: form.title
})
}).then(() => {
this.props.dispatch(ArticlesActions.retrieveArticles());
this.setState({
@ -151,7 +153,7 @@ class AdminPanelViewArticle extends React.Component {
}
onArticleDeleted(article) {
API.call({
return API.call({
path: '/article/delete',
data: {
articleId: article.id
@ -162,6 +164,7 @@ class AdminPanelViewArticle extends React.Component {
export default connect((store) => {
return {
allowAttachments: store.config['allow-attachments'],
topics: store.articles.topics,
loading: store.articles.loading
};

View File

@ -1,12 +1,28 @@
.admin-panel-view-article {
&__edit-buttons {
text-align: left;
margin-bottom: 20px;
&__content {
word-break: break-word;
}
&__edit-button {
margin-right: 20px;
&__header-wrapper {
display: flex;
flex-direction: row;
height: 35px;
}
&__header-buttons {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 50px;
margin-bottom: 5px;
margin-left: 15px;
}
&__edit-icon {
color: $grey;
cursor: pointer;
}
&__last-edited {
@ -19,8 +35,8 @@
text-align: left;
margin-bottom: 20px;
}
&__button {
margin-right: 20px;
}
}
}

View File

@ -43,7 +43,7 @@ class AdminPanelActivity extends React.Component {
</div>
);
}
getMenuProps() {
return {
className: 'admin-panel-activity__menu',
@ -148,4 +148,4 @@ class AdminPanelActivity extends React.Component {
}
}
export default AdminPanelActivity;
export default AdminPanelActivity;

View File

@ -2,6 +2,10 @@
&__menu {
margin: 0 auto 20px auto;
width: 300px;
min-width: 300px;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
}

View File

@ -3,7 +3,6 @@ import {connect} from 'react-redux';
import _ from 'lodash';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import SessionActions from 'actions/session-actions';
import StaffEditor from 'app/admin/panel/staff/staff-editor';

View File

@ -1,20 +1,195 @@
import React from 'react';
import { connect } from 'react-redux';
import API from 'lib-app/api-call';
import i18n from 'lib-app/i18n';
import Stats from 'app-components/stats';
import statsUtils from 'lib-app/stats-utils';
import date from 'lib-app/date';
import Header from 'core-components/header';
import Form from 'core-components/form';
import FormField from 'core-components/form-field';
import Icon from 'core-components/icon';
import Loading from 'core-components/loading';
import SubmitButton from 'core-components/submit-button';
import Button from 'core-components/button';
class AdminPanelStats extends React.Component {
state = {
loading: true,
rawForm: {
period: 0,
departments: [],
owners: [],
tags: []
},
ticketData: {}
};
componentDidMount() {
statsUtils.retrieveStats({
rawForm: this.getFormWithDateRange(this.state.rawForm),
tags: this.props.tags
}).then(({data}) => {
this.setState({ticketData: data, loading: false});
}).catch((error) => {
if (showLogs) console.error('ERROR: ', error);
});
}
render() {
const { loading, rawForm, ticketData } = this.state;
return (
<div className="admin-panel-stats">
<Header title={i18n('STATISTICS')} description={i18n('STATISTICS_DESCRIPTION')}/>
<Stats type="general"/>
<Header title={i18n('STATISTICS')} description={i18n('STATISTICS_DESCRIPTION')} />
<Form className="admin-panel-stats__form" loading={loading} values={rawForm} onChange={this.onFormChange.bind(this)} onSubmit={this.onFormSubmit.bind(this)}>
<div className="admin-panel-stats__form__container">
<div className="admin-panel-stats__form__container__row">
<div className="admin-panel-stats__form__container__col">
<FormField name="period" label={i18n('DATE')} field="select" fieldProps={{size: 'large', items: [{content: i18n('LAST_7_DAYS')}, {content: i18n('LAST_30_DAYS')}, {content: i18n('LAST_90_DAYS')}, {content: i18n('LAST_365_DAYS')}]}} />
<FormField name="tags" label={i18n('TAGS')} field="tag-selector" fieldProps={{items: this.getTagItems()}} />
</div>
<div className="admin-panel-stats__form__container__col">
<FormField name="departments" label={i18n('DEPARTMENTS')} field="autocomplete" fieldProps={{items: this.getDepartmentsItems()}} />
<FormField name="owners" label={i18n('OWNER')} field="autocomplete" fieldProps={{items: this.getStaffItems()}} />
</div>
</div>
</div>
<div className="admin-panel-stats__container">
<Button
className="admin-panel-stats__container__button admin-panel-stats__container__clear-button"
size= "medium"
disabled={loading}
onClick={this.clearFormValues.bind(this)}>
{loading ? <Loading /> : i18n('CLEAR')}
</Button>
<SubmitButton
className="admin-panel-stats__container__button admin-panel-stats__container__apply-button"
type="secondary"
size= "medium">
{i18n('APPLY')}
</SubmitButton>
</div>
</Form>
<div className="row">
<div className="col-md-12">
<span className="separator" />
</div>
</div>
{
loading ?
<div className="admin-panel-stats__loading"><Loading backgrounded size="large" /></div> :
statsUtils.renderStatistics({showStatCards: true, showStatsByHours: true, showStatsByDays: true, ticketData})
}
</div>
);
)
}
clearFormValues(event) {
event.preventDefault();
this.setState({
rawForm: {
period: 0,
departments: [],
owners: [],
tags: []
}
});
}
getTagItems() {
return this.props.tags.map((tag) => {
return {
id: JSON.parse(tag.id),
name: tag.name,
color : tag.color
}
});
}
getStaffItems() {
const getStaffProfilePic = (staff) => {
return staff.profilePic ? API.getFileLink(staff.profilePic) : (API.getURL() + '/images/profile.png');
}
const renderStaffItem = (staff, style) => {
return (
<div className={`admin-panel-stats__staff-${style}`} key={`staff-${style}-${staff.id}`}>
<img className={`admin-panel-stats__staff-${style}__profile-pic`} src={getStaffProfilePic(staff)} />
<span className={`admin-panel-stats__staff-${style}__name`}>{staff.name}</span>
</div>
)
};
const { staffList } = this.props;
let newStaffList = staffList.map(staff => {
return {
id: JSON.parse(staff.id),
name: staff.name.toLowerCase(),
color: 'gray',
contentOnSelected: renderStaffItem(staff, 'selected'),
content: renderStaffItem(staff, 'option'),
}
});
return newStaffList;
}
getDepartmentsItems() {
const renderDepartmentItem = (department, style) => {
return (
<div className={`admin-panel-stats__department-${style}`} key={`department-${style}-${department.id}`}>
{department.private*1 ? <Icon className={`admin-panel-stats__department-${style}__icon`} name='user-secret' /> : null}
<span className={`admin-panel-stats__department-${style}__name`}>{department.name}</span>
</div>
);
};
return this.props.departments.map(department => {
return {
id: JSON.parse(department.id),
name: department.name.toLowerCase(),
color: 'gray',
contentOnSelected: renderDepartmentItem(department, 'selected'),
content: renderDepartmentItem(department, 'option'),
}
});
}
onFormChange(newFormState) {
this.setState({rawForm: newFormState});
}
onFormSubmit() {
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
}
};
}
}
export default AdminPanelStats;
export default connect((store) => {
return {
tags: store.config.tags,
departments: store.config.departments,
staffList: store.adminData.staffMembers
};
})(AdminPanelStats);

View File

@ -0,0 +1,110 @@
@import "../../../../scss/vars";
.admin-panel-stats {
text-align: left;
&__form {
&__container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
&__row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 80%;
}
&__col {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
}
}
&__container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
&__button {
margin: 0 10px;
}
}
&__loading {
min-height: 361px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: $grey;
}
&__card-list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-around;
}
&__department-option { // Duplicated from ticket-query-filters, please REMOVE
&__name {
margin-left: 10px;
}
}
&__department-selected { // Duplicated from ticket-query-filters, please REMOVE
display: inline-block;
border-radius: 3px;
margin-left: 5px;
padding: 1px;
font-size: 13px;
cursor: default;
&__icon {
padding-right: 5px;
}
}
&__staff-option { // Duplicated from ticket-query-filters, please REMOVE
&__profile-pic {
width: 25px;
height: 25px;
border-radius: 50%;
}
&__name {
margin-left: 10px;
}
}
&__staff-selected { // Duplicated from ticket-query-filters, please REMOVE
display: inline-block;
border-radius: 3px;
margin-left: 5px;
padding: 1px;
font-size: 13px;
cursor: default;
&__profile-pic {
width: 25px;
height: 25px;
border-radius: 50%;
margin-right: 5px;
}
}
}
@media screen and (max-width: 992px) {
.admin-panel-stats__form__container__row {
display: flex;
flex-direction: column;
}
}

View File

@ -4,7 +4,6 @@ import {connect} from 'react-redux';
import ConfigActions from 'actions/config-actions';
import API from 'lib-app/api-call';
import i18n from 'lib-app/i18n';
import ToggleButton from 'app-components/toggle-button';
import AreYouSure from 'app-components/are-you-sure';
import ModalContainer from 'app-components/modal-container';
@ -17,6 +16,7 @@ import Listing from 'core-components/listing';
import Form from 'core-components/form';
import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button';
import Checkbox from 'core-components/checkbox';
class AdminPanelAdvancedSettings extends React.Component {
@ -25,10 +25,11 @@ class AdminPanelAdvancedSettings extends React.Component {
messageTitle: null,
messageType: '',
messageContent: '',
keyName: '',
keyCode: '',
selectedAPIKey: -1,
APIKeys: []
APIKeys: [],
error: '',
showMessage: true,
showAPIKeyMessage: true
};
componentDidMount() {
@ -36,24 +37,33 @@ class AdminPanelAdvancedSettings extends React.Component {
}
render() {
const { config } = this.props;
const { messageType, error, selectedAPIKey, showAPIKeyMessage } = this.state;
return (
<div className="admin-panel-advanced-settings">
<Header title={i18n('ADVANCED_SETTINGS')} description={i18n('ADVANCED_SETTINGS_DESCRIPTION')}/>
{(this.state.messageType) ? this.renderMessage() : null}
<Header title={i18n('ADVANCED_SETTINGS')} description={i18n('ADVANCED_SETTINGS_DESCRIPTION')} />
{messageType ? this.renderMessage() : null}
<div className="row">
<div className="col-md-12">
<div className="col-md-6">
<div className="admin-panel-advanced-settings__user-system-enabled">
<span className="admin-panel-advanced-settings__text">
{i18n('ENABLE_USER_SYSTEM')} <InfoTooltip text={i18n('ENABLE_USER_SYSTEM_DESCRIPTION')} />
</span>
<ToggleButton className="admin-panel-advanced-settings__toggle-button" value={this.props.config['user-system-enabled']} onChange={this.onToggleButtonUserSystemChange.bind(this)}/>
</div>
<div className="col-md-6 admin-panel-advanced-settings__mandatory-login">
<Checkbox
label={i18n('ENABLE_MANDATORY_LOGIN')}
disabled={!config['registration']}
className="admin-panel-advanced-settings__mandatory-login__checkbox"
value={config['mandatory-login']}
onChange={this.onCheckboxMandatoryLoginChange.bind(this)}
wrapInLabel />
</div>
<div className="col-md-6">
<div className="admin-panel-advanced-settings__registration">
<span className="admin-panel-advanced-settings__text">{i18n('ENABLE_USER_REGISTRATION')}</span>
<ToggleButton className="admin-panel-advanced-settings__toggle-button" value={this.props.config['registration']} onChange={this.onToggleButtonRegistrationChange.bind(this)}/>
<Checkbox
label={i18n('ENABLE_USER_REGISTRATION')}
disabled={!config['mandatory-login']}
className="admin-panel-advanced-settings__registration__checkbox"
value={config['registration']}
onChange={this.onCheckboxRegistrationChange.bind(this)}
wrapInLabel />
</div>
</div>
</div>
@ -65,7 +75,7 @@ class AdminPanelAdvancedSettings extends React.Component {
<div className="admin-panel-advanced-settings__text">
{i18n('INCLUDE_USERS_VIA_CSV')} <InfoTooltip text={i18n('CSV_DESCRIPTION')} />
</div>
<FileUploader className="admin-panel-advanced-settings__button" text="Upload" onChange={this.onImportCSV.bind(this)}/>
<FileUploader className="admin-panel-advanced-settings__button" text="Upload" onChange={this.onImportCSV.bind(this)} />
</div>
<div className="col-md-4">
<div className="admin-panel-advanced-settings__text">{i18n('BACKUP_DATABASE')}</div>
@ -80,12 +90,21 @@ class AdminPanelAdvancedSettings extends React.Component {
<span className="separator" />
</div>
<div className="col-md-12 admin-panel-advanced-settings__api-keys">
<div className="col-md-12 admin-panel-advanced-settings__api-keys-title">{i18n('REGISTRATION_API_KEYS')}</div>
<div className="col-md-12 admin-panel-advanced-settings__api-keys-title">{i18n('API_KEYS')}</div>
<div className="col-md-4">
<Listing {...this.getListingProps()} />
</div>
<div className="col-md-8">
{(this.state.selectedAPIKey === -1) ? this.renderNoKey() : this.renderKey()}
<div className="col-md-8 admin-panel-advanced-settings__api-keys__container">
{
error ?
<Message
showMessage={showAPIKeyMessage}
onCloseMessage={this.onCloseMessage.bind(this, "showAPIKeyMessage")}
type="error">
{i18n(error)}
</Message> :
((selectedAPIKey === -1) ? this.renderNoKey() : this.renderKey())
}
</div>
</div>
</div>
@ -94,8 +113,17 @@ class AdminPanelAdvancedSettings extends React.Component {
}
renderMessage() {
const { messageType, messageTitle, messageContent, showMessage } = this.state;
return (
<Message type={this.state.messageType} title={this.state.messageTitle}>{this.state.messageContent}</Message>
<Message
showMessage={showMessage}
onCloseMessage={this.onCloseMessage.bind(this, "showMessage")}
className="admin-panel-advanced-settings__message"
type={messageType}
title={messageTitle}>
{messageContent}
</Message>
);
}
@ -108,14 +136,31 @@ class AdminPanelAdvancedSettings extends React.Component {
}
renderKey() {
let currentAPIKey = this.state.APIKeys[this.state.selectedAPIKey];
const { APIKeys, selectedAPIKey } = this.state;
const {
name,
token,
canCreateTickets,
shouldReturnTicketNumber,
canCheckTickets,
canCreateUser
} = APIKeys[selectedAPIKey];
return (
<div className="admin-panel-advanced-settings__api-keys-info">
<div className="admin-panel-advanced-settings__api-keys__container-info">
<div className="admin-panel-advanced-settings__api-keys-subtitle">{i18n('NAME_OF_KEY')}</div>
<div className="admin-panel-advanced-settings__api-keys-data">{currentAPIKey.name}</div>
<div className="admin-panel-advanced-settings__api-keys-data">{name}</div>
<div className="admin-panel-advanced-settings__api-keys-subtitle">{i18n('KEY')}</div>
<div className="admin-panel-advanced-settings__api-keys-data">{currentAPIKey.token}</div>
<div className="admin-panel-advanced-settings__api-keys-data">{token}</div>
<div className="admin-panel-advanced-settings__api-keys-subtitle">{i18n('PERMISSIONS')}</div>
<div className="admin-panel-advanced-settings__api-keys__permissions">
<FormField className="admin-panel-advanced-settings__api-keys__permissions__item" value={canCreateTickets*1} label={i18n('TICKET_CREATION_PERMISSION')} field='checkbox' />
<FormField value={shouldReturnTicketNumber*1} label={i18n('TICKET_NUMBER_RETURN_PERMISSION')} field='checkbox' />
</div>
<div className="admin-panel-advanced-settings__api-keys__permissions">
<FormField className="admin-panel-advanced-settings__api-keys__permissions__item" value={canCheckTickets*1} label={i18n('TICKET_CHECK_PERMISSION')} field='checkbox' />
<FormField value={canCreateUser*1} label={i18n('USER_CREATION_PERMISSION')} field='checkbox' />
</div>
<Button className="admin-panel-advanced-settings__api-keys-button" size="medium" onClick={this.onDeleteKeyClick.bind(this)}>
{i18n('DELETE')}
</Button>
@ -125,7 +170,7 @@ class AdminPanelAdvancedSettings extends React.Component {
getListingProps() {
return {
title: i18n('REGISTRATION_API_KEYS'),
title: i18n('API_KEYS'),
enableAddNew: true,
items: this.state.APIKeys.map((item) => {
return {
@ -134,7 +179,7 @@ class AdminPanelAdvancedSettings extends React.Component {
};
}),
selectedIndex: this.state.selectedAPIKey,
onChange: index => this.setState({selectedAPIKey: index}),
onChange: index => this.setState({selectedAPIKey: index, error:''}),
onAddClick: this.openAPIKeyModal.bind(this)
};
}
@ -142,18 +187,51 @@ class AdminPanelAdvancedSettings extends React.Component {
openAPIKeyModal() {
ModalContainer.openModal(
<Form className="admin-panel-advanced-settings__api-keys-modal" onSubmit={this.addAPIKey.bind(this)}>
<Header title={i18n('ADD_API_KEY')} description={i18n('ADD_API_KEY_DESCRIPTION')}/>
<FormField name="name" label={i18n('NAME_OF_KEY')} validation="DEFAULT" required fieldProps={{size: 'large'}}/>
<SubmitButton type="secondary">{i18n('SUBMIT')}</SubmitButton>
</Form>
<Header title={i18n('ADD_API_KEY')} description={i18n('ADD_API_KEY_DESCRIPTION')} />
<FormField name="name" label={i18n('NAME_OF_KEY')} validation="DEFAULT" required fieldProps={{size: 'large'}} />
<div className="admin-panel-advanced-settings__api-keys__permissions">
<FormField className = "admin-panel-advanced-settings__api-keys__permissions__item" name="createTicketPermission" label={i18n('TICKET_CREATION_PERMISSION')} field='checkbox' />
<FormField name="ticketNumberPermission" label={i18n('TICKET_NUMBER_RETURN_PERMISSION')} field='checkbox' />
</div>
<div className="admin-panel-advanced-settings__api-keys__permissions" >
<FormField className = "admin-panel-advanced-settings__api-keys__permissions__item" name="checkTicketPermission" label={i18n('TICKET_CHECK_PERMISSION')} field='checkbox' />
<FormField name="userPermission" label={i18n('USER_CREATION_PERMISSION')} field='checkbox' />
</div>
<div className="admin-panel-advanced-settings__api-keys__buttons-container">
<Button
className="admin-panel-advanced-settings__api-keys__cancel-button"
onClick={(e) => {e.preventDefault(); ModalContainer.closeModal();}}
type='link'
size="medium">
{i18n('CANCEL')}
</Button>
<SubmitButton className="admin-panel-advanced-settings__api-keys-modal__submit-button" type="secondary">{i18n('SUBMIT')}</SubmitButton>
</div>
</Form>,
{
closeButton: {
showCloseButton: true
}
}
);
}
addAPIKey({name}) {
addAPIKey({name,userPermission,createTicketPermission,checkTicketPermission,ticketNumberPermission}) {
ModalContainer.closeModal();
this.setState({
error: ''
});
API.call({
path: '/system/add-api-key',
data: {name}
data: {
name,
canCreateUsers: userPermission*1,
canCreateTickets: createTicketPermission*1,
canCheckTickets: checkTicketPermission*1,
shouldReturnTicketNumber: ticketNumberPermission*1
}
}).then(this.getAllKeys.bind(this));
}
@ -161,15 +239,20 @@ class AdminPanelAdvancedSettings extends React.Component {
API.call({
path: '/system/get-api-keys',
data: {}
}).then(this.onRetrieveSuccess.bind(this));
}).then(this.onRetrieveSuccess.bind(this))
}
onDeleteKeyClick() {
const {
APIKeys,
selectedAPIKey
} = this.state;
AreYouSure.openModal(null, () => {
API.call({
return API.call({
path: '/system/delete-api-key',
data: {
name: this.state.APIKeys[this.state.selectedAPIKey].name
name: APIKeys[selectedAPIKey].name
}
}).then(this.getAllKeys.bind(this));
});
@ -178,21 +261,24 @@ class AdminPanelAdvancedSettings extends React.Component {
onRetrieveSuccess(result) {
this.setState({
APIKeys: result.data,
selectedAPIKey: -1
selectedAPIKey: -1,
error: null
});
}
onToggleButtonUserSystemChange() {
AreYouSure.openModal(null, this.onAreYouSureUserSystemOk.bind(this), 'secure');
onCheckboxMandatoryLoginChange() {
AreYouSure.openModal(null, this.onAreYouSureMandatoryLoginOk.bind(this), 'secure');
}
onToggleButtonRegistrationChange() {
onCheckboxRegistrationChange() {
AreYouSure.openModal(null, this.onAreYouSureRegistrationOk.bind(this), 'secure');
}
onAreYouSureUserSystemOk(password) {
API.call({
path: this.props.config['user-system-enabled'] ? '/system/disable-user-system' : '/system/enable-user-system',
onAreYouSureMandatoryLoginOk(password) {
const { config, dispatch } = this.props;
return API.call({
path: config['mandatory-login'] ? '/system/disable-mandatory-login' : '/system/enable-mandatory-login',
data: {
password: password
}
@ -200,26 +286,30 @@ class AdminPanelAdvancedSettings extends React.Component {
this.setState({
messageType: 'success',
messageTitle: null,
messageContent: this.props.config['user-system-enabled'] ? i18n('USER_SYSTEM_DISABLED') : i18n('USER_SYSTEM_ENABLED')
showMessage: true,
messageContent: config['mandatory-login'] ? i18n('MANDATORY_LOGIN_DISABLED') : i18n('MANDATORY_LOGIN_ENABLED')
});
this.props.dispatch(ConfigActions.updateData());
}).catch(() => this.setState({messageType: 'error', messageTitle: null, messageContent: i18n('ERROR_UPDATING_SETTINGS')}));
dispatch(ConfigActions.updateData());
}).catch(() => this.setState({messageType: 'error', showMessage: true, messageTitle: null, messageContent: i18n('ERROR_UPDATING_SETTINGS')}));
}
onAreYouSureRegistrationOk(password) {
API.call({
path: this.props.config['registration'] ? '/system/disable-registration' : '/system/enable-registration',
const { config, dispatch } = this.props;
return API.call({
path: config['registration'] ? '/system/disable-registration' : '/system/enable-registration',
data: {
password: password
}
}).then(() => {
this.setState({
messageType: 'success',
showMessage: true,
messageTitle: null,
messageContent: this.props.config['registration'] ? i18n('REGISTRATION_DISABLED') : i18n('REGISTRATION_ENABLED')
messageContent: config['registration'] ? i18n('REGISTRATION_DISABLED') : i18n('REGISTRATION_ENABLED')
});
this.props.dispatch(ConfigActions.updateData());
}).catch(() => this.setState({messageType: 'error', messageTitle: null, messageContent: i18n('ERROR_UPDATING_SETTINGS')}));
dispatch(ConfigActions.updateData());
}).catch(() => this.setState({messageType: 'error', showMessage: true, messageTitle: null, messageContent: i18n('ERROR_UPDATING_SETTINGS')}));
}
onImportCSV(event) {
@ -227,27 +317,35 @@ class AdminPanelAdvancedSettings extends React.Component {
}
onAreYouSureCSVOk(file, password) {
API.call({
return API.call({
path: '/system/csv-import',
dataAsForm: true,
data: {
file: file,
password: password
file,
password
}
})
.then((result) => this.setState({
messageType: 'success',
messageType: 'success',
showMessage: true,
messageTitle: i18n('SUCCESS_IMPORTING_CSV_DESCRIPTION'),
messageContent: (result.data.length) ? (
<div>
{i18n('ERRORS_FOUND')}
<ul>
{result.data.map((error) => <li>{error}</li>)}
{result.data.map((error, index) => <li key={`csv-file__key-${index}`} >{error}</li>)}
</ul>
</div>
) : null
}))
.catch(() => this.setState({messageType: 'error', messageTitle: null, messageContent: i18n('INVALID_FILE')}));
.catch((error) => {
this.setState({
messageType: 'error',
showMessage: true,
messageTitle: null,
messageContent: i18n(error.message)
})
});
}
onBackupDatabase() {
@ -270,17 +368,22 @@ class AdminPanelAdvancedSettings extends React.Component {
}
onAreYouSureDeleteAllUsersOk(password) {
API.call({
return API.call({
path: '/system/delete-all-users',
data: {
password: password
}
}).then(() => this.setState({messageType: 'success', messageTitle: null, messageContent: i18n('SUCCESS_DELETING_ALL_USERS')}
)).catch(() => this.setState({messageType: 'error', messageTitle: null, messageContent: i18n('ERROR_DELETING_ALL_USERS')}));
}).then(() => this.setState({messageType: 'success', showMessage: true, messageTitle: null, messageContent: i18n('SUCCESS_DELETING_ALL_USERS')}
)).catch(() => this.setState({messageType: 'error', showMessage: true, messageTitle: null, messageContent: i18n('ERROR_DELETING_ALL_USERS')}));
}
onCloseMessage(showMessage) {
this.setState({
[showMessage]: false
});
}
}
export default connect((store) => {
return {
config: store.config

View File

@ -1,7 +1,7 @@
@import "../../../../scss/vars";
.admin-panel-advanced-settings {
&__user-system-enabled {
&__mandatory-login {
}
@ -9,13 +9,6 @@
}
&__toggle-button {
display: inline-block;
margin-left: 20px;
margin-top: 20px;
margin-bottom: 20px;
}
&__text {
margin-top: 30px;
margin-bottom: 20px;
@ -28,13 +21,20 @@
&__api-keys {
&__buttons-container {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-end;
}
&-title {
font-size: $font-size--bg;
margin-bottom: 20px;
text-align: left;
}
&-info {
&__container-info {
text-align: left;
}
@ -52,8 +52,21 @@
padding: 5px 0;
}
&__permissions {
display: flex;
justify-content: flex-start;
margin-bottom: 20px;
&__item {
margin-right: 25px;
}
}
&-modal {
min-width: 500px;
min-width: 400px;
&__submit-button {
margin-top: 20px;
}
}
&-none {
@ -61,4 +74,36 @@
font-size: $font-size--md;
}
}
&__message {
margin-bottom: 20px;
}
@media screen and (max-width: 415px) {
.admin-panel-advanced-settings {
&__api-keys {
&-button {
margin-bottom: 30px;
width: 150px;
}
&__container {
padding: 30px 0;
&-info {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
}
&-subtitle {
margin: 10px 0 0 0;
}
}
}
}
}

View File

@ -0,0 +1,156 @@
import React from 'react';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import Button from 'core-components/button';
import Header from 'core-components/header';
import Form from 'core-components/form';
import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button';
import ColorSelector from 'core-components/color-selector';
class AdminPanelCustomTagsModal extends React.Component {
static contextTypes = {
closeModal: React.PropTypes.func,
createTag: React.PropTypes.bool
};
static propTypes = {
defaultValues: React.PropTypes.object,
onTagCreated: React.PropTypes.func
};
state = {
form: this.props.defaultValues || {name: '', color: '#ff6900'},
loading: false
};
render() {
return (
this.renderTagContentPopUp(this.props.createTag)
);
}
renderTagContentPopUp(create) {
const {
form,
errors,
loading,
} = this.state;
let title, description, nameRequired, submitFunction;
if(create) {
title = i18n('ADD_CUSTOM_TAG');
description = i18n('DESCRIPTION_ADD_CUSTOM_TAG');
submitFunction = this.onSubmitNewTag.bind(this);
nameRequired = true;
} else {
title = i18n('EDIT_CUSTOM_TAG');
description = i18n('DESCRIPTION_EDIT_CUSTOM_TAG');
nameRequired = false;
submitFunction = this.onSubmitEditTag.bind(this);
}
return (
<div className='admin-panel-custom-tags-modal'>
<Header title={title} description={description} />
<Form
values={form}
onChange={this.onFormChange.bind(this)}
onSubmit={submitFunction}
errors={errors}
onValidateErrors={errors => this.setState({errors})}
loading={loading}>
<FormField name="name" label={i18n('NAME')} fieldProps={{size: 'large'}} required={nameRequired} />
<FormField name="color" label={i18n('COLOR')} decorator={ColorSelector} />
<div className='admin-panel-custom-tags-modal__actions'>
<SubmitButton type="secondary" size="small">
{i18n('SAVE')}
</SubmitButton>
<Button onClick={this.onDiscardClick.bind(this)} size="small">
{i18n('CANCEL')}
</Button>
</div>
</Form>
</div>
);
}
onFormChange(form) {
this.setState({
form
});
}
onSubmitEditTag(form) {
this.setState({
loading: true
});
API.call({
path: '/ticket/edit-tag',
data: {
tagId: this.props.id,
name: form.name,
color: form.color,
}
}).then(() => {
this.context.closeModal();
if(this.props.onTagChange) {
this.props.onTagChange();
}
}).catch((result) => {
this.setState({
loading: false,
errors: {
'name': result.message
}
});
});
}
onSubmitNewTag(form) {
this.setState({
loading: true
});
API.call({
path: '/ticket/create-tag',
data: {
name: form.name,
color: form.color,
}
}).then(() => {
this.context.closeModal();
if(this.props.onTagCreated) {
this.props.onTagCreated();
}
}).catch((result) => {
this.setState({
loading: false,
errors: {
'name': result.message
}
});
});
}
onDiscardClick(event) {
event.preventDefault();
this.context.closeModal();
this.setState({
loading: false,
errors: {}
});
}
}
export default AdminPanelCustomTagsModal;

View File

@ -0,0 +1,8 @@
.admin-panel-custom-tags-modal {
&__actions{
display: flex;
flex-direction: row-reverse;
justify-content: space-between;
}
}

Some files were not shown because too many files have changed in this diff Show More