diff options
-rw-r--r-- | modules/timetrack/timetrack.html | 125 | ||||
-rw-r--r-- | package-lock.json | 1 |
2 files changed, 116 insertions, 10 deletions
diff --git a/modules/timetrack/timetrack.html b/modules/timetrack/timetrack.html index c3e92dd..8d89455 100644 --- a/modules/timetrack/timetrack.html +++ b/modules/timetrack/timetrack.html @@ -6,6 +6,8 @@ <script> "use strict"; +var lastlist=null; + function fetch(method,url,data/*?*/,creds/*?*/,cb){ if(!creds){ cb=data; @@ -46,6 +48,7 @@ function toinputdate(date) { pad(date.getSeconds(),2,"0"); } +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) { @@ -110,11 +113,9 @@ function refreshlist(list){ var listelem=document.getElementById("eventlist"); listelem.innerHTML=""; var rows=[]; - var i,ev; - var now=new Date(); + var i; for(i=0;i<list.length;i++){ - ev=list[i]; - rows.push([ev.date,tablerowfor(ev)]); + rows.push([list[i].date,tablerowfor(list[i])]); } if(rows.length==0){ var div=document.createElement("div"); @@ -123,16 +124,107 @@ function refreshlist(list){ 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 weekstart(date){ + var d=new Date(date.getFullYear(), date.getMonth(), date.getDate()); + var wkday=(d.getDay()+6)%7; + return new Date(d - wkday*24*3600*1000); +} + +function refreshcalendar(list,npreweeks){ + if(npreweeks==undefined)npreweeks=2; + + function datekey(d){return d.getFullYear()+"-"+d.getMonth()+"-"+d.getDate();} + var hist=new Map(), incheck=new Map(); + var errors=[]; + for(var i=0;i<list.length;i++){ + var key=datekey(list[i].date); + if(list[i].type=="in"){ + if(incheck.has(key)){ + errors.push("Double check-in at "+formatdate(list[i].date)); + continue; + } + incheck.set(key,list[i]); + } else if(list[i].type=="out"){ + if(!incheck.has(key)){ + errors.push("Loose check-out at "+formatdate(list[i].date)); + continue; + } + var thistime=list[i].date-incheck.get(key).date; + incheck.delete(key); + var yet=hist.has(key)?hist.get(key):0; + hist.set(key,yet+thistime); + } else { + errors.push("Unknown type '"+list[i].type+"'"); + } + } + + var tb=document.getElementById("calendartb"); + tb.innerHTML=""; + + var tr,td; + for(var i=0;i<errors.length;i++){ + tr=document.createElement("tr"); + td=document.createElement("td"); + td.appendChild(document.createTextNode(errors[i])); + tr.appendChild(td); + tb.appendChild(tr); + } + + 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 thismonday=weekstart(new Date()); + for(var wkoff=-npreweeks;wkoff<=1;wkoff++){ + var monday=new Date(thismonday.getTime() + wkoff*7*24*3600*1000); + + 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=new Date(thismonday.getTime() + (wkoff*7+i)*24*3600*1000); + 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)+")"); + tr.appendChild(td); + } + tb.appendChild(tr); + } +} + function handleReceivedList(list){ - var i; - for(i=0;i<list.length;i++)list[i].date=new Date(list[i].date); + lastlist=list; refreshlist(list); + refreshcalendar(list); } function getlist(){ @@ -148,6 +240,8 @@ function getlist(){ alert("An error occurred!"); return; } + for(var i=0;i<list.length;i++)list[i].date=new Date(list[i].date); + list.sort(function(a,b){return a.date-b.date;}); handleReceivedList(list); }); } @@ -280,17 +374,24 @@ body{ color:red; cursor:pointer; } -.addeventform{ +#addeventform{ border:1px #ddd solid; display:inline-block; padding:5px; } -.addeventform > input[type="text"] { +#addeventform > input[type="text"] { margin-bottom: 5px; } #logoutwrapper{ float:right; } +#calendartb td{ + width:35px; +} +#calendartb td:first-child{ + text-align:right; + width:50px; +} </style> </head> <body> @@ -300,7 +401,7 @@ body{ <h1>TimeTrack</h1> <div id="eventlist"></div> <br><br> -<div class="addeventform"> +<div id="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> @@ -310,5 +411,9 @@ body{ <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> +<br><br> +<div id="calendar"> + <table><tbody id="calendartb"></tbody></table> +</div> </body> </html> diff --git a/package-lock.json b/package-lock.json index 75aa7ba..7e9271b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "webserver", "version": "1.0.0", "license": "MIT", "dependencies": { |