ftracker/web/view.html

329 lines
8.4 KiB
HTML

<!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;
white-space: nowrap;
overflow: hidden;
}
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: 199px;
overflow: hidden;
border-right: 1px solid gray;
font-weight: bold;
text-transform: capitalize;
white-space: nowrap;
}
main > section.times, #timeheader {
width: calc(100% - 200px);
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: 256px;"></div>
</div>
<section class="names">
<div id="names" style="padding-bottom: 256px;"></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 rowHasBlock = false
for (entry of list) {
if (entry.room.match(roomRE) == null)
continue
arrD = new Date(entry.arrival)
depD = entry.departure ? new Date(entry.departure) : new Date()
if (depD < startDate || arrD > endDate)
continue
rowHasBlock = true
// 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 (rowHasBlock) {
var vname = name.replace(/-/g, ' ')
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>