<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>TimeTrack</title>
<script>
"use strict";

var ROOT_ENDPOINT="/timetrack2";

var lastlist=null;

function fetch(method,url,data/*?*/,creds/*?*/,cb){
	if(!creds){
		cb=data;
		data=undefined;
		creds=undefined;
	} else if(!cb){
		cb=creds;
		creds=undefined;
	}
	if(!cb)throw new Error("No callback passed to fetch");
	var xhr=new XMLHttpRequest();
	xhr.onreadystatechange=function(ev){
		if(xhr.readyState<4)return;
		cb(xhr.status,xhr.responseText);
	};
	if(creds){
		xhr.open(method,url,true,creds[0],creds[1]);
	} else {
		xhr.open(method,url);
	}
	xhr.send(data);
}

function pad(s,n,c){
	if(c==null)c=" ";
	else c=c[0];
	s=s+"";
	while(s.length<n)s=c+s;
	return s;
}

function toinputdate(date) {
	return date.getFullYear() + "-" +
			pad(date.getMonth()+1,2,"0") + "-" +
			pad(date.getDate(),2,"0") + " " +
			pad(date.getHours(),2,"0") + ":" +
			pad(date.getMinutes(),2,"0") + ":" +
			pad(date.getSeconds(),2,"0");
}

function daystart(date){
	return new Date(date.getFullYear(), date.getMonth(), date.getDate());
}

function shiftdays(date, off) {
	var d = new Date(date);
	d.setDate(d.getDate() + off);
	return d;
}

function weekstart(date){
	return shiftdays(daystart(date), -(date.getDay() + 6) % 7);
}

var monthnames=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
var weekdays=["Mon","Tue","Wed","Thu","Fri","Sat","Sun"];

function formatdate(date) {
	return weekdays[(date.getDay() + 6) % 7] + ", " + toinputdate(date);
}

function formatdaterange(d1, d2) {
	var s1 = formatdate(d1);
	if (daystart(d1).getTime() == daystart(d2).getTime()) {
		return formatdate(d1) + " - " +
			pad(d2.getHours(), 2, "0") + ":" +
			pad(d2.getMinutes(), 2, "0") + ":" +
			pad(d2.getSeconds(), 2, "0")
	} else {
		return formatdate(d1) + " - " + formatdate(d2);
	}
}

function tablerowfor(ev){
	var div=document.createElement("div");
	div.classList.add("event");

	var e,e2;

	e=document.createElement("span");
	e.classList.add("eventsheet");
	e.appendChild(document.createTextNode(ev.sheet));
	div.appendChild(e);

	if (ev.text != "") {
		e=document.createElement("span");
		e.classList.add("eventtext");
		e.appendChild(document.createTextNode("(" + ev.text + ")"));
		div.appendChild(e);
	}

	var float=document.createElement("div");
	float.classList.add("eventfloat");

		e=document.createElement("span");
		e.classList.add("eventdate");
		e.appendChild(document.createTextNode(formatdaterange(ev.indate,ev.outdate)));
		e.setAttribute("title",ev.indate.toString() + " - " + ev.outdate.toString());
		float.appendChild(e);

		e=document.createElement("div");
		e.classList.add("eventbuttons");
		e2=document.createElement("span");
			e2.classList.add("eventdelete");
			e2.appendChild(document.createTextNode("X"));
			e2.addEventListener("click",function(){
				if(!confirm("Really delete  event \""+ev.sheet+"\" (\""+ev.text+"\") at "+formatdate(ev.indate)+"?"))return;
				fetch("DELETE",ROOT_ENDPOINT+"/event",ev.id,function(status,body){
					if(status==200)getlist(false);
					else alert("Delete failed: "+body);
				});
			});
		e.appendChild(e2);
		float.appendChild(e);

	div.appendChild(float);

	return div;
}

