Split down files and add question about covid-test
This commit is contained in:
403
web/view.html
403
web/view.html
@ -2,130 +2,10 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>FTracker Data</title>
|
||||
<meta charset="utf-8">
|
||||
<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;
|
||||
margin-right: 16px;
|
||||
}
|
||||
.times span {
|
||||
background: #c50e1f;
|
||||
color: #ddd;
|
||||
font-weight: bold;
|
||||
-webkit-text-stroke: .4px #c50e1f;
|
||||
}
|
||||
.times span.implausible {
|
||||
background: linear-gradient(to right, #c50e1f, rgba(197,14,31,0.2) 1000px);
|
||||
}
|
||||
.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>
|
||||
<link href="view.css" rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
@ -151,283 +31,6 @@
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<script>
|
||||
var data = null;
|
||||
|
||||
var names = document.querySelector('main #names')
|
||||
var times = document.querySelector('main #times')
|
||||
|
||||
function exportCSV() {
|
||||
|
||||
if (data == null) {
|
||||
alert('No data found.')
|
||||
return
|
||||
}
|
||||
|
||||
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 || '.*')
|
||||
|
||||
csv = '"ftracker-export",'
|
||||
days = []
|
||||
|
||||
var tc = new Date(startDate.getTime())
|
||||
tc.setHours(1,0,0,0)
|
||||
while (tc < endDate) {
|
||||
var isodate = tc.toISOString().split('T')[0]
|
||||
csv += ('"' + isodate + '",')
|
||||
days.push(isodate)
|
||||
tc.setDate(tc.getDate() + 1);
|
||||
}
|
||||
|
||||
csv = csv.replace(/,$/, '')
|
||||
|
||||
csv += '\n'
|
||||
|
||||
for (var [name, list] of Object.entries(data)) {
|
||||
|
||||
csv += '"' + name + '"'
|
||||
|
||||
for (day of days) {
|
||||
|
||||
csv += ',"'
|
||||
|
||||
daytexts = []
|
||||
|
||||
for (entry of list) {
|
||||
|
||||
if (entry.room.match(roomRE) == null)
|
||||
continue
|
||||
|
||||
var arrD = new Date(entry.arrival)
|
||||
var depD = entry.departure ? new Date(entry.departure) : new Date()
|
||||
|
||||
if (depD < startDate || arrD > endDate)
|
||||
continue
|
||||
|
||||
var [arrDay, arrT] = localISOTimeMinutes(arrD).split('T')
|
||||
var [depDay, depT] = localISOTimeMinutes(depD).split('T')
|
||||
|
||||
if ((arrDay == day) && (depDay == day)) {
|
||||
daytexts.push(arrT + '-' + depT + ' (' + entry.room + ')')
|
||||
} else if (arrDay == day) {
|
||||
daytexts.push(arrT + '-... (' + entry.room + ')')
|
||||
} else if (depDay == day) {
|
||||
daytexts.push('...-' + depT + ' (' + entry.room + ')')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
csv += daytexts.join('\n')
|
||||
|
||||
csv += '"'
|
||||
|
||||
}
|
||||
|
||||
csv += '\n'
|
||||
|
||||
}
|
||||
|
||||
var element = document.createElement('a');
|
||||
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(csv));
|
||||
element.setAttribute('download', 'ftracker-export.csv');
|
||||
element.style.display = 'none';
|
||||
document.body.appendChild(element);
|
||||
element.click();
|
||||
document.body.removeChild(element);
|
||||
|
||||
}
|
||||
|
||||
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 || '.*')
|
||||
|
||||
// Clear everything below hours because that would lead to
|
||||
// misalignments with the day grid
|
||||
startDate.setMinutes(0,0,0)
|
||||
|
||||
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
|
||||
|
||||
var arrD = new Date(entry.arrival)
|
||||
var 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 = Math.max(0,(dur-14)) + 'px' // 1px/min
|
||||
|
||||
if (dur > 60 * 24)
|
||||
block.classList.add('implausible')
|
||||
|
||||
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(':')
|
||||
|
||||
}
|
||||
|
||||
var now = new Date()
|
||||
var startDate = new Date()
|
||||
startDate.setDate(now.getDate() - (4*7))
|
||||
startDate.setHours(0,0,0,0)
|
||||
document.querySelector('input#start').value = localISOTimeMinutes(startDate)
|
||||
document.querySelector('input#end').value = localISOTimeMinutes(now)
|
||||
|
||||
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>
|
||||
<script src="view.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user