diff options
author | Tom Smeding <tom@tomsmeding.com> | 2022-02-03 10:13:46 +0100 |
---|---|---|
committer | Tom Smeding <tom@tomsmeding.com> | 2022-02-03 10:13:46 +0100 |
commit | d601d0431d6a35ce55680af9987c2ecfee719870 (patch) | |
tree | 8698dc4cbf764f89bf3cdab6eaa71f03df8e5b5a /modules/timetrack/timetrack.html | |
parent | 38b2f19f563d560be208b0c8f53bc7b6630c0436 (diff) |
timetrack: Initial
Diffstat (limited to 'modules/timetrack/timetrack.html')
-rw-r--r-- | modules/timetrack/timetrack.html | 314 |
1 files changed, 314 insertions, 0 deletions
diff --git a/modules/timetrack/timetrack.html b/modules/timetrack/timetrack.html new file mode 100644 index 0000000..c3e92dd --- /dev/null +++ b/modules/timetrack/timetrack.html @@ -0,0 +1,314 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>TimeTrack</title> +<script> +"use strict"; + +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"); +} + +var weekdays=["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]; + +function formatdate(date) { + return weekdays[(date.getDay() + 6) % 7] + ", " + toinputdate(date); +} + +function tablerowfor(ev){ + var div=document.createElement("div"); + div.classList.add("event"); + + var e,e2; + + e=document.createElement("span"); + e.classList.add("eventtype"); + e.classList.add("eventtype"+ev.type); + if (ev.type == "in") e.innerHTML = "→in" + else if (ev.type == "out") e.innerHTML = "←out" + div.appendChild(e); + + 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(formatdate(ev.date))); + e.setAttribute("title",ev.date.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 check-"+ev.type+" event \""+ev.sheet+"\" (\""+ev.text+"\") at "+formatdate(ev.date)+"?"))return; + fetch("DELETE","/timetrack/event",ev.id,function(status,body){ + if(status==200)getlist(); + 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=""; + var rows=[]; + var i,ev; + var now=new Date(); + for(i=0;i<list.length;i++){ + ev=list[i]; + rows.push([ev.date,tablerowfor(ev)]); + } + if(rows.length==0){ + var div=document.createElement("div"); + div.classList.add("noevents"); + div.appendChild(document.createTextNode("No events yet")); + listelem.appendChild(div); + return; + } + rows.sort(function(a,b){return a[0]-b[0];}); //ascending sort on the dates + for(i=0;i<rows.length;i++){ + listelem.appendChild(rows[i][1]); + } +} + +function handleReceivedList(list){ + var i; + for(i=0;i<list.length;i++)list[i].date=new Date(list[i].date); + refreshlist(list); +} + +function getlist(){ + fetch("GET","/timetrack/list",function(status,body){ + if(status!=200){ + alert("Error: "+body); + return; + } + var list; + try { + list=JSON.parse(body); + } catch(e){ + alert("An error occurred!"); + return; + } + handleReceivedList(list); + }); +} + +function getsheets() { + fetch("GET","/timetrack/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 doAddEvent(ev){ + var sheet=document.getElementById("addeventsheet").value; + var text=document.getElementById("addeventtext").value; + var date=document.getElementById("addeventdate").value; + var type=document.getElementById("addeventtypein").checked?"in":"out"; + if(typeof date=="string")date=new Date(date); + fetch("POST","/timetrack/event",JSON.stringify({ + sheet:sheet, + text:text, + date:date, + type:type + }),function(status,body){ + if(status==200){ + location.href=location.href; + return; + } + alert("Error while adding: "+body); + }); +} + +function setSheetFromSelect() { + document.getElementById("addeventsheet").value = document.getElementById("sheetselect").value; +} + +function logoutReload(){ + fetch("GET","/timetrack/authfail",undefined,["x","x"],function(status,body){ + location.href=location.href; + }); +} + +function dateToNow() { + document.getElementById("addeventdate").value = toinputdate(new Date()); +} + +window.addEventListener("load",function() { + getlist(); + getsheets(); + dateToNow(); +}); +</script> +<style> +body{ + font-family:Georgia,Times,serif; + font-size:14px; +} +.event{ + border:1px #ddd solid; + border-bottom-width:0px; + padding:9px; + background-color:#f8f8f8; + width:500px; +} +.event:last-child{ + border-bottom-width:1px; +} +.eventtype{ + font-weight:bold; + font-size:20px; + width:50px; + display:inline-block; +} +.eventtypein{ + color:blue; +} +.eventtypeout{ + color:red; +} +.eventsheet{ + font-size:20px; + font-weight:bold; + margin-left:10px; +} +.eventtext{ + font-size:18px; + margin-left:10px; +} +.eventfloat{ + float:right; + text-align:right; +} +.eventdate{ + font-size:12px; + font-style:italic; + display:inline-block; + margin-top:6px; +} +.eventbuttons{ + margin-top:-5px; + display:inline-block; + margin-left:60px; + font-size:10px; + vertical-align:top; + width:10px; + text-align:center; +} +.eventdelete{ + margin-bottom:5px; + font-size:10px; + font-family:sans-serif; + color:red; + cursor:pointer; +} +.addeventform{ + border:1px #ddd solid; + display:inline-block; + padding:5px; +} +.addeventform > input[type="text"] { + margin-bottom: 5px; +} +#logoutwrapper{ + float:right; +} +</style> +</head> +<body> +<div id="logoutwrapper"> + <input type="button" onclick="logoutReload();" value="Logout"> +</div> +<h1>TimeTrack</h1> +<div id="eventlist"></div> +<br><br> +<div class="addeventform"> + Sheet: <input type="text" id="addeventsheet" placeholder="Sheet"> + <select id="sheetselect" onchange="setSheetFromSelect()"></select> <br> + Text: <input type="text" id="addeventtext" placeholder="Text"> (optional) <br> + Date: <input type="datetime" id="addeventdate" placeholder="YYYY-MM-DD HH:MM:SS" size="25"> + <input type="button" onclick="dateToNow()" value="now"> <br> + <label for="addeventtypein"><input type="radio" id="addeventtypein" name="addeventtype" checked> In</label> + <label for="addeventtypeout" style="margin-left: 20px"><input type="radio" id="addeventtypeout" name="addeventtype"> Out</label> <br style="margin-top: 5px"> + <input type="button" onclick="doAddEvent(event)" value="Add" style="margin-top: 5px"> +</div> +</body> +</html> |