summaryrefslogtreecommitdiff
path: root/modules/timetrack/timetrack.html
diff options
context:
space:
mode:
authorTom Smeding <tom@tomsmeding.com>2022-02-03 10:13:46 +0100
committerTom Smeding <tom@tomsmeding.com>2022-02-03 10:13:46 +0100
commitd601d0431d6a35ce55680af9987c2ecfee719870 (patch)
tree8698dc4cbf764f89bf3cdab6eaa71f03df8e5b5a /modules/timetrack/timetrack.html
parent38b2f19f563d560be208b0c8f53bc7b6630c0436 (diff)
timetrack: Initial
Diffstat (limited to 'modules/timetrack/timetrack.html')
-rw-r--r--modules/timetrack/timetrack.html314
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 = "&rarr;in"
+ else if (ev.type == "out") e.innerHTML = "&larr;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>