diff --git a/DataCenter_Windows/DBTestData/flexitimedb_12-Apr-2017.db b/DataCenter_Windows/DBTestData/flexitimedb_12-Apr-2017.db new file mode 100644 index 0000000..7c27a46 Binary files /dev/null and b/DataCenter_Windows/DBTestData/flexitimedb_12-Apr-2017.db differ diff --git a/DataCenter_Windows/WindowsDataCenter/Interfaces/IRepository.cs b/DataCenter_Windows/WindowsDataCenter/Interfaces/IRepository.cs index 6600e0f..bdd3acf 100644 --- a/DataCenter_Windows/WindowsDataCenter/Interfaces/IRepository.cs +++ b/DataCenter_Windows/WindowsDataCenter/Interfaces/IRepository.cs @@ -102,5 +102,9 @@ namespace Interfaces Group GetGroup(int groupId); OperationResponse UpdateGroup(Group group); OperationResponse DeleteGroup(int groupId); + + OperationResponse DeleteLog(TimeLog log); + OperationResponse CreateLog(TimeLog log); + OperationResponse UpdateLog(TimeLog log); } } diff --git a/DataCenter_Windows/WindowsDataCenter/Interfaces/TimeLog.cs b/DataCenter_Windows/WindowsDataCenter/Interfaces/TimeLog.cs index 416fb4a..57cedae 100644 --- a/DataCenter_Windows/WindowsDataCenter/Interfaces/TimeLog.cs +++ b/DataCenter_Windows/WindowsDataCenter/Interfaces/TimeLog.cs @@ -11,6 +11,7 @@ namespace Interfaces public DateTimeOffset EventTime { get; set; } public int CalendarWeek { get; set; } public int Year { get; set; } + public LogSource Source { get; set; } } public enum LogDirection @@ -19,4 +20,12 @@ namespace Interfaces IN = 1, OUT = 2 } + + public enum LogSource + { + UNKNOWN=0, + IDENTIFIER=1, + UI=2, + TRAYAPP=3 + } } \ No newline at end of file diff --git a/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/LogSourceDb.cs b/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/LogSourceDb.cs new file mode 100644 index 0000000..8cf1ed5 --- /dev/null +++ b/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/LogSourceDb.cs @@ -0,0 +1,10 @@ +namespace SQLiteRepository +{ + public enum LogSourceDb + { + UNKNOWN=0, + IDENTIFIER = 1, + UI = 2, + TRAYAPP = 3 + } +} \ No newline at end of file diff --git a/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/SQLiteProcedures.cs b/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/SQLiteProcedures.cs index ac5e79c..b38588e 100644 --- a/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/SQLiteProcedures.cs +++ b/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/SQLiteProcedures.cs @@ -18,6 +18,14 @@ namespace SQLiteRepository "select * from " + nameof(UserIdentity) + " order by " + nameof(UserIdentity.LastName) + " collate nocase, " + nameof(UserIdentity.FirstName) + " collate nocase limit ? offset ?"; + public const string GET_ALL_USERS_BY_GROUP = + "select u." + nameof(UserIdentity.Id) + ", u." + nameof(UserIdentity.FirstName) + ", u." + + nameof(UserIdentity.LastName) + ", u." + nameof(UserIdentity.HoursPerWeek) + ", u." + + nameof(UserIdentity.IsContractor) + " from " + nameof(UserIdentity) + " u left join " + + nameof(UserGroupJoinDb) + " ugj on ugj." + nameof(UserGroupJoinDb.UserId_FK) + " = u." + + nameof(UserIdentity.Id) + " where ugj." + nameof(UserGroupJoinDb.GroupId_FK) + "=? order by u." + + nameof(UserIdentity.LastName) + " collate nocase, u." + nameof(UserIdentity.LastName) + " collate nocase"; + public const string GET_USER_BY_ID = "select * from " + nameof(UserIdentity) + " where " + nameof(UserIdentity.Id) + "=?"; @@ -38,6 +46,11 @@ namespace SQLiteRepository "update " + nameof(CardUniqueId) + " set " + nameof(CardUniqueId.LastUsed) + " = ? where " + nameof(CardUniqueId.Id) + " = ?"; + public const string UPDATE_USER_DETAILS = + "update " + nameof(UserIdentity) + " set " + nameof(UserIdentity.FirstName) + "=?, " + + nameof(UserIdentity.LastName) + "=?, " + nameof(UserIdentity.HoursPerWeek) + "=?," + + nameof(UserIdentity.IsContractor) + "=? where " + nameof(UserIdentity.Id) + "=?"; + public const string SEARCH_USER_LIST = "SELECT * FROM " + nameof(UserIdentity) + " where(" + nameof(UserIdentity.FirstName) + " Like ? OR " + nameof(UserIdentity.LastName) + " Like ?)"; diff --git a/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/SQLiteRepository.cs b/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/SQLiteRepository.cs index ef49892..3e2d97d 100644 --- a/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/SQLiteRepository.cs +++ b/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/SQLiteRepository.cs @@ -50,7 +50,7 @@ namespace SQLiteRepository { users = _connection.Query( - "select u.Id, u.FirstName, u.LastName, u.HoursPerWeek, u.IsContractor from UserIdentity u left join UserGroupJoinDb ugj on ugj.UserId_FK = u.Id where ugj.GroupId_FK=?", + SQLiteProcedures.GET_ALL_USERS_BY_GROUP, groupId); userCount = users.Count; } @@ -87,7 +87,7 @@ namespace SQLiteRepository } if (pageNumber == -1 && pageSize == -1) { - ret.PageSize = 10; + ret.PageSize = 10; ret.PageNumber = 1; } else @@ -311,7 +311,7 @@ namespace SQLiteRepository { //edit.. _connection.Query( - "update UserIdentity set FirstName=?, LastName=?, HoursPerWeek=?,IsContractor=? where Id=?", + SQLiteProcedures.UPDATE_USER_DETAILS, user.FirstName, user.LastName, user.HoursPerWeek, @@ -353,7 +353,7 @@ namespace SQLiteRepository #region Update Group Associations - SetUserGroups(userId, user.Groups.Where(x=>x.IsAssociatedToUser).ToList()); + SetUserGroups(userId, user.Groups.Where(x => x.IsAssociatedToUser).ToList()); #endregion @@ -392,7 +392,7 @@ namespace SQLiteRepository #region Check the user hasnt registered an event in the last few minutes.. - if (ident.UserId_FK!=-1) + if (ident.UserId_FK != -1) { //only check log gap if the card is associated to a user var hysteresisThresholdMinutes = Convert.ToInt32(ConfigurationHandler.ConfigurationHandler.GetConfiguration("SwipeTimeGap") ?? "3"); var threshold = DateTime.UtcNow.AddMinutes(0 - hysteresisThresholdMinutes); @@ -405,7 +405,7 @@ namespace SQLiteRepository _logger.Error("Not logging event for user id: {0}, logged event within TimeGap Threshold of {1}", ident.UserId_FK, threshold); logId = -1; return OperationResponse.FAILED; - } + } } #endregion @@ -415,7 +415,7 @@ namespace SQLiteRepository var calendarWeek = GetIso8601CalendarWeek(logTime); var year = logTime.Year; #endregion - + var timeLog = new TimeLogDb { SwipeEventDateTime = DateTime.UtcNow, @@ -423,7 +423,8 @@ namespace SQLiteRepository IdentifierId = ident.Id, Direction = logDirection, Year = year, - CalendarWeek = calendarWeek + CalendarWeek = calendarWeek, + Source = LogSourceDb.IDENTIFIER }; _connection.Insert(timeLog); @@ -438,13 +439,13 @@ namespace SQLiteRepository //TODO: check group name can only be entered once. public OperationResponse CreateGroup(Group group, out int groupId) { - var groupDb = new GroupDb { GroupName=group.Name}; + var groupDb = new GroupDb { GroupName = group.Name }; var resp = _connection.Insert(groupDb); groupId = groupDb.GroupId; return OperationResponse.CREATED; } - public List GetGroups(int userId=-1) + public List GetGroups(int userId = -1) { var ret = new List(); List query; @@ -459,13 +460,12 @@ namespace SQLiteRepository } else { - var t = new UserGroupJoinDb(); query = _connection.Query( - "select gdb.GroupId, gdb.GroupName, gdb.AssignedUserCount"+ - " from GroupDb gdb"+ - " left join UserGroupJoinDb ujdb"+ - " on gdb.GroupId = ujdb.GroupId_FK"+ + "select gdb.GroupId, gdb.GroupName, gdb.AssignedUserCount" + + " from GroupDb gdb" + + " left join UserGroupJoinDb ujdb" + + " on gdb.GroupId = ujdb.GroupId_FK" + " where ujdb.UserId_FK = ?", userId); } @@ -473,9 +473,9 @@ namespace SQLiteRepository { ret.Add(new Group { - Id=group.GroupId, + Id = group.GroupId, Name = group.GroupName, - UserCount = int.Parse(group.AssignedUserCount??"0") + UserCount = int.Parse(group.AssignedUserCount ?? "0") }); } return ret; @@ -500,11 +500,6 @@ namespace SQLiteRepository public OperationResponse UpdateGroup(Group group) { //TODO: I would probably prefer to do this manually.... - var groupDb = new GroupDb - { - GroupId = group.Id, - GroupName = group.Name - }; var resp = _connection.Query("update GroupDb set GroupName=? where GroupId=?", group.Name, group.Id); //var resp = _connection.Update(groupDb); return OperationResponse.UPDATED; @@ -516,9 +511,76 @@ namespace SQLiteRepository return OperationResponse.DELETED; } + public OperationResponse DeleteLog(TimeLog log) + { + var query = _connection.Query( + "select * from TimeLogDb where Id=?", log.Id); + + if (!query.Any()) + return OperationResponse.FAILED; + + _connection.ExecuteScalar("delete from TimeLogDb where Id=?", log.Id); + + return OperationResponse.DELETED; + } + + public OperationResponse CreateLog(TimeLog log) + { + var calendarWeek = GetIso8601CalendarWeek(log.EventTime.UtcDateTime); + var year = log.EventTime.Year; + var dbLog = new TimeLogDb + { + SwipeEventDateTime = log.EventTime, + Direction = (LogDirectionDb)(int)log.Direction, + Year = year, + CalendarWeek = calendarWeek, + Source = (LogSourceDb)(int)log.Source, + UserId_FK = log.UserId, + IdentifierId = ConvertSourceToIdentifierId(log.Source), + }; + _connection.Insert(dbLog); + return OperationResponse.CREATED; + } + + public OperationResponse UpdateLog(TimeLog log) + { + var query = _connection.Query( + "select * from TimeLogDb where Id=?", log.Id); + if(!query.Any()) + return OperationResponse.FAILED; + + if (log.CalendarWeek > 52 || log.CalendarWeek < 1) + { + log.CalendarWeek = GetIso8601CalendarWeek(log.EventTime.UtcDateTime); + } + if (log.Year < 2017) + { + log.Year = log.EventTime.Year; + } + _connection.ExecuteScalar( + "update TimeLogDb set UserId_FK=?, Direction=?,SwipeEventDateTime=?,CalendarWeek=?,Year=?,Source=? where Id=?", + log.UserId, (LogDirectionDb) (int) log.Direction, log.EventTime, log.CalendarWeek, log.Year, + (LogSourceDb) (int) log.Source, log.Id); + + return OperationResponse.UPDATED; + } + + private int ConvertSourceToIdentifierId(LogSource logSource) + { + switch (logSource) + { + case LogSource.UI: + return -100; + case LogSource.TRAYAPP: + return -200; + default: + return -10; + } + } + private bool SetUserGroups(int userId, List groups) { - var groupIds = GetGroupIds(groups.Select(x=>x.Name).ToList()); + var groupIds = GetGroupIds(groups.Select(x => x.Name).ToList()); return SetUserGroups(userId, groupIds); } @@ -527,7 +589,7 @@ namespace SQLiteRepository //remove the existing user>group associations _connection.Query("delete from UserGroupJoinDb where UserId_FK = ?", userId); //add the new group associations. - _connection.InsertAll(groupIds.Select(x => new UserGroupJoinDb {GroupId_FK = x, UserId_FK = userId})); + _connection.InsertAll(groupIds.Select(x => new UserGroupJoinDb { GroupId_FK = x, UserId_FK = userId })); return true; } @@ -560,7 +622,7 @@ namespace SQLiteRepository EventTime = x.SwipeEventDateTime, UserId = x.UserId_FK, Year = x.Year - }).ToList(); + }).OrderBy(x=>x.EventTime.UtcDateTime).ToList(); var dict = new Dictionary(); var logList = new List(); @@ -572,7 +634,7 @@ namespace SQLiteRepository } //add the logs to the respective day of the week. - foreach (var log in timeLogs) + foreach (var log in timeLogs.OrderBy(x=>x.EventTime)) { dict[log.EventTime.DayOfWeek].Logs.Add(log); } @@ -592,7 +654,7 @@ namespace SQLiteRepository foreach (DayOfWeek day in Enum.GetValues(typeof(DayOfWeek))) { if (logList.Any(x => x.Day == day)) continue; - var dailyLog = new DailyLogs {Day = day, DayOfWeek = day.ToString()}; + var dailyLog = new DailyLogs { Day = day, DayOfWeek = day.ToString() }; logList.Add(dailyLog); } @@ -601,13 +663,13 @@ namespace SQLiteRepository dailyCollection.Value.DailyTotal = CalculateDailyTotal(dailyCollection.Value); } - return logList.OrderBy(x => ((int) x.Day + 6)%7).ToList(); + return logList.OrderBy(x => ((int)x.Day + 6) % 7).ToList(); } private double CalculateDailyTotal(DailyLogs dailyLogs) { var totalInTime = TimeSpan.FromSeconds(0); - var logs = dailyLogs.Logs.OrderBy(x => x.Id).ToArray(); + var logs = dailyLogs.Logs.OrderBy(x => x.EventTime.UtcDateTime).ToArray(); var totalCalcMax = IsOdd(logs.Length) ? logs.Length - 1 : logs.Length; for (int i = 0; i < totalCalcMax; i += 2) { diff --git a/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/SQLiteRepository.csproj b/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/SQLiteRepository.csproj index 20ca85b..4389354 100644 --- a/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/SQLiteRepository.csproj +++ b/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/SQLiteRepository.csproj @@ -61,6 +61,7 @@ + diff --git a/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/TimeLogDb.cs b/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/TimeLogDb.cs index 7e13b4e..a59fcb2 100644 --- a/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/TimeLogDb.cs +++ b/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/TimeLogDb.cs @@ -13,5 +13,6 @@ namespace SQLiteRepository public DateTimeOffset SwipeEventDateTime { get; set; } public int CalendarWeek { get; set; } public int Year { get; set; } + public LogSourceDb Source { get; set; } } } \ No newline at end of file diff --git a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/Controllers/LogsController.cs b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/Controllers/LogsController.cs new file mode 100644 index 0000000..5a3d8b4 --- /dev/null +++ b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/Controllers/LogsController.cs @@ -0,0 +1,48 @@ +using System; +using System.Web.Http; +using WindowsDataCenter.Helpers; +using Interfaces; + +namespace WindowsDataCenter +{ + [RoutePrefix("api/logs")] + public class LogsController:ApiController + { + private IRepository _repo; + private ILogger _logger; + + public LogsController(IRepository repo, ILogger logger) + { + if (repo == null) + { + throw new ArgumentNullException(nameof(repo)); + } + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + _repo = repo; + _logger = logger; + } + + [HttpPost] + [Route("create")] + [CacheControl(MaxAge = 0)] + public IHttpActionResult CreateAndEditResultLog([FromBody] TimeLog log) + { + log.Source = LogSource.UI; + var resp = log.Id > 0 ? _repo.UpdateLog(log) : _repo.CreateLog(log); + return Ok(new {Id=log.Id, OperationResponse=resp}); + } + + [HttpDelete] + [Route("delete")] + [CacheControl(MaxAge = 0)] + public IHttpActionResult DeleteLog([FromBody] TimeLog log) + { + _logger.Info("Removing Log {0} for user id {1}", log.Id, log.UserId); + _repo.DeleteLog(log); + return Ok(); + } + } +} diff --git a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/Controllers/SwipeDataController.cs b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/Controllers/SwipeDataController.cs index d2e5d29..a1b8a9d 100644 --- a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/Controllers/SwipeDataController.cs +++ b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/Controllers/SwipeDataController.cs @@ -44,16 +44,5 @@ namespace WindowsDataCenter Content = new StringContent(logId.ToString()) }); } - /// - /// - /// - /// - /// - [HttpPost] - [Route("manual")] - public IHttpActionResult ManuallyPostData([FromBody] ManualLog log) - { - throw new NotImplementedException(); - } } } \ No newline at end of file diff --git a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/WindowsDataCenter.csproj b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/WindowsDataCenter.csproj index b86b216..6b8df4f 100644 --- a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/WindowsDataCenter.csproj +++ b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/WindowsDataCenter.csproj @@ -151,6 +151,7 @@ + @@ -199,6 +200,9 @@ PreserveNewest + + Always + Always @@ -220,6 +224,9 @@ PreserveNewest + + Always + Always diff --git a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/Helpers.js b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/Helpers.js index dd19e67..9f241b8 100644 --- a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/Helpers.js +++ b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/Helpers.js @@ -3,7 +3,7 @@ /** * Create a request URL - references apiEndpoints object to construct url with args, and optional callback url. * @param {string} routePath - * @param {Array} params - Key, Value object detailing the param name (key) and value (value). + * @param {Array>} params - Key, Value object detailing the param name (key) and value (value). * @param {boolean} requiresCallback - True - add callback function for JSONP/CORS. * @param {boolean} isAbsolutePath - True, create a relative URL (without root). * @returns {string} the url generated diff --git a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/css/knockout.contextmenu.css b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/css/knockout.contextmenu.css new file mode 100644 index 0000000..bedb129 --- /dev/null +++ b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/css/knockout.contextmenu.css @@ -0,0 +1,59 @@ +/* knockout.contextmenu v1.0.0 + Nicolás Escalante - nlante@gmail.com + Issues: https://github.com/nescalante/knockout.contextmenu/issues + License: MIT */ + +.context-menu { + position: absolute; + padding: 0; + margin: 0; + z-index: 1030; + background-color: #ffffff; +} +.context-menu ul { + line-height: 1.6; + padding: 0; + margin: 0; + border: 1px solid #dddddd; + box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.3); +} +.context-menu ul > li { + padding: 4px 20px; + margin: 0; + z-index: 1031; + list-style-type: none; + cursor: pointer; + white-space: nowrap; + color: #333333; +} +.context-menu ul > li:hover { + background-color: #eeeeee; +} +.context-menu ul > li.disabled, +.context-menu ul > li.disabled a { + color: #666666; + cursor: default; +} +.context-menu ul > li.checked:before { + position: absolute; + content: "\2713"; + left: 7px; +} +.context-menu ul > li.with-url { + padding: 0; +} +.context-menu ul > li.with-url a { + display: block; + padding: 4px 20px; + text-decoration: none; + color: #333333; +} +.context-menu ul > li.separator { + margin: 4px 0; + padding: 0; + border-bottom: 1px solid #cccccc; + cursor: default; +} +.context-menu ul > li.separator:hover { + background-color: #ffffff; +} \ No newline at end of file diff --git a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/index.html b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/index.html index 079b15d..0b8e067 100644 --- a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/index.html +++ b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/index.html @@ -4,16 +4,20 @@ Flexi Time Data Viewer - - + + - + + + + - + +