<!DOCTYPE html> <html> <head> <title>FTracker Data</title> <meta name="theme-color" content="#c50e1f"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <style> html, body { margin: 0; height: 100%; font-family: sans-serif; } header { height: calc(38px - 16px); padding: 8px; } main { height: calc(100% - 38px); vertical-align: top; } main > section { height: calc(100% - 32px); display: inline-block; vertical-align: top; } main > .viewheader { display: inline-block; vertical-align: top; } main > section.names, #nameheader { width: 127px; overflow: hidden; border-right: 1px solid gray; font-weight: bold; text-transform: capitalize; } main > section.times, #timeheader { width: calc(100% - 128px); overflow: hidden; } .scroll { height: 100%; width: 100%; overflow: scroll; } .row, .row #timelabels { position: relative; height: 32px; background-size: 60px 100%; } .names .row:nth-child(even) { background: #ddd; } .names .row:nth-child(odd) { background: #eee; } .times .row:nth-child(even) { background-image: linear-gradient(to right, #bbb 1px, #ddd 1px); } .times .row:nth-child(odd) { background-image: linear-gradient(to right, #bbb 1px, #eee 1px); } .row span { padding: 7px; display: inline-block; } .times span, #timeheader span { position: absolute; background: #c50e1f; color: #ddd; margin-right: 16px; } .viewheader.row { height: 30px; background: #ddd !important; border-top: 1px solid gray; border-bottom: 1px solid gray; } .viewheader span { background: none !important; color: #000 !important; padding-left: 4px; } #credprompt { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 95%; max-width: 320px; margin: auto; background: #ddd; box-shadow: 0 0 0 10000px rgba(0,0,0,.75); } #credprompt h1 { margin: 0; padding: 16px; text-transform: uppercase; color: #eee; background: #c50e1f; text-align: center; } #credprompt input { display: block; border: none; margin: 16px auto; padding: 16px; font-size: 16px; } #credprompt input[type=text], #credprompt input[type=password] { color: #000; width: calc(100% - 64px); } #credprompt input[type=submit] { background: #c50e1f; text-transform: uppercase; font-weight: bold; color: #fff; width: calc(100% - 32px); } </style> </head> <body> <header> Start: <input type="datetime-local" id="start" value="2020-12-01T00:00" onchange="renderData()"> Ende: <input type="datetime-local" id="end" onchange="renderData()"> Raum: <input type="text" id="room" placeholder=".* (regex)" onchange="renderData()"> <input type="button" value="Log Out" style="float:right" onclick="localStorage.removeItem('dataauth'); location.reload()"> </header> <main> <div class="row viewheader" id="nameheader"> <span><b>Names</b></span> </div><!-- --><div class="row viewheader" id="timeheader"> <div id="timelabels" style="padding-right: 32px;"></div> </div> <section class="names"> <div id="names" style="padding-bottom: 32px;"></div> </section><!-- --><section class="times"> <div class="scroll"> <div id="times"></div> </div> </section> </main> <script> var data = null; var names = document.querySelector('main #names') var times = document.querySelector('main #times') function renderData() { if (data == null) { alert('No data found.') return } names = document.querySelector('main #names') times = document.querySelector('main #times') names.innerHTML = '' times.innerHTML = '' var startInput = document.querySelector('input#start') var endInput = document.querySelector('input#end') var roomInput = document.querySelector('input#room') var startDate = new Date(startInput.value) var endDate = new Date(endInput.value) var roomRE = new RegExp(roomInput.value || '.*') var tc = new Date(startDate.getTime()) var content = '' while (tc < endDate) { var h = tc.getHours() var t = (h == 0) ? '<b>'+tc.getDate()+'.'+(tc.getMonth()+1)+'.</b>' : h+':00' var left = ((tc - startDate) / (1000 * 60)) content += '<span style="left:'+left+'px;">'+t+'</span>' tc.setTime(tc.getTime() + (60*60*1000)); } var timeheader = document.getElementById('timelabels') timeheader.innerHTML = content var viewwidth = ((endDate - startDate) / (1000 * 60)) timeheader.style.width = viewwidth + 'px' times.style.width = viewwidth + 'px' var rowCount = 0 for (var [name, list] of Object.entries(data)) { var row = document.createElement('div') row.classList.add('row') var rowHasRoom = false for (entry of list) { if (entry.room.match(roomRE) == null) continue rowHasRoom = true arrD = new Date(entry.arrival) depD = entry.departure ? new Date(entry.departure) : endDate // Minutes since start date / beginning var arr = (arrD - startDate) / (1000 * 60) var dep = (depD - startDate) / (1000 * 60) var dur = dep - arr var block = document.createElement('span') block.innerHTML = entry.room block.style.left = arr + 'px' // 1px/min block.style.width = (dur-14) + 'px' // 1px/min row.appendChild(block) } if (rowHasRoom) { var vname = name.replace('-', ' ') names.innerHTML += '<div class="row"><span>'+vname+'</span></div>' times.appendChild(row) rowCount += 12 } } //var viewheight = rowCount * 32; //times.style.height = viewheight + 'px' var tw = document.querySelector('main .scroll') tw.scrollLeft = tw.scrollWidth } function saveData(rdata) { data = rdata.reduce((acc, entry) => { var name = entry.name delete entry.name acc[name] = [...acc[name] || [], entry]; return acc; }, {}); console.log(data) renderData() } function submitCredentials() { var cp = document.querySelector('#credprompt') var user = cp.querySelector('#user').value var pass = cp.querySelector('#pass').value cp.remove() var auth = btoa(user + ":" + pass) localStorage.setItem('dataauth', auth) loadData() } function loadData() { var auth = localStorage.getItem('dataauth') if (auth == null) { var prompt = document.createElement('div') prompt.id = 'credprompt' prompt.innerHTML = '<h1>Credentials Required</h1>\ <input type="text" id="user" placeholder="username" onkeydown="if (event.keyCode == 13) {submitCredentials()}">\ <input type="password" id="pass" placeholder="password" onkeydown="if (event.keyCode == 13) {submitCredentials()}">\ <input type="submit" onclick="submitCredentials()">' document.body.appendChild(prompt) document.querySelector('#credprompt #user').focus() return // Abort load, wait for submit } var headers = new Headers() headers.append('Authorization', 'Basic ' + auth) var fetchopts = { method: 'GET', headers: headers } fetch('/data', fetchopts) .then(res => { if (Math.floor(res.status / 100) == 2) return res.json() else localStorage.removeItem('dataauth') res.text().then(function (text) { alert(text) location.reload() }) }) .then(rdata => saveData(rdata)) } function localISOTimeMinutes(date) { var tzoffset = date.getTimezoneOffset() * 60000; //offset in milliseconds var localISOTime = (new Date(date - tzoffset)).toISOString().slice(0, -1); return localISOTime.split(':').slice(0,2).join(':') } document.querySelector('input#end').value = localISOTimeMinutes(new Date()) var scrollbox = document.querySelector('.scroll') var timehead = document.querySelector('#timeheader') var namebox = document.querySelector('section.names') scrollbox.onscroll = function() { timehead.scrollLeft = scrollbox.scrollLeft namebox.scrollTop = scrollbox.scrollTop } loadData() </script> </body> </html>