Skip to content

Commit 3075581

Browse files
authored
Adding Gantt view
1 parent b1e9d91 commit 3075581

1 file changed

Lines changed: 223 additions & 19 deletions

File tree

Calendar/Calendar.py

Lines changed: 223 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,61 @@
44
from System import DateTime
55

66
def 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

2363
def 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'>&laquo; Previous %s</a> | " % (span_link, prev_link, span_term))
221+
html.append("<a href='?v=%s&%s'>&laquo; 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 &raquo;</a>" % (span_link, next_link, span_term))
223+
html.append(" | <a href='?v=%s&%s'>Next %s &raquo;</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'>&laquo; 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 &raquo;</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 = "&nbsp;" * (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

285486
if 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

Comments
 (0)