Merge branch 'ManuallyCreateLogs-#29' into Release0.2
This commit is contained in:
commit
a3c4212f0b
BIN
DataCenter_Windows/DBTestData/flexitimedb_12-Apr-2017.db
Normal file
BIN
DataCenter_Windows/DBTestData/flexitimedb_12-Apr-2017.db
Normal file
Binary file not shown.
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
namespace SQLiteRepository
|
||||
{
|
||||
public enum LogSourceDb
|
||||
{
|
||||
UNKNOWN=0,
|
||||
IDENTIFIER = 1,
|
||||
UI = 2,
|
||||
TRAYAPP = 3
|
||||
}
|
||||
}
|
||||
@ -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 ?)";
|
||||
|
||||
@ -50,7 +50,7 @@ namespace SQLiteRepository
|
||||
{
|
||||
users =
|
||||
_connection.Query<UserIdentity>(
|
||||
"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;
|
||||
}
|
||||
@ -311,7 +311,7 @@ namespace SQLiteRepository
|
||||
{
|
||||
//edit..
|
||||
_connection.Query<UserIdentity>(
|
||||
"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);
|
||||
@ -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<Group> GetGroups(int userId=-1)
|
||||
public List<Group> GetGroups(int userId = -1)
|
||||
{
|
||||
var ret = new List<Group>();
|
||||
List<GroupDb> query;
|
||||
@ -459,13 +460,12 @@ namespace SQLiteRepository
|
||||
}
|
||||
else
|
||||
{
|
||||
var t = new UserGroupJoinDb();
|
||||
query =
|
||||
_connection.Query<GroupDb>(
|
||||
"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<GroupDb>("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<TimeLogDb>(
|
||||
"select * from TimeLogDb where Id=?", log.Id);
|
||||
|
||||
if (!query.Any())
|
||||
return OperationResponse.FAILED;
|
||||
|
||||
_connection.ExecuteScalar<TimeLogDb>("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<TimeLogDb>(
|
||||
"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<TimeLogDb>(
|
||||
"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<Group> 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<UserGroupJoinDb>("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<DayOfWeek, DailyLogs>();
|
||||
var logList = new List<DailyLogs>();
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -61,6 +61,7 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="CardUniqueId.cs" />
|
||||
<Compile Include="GroupDb.cs" />
|
||||
<Compile Include="LogSourceDb.cs" />
|
||||
<Compile Include="SQLiteRepository.cs" />
|
||||
<Compile Include="Constants.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.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; }
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -44,16 +44,5 @@ namespace WindowsDataCenter
|
||||
Content = new StringContent(logId.ToString())
|
||||
});
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="log"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("manual")]
|
||||
public IHttpActionResult ManuallyPostData([FromBody] ManualLog log)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -151,6 +151,7 @@
|
||||
<Compile Include="Controllers\CardsController.cs" />
|
||||
<Compile Include="Configuration.cs" />
|
||||
<Compile Include="Controllers\GroupController.cs" />
|
||||
<Compile Include="Controllers\LogsController.cs" />
|
||||
<Compile Include="Controllers\TimelogController.cs" />
|
||||
<Compile Include="DefaultComponents\DefaultLogger.cs" />
|
||||
<Compile Include="Helpers\CacheControlAttribute.cs" />
|
||||
@ -199,6 +200,9 @@
|
||||
<Content Include="www\css\bootstrap.min.css">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="www\css\knockout.contextmenu.css">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="www\favicon.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@ -220,6 +224,9 @@
|
||||
<Content Include="www\js\bootstrap.min.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="www\js\knockout.contextmenu.js">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="www\spa.css">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
||||
@ -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<Object<string>} params - Key, Value object detailing the param name (key) and value (value).
|
||||
* @param {Array<Object<string>>} 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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -4,16 +4,20 @@
|
||||
<title>Flexi Time Data Viewer</title>
|
||||
<link rel="shortcut icon" href="favicon.ico" />
|
||||
|
||||
<link rel="stylesheet preload" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
|
||||
<link rel="stylesheet preload" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.6.1/css/bootstrap-datepicker3.min.css">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"/>
|
||||
<!--<link rel="stylesheet preload" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.6.1/css/bootstrap-datepicker3.min.css"/>-->
|
||||
<link href="spa.min.css" rel="stylesheet" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||
<link href="css/knockout.contextmenu.css" rel="stylesheet" type="text/css"/>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/css/bootstrap-datetimepicker.min.css" />
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.0.0/knockout-min.js" type="text/javascript"></script>
|
||||
<script src="js/knockout.contextmenu.js" type="text/javascript"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/sammy.js/0.7.6/sammy.js" type="text/javascript"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/momentjs/2.10.6/moment.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.6.1/js/bootstrap-datepicker.js"></script>
|
||||
<!--<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.6.1/js/bootstrap-datepicker.js"></script>-->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/js/bootstrap-datetimepicker.min.js"></script>
|
||||
</head>
|
||||
<body data-bind="css: {footerBody: errorData() !== null}">
|
||||
<nav class="navbar navbar-default">
|
||||
@ -246,9 +250,6 @@
|
||||
</div>
|
||||
<br/>
|
||||
<br/>
|
||||
<!--<div class="row">
|
||||
|
||||
</div>-->
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-xs-12">
|
||||
<div id="datePickerContainer">
|
||||
@ -259,7 +260,7 @@
|
||||
<!-- main content panel. -->
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<tr data-bind="contextMenu: $root.createContextMenu">
|
||||
<th>Day Of Week</th>
|
||||
<!-- ko foreach: new Array($root.getTimeLogEntryArrayLength(MaxDailyLogCount)) -->
|
||||
<th>In</th>
|
||||
@ -271,24 +272,30 @@
|
||||
<tbody>
|
||||
<!-- ko foreach: TimeLogs-->
|
||||
<tr>
|
||||
<td class="valign" data-bind="text: DayOfWeek"></td>
|
||||
<td class="valign" data-bind="text: DayOfWeek, contextMenu: $root.createContextMenu"></td>
|
||||
<!-- ko foreach: Logs -->
|
||||
<td class="valign" data-bind="text: $root.convertToDisplayTime(EventTime)"></td>
|
||||
<td class="valign" data-bind="text: $root.convertToDisplayTime(EventTime), contextMenu: $root.editContextMenu"></td>
|
||||
<!-- /ko -->
|
||||
<!-- ko foreach: new Array($root.correctLogOffset($parent.MaxDailyLogCount)-LogCount)-->
|
||||
<td class="valign"></td>
|
||||
<td class="valign" data-bind="contextMenu: $root.createContextMenu"></td>
|
||||
<!-- /ko -->
|
||||
<td class="valign"data-bind="text: $root.convertToHours(DailyTotal)"></td>
|
||||
<td class="valign" data-bind="text: $root.convertToHours(DailyTotal), contextMenu: $root.createContextMenu"></td>
|
||||
</tr>
|
||||
<!-- /ko -->
|
||||
<tr>
|
||||
<td class="valign" data-bind="attr:{colspan: $root.correctLogOffset(MaxDailyLogCount)+1}">Weekly Total</td>
|
||||
<td class="valign" for="dailyHrsTotal" data-bind="text: $root.convertToHours(WeeklyTotal)"></td>
|
||||
<td class="valign" data-bind="attr:{colspan: $root.correctLogOffset(MaxDailyLogCount)+1}, contextMenu: $root.createContextMenu">Weekly Total</td>
|
||||
<td class="valign" for="dailyHrsTotal" data-bind="text: $root.convertToHours(WeeklyTotal), contextMenu: $root.createContextMenu"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!--<menu type="context" id="manualLogsMenu">
|
||||
<menu label="ManuallyCreateLogs">
|
||||
<menuitem label="Edit" onclick="$root.editLogClick()"/>
|
||||
<menuitem label="Create" onlcick="$root.createLogClick()"/>
|
||||
</menu>
|
||||
</menu>-->
|
||||
</div>
|
||||
|
||||
<div id="aboutDialog" class="modal fade" role="dialog" data-bind="with: appDetails">
|
||||
@ -318,21 +325,55 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer data-bind="with: errorData, css: {footer: $root.errorData()!==null}">
|
||||
<div class="container">
|
||||
<!-- Alert/Error banner-->
|
||||
<div class="alert alert-danger alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close" data-bind="click: $root.dismissAlert"><span aria-hidden="true">×</span></button>
|
||||
<strong>Error!</strong>
|
||||
<br />
|
||||
<div data-bind="text: errorMessage"></div>
|
||||
<div data-bind="text: errorSource"></div>
|
||||
<div data-bind="text: errorDate"></div>
|
||||
<div id="manualLogDialog" class="modal fade" role="dialog" data-bind="with: manualLog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4>MANUAL EDIT</h4>
|
||||
</div>
|
||||
<div class="modal-body" style="height: 300px;">
|
||||
<form action="#manualLog" method="post" class="form-group">
|
||||
<input type="hidden" name="Id" data-bind="value: Id"/>
|
||||
<div class="form-group">
|
||||
<div class="input-group date" id="datetimepicker1">
|
||||
<input type="text" class="form-control" />
|
||||
<span class="input-group-addon">
|
||||
<span class="glyphicon glyphicon-calendar"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="input-group">
|
||||
<select data-bind="options: $root.possibleLogDirections,
|
||||
optionsText: function(item) { return item.Text },
|
||||
optionsValue: function(item){ return item.value },
|
||||
value: Direction,
|
||||
optionsCaption: 'Choose...'"></select>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
<button type="button" class="btn btn-secondary close" data-dismiss="modal">Cancel</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="spa.min.js" type="text/javascript"></script>
|
||||
</div>
|
||||
</div>
|
||||
<footer data-bind="with: errorData, css: {footer: $root.errorData()!==null}">
|
||||
<div class="container">
|
||||
<!-- Alert/Error banner-->
|
||||
<div class="alert alert-danger alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close" data-bind="click: $root.dismissAlert"><span aria-hidden="true">×</span></button>
|
||||
<strong>Error!</strong>
|
||||
<br />
|
||||
<div data-bind="text: errorMessage"></div>
|
||||
<div data-bind="text: errorSource"></div>
|
||||
<div data-bind="text: errorDate"></div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="Helpers.js" type="text/javascript"></script>
|
||||
<script src="spa.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,362 @@
|
||||
/* knockout.contextmenu v1.0.0
|
||||
Nicolás Escalante - nlante@gmail.com
|
||||
Issues: https://github.com/nescalante/knockout.contextmenu/issues
|
||||
License: MIT */
|
||||
|
||||
(function (undefined) {
|
||||
"use strict";
|
||||
|
||||
// client
|
||||
if (typeof ko !== undefined + "") {
|
||||
bindContextMenu(ko);
|
||||
}
|
||||
|
||||
// node
|
||||
if (typeof module !== undefined + "" && module.exports && typeof require !== undefined + "") {
|
||||
bindContextMenu(require("knockout"));
|
||||
}
|
||||
|
||||
function bindContextMenu(ko) {
|
||||
var currentMenu;
|
||||
var elementMapping = [];
|
||||
var utils = ko.utils;
|
||||
var registerEvent = utils.registerEventHandler;
|
||||
var isObservable = ko.isObservable;
|
||||
|
||||
registerEvent(document, "click", function (event) {
|
||||
var button = event.which || event.button;
|
||||
if (!event.defaultPrevented && button < 2) {
|
||||
hideCurrentMenu();
|
||||
}
|
||||
});
|
||||
|
||||
utils.contextMenu = {
|
||||
getMenuFor: function (element, event) {
|
||||
var result = getMapping(element);
|
||||
|
||||
if (result) {
|
||||
return result.get(event);
|
||||
}
|
||||
},
|
||||
|
||||
openMenuFor: function (element, event) {
|
||||
var result = getMapping(element);
|
||||
|
||||
if (result) {
|
||||
return result.open(event);
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
ko.bindingHandlers.contextMenu = {
|
||||
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
|
||||
var eventsToHandle = valueAccessor() || {};
|
||||
var allBindings = allBindingsAccessor();
|
||||
var defaultClass = allBindings.contextMenuClass || "context-menu";
|
||||
var activeElement;
|
||||
|
||||
// bind on click? bind on context click?
|
||||
if (allBindings.bindMenuOnClick) {
|
||||
registerEvent(element, "click", openMenu);
|
||||
}
|
||||
|
||||
if (allBindings.bindMenuOnContextMenu === undefined || allBindings.bindMenuOnContextMenu) {
|
||||
registerEvent(element, "contextmenu", openMenu);
|
||||
}
|
||||
|
||||
elementMapping.push({
|
||||
element: element,
|
||||
get: function () {
|
||||
return activeElement;
|
||||
},
|
||||
|
||||
open: openMenu,
|
||||
hide: function () {
|
||||
if (activeElement) {
|
||||
activeElement.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function mouseX(evt) {
|
||||
if (evt.pageX) {
|
||||
return evt.pageX;
|
||||
} else if (evt.clientX) {
|
||||
return evt.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function mouseY(evt) {
|
||||
if (evt.pageY) {
|
||||
return evt.pageY;
|
||||
} else if (evt.clientY) {
|
||||
return evt.clientY + (document.documentElement.scrollTop || document.body.scrollTop);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function openMenu(event) {
|
||||
activeElement = getMenu(event);
|
||||
var menuElement = activeElement.element;
|
||||
|
||||
hideCurrentMenu();
|
||||
|
||||
if (menuElement) {
|
||||
// make visibility hidden, then add to DOM so that we can get the height/width of the menu
|
||||
menuElement.style.visibility = "hidden";
|
||||
(document.body || document).appendChild(menuElement);
|
||||
|
||||
// set location
|
||||
if (event) {
|
||||
var bottomOfViewport = window.innerHeight + window.pageYOffset;
|
||||
var rightOfViewport = window.innerWidth + window.pageXOffset;
|
||||
|
||||
if (mouseY(event) + menuElement.offsetHeight > bottomOfViewport) {
|
||||
menuElement.style.top = 1 * (bottomOfViewport - menuElement.offsetHeight - 10) + "px";
|
||||
} else {
|
||||
menuElement.style.top = mouseY(event) + "px";
|
||||
}
|
||||
|
||||
if (mouseX(event) + menuElement.offsetWidth > rightOfViewport) {
|
||||
menuElement.style.left = 1 * (rightOfViewport - menuElement.offsetWidth - 10) + "px";
|
||||
} else {
|
||||
menuElement.style.left = mouseX(event) + "px";
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
} else {
|
||||
menuElement.style.top = (element.offsetTop + element.offsetHeight) + "px";
|
||||
menuElement.style.left = (element.offsetLeft + element.offsetWidth) + "px";
|
||||
}
|
||||
|
||||
// now set to visible
|
||||
menuElement.style.visibility = "";
|
||||
}
|
||||
|
||||
// replace current menu with the recently created
|
||||
currentMenu = menuElement;
|
||||
|
||||
return activeElement;
|
||||
}
|
||||
|
||||
function getMenu(event) {
|
||||
var menu;
|
||||
var hasChecks = false;
|
||||
var elements = [];
|
||||
var actions = [];
|
||||
var items = [];
|
||||
var props = Object.keys(
|
||||
ko.isObservable(eventsToHandle) ?
|
||||
eventsToHandle() :
|
||||
eventsToHandle
|
||||
);
|
||||
|
||||
props.forEach(function (eventNameOutsideClosure) {
|
||||
pushItem(eventNameOutsideClosure);
|
||||
});
|
||||
|
||||
if (elements.length) {
|
||||
menu = document.createElement("div");
|
||||
menu.className = defaultClass;
|
||||
|
||||
// you may need padding to menus that has checks
|
||||
menu.innerHTML = "<ul class=\"" + (hasChecks ? "has-checks" : "") + '">' +
|
||||
elements.join("") +
|
||||
"</ul>";
|
||||
|
||||
// map items to actions
|
||||
elements.forEach(function (item, index) {
|
||||
registerEvent(menu.children[0].children[index], "click", function (event) {
|
||||
var result = actions[index](viewModel, event);
|
||||
|
||||
if (!result && event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
element: menu,
|
||||
items: items,
|
||||
open: openMenu,
|
||||
hide: function () {
|
||||
if (menu && menu.parentNode) {
|
||||
menu.parentNode.removeChild(menu);
|
||||
}
|
||||
|
||||
currentMenu = null;
|
||||
},
|
||||
};
|
||||
|
||||
function pushItem(eventName) {
|
||||
var item = getMenuProperties(eventName);
|
||||
var classes = [];
|
||||
var id = "";
|
||||
var liHtml;
|
||||
|
||||
if (item.isVisible) {
|
||||
hasChecks = hasChecks || item.isBoolean;
|
||||
|
||||
if (item.id) {
|
||||
id = item.id;
|
||||
}
|
||||
|
||||
// set css classes
|
||||
if (item.isChecked) {
|
||||
classes.push("checked");
|
||||
}
|
||||
|
||||
if (item.isDisabled) {
|
||||
classes.push("disabled");
|
||||
}
|
||||
|
||||
if (item.isSeparator) {
|
||||
classes.push("separator");
|
||||
}
|
||||
|
||||
if (item.url) {
|
||||
classes.push("with-url");
|
||||
}
|
||||
|
||||
liHtml = "<li " + (id ? ('id="' + id + '" ') : "") +
|
||||
' class="' + classes.join(" ") + '">' +
|
||||
item.html +
|
||||
"</li>";
|
||||
|
||||
elements.push(liHtml);
|
||||
actions.push(item.action);
|
||||
}
|
||||
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
function getMenuProperties(eventName) {
|
||||
var text = "";
|
||||
var html = "";
|
||||
var currentEvent = ko.isObservable(eventsToHandle) ?
|
||||
eventsToHandle()[eventName] :
|
||||
eventsToHandle[eventName];
|
||||
var item = currentEvent || {};
|
||||
var id = item.id;
|
||||
var url = (isObservable(item.url) ? item.url() : item.url);
|
||||
var isVisible = item.visible === undefined || item.visible === null ||
|
||||
(isObservable(item.visible) && item.visible()) ||
|
||||
(!isObservable(item.visible) && !!item.visible);
|
||||
var isChecked = false;
|
||||
var isEnabled = !item.disabled ||
|
||||
(isObservable(item.disabled) && !item.disabled()) ||
|
||||
(isObservable(item.enabled) && item.enabled()) ||
|
||||
(!isObservable(item.enabled) && !!item.enabled);
|
||||
var isBoolean = false;
|
||||
var isDisabled = !isEnabled;
|
||||
var isSeparator = !!currentEvent.separator;
|
||||
|
||||
if (!isSeparator) {
|
||||
text = isObservable(item.text) ? item.text() : item.text;
|
||||
|
||||
if (!text) {
|
||||
text = eventName;
|
||||
}
|
||||
|
||||
if (url) {
|
||||
html = '<a href="' + url + '">' + text + "</a>";
|
||||
} else {
|
||||
html = text;
|
||||
}
|
||||
}
|
||||
|
||||
if ((isObservable(item) && typeof item() === "boolean") ||
|
||||
(isObservable(item.action) && typeof item.action() === "boolean")) {
|
||||
isBoolean = true;
|
||||
|
||||
if ((item.action && item.action()) ||
|
||||
(typeof item === "function" && item())) {
|
||||
isChecked = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
html: html,
|
||||
text: text,
|
||||
url: url,
|
||||
id: id,
|
||||
isVisible: isVisible,
|
||||
isChecked: isChecked,
|
||||
isEnabled: isEnabled,
|
||||
isDisabled: isDisabled,
|
||||
isBoolean: isBoolean,
|
||||
isSeparator: isSeparator,
|
||||
action: action
|
||||
};
|
||||
|
||||
function action(viewModel, event) {
|
||||
var error = eventName + " option must have an action or an url.";
|
||||
|
||||
if (isDisabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if option is a boolean
|
||||
if (isObservable(item) && typeof item() === "boolean") {
|
||||
item(!item());
|
||||
}
|
||||
|
||||
// is an object? well, lets check it properties
|
||||
else if (typeof item === "object") {
|
||||
// check if has an action or if its a separator
|
||||
if (!item.action && !url && !isSeparator) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// evaluate action
|
||||
else if (item.action) {
|
||||
if (isObservable(item.action) && typeof item.action() === "boolean") {
|
||||
item.action(!item.action());
|
||||
} else {
|
||||
item.action(viewModel, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// its not an observable, should be a function
|
||||
else if (typeof item === "function") {
|
||||
item(viewModel, event);
|
||||
}
|
||||
|
||||
// nothing to do with this
|
||||
else {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function hideCurrentMenu() {
|
||||
if (currentMenu && currentMenu.parentNode) {
|
||||
currentMenu.parentNode.removeChild(currentMenu);
|
||||
}
|
||||
|
||||
currentMenu = null;
|
||||
}
|
||||
|
||||
function getMapping(element) {
|
||||
var i = 0;
|
||||
|
||||
for (; i < elementMapping.length; i++) {
|
||||
if (elementMapping[i].element === element) {
|
||||
return elementMapping[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
@ -12,7 +12,7 @@
|
||||
}
|
||||
}
|
||||
.bootstrap-datetimepicker-widget tr:hover {
|
||||
background-color: #808080;
|
||||
background-color: #a9a9a9;
|
||||
}
|
||||
.datepicker tr.highlight {
|
||||
background: #eeeeee;
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
function DataVM() {
|
||||
"use strict";
|
||||
var self = this;
|
||||
self.helpers = new Helpers();
|
||||
self.menuOptions = ["Home"];
|
||||
self.possibleLogDirections = ko.observableArray([
|
||||
{ Text: "In", value: 1 },
|
||||
{ Text: "Out", value: 2 }
|
||||
]),
|
||||
self.chosenMenuItemId = ko.observable();
|
||||
self.appDetails = ko.observable(null);
|
||||
self.userList = ko.observable(null);
|
||||
@ -10,8 +15,10 @@
|
||||
self.userTimeLogData = ko.observable(null);
|
||||
self.unassignedCardData = ko.observable(null);
|
||||
self.chosenTimeLogUserId = -1;
|
||||
self.selectedTimeLogDate = ko.observable(null);
|
||||
self.selectedCalendarWeek = ko.observable(0);
|
||||
self.errorData = ko.observable(null);
|
||||
self.manualLog = ko.observable(null);
|
||||
self.apiEndpoints = {
|
||||
root: "http://localhost:8800",
|
||||
getUserList: "/api/users",
|
||||
@ -20,7 +27,9 @@
|
||||
getTimeLogs: "/api/timelogs",
|
||||
getUnassignedCards: "/api/cards/unassigned",
|
||||
getGroups: "/api/groups",
|
||||
getAppDetails: "/api/app"
|
||||
getAppDetails: "/api/app",
|
||||
manualLogsCreate: "/api/logs/create",
|
||||
manualLogsDelete: "/api/logs/delete"
|
||||
};
|
||||
self.uiPages = {
|
||||
users: "users",
|
||||
@ -39,7 +48,7 @@
|
||||
}
|
||||
var url = "timelogs" + "/" + userId;
|
||||
if (args) {
|
||||
url = self.createRequestUrl(url, args, false, false);
|
||||
url = self.helpers.createRequestUrl(url, args, false, false);
|
||||
}
|
||||
location.hash = url;
|
||||
};
|
||||
@ -52,85 +61,10 @@
|
||||
};
|
||||
self.errorData(errDat);
|
||||
}
|
||||
self.processRequestFailure = function (xmlHttpRequest, textStatus, errorThrown) {
|
||||
if (xmlHttpRequest.readyState === 4) {
|
||||
return {
|
||||
errorCode: xmlHttpRequest.status,
|
||||
errorMessage: xmlHttpRequest.statusText,
|
||||
errorSource: ""
|
||||
};
|
||||
}
|
||||
else if (xmlHttpRequest.readyState === 0) {
|
||||
return {
|
||||
errorCode: xmlHttpRequest.status,
|
||||
errorMessage: "Network Error - Is the server available?",
|
||||
errorSource: ""
|
||||
};
|
||||
}
|
||||
else {
|
||||
return {
|
||||
errorCode: xmlHttpRequest.status,
|
||||
errorMessage: "Unknown Error",
|
||||
errorSource: ""
|
||||
};
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Create a request URL - references apiEndpoints object to construct url with args, and optional callback url.
|
||||
* @param {string} routePath
|
||||
* @param {Array<Object<string>} 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
|
||||
* @example
|
||||
* createRequestUrl("/api/endpoint", [{key:"param", value:"value"}], true, false);
|
||||
* returns: "http://192.168.2.2/api/endpoint?param=value&callback=?"
|
||||
*/
|
||||
self.createRequestUrl = function (routePath, params, requiresCallback, isAbsoluteUrl) {
|
||||
var appender = "?";
|
||||
var url = "";
|
||||
if (isAbsoluteUrl) {
|
||||
url = self.apiEndpoints.root;
|
||||
}
|
||||
url = url + routePath;
|
||||
if (params !== undefined
|
||||
&& params !== null) {
|
||||
if (params.length > 0) {
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
url += appender + params[i].key + "=" + params[i].value;
|
||||
appender = "&";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (requiresCallback) {
|
||||
url += appender + "callback=?";
|
||||
}
|
||||
return url;
|
||||
};
|
||||
/**
|
||||
* Function to redirect to a page in the sammy.js eco system.
|
||||
* Relies on "pagedestination" tag in the html. This is a button click handler.
|
||||
* @param {Object<unknown>} data - dunno?
|
||||
* @param {Object<buttonhandle>} event - handle to the button that was clicked.
|
||||
* @returns {nothing} - redirects to the url referenced by the pageDestination tag.
|
||||
*/
|
||||
self.returnButtonClick = function (data, event) {
|
||||
var target = null;
|
||||
if (event.target) target = event.target;
|
||||
else if (event.srcElement) target = event.srcElement;
|
||||
var destination = "";
|
||||
if (target != null) {
|
||||
for (var i = 0; i < target.attributes.length; i++) {
|
||||
if (target.attributes[i].nodeName === "pagedestination") {
|
||||
destination = target.attributes[i].value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (destination !== "") {
|
||||
self.goToMenuOption(destination); //redirect to whereever the button is telling us to go..
|
||||
}
|
||||
} else {
|
||||
console.log("target is null, going nowhere");
|
||||
var destination = self.helpers.getPageDestination(data, event);
|
||||
if (destination !== "") {
|
||||
self.goToMenuOption(destination); //redirect to whereever the button is telling us to go..
|
||||
}
|
||||
};
|
||||
self.convertToHours = function (value) {
|
||||
@ -146,21 +80,21 @@
|
||||
var date = new Date(dateValue);
|
||||
return date.getHours() + ":" + self.padNumber(date.getMinutes());
|
||||
};
|
||||
self.correctLogOffset = function (logCount) {
|
||||
self.correctLogOffset = function(logCount) {
|
||||
if (logCount % 2 !== 0) {
|
||||
logCount += 1;
|
||||
}
|
||||
return logCount;
|
||||
}
|
||||
self.round = function (value, decimals) {
|
||||
};
|
||||
self.round = function(value, decimals) {
|
||||
return parseFloat(Math.round(value * 100) / 100).toFixed(decimals);
|
||||
}
|
||||
};
|
||||
self.getTimeLogEntryArrayLength = function(maxDailyLogs) {
|
||||
return Math.round(maxDailyLogs/2);
|
||||
};
|
||||
self.padNumber = function(number) {
|
||||
return (number < 10 ? '0' : '') + number;
|
||||
}
|
||||
return (number < 10 ? "0" : "") + number;
|
||||
};
|
||||
self.convertToDisplayDateTime = function (dateValue) {
|
||||
var date = new Date(dateValue); // dd MM YY HH:mm:ss e.g.: 01 Mar 17 17:34:02
|
||||
return date.getDate() + " "
|
||||
@ -215,7 +149,7 @@
|
||||
value: data
|
||||
}
|
||||
];
|
||||
var url = self.createRequestUrl("users", args, false, false);
|
||||
var url = self.helpers.createRequestUrl("users", args, false, false);
|
||||
location.hash = url;
|
||||
console.log(url);
|
||||
};
|
||||
@ -230,38 +164,53 @@
|
||||
value: pageNumber
|
||||
}
|
||||
];
|
||||
var url = self.createRequestUrl("users", args, false, false);
|
||||
var url = self.helpers.createRequestUrl("users", args, false, false);
|
||||
location.hash = url;
|
||||
console.log(url);
|
||||
};
|
||||
self.initDatePicker = function (selectedDate) {
|
||||
$("#weeklyDatePicker").datepicker({
|
||||
weekStart: 1,
|
||||
maxViewMode: 2,
|
||||
endDate: "+0d",
|
||||
todayBtn: "linked",
|
||||
format: "yyyy-mm-dd",
|
||||
todayHighlight: true,
|
||||
calendarWeeks: true
|
||||
});
|
||||
if (!selectedDate) {
|
||||
selectedDate = new Date();
|
||||
} else {
|
||||
selectedDate = new Date(selectedDate);
|
||||
}
|
||||
$("#weeklyDatePicker").datepicker("setDate", selectedDate);
|
||||
moment.locale("en", { week: { dow: 1 } });
|
||||
$("#weeklyDatePicker").datetimepicker({
|
||||
format: "DD/MM/YYYY",
|
||||
inline: true,
|
||||
showTodayButton: true,
|
||||
calendarWeeks: true,
|
||||
maxDate: "now",
|
||||
date: selectedDate
|
||||
});
|
||||
};
|
||||
self.assignHandler = function () {
|
||||
self.assignHandler = function() {
|
||||
var elem = $("#weeklyDatePicker")[0];
|
||||
var data = jQuery.hasData(elem) && jQuery._data(elem);
|
||||
if (!data.events.changeDate) {
|
||||
$("#weeklyDatePicker").on("changeDate", function (e) {
|
||||
var kk = e.date;
|
||||
self.selectedCalendarWeek(moment(kk).isoWeek());
|
||||
self.goToTimeLogs(self.chosenTimeLogUserId, null, [{ key: "selectedDate", value: moment(kk).format("MM-DD-YYYY") }]);
|
||||
});
|
||||
if (!data.events) {
|
||||
$("#weeklyDatePicker")
|
||||
.on("dp.change",
|
||||
function(e) {
|
||||
var value = e.date;
|
||||
self.selectedCalendarWeek(moment(value).isoWeek());
|
||||
self.goToTimeLogs(self.chosenTimeLogUserId,
|
||||
null,
|
||||
[{ key: "selectedDate", value: moment(value).format("MM-DD-YYYY") }]);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
self.assignUpdateHandler = function() {
|
||||
var elem = $("#datetimepicker1")[0];
|
||||
var data = jQuery.hasData(elem) && jQuery._data(elem);
|
||||
if (!data.events) {
|
||||
$("#datetimepicker1")
|
||||
.on("dp.change",
|
||||
function(e) {
|
||||
var value = e.date.toISOString();
|
||||
self.manualLog().EventTime = value;
|
||||
});
|
||||
}
|
||||
};
|
||||
self.getUserList = function (pageSize, pageNumber, groupId) {
|
||||
var args = null;
|
||||
if (pageSize && pageNumber) {
|
||||
@ -282,52 +231,52 @@
|
||||
value: groupId
|
||||
}];
|
||||
}
|
||||
var url = self.createRequestUrl(self.apiEndpoints.getUserList, args, false);
|
||||
var url = self.helpers.createRequestUrl(self.apiEndpoints.getUserList, args, false);
|
||||
$.getJSON(url, function (res) {
|
||||
self.userList(res);
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
}).fail(function (response, status, error) {
|
||||
console.log("error - getusers");
|
||||
var errObj = self.processRequestFailure(response, status, error);
|
||||
var errObj = self.helpers.processRequestFailure(response, status, error);
|
||||
self.assignErrorObject(errObj.errorCode, errObj.errorMessage, "getUserList");
|
||||
});
|
||||
};
|
||||
self.getAppDetails = function() {
|
||||
var url = self.createRequestUrl(self.apiEndpoints.getAppDetails, null, false, false);
|
||||
var url = self.helpers.createRequestUrl(self.apiEndpoints.getAppDetails, null, false, false);
|
||||
$.getJSON(url, function (res) {
|
||||
self.appDetails(res);
|
||||
}).fail(function (response, status, error) {
|
||||
console.log("error - getusers");
|
||||
var errObj = self.processRequestFailure(response, status, error);
|
||||
var errObj = self.helpers.processRequestFailure(response, status, error);
|
||||
self.assignErrorObject(errObj.errorCode, errObj.errorMessage, "getUserList");
|
||||
});
|
||||
};
|
||||
self.searchUsers = function(query) {
|
||||
var url = self.createRequestUrl(self.apiEndpoints.getUserList,
|
||||
var url = self.helpers.createRequestUrl(self.apiEndpoints.getUserList,
|
||||
[{ key: "query", value: query }], false, false);
|
||||
$.getJSON(url,
|
||||
function(res) {
|
||||
self.userList(res);
|
||||
}).fail(function(resp, status, error) {
|
||||
self.goToMenuOption(self.uiPages.home());
|
||||
var errObj = self.processRequestFailure(resp, status, error);
|
||||
var errObj = self.helpers.processRequestFailure(resp, status, error);
|
||||
self.assignErrorObject(errObj.errorCode, errObj.errorMessage, "searchUsers");
|
||||
}
|
||||
);
|
||||
};
|
||||
self.getUserDetails = function (userId) {
|
||||
var url = self.createRequestUrl(self.apiEndpoints.getUserDetails + "/" + userId, null, false);
|
||||
var url = self.helpers.createRequestUrl(self.apiEndpoints.getUserDetails + "/" + userId, null, false);
|
||||
$.getJSON(url, function (res) {
|
||||
self.chosenUserDetails(res);
|
||||
}).fail(function (resp, status, error) {
|
||||
console.log("error - getuserdetails");
|
||||
var errObj = self.processRequestFailure(resp, status, error);
|
||||
var errObj = self.helpers.processRequestFailure(resp, status, error);
|
||||
self.assignErrorObject(errObj.errorCode, errObj.errorMessage, "getUserDetails");
|
||||
self.goToMenuOption(self.uiPages.home());
|
||||
});
|
||||
};
|
||||
self.submitChangedUser = function (user) {
|
||||
var url = self.apiEndpoints.editUser;
|
||||
var url = self.helpers.createRequestUrl(self.apiEndpoints.editUser, null, false, false);
|
||||
$.post(url, user, function () {
|
||||
}, "json")
|
||||
.done(function () {
|
||||
@ -335,7 +284,7 @@
|
||||
self.goToMenuOption(self.uiPages.home());
|
||||
})
|
||||
.fail(function (resp, status, error) {
|
||||
var errObj = self.processRequestFailure(resp, status, error);
|
||||
var errObj = self.helpers.processRequestFailure(resp, status, error);
|
||||
self.assignErrorObject(errObj.errorCode, errObj.errorMessage, "submitChangedUser");
|
||||
self.chosenUserDetails(null);
|
||||
self.goToMenuOption(self.uiPages.home());
|
||||
@ -346,7 +295,7 @@
|
||||
if (selectedDate) {
|
||||
urlArgs.push({ key: "selectedDate", value: selectedDate });
|
||||
}
|
||||
var url = self.createRequestUrl(self.apiEndpoints.getTimeLogs,
|
||||
var url = self.helpers.createRequestUrl(self.apiEndpoints.getTimeLogs,
|
||||
urlArgs,
|
||||
false);
|
||||
$.getJSON(url, function (res) {
|
||||
@ -355,33 +304,103 @@
|
||||
self.assignHandler();
|
||||
}).fail(function (resp, status, error) {
|
||||
console.log("error - getuserdetails");
|
||||
var errObj = self.processRequestFailure(resp, status, error);
|
||||
var errObj = self.helpers.processRequestFailure(resp, status, error);
|
||||
self.assignErrorObject(errObj.errorCode, errObj.errorMessage, "getTimeLogData");
|
||||
self.goToMenuOption(self.uiPages.home()); //go home.
|
||||
});
|
||||
};
|
||||
self.getUnassignedCardData = function () {
|
||||
var url = self.createRequestUrl(self.apiEndpoints.getUnassignedCards, null, false);
|
||||
var url = self.helpers.createRequestUrl(self.apiEndpoints.getUnassignedCards, null, false);
|
||||
$.getJSON(url, function (res) {
|
||||
self.unassignedCardData(res);
|
||||
}).fail(function (resp, status, error) {
|
||||
console.log("error - getuserdetails");
|
||||
var errObj = self.processRequestFailure(resp, status, error);
|
||||
var errObj = self.helpers.processRequestFailure(resp, status, error);
|
||||
self.assignErrorObject(errObj.errorCode, errObj.errorMessage, "getUnassignedCardData");
|
||||
});
|
||||
};
|
||||
self.getGroups = function (successFunc) {
|
||||
var url = self.createRequestUrl(self.apiEndpoints.getGroups, null, false);
|
||||
var url = self.helpers.createRequestUrl(self.apiEndpoints.getGroups, null, false);
|
||||
return $.getJSON(url, function (res) {
|
||||
successFunc(res);
|
||||
//self.chosenUserDetails().Groups = res;
|
||||
//self.chosenUserDetails.valueHasMutated();
|
||||
}).fail(function (resp, status, error) {
|
||||
console.log("error - getGroups");
|
||||
var errObj = self.processRequestFailure(resp, status, error);
|
||||
var errObj = self.helpers.processRequestFailure(resp, status, error);
|
||||
self.assignErrorObject(errObj.errorCode, errObj.errorMessage, "getGroups");
|
||||
});
|
||||
};
|
||||
self.createManualLog = function(newLog) {
|
||||
var url = self.helpers.createRequestUrl(self.apiEndpoints.manualLogsCreate, null, false, false);
|
||||
$.post(url, newLog, function () {
|
||||
}, "json")
|
||||
.done(function () {
|
||||
self.manualLog(null);
|
||||
$('#manualLogDialog').modal("hide");
|
||||
self.goToMenuOption(self.uiPages.home());
|
||||
})
|
||||
.fail(function (resp, status, error) {
|
||||
var errObj = self.helpers.processRequestFailure(resp, status, error);
|
||||
self.assignErrorObject(errObj.errorCode, errObj.errorMessage, "createManualLog");
|
||||
self.chosenUserDetails(null);
|
||||
self.goToMenuOption(self.uiPages.home());
|
||||
});
|
||||
};
|
||||
self.deleteManualLog = function (logToDelete) {
|
||||
var url = self.helpers.createRequestUrl(self.apiEndpoints.manualLogsDelete, null, false, false);
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: "DELETE",
|
||||
data: logToDelete,
|
||||
success: function (result) {
|
||||
console.log("successfully deleted .." + result);
|
||||
self.goToMenuOption(self.uiPages.home());
|
||||
}
|
||||
});
|
||||
};
|
||||
self.createContextMenu = ko.observableArray([
|
||||
{ text: "Create", action: createlog }
|
||||
]);
|
||||
self.editContextMenu = ko.observableArray([
|
||||
{ text: "Create", action: createlog },
|
||||
{ text: "Edit", action: editlog },
|
||||
{ text: "Delete", action: deleteLog }
|
||||
]);
|
||||
function editlog (data) {
|
||||
self.manualLog(data);
|
||||
$('#manualLogDialog').modal("show");
|
||||
$('#datetimepicker1').datetimepicker({
|
||||
format: "YYYY-DD-MM HH:mm:ss",
|
||||
date: new Date(data.EventTime),
|
||||
minDate: moment(new Date(data.EventTime)).startOf('week'),
|
||||
maxDate: moment(new Date(data.EventTime)).endOf('week')
|
||||
});
|
||||
self.assignUpdateHandler();
|
||||
};
|
||||
function createlog(data) {
|
||||
self.manualLog({
|
||||
CalendarWeek:-1,
|
||||
Direction:-1,
|
||||
EventTime: new Date().toISOString(),
|
||||
Id: -1,
|
||||
IdentifierId: -1,
|
||||
UserId: self.chosenTimeLogUserId,
|
||||
Year: 0
|
||||
});
|
||||
$('#manualLogDialog').modal("show");
|
||||
$('#datetimepicker1').datetimepicker({
|
||||
format: "YYYY-DD-MM HH:mm:ss",
|
||||
minDate: moment(self.selectedTimeLogDate()).startOf("week"),
|
||||
maxDate: moment(self.selectedTimeLogDate()).endOf("week")
|
||||
});
|
||||
self.assignUpdateHandler();
|
||||
};
|
||||
function deleteLog(data) {
|
||||
if (confirm("Are you sure you want to delete this log?")) {
|
||||
self.deleteManualLog(data);
|
||||
}
|
||||
};
|
||||
Sammy(function () {
|
||||
this.get("#users", function () {
|
||||
var query = this.params.query;
|
||||
@ -389,9 +408,11 @@
|
||||
var pageNumber = this.params.pageNumber;
|
||||
var groupId = this.params.groupId;
|
||||
self.chosenMenuItemId("Home");
|
||||
self.groupsList(null);
|
||||
self.chosenUserDetails(null);
|
||||
self.userList(null);
|
||||
self.userTimeLogData(null);
|
||||
self.manualLog(null);
|
||||
if (self.appDetails() === null) {
|
||||
self.getAppDetails();
|
||||
}
|
||||
@ -405,18 +426,28 @@
|
||||
});
|
||||
this.get("#userData/:userId", function () {
|
||||
self.chosenMenuItemId("Data");
|
||||
self.groupsList(null);
|
||||
self.chosenUserDetails(null);
|
||||
self.userList(null);
|
||||
self.getUserDetails(this.params.userId);
|
||||
self.userTimeLogData(null);
|
||||
self.manualLog(null);
|
||||
self.getUserDetails(this.params.userId);
|
||||
self.getUnassignedCardData();
|
||||
});
|
||||
this.get("#timelogs/:userId", function () {
|
||||
var selectedDate = this.params.selectedDate;
|
||||
self.chosenMenuItemId("Other");
|
||||
self.userList(null);
|
||||
self.chosenUserDetails(null);
|
||||
if (this.params.selectedDate) {
|
||||
self.selectedTimeLogDate(this.params.selectedDate);
|
||||
} else {
|
||||
self.selectedTimeLogDate(new Date());
|
||||
}
|
||||
self.chosenTimeLogUserId = this.params.userId;
|
||||
self.getTimeLogData(this.params.userId, selectedDate);
|
||||
self.chosenMenuItemId("Other");
|
||||
self.groupsList(null);
|
||||
self.chosenUserDetails(null);
|
||||
self.userList(null);
|
||||
self.userTimeLogData(null);
|
||||
self.manualLog(null);
|
||||
self.getTimeLogData(this.params.userId, self.selectedTimeLogDate());
|
||||
});
|
||||
this.get("#newUser", function () {
|
||||
self.chosenMenuItemId("newUser");
|
||||
@ -455,14 +486,21 @@
|
||||
self.submitChangedUser(self.chosenUserDetails());
|
||||
return false;
|
||||
});
|
||||
this.post("#manualLog",
|
||||
function() {
|
||||
self.createManualLog(self.manualLog());
|
||||
|
||||
$('#manualLogDialog').modal("hide");
|
||||
//self.goToTimeLogs(self.chosenTimeLogUserId, null, [{ key: "selectedDate", value: self.selectedTimeLogDate() }]);
|
||||
});
|
||||
//default route (home page)
|
||||
this.get("", function () { this.app.runRoute("get", "#" + self.uiPages.home()) });
|
||||
}).run();
|
||||
};
|
||||
ko.applyBindings(new DataVM());
|
||||
$(document).on("mouseenter", ".datepicker-days tbody tr", function () {
|
||||
$(this).addClass('highlight');
|
||||
});
|
||||
$(document).on("mouseleave", ".datepicker-days tbody tr", function () {
|
||||
$(this).removeClass('highlight');
|
||||
});
|
||||
//$(document).on("mouseenter", ".datepicker-days tbody tr", function () {
|
||||
// $(this).addClass("highlight");
|
||||
//});
|
||||
//$(document).on("mouseleave", ".datepicker-days tbody tr", function () {
|
||||
// $(this).removeClass("highlight");
|
||||
//});
|
||||
Loading…
Reference in New Issue
Block a user