1- import { ReactElement , useEffect } from "react" ;
1+ import { useEffect } from "react" ;
22
33import TimeIndicator , {
44 resizeAndPositionTimeIndicator ,
@@ -7,8 +7,34 @@ import {
77 TimetableDayColumn ,
88 TimetableTimeColumn ,
99} from "@/components/timetable_column" ;
10- import { resizeAndPositionTimetableTasks } from "@/components/timetable_task" ;
11-
10+ import TimetableTask , {
11+ resizeAndPositionTimetableTasks ,
12+ TimetableTaskProps ,
13+ } from "@/components/timetable_task" ;
14+
15+ /*
16+ KNOWN BUGS:
17+ 1. Sticky positioning does not work at certain zooms/screen sizes towards the end
18+ of scroll.
19+ 2. Using padding on the TimetableTask containers looks the nicest but causes
20+ the resizing width to reach a minimum before disappearing rather than smoothly
21+ resizing until 0. Using margin on the content looks worse but does not have this
22+ issue.
23+ */
24+
25+ /*
26+ Returns a rect {left, right, top, bottom, width, height} of the visible area of
27+ the timetable where timetable tasks are to be rendered.
28+
29+ Visible area is the area:
30+ - Within timetable-barrier's bounding client rect
31+ - Right of timetable-separator (right of time labels)
32+ - Below time-header(s))
33+
34+ Returns nothing if any of the required elements are not found (that is, elements
35+ with id: time-header, timetable-barrier, timetable-separator, and
36+ timetable-barrier).
37+ */
1238export function getVisibleTimetableRect ( ) {
1339 const time_header = document . getElementById ( "time-header" ) ;
1440 if ( time_header == null ) return ;
@@ -36,6 +62,9 @@ export function getVisibleTimetableRect() {
3662 return rect ;
3763}
3864
65+ /*
66+ Returns the number of minutes between two strings of format "HH:MM:SS".
67+ */
3968export function getDurationMinutes ( start_time : string , end_time : string ) {
4069 const start_hour : number = + start_time . substring ( 0 , 2 ) ;
4170 const start_minute : number = + start_time . substring ( 3 , 5 ) ;
@@ -44,45 +73,77 @@ export function getDurationMinutes(start_time: string, end_time: string) {
4473 return 60 * ( end_hour - start_hour ) + ( end_minute - start_minute ) ;
4574}
4675
76+ /*
77+ Returns the y-position (top) of any time on the timetable.
78+
79+ Returns nothing if the hour is not in range [0-23] as time-label with matching
80+ id will not exist.
81+ */
4782export function getTimeTopPosition ( hour : number , mins : number ) {
4883 const hour_label = document . getElementById ( hour + ":00:00" ) ;
4984 if ( hour_label == null ) return ;
5085
5186 const hour_rect = hour_label . getBoundingClientRect ( ) ;
52- if ( hour_rect == undefined ) return ;
5387
5488 const offset = ( mins / 60 ) * hour_rect . height ;
5589 const top = hour_rect . top + offset ;
5690
5791 return top ;
5892}
5993
94+ /*
95+ Returns the x-position (left) of any day (string) on the timetable.
96+
97+ Returns early if an element with the id corresponding to the day is not found.
98+ */
6099export function getDayLeftPosition ( day : string ) {
61100 const day_label = document . getElementById ( day ) ;
62101 if ( day_label == null ) return ;
63102 return day_label . getBoundingClientRect ( ) . left ;
64103}
65104
105+ /*
106+ Sets the size and position of the Timetable Separator to directly right of the
107+ time label column and taking up the entire visible vertical space of the
108+ timetable.
109+
110+ Returns nothing if elements with id: timetable-separator, Time do not exist.
111+ */
66112function resizeAndPositionTimetableSeparator ( ) {
67113 const separator = document . getElementById ( "timetable-separator" ) ;
68114 if ( separator == null ) return ;
69115
70116 const time_col = document . getElementById ( "Time" ) ;
71117 if ( time_col == null ) return ;
72-
73118 const col_rect = time_col . getBoundingClientRect ( ) ;
74- if ( col_rect == undefined ) return ;
119+
120+ const corner_cell = document . getElementById ( "time-header" ) ;
121+ if ( corner_cell == null ) return ;
122+ const corner_rect = corner_cell . getBoundingClientRect ( ) ;
75123
76124 separator . style . left = col_rect . right + "px" ;
77125 separator . style . height = col_rect . height + "px" ;
126+ separator . style . top = corner_rect . top + "px" ;
78127}
79128
129+ /*
130+ Calls all of the resizeAndPosition functions.
131+ */
80132export function resizeTimetableElements ( ) {
81133 resizeAndPositionTimetableSeparator ( ) ;
82134 resizeAndPositionTimetableTasks ( ) ;
83135 resizeAndPositionTimeIndicator ( ) ;
84136}
85137
138+ /*
139+ Sets the vertical scroll of the timetable-barrier to make the current time
140+ visible.
141+
142+ @param align: An optional string argument indicating where the now position
143+ should be aligned to in the visible area of the timetable. By default,
144+ sets the now position to the center of the visible area, can additionally be
145+ specified "top" or "bottom".
146+ */
86147export function scrollToCurrentTime ( align ?: string ) {
87148 const timetable_barrier = document . getElementById ( "timetable-barrier" ) ;
88149 if ( timetable_barrier == null ) return ;
@@ -119,6 +180,16 @@ export function scrollToCurrentTime(align?: string) {
119180 timetable_barrier . scrollTop = scroll_amount ;
120181}
121182
183+ /*
184+ Sets the horizontal scroll position of the timetable-barrier to display the
185+ column of the current day positioned in the center/left/right specified by
186+ align argument.
187+
188+ @param align: An optional String argument that controls whether the scroll
189+ position should be set to align the current day to the left, right or center
190+ of the visible timetable area. By default center, otherwise can be specified
191+ "left" or "right".
192+ */
122193export function scrollToCurrentDay ( align ?: string ) {
123194 const timetable_barrier = document . getElementById ( "timetable-barrier" ) ;
124195 if ( timetable_barrier == null ) return ;
@@ -167,6 +238,12 @@ export function scrollToCurrentDay(align?: string) {
167238 timetable_barrier . scrollLeft = scroll_amount ;
168239}
169240
241+ /*
242+ Scrolls the vertical and horizonal scroll of timetable-barrier to position
243+ the current time and current day within the visible area of the timetable.
244+
245+ Calls scrollToCurrentTime and scrollToCurrentDay.
246+ */
170247export function scrollToCurrentTimeAndDay (
171248 time_align ?: string ,
172249 day_align ?: string ,
@@ -175,11 +252,25 @@ export function scrollToCurrentTimeAndDay(
175252 scrollToCurrentDay ( day_align ) ;
176253}
177254
255+ /*
256+ @prop timetable_tasks_props: TimetableTaskProps to be rendered as TimetableTasks
257+ within the timetable.
258+ */
178259interface TimetableProps {
179- children : ReactElement [ ] ;
260+ timetable_tasks_props : TimetableTaskProps [ ] ;
180261}
181262
182- function Timetable ( { children } : TimetableProps ) {
263+ /*
264+ A Timetable composed of 8 columns (header + each day) and 25 rows
265+ (header + each hour). The header row and leftmost column are sticky to allow
266+ user to scroll without positional information (day and time) disappearing.
267+
268+ Takes 1 prop containing the props for TimetableTasks to be rendered within the
269+ timetable.
270+
271+ There can only be one timetable per page as the logic uses ids.
272+ */
273+ function Timetable ( { timetable_tasks_props } : TimetableProps ) {
183274 useEffect ( ( ) => {
184275 resizeTimetableElements ( ) ;
185276 scrollToCurrentTimeAndDay ( ) ;
@@ -200,24 +291,28 @@ function Timetable({ children }: TimetableProps) {
200291 className = "timetable flex h-full w-full flex-row gap-[4px]"
201292 >
202293 < TimetableTimeColumn />
203- < TimetableDayColumn day = "Monday" label = "Monday" />
204- < TimetableDayColumn day = "Tuesday" label = "Tuesday" />
205- < TimetableDayColumn day = "Wednesday" label = "Wednesday" />
206- < TimetableDayColumn day = "Thursday" label = "Thursday" />
207- < TimetableDayColumn day = "Friday" label = "Friday" />
208- < TimetableDayColumn day = "Saturday" label = "Saturday" />
209- < TimetableDayColumn day = "Sunday" label = "Sunday" />
210- < TimeIndicator />
211- < div
212- id = "timetable-tasks"
213- className = "timetable-tasks absolute left-0 top-0"
214- >
215- { children }
294+ < TimetableDayColumn day = "Monday" />
295+ < TimetableDayColumn day = "Tuesday" />
296+ < TimetableDayColumn day = "Wednesday" />
297+ < TimetableDayColumn day = "Thursday" />
298+ < TimetableDayColumn day = "Friday" />
299+ < TimetableDayColumn day = "Saturday" />
300+ < TimetableDayColumn day = "Sunday" />
301+ < div id = "timetable-foreground" className = "absolute left-0 top-0" >
302+ < TimeIndicator />
303+ < div
304+ id = "timetable-tasks"
305+ className = "timetable-tasks absolute left-0 top-0"
306+ >
307+ { timetable_tasks_props . map ( ( props ) => (
308+ < TimetableTask key = { props . id } { ...props } />
309+ ) ) }
310+ </ div >
311+ < div
312+ id = "timetable-separator"
313+ className = "absolute z-[10] w-[4px] bg-slate-900"
314+ > </ div >
216315 </ div >
217- < div
218- id = "timetable-separator"
219- className = "absolute z-[10] w-[4px] bg-slate-900"
220- > </ div >
221316 </ div >
222317 </ div >
223318 </ div >
0 commit comments