Add data view to resolve #13
This commit is contained in:
		@ -7,6 +7,10 @@ if __name__ == "__main__":
 | 
				
			|||||||
	def get_root():
 | 
						def get_root():
 | 
				
			||||||
		return app.send_static_file('index.html')
 | 
							return app.send_static_file('index.html')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@app.route('/view')
 | 
				
			||||||
 | 
						def get_view():
 | 
				
			||||||
 | 
							return app.send_static_file('viewdata.html')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@app.route('/<path:path>')
 | 
						@app.route('/<path:path>')
 | 
				
			||||||
	def get_file(path):
 | 
						def get_file(path):
 | 
				
			||||||
		return app.send_static_file(path)
 | 
							return app.send_static_file(path)
 | 
				
			||||||
 | 
				
			|||||||
@ -62,11 +62,11 @@ def post_arrival():
 | 
				
			|||||||
			'message': "Error: Undeparted arrival exists"
 | 
								'message': "Error: Undeparted arrival exists"
 | 
				
			||||||
		}, indent=SPACES), 409
 | 
							}, indent=SPACES), 409
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	now = datetime.utcnow()
 | 
						now = datetime.utcnow().isoformat() + 'Z'
 | 
				
			||||||
	db.insert({
 | 
						db.insert({
 | 
				
			||||||
		'name': name,
 | 
							'name': name,
 | 
				
			||||||
		'room': data['room'],
 | 
							'room': data['room'],
 | 
				
			||||||
		'arrival': data.get('arrival') or now.isoformat(),
 | 
							'arrival': data.get('arrival') or now,
 | 
				
			||||||
		'departure': None
 | 
							'departure': None
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -102,9 +102,9 @@ def post_departure():
 | 
				
			|||||||
			'message': "Error: No arrival exists"
 | 
								'message': "Error: No arrival exists"
 | 
				
			||||||
		}, indent=SPACES), 409
 | 
							}, indent=SPACES), 409
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	now = datetime.utcnow()
 | 
						now = datetime.utcnow().isoformat() + 'Z'
 | 
				
			||||||
	db.update(
 | 
						db.update(
 | 
				
			||||||
		operations.set('departure', data.get('departure') or now.isoformat()),
 | 
							operations.set('departure', data.get('departure') or now),
 | 
				
			||||||
		(Entry.name == name) & (Entry.departure == None)
 | 
							(Entry.name == name) & (Entry.departure == None)
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										204
									
								
								web/viewdata.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								web/viewdata.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,204 @@
 | 
				
			|||||||
 | 
					<!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(42px - 16px);
 | 
				
			||||||
 | 
									padding: 8px;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								main {
 | 
				
			||||||
 | 
									height: calc(100% - 42px);
 | 
				
			||||||
 | 
									overflow-Y: scroll;
 | 
				
			||||||
 | 
									vertical-align: top;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								main > section {
 | 
				
			||||||
 | 
									height: 100%;
 | 
				
			||||||
 | 
									display: inline-block;
 | 
				
			||||||
 | 
									vertical-align: top;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								main > section.names {
 | 
				
			||||||
 | 
									width: 127px;
 | 
				
			||||||
 | 
									border-right: 1px solid gray;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								main > section.timewrap {
 | 
				
			||||||
 | 
									width: calc(100% - 128px);
 | 
				
			||||||
 | 
									overflow-X: scroll;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								.times {
 | 
				
			||||||
 | 
									overflow-X: hidden;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								.row {
 | 
				
			||||||
 | 
									position: relative;
 | 
				
			||||||
 | 
									height: 2em;
 | 
				
			||||||
 | 
									background-size: 60px 100%;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								.names .row:nth-child(odd)  { background: #ddd; }
 | 
				
			||||||
 | 
								.names .row:nth-child(even) { background: #eee; }
 | 
				
			||||||
 | 
								.times .row:nth-child(odd)  {
 | 
				
			||||||
 | 
									background-image: linear-gradient(to right, #bbb 1px, #ddd 1px);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								.times .row:nth-child(even) {
 | 
				
			||||||
 | 
									background-image: linear-gradient(to right, #bbb 1px, #eee 1px);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								.row > span {
 | 
				
			||||||
 | 
									padding: 7px;
 | 
				
			||||||
 | 
									display: inline-block;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								.names {
 | 
				
			||||||
 | 
									font-weight: bold;
 | 
				
			||||||
 | 
									text-transform: capitalize;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								.times span {
 | 
				
			||||||
 | 
									position: absolute;
 | 
				
			||||||
 | 
									background: #c50e1f;
 | 
				
			||||||
 | 
									color: #ddd;
 | 
				
			||||||
 | 
									margin-right: 16px;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								#timeheader span {
 | 
				
			||||||
 | 
									background: none;
 | 
				
			||||||
 | 
									color: #444;
 | 
				
			||||||
 | 
									padding-left: 4px;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							</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()">
 | 
				
			||||||
 | 
							</header>
 | 
				
			||||||
 | 
							<main>
 | 
				
			||||||
 | 
								<section class="names"></section><section class="timewrap"><div class="times"></div></section>
 | 
				
			||||||
 | 
							</main>
 | 
				
			||||||
 | 
							<script>
 | 
				
			||||||
 | 
								var data = null;
 | 
				
			||||||
 | 
								function renderData() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (data == null) {
 | 
				
			||||||
 | 
										alert('No data found.')
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									var names = document.querySelector('main .names')
 | 
				
			||||||
 | 
									var times = document.querySelector('main .times')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									names.innerHTML = '<div class="row"><span>Names</span></div>'
 | 
				
			||||||
 | 
									times.innerHTML = '<div class="row" id="timeheader"></div>'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									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('timeheader')
 | 
				
			||||||
 | 
									timeheader.innerHTML = content
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									times.style.width = ((endDate - startDate) / (1000 * 60)) + 'px'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									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)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									var tw = document.querySelector('main .timewrap')
 | 
				
			||||||
 | 
									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 loadData() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									fetch('/data')
 | 
				
			||||||
 | 
										.then(res => {
 | 
				
			||||||
 | 
											if (Math.floor(res.status / 100) == 2)
 | 
				
			||||||
 | 
												return res.json()
 | 
				
			||||||
 | 
											else
 | 
				
			||||||
 | 
												res.text().then(function (text) {
 | 
				
			||||||
 | 
													alert(text + "\nRestart your browser (yes, all tabs and windows) to relogin")
 | 
				
			||||||
 | 
												})
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
										.then(rdata => saveData(rdata))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								var now = new Date()
 | 
				
			||||||
 | 
								var localISODate = new Date(now.getTime() -
 | 
				
			||||||
 | 
									(now.getTimezoneOffset() * 60000)).toISOString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								document.querySelector('input#end').value =
 | 
				
			||||||
 | 
									localISODate.split(':').slice(0,2).join(':')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								loadData()
 | 
				
			||||||
 | 
							</script>
 | 
				
			||||||
 | 
						</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
		Reference in New Issue
	
	Block a user