function refreshlist(list){
	var listelem = document.getElementById("eventlist");
	listelem.innerHTML = "";

	if (list.length == 0) {
		var div = document.createElement("div");
		div.classList.add("noevents");
		div.appendChild(document.createTextNode("No events yet"));
		listelem.appendChild(div);
		return;
	}

	var maxshown = 40;
	if (list.length > maxshown) {
		var div = document.createElement("div");
		div.classList.add("moreevents");
		div.appendChild(document.createTextNode("... more events ..."));
		listelem.appendChild(div);
	}
	for (var i = Math.max(0, list.length - maxshown); i < list.length; i++) {
		listelem.appendChild(tablerowfor(list[i]));
	}
}

function refreshcalendar(list,npreweeks){
	if (npreweeks == undefined) npreweeks = 2;

	var dayTargetH = 38/5 - 1;

	var weekTotals = new Map();  // only in weeks that have entries
	var totalTime = 0;

	function datekey(d) { return d.getFullYear() + "-" + d.getMonth() + "-" + d.getDate(); }
	var hist = new Map();
	for (var i = 0; i < list.length; i++) {
		var key = datekey(list[i].indate);
		var wkey = datekey(weekstart(list[i].indate));

		var thistime = list[i].outdate - list[i].indate;
		var yet = hist.has(key) ? hist.get(key) : 0;
		hist.set(key, yet + thistime);

		var weekyet = weekTotals.has(wkey) ? weekTotals.get(wkey) : 0;
		weekTotals.set(wkey, weekyet + thistime);

		totalTime += thistime;
	}

	var totalTargetH = 5 * weekTotals.size * dayTargetH;
	var totalSurplus = totalTime - totalTargetH * 3600 * 1000;

	var tb = document.getElementById("calendartb");
	tb.innerHTML = "";

	var tr = document.createElement("tr");
	tr.appendChild(document.createElement("td"));
	var td;
	for (var i = 0; i < 7; i++) {
		td = document.createElement("td");
		td.appendChild(document.createTextNode(weekdays[i]));
		tr.appendChild(td);
	}
	td = document.createElement("td");
	var a = document.createElement("a");
	a.href = "javascript:refreshcalendar(lastlist," + (npreweeks + 4) + ")";
	a.appendChild(document.createTextNode("\u2191"));
	td.appendChild(a);
	tr.appendChild(td);
	tb.appendChild(tr);

	var now = new Date();
	var today = daystart(now), thismonday = weekstart(now);
	for (var wkoff = -npreweeks; wkoff <= 1; wkoff++) {
		var monday = shiftdays(thismonday, wkoff * 7);

		tr = document.createElement("tr");
		td = document.createElement("td");
		var label = monday.getDate().toString();
		if (monday.getDate() <= 7 || wkoff == -npreweeks) {
			label = monthnames[monday.getMonth()].slice(0, 3) + " " + label;
		}
		td.appendChild(document.createTextNode(label));
		tr.appendChild(td);

		for (var i = 0; i < 7; i++) {
			var day = shiftdays(thismonday, wkoff * 7 + i);
			var tm = hist.get(datekey(day)) || 0;
			var descr = Math.round(tm/1000/3600*10)/10 + "h";
			var target = (day.getDay()+6)%7 < 5 ? 38/5 - 1 : 0;
			var diff = tm - target*3600*1000;
			var surplus = 1 - Math.exp(-(diff>0?diff:0)/(3*3600*1000));
			var deficit = 1 - Math.exp(-(diff<0?-diff:0)/(3*3600*1000));
			td = document.createElement("td");
			td.appendChild(document.createTextNode(descr));
			td.setAttribute("style",
				"background-color:rgb(" +
				(255 - 255*surplus) + "," +
				(255 - 255*(deficit+surplus)) + "," +
				(255 - 255*deficit) + ")"
			);
			if (day.getTime() == today.getTime()) td.classList.add("today");
			tr.appendChild(td);
		}

		if (wkoff == -1) {
			// "prev" refers to "all weeks before this week"
			var thisWeekTotal = weekTotals.get(datekey(thismonday)) || 0;
			var prevTotal = totalTime - thisWeekTotal;
				var numPrevWeeks = weekTotals.size - (thisWeekTotal > 0);
			var prevTarget = numPrevWeeks * 5 * dayTargetH;
			var prevSurplus = prevTotal - prevTarget*3600*1000;
			var descr = Math.round(prevSurplus/1000/3600*10)/10 + "h";

			td = document.createElement("td");
			var span = document.createElement("span");
			span.setAttribute("style", "display:inline-block;width: max-content;");
			span.appendChild(document.createTextNode("Account: " + descr));
			td.appendChild(span);
			tr.appendChild(td);
		}

		tb.appendChild(tr);
	}
}

