326 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			326 lines
		
	
	
		
			8.3 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 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>
 |