Skip to content

Commit c6e5de1

Browse files
authored
Adding weekly calendar view
1 parent c902abc commit c6e5de1

1 file changed

Lines changed: 177 additions & 23 deletions

File tree

Calendar/Calendar.py

Lines changed: 177 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import calendar
22
import datetime
3+
from clr import Convert
4+
from System import DateTime
35

46
def get_events(dt):
57
return q.QuerySql('''
@@ -16,7 +18,9 @@ def get_events(dt):
1618
ORDER BY MeetingDate, o.DivisionId
1719
'''.format(dt))
1820

19-
def generate_calendar_html(year, month):
21+
def generate_calendar_html(date):
22+
year = date.year
23+
month = date.month
2024
cal = calendar.Calendar(firstweekday=6) # Sunday start
2125
month_days = cal.monthdatescalendar(year, month)
2226

@@ -33,8 +37,9 @@ def generate_calendar_html(year, month):
3337
next_month = 1
3438
next_year += 1
3539

36-
prev_link = "?year=%d&month=%d" % (prev_year, prev_month)
37-
next_link = "?year=%d&month=%d" % (next_year, next_month)
40+
prev_link = "?d=%d-%d" % (prev_year, prev_month)
41+
next_link = "?d=%d-%d" % (next_year, next_month)
42+
week_link = "?v=w&d=%d-%d-1" % (date.year, date.month)
3843

3944
html = []
4045
html.append("<style>")
@@ -44,17 +49,27 @@ def generate_calendar_html(year, month):
4449
html.append("th { background: #eee; }")
4550
html.append(".daynum { font-weight: bold; }")
4651
html.append(".event { margin: 2px 0; padding: 2px; background: #def; border-radius: 3px; }")
52+
html.append(".nav { display: flex; gap: 1em; }")
53+
html.append(".nav-l, .nav-r { flex:1; }")
54+
html.append(".nav-r { text-align:right; }")
4755
html.append("</style>")
4856
# html.append("<h2>%s %d</h2>" % (calendar.month_name[month], year))
4957

5058
model.Title = "Calendar: %s %d" % (calendar.month_name[month], year)
5159
model.Header = "%s %d" % (calendar.month_name[month], year)
5260

5361
html.append("<div class='nav'>")
62+
html.append("<div class='nav-l'>")
5463
html.append("<a href='%s'>&laquo; %s %d</a> | " % (prev_link, calendar.month_name[prev_month], prev_year))
5564
html.append("<strong>%s %d</strong>" % (calendar.month_name[month], year))
5665
html.append(" | <a href='%s'>%s %d &raquo;</a>" % (next_link, calendar.month_name[next_month], next_year))
5766
html.append("</div>")
67+
68+
html.append("<div class='nav-r'>")
69+
html.append("<strong>Month</strong>")
70+
html.append(" | <a href='%s'>Week</a>" % (week_link))
71+
html.append("</div>")
72+
html.append("</div>")
5873

5974

6075
html.append("<table>")
@@ -79,7 +94,7 @@ def generate_calendar_html(year, month):
7994
if ev.Featured:
8095
classes.append('feat')
8196

82-
#html.append("<a href='/Meeting/%s'>" % ev.MeetingId)
97+
html.append("<a href='/Meeting/%s'>" % ev.MeetingId)
8398

8499
html.append("<div class='%s'>" % ' '.join(classes))
85100

@@ -89,31 +104,170 @@ def generate_calendar_html(year, month):
89104
ev.MeetingDate.ToString("h:mmtt") if ev.MeetingEnd is None else ev.MeetingEnd.ToString("h:mmtt")
90105
)).lower().replace(":00",""))
91106

92-
html.append("</div>")
93-
# html.append("</a>")
107+
html.append("</div></a>")
94108
html.append("</td>")
95109
html.append("</tr>")
96110

97111
html.append("</table>")
98112
return "\n".join(html)
99113

100114