function handleReceivedList(list){
	lastlist = list;
	refreshlist(list);
	refreshcalendar(list);
}

function updateCurrentBox(current) {
	var sheet = current ? current.sheet : "";
	var text = current ? current.text : "";
	var indate = current ? toinputdate(current.indate) : "";

	var e = document.getElementById("currentsheet");
	e.innerHTML = "";
	e.appendChild(document.createTextNode(sheet));
	e = document.getElementById("currenttext");
	e.innerHTML = "";
	e.appendChild(document.createTextNode(text));
	e = document.getElementById("currentindate");
	e.innerHTML = "";
	e.appendChild(document.createTextNode(indate));

	var box = document.getElementById("currentbox");
	var inputs = box.getElementsByTagName("input");
	if (current == null) {
		for (var i = 0; i < inputs.length; i++) inputs[i].setAttribute("disabled", "");
		box.classList.add("disabled");
		box.classList.remove("enabled");
	} else {
		for (var i = 0; i < inputs.length; i++) inputs[i].removeAttribute("disabled");
		box.classList.add("enabled");
		box.classList.remove("disabled");
	}
}

function getlist(isinitial){
	fetch("GET", ROOT_ENDPOINT + "/list", function(status, body) {
		if (status != 200) {
			alert("Error: "+body);
			return;
		}
		var obj;
		try {
			obj = JSON.parse(body);
		} catch(e){
			alert("An error occurred!");
			return;
		}
		var list = obj.list, current = obj.current;
		for (var i = 0; i < list.length; i++) {
			list[i].indate = new Date(list[i].indate);
			list[i].outdate = new Date(list[i].outdate);
		}
		if (current != null) current.indate = new Date(current.indate);
		handleReceivedList(list);
		updateCurrentBox(current);
		if (isinitial) document.getElementById("checkinbox").scrollIntoView();
		dateToNow("checkindate");
		dateToNow("checkoutdate");
	});
}

function getsheets() {
	fetch("GET",ROOT_ENDPOINT+"/sheets",function(status,body){
		if(status!=200){
			// alert("Error: "+body);
			return;
		}
		var sheets;
		try {
			sheets=JSON.parse(body);
		} catch(e){
			// alert("An error occurred!");
			return;
		}

		var el=document.getElementById("sheetselect");
		el.innerHTML = "<option value=\"\" selected></option>";

		for (var i = 0; i < sheets.length; i++) {
			var opt = document.createElement("option");
			opt.value = sheets[i];
			opt.innerHTML = sheets[i];
			el.appendChild(opt);
		}
	});
}

function doCheckin(atdate) {
	if (atdate) atdate = new Date(document.getElementById("checkindate").value);
	else atdate = new Date();
	var sheet=document.getElementById("checkinsheet").value;
	var text=document.getElementById("checkintext").value;
	fetch("POST", ROOT_ENDPOINT + "/checkin", JSON.stringify({
		sheet: sheet,
		text: text,
		date: atdate
	}), function(status, body) {
		if (status != 200) {
			alert("Error performing check-in: " + body);
			return;
		}
		getlist(false);
	});
}

