44from System import DateTime
55
66def get_events (dt ):
7- return q .QuerySql ('''
7+ return q .QuerySql ("""
88 SELECT
99 m.MeetingDate,
1010 m.MeetingEnd,
1111 COALESCE(NULLIF(m.Description,''), o.organizationName) as MeetingName,
1212 m.MeetingId,
1313 1 * o.ShowInSites as Featured
14- FROM Meetings m
14+ FROM Meetings m
1515 LEFT JOIN Organizations o ON m.OrganizationId = o.OrganizationId
1616
1717 WHERE m.MeetingDate < DATEADD(day, 1, '{0}')
1818 AND COALESCE(m.MeetingEnd, m.MeetingDate) > '{0}'
1919 AND m.Canceled = 0
2020 ORDER BY MeetingDate, o.DivisionId
21- ''' .format (dt ))
21+ """ .format (dt ))
22+
23+ def get_reservables (typ ):
24+ return q .QuerySql ("""
25+ SELECT ReservableId,
26+ ParentId,
27+ BadgeColor as Color,
28+ Name,
29+ Description,
30+ IsReservable,
31+ IsEnabled,
32+ IsCountable,
33+ Quantity
34+ FROM Reservable
35+ WHERE ReservableTypeId = 1
36+ AND IsDeleted = 0
37+ AND IsEnabled = 1
38+ ;
39+ """ )
40+
41+
42+ def get_reservations (typ , dt ):
43+ return q .QuerySql ("""
44+ SELECT
45+ rb.ReservableId,
46+ rv.MeetingStart,
47+ COALESCE(rv.MeetingEnd, rv.MeetingStart) as MeetingEnd,
48+ COALESCE(NULLIF(rv.SetupMinutes, ''), 0) as SetupMinutes,
49+ COALESCE(NULLIF(rv.SetupMinutes, ''), 0) as TeardownMinutes,
50+ COALESCE(NULLIF(m.Description, ''), o.OrganizationName) as Name,
51+ m.MeetingId
52+ FROM Reservations rv
53+ JOIN Reservable rb ON rv.ReservableId = rb.ReservableId
54+ JOIN Meetings m ON rv.MeetingId = m.MeetingId
55+ JOIN Organizations o ON m.OrganizationId = o.OrganizationId
56+ WHERE rv.MeetingId IS NOT NULL
57+ AND rv.MeetingEnd > '{0}'
58+ AND rv.MeetingStart < DATEADD(day, 1, '{0}')
59+ ORDER BY rb.ReservableId, rv.MeetingStart
60+ ;
61+ """ .format (dt ))
2262
2363def generate_calendar_html (date ):
2464 year = date .year
@@ -41,8 +81,7 @@ def generate_calendar_html(date):
4181
4282 prev_link = "?d=%d-%d" % (prev_year , prev_month )
4383 next_link = "?d=%d-%d" % (next_year , next_month )
44- week_link = "?v=w&d=%d-%d-%d" % (date .year , date .month , date .day )
45- day_link = "?v=d&d=%d-%d-%d" % (date .year , date .month , date .day )
84+ curr_link = "d=%d-%d-%d" % (date .year , date .month , date .day )
4685
4786 html = []
4887 html .append ("<style>" )
@@ -74,8 +113,9 @@ def generate_calendar_html(date):
74113
75114 html .append ("<div class='nav-r'>" )
76115 html .append ("<strong>Month</strong>" )
77- html .append (" | <a href='%s'>Week</a>" % (week_link ))
78- html .append (" | <a href='%s'>Day</a>" % (day_link ))
116+ html .append (" | <a href='?v=w&%s'>Week</a>" % (curr_link ))
117+ html .append (" | <a href='?v=d&%s'>Day</a>" % (curr_link ))
118+ html .append (" | <a href='?v=r&%s'>Rooms</a>" % (curr_link ))
79119 html .append ("</div>" )
80120 html .append ("</div>" )
81121
@@ -149,10 +189,9 @@ def generate_calendar_vert_html(start_date, dayCount):
149189 prev_date = start_date - datetime .timedelta (days = dayCount )
150190 next_date = start_date + datetime .timedelta (days = dayCount )
151191
152- prev_link = "&d=%d-%d-%d" % (prev_date .year , prev_date .month , prev_date .day )
153- next_link = "&d=%d-%d-%d" % (next_date .year , next_date .month , next_date .day )
154- curr_link = "&d=%d-%d-%d" % (start_date .year , start_date .month , start_date .day )
155- month_link = "?d=%d-%d-%d" % (start_date .year , start_date .month , start_date .day )
192+ prev_link = "d=%d-%d-%d" % (prev_date .year , prev_date .month , prev_date .day )
193+ next_link = "d=%d-%d-%d" % (next_date .year , next_date .month , next_date .day )
194+ curr_link = "d=%d-%d-%d" % (start_date .year , start_date .month , start_date .day )
156195
157196 model .Title = "Calendar: %s of %s %d, %s" % (span_term , calendar .month_name [start_date .month ], start_date .day , start_date .year )
158197
@@ -179,21 +218,23 @@ def generate_calendar_vert_html(start_date, dayCount):
179218 html .append ("</div>" )
180219
181220 html .append ("<div class='nav-c'>" )
182- html .append ("<a href='?v=%s%s'>« Previous %s</a> | " % (span_link , prev_link , span_term ))
221+ html .append ("<a href='?v=%s& %s'>« Previous %s</a> | " % (span_link , prev_link , span_term ))
183222 html .append ("<strong>%s of %s %d, %s</strong>" % (span_term , calendar .month_name [start_date .month ], start_date .day , start_date .year ))
184- html .append (" | <a href='?v=%s%s'>Next %s »</a>" % (span_link , next_link , span_term ))
223+ html .append (" | <a href='?v=%s& %s'>Next %s »</a>" % (span_link , next_link , span_term ))
185224 html .append ("</div>" )
186225
187226 html .append ("<div class='nav-r'>" )
188- html .append ("<a href='%s'>Month</a>" % (month_link ))
227+ html .append ("<a href='? %s'>Month</a>" % (curr_link ))
189228 if dayCount == 7 :
190229 html .append (" | <strong>Week</strong>" )
191230 else :
192- html .append (" | <a href='?v=w%s'>Week</a>" % (curr_link ))
231+ html .append (" | <a href='?v=w& %s'>Week</a>" % (curr_link ))
193232 if dayCount == 1 :
194233 html .append (" | <strong>Day</strong>" )
195234 else :
196- html .append (" | <a href='?v=d%s'>Day</a>" % (curr_link ))
235+ html .append (" | <a href='?v=d&%s'>Day</a>" % (curr_link ))
236+
237+ html .append (" | <a href='?v=r&%s'>Rooms</a>" % (curr_link ))
197238
198239 html .append ("</div>" )
199240
@@ -239,7 +280,7 @@ def generate_calendar_vert_html(start_date, dayCount):
239280
240281 for ev in events :
241282 start_hour = 0 if ev .MeetingDate < day_c else ev .MeetingDate .Hour + ev .MeetingDate .Minute / 60.0
242- end_hour = 24 if ev .MeetingEnd > tom_c else (ev .MeetingEnd .Hour + ev .MeetingEnd .Minute / 60.0 ) if ev .MeetingEnd else (start_hour + 1 )
283+ end_hour = 24 if ev .MeetingEnd > tom_c else (ev .MeetingEnd .Hour + ev .MeetingEnd .Minute / 60.0 ) if ev .MeetingEnd else (start_hour )
243284
244285 if start_hour < day_start : start_hour = day_start
245286 if end_hour > day_end : end_hour = day_end
@@ -273,14 +314,174 @@ def generate_calendar_vert_html(start_date, dayCount):
273314 html .append ("<script>" )
274315 html .append ("window.addEventListener('load', function(){" )
275316 html .append (" var container = document.querySelector('.week-container');" )
276- html .append (" if(container){ container.scrollTop = container.clientHeight / 2 ; }" )
317+ html .append (" if(container){ container.scrollTop = container.firstElementChild. clientHeight / 4 ; }" )
277318 html .append ("});" )
278319 html .append ("</script>" )
279320
280321 return "\n " .join (html )
281322
282323
283324
325+ def generate_room_gantt_html (date ):
326+ rooms = get_reservables ("r" )
327+ reservations = get_reservations ("r" , date )
328+
329+ hour_width = 6 # vw per hour
330+ total_hours = 25
331+
332+ room_dict = {r .ReservableId : r for r in rooms }
333+ children = {r .ReservableId : [] for r in rooms }
334+ for r in rooms :
335+ if r .ParentId in room_dict :
336+ children [r .ParentId ].append (r )
337+
338+ def time_to_vw (dt ):
339+ hour = dt .Hour + dt .Minute / 60.0
340+ return hour * hour_width
341+
342+ prev_date = date - datetime .timedelta (days = 1 )
343+ next_date = date + datetime .timedelta (days = 1 )
344+
345+ prev_link = "d=%d-%d-%d" % (prev_date .year , prev_date .month , prev_date .day )
346+ next_link = "d=%d-%d-%d" % (next_date .year , next_date .month , next_date .day )
347+ curr_link = "d=%d-%d-%d" % (date .year , date .month , date .day )
348+
349+ model .Title = "Usage for %s %d, %d" % (calendar .month_name [date .month ], date .day , date .year )
350+
351+ html = []
352+ html .append ("<style>" )
353+ html .append (".gantt-wrapper { display: flex; height: calc(100vh - 10em); overflow-y: auto; }" )
354+ html .append (".room-labels { flex: 0 0 15em; border-right: 1px solid #999; background: #f9f9f9; white-space: nowrap; height:fit-content; }" )
355+ html .append (".room-label { padding: 4px; border-bottom: 1px solid #ccc; height: 2em; font-weight: bold; }" )
356+ html .append (".gantt-container { overflow-x: scroll; flex: 1; position: relative; height: fit-content; }" )
357+ html .append (".gantt-chart { position: relative; width: %dvw; }" % (hour_width * (total_hours - 1 )))
358+ html .append (".room-row { position: relative; height: 2em; border-bottom: 1px solid #ccc; }" )
359+ html .append (".bar { position: absolute; top: 2px; bottom: 2px; border-radius: 3px; color: #fff; padding: 2px; font-size: 0.8em; overflow: hidden; white-space: nowrap; }" )
360+ html .append (".setup { opacity: 0.5; }" )
361+ html .append (".timegrid { position: absolute; top: 0; height: 100%; border-left: 1px dashed #ccc; font-size: 0.7em; color: #666; text-align: center; }" )
362+
363+ html .append (".nav.cal { display: flex; gap: 1em; }" )
364+ html .append (".nav.cal > * { flex:1; }" )
365+ html .append (".nav-c { text-align:center; }" )
366+ html .append (".nav-r { text-align:right; }" )
367+ html .append ("</style>" )
368+
369+ html .append ("<div class='nav cal'>" )
370+
371+ html .append ("<div class='nav-l'></div>" )
372+
373+ html .append ("<div class='nav-c'>" )
374+ html .append ("<a href='?v=r&%s'>« Previous</a> | " % (prev_link ))
375+ html .append ("<strong>%s %d, %d</strong>" % (calendar .month_name [date .month ], date .day , date .year ))
376+ html .append (" | <a href='?v=r&%s'>Next »</a>" % (next_link ))
377+ html .append ("</div>" )
378+
379+ html .append ("<div class='nav-r'>" )
380+
381+ html .append ("<a href='?%s'>Month</a>" % (curr_link ))
382+ html .append (" | <a href='?v=w&%s'>Week</a>" % (curr_link ))
383+ html .append (" | <a href='?v=d&%s'>Day</a>" % (curr_link ))
384+ html .append (" | <strong>Rooms</strong>" )
385+ html .append ("</div>" )
386+ html .append ("</div>" )
387+
388+ html .append ("<div class='gantt-wrapper'>" )
389+
390+ def room_has_reservations (room ):
391+ if len ([r for r in reservations if r .ReservableId == room .ReservableId ]) > 0 :
392+ return True
393+
394+ for child in children .get (room .ReservableId , []):
395+ if room_has_reservations (child ):
396+ return True
397+
398+ return False
399+
400+
401+ # Room rows and bars
402+ def render_room_rows (room ):
403+ if not room_has_reservations (room ):
404+ return
405+
406+ html .append ("<div class='room-row'>" )
407+ for res in [r for r in reservations if r .ReservableId == room .ReservableId ]:
408+ start = res .MeetingStart
409+ end = res .MeetingEnd
410+ setup_start = start .AddMinutes (- res .SetupMinutes )
411+ teardown_end = end .AddMinutes (res .TeardownMinutes )
412+
413+ left = time_to_vw (start )
414+ width = time_to_vw (end ) - left
415+ setup_left = time_to_vw (setup_start )
416+ setup_width = time_to_vw (teardown_end ) - setup_left
417+
418+ color = room_dict [res .ReservableId ].Color
419+
420+ html .append ("<a href='/Meeting/MeetingDetails/%s'>" % res .MeetingId )
421+
422+ if res .SetupMinutes > 0 or res .TeardownMinutes > 0 :
423+ html .append ("<div class='bar setup' style='left:%.2fvw; width:%.2fvw; background:%s;'></div>" % (
424+ setup_left , setup_width , color
425+ ))
426+
427+ html .append ("<div class='bar' style='left:%.2fvw; width:%.2fvw; background:%s;' title=\" %s\" >%s</div>" % (
428+ left , width , color , res .Name , res .Name
429+ ))
430+ html .append ("</a>" )
431+ html .append ("</div>" )
432+ for child in children .get (room .ReservableId , []):
433+ render_room_rows (child )
434+
435+ # Static room labels
436+ html .append ("<div class='room-labels'>" )
437+ def render_room_labels (room , depth = 0 ):
438+ if not room_has_reservations (room ):
439+ return
440+
441+ indent = " " * (depth * 4 )
442+ html .append ("<div class='room-label'>%s%s</div>" % (indent , room .Name ))
443+ for child in children .get (room .ReservableId , []):
444+ render_room_labels (child , depth + 1 )
445+ for room in rooms :
446+ if room .ParentId not in room_dict :
447+ render_room_labels (room )
448+ html .append ("</div>" )
449+
450+ # Scrollable Gantt chart
451+ html .append ("<div class='gantt-container'>" )
452+ html .append ("<div class='gantt-chart'>" )
453+
454+ # Time grid lines
455+ for h in range (total_hours ):
456+ left = (h - .5 ) * hour_width
457+ label = "%02d:00" % (h % 24 )
458+ # lines for halves, labels for wholes.
459+ html .append ("<div class='timegrid' style='left:%.2fvw; width:%.2fvw;'>%s</div>" % (left , hour_width , label ))
460+
461+ # whole hour line
462+ if h < total_hours :
463+ half_left = left + hour_width / 2
464+ html .append ("<div class='timegrid' style='left:%.2fvw; width:0; border-left: 1px dashed #ccc;'></div>" % half_left )
465+
466+ for room in rooms :
467+ if room .ParentId not in room_dict :
468+ render_room_rows (room )
469+
470+ html .append ("</div></div>" ) # end gantt-chart and container
471+ html .append ("</div>" ) # end gantt-wrapper
472+
473+ # JavaScript to scroll to 8am
474+ html .append ("<script>" )
475+ html .append ("window.addEventListener('load', function(){" )
476+ html .append (" var container = document.querySelector('.gantt-container');" )
477+ html .append (" if(container){ container.scrollLeft = container.firstElementChild.clientWidth / 3; }" )
478+ html .append ("});" )
479+ html .append ("</script>" )
480+
481+ return "\n " .join (html )
482+
483+
484+
284485
285486if True :
286487
@@ -310,8 +511,11 @@ def generate_calendar_vert_html(start_date, dayCount):
310511
311512 elif Data .v == 'd' :
312513 vi = 1
514+
515+ if Data .v == 'r' :
516+ print generate_room_gantt_html (d )
313517
314- if vi > 0 :
518+ elif vi > 0 :
315519 print generate_calendar_vert_html (d , vi )
316520
317521 else :
0 commit comments