115+
def generate_calendar_week_html(start_date):
116+
"""
117+
Generate an HTML week view calendar with overlap handling,
118+
horizontal hour/half-hour lines, scrollable, and auto-scroll to 8am.
119+
"""
120+
start_of_week = start_date - datetime.timedelta(days=start_date.weekday() + 1 if start_date.weekday() != 6 else 0)
121+
days = [start_of_week + datetime.timedelta(days=i) for i in range(7)]
122+
123+
hour_height = 60 # pixels per hour (so 30 min = 30px)
124+
day_start = 0
125+
day_end = 24
126+
127+
prev_date = start_date - datetime.timedelta(days=7)
128+
next_date = start_date + datetime.timedelta(days=7)
129+
130+
prev_link = "?v=w&d=%d-%d-%d" % (prev_date.year, prev_date.month, prev_date.day)
131+
next_link = "?v=w&d=%d-%d-%d" % (next_date.year, next_date.month, next_date.day)
132+
month_link = "?d=%d-%d-%d" % (next_date.year, next_date.month, next_date.day)
133+
134+
html = []
135+
html.append("<style>")
136+
html.append("div#main { width: 100% !important;}")
137+
html.append(".week-container { height: calc(100vh - 10em); overflow-y: scroll; border: 1px solid #999; }")
138+
html.append(".week { display: flex; min-height: %dpx; position: relative; padding-left:3em; }" % ((day_end - day_start) * hour_height))
139+
html.append(".daycol { flex: 1; border-left: 1px solid #999; position: relative; }")
140+
html.append(".dayheader { background: #eee; text-align: center; padding: 4px; font-weight: bold; position: sticky; top: 0; z-index: 2; }")
141+
html.append(".event { position: absolute; background: #def; border: 1px solid #69c; border-radius: 3px; padding: 2px; font-size: 0.85em; overflow: hidden; }")
142+
html.append(".feat { font-size: 1.1em; font-weight: bold; }")
143+
html.append(".timegrid { position: absolute; left: 0; right: 0; border-top: 1px dashed #ccc; font-size: 0.7em; color: #666; }")
144+
html.append(".nav { display: flex; gap: 1em; }")
145+
html.append(".nav-l, .nav-r { flex:1; }")
146+
html.append(".nav-c { text-align:center; }")
147+
html.append(".nav-r { text-align:right; }")
148+
html.append("</style>")
149+
150+
# JavaScript to scroll to 8am
151+
html.append("<script>")
152+
html.append("window.addEventListener('load', function(){")
153+
html.append(" var container = document.querySelector('.week-container');")
154+
html.append(" if(container){ container.scrollTop = %d; }" % (8 * hour_height))
155+
html.append("});")
156+
html.append("</script>")
157+
158+
html.append("<div class='nav'>")
159+
160+
html.append("<div class='nav-l'>")
161+
html.append("<a href='%s'>&laquo; Previous Week</a> | " % prev_link)
162+
html.append("<a href='%s'>Next Week &raquo;</a>" % next_link)
163+
html.append("</div>")
164+
165+
html.append("<div class='nav-c'>")
166+
html.append("<strong>Week of %s %d, %s</strong>" % (calendar.month_name[start_date.month], start_date.day, start_date.year))
167+
html.append("</div>")
168+
169+
html.append("<div class='nav-r'>")
170+
html.append("<a href='%s'>Month</a>" % (month_link))
171+
html.append(" | <strong>Week</strong>")
172+
html.append("</div>")
173+
174+
html.append("</div>")
175+
176+
html.append("<div class='week-container'>")
177+
html.append("<div class='week'>")
178+
179+
# Time grid lines (shared across all days)
180+
for h in range(day_start, day_end + 1):
181+
top = (h - day_start) * hour_height
182+
html.append("<div class='timegrid' style='top:%dpx'>%02d:00</div>" % (top, h))
183+
# half-hour line
184+
if h < day_end:
185+
html.append("<div class='timegrid' style='top:%dpx'></div>" % (top + hour_height // 2))
186+
187+
for day in days:
188+
events = get_events(day)
189+
events = sorted(events, key=lambda e: (e.MeetingDate, e.MeetingEnd or e.MeetingDate))
190+
191+
day_c = Convert(day, DateTime)
192+
tom_c = day_c.AddDays(1)
193+
194+
# Allocate lanes for overlaps
195+
lanes = []
196+
event_positions = {}
197+
for ev in events:
198+
placed = False
199+
for lane_index, lane in enumerate(lanes):
200+
last = lane[-1]
201+
if last.MeetingEnd <= ev.MeetingDate:
202+
lane.append(ev)
203+
event_positions[ev.MeetingId] = lane_index
204+
placed = True
205+
break
206+
if not placed:
207+
lanes.append([ev])
208+
event_positions[ev.MeetingId] = len(lanes) - 1
209+
lane_count = max(1, len(lanes))
210+
211+
html.append("<div class='daycol'>")
212+
html.append("<div class='dayheader'>%s<br>%d</div>" % (calendar.day_abbr[day.weekday()], day.day))
213+
214+
for ev in events:
215+
start_hour = 0 if ev.MeetingDate < day_c else ev.MeetingDate.Hour + ev.MeetingDate.Minute / 60.0
216+
end_hour = 24 if ev.MeetingEnd > tom_c else (ev.MeetingEnd.Hour + ev.MeetingEnd.Minute / 60.0) if ev.MeetingEnd else (start_hour + 1)
217+
218+
if start_hour < day_start: start_hour = day_start
219+
if end_hour > day_end: end_hour = day_end
220+
221+
top = (start_hour - day_start) * hour_height
222+
height = (end_hour - start_hour) * hour_height
223+
224+
lane_index = event_positions[ev.MeetingId]
225+
width = 100.0 / lane_count
226+
left = lane_index * width
227+
228+
classes = ['event']
229+
if ev.Featured:
230+
classes.append('feat')
231+
232+
html.append("<a href='/Meeting/%s'>" % ev.MeetingId)
233+
html.append("<div class='%s' style='top:%dpx; height:%dpx; left:%.2f%%; width:%.2f%%'>" % (
234+
' '.join(classes), top, height, left, width))
235+
html.append("%s<br><small>%s - %s</small>" % (
236+
ev.MeetingName,
237+
ev.MeetingDate.ToString("h:mmtt").lower().replace(":00",""),
238+
ev.MeetingEnd.ToString("h:mmtt").lower().replace(":00","") if ev.MeetingEnd else ""
239+
))
240+
html.append("</div></a>")
241+
242+
html.append("</div>") # end daycol
243+
244+
html.append("</div></div>") # end week and container
245+
return "\n".join(html)
246+
247+
248+
249+
101250
if True:
102251

103-
today = datetime.date.today()
104-
year = today.year
105-
month = today.month
106-
107-
if Data.year != '':
108-
try:
109-
year = int(Data.year)
110-
except:
111-
pass
112-
if Data.month != '':
113-
try:
114-
month = int(Data.month)
115-
except:
116-
pass
117-
118-
html_calendar = generate_calendar_html(year, month)
119-
print(html_calendar)
252+
d = datetime.date.today()
253+
254+
if Data.d != '':
255+
d = Data.d.split('-')
256+
if len(d) == 2:
257+
try:
258+
d = datetime.date(int(d[0]), int(d[1]), 1)
259+
except:
260+
pass
261+
elif len(d) == 3:
262+
try:
263+
d = datetime.date(int(d[0]), int(d[1]), int(d[2]))
264+
except:
265+
pass
266+
267+
if Data.v == 'w':
268+
print generate_calendar_week_html(d)
269+
270+
else:
271+
print generate_calendar_html(d)
272+
273+
#

0 commit comments

Comments
 (0)