function doCheckout(atdate) {
	if (atdate) atdate = new Date(document.getElementById("checkoutdate").value);
	else atdate = new Date();
	fetch("POST", ROOT_ENDPOINT + "/checkout", JSON.stringify({
		date: atdate
	}), function(status, body) {
		if (status != 200) {
			alert("Error performing check-out: " + body);
			return;
		}
		getlist(false);
	});
}

function setSheetFromSelect() {
	document.getElementById("checkinsheet").value =
		document.getElementById("sheetselect").value;
}

function logoutReload(){
	fetch("GET", ROOT_ENDPOINT + "/authfail", undefined, ["x", "x"], function(status, body) {
		location.href = location.href;
	});
}

function dateToNow(textboxid) {
	document.getElementById(textboxid).value = toinputdate(new Date());
}

window.addEventListener("load", function() {
	getlist(true);
	getsheets();
	dateToNow("checkindate");
	dateToNow("checkoutdate");
});
</script>
<style>
body{
	font-family:Georgia,Times,serif;
	font-size:14px;
}
.event{
	border:1px #ddd solid;
	border-bottom-width:0px;
	padding:0px 9px;
	background-color:#f8f8f8;
	width:500px;
}
.event:last-child{
	border-bottom-width:1px;
}
.eventsheet{
	font-size:20px;
	margin-left:10px;
}
.eventtext{
	font-size:15px;
	margin-left:10px;
	width:150px;
	display:inline-block;
}
.eventfloat{
	float:right;
	text-align:right;
}
.eventdate{
	font-size:12px;
	font-style:italic;
	display:inline-block;
	margin-top:6px;
}
.eventbuttons{
	display:inline-block;
	margin-left:60px;
	font-size:10px;
	vertical-align:middle;
	width:10px;
	text-align:center;
}
.eventdelete{
	margin-bottom:5px;
	font-size:10px;
	font-family:sans-serif;
	color:red;
	cursor:pointer;
}
#currentbox.enabled{
	background-color: #dfd;
}
#currentbox.disabled{
	background-color: #eee;
}
#currentsheet{
	font-weight: bold;
}
#checkinbox, #currentbox{
	border:1px #ddd solid;
	display:inline-block;
	padding:5px;
}
#checkinbox > input[type="text"], #currentbox > input[type="text"] {
	margin-bottom: 5px;
}
#logoutwrapper{
	float:right;
}
#calendartb td{
	width:35px;
}
#calendartb td:first-child{
	text-align:right;
	width:50px;
}
#calendartb td.today{
	border:1px black solid;
	font-weight:bold;
}
</style>
</head>
<body>
<div id="logoutwrapper">
	<input type="button" onclick="logoutReload();" value="Logout">
</div>
<h1>TimeTrack</h1>
<div id="eventlist"></div>
<br><br>
<div id="currentbox">
	Checked into: <span id="currentsheet"></span> (<span id="currenttext"></span>)<br>
	Check-in at: <span id="currentindate"></span><br><br>

	<input type="button" onclick="doCheckout(false)" value="Check out now"><br>
	Or: <input type="datetime" id="checkoutdate" placeholder="YYYY-MM-DD HH:MM:SS" size="25">
	<input type="button" onclick="dateToNow('checkoutdate')" value="now"><br>
	<input type="button" onclick="doCheckout(true)" value="Check out at date">
</div>
<br><br>
<div id="checkinbox">
	Sheet: <input type="text" id="checkinsheet" placeholder="Sheet">
	<select id="sheetselect" onchange="setSheetFromSelect()"></select> <br>
	Text: <input type="text" id="checkintext" placeholder="Text"> (optional) <br>

	<input type="button" onclick="doCheckin(false)" value="Check in now"><br>
	Or: <input type="datetime" id="checkindate" placeholder="YYYY-MM-DD HH:MM:SS" size="25">
	<input type="button" onclick="dateToNow('checkindate')" value="now"><br>
	<input type="button" onclick="doCheckin(true)" value="Check in at date">
</div>
<br><br>
<div id="calendar">
	<table><tbody id="calendartb"></tbody></table>
</div>
</body>
</html>