diff --git a/.leu b/.leu new file mode 100644 index 00000000..8b958407 --- /dev/null +++ b/.leu @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/DatabaseMigrations/2.3.sql b/DatabaseMigrations/2.3.sql new file mode 100644 index 00000000..d18b4486 --- /dev/null +++ b/DatabaseMigrations/2.3.sql @@ -0,0 +1,56 @@ +DELETE FROM TRA_ACTIVITY WHERE NAME = 'Low'; +DELETE FROM TRA_ACTIVITY WHERE NAME = 'Medium'; +DELETE FROM TRA_ACTIVITY WHERE NAME = 'High'; + +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Walking', 'Walking Low Intensity', 1, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Gardening', 'Gardening Low Intensity', 1, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Yoga', 'Yoga Low Intensity', 1, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Kayaking', 'Kayaking Low Intensity', 1, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Pilates', 'Pilates Low Intensity', 1, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Swimming', 'Swimming Low Intensity', 1, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Other', 'Other Low Intensity', 1, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Brisk Walking', 'Brisk Walking Medium Intensity', 2, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Water Aerobics', 'Water Aerobics Medium Intensity', 2, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Jogging', 'Jogging Medium Intensity', 2, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Hiking', 'Hiking Medium Intensity', 2, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Cycling', 'Cycling Medium Intensity', 2, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Dancing', 'Dancing Medium Intensity', 2, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Other', 'Other Medium Intensity', 2, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Running', 'Running High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Rowing', 'Rowing High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Hot Yoga', 'Hot Yoga High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Soccer', 'Soccer High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Aerobics', 'Aerobics High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Squash', 'Squash High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Strength Training', 'Strength Training High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Basketball', 'Basketball High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('CrossFit', 'CrossFit High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Volleyball', 'Volleyball High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) +VALUES ('Other', 'Other High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) diff --git a/TransAction.API/Controllers/ActivityController.cs b/TransAction.API/Controllers/ActivityController.cs index 9acad284..3d334ceb 100644 --- a/TransAction.API/Controllers/ActivityController.cs +++ b/TransAction.API/Controllers/ActivityController.cs @@ -9,6 +9,7 @@ using TransAction.Data.Repositories.Interfaces; using AutoMapper; using TransAction.API.Responses; +using TransAction.API.Authorization; namespace TransAction.API.Controllers { @@ -21,7 +22,7 @@ public ActivityController(IHttpContextAccessor httpContextAccessor, ILogger>(activities); @@ -54,6 +55,7 @@ public IActionResult GetActivityById(int id) } + [ClaimRequirement(AuthorizationTypes.ADMIN_CLAIM)] [HttpPost()] public IActionResult CreateActivity([FromBody] ActivityCreateDto createActivity) { @@ -65,28 +67,27 @@ public IActionResult CreateActivity([FromBody] ActivityCreateDto createActivity) { return BadRequest(new TransActionResponse(ModelState)); } - if (_unitOfWork.Activity.ActivityExists(createActivity.Name)) + if (_unitOfWork.Activity.Exists(createActivity.Name, createActivity.Intensity)) { return BadRequest(new TransActionResponse("Activity already exists.")); } var newActivity = _mapper.Map(createActivity); - _unitOfWork.Activity.Create(newActivity); if (!_unitOfWork.Save()) { return StatusCode(500, new TransActionResponse("A problem happened while handling your request.")); - // return StatusCode(500, "A problem happened while handling your request."); } var createActivityResult = _mapper.Map(newActivity); - return CreatedAtRoute("GetActivity", new { id = createActivityResult.ActivityId }, new TransActionResponse(createActivity)); + return CreatedAtRoute("GetActivity", new { id = createActivityResult.ActivityId }, new TransActionResponse(createActivityResult)); } + [ClaimRequirement(AuthorizationTypes.ADMIN_CLAIM)] [HttpPut("{id}")] public IActionResult UpdateActivity(int id, [FromBody] ActivityUpdateDto updateActivity) { @@ -94,12 +95,6 @@ public IActionResult UpdateActivity(int id, [FromBody] ActivityUpdateDto updateA { return BadRequest(new TransActionResponse(ModelState)); } - string userGuid = UserHelper.GetUserGuid(_httpContextAccessor); - var getUser = _unitOfWork.User.GetByGuid(userGuid); - if (getUser.TeamId == null) - { - return BadRequest(new TransActionResponse("User is not in a team.")); - } var activityEntity = _unitOfWork.Activity.GetById(id); if (activityEntity == null) return StatusCode(404, new TransActionResponse("Activity does not exist")); @@ -107,13 +102,37 @@ public IActionResult UpdateActivity(int id, [FromBody] ActivityUpdateDto updateA _mapper.Map(updateActivity, activityEntity); _unitOfWork.Activity.Update(activityEntity); - if (!!_unitOfWork.Save()) + if (!_unitOfWork.Save()) { return StatusCode(500, new TransActionResponse("A problem happened while handling your request.")); } - return StatusCode(StatusCodes.Status204NoContent, new TransActionResponse()); + return Ok(_mapper.Map(activityEntity)); } + [ClaimRequirement(AuthorizationTypes.ADMIN_CLAIM)] + [HttpDelete("{id}")] + public IActionResult DeleteActivity(int id) + { + if (!ModelState.IsValid) + { + return BadRequest(new TransActionResponse(ModelState)); + } + + var activityEntity = _unitOfWork.Activity.GetById(id); + if (activityEntity == null) return StatusCode(404, new TransActionResponse("Activity does not exist")); + + if (_unitOfWork.UserActivity.CountByActivityType(activityEntity.ActivityId) > 0) + return StatusCode(400, new TransActionResponse("Cannot delete activity type. Activity type is in use.")); + + _unitOfWork.Activity.Delete(activityEntity); + + if (!_unitOfWork.Save()) + { + return StatusCode(500, new TransActionResponse("A problem happened while handling your request.")); + } + + return StatusCode(StatusCodes.Status204NoContent, new TransActionResponse()); + } } } \ No newline at end of file diff --git a/TransAction.API/Controllers/AdminController.cs b/TransAction.API/Controllers/AdminController.cs index c081d71d..358084b8 100644 --- a/TransAction.API/Controllers/AdminController.cs +++ b/TransAction.API/Controllers/AdminController.cs @@ -2,6 +2,8 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; +using System.Collections.Generic; +using System.Linq; using TransAction.API.Authorization; using TransAction.API.Responses; using TransAction.Data.Models; @@ -19,10 +21,10 @@ public AdminController(IHttpContextAccessor httpContextAccessor, ILogger x.Name.ToLower() == "admin").FirstOrDefault(); + var adminUsers = _unitOfWork.User.GetAdmins(adminRole.RoleId); + + if (role != adminRole && adminUsers.Count() == 1 && user == adminUsers.FirstOrDefault()) + { + return StatusCode(400, new TransActionResponse("Cannot change the only admin's role")); + } + _unitOfWork.User.Update(user); if (!_unitOfWork.Save()) return StatusCode(500, new TransActionResponse("Error occurred while updating user role")); return CreatedAtRoute("GetUser", new { controller = "user", id = user.UserId }, _mapper.Map(user)); } + + [ClaimRequirement(AuthorizationTypes.ADMIN_CLAIM)] + [HttpGet("users")] + public IActionResult GetAdminUsers() + { + var adminRole = _unitOfWork.Role.GetAllRoles().Where(x => x.Name.ToLower() == "admin").FirstOrDefault(); + if (adminRole == null) + return StatusCode(404, new TransActionResponse("Role not found")); + + var users = _unitOfWork.User.GetAdmins(adminRole.RoleId); + + return Ok(new TransActionResponse(_mapper.Map>(users))); + } } } \ No newline at end of file diff --git a/TransAction.API/Controllers/EventController.cs b/TransAction.API/Controllers/EventController.cs index 7af987b5..b353dea0 100644 --- a/TransAction.API/Controllers/EventController.cs +++ b/TransAction.API/Controllers/EventController.cs @@ -21,11 +21,11 @@ public EventController(IHttpContextAccessor httpContextAccessor, ILogger>(events); - int count = _unitOfWork.Event.Count(name); + int count = _unitOfWork.Event.Count(name, isActive); return StatusCode(200, new TransActionPagedResponse(getEvents, page, pageSize, count)); } diff --git a/TransAction.API/Controllers/MessageBoardController.cs b/TransAction.API/Controllers/MessageBoardController.cs index 0b6a72b8..9f631ac7 100644 --- a/TransAction.API/Controllers/MessageBoardController.cs +++ b/TransAction.API/Controllers/MessageBoardController.cs @@ -64,6 +64,7 @@ public IActionResult CreateTopic([FromBody] TopicCreateDto createTopic) string userGuid = UserHelper.GetUserGuid(_httpContextAccessor); var getUser = _unitOfWork.User.GetByGuid(userGuid); newTopic.UserId = getUser.UserId; + newTopic.LastMessageTimestamp = DateTime.Now; _unitOfWork.Topic.Create(newTopic); if (!_unitOfWork.Save()) @@ -192,6 +193,7 @@ public IActionResult CreateMessage([FromBody] MessageCreateDto createMessage) var topic = _unitOfWork.Topic.GetTopicById(newMessage.TopicId); topic.DbLastUpdateTimestamp = DateTime.Now; + topic.LastMessageTimestamp = DateTime.Now; if (!_unitOfWork.Save()) { diff --git a/TransAction.API/Controllers/RoleController.cs b/TransAction.API/Controllers/RoleController.cs index 1c4a4d38..75d9c300 100644 --- a/TransAction.API/Controllers/RoleController.cs +++ b/TransAction.API/Controllers/RoleController.cs @@ -21,11 +21,11 @@ public RoleController(IHttpContextAccessor httpContextAccessor, ILogger>(roles); - return StatusCode(200, new TransActionPagedResponse(getRoles, page, pageSize, _unitOfWork.Role.Count())); + return StatusCode(200, new TransActionResponse(getRoles)); } diff --git a/TransAction.API/Controllers/TeamRequestController.cs b/TransAction.API/Controllers/TeamRequestController.cs index 64ed3d54..94190ebf 100644 --- a/TransAction.API/Controllers/TeamRequestController.cs +++ b/TransAction.API/Controllers/TeamRequestController.cs @@ -8,6 +8,7 @@ using TransAction.Data.Repositories.Interfaces; using AutoMapper; using TransAction.API.Responses; +using TransAction.API.Helpers; namespace TransAction.API.Controllers { @@ -20,15 +21,17 @@ public TeamRequestController(IHttpContextAccessor httpContextAccessor, ILogger>(request); - return StatusCode(200, new TransActionPagedResponse(getRequest, page, pageSize, _unitOfWork.Request.Count())); + return StatusCode(200, new TransActionResponse(getRequest)); } [HttpGet("{id}", Name = "GetMemberReq")] - public IActionResult GetUserActivityById(int id) + public IActionResult GetmemberRequestById(int id) { try { @@ -40,8 +43,8 @@ public IActionResult GetUserActivityById(int id) } var getRequest = _unitOfWork.Request.GetReqById(id); var getUserResult = _mapper.Map(getRequest); - return StatusCode(200, new TransActionResponse(getUserResult)); + return StatusCode(200, new TransActionResponse(getUserResult)); } catch (Exception) @@ -111,8 +114,8 @@ public IActionResult UpdateRequest(int id, [FromBody] MemberReqUpdateDto updateR [HttpGet("team/{teamId}")] public IActionResult CurrentTeamRequests(int teamId) { - var result = _unitOfWork.Request.CurrentTeamReq(teamId); - return StatusCode(200, new TransActionResponse(result)); + var result = _unitOfWork.Request.GetByTeamId(teamId); + return StatusCode(200, new TransActionResponse(_mapper.Map>(result))); } } } \ No newline at end of file diff --git a/TransAction.API/Controllers/UserActivityController.cs b/TransAction.API/Controllers/UserActivityController.cs index d42c9ee9..dacb0ad9 100644 --- a/TransAction.API/Controllers/UserActivityController.cs +++ b/TransAction.API/Controllers/UserActivityController.cs @@ -8,10 +8,12 @@ using TransAction.Data.Repositories.Interfaces; using AutoMapper; using TransAction.API.Responses; +using TransAction.API.Authorization; +using TransAction.API.Helpers; namespace TransAction.API.Controllers { - [Route("api/useractivity")] + [Route("api/useractivities")] public class UserActivityController : BaseController { @@ -19,21 +21,36 @@ public UserActivityController(IHttpContextAccessor httpContextAccessor, ILogger< base(httpContextAccessor, logger, unitOfWork, mapper) { } + [ClaimRequirement(AuthorizationTypes.ADMIN_CLAIM)] [HttpGet()] public IActionResult GetUserActivity(int page = 1, int pageSize = 25) { - var userActivity = _unitOfWork.UserAct.GetAllUserActivities(page, pageSize); + var userActivity = _unitOfWork.UserActivity.GetAll(page, pageSize); var getUserActivities = _mapper.Map>(userActivity); - return StatusCode(200, new TransActionPagedResponse(getUserActivities, page, pageSize, _unitOfWork.UserAct.Count())); + return StatusCode(200, new TransActionPagedResponse(getUserActivities, page, pageSize, _unitOfWork.UserActivity.Count())); } + [HttpGet("event/{eventId}/user/{userId}")] + public IActionResult GetUserActivityByEventUser(int eventId, int userId) + { + string userGuid = UserHelper.GetUserGuid(_httpContextAccessor); + var user = _unitOfWork.User.GetByGuid(userGuid); + if (user.Role.Name.ToLower() != "admin" || user.UserId != userId) + { + return BadRequest(new TransActionResponse("Unauthorized user")); + } + + var userActivities = _unitOfWork.UserActivity.GetAllByEventUser(eventId, userId); + return StatusCode(200, new TransActionResponse(_mapper.Map>(userActivities))); + } + [HttpGet("{id}", Name = "GetThatUserActivity")] public IActionResult GetUserActivityById(int id) { try { - var getUserActivity = _unitOfWork.UserAct.GetUserActivity(id); + var getUserActivity = _unitOfWork.UserActivity.GetUserActivity(id); if (getUserActivity == null) { @@ -68,9 +85,17 @@ public IActionResult CreateUserActivity([FromBody] UserActivityCreateDto createU return BadRequest(new TransActionResponse(ModelState)); } + var eventEntity = _unitOfWork.Event.GetById(createUserActivity.EventId); + + if (eventEntity == null) + return NotFound(new TransActionResponse("Event not found")); + + if (createUserActivity.ActivityTimestamp < eventEntity.StartDate || createUserActivity.ActivityTimestamp > eventEntity.EndDate) + return BadRequest(new TransActionResponse("Activity time is outside of event start and end date")); + var newUserActivity = _mapper.Map(createUserActivity); - _unitOfWork.UserAct.Create(newUserActivity); + _unitOfWork.UserActivity.Create(newUserActivity); if (!_unitOfWork.Save()) { @@ -89,7 +114,7 @@ public IActionResult UpdateUserActivity(int id, [FromBody] UserActivityUpdateDto return BadRequest(new TransActionResponse(ModelState)); } - var userActivityEntity = _unitOfWork.UserAct.GetUserActivity(id); + var userActivityEntity = _unitOfWork.UserActivity.GetUserActivity(id); if (userActivityEntity == null) return StatusCode(404, new TransActionResponse("User Activity Not Found")); _mapper.Map(updateUserActivity, userActivityEntity); @@ -103,10 +128,10 @@ public IActionResult UpdateUserActivity(int id, [FromBody] UserActivityUpdateDto } - [HttpGet("event/{eventId}")] + [HttpGet("score/event/{eventId}")] public IActionResult EventSpecificScore(int eventId) { - var score = _unitOfWork.UserAct.EventSpecificScore(eventId); + var score = _unitOfWork.UserActivity.EventSpecificScore(eventId); var result = new EventSpecificScoreDto { @@ -117,63 +142,67 @@ public IActionResult EventSpecificScore(int eventId) } - [HttpGet("user/{userId}/event/{eventId}")] + [HttpGet("score/user/{userId}/event/{eventId}")] public IActionResult UserSpecificScore(int userId, int eventId) { - var score = _unitOfWork.UserAct.UserSpecificScore(userId, eventId); + var eventEntity = _unitOfWork.Event.GetById(eventId); + var score = _unitOfWork.UserActivity.UserSpecificScore(userId, eventId); var result = new UserScoreDto { EventId = eventId, UserId = userId, - Score = score + Score = score, + EventName = eventEntity.Name }; return StatusCode(200, new TransActionResponse(result)); } - [HttpGet("team/{teamId}/event/{eventId}")] + [HttpGet("score/team/{teamId}/event/{eventId}")] public IActionResult TeamEventSpecificScore(int teamId, int eventId) { + var eventEntity = _unitOfWork.Event.GetById(eventId); var users = _unitOfWork.User.GetByTeamId(teamId); - var score = _unitOfWork.UserAct.TeamEventSpecificScore(users, teamId, eventId); + var score = _unitOfWork.UserActivity.TeamEventSpecificScore(users, teamId, eventId); var result = new TeamSpecificScoreDto { EventId = eventId, TeamId = teamId, - Score = score + Score = score, + EventName = eventEntity.Name }; return StatusCode(200, new TransActionResponse(result)); } - [HttpGet("team/{teamId}")] + [HttpGet("score/team/{teamId}")] public IActionResult TeamSpecificScore(int teamId) { var users = _unitOfWork.User.GetByTeamId(teamId); - var score = _unitOfWork.UserAct.TeamSpecificScore(users, teamId); + var score = _unitOfWork.UserActivity.TeamSpecificScore(users, teamId); return StatusCode(200, new TransActionResponse(score)); } - [HttpGet("user/{userId}")] + [HttpGet("score/user/{userId}")] public IActionResult CurrentUserScore(int userId) { - var result = _unitOfWork.UserAct.CurrentUserScore(userId); + var result = _unitOfWork.UserActivity.CurrentUserScore(userId); return StatusCode(200, new TransActionResponse(result)); } - [HttpGet("event/{eventId}/top/{number}")] + [HttpGet("score/event/{eventId}/top/{number}")] public IActionResult TopTeams(int number, int eventId) { - var result = _unitOfWork.UserAct.TopTeams(number, eventId); + var result = _unitOfWork.UserActivity.TopTeams(number, eventId); return StatusCode(200, new TransActionResponse(result)); } - [HttpGet("event/{eventId}/region")] + [HttpGet("score/event/{eventId}/region")] public IActionResult RegionScore(int eventId) { - var result = _unitOfWork.UserAct.RegionalScore(eventId); + var result = _unitOfWork.UserActivity.RegionalScore(eventId); return StatusCode(200, new TransActionResponse(result)); } diff --git a/TransAction.API/Helpers/TrimmingConverter.cs b/TransAction.API/Helpers/TrimmingConverter.cs new file mode 100644 index 00000000..b6723d73 --- /dev/null +++ b/TransAction.API/Helpers/TrimmingConverter.cs @@ -0,0 +1,31 @@ +using Newtonsoft.Json; +using System; + +namespace TransAction.API.Helpers +{ + public class TrimmingConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(string); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.String) + if (reader.Value != null) + return (reader.Value as string).Trim(); + + return reader.Value; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var text = (string)value; + if (text == null) + writer.WriteNull(); + else + writer.WriteValue(text.Trim()); + } + } +} diff --git a/TransAction.API/Helpers/UserHelper.cs b/TransAction.API/Helpers/UserHelper.cs index 2a030c2e..4c65fd1a 100644 --- a/TransAction.API/Helpers/UserHelper.cs +++ b/TransAction.API/Helpers/UserHelper.cs @@ -9,16 +9,16 @@ namespace TransAction.API.Helpers { public class UserHelper { - - + + public static string GetUserGuid(IHttpContextAccessor httpContextAccessor) { - String userGuid = httpContextAccessor.HttpContext.User.FindFirst("idir_guid").Value; - + String userGuid = httpContextAccessor.HttpContext.User.FindFirst("idir_guid").Value; + return userGuid; } - - + + } } diff --git a/TransAction.API/Responses/TransActionPagedResponse.cs b/TransAction.API/Responses/TransActionPagedResponse.cs index b3526e1f..e0c43447 100644 --- a/TransAction.API/Responses/TransActionPagedResponse.cs +++ b/TransAction.API/Responses/TransActionPagedResponse.cs @@ -3,22 +3,21 @@ using System.Linq; using System.Threading.Tasks; + namespace TransAction.API.Responses { public class TransActionPagedResponse : TransActionResponse { public TransActionPagedResponse(object responseData, int page, int pageSize, int itemCount) : base(responseData) { - this.Page = page; - this.PageSize = pageSize; - this.ItemCount = itemCount; - if (itemCount < pageSize || pageSize <= 0) - { - PageCount = 1; - } - else + Page = page; + PageSize = pageSize; + ItemCount = itemCount; + PageCount = 1; + + if (itemCount > pageSize) { - PageCount = (int)((itemCount / pageSize) + 1); + PageCount = (int)Math.Ceiling((double)itemCount / (double)pageSize); } } diff --git a/TransAction.API/Startup.cs b/TransAction.API/Startup.cs index 18b14e9a..c431ad4e 100644 --- a/TransAction.API/Startup.cs +++ b/TransAction.API/Startup.cs @@ -12,6 +12,7 @@ using Serilog; using TransAction.API.Authentication; using TransAction.API.Extensions; +using TransAction.API.Helpers; using TransAction.Data.Models; using TransAction.Data.Repositories; using TransAction.Data.Repositories.Interfaces; @@ -62,7 +63,6 @@ public void ConfigureServices(IServiceCollection services) builder => { builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod(); - }); }); @@ -70,7 +70,8 @@ public void ConfigureServices(IServiceCollection services) { options.Filters.Add(new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build())); }) - .SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) + .AddJsonOptions(a => a.SerializerSettings.Converters.Add(new TrimmingConverter())); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/TransAction.Data/Models/TeamSpecificScoreDto.cs b/TransAction.Data/Models/TeamSpecificScoreDto.cs index 0413e4a6..1ee8ecbb 100644 --- a/TransAction.Data/Models/TeamSpecificScoreDto.cs +++ b/TransAction.Data/Models/TeamSpecificScoreDto.cs @@ -9,5 +9,6 @@ public class TeamSpecificScoreDto public int Score { get; set; } public int EventId { get; set; } public int TeamId { get; set; } + public string EventName { get; set; } } } diff --git a/TransAction.Data/Models/TopicDto.cs b/TransAction.Data/Models/TopicDto.cs index 7f8b75eb..cfd42868 100644 --- a/TransAction.Data/Models/TopicDto.cs +++ b/TransAction.Data/Models/TopicDto.cs @@ -11,6 +11,7 @@ public class TopicDto public int TopicId { get; set; } public string Title { get; set; } public int UserId { get; set; } + public DateTime LastMessageTimestamp { get; set; } public DateTime DbCreateTimestamp { get; set; } public DateTime DbLastUpdateTimestamp { get; set; } public long ConcurrencyControlNumber { get; set; } diff --git a/TransAction.Data/Models/TraTopic.cs b/TransAction.Data/Models/TraTopic.cs index 22be3370..67fbda95 100644 --- a/TransAction.Data/Models/TraTopic.cs +++ b/TransAction.Data/Models/TraTopic.cs @@ -14,6 +14,7 @@ public TraTopic() public string Title { get; set; } public int UserId { get; set; } public DateTime DbCreateTimestamp { get; set; } + public DateTime LastMessageTimestamp { get; set; } public string DbCreateUserid { get; set; } public DateTime DbLastUpdateTimestamp { get; set; } public string DbLastUpdateUserid { get; set; } diff --git a/TransAction.Data/Models/TransActionContext.cs b/TransAction.Data/Models/TransActionContext.cs index b65c9053..4adde517 100644 --- a/TransAction.Data/Models/TransActionContext.cs +++ b/TransAction.Data/Models/TransActionContext.cs @@ -555,6 +555,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasColumnName("CONCURRENCY_CONTROL_NUMBER") .HasDefaultValueSql("((1))"); + entity.Property(e => e.LastMessageTimestamp) + .HasColumnName("LAST_MESSAGE_TIMESTAMP") + .HasColumnType("datetime"); + entity.Property(e => e.DbCreateTimestamp) .HasColumnName("DB_CREATE_TIMESTAMP") .HasColumnType("datetime"); diff --git a/TransAction.Data/Models/UserActivityCreateDto.cs b/TransAction.Data/Models/UserActivityCreateDto.cs index b325806e..40c71c12 100644 --- a/TransAction.Data/Models/UserActivityCreateDto.cs +++ b/TransAction.Data/Models/UserActivityCreateDto.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Text; namespace TransAction.Data.Models { @@ -15,13 +13,13 @@ public class UserActivityCreateDto public int Minutes { get; set; } [Required(ErrorMessage = "Activity TimeStamp Required")] public DateTime ActivityTimestamp { get; set; } - [Required(ErrorMessage = "User Id Required")] + [Range(1, Int32.MaxValue, ErrorMessage = "User Id Required")] public int UserId { get; set; } - [Required(ErrorMessage = "Activity Id Required")] + [Range(1, Int32.MaxValue, ErrorMessage = "Activity Id Required")] public int ActivityId { get; set; } - [Required(ErrorMessage = "Event Id Required")] + [Range(1, Int32.MaxValue, ErrorMessage = "Event Id Required")] public int EventId { get; set; } - [Required(ErrorMessage = "Team Id Required")] + [Range(1, Int32.MaxValue, ErrorMessage = "Team Id Required")] public int TeamId { get; set; } } } diff --git a/TransAction.Data/Models/UserRoleUpdateDto.cs b/TransAction.Data/Models/UserRoleUpdateDto.cs index e5a5936e..f2375fbc 100644 --- a/TransAction.Data/Models/UserRoleUpdateDto.cs +++ b/TransAction.Data/Models/UserRoleUpdateDto.cs @@ -7,8 +7,6 @@ namespace TransAction.Data.Models { public class UserRoleUpdateDto { - [Required(ErrorMessage = "UserId Required")] - public int UserId { get; set; } [Required(ErrorMessage = "RoleId Required")] public int RoleId { get; set; } } diff --git a/TransAction.Data/Models/UserSpecificScoreDto.cs b/TransAction.Data/Models/UserSpecificScoreDto.cs index 060fdb93..00255188 100644 --- a/TransAction.Data/Models/UserSpecificScoreDto.cs +++ b/TransAction.Data/Models/UserSpecificScoreDto.cs @@ -9,5 +9,6 @@ public class UserScoreDto public int Score { get; set; } public int EventId { get; set; } public int UserId { get; set; } + public string EventName { get; set; } } } diff --git a/TransAction.Data/Repositories/ActivityRepository.cs b/TransAction.Data/Repositories/ActivityRepository.cs index 3530e942..817d6ad5 100644 --- a/TransAction.Data/Repositories/ActivityRepository.cs +++ b/TransAction.Data/Repositories/ActivityRepository.cs @@ -13,9 +13,9 @@ public ActivityRepository(TransActionContext repositoryContext) : base(repositor { } - public bool ActivityExists(string Name) + public bool Exists(string Name, int intensity) { - var checkActivity = FindAll().FirstOrDefault(c => c.Name == Name); + var checkActivity = FindAll().FirstOrDefault(c => c.Name == Name && c.Intensity == intensity); if (checkActivity != null) { return true; diff --git a/TransAction.Data/Repositories/EventRepository.cs b/TransAction.Data/Repositories/EventRepository.cs index 1809243b..50d44d88 100644 --- a/TransAction.Data/Repositories/EventRepository.cs +++ b/TransAction.Data/Repositories/EventRepository.cs @@ -12,10 +12,10 @@ public EventRepository(TransActionContext context) : base(context) } - public IEnumerable GetAll(int page, int pageSize, string name) + public IEnumerable GetAll(int page, int pageSize, string name, bool isAcitve) { if (--page < 0) page = 0; - var events = FindAll().Where(x => x.IsActive == true); + var events = FindAll().Where(x => x.IsActive == isAcitve); if (!string.IsNullOrEmpty(name)) { events = events.Where(x => x.Name.Contains(name)); @@ -28,9 +28,9 @@ public TraEvent GetById(int id) { return Find(e => e.EventId == id).FirstOrDefault(); } - public int Count(string name) + public int Count(string name, bool isActive) { - var eventCount = FindAll().Where(x => x.IsActive == true); + var eventCount = FindAll().Where(x => x.IsActive == isActive); if (!string.IsNullOrEmpty(name)) { return eventCount.Where(x => x.Name.Contains(name)).Count(); diff --git a/TransAction.Data/Repositories/Interfaces/IActivityRepository.cs b/TransAction.Data/Repositories/Interfaces/IActivityRepository.cs index bd9ab9ef..d6ca1bbe 100644 --- a/TransAction.Data/Repositories/Interfaces/IActivityRepository.cs +++ b/TransAction.Data/Repositories/Interfaces/IActivityRepository.cs @@ -11,6 +11,7 @@ public interface IActivityRepository TraActivity GetById(int id); void Create(TraActivity newActivity); void Update(TraActivity updateActivity); - bool ActivityExists(string Name); + void Delete(TraActivity activity); + bool Exists(string Name, int intensity); } } diff --git a/TransAction.Data/Repositories/Interfaces/IEventRepository.cs b/TransAction.Data/Repositories/Interfaces/IEventRepository.cs index d9ee0fdc..4ebf5951 100644 --- a/TransAction.Data/Repositories/Interfaces/IEventRepository.cs +++ b/TransAction.Data/Repositories/Interfaces/IEventRepository.cs @@ -5,8 +5,8 @@ namespace TransAction.Data.Repositories.Interfaces { public interface IEventRepository { - IEnumerable GetAll(int page, int pageSize, string name); - int Count(string name); + IEnumerable GetAll(int page, int pageSize, string name, bool isActive); + int Count(string name, bool isActive); TraEvent GetById(int id); void Create(TraEvent newEvent); void Update(TraEvent updateEvent); diff --git a/TransAction.Data/Repositories/Interfaces/IRequestRepository.cs b/TransAction.Data/Repositories/Interfaces/IRequestRepository.cs index d16ac8a5..d3b6faaf 100644 --- a/TransAction.Data/Repositories/Interfaces/IRequestRepository.cs +++ b/TransAction.Data/Repositories/Interfaces/IRequestRepository.cs @@ -11,7 +11,8 @@ public interface IRequestRepository TraMemberReq GetReqById(int id); void Create(TraMemberReq newRequest); void Update(TraMemberReq updateRequest); - IEnumerable CurrentTeamReq(int teamId); + IEnumerable GetByTeamId(int teamId); + IEnumerable GetByUserId(int userId); int Count(); } } diff --git a/TransAction.Data/Repositories/Interfaces/IRoleRepository.cs b/TransAction.Data/Repositories/Interfaces/IRoleRepository.cs index e7c7ac4c..69ba39cb 100644 --- a/TransAction.Data/Repositories/Interfaces/IRoleRepository.cs +++ b/TransAction.Data/Repositories/Interfaces/IRoleRepository.cs @@ -7,7 +7,7 @@ namespace TransAction.Data.Repositories.Interfaces { public interface IRoleRepository { - IEnumerable GetAllRoles(int page, int pageSize); + IEnumerable GetAllRoles(); TraRole GetRoleById(int id); void Create(TraRole newRole); void Update(TraRole updateRole); diff --git a/TransAction.Data/Repositories/Interfaces/IUnitOfWork.cs b/TransAction.Data/Repositories/Interfaces/IUnitOfWork.cs index c8e1f913..5c671e8e 100644 --- a/TransAction.Data/Repositories/Interfaces/IUnitOfWork.cs +++ b/TransAction.Data/Repositories/Interfaces/IUnitOfWork.cs @@ -12,7 +12,7 @@ public interface IUnitOfWork IRoleRepository Role { get; } IRequestRepository Request { get; } IImageRepository Image { get; } - IUserActivityRepository UserAct { get; } + IUserActivityRepository UserActivity { get; } bool Save(); } } diff --git a/TransAction.Data/Repositories/Interfaces/IUserActivityRepository.cs b/TransAction.Data/Repositories/Interfaces/IUserActivityRepository.cs index 9eb6f3ae..0468c013 100644 --- a/TransAction.Data/Repositories/Interfaces/IUserActivityRepository.cs +++ b/TransAction.Data/Repositories/Interfaces/IUserActivityRepository.cs @@ -5,7 +5,8 @@ namespace TransAction.Data.Repositories.Interfaces { public interface IUserActivityRepository { - IEnumerable GetAllUserActivities(int page, int pageSize); + IEnumerable GetAll(int page, int pageSize); + IEnumerable GetAllByEventUser(int eventId, int userId); void Create(TraUserActivity newUserActivity); void Update(TraUserActivity updateUserActivity); TraUserActivity GetUserActivity(int id); @@ -17,6 +18,7 @@ public interface IUserActivityRepository IEnumerable TopTeams(int number, int eventId); IEnumerable RegionalScore(int eventId); int Count(); + int CountByActivityType(int activityId); } } diff --git a/TransAction.Data/Repositories/Interfaces/IUserRepository.cs b/TransAction.Data/Repositories/Interfaces/IUserRepository.cs index a847d7e7..87bc21a7 100644 --- a/TransAction.Data/Repositories/Interfaces/IUserRepository.cs +++ b/TransAction.Data/Repositories/Interfaces/IUserRepository.cs @@ -9,6 +9,7 @@ public interface IUserRepository TraUser GetById(int id); TraUser GetByGuid(string guid); IEnumerable GetByTeamId(int teamId); + IEnumerable GetAdmins(int adminRoleId); void Create(TraUser newUser); void Update(TraUser updateUser); TraUser GetCurrentUser(string guid); diff --git a/TransAction.Data/Repositories/RequestRepository.cs b/TransAction.Data/Repositories/RequestRepository.cs index 1c4fdac0..02428fe8 100644 --- a/TransAction.Data/Repositories/RequestRepository.cs +++ b/TransAction.Data/Repositories/RequestRepository.cs @@ -15,25 +15,19 @@ public RequestRepository(TransActionContext repositoryContext) : base(repository public int Count() { - return FindAll().Where(x => x.IsActive == true).OrderBy(c => c.MemberReqId).Count(); + return Find(x => x.IsActive == true).OrderBy(c => c.MemberReqId).Count(); } - public IEnumerable CurrentTeamReq(int teamId) + public IEnumerable GetByTeamId(int teamId) { - var teamRequests = Find() - .Where(p => p.TeamId == teamId) - .Where(p => p.IsActive == true) - .Select(x => new MemberReqDto() - { - MemberReqId = x.MemberReqId, - TeamId = teamId, - UserId = x.UserId, - IsActive = x.IsActive, - ConcurrencyControlNumber = x.ConcurrencyControlNumber - }) - .ToList(); + var teamRequests = Find(p => p.TeamId == teamId && p.IsActive == true).ToList(); + return teamRequests; + } + public IEnumerable GetByUserId(int userId) + { + var teamRequests = Find(p => p.UserId == userId && p.IsActive == true).ToList(); return teamRequests; } @@ -41,7 +35,7 @@ public IEnumerable CurrentTeamReq(int teamId) public IEnumerable GetAllReq(int page, int pageSize) { if (--page < 0) page = 0; - return FindAll().Where(x => x.IsActive == true).OrderBy(c => c.MemberReqId).Skip(page * pageSize).Take(pageSize).ToList(); + return Find(x => x.IsActive == true).OrderBy(c => c.MemberReqId).Skip(page * pageSize).Take(pageSize).ToList(); } public TraMemberReq GetReqById(int id) diff --git a/TransAction.Data/Repositories/RoleRepository.cs b/TransAction.Data/Repositories/RoleRepository.cs index 27cb3b67..f048b885 100644 --- a/TransAction.Data/Repositories/RoleRepository.cs +++ b/TransAction.Data/Repositories/RoleRepository.cs @@ -18,10 +18,9 @@ public int Count() return FindAll().Count(); } - public IEnumerable GetAllRoles(int page, int pageSize) + public IEnumerable GetAllRoles() { - if (--page < 0) page = 0; - return FindAll().Skip(page * pageSize).Take(pageSize).ToList(); + return FindAll().ToList(); } public TraRole GetRoleById(int id) diff --git a/TransAction.Data/Repositories/TopicRepository.cs b/TransAction.Data/Repositories/TopicRepository.cs index da65666b..9318a8bb 100644 --- a/TransAction.Data/Repositories/TopicRepository.cs +++ b/TransAction.Data/Repositories/TopicRepository.cs @@ -21,7 +21,13 @@ public int Count() public IEnumerable GetAllTopics(int page, int pageSize) { if (--page < 0) page = 0; - return FindAll().Include(x => x.TraTopicMessage).ThenInclude(m => m.User).Skip(page * pageSize).Take(pageSize).ToList(); + return FindAll() + .Include(x => x.TraTopicMessage) + .ThenInclude(m => m.User) + .OrderByDescending(x => x.LastMessageTimestamp) + .Skip(page * pageSize) + .Take(pageSize) + .ToList(); } public TraTopic GetTopicById(int id) diff --git a/TransAction.Data/Repositories/UnitOfWork.cs b/TransAction.Data/Repositories/UnitOfWork.cs index 66ed0547..a633f5d5 100644 --- a/TransAction.Data/Repositories/UnitOfWork.cs +++ b/TransAction.Data/Repositories/UnitOfWork.cs @@ -149,17 +149,17 @@ public IImageRepository Image } } - private IUserActivityRepository _userAct; - public IUserActivityRepository UserAct + private IUserActivityRepository _userActivity; + public IUserActivityRepository UserActivity { get { - if (_userAct == null) + if (_userActivity == null) { - _userAct = new UserActivityRepository(_context); + _userActivity = new UserActivityRepository(_context); } - return _userAct; + return _userActivity; } } diff --git a/TransAction.Data/Repositories/UserActivityRepository.cs b/TransAction.Data/Repositories/UserActivityRepository.cs index e896f564..417162f1 100644 --- a/TransAction.Data/Repositories/UserActivityRepository.cs +++ b/TransAction.Data/Repositories/UserActivityRepository.cs @@ -27,7 +27,7 @@ public IEnumerable CurrentUserScore(int id) Score = x.Sum(y => y.Minutes * y.Activity.Intensity), EventId = x.Key.EventId, UserId = id, - + EventName = x.Select(y => y.Event.Name).FirstOrDefault() }) .ToList(); @@ -55,12 +55,17 @@ public int Count() return FindAll().OrderBy(c => c.UserActivityId).Count(); } - public IEnumerable GetAllUserActivities(int page, int pageSize) + public IEnumerable GetAll(int page, int pageSize) { if (--page < 0) page = 0; return FindAll().OrderBy(c => c.UserActivityId).Skip(page * pageSize).Take(pageSize).ToList(); } + public IEnumerable GetAllByEventUser(int eventId, int userId) + { + return Find(x => x.EventId == eventId && x.UserId == userId).ToList(); + } + public TraUserActivity GetUserActivity(int id) { return Find(c => c.UserActivityId == id).FirstOrDefault(); @@ -105,14 +110,13 @@ public IEnumerable RegionalScore(int eventId) public int TeamEventSpecificScore(IEnumerable users, int teamId, int eventId) { var userList = users.Select(x => x.UserId).ToList(); - var userAct = Find() - .Where(p => p.EventId == eventId && userList.Contains(p.UserId)) - .Include(x => x.Activity) - .GroupBy(x => new { x.TeamId, x.EventId }) - .Select(x => new - { - Score = x.Sum(y => y.Minutes * y.Activity.Intensity) - }).Select(c => c.Score).Sum(); + var userAct = Find(p => p.EventId == eventId && userList.Contains(p.UserId)) + .Include(x => x.Activity) + .GroupBy(x => new { x.TeamId, x.EventId }) + .Select(x => new + { + Score = x.Sum(y => y.Minutes * y.Activity.Intensity) + }).Select(c => c.Score).Sum(); return userAct; @@ -121,57 +125,56 @@ public int TeamEventSpecificScore(IEnumerable users, int teamId, int ev public IEnumerable TeamSpecificScore(IEnumerable users, int teamId) { var userList = users.Select(x => x.UserId).ToList(); - var teamAct = Find() - .Where(p => userList.Contains(p.UserId)) - .Include(x => x.Activity) - .Include(x => x.Event).Where(x => x.Event.IsActive == true) - .GroupBy(x => new { x.TeamId, x.EventId }) - .Select(x => new TeamSpecificScoreDto() - { - Score = x.Sum(y => y.Minutes * y.Activity.Intensity), - EventId = x.Key.EventId, - TeamId = teamId - }) - .ToList(); - - + var teamAct = Find(p => userList.Contains(p.UserId)) + .Include(x => x.Activity) + .Include(x => x.Event).Where(x => x.Event.IsActive == true) + .GroupBy(x => new { x.TeamId, x.EventId }) + .Select(x => new TeamSpecificScoreDto() + { + Score = x.Sum(y => y.Minutes * y.Activity.Intensity), + EventId = x.Key.EventId, + TeamId = teamId, + EventName = x.Select(y => y.Event.Name).FirstOrDefault() + }) + .ToList(); return teamAct; } public IEnumerable TopTeams(int number, int eventId) { - var teams = Find() - .Where(p => p.EventId == eventId) - .Include(x => x.Activity) - .Include(x => x.Event) - .GroupBy(x => new { x.TeamId, x.EventId }) - .Select(x => new TeamSpecificScoreDto() - { - Score = x.Sum(y => y.Minutes * y.Activity.Intensity), - EventId = x.Key.EventId, - TeamId = x.Key.TeamId - }).OrderByDescending(x => x.Score).Take(number) - .ToList(); + var teams = Find(p => p.EventId == eventId) + .Include(x => x.Activity) + .Include(x => x.Event) + .GroupBy(x => new { x.TeamId, x.EventId }) + .Select(x => new TeamSpecificScoreDto() + { + Score = x.Sum(y => y.Minutes * y.Activity.Intensity), + EventId = x.Key.EventId, + TeamId = x.Key.TeamId + }).OrderByDescending(x => x.Score).Take(number) + .ToList(); return teams; } - - public int UserSpecificScore(int userId, int eventId) { - var userAct = Find() - .Where(p => p.EventId == eventId && p.UserId == userId) - .Include(x => x.Activity) - .Include(x => x.Event) - .GroupBy(x => new { x.UserId, x.EventId }) - .Select(x => new - { - Score = x.Sum(y => y.Minutes * y.Activity.Intensity) - }).Select(c => c.Score).Sum(); + var userAct = Find(p => p.EventId == eventId && p.UserId == userId) + .Include(x => x.Activity) + .Include(x => x.Event) + .GroupBy(x => new { x.UserId, x.EventId }) + .Select(x => new + { + Score = x.Sum(y => y.Minutes * y.Activity.Intensity), + }).Select(c => c.Score).Sum(); return userAct; } + + public int CountByActivityType(int activityId) + { + return Find(x => x.ActivityId == activityId).Count(); + } } } diff --git a/TransAction.Data/Repositories/UserRepository.cs b/TransAction.Data/Repositories/UserRepository.cs index b66faa4f..0eac97e3 100644 --- a/TransAction.Data/Repositories/UserRepository.cs +++ b/TransAction.Data/Repositories/UserRepository.cs @@ -20,7 +20,7 @@ public IEnumerable GetAll(string name, int page, int pageSize) var users = FindAll(); if (!string.IsNullOrEmpty(name)) { - users = users.Where(x => (x.Fname + " " + x.Lname).Contains(name)); + users = users.Where(x => ($"{x.Fname.ToLower()} {x.Lname.ToLower()}").Contains(name.ToLower())); } return users.Include(x => x.TraImage).OrderBy(x => x.Fname).ThenBy(x => x.Lname).Skip(page * pageSize).Take(pageSize).ToList(); } @@ -39,6 +39,11 @@ public IEnumerable GetByTeamId(int teamId) return Find(e => e.TeamId == teamId).ToList(); } + public IEnumerable GetAdmins(int adminRoleId) + { + return Find(e => e.RoleId == adminRoleId).ToList(); + } + public TraUser GetCurrentUser(string guid) { return Find(c => c.Guid == guid).Include(x => x.Role).Include(x => x.TraImage).FirstOrDefault(); @@ -49,7 +54,7 @@ public int Count(string name) var userCount = FindAll().Include(x => x.TraImage); if (!string.IsNullOrEmpty(name)) { - return userCount.Where(x => (x.Fname + " " + x.Lname).Contains(name)).Count(); + return userCount.Where(x => ($"{x.Fname.ToLower()} {x.Lname.ToLower()}").Contains(name.ToLower())).Count(); } return userCount.Count(); } diff --git a/TransAction.SQL/TransAction.SQL.sqlproj b/TransAction.SQL/TransAction.SQL.sqlproj index 84ba2ff8..5c14734c 100644 --- a/TransAction.SQL/TransAction.SQL.sqlproj +++ b/TransAction.SQL/TransAction.SQL.sqlproj @@ -81,6 +81,8 @@ + + diff --git a/TransAction.SQL/dbo/Scripts/Script.PostDeployment.sql b/TransAction.SQL/dbo/Scripts/Script.PostDeployment.sql index 327c4bd9..aa1d758a 100644 --- a/TransAction.SQL/dbo/Scripts/Script.PostDeployment.sql +++ b/TransAction.SQL/dbo/Scripts/Script.PostDeployment.sql @@ -39,10 +39,86 @@ END IF NOT EXISTS (SELECT 1 FROM TRA_ACTIVITY) BEGIN - INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) - VALUES ('Low', 'Low intensity activity', 1, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Walking', 'Walking Low Intensity', 1, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Gardening', 'Gardening Low Intensity', 1, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Yoga', 'Yoga Low Intensity', 1, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Kayaking', 'Kayaking Low Intensity', 1, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Pilates', 'Pilates Low Intensity', 1, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Swimming', 'Swimming Low Intensity', 1, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Other', 'Other Low Intensity', 1, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Brisk Walking', 'Brisk Walking Medium Intensity', 2, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Water Aerobics', 'Water Aerobics Medium Intensity', 2, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Jogging', 'Jogging Medium Intensity', 2, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Hiking', 'Hiking Medium Intensity', 2, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Cycling', 'Cycling Medium Intensity', 2, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Dancing', 'Dancing Medium Intensity', 2, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Other', 'Other Medium Intensity', 2, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Running', 'Running High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) - VALUES ('Medium', 'Medium intensity activity', 2, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) - INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) - VALUES ('High', 'High intensity activity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + VALUES ('Rowing', 'Rowing High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Hot Yoga', 'Hot Yoga High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Soccer', 'Soccer High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Aerobics', 'Aerobics High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Squash', 'Squash High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Strength Training', 'Strength Training High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Basketball', 'Basketball High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('CrossFit', 'CrossFit High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Volleyball', 'Volleyball High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_ACTIVITY ([NAME], [DESCRIPTION], [INTENSITY], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Other', 'Other High Intensity', 3, GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) END + +-- Seed TRA_CONTENT_TYPE Table + +IF NOT EXISTS (SELECT 1 FROM TRA_CONTENT_TYPE) +BEGIN + INSERT INTO TRA_CONTENT_TYPE ([NAME], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Main', GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_CONTENT_TYPE ([NAME], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('FAQ', GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) + INSERT INTO TRA_CONTENT_TYPE ([NAME], [DB_CREATE_TIMESTAMP], [DB_CREATE_USERID], [DB_LAST_UPDATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [CONCURRENCY_CONTROL_NUMBER]) + VALUES ('Incentives', GETDATE(), 'Transaction_Dev', GETDATE(), 'Transaction_Dev', 1) +END + + +-- 2.3 Migration, set last message create timestamp on topics +IF EXISTS (SELECT 1 FROM TRA_TOPIC WHERE LAST_MESSAGE_TIMESTAMP IS NULL) +BEGIN +UPDATE T + +SET T.[LAST_MESSAGE_TIMESTAMP] = +(CASE + WHEN T.[DB_CREATE_TIMESTAMP] > (SELECT MAX(M.[DB_CREATE_TIMESTAMP]) FROM [TRA_TOPIC_MESSAGE] M WHERE M.TOPIC_ID = T.TOPIC_ID) + THEN T.[DB_CREATE_TIMESTAMP] + ELSE (SELECT MAX(M.[DB_CREATE_TIMESTAMP]) FROM [TRA_TOPIC_MESSAGE] M WHERE M.TOPIC_ID = T.TOPIC_ID) + END) +, T.[CONCURRENCY_CONTROL_NUMBER] = T.[CONCURRENCY_CONTROL_NUMBER] + 1 + +FROM [TRA_TOPIC] T +END \ No newline at end of file diff --git a/TransAction.SQL/dbo/Tables/TRA_CONTENT.sql b/TransAction.SQL/dbo/Tables/TRA_CONTENT.sql new file mode 100644 index 00000000..eb269017 --- /dev/null +++ b/TransAction.SQL/dbo/Tables/TRA_CONTENT.sql @@ -0,0 +1,116 @@ +CREATE TABLE [dbo].[TRA_CONTENT] +( + [CONTENT_ID] INT NOT NULL PRIMARY KEY IDENTITY, + [BODY] VARCHAR(MAX) NOT NULL, + [SORT] INT NOT NULL DEFAULT 1, + [CONTENT_TYPE_ID] INT NOT NULL, + [DB_CREATE_TIMESTAMP] DATETIME NOT NULL, + [DB_CREATE_USERID] VARCHAR(30) NOT NULL, + [DB_LAST_UPDATE_TIMESTAMP] DATETIME NOT NULL, + [DB_LAST_UPDATE_USERID] VARCHAR(30) NOT NULL, + [CONCURRENCY_CONTROL_NUMBER] BIGINT NOT NULL DEFAULT 1 + CONSTRAINT [FK_TRA_CONTENT_CONTENT_TYPE] FOREIGN KEY ([CONTENT_TYPE_ID]) REFERENCES [TRA_CONTENT_TYPE]([CONTENT_TYPE_ID]) +) + +GO +EXEC sp_addextendedproperty @name = N'MS_Description', + @value = N'The date and time the record was created.', + @level0type = N'SCHEMA', + @level0name = N'dbo', + @level1type = N'TABLE', + @level1name = N'TRA_CONTENT', + @level2type = N'COLUMN', + @level2name = N'DB_CREATE_TIMESTAMP' +GO +EXEC sp_addextendedproperty @name = N'MS_Description', + @value = N'The user or proxy account that created the record. ', + @level0type = N'SCHEMA', + @level0name = N'dbo', + @level1type = N'TABLE', + @level1name = N'TRA_CONTENT', + @level2type = N'COLUMN', + @level2name = N'DB_CREATE_USERID' +GO +EXEC sp_addextendedproperty @name = N'MS_Description', + @value = N'The date and time the record was created or last updated.', + @level0type = N'SCHEMA', + @level0name = N'dbo', + @level1type = N'TABLE', + @level1name = N'TRA_CONTENT', + @level2type = N'COLUMN', + @level2name = N'DB_LAST_UPDATE_TIMESTAMP' +GO +EXEC sp_addextendedproperty @name = N'MS_Description', + @value = N'The user or proxy account that created or last updated the record. ', + @level0type = N'SCHEMA', + @level0name = N'dbo', + @level1type = N'TABLE', + @level1name = N'TRA_CONTENT', + @level2type = N'COLUMN', + @level2name = N'DB_LAST_UPDATE_USERID' +GO + +CREATE TRIGGER [dbo].[TRA_CONTENT_AS_I_TR] + ON [dbo].[TRA_CONTENT] + AFTER INSERT +AS +BEGIN + + SET NOCOUNT ON; + + UPDATE TRA_CONTENT + SET [DB_CREATE_TIMESTAMP] = CURRENT_TIMESTAMP + ,[DB_CREATE_USERID] = CURRENT_USER + ,[DB_LAST_UPDATE_TIMESTAMP] = CURRENT_TIMESTAMP + ,[DB_LAST_UPDATE_USERID] = CURRENT_USER + ,[CONCURRENCY_CONTROL_NUMBER] = 1 + + FROM inserted + WHERE TRA_CONTENT.CONTENT_ID = inserted.CONTENT_ID + +END +GO + +CREATE TRIGGER [dbo].[TRA_CONTENT_IS_U_TR] + ON [dbo].[TRA_CONTENT] + INSTEAD OF UPDATE +AS +BEGIN + SET NOCOUNT ON; + + DECLARE @FAIL_COUNT INT; + SET @FAIL_COUNT = ( + SELECT COUNT(*) + FROM TRA_CONTENT + + JOIN inserted + ON TRA_CONTENT.CONTENT_ID = inserted.CONTENT_ID + + WHERE TRA_CONTENT.CONCURRENCY_CONTROL_NUMBER + 1 != inserted.CONCURRENCY_CONTROL_NUMBER + ) + + + IF(@FAIL_COUNT != 0) + BEGIN + RAISERROR('Concurrency Failure',16,10068); + RETURN + END + + ELSE + BEGIN + UPDATE TRA_CONTENT + SET + TRA_CONTENT.BODY = inserted.BODY, + TRA_CONTENT.SORT = inserted.SORT, + TRA_CONTENT.CONTENT_TYPE_ID = inserted.CONTENT_TYPE_ID, + TRA_CONTENT.DB_CREATE_TIMESTAMP = inserted.DB_CREATE_TIMESTAMP, + TRA_CONTENT.DB_CREATE_USERID = inserted.DB_CREATE_USERID, + TRA_CONTENT.DB_LAST_UPDATE_TIMESTAMP = CURRENT_TIMESTAMP, + TRA_CONTENT.DB_LAST_UPDATE_USERID = CURRENT_USER, + TRA_CONTENT.CONCURRENCY_CONTROL_NUMBER = inserted.CONCURRENCY_CONTROL_NUMBER + FROM TRA_CONTENT + INNER JOIN inserted + ON TRA_CONTENT.CONTENT_ID = inserted.CONTENT_ID; + END + +END \ No newline at end of file diff --git a/TransAction.SQL/dbo/Tables/TRA_CONTENT_TYPE.sql b/TransAction.SQL/dbo/Tables/TRA_CONTENT_TYPE.sql new file mode 100644 index 00000000..3700ca2a --- /dev/null +++ b/TransAction.SQL/dbo/Tables/TRA_CONTENT_TYPE.sql @@ -0,0 +1,111 @@ +CREATE TABLE [dbo].[TRA_CONTENT_TYPE] +( + [CONTENT_TYPE_ID] INT NOT NULL PRIMARY KEY IDENTITY, + [NAME] VARCHAR(256) NOT NULL, + [DB_CREATE_TIMESTAMP] DATETIME NOT NULL, + [DB_CREATE_USERID] VARCHAR(30) NOT NULL, + [DB_LAST_UPDATE_TIMESTAMP] DATETIME NOT NULL, + [DB_LAST_UPDATE_USERID] VARCHAR(30) NOT NULL, + [CONCURRENCY_CONTROL_NUMBER] BIGINT NOT NULL DEFAULT 1 +) + +GO +EXEC sp_addextendedproperty @name = N'MS_Description', + @value = N'The date and time the record was created.', + @level0type = N'SCHEMA', + @level0name = N'dbo', + @level1type = N'TABLE', + @level1name = N'TRA_CONTENT_TYPE', + @level2type = N'COLUMN', + @level2name = N'DB_CREATE_TIMESTAMP' +GO +EXEC sp_addextendedproperty @name = N'MS_Description', + @value = N'The user or proxy account that created the record. ', + @level0type = N'SCHEMA', + @level0name = N'dbo', + @level1type = N'TABLE', + @level1name = N'TRA_CONTENT_TYPE', + @level2type = N'COLUMN', + @level2name = N'DB_CREATE_USERID' +GO +EXEC sp_addextendedproperty @name = N'MS_Description', + @value = N'The date and time the record was created or last updated.', + @level0type = N'SCHEMA', + @level0name = N'dbo', + @level1type = N'TABLE', + @level1name = N'TRA_CONTENT_TYPE', + @level2type = N'COLUMN', + @level2name = N'DB_LAST_UPDATE_TIMESTAMP' +GO +EXEC sp_addextendedproperty @name = N'MS_Description', + @value = N'The user or proxy account that created or last updated the record. ', + @level0type = N'SCHEMA', + @level0name = N'dbo', + @level1type = N'TABLE', + @level1name = N'TRA_CONTENT_TYPE', + @level2type = N'COLUMN', + @level2name = N'DB_LAST_UPDATE_USERID' +GO + +CREATE TRIGGER [dbo].[TRA_CONTENT_TYPE_IS_U_TR] + ON [dbo].[TRA_CONTENT_TYPE] + INSTEAD OF UPDATE +AS +BEGIN + SET NOCOUNT ON; + + DECLARE @FAIL_COUNT INT; + SET @FAIL_COUNT = ( + SELECT COUNT(*) + FROM TRA_CONTENT_TYPE + + JOIN inserted + ON TRA_CONTENT_TYPE.CONTENT_TYPE_ID = inserted.CONTENT_TYPE_ID + + WHERE TRA_CONTENT_TYPE.CONCURRENCY_CONTROL_NUMBER + 1 != inserted.CONCURRENCY_CONTROL_NUMBER + ) + + + IF(@FAIL_COUNT != 0) + BEGIN + RAISERROR('Concurrency Failure',16,10068); + RETURN + END + + ELSE + BEGIN + UPDATE TRA_CONTENT_TYPE + SET + TRA_CONTENT_TYPE.NAME = inserted.NAME, + TRA_CONTENT_TYPE.DB_CREATE_TIMESTAMP = inserted.DB_CREATE_TIMESTAMP, + TRA_CONTENT_TYPE.DB_CREATE_USERID = inserted.DB_CREATE_USERID, + TRA_CONTENT_TYPE.DB_LAST_UPDATE_TIMESTAMP = CURRENT_TIMESTAMP, + TRA_CONTENT_TYPE.DB_LAST_UPDATE_USERID = CURRENT_USER, + TRA_CONTENT_TYPE.CONCURRENCY_CONTROL_NUMBER = inserted.CONCURRENCY_CONTROL_NUMBER + FROM TRA_CONTENT_TYPE + INNER JOIN inserted + ON TRA_CONTENT_TYPE.CONTENT_TYPE_ID = inserted.CONTENT_TYPE_ID; + END + +END +GO + +CREATE TRIGGER [dbo].[TRA_CONTENT_TYPE_AS_I_TR] + ON [dbo].[TRA_CONTENT_TYPE] + AFTER INSERT +AS +BEGIN + + SET NOCOUNT ON; + + UPDATE TRA_CONTENT_TYPE + SET [DB_CREATE_TIMESTAMP] = CURRENT_TIMESTAMP + ,[DB_CREATE_USERID] = CURRENT_USER + ,[DB_LAST_UPDATE_TIMESTAMP] = CURRENT_TIMESTAMP + ,[DB_LAST_UPDATE_USERID] = CURRENT_USER + ,[CONCURRENCY_CONTROL_NUMBER] = 1 + + FROM inserted + WHERE TRA_CONTENT_TYPE.CONTENT_TYPE_ID = inserted.CONTENT_TYPE_ID + +END \ No newline at end of file diff --git a/transaction-client/src/index.js b/transaction-client/src/index.js index 6ac4b372..e53bbdce 100644 --- a/transaction-client/src/index.js +++ b/transaction-client/src/index.js @@ -12,7 +12,7 @@ import Keycloak from 'keycloak-js'; import App from './js/App'; import store from './js/store'; -import Api from './js/api/api'; +import * as api from './js/api/api'; import initFontAwesome from './js/fontAwesome'; import { UPDATE_AUTH_USER } from './js/actions/types'; @@ -73,7 +73,7 @@ keycloak //alert('failed to initialize'); }); -Api.interceptors.request.use( +api.instance.interceptors.request.use( config => new Promise(resolve => keycloak diff --git a/transaction-client/src/js/actions/eventActions.js b/transaction-client/src/js/actions/eventActions.js index ac150fd2..1a204bd0 100644 --- a/transaction-client/src/js/actions/eventActions.js +++ b/transaction-client/src/js/actions/eventActions.js @@ -1,80 +1,129 @@ -import api from '../api/api'; +import * as api from '../api/api'; import { getApiReponseData, getApiPagedReponseData, buildApiErrorObject, buildApiQueryString } from '../utils'; -import { CREATE_EVENT, FETCH_EVENTS, FETCH_EVENT, EDIT_EVENT, ARCHIVE_EVENT, SHOW_ERROR_DIALOG_MODAL } from './types'; +import { + CREATE_EVENT, + FETCH_EVENTS, + FETCH_EVENT, + EDIT_EVENT, + ARCHIVE_EVENT, + UN_ARCHIVE_EVENT, + SHOW_ERROR_DIALOG_MODAL, +} from './types'; -export const fetchEvents = (name, page, pageSize) => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.get(`/events/?${buildApiQueryString(name, page, pageSize)}`); - const data = getApiPagedReponseData(response).data; - dispatch({ type: FETCH_EVENTS, payload: data }); +export const fetchEvents = (name, page, pageSize, isActive) => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .get(`/events/?${buildApiQueryString(name, page, pageSize, isActive)}`, { + cancelToken: api.cancelTokenSource.token, + }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_EVENTS, payload: data }); - resolve(getApiPagedReponseData(response).pageCount); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } + resolve(getApiPagedReponseData(response).pageCount); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const createEvent = formValues => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.post('/events', formValues); - const data = getApiReponseData(response); - dispatch({ type: CREATE_EVENT, payload: data }); +export const createEvent = formValues => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .post('/events', formValues, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: CREATE_EVENT, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const editEvent = (id, formValues) => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.put(`/events/${id}`, formValues); - const data = getApiReponseData(response); - dispatch({ type: EDIT_EVENT, payload: data }); +export const editEvent = (id, formValues) => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .put(`/events/${id}`, formValues, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: EDIT_EVENT, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const fetchEvent = id => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.get(`/events/${id}`); - const data = getApiReponseData(response); - dispatch({ type: FETCH_EVENT, payload: data }); +export const fetchEvent = id => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .get(`/events/${id}`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_EVENT, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const archiveEvent = event => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - event = { ...event, isActive: false }; +export const archiveEvent = event => dispatch => { + return new Promise((resolve, reject) => { + event = { ...event, isActive: false }; + api.instance + .put(`/events/${event.id}`, event, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: ARCHIVE_EVENT, payload: data }); - const response = await api.put(`/events/${event.id}`, event); - const data = getApiReponseData(response); - dispatch({ type: ARCHIVE_EVENT, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); + }); +}; + +export const unarchiveEvent = event => dispatch => { + return new Promise((resolve, reject) => { + event = { ...event, isActive: true }; + api.instance + .put(`/events/${event.id}`, event, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: UN_ARCHIVE_EVENT, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; diff --git a/transaction-client/src/js/actions/messageActions.js b/transaction-client/src/js/actions/messageActions.js index ccc738ab..a12cae9d 100644 --- a/transaction-client/src/js/actions/messageActions.js +++ b/transaction-client/src/js/actions/messageActions.js @@ -1,5 +1,5 @@ -import api from '../api/api'; -import { getApiReponseData, buildApiErrorObject } from '../utils'; +import * as api from '../api/api'; +import { getApiReponseData, buildApiErrorObject, buildApiQueryString, getApiPagedReponseData } from '../utils'; import { FETCH_TOPICS, FETCH_TOPIC, @@ -13,88 +13,112 @@ import { import history from '../history'; import * as Constants from '../Constants'; -export const fetchTopics = () => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.get('/messageboard'); - const data = getApiReponseData(response); - dispatch({ type: FETCH_TOPICS, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const fetchTopics = (title, page, pageSize) => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .get(`/messageboard/?${buildApiQueryString(title, page, pageSize)}`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_TOPICS, payload: data }); + resolve(getApiPagedReponseData(response).pageCount); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const fetchTopicDetail = topicId => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.get(`/messageboard/${topicId}`); - const data = getApiReponseData(response); - dispatch({ type: FETCH_TOPIC, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const fetchTopicDetail = topicId => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .get(`/messageboard/${topicId}`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_TOPIC, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const editTopic = topic => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.put(`/messageboard/${topic.id}`, topic); - const data = getApiReponseData(response); - dispatch({ type: EDIT_TOPIC, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const editTopic = topic => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .put(`/messageboard/${topic.id}`, topic, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: EDIT_TOPIC, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const createTopic = topic => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.post(`/messageboard`, topic); - const data = getApiReponseData(response); - dispatch({ type: CREATE_TOPIC, payload: data }); +export const createTopic = topic => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .post(`/messageboard`, topic, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: CREATE_TOPIC, payload: data }); - history.push(`${Constants.PATHS.MESSAGES}/${data.id}`); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } + history.push(`${Constants.PATHS.MESSAGES}/${data.id}`); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const createPost = message => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.post(`/messageboard/message`, message); - const data = getApiReponseData(response); - dispatch({ type: CREATE_POST, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const createPost = message => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .post(`/messageboard/message`, message, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: CREATE_POST, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const editPost = message => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.put(`/messageboard/message/${message.id}`, message); - const data = getApiReponseData(response); - dispatch({ type: EDIT_POST, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const editPost = message => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .put(`/messageboard/message/${message.id}`, message, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: EDIT_POST, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; diff --git a/transaction-client/src/js/actions/regionActions.js b/transaction-client/src/js/actions/regionActions.js index 98d33ced..1ddac70e 100644 --- a/transaction-client/src/js/actions/regionActions.js +++ b/transaction-client/src/js/actions/regionActions.js @@ -1,18 +1,21 @@ -import api from '../api/api'; +import * as api from '../api/api'; import { getApiReponseData, buildApiErrorObject } from '../utils'; import { FETCH_REGIONS, SHOW_ERROR_DIALOG_MODAL } from './types'; -export const fetchRegions = () => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.get(`/regions`); - const data = getApiReponseData(response); - dispatch({ type: FETCH_REGIONS, payload: data }); - - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const fetchRegions = () => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .get(`/regions`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_REGIONS, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; diff --git a/transaction-client/src/js/actions/roleActions.js b/transaction-client/src/js/actions/roleActions.js index dc8d2302..9465b328 100644 --- a/transaction-client/src/js/actions/roleActions.js +++ b/transaction-client/src/js/actions/roleActions.js @@ -1,40 +1,49 @@ -import api from '../api/api'; +import * as api from '../api/api'; import { getApiReponseData, buildApiErrorObject } from '../utils'; import { FETCH_ROLES, FETCH_CURRENT_ROLE, SHOW_ERROR_DIALOG_MODAL } from './types'; -export const fetchRoles = () => async (dispatch, getState) => { - return new Promise(async (resolve, reject) => { - try { - if (Object.keys(getState().roles).length === 0) { - const response = await api.get(`/roles`); - const data = getApiReponseData(response); +export const fetchRoles = () => (dispatch, getState) => { + return new Promise((resolve, reject) => { + if (Object.keys(getState().roles).length === 0) { + api.instance + .get(`/roles`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); - const resultToLower = data.map(role => { - return { ...role, name: role.name.toLowerCase() }; - }); + const resultToLower = data.map(role => { + return { ...role, name: role.name.toLowerCase() }; + }); - dispatch({ type: FETCH_ROLES, payload: resultToLower }); - } + dispatch({ type: FETCH_ROLES, payload: resultToLower }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); + } else { resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); } }); }; -export const fetchRole = id => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.get(`/roles/${id}`); - const data = getApiReponseData(response); - dispatch({ type: FETCH_CURRENT_ROLE, payload: data }); - - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const fetchRole = id => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .get(`/roles/${id}`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_CURRENT_ROLE, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; diff --git a/transaction-client/src/js/actions/teamActions.js b/transaction-client/src/js/actions/teamActions.js index c39685aa..95e63ed4 100644 --- a/transaction-client/src/js/actions/teamActions.js +++ b/transaction-client/src/js/actions/teamActions.js @@ -1,5 +1,5 @@ -import api from '../api/api'; -import { getApiReponseData, buildApiErrorObject } from '../utils'; +import * as api from '../api/api'; +import { getApiReponseData, getApiPagedReponseData, buildApiErrorObject, buildApiQueryString } from '../utils'; import { FETCH_TEAM, CREATE_TEAM, @@ -15,207 +15,249 @@ import { import history from '../history'; import * as Constants from '../Constants'; -export const fetchCurrentTeam = () => async (dispatch, getStore) => { - return new Promise(async (resolve, reject) => { - try { - const teamId = getStore().users.current.teamId; - if (teamId) { - const response = await api.get(`/teams/${teamId}`); - const data = getApiReponseData(response); - - dispatch({ type: FETCH_TEAM, payload: data }); - dispatch({ type: FETCH_CURRENT_TEAM, payload: data }); - } - +export const fetchCurrentTeam = () => (dispatch, getStore) => { + return new Promise((resolve, reject) => { + const teamId = getStore().users.current.teamId; + if (teamId) { + api.instance + .get(`/teams/${teamId}`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_TEAM, payload: data }); + dispatch({ type: FETCH_CURRENT_TEAM, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); + } else { resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); } }); }; -export const fetchTeam = id => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.get(`/teams/${id}`); - const data = getApiReponseData(response); - - dispatch({ type: FETCH_TEAM, payload: data }); - - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const fetchTeam = id => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .get(`/teams/${id}`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_TEAM, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const editTeam = (id, teamObj) => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - teamObj = { ...teamObj, name: teamObj.name.trim(), description: teamObj.description.trim() }; - - const response = await api.put(`/teams/${id}`, teamObj); - const data = getApiReponseData(response); - - dispatch({ type: FETCH_TEAM, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const editTeam = (id, teamObj) => dispatch => { + return new Promise((resolve, reject) => { + teamObj = { ...teamObj, name: teamObj.name.trim(), description: teamObj.description.trim() }; + api.instance + .put(`/teams/${id}`, teamObj, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_TEAM, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const createTeam = teamObj => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - teamObj = { ...teamObj, name: teamObj.name.trim(), description: teamObj.description.trim() }; - - const response = await api.post('/teams', teamObj); - const data = getApiReponseData(response); - - dispatch({ type: CREATE_TEAM, payload: data }); - - history.push(`${Constants.PATHS.TEAM}/${data.id}`); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const createTeam = teamObj => dispatch => { + return new Promise((resolve, reject) => { + teamObj = { ...teamObj, name: teamObj.name.trim(), description: teamObj.description.trim() }; + api.instance + .post('/teams', teamObj, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: CREATE_TEAM, payload: data }); + history.push(`${Constants.PATHS.TEAM}/${data.id}`); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const fetchTeams = () => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.get('/teams'); - const data = getApiReponseData(response); - dispatch({ type: FETCH_TEAMS, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const fetchTeams = (name, page, pageSize) => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .get(`/teams/?${buildApiQueryString(name, page, pageSize)}`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_TEAMS, payload: data }); + resolve(getApiPagedReponseData(response).pageCount); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; // Add / Remove members //Team Requests -export const fetchJoinRequests = () => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.get(`/teamrequests`); - const data = getApiReponseData(response); - dispatch({ type: FETCH_JOIN_REQUESTS, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const fetchJoinRequests = () => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .get(`/teamrequests`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_JOIN_REQUESTS, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const fetchSpecificTeamRequests = id => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.get(`/teamrequests/team/${id}`); - const data = getApiReponseData(response); - dispatch({ type: FETCH_SPECIFIC_TEAM_REQUESTS, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const fetchSpecificTeamRequests = id => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .get(`/teamrequests/team/${id}`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_SPECIFIC_TEAM_REQUESTS, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const createJoinRequest = reqObj => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.post(`/teamrequests`, reqObj); - const data = getApiReponseData(response); - dispatch({ type: POST_REQUEST, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const createJoinRequest = reqObj => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .post(`/teamrequests`, reqObj, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: POST_REQUEST, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const editJoinRequest = (id, reqObj) => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.put(`/teamrequests/${id}`, reqObj); - const data = getApiReponseData(response); - dispatch({ type: EDIT_JOIN_REQUEST, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const editJoinRequest = (id, reqObj) => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .put(`/teamrequests/${id}`, reqObj, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: EDIT_JOIN_REQUEST, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const addUserToTeam = reqObj => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.post(`/teams/join`, reqObj); - const data = getApiReponseData(response); - - dispatch({ type: FETCH_TEAM, payload: data }); - dispatch({ type: DELETE_JOIN_REQUEST, payload: reqObj.id }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const addUserToTeam = reqObj => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .post(`/teams/join`, reqObj, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_TEAM, payload: data }); + dispatch({ type: DELETE_JOIN_REQUEST, payload: reqObj.id }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const rejectJoinRequest = reqObj => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - reqObj = { ...reqObj, isActive: false }; - - await api.put(`/teamrequests/${reqObj.id}`, reqObj); - dispatch({ type: DELETE_JOIN_REQUEST, payload: reqObj.id }); - - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const rejectJoinRequest = reqObj => dispatch => { + return new Promise((resolve, reject) => { + reqObj = { ...reqObj, isActive: false }; + api.instance + .put(`/teamrequests/${reqObj.id}`, reqObj, { cancelToken: api.cancelTokenSource.token }) + .then(() => { + dispatch({ type: DELETE_JOIN_REQUEST, payload: reqObj.id }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const joinTeam = joinObj => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.post(`/teams/join`, joinObj); - const data = getApiReponseData(response); - dispatch({ type: FETCH_TEAM, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const joinTeam = joinObj => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .post(`/teams/join`, joinObj, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_TEAM, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const leaveTeam = (teamId, userId) => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.post(`/teams/remove`, { teamId, userId }); - const data = getApiReponseData(response); - dispatch({ type: FETCH_TEAM, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const leaveTeam = (teamId, userId) => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .post(`/teams/remove`, { teamId, userId }, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_TEAM, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; diff --git a/transaction-client/src/js/actions/types.js b/transaction-client/src/js/actions/types.js index 36ac393e..a370249f 100644 --- a/transaction-client/src/js/actions/types.js +++ b/transaction-client/src/js/actions/types.js @@ -3,9 +3,11 @@ export const FETCH_EVENTS = 'FETCH_EVENTS'; export const FETCH_EVENT = 'FETCH_EVENT'; export const EDIT_EVENT = 'EDIT_EVENT'; export const ARCHIVE_EVENT = 'ARCHIVE_EVENT'; +export const UN_ARCHIVE_EVENT = 'UN_ARCHIVE_EVENT'; export const FETCH_USER = 'FETCH_USER'; export const FETCH_USERS = 'FETCH_USERS'; +export const FETCH_ADMIN_USERS = 'FETCH_ADMIN_USERS'; export const FETCH_CURRENT_USER = 'FETCH_CURRENT_USER'; export const EDIT_USER_DESCRIPTION = 'EDIT_USER_DESCRIPTION'; @@ -22,6 +24,9 @@ export const FETCH_USER_ACTIVITIES = 'FETCH_USER_ACTIVITIES'; export const CREATE_USER_ACTIVITY = 'CREATE_USER_ACTIVITY'; export const FETCH_ACTIVITY_LIST = 'FETCH_ACTIVITY_LIST'; +export const CREATE_ACTIVITY_TYPE = 'CREATE_ACTIVITY_TYPE'; +export const EDIT_ACTIVITY_TYPE = 'EDIT_ACTIVITY_TYPE'; +export const DELETE_ACTIVITY_TYPE = 'DELETE_ACTIVITY_TYPE'; export const FETCH_USER_SCORES = 'FETCH_USER_SCORES'; export const FETCH_USER_EVENT_SCORE = 'FETCH_USER_EVENT_SCORE'; diff --git a/transaction-client/src/js/actions/userActions.js b/transaction-client/src/js/actions/userActions.js index fbef3042..f7f4ee88 100644 --- a/transaction-client/src/js/actions/userActions.js +++ b/transaction-client/src/js/actions/userActions.js @@ -1,10 +1,11 @@ -import api from '../api/api'; -import { getApiReponseData, buildApiErrorObject } from '../utils'; +import * as api from '../api/api'; +import { getApiReponseData, getApiPagedReponseData, buildApiErrorObject, buildApiQueryString } from '../utils'; import * as Constants from '../Constants'; import { FETCH_USER, FETCH_USERS, + FETCH_ADMIN_USERS, FETCH_CURRENT_USER, UPDATE_AUTH_USER, SET_CURRENT_USER_ROLE, @@ -12,81 +13,117 @@ import { } from './types'; //User Actions -export const fetchCurrentUser = () => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.get(`/users/me`); - const data = getApiReponseData(response); +export const fetchCurrentUser = () => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .get(`/users/me`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); - dispatch({ type: FETCH_USER, payload: data }); - dispatch({ type: FETCH_CURRENT_USER, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } + dispatch({ type: FETCH_USER, payload: data }); + dispatch({ type: FETCH_CURRENT_USER, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const fetchUsers = () => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.get('/users'); - const data = getApiReponseData(response); - - dispatch({ type: FETCH_USERS, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const fetchUsers = (name, page, pageSize) => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .get(`/users/?${buildApiQueryString(name, page, pageSize)}`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_USERS, payload: data }); + resolve(getApiPagedReponseData(response).pageCount); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const fetchUser = id => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - if (id) { - const response = await api.get(`/users/${id}`); - const data = getApiReponseData(response); - dispatch({ type: FETCH_USER, payload: data }); - } - - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const fetchAdminUsers = () => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .get('/admin/users', { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_ADMIN_USERS, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const editUser = (id, userObj) => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.put(`/users/${id}`, userObj); - const data = getApiReponseData(response); - - dispatch({ type: FETCH_USER, payload: data }); +export const fetchUser = id => dispatch => { + return new Promise((resolve, reject) => { + if (id) { + api.instance + .get(`/users/${id}`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_USER, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); + } else { resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); } }); }; -export const editUserRole = userObj => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.post(`/admin/user/role`, userObj); - const data = getApiReponseData(response); +export const editUser = (id, userObj) => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .put(`/users/${id}`, userObj, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_USER, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); + }); +}; - dispatch({ type: FETCH_USER, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const editUserRole = (userId, roleId) => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .put(`/admin/users/${userId}/role`, { roleId }, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_USER, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; diff --git a/transaction-client/src/js/actions/userActivityActions.js b/transaction-client/src/js/actions/userActivityActions.js index 694973ed..79047940 100644 --- a/transaction-client/src/js/actions/userActivityActions.js +++ b/transaction-client/src/js/actions/userActivityActions.js @@ -1,8 +1,12 @@ -import api from '../api/api'; +import * as api from '../api/api'; import { getApiReponseData, buildApiErrorObject } from '../utils'; import { FETCH_ACTIVITY_LIST, + CREATE_ACTIVITY_TYPE, + EDIT_ACTIVITY_TYPE, + DELETE_ACTIVITY_TYPE, CREATE_USER_ACTIVITY, + FETCH_USER_ACTIVITIES, FETCH_USER_SCORES, FETCH_TEAM_SCORES, FETCH_USER_EVENT_SCORE, @@ -14,129 +18,224 @@ import { //Activity Actions -export const fetchActivityList = () => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.get('/activities'); - const data = getApiReponseData(response); - dispatch({ type: FETCH_ACTIVITY_LIST, payload: data }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const fetchActivityList = () => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .get('/activities', { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_ACTIVITY_LIST, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const createUserActivity = activityObj => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - // create new activity, should contain minutes, activity type, team id , user id, event id - const response = await api.post(`/useractivity`, activityObj); - const data = getApiReponseData(response); - - dispatch({ type: CREATE_USER_ACTIVITY, payload: data }); - dispatch(fetchUserEventScore(activityObj.userId, activityObj.eventId)); - dispatch(fetchTeamEventScore(activityObj.teamId, activityObj.eventId)); - - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const createActivityType = activityType => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .post(`/activities/`, activityType, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: CREATE_ACTIVITY_TYPE, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); + }); +}; + +export const editActivityType = (id, activityType) => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .put(`/activities/${id}`, activityType, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: EDIT_ACTIVITY_TYPE, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); + }); +}; + +export const deleteActivityType = id => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .delete(`/activities/${id}`, { cancelToken: api.cancelTokenSource.token }) + .then(() => { + dispatch({ type: DELETE_ACTIVITY_TYPE, payload: id }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); + }); +}; + +export const fetchEventUserActivityList = (eventId, userId) => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .get(`/useractivities/event/${eventId}/user/${userId}`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_USER_ACTIVITIES, payload: data }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); + }); +}; + +export const createUserActivity = activityObj => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .post(`/useractivities`, activityObj, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: CREATE_USER_ACTIVITY, payload: data }); + dispatch(fetchUserEventScore(activityObj.userId, activityObj.eventId)); + dispatch(fetchTeamEventScore(activityObj.teamId, activityObj.eventId)); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; //Score Actions -export const fetchUserEventScore = (userId, eventId) => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - // this fetches a specific user score for a specific event - const response = await api.get(`/useractivity/user/${userId}/event/${eventId}`); - const data = getApiReponseData(response); - dispatch({ type: FETCH_USER_EVENT_SCORE, payload: { userId, eventId, data: data } }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const fetchUserEventScore = (userId, eventId) => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .get(`/useractivities/score/user/${userId}/event/${eventId}`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_USER_EVENT_SCORE, payload: { userId, eventId, data: data } }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const fetchAllUserScores = userId => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - // this fetches a specific user scores for ALL active events - const response = await api.get(`/useractivity/user/${userId}`); - const data = getApiReponseData(response); - - dispatch({ type: FETCH_USER_SCORES, payload: { userId, data: data } }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const fetchAllUserScores = userId => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .get(`/useractivities/score/user/${userId}`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + + dispatch({ type: FETCH_USER_SCORES, payload: { userId, data: data } }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const fetchTeamEventScore = (teamId, eventId) => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - // specific team specific event - const response = await api.get(`/useractivity/team/${teamId}/event/${eventId}`); - const data = getApiReponseData(response); - dispatch({ type: FETCH_TEAM_EVENT_SCORE, payload: { teamId, eventId, data: data } }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const fetchTeamEventScore = (teamId, eventId) => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .get(`/useractivities/score/team/${teamId}/event/${eventId}`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_TEAM_EVENT_SCORE, payload: { teamId, eventId, data: data } }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const fetchAllTeamScores = teamId => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - // specific team all ACTIVE events - const response = await api.get(`/useractivity/team/${teamId}`); - const data = getApiReponseData(response); - - dispatch({ type: FETCH_TEAM_SCORES, payload: { teamId, data: data } }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const fetchAllTeamScores = teamId => dispatch => { + return new Promise((resolve, reject) => { + // specific team all ACTIVE events + api.instance + .get(`/useractivities/score/team/${teamId}`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + + dispatch({ type: FETCH_TEAM_SCORES, payload: { teamId, data: data } }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const fetchTeamStandings = (eventId, teamCount = 20) => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.get(`/useractivity/event/${eventId}/top/${teamCount}`); - const data = getApiReponseData(response); - - dispatch({ type: FETCH_TEAM_STANDINGS, payload: { eventId, data: data } }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const fetchTeamStandings = (eventId, teamCount = 20) => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .get(`/useractivities/score/event/${eventId}/top/${teamCount}`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_TEAM_STANDINGS, payload: { eventId, data: data } }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; -export const fetchRegionStandings = eventId => async dispatch => { - return new Promise(async (resolve, reject) => { - try { - const response = await api.get(`/useractivity/event/${eventId}/region`); - const data = getApiReponseData(response); - - dispatch({ type: FETCH_REGION_STANDINGS, payload: { eventId, data: data } }); - resolve(); - } catch (e) { - dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); - reject(e); - } +export const fetchRegionStandings = eventId => dispatch => { + return new Promise((resolve, reject) => { + api.instance + .get(`/useractivities/score/event/${eventId}/region`, { cancelToken: api.cancelTokenSource.token }) + .then(response => { + const data = getApiReponseData(response); + dispatch({ type: FETCH_REGION_STANDINGS, payload: { eventId, data: data } }); + resolve(); + }) + .catch(e => { + if (!api.isCancel(e)) { + dispatch({ type: SHOW_ERROR_DIALOG_MODAL, payload: buildApiErrorObject(e.response) }); + reject(e); + } + }); }); }; diff --git a/transaction-client/src/js/api/api.js b/transaction-client/src/js/api/api.js index a7ed800d..24e9771d 100644 --- a/transaction-client/src/js/api/api.js +++ b/transaction-client/src/js/api/api.js @@ -2,9 +2,19 @@ import axios from 'axios'; import { API_URL } from '../Constants'; -const api = axios.create({ +export const instance = axios.create({ baseURL: `${API_URL}`, 'Access-Control-Allow-Origin': '*', }); -export default api; +export let cancelTokenSource = axios.CancelToken.source(); + +export const resetCancelTokenSource = () => { + cancelTokenSource = axios.CancelToken.source(); +}; + +export const isCancel = axios.isCancel; + +export const cancelRequest = () => { + cancelTokenSource.cancel(); +}; diff --git a/transaction-client/src/js/components/Admin.js b/transaction-client/src/js/components/Admin.js index 0b2f7fd3..e3bf2003 100644 --- a/transaction-client/src/js/components/Admin.js +++ b/transaction-client/src/js/components/Admin.js @@ -1,92 +1,12 @@ import React from 'react'; -import { connect } from 'react-redux'; -import { Input, Table } from 'reactstrap'; -import _ from 'lodash'; -import CardWrapper from './ui/CardWrapper'; +import AdminUser from './AdminUser'; +import AdminActivity from './AdminActivity'; import BreadcrumbFragment from './fragments/BreadcrumbFragment'; -import { fetchUsers, editUserRole } from '../actions'; -import DialogModal from './ui/DialogModal'; import * as utils from '../utils'; -// import * as Constants from '../Constants'; class Admin extends React.Component { - state = { toggleActive: {}, showConfirmDialog: false, confirmDialogOptions: {} }; - - componentDidMount() { - this.props.fetchUsers(); - } - - handleRoleIdChanged = (confirm, roleId, userId) => { - if (confirm) { - this.props.editUserRole({ roleId, userId }).then(() => this.closeConfirmDialog()); - } else { - this.closeConfirmDialog(); - window.location.reload(); - } - }; - - confirmRoleChange = (roleId, userId) => { - this.setState({ - showConfirmDialog: true, - confirmDialogOptions: { - title: 'Change User Role?', - body: "The use's role will be changed.", - secondary: true, - callback: confirm => this.handleRoleIdChanged(confirm, roleId, userId), - }, - }); - }; - - closeConfirmDialog() { - this.setState({ showConfirmDialog: false, confirmDialogOptions: {}, clicked: false }); - } - - renderContent() { - const roleOptions = Object.values(this.props.roles).map(role => ( - - )); - - const userList = _.orderBy(Object.values(this.props.users), ['fname', 'lname']).map(user => { - return ( - - {`${user.fname} ${user.lname}`} - - this.confirmRoleChange(e.target.value, user.id)} - > - {roleOptions} - - - - ); - }); - - return ( - -

User Management

- - - - - - - - {userList} -
UserRole
- {this.state.showConfirmDialog && ( - - )} -
- ); - } - render() { return ( @@ -94,7 +14,8 @@ class Admin extends React.Component { {[{ active: true, text: 'Admin' }]} - {this.renderContent()} + + ) : (

Hey you shouldn't be here...

@@ -104,15 +25,4 @@ class Admin extends React.Component { } } -const mapStateToProps = state => { - return { - currentUser: state.users.all[state.users.current.id], - users: state.users.all, - roles: state.roles, - }; -}; - -export default connect( - mapStateToProps, - { fetchUsers, editUserRole } -)(Admin); +export default Admin; diff --git a/transaction-client/src/js/components/AdminActivity.js b/transaction-client/src/js/components/AdminActivity.js new file mode 100644 index 00000000..0f2a03f5 --- /dev/null +++ b/transaction-client/src/js/components/AdminActivity.js @@ -0,0 +1,149 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { Button, Table } from 'reactstrap'; +import _ from 'lodash'; + +import CardWrapper from './ui/CardWrapper'; +import { fetchActivityList, deleteActivityType } from '../actions'; +import DialogModal from './ui/DialogModal'; +import EditActivityTypeForm from './forms/EditActivityTypeForm'; + +import * as Constants from '../Constants'; +import * as api from '../api/api'; + +class AdminActivity extends React.Component { + state = { + showConfirmDialog: false, + confirmDialogOptions: {}, + showEditActivityTypeForm: false, + selectedActivity: undefined, + editActivityTypeFormType: Constants.FORM_TYPE.EDIT, + }; + + componentDidMount() { + api.resetCancelTokenSource(); + this.props.fetchActivityList(); + } + + componentWillUnmount() { + api.cancelRequest(); + } + + closeConfirmDialog() { + this.setState({ showConfirmDialog: false, confirmDialogOptions: {} }); + } + + confirmRemoveActivity = activity => { + this.setState({ + showConfirmDialog: true, + confirmDialogOptions: { + title: 'Remove Activity Type?', + body: `${activity.name} will be removed.`, + secondary: true, + callback: confirm => this.handleRemoveActivity(confirm, activity.id), + }, + }); + }; + + handleRemoveActivity = (confirm, id) => { + if (confirm) { + this.props.deleteActivityType(id).finally(() => this.closeConfirmDialog()); + } else { + this.closeConfirmDialog(); + } + }; + + showEditActivityTypeForm = (activity, editActivityTypeFormType) => { + this.setState({ showEditActivityTypeForm: true, selectedActivity: activity, editActivityTypeFormType }); + }; + + toggleEditActivityTypeForm = () => { + this.setState(prevState => ({ + showEditActivityTypeForm: !prevState.showEditActivityTypeForm, + })); + }; + + renderActivityList = () => { + const { activities } = this.props; + const activityTableRows = _.orderBy(activities, ['intensity', 'name']).map(o => ( + + {o.name} + {o.description} + {o.intensity} + + + + + + + + )); + + return ( + + + + + + + + + + + {activityTableRows} +
NameDescriptionIntensity
+ ); + }; + + render() { + return ( + + +

Activity List Management

+ + {this.renderActivityList()} +
+ + {this.state.showConfirmDialog && ( + + )} + + {this.state.showEditActivityTypeForm && ( + + )} +
+ ); + } +} + +const mapStateToProps = state => { + return { + activities: Object.values(state.activities), + }; +}; + +export default connect( + mapStateToProps, + { fetchActivityList, deleteActivityType } +)(AdminActivity); diff --git a/transaction-client/src/js/components/AdminUser.js b/transaction-client/src/js/components/AdminUser.js new file mode 100644 index 00000000..25ed94b0 --- /dev/null +++ b/transaction-client/src/js/components/AdminUser.js @@ -0,0 +1,176 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { Alert, Row, Col, Input, Table } from 'reactstrap'; +import _ from 'lodash'; + +import CardWrapper from './ui/CardWrapper'; +import { fetchUsers, editUserRole, fetchAdminUsers } from '../actions'; +import DialogModal from './ui/DialogModal'; + +import * as api from '../api/api'; + +class AdminUser extends React.Component { + state = { showConfirmDialog: false, confirmDialogOptions: {}, userSearchTerm: '' }; + + componentDidMount() { + api.resetCancelTokenSource(); + this.props.fetchAdminUsers(); + } + + componentWillUnmount() { + api.cancelRequest(); + } + + handleRoleIdChanged = (confirm, roleId, userId) => { + if (confirm) { + this.props.editUserRole(userId, roleId).finally(() => this.closeConfirmDialog()); + } else { + this.closeConfirmDialog(); + window.location.reload(); + } + }; + + confirmRoleChange = (roleId, userId) => { + this.setState({ + showConfirmDialog: true, + confirmDialogOptions: { + title: 'Change User Role?', + body: "The use's role will be changed.", + secondary: true, + callback: confirm => this.handleRoleIdChanged(confirm, roleId, userId), + }, + }); + }; + + closeConfirmDialog() { + this.setState({ showConfirmDialog: false, confirmDialogOptions: {}, clicked: false }); + } + + handleUserSearchTermChanged = e => { + this.setState({ userSearchTerm: e.target.value }); + + const value = e.target.value.trim(); + if (value !== '') this.props.fetchUsers(e.target.value.trim()); + }; + + renderUserSearch() { + const userSearchTerm = this.state.userSearchTerm.trim().toUpperCase(); + let filteredUsers = []; + + if (this.state.userSearchTerm.trim() !== '') + filteredUsers = _.filter(Object.values(this.props.users), u => { + return `${u.fname.toUpperCase()} ${u.lname.toUpperCase()}`.includes(userSearchTerm); + }); + + const userList = this.buildUserList(filteredUsers); + + return ( + + + + + + + + + {userList.length > 0 ? ( + this.renderUserTable(userList) + ) : this.state.userSearchTerm.trim() !== '' ? ( + No users found with that search criteria. + ) : ( + Start a search using the field above + )} + + ); + } + + renderExistingAdmins() { + const filteredUsers = _.filter(Object.values(this.props.users), u => { + return u.roleId === 1; + }); + + const userList = this.buildUserList(filteredUsers); + + return this.renderUserTable(userList); + } + + buildUserList(inputUserList) { + const roleOptions = Object.values(this.props.roles).map(role => ( + + )); + + return _.orderBy(inputUserList, ['fname', 'lname']).map(user => { + return ( + + {`${user.fname} ${user.lname}`} + + this.confirmRoleChange(e.target.value, user.id)} + > + {roleOptions} + + + + ); + }); + } + + renderUserTable(userList) { + return ( + + + + + + + + {userList} +
UserRole
+ ); + } + + render() { + return ( + + +

Site Administrator Management

+ {this.renderExistingAdmins()} +
+ + +

Add New Admin

+ {this.renderUserSearch()} +
+ + {this.state.showConfirmDialog && ( + + )} +
+ ); + } +} + +const mapStateToProps = state => { + return { + currentUser: state.users.all[state.users.current.id], + users: state.users.all, + roles: state.roles, + }; +}; + +export default connect( + mapStateToProps, + { fetchUsers, editUserRole, fetchAdminUsers } +)(AdminUser); diff --git a/transaction-client/src/js/components/EventDetail.js b/transaction-client/src/js/components/EventDetail.js index d48bbb34..5fd7c4cf 100644 --- a/transaction-client/src/js/components/EventDetail.js +++ b/transaction-client/src/js/components/EventDetail.js @@ -11,12 +11,14 @@ import EventRegionStandings from './fragments/EventRegionStandings'; import EventScoresPanel from './fragments/EventScoresPanel'; import CardWrapper from './ui/CardWrapper'; +import * as api from '../api/api'; import * as Constants from '../Constants'; class EventDetail extends React.Component { state = { loading: true }; componentDidMount() { + api.resetCancelTokenSource(); const eventId = this.props.match.params.id; this.props.fetchEvent(eventId).then(() => { @@ -24,6 +26,10 @@ class EventDetail extends React.Component { }); } + componentWillUnmount() { + api.cancelRequest(); + } + renderContent() { if (!this.props.event) return
; diff --git a/transaction-client/src/js/components/EventList.js b/transaction-client/src/js/components/EventList.js index ef49cc4f..78eec67b 100644 --- a/transaction-client/src/js/components/EventList.js +++ b/transaction-client/src/js/components/EventList.js @@ -6,11 +6,12 @@ import _ from 'lodash'; import EventListItem from './fragments/EventListItem'; import EditEventForm from './forms/EditEventForm'; import PageSpinner from './ui/PageSpinner'; -import { fetchEvents, archiveEvent } from '../actions'; +import { fetchEvents, archiveEvent, unarchiveEvent } from '../actions'; import BreadcrumbFragment from './fragments/BreadcrumbFragment'; import DialogModal from './ui/DialogModal'; import ScrollLoader from './fragments/ScollLoader'; +import * as api from '../api/api'; import * as utils from '../utils'; import * as Constants from '../Constants'; class EventList extends Component { @@ -25,21 +26,41 @@ class EventList extends Component { page: 0, pageSize: 3, pageCount: 1, + isActive: true, }; componentDidMount() { + api.resetCancelTokenSource(); this.loadData(); } + componentWillUnmount() { + api.cancelRequest(); + } + loadData = () => { const nextPage = this.state.page + 1; if (this.state.page < this.state.pageCount) { - this.props.fetchEvents(this.state.searchTerm, nextPage, this.state.pageSize).then(pageCount => { - this.setState({ loading: false, page: nextPage, pageCount }); - }); + this.props + .fetchEvents(this.state.searchTerm, nextPage, this.state.pageSize, this.state.isActive) + .then(pageCount => { + this.setState({ loading: false, page: nextPage, pageCount }); + }); } }; + showArchiveEvents = () => { + this.setState({ isActive: false, page: 0 }, () => { + this.loadData(); + }); + }; + + showActiveEvents = () => { + this.setState({ isActive: true, page: 0 }, () => { + this.loadData(); + }); + }; + loadMoreData = () => { if (this.state.page <= this.state.pageCount) this.loadData(); }; @@ -64,18 +85,38 @@ class EventList extends Component { archiveEvent = (confirm, event) => { if (confirm) { - this.props.archiveEvent(event).then(() => this.closeConfirmDialog()); + this.props.archiveEvent(event).finally(() => this.closeConfirmDialog()); + } else { + this.closeConfirmDialog(); + } + }; + + unarchiveEvent = (confirm, event) => { + if (confirm) { + this.props.unarchiveEvent(event).finally(() => this.closeConfirmDialog()); } else { this.closeConfirmDialog(); } }; + confirmUnArchive = event => { + this.setState({ + showConfirmDialog: true, + confirmDialogOptions: { + title: 'UnArchive Event', + body: 'The event will be unarchived and enable user participation', + secondary: true, + callback: confirm => this.unarchiveEvent(confirm, event), + }, + }); + }; + confirmArchive = event => { this.setState({ showConfirmDialog: true, confirmDialogOptions: { title: 'Archive Event?', - body: 'The event will be archived and hidden from view.', + body: 'The event will be archived and disable user participation', secondary: true, callback: confirm => this.archiveEvent(confirm, event), }, @@ -87,24 +128,46 @@ class EventList extends Component { } renderEventList() { - const events = this.props.events.map(event => ( + const events = _.filter(this.props.events, o => { + return o.isActive === this.state.isActive; + }).map(event => ( )); - return events.length === 0 ? There are no active events at the moment. : events; + return events.length === 0 ? ( + this.state.isActive ? ( + There are no active events at the moment. + ) : ( + There are no archived events at the moment. + ) + ) : ( + events + ); } - renderAddEventButton() { + renderAdminEventButtons() { if (utils.isCurrentUserAdmin()) { return ( + {this.state.isActive ? ( + + ) : ( + + )} + @@ -117,19 +180,12 @@ class EventList extends Component { renderContent() { return ( - {this.renderAddEventButton()} + {this.renderAdminEventButtons()} {this.state.loading ? ( ) : ( - + {this.renderEventList()} - {this.state.page < this.state.pageCount && ( -
- -
- )}
)}
@@ -166,5 +222,5 @@ const mapStateToProps = state => { export default connect( mapStateToProps, - { fetchEvents, archiveEvent } + { fetchEvents, archiveEvent, unarchiveEvent } )(EventList); diff --git a/transaction-client/src/js/components/FreeAgentsList.js b/transaction-client/src/js/components/FreeAgentsList.js index 05321809..83fcb768 100644 --- a/transaction-client/src/js/components/FreeAgentsList.js +++ b/transaction-client/src/js/components/FreeAgentsList.js @@ -8,6 +8,7 @@ import CardWrapper from './ui/CardWrapper'; import BreadcrumbFragment from './fragments/BreadcrumbFragment'; import DialogModal from './ui/DialogModal'; +import * as api from '../api/api'; import * as utils from '../utils'; class FreeAgentsList extends Component { @@ -20,6 +21,7 @@ class FreeAgentsList extends Component { }; componentDidMount() { + api.resetCancelTokenSource(); const { fetchUsers, fetchTeam, teams, currentUser } = this.props; fetchUsers() @@ -32,6 +34,10 @@ class FreeAgentsList extends Component { }); } + componentWillUnmount() { + api.cancelRequest(); + } + handleRecruitUser = (confirm, userId) => { if (confirm) { const { teams, currentUser, addUserToTeam, fetchUser } = this.props; @@ -39,7 +45,7 @@ class FreeAgentsList extends Component { addUserToTeam({ userId, teamId: team.id }) .then(() => Promise.all([fetchUser(userId), fetchTeam[team.id]])) - .then(() => this.closeConfirmDialog()); + .finally(() => this.closeConfirmDialog()); } else { this.closeConfirmDialog(); } diff --git a/transaction-client/src/js/components/GettingStarted.js b/transaction-client/src/js/components/GettingStarted.js index 11abb190..85ed5659 100644 --- a/transaction-client/src/js/components/GettingStarted.js +++ b/transaction-client/src/js/components/GettingStarted.js @@ -7,11 +7,20 @@ import EditTeamForm from './forms/EditTeamForm'; import CardWrapper from './ui/CardWrapper'; import BreadcrumbFragment from './fragments/BreadcrumbFragment'; +import * as api from '../api/api'; import * as Constants from '../Constants'; class GettingStarted extends React.Component { state = { showCreateTeamForm: false }; + componentDidMount() { + api.resetCancelTokenSource(); + } + + componentWillUnmount() { + api.cancelRequest(); + } + showCreateTeamForm = () => { this.setState({ showCreateTeamForm: true }); }; diff --git a/transaction-client/src/js/components/Home.js b/transaction-client/src/js/components/Home.js index 692920ae..c1540c1b 100644 --- a/transaction-client/src/js/components/Home.js +++ b/transaction-client/src/js/components/Home.js @@ -65,11 +65,13 @@ class Home extends React.Component { - Employee Advisory Forum +
+ Employee Advisory Forum + diff --git a/transaction-client/src/js/components/MessageBoard.js b/transaction-client/src/js/components/MessageBoard.js index 63346a66..7039bed3 100644 --- a/transaction-client/src/js/components/MessageBoard.js +++ b/transaction-client/src/js/components/MessageBoard.js @@ -10,18 +10,32 @@ import PageSpinner from './ui/PageSpinner'; import CardWrapper from './ui/CardWrapper'; import BreadcrumbFragment from './fragments/BreadcrumbFragment'; import EditTopicForm from './forms/EditTopicForm'; +import ScrollLoader from './fragments/ScollLoader'; +import * as api from '../api/api'; import * as Constants from '../Constants'; class MessageBoard extends React.Component { - state = { loading: true, showEditTopicForm: false }; + state = { loading: true, showEditTopicForm: false, searchTerm: undefined, page: 0, pageSize: 10, pageCount: 1 }; componentDidMount() { - this.props.fetchTopics().then(() => { - this.setState({ loading: false }); - }); + api.resetCancelTokenSource(); + this.loadData(); } + componentWillUnmount() { + api.cancelRequest(); + } + + loadData = () => { + const nextPage = this.state.page + 1; + if (this.state.page < this.state.pageCount) { + this.props.fetchTopics(this.state.searchTerm, nextPage, this.state.pageSize).then(pageCount => { + this.setState({ loading: false, page: nextPage, pageCount }); + }); + } + }; + showEditTopicForm = () => { this.setState({ showEditTopicForm: true }); }; @@ -39,48 +53,50 @@ class MessageBoard extends React.Component { return ( - - - - - - - - - - {topics.map(topic => { - const lastMessage = topic.messages[topic.messages.length - 1]; - const postTime = moment(topic.dbCreateTimestamp); - const lastUpdateTime = moment.max(moment(lastMessage.dbCreateTimestamp), moment(topic.dbCreateTimestamp)); - return ( - - + + ); + })} + +
TopicsRepliesLast Post
-
- - {topic.title} - -
-
+ + + + + + + + + + + {topics.map(topic => { + const lastMessage = topic.messages[topic.messages.length - 1]; + const postTime = moment(topic.dbCreateTimestamp); + const lastUpdateTime = moment(lastMessage.dbCreateTimestamp); + return ( + + + + + - - - - - ); - })} - -
TopicsRepliesLast Post
+
+ + {topic.title} + +
+
+ + by {topic.userName} >>{' '} + {postTime.format(Constants.MESSAGE_DATE_FORMAT)} + +
+
{topic.postCount} - by {topic.userName} >>{' '} - {postTime.format(Constants.MESSAGE_DATE_FORMAT)} + by {lastMessage.userName} +
+ {lastUpdateTime.format(Constants.MESSAGE_DATE_FORMAT)}
- -
{topic.postCount} - - by {lastMessage.userName} -
- {lastUpdateTime.format(Constants.MESSAGE_DATE_FORMAT)} -
-
+
+
); } @@ -116,16 +132,7 @@ class MessageBoard extends React.Component { const mapStateToProps = state => { return { - topics: _.orderBy( - Object.values(state.messages), - topic => { - const lastMessage = topic.messages[topic.messages.length - 1]; - const lastUpdateTime = moment.max(moment(lastMessage.dbCreateTimestamp), moment(topic.dbCreateTimestamp)); - - return lastUpdateTime; - }, - ['desc'] - ), + topics: _.orderBy(Object.values(state.messages), ['lastMessageTimestamp'], ['desc']), }; }; diff --git a/transaction-client/src/js/components/MessageBoardTopicDetail.js b/transaction-client/src/js/components/MessageBoardTopicDetail.js index 85ddd417..80923a70 100644 --- a/transaction-client/src/js/components/MessageBoardTopicDetail.js +++ b/transaction-client/src/js/components/MessageBoardTopicDetail.js @@ -9,12 +9,14 @@ import BreadcrumbFragment from './fragments/BreadcrumbFragment'; import MessagePostFragment from './fragments/MessagePostFragment'; import EditMessageForm from './forms/EditMessageForm'; +import * as api from '../api/api'; import * as Constants from '../Constants'; class MessageBoardTopicDetail extends React.Component { state = { loading: true, topicId: null, showReplyForm: false }; componentDidMount() { + api.resetCancelTokenSource(); const topicId = parseInt(this.props.match.params.id); const { messages, fetchTopicDetail } = this.props; @@ -27,6 +29,10 @@ class MessageBoardTopicDetail extends React.Component { } } + componentWillUnmount() { + api.cancelRequest(); + } + showReplyForm = () => { this.setState({ showReplyForm: true }); }; diff --git a/transaction-client/src/js/components/Profile.js b/transaction-client/src/js/components/Profile.js index 0fb08851..bc7ff27f 100644 --- a/transaction-client/src/js/components/Profile.js +++ b/transaction-client/src/js/components/Profile.js @@ -13,6 +13,7 @@ import ProfileScoresPanel from './fragments/ProfileScoresPanel'; import UserProfileTeamPanel from './fragments/UserProfileTeamPanel'; import CardWrapper from './ui/CardWrapper'; +import * as api from '../api/api'; import * as utils from '../utils'; import * as Constants from '../Constants'; @@ -24,9 +25,14 @@ class Profile extends Component { }; componentDidMount() { + api.resetCancelTokenSource(); this.init(this.props.match.params.id); } + componentWillUnmount() { + api.cancelRequest(); + } + componentWillReceiveProps(newProps) { const currId = newProps.match.params.id; const prevId = this.props.match.params.id; diff --git a/transaction-client/src/js/components/TeamDetail.js b/transaction-client/src/js/components/TeamDetail.js index dac88e80..ba1521be 100644 --- a/transaction-client/src/js/components/TeamDetail.js +++ b/transaction-client/src/js/components/TeamDetail.js @@ -12,6 +12,7 @@ import TeamMembersPanel from './fragments/TeamMembersPanel'; import ProfileScoresPanel from './fragments/ProfileScoresPanel'; import CardWrapper from './ui/CardWrapper'; +import * as api from '../api/api'; import * as utils from '../utils'; import * as Constants from '../Constants'; @@ -22,9 +23,14 @@ class Team extends Component { }; componentDidMount() { + api.resetCancelTokenSource(); this.init(this.props.match.params.id); } + componentWillUnmount() { + api.cancelRequest(); + } + componentDidUpdate(prevProps) { // Re-init if URL param has changed const prevId = prevProps.match.params.id; diff --git a/transaction-client/src/js/components/TeamsList.js b/transaction-client/src/js/components/TeamsList.js index 88e5f216..6f75bcfd 100644 --- a/transaction-client/src/js/components/TeamsList.js +++ b/transaction-client/src/js/components/TeamsList.js @@ -1,34 +1,59 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { Link } from 'react-router-dom'; -import { Alert, Row, Col, Table, Button } from 'reactstrap'; +import { Alert, Row, Col, Input, Table, Button } from 'reactstrap'; +import _ from 'lodash'; -import { fetchTeams, fetchUsers, editUser, createJoinRequest, fetchJoinRequests } from '../actions'; +import { fetchTeams, fetchCurrentTeam, fetchUsers, editUser, createJoinRequest, fetchJoinRequests } from '../actions'; import PageSpinner from './ui/PageSpinner'; import CardWrapper from './ui/CardWrapper'; import BreadcrumbFragment from './fragments/BreadcrumbFragment'; import DialogModal from './ui/DialogModal'; +import ScrollLoader from './fragments/ScollLoader'; +import * as api from '../api/api'; import * as Constants from '../Constants'; class TeamsList extends Component { - state = { loading: true, showConfirmDialog: false, confirmDialogOptions: {} }; + state = { + loading: true, + showConfirmDialog: false, + confirmDialogOptions: {}, + searchTerm: undefined, + page: 0, + pageSize: 15, + pageCount: 1, + teamSearchTerm: '', + }; componentDidMount() { - this.setState({ loading: true }); - Promise.all([this.props.fetchTeams(), this.props.fetchJoinRequests()]) - .then(() => { - this.setState({ loading: false }); - }) - .catch(() => {}); + api.resetCancelTokenSource(); + const { currentUser, teams, fetchCurrentTeam, fetchJoinRequests } = this.props; + if (currentUser.teamId && !teams[currentUser.teamId]) { + fetchCurrentTeam(); + } + + fetchJoinRequests(); + this.loadData(); + } + + componentWillUnmount() { + api.cancelRequest(); } + loadData = () => { + const nextPage = this.state.page + 1; + if (this.state.page < this.state.pageCount) { + this.props.fetchTeams(this.state.searchTerm, nextPage, this.state.pageSize).then(pageCount => { + this.setState({ loading: false, page: nextPage, pageCount }); + }); + } + }; + sendJoinRequest = (confirm, userId, teamId) => { if (confirm) { - this.props.createJoinRequest({ userId, teamId }).then(() => { - this.closeConfirmDialog(); - }); + this.props.createJoinRequest({ userId, teamId }).finally(() => this.closeConfirmDialog()); } else { this.closeConfirmDialog(); } @@ -51,7 +76,7 @@ class TeamsList extends Component { const { currentUser, editUser } = this.props; const userObj = { ...currentUser, isFreeAgent: true }; - editUser(userObj.id, userObj).then(() => this.closeConfirmDialog()); + editUser(userObj.id, userObj).finally(() => this.closeConfirmDialog()); } else { this.closeConfirmDialog(); } @@ -84,7 +109,17 @@ class TeamsList extends Component { return request.teamId; }); - var teams = Object.values(this.props.teams).map(team => { + const teamSearchTerm = this.state.teamSearchTerm.trim().toUpperCase(); + let filteredTeams = Object.values(this.props.teams); + + if (this.state.teamSearchTerm.trim() !== '') + filteredTeams = _.filter(filteredTeams, t => { + return t.name.toUpperCase().includes(teamSearchTerm); + }); + + var teams = _.orderBy(filteredTeams, user => { + return user.name.toLowerCase(); + }).map(team => { return ( @@ -110,6 +145,13 @@ class TeamsList extends Component { return teams; } + handleTeamSearchTermChanged = e => { + this.setState({ teamSearchTerm: e.target.value }); + + const value = e.target.value.trim(); + if (value !== '') this.props.fetchTeams(e.target.value.trim()); + }; + renderTeamList() { const teamRows = this.renderTeamRows(); @@ -124,19 +166,41 @@ class TeamsList extends Component {
)} + + + + + + + + {teamRows.length > 0 ? ( - - - - - - - - {!this.props.currentUser.teamId && - - {teamRows} -
Team NameTeam LeaderRegionMembers} -
+ + + + + + + + + {!this.props.currentUser.teamId && + + {teamRows} +
Team NameTeam LeaderRegionMembers} +
+
) : ( There are no teams at the moment. )} @@ -155,10 +219,17 @@ class TeamsList extends Component { if (currentUser.teamId) { output = ( -

- You are on team {teams[currentUser.teamId].name}!{' '} - View Details. -

+
+ {teams[currentUser.teamId] && ( + + {teams[currentUser.teamId].name} + + )} +
); } else { output = ( @@ -170,7 +241,7 @@ class TeamsList extends Component { return ( -

Personal Team Status

+

Personal Team

{output}
); @@ -214,5 +285,5 @@ const mapStateToProps = state => { export default connect( mapStateToProps, - { fetchTeams, fetchUsers, editUser, createJoinRequest, fetchJoinRequests } + { fetchTeams, fetchCurrentTeam, fetchUsers, editUser, createJoinRequest, fetchJoinRequests } )(TeamsList); diff --git a/transaction-client/src/js/components/forms/EditActivityTypeForm.js b/transaction-client/src/js/components/forms/EditActivityTypeForm.js new file mode 100644 index 00000000..92fbca9b --- /dev/null +++ b/transaction-client/src/js/components/forms/EditActivityTypeForm.js @@ -0,0 +1,121 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { Field, reduxForm } from 'redux-form'; +import _ from 'lodash'; + +import { createActivityType, editActivityType } from '../../actions'; + +import FormModal from '../ui/FormModal'; +import FormInput from '../ui/FormInput'; + +import * as Constants from '../../Constants'; + +class EditActivityTypeForm extends React.Component { + state = { submitting: false }; + + onInit = () => { + this.props.initialize(this.props.initialValues); + }; + + onSubmit = formValues => { + if (!this.state.submitting) { + this.setState({ submitting: true }); + } + + if (this.props.formType === Constants.FORM_TYPE.ADD) { + this.props.createActivityType(formValues).then(() => { + this.toggleModal(); + }); + } else { + this.props.editActivityType(formValues.id, formValues).then(() => { + this.toggleModal(); + }); + } + }; + + toggleModal = () => { + this.setState({ submitting: false }); + + this.props.toggle(); + }; + + renderRegionOptions() { + const regionOptions = Object.values(this.props.regions).map(region => { + return ( + + ); + }); + + regionOptions.unshift( + ); + + return regionOptions; + } + + render() { + const title = this.props.formType === Constants.FORM_TYPE.ADD ? 'Create Activity Type' : 'Edit Activity Type'; + + return ( + + + + + + + + + + ); + } +} + +EditActivityTypeForm.propTypes = { + isOpen: PropTypes.bool.isRequired, + pristine: PropTypes.bool.isRequired, + handleSubmit: PropTypes.func.isRequired, +}; + +EditActivityTypeForm.defaultProps = { isOpen: false, pristine: false }; + +const validate = formValues => { + const errors = {}; + + if (!formValues.name) { + errors.name = 'Name required'; + } + + if (!formValues.description) { + errors.description = 'Description required'; + } + + return errors; +}; + +const form = reduxForm({ form: 'editActivityTypeForm', enableReinitialize: true, validate })(EditActivityTypeForm); + +const formConnect = connect( + null, + { createActivityType, editActivityType } +)(form); + +export default formConnect; diff --git a/transaction-client/src/js/components/forms/LogActivityForm.js b/transaction-client/src/js/components/forms/LogActivityForm.js index 49ed31c5..341fed51 100644 --- a/transaction-client/src/js/components/forms/LogActivityForm.js +++ b/transaction-client/src/js/components/forms/LogActivityForm.js @@ -6,22 +6,43 @@ import { Field, reduxForm } from 'redux-form'; import moment from 'moment'; import _ from 'lodash'; -import { fetchActivityList, createUserActivity, fetchTeamStandings } from '../../actions'; +import { fetchActivityList, createUserActivity, fetchTeamStandings, fetchEvent } from '../../actions'; import FormModal from '../ui/FormModal'; import FormInput from '../ui/FormInput'; import DatePickerInput from '../ui/DatePickerInput'; +import DropdownInput from '../ui/DropdownInput'; import PageSpinner from '../ui/PageSpinner'; +import * as utils from '../../utils'; + +const headers = ['> Low Intensity Activities', '> Medium Intensity Activities', '> High Intensity Activities']; + class LogActivityForm extends React.Component { - state = { submitting: false, loading: false }; + state = { submitting: false, loading: true }; + + componentDidMount() {} onInit = () => { - if (this.props.activities.length === 0) { + const { events, eventId, fetchEvent, activities, fetchActivityList, initialize, initialValues } = this.props; + + const actions = []; + + if (!events[eventId]) { + actions.push(utils.buildActionWithParam(fetchEvent, eventId)); + } + + if (activities.length === 0) { + actions.push(utils.buildActionWithParam(fetchActivityList)); + } + + if (actions.length > 0) { this.setState({ loading: true }); - this.props.fetchActivityList().then(() => { - this.props.initialize(this.props.initialValues); + Promise.all(actions.map(action => action.action(action.param))).then(() => { + initialize(initialValues); this.setState({ loading: false }); }); + } else { + this.setState({ loading: false }); } }; @@ -48,31 +69,63 @@ class LogActivityForm extends React.Component { this.props.toggle(); }; - renderActivityOptions = () => { - const activityOptions = this.props.activities.map(activity => { - return ( - - ); - }); - - activityOptions.unshift( - ); + createActivityOptions = () => { + const activityOptions = []; + + let i; + for (i = 0; i < 3; i++) { + activityOptions.push(...this.createActivityIntensitySection(i + 1)); + } return activityOptions; }; + createActivityIntensitySection(intensity) { + const { activities } = this.props; + const activityOptions = []; + + activityOptions.push({ type: 'header', text: headers[intensity - 1] }); + + activityOptions.push( + ..._.orderBy(activities.filter(o => o.name.toLowerCase() !== 'other' && o.intensity === intensity), ['name']).map( + o => ({ + type: 'item', + text: o.name, + description: o.description, + value: o.id, + }) + ) + ); + + activityOptions.push( + ...activities + .filter(o => o.name.toLowerCase() === 'other' && o.intensity === intensity) + .map(o => ({ + type: 'item', + text: o.name, + description: o.description, + value: o.id, + })) + ); + + return activityOptions; + } + renderFields = () => { + const { eventId, events } = this.props; + + const maxDate = moment.min(moment(), moment(events[eventId].endDate, 'YYYY-MM-DD')).toDate(); + const minDate = moment(events[eventId].startDate, 'YYYY-MM-DD').toDate(); return ( - - {this.renderActivityOptions()} - + + @@ -124,12 +178,13 @@ class LogActivityForm extends React.Component { } LogActivityForm.propTypes = { + eventId: PropTypes.number.isRequired, isOpen: PropTypes.bool.isRequired, pristine: PropTypes.bool.isRequired, handleSubmit: PropTypes.func.isRequired, }; -const validate = formValues => { +const validate = (formValues, props) => { const errors = {}; if (formValues.activityId <= 0) { @@ -146,6 +201,19 @@ const validate = formValues => { errors.activityTimestamp = 'Selected date is in the future'; } + const event = props.events[props.eventId]; + + if (event) { + const eventStartDate = moment(event.startDate, 'YYYY-MM-DD'); + const eventEndDate = moment(event.endDate, 'YYYY-MM-DD'); + + if (activityTimestamp > eventEndDate) { + errors.activityTimestamp = 'Selected date is after the event end date'; + } else if (activityTimestamp < eventStartDate) { + errors.activityTimestamp = 'Selected date is before the event start date'; + } + } + if (isNaN(formValues.activityHours) || isNaN(formValues.activityMinutes)) { errors.activityHours = 'Enter a number'; } else { @@ -182,12 +250,13 @@ const mapStateToProps = (state, ownProps) => { activityMinutes: 0, activityId: -1, }, + events: state.events, }; }; const formConnect = connect( mapStateToProps, - { fetchActivityList, createUserActivity, fetchTeamStandings } + { fetchActivityList, createUserActivity, fetchTeamStandings, fetchEvent } )(form); export default formConnect; diff --git a/transaction-client/src/js/components/fragments/EventListItem.js b/transaction-client/src/js/components/fragments/EventListItem.js index 6b810bbe..2036a576 100644 --- a/transaction-client/src/js/components/fragments/EventListItem.js +++ b/transaction-client/src/js/components/fragments/EventListItem.js @@ -18,17 +18,33 @@ class EventListItem extends React.Component { this.props.handleArchiveEvent(this.props.event); }; + unArchiveEvent = () => { + this.props.handleUnArchiveEvent(this.props.event); + }; renderEditButton() { - return ( -
- - -
- ); + if (this.props.isActive) { + return ( +
+ + +
+ ); + } else { + return ( +
+ + +
+ ); + } } render() { diff --git a/transaction-client/src/js/components/fragments/ProfileScoresPanel.js b/transaction-client/src/js/components/fragments/ProfileScoresPanel.js index 93a79df8..b9249a32 100644 --- a/transaction-client/src/js/components/fragments/ProfileScoresPanel.js +++ b/transaction-client/src/js/components/fragments/ProfileScoresPanel.js @@ -3,36 +3,28 @@ import { connect } from 'react-redux'; import { Link } from 'react-router-dom'; import { Row, Col, Alert } from 'reactstrap'; -import { fetchAllUserScores, fetchAllTeamScores, fetchEvents } from '../../actions'; +import { fetchAllUserScores, fetchAllTeamScores } from '../../actions'; import PageSpinner from '../ui/PageSpinner'; import UserScoreCard from './UserScoreCard'; import LogActivityForm from '../forms/LogActivityForm'; import * as Constants from '../../Constants'; -import * as utils from '../../utils'; class ProfileScoresPanel extends React.Component { - state = { loading: true, showLogActivityForm: false, logActivityEventId: null }; + state = { loading: true, showLogActivityForm: false, logActivityEventId: null, cancelTokenSource: undefined }; componentDidMount() { this.setState({ loading: true }); - const { fetchAllUserScores, fetchAllTeamScores, fetchEvents, currentUser } = this.props; - const actionPromises = []; + const { fetchAllUserScores, fetchAllTeamScores, currentUser } = this.props; if (currentUser.teamId) { - actionPromises.push(utils.buildActionWithParam(fetchAllUserScores, currentUser.id)); - actionPromises.push(utils.buildActionWithParam(fetchEvents)); - actionPromises.push(utils.buildActionWithParam(fetchAllTeamScores, currentUser.teamId)); - } - - Promise.all( - actionPromises.map(promise => { - return promise.action(promise.param); - }) - ).then(() => { + Promise.all([fetchAllUserScores(currentUser.id), fetchAllTeamScores(currentUser.teamId)]).then(() => { + this.setState({ loading: false }); + }); + } else { this.setState({ loading: false }); - }); + } } showLogActivityForm = eventId => { @@ -46,7 +38,7 @@ class ProfileScoresPanel extends React.Component { }; renderUserScores() { - const { events, userIdToDisplay, teamIdToDisplay, scores, currentUser } = this.props; + const { userIdToDisplay, teamIdToDisplay, scores, currentUser } = this.props; const combinedScores = []; const userScores = scores.user[userIdToDisplay]; @@ -54,13 +46,21 @@ class ProfileScoresPanel extends React.Component { if (userScores) { Object.values(userScores).forEach(score => { - combinedScores[score.eventId] = { ...combinedScores[score.eventId], userScore: score.score }; + combinedScores[score.eventId] = { + ...combinedScores[score.eventId], + userScore: score.score, + event: { name: score.eventName, eventId: score.eventId }, + }; }); } if (teamScores) { Object.values(teamScores).forEach(score => { - combinedScores[score.eventId] = { ...combinedScores[score.eventId], teamScore: score.score }; + combinedScores[score.eventId] = { + ...combinedScores[score.eventId], + teamScore: score.score, + event: { name: score.eventName, id: score.eventId }, + }; }); } @@ -72,7 +72,7 @@ class ProfileScoresPanel extends React.Component { @@ -138,11 +138,10 @@ const mapStateToProps = state => { return { scores: state.scores, currentUser: state.users.all[state.users.current.id], - events: state.events, }; }; export default connect( mapStateToProps, - { fetchAllUserScores, fetchAllTeamScores, fetchEvents } + { fetchAllUserScores, fetchAllTeamScores } )(ProfileScoresPanel); diff --git a/transaction-client/src/js/components/fragments/ScollLoader.js b/transaction-client/src/js/components/fragments/ScollLoader.js index a5a16d79..35ad7d52 100644 --- a/transaction-client/src/js/components/fragments/ScollLoader.js +++ b/transaction-client/src/js/components/fragments/ScollLoader.js @@ -1,4 +1,6 @@ import React from 'react'; +import PropTypes from 'prop-types'; +import { Button } from 'reactstrap'; import _ from 'lodash'; class ScrollLoader extends React.Component { @@ -15,13 +17,32 @@ class ScrollLoader extends React.Component { scrollListener = _.debounce(() => { if (window.innerHeight + window.pageYOffset >= document.documentElement.offsetHeight) { // Scrolled to the bottom - if (this.props.loader) this.props.loader(); + if (this.props.shouldLoad && this.props.loader) this.props.loader(); } }, 100); render() { - return this.props.children; + const { children, page, pageCount, loader, shouldLoad } = this.props; + + return ( + + {children} + {shouldLoad && page < pageCount && ( +
+ +
+ )} +
+ ); } } +ScrollLoader.propTypes = { + shouldLoad: PropTypes.bool, +}; + +ScrollLoader.defaultProps = { shouldLoad: true }; + export default ScrollLoader; diff --git a/transaction-client/src/js/components/fragments/TeamJoinRequestPanel.js b/transaction-client/src/js/components/fragments/TeamJoinRequestPanel.js index d008102c..24aae897 100644 --- a/transaction-client/src/js/components/fragments/TeamJoinRequestPanel.js +++ b/transaction-client/src/js/components/fragments/TeamJoinRequestPanel.js @@ -39,17 +39,13 @@ class TeamJoinRequestPanel extends React.Component { .then(() => { return this.props.fetchUser(request.userId); }) - .then(() => { - this.closeConfirmDialog(); - }); + .finally(() => this.closeConfirmDialog()); } }; rejectRequest = (confirm, request) => { if (confirm) { - this.props.rejectJoinRequest(request).then(() => { - this.closeConfirmDialog(); - }); + this.props.rejectJoinRequest(request).finally(() => this.closeConfirmDialog()); } else { this.closeConfirmDialog(); } diff --git a/transaction-client/src/js/components/fragments/TeamMembersPanel.js b/transaction-client/src/js/components/fragments/TeamMembersPanel.js index d8f6b822..e7329917 100644 --- a/transaction-client/src/js/components/fragments/TeamMembersPanel.js +++ b/transaction-client/src/js/components/fragments/TeamMembersPanel.js @@ -16,12 +16,13 @@ class TeamMembersPanel extends React.Component { handleRemoveUser = (confirm, user) => { if (confirm) { - this.props.leaveTeam(user.teamId, user.id).then(() => { - if (user.id === this.props.currentUser.id) this.props.fetchCurrentUser(); - else this.props.fetchUser(user.id); - - this.closeConfirmDialog(); - }); + this.props + .leaveTeam(user.teamId, user.id) + .then(() => { + if (user.id === this.props.currentUser.id) this.props.fetchCurrentUser(); + else this.props.fetchUser(user.id); + }) + .finally(() => this.closeConfirmDialog()); } else { this.closeConfirmDialog(); } diff --git a/transaction-client/src/js/components/fragments/UserScoreCard.js b/transaction-client/src/js/components/fragments/UserScoreCard.js index a1a3a492..cd9c3dec 100644 --- a/transaction-client/src/js/components/fragments/UserScoreCard.js +++ b/transaction-client/src/js/components/fragments/UserScoreCard.js @@ -4,6 +4,7 @@ import { Link } from 'react-router-dom'; import { Row, Col, Card, CardBody, CardHeader, Button, Progress } from 'reactstrap'; import LogActivityForm from '../forms/LogActivityForm'; +import ActivityJournalModal from '../ui/ActivityJournalModal'; import * as Constants from '../../Constants'; const ScoreRow = ({ score, description }) => { @@ -22,7 +23,7 @@ const ScoreRow = ({ score, description }) => { }; class UserScoreCard extends React.Component { - state = { loading: true, showLogActivityForm: false }; + state = { loading: true, showLogActivityForm: false, showActivityJournal: false }; showLogActivityForm = () => { this.setState({ showLogActivityForm: true }); @@ -34,13 +35,23 @@ class UserScoreCard extends React.Component { })); }; - handleOnClick = () => { + showActivityJournal = () => { + this.setState({ showActivityJournal: true }); + }; + + toggleActivityJournal = () => { + this.setState(prevState => ({ + showActivityJournal: !prevState.showActivityJournal, + })); + }; + + handleShowLogActivityFormClick = () => { this.showLogActivityForm(this.props.event.id); }; renderLogActivityButton = () => { return ( - ); @@ -63,7 +74,12 @@ class UserScoreCard extends React.Component { {cardWidth === Constants.USER_SCORE_CARD_WIDTH.WIDE && ( - {this.renderLogActivityButton()} + + {this.renderLogActivityButton()} + + )}
@@ -90,6 +106,13 @@ class UserScoreCard extends React.Component { refreshStandings={refreshStandings} /> )} + {this.state.showActivityJournal && ( + + )}
); } diff --git a/transaction-client/src/js/components/ui/ActivityJournalModal.js b/transaction-client/src/js/components/ui/ActivityJournalModal.js new file mode 100644 index 00000000..1a668bf8 --- /dev/null +++ b/transaction-client/src/js/components/ui/ActivityJournalModal.js @@ -0,0 +1,100 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Table } from 'reactstrap'; +import _ from 'lodash'; +import moment from 'moment'; + +import PageSpinner from './PageSpinner'; + +import { fetchEventUserActivityList, fetchActivityList } from '../../actions'; + +class ActivityJournalModal extends React.Component { + state = { loading: true }; + + componentDidMount() { + const { currentUser, eventId, fetchEventUserActivityList, activityTypes, fetchActivityList } = this.props; + + fetchEventUserActivityList(eventId, currentUser.id) + .then(() => { + if (Object.keys(activityTypes).length === 0) return fetchActivityList(); + else Promise.resolve(); + }) + .then(() => { + this.setState({ loading: false }); + }); + } + + renderContent() { + const { userActivities, activityTypes, eventId, currentUser } = this.props; + + const thisEventUserActivities = Object.values(userActivities).filter( + o => o.eventId === eventId && o.userId === currentUser.id + ); + + const tableRows = _.orderBy(thisEventUserActivities, ['activityTimestamp'], ['desc']).map(o => ( + + {activityTypes[o.activityId].name} + {activityTypes[o.activityId].intensity} + {o.description} + {o.minutes} + {moment(o.activityTimestamp).format('YYYY-MM-DD')} + + )); + + return ( + + + + + + + + + + + {tableRows} +
ActivityIntensityDescriptionDuration (minutes)Date
+ ); + } + + render() { + const { isOpen, toggle } = this.props; + return ( +
+ + Activity Journal + + {this.state.loading ? ( + + ) : ( +
{this.renderContent()}
+ )} +
+ + + +
+
+ ); + } +} + +ActivityJournalModal.propTypes = { + eventId: PropTypes.number.isRequired, +}; + +const mapStateToProps = state => { + return { + currentUser: state.users.current, + userActivities: state.userActivities, + activityTypes: state.activities, + }; +}; + +export default connect( + mapStateToProps, + { fetchEventUserActivityList, fetchActivityList } +)(ActivityJournalModal); diff --git a/transaction-client/src/js/components/ui/DatePickerInput.js b/transaction-client/src/js/components/ui/DatePickerInput.js index ee897f3a..12fb4aa3 100644 --- a/transaction-client/src/js/components/ui/DatePickerInput.js +++ b/transaction-client/src/js/components/ui/DatePickerInput.js @@ -11,10 +11,10 @@ class DatePickerInput extends React.Component { }; handleOnFucus = e => { - const { input } = this.props; + const { input, maxDate } = this.props; if (!input.value) { - this.props.input.onChange(new Date()); + this.props.input.onChange(moment.min(moment(), moment(maxDate)).toDate()); } input.onFocus(e); @@ -33,7 +33,9 @@ class DatePickerInput extends React.Component { maxDate, } = this.props; - const date = input.value ? moment(input.value, 'YYYY-MM-DD').toDate() : new Date(); + const date = input.value + ? moment(input.value, 'YYYY-MM-DD').toDate() + : moment.min(moment(), moment(maxDate)).toDate(); return ( diff --git a/transaction-client/src/js/components/ui/DropdownInput.js b/transaction-client/src/js/components/ui/DropdownInput.js new file mode 100644 index 00000000..91ea775d --- /dev/null +++ b/transaction-client/src/js/components/ui/DropdownInput.js @@ -0,0 +1,78 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { FormGroup, Label, Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +class DropdownInput extends React.Component { + state = { submitting: false, loading: false, dropdownOpen: false, title: '' }; + + toggle = () => { + this.setState(prevState => ({ + dropdownOpen: !prevState.dropdownOpen, + })); + }; + + handleOnSelect = item => { + this.props.input.onChange(item.value); + this.setState({ title: item.description }); + }; + + renderMenuItems = () => { + return this.props.menuItems.map((item, index) => { + if (item.type === 'header') { + return ( + + {item.text} + + ); + } else { + return ( + this.handleOnSelect(item)}> + {item.text} + + ); + } + }); + }; + + render() { + const { + label, + meta: { touched, error }, + title, + } = this.props; + + return ( + + +
+ + {this.state.title ? this.state.title : title} + {this.renderMenuItems()} + +
+ {touched && + (error && ( +
+ {error} +
+ ))} +
+ ); + } +} + +DropdownInput.propTypes = { + input: PropTypes.object.isRequired, + meta: PropTypes.object.isRequired, + label: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + menuItems: PropTypes.array.isRequired, + className: PropTypes.string, +}; + +DropdownInput.defaultProps = { + className: 'form-input', +}; + +export default DropdownInput; diff --git a/transaction-client/src/js/components/ui/ProfileImage.js b/transaction-client/src/js/components/ui/ProfileImage.js index 02ebc854..ed8a1cfc 100644 --- a/transaction-client/src/js/components/ui/ProfileImage.js +++ b/transaction-client/src/js/components/ui/ProfileImage.js @@ -5,7 +5,7 @@ import { Button } from 'reactstrap'; import ImageUploadModal from './ImageUploadModal'; import { fetchUser, fetchCurrentUser, fetchTeam } from '../../actions'; -import api from '../../api/api'; +import * as api from '../../api/api'; import * as Constants from '../../Constants'; @@ -34,7 +34,7 @@ class ProfileImage extends React.Component { formData.append('teamId', profileId); } - await api.post('/images', formData); + await api.instance.post('/images', formData); if (type === Constants.PROFILE_TYPE.USER) { if (currentUser.id === profileId) fetchCurrentUser(); diff --git a/transaction-client/src/js/reducers/activityReducer.js b/transaction-client/src/js/reducers/activityReducer.js index 413c2a7a..bf0ddd9c 100644 --- a/transaction-client/src/js/reducers/activityReducer.js +++ b/transaction-client/src/js/reducers/activityReducer.js @@ -1,11 +1,16 @@ import _ from 'lodash'; -import { FETCH_ACTIVITY_LIST } from '../actions/types'; +import { FETCH_ACTIVITY_LIST, CREATE_ACTIVITY_TYPE, EDIT_ACTIVITY_TYPE, DELETE_ACTIVITY_TYPE } from '../actions/types'; export default (state = {}, action) => { switch (action.type) { case FETCH_ACTIVITY_LIST: return { ...state, ..._.mapKeys(action.payload, 'id') }; - + case CREATE_ACTIVITY_TYPE: + return { ...state, [action.payload.id]: action.payload }; + case EDIT_ACTIVITY_TYPE: + return { ...state, [action.payload.id]: action.payload }; + case DELETE_ACTIVITY_TYPE: + return _.omit(state, action.payload); default: return state; } diff --git a/transaction-client/src/js/reducers/eventReducer.js b/transaction-client/src/js/reducers/eventReducer.js index 594ed155..806e4dda 100644 --- a/transaction-client/src/js/reducers/eventReducer.js +++ b/transaction-client/src/js/reducers/eventReducer.js @@ -1,6 +1,6 @@ import _ from 'lodash'; -import { CREATE_EVENT, FETCH_EVENTS, FETCH_EVENT, EDIT_EVENT, ARCHIVE_EVENT } from '../actions/types'; +import { CREATE_EVENT, FETCH_EVENTS, FETCH_EVENT, EDIT_EVENT, ARCHIVE_EVENT, UN_ARCHIVE_EVENT } from '../actions/types'; export default (state = {}, action) => { switch (action.type) { @@ -14,6 +14,8 @@ export default (state = {}, action) => { return { ...state, [action.payload.id]: action.payload }; case ARCHIVE_EVENT: return _.omit(state, action.payload.id); + case UN_ARCHIVE_EVENT: + return _.omit(state, action.payload.id); default: return state; } diff --git a/transaction-client/src/js/reducers/userActivityReducer.js b/transaction-client/src/js/reducers/userActivityReducer.js index aeafd0b0..6791e21d 100644 --- a/transaction-client/src/js/reducers/userActivityReducer.js +++ b/transaction-client/src/js/reducers/userActivityReducer.js @@ -4,7 +4,7 @@ import { CREATE_USER_ACTIVITY, FETCH_USER_ACTIVITIES, FETCH_USER_ACTIVITY } from export default (state = {}, action) => { switch (action.type) { case FETCH_USER_ACTIVITIES: - return { ...state, ..._.mapKeys(action.payload) }; + return { ...state, ..._.mapKeys(action.payload, 'id') }; case FETCH_USER_ACTIVITY: case CREATE_USER_ACTIVITY: return { ...state, [action.payload.id]: action.payload }; diff --git a/transaction-client/src/js/reducers/usersReducer.js b/transaction-client/src/js/reducers/usersReducer.js index 5bc37c80..43fa0fad 100644 --- a/transaction-client/src/js/reducers/usersReducer.js +++ b/transaction-client/src/js/reducers/usersReducer.js @@ -1,6 +1,12 @@ import _ from 'lodash'; -import { FETCH_USERS, FETCH_USER, FETCH_CURRENT_USER, SET_CURRENT_USER_ROLE } from '../actions/types'; +import { + FETCH_USERS, + FETCH_ADMIN_USERS, + FETCH_USER, + FETCH_CURRENT_USER, + SET_CURRENT_USER_ROLE, +} from '../actions/types'; const defaultState = { current: {}, @@ -10,7 +16,8 @@ const defaultState = { export default (state = defaultState, action) => { switch (action.type) { case FETCH_USERS: - return { ...state, all: { ..._.mapKeys(action.payload, 'id') } }; + case FETCH_ADMIN_USERS: + return { ...state, all: { ...state.all, ..._.mapKeys(action.payload, 'id') } }; case FETCH_USER: return { ...state, all: { ...state.all, [action.payload.id]: action.payload } }; case FETCH_CURRENT_USER: diff --git a/transaction-client/src/js/utils.js b/transaction-client/src/js/utils.js index dacbb864..c6eef0aa 100644 --- a/transaction-client/src/js/utils.js +++ b/transaction-client/src/js/utils.js @@ -62,12 +62,12 @@ export const buildApiErrorObject = response => { } }; -export const buildApiQueryString = (searchTerm, page, pageSize) => { +export const buildApiQueryString = (searchTerm, page, pageSize, isActive) => { const queries = []; - if (searchTerm) queries.push(`name=${searchTerm}`); if (page) queries.push(`page=${page}`); if (pageSize) queries.push(`pageSize=${pageSize}`); + if (!isActive) queries.push(`isActive=${isActive}`); return queries.join('&'); }; diff --git a/transaction-client/src/scss/_vendor-overrides.scss b/transaction-client/src/scss/_vendor-overrides.scss index 39abf5a3..af98276d 100644 --- a/transaction-client/src/scss/_vendor-overrides.scss +++ b/transaction-client/src/scss/_vendor-overrides.scss @@ -26,3 +26,54 @@ .react-datepicker-wrapper { width: 100%; } + +.form-input.dropdown, +.form-input.dropdown.show { + .dropdown-toggle::after { + position: absolute; + top: 50%; + right: 10px; + } + + button { + width: 100%; + height: calc(1.5em + 0.75rem + 2px); + color: #495057; + background-color: unset; + border: 1px solid #ced4da; + text-align: left; + } + + .btn-secondary.dropdown-toggle, + .btn-secondary.dropdown-toggle:hover, + .btn-secondary.dropdown-toggle:focus, + .btn-secondary.dropdown-toggle:active { + color: #495057; + background-color: unset; + border: 1px solid #ced4da; + } + + .btn-secondary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(205, 213, 226, 1); + } + + .dropdown-menu { + width: 100%; + max-height: 222px; + overflow-y: auto; + + .dropdown-item { + border: unset; + height: unset; + padding: 0 1.5rem; + } + + .dropdown-item:hover { + background-color: #cdd5e2; + } + + .dropdown-header { + padding: 0.5rem 0.5rem; + } + } +}