Skip to content

Commit 005c840

Browse files
committed
fixed an issue where the timetable separator was not having its top position set correctly, added documentation to all components interfaces and functions
1 parent 04e9d5e commit 005c840

8 files changed

Lines changed: 334 additions & 78 deletions

File tree

client/src/components/time_indicator.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import {
33
getVisibleTimetableRect,
44
} from "@/components/timetable";
55

6+
/*
7+
Resizes the time indicator to the correct width and positions it at the
8+
y-position corresponding to the current time.
9+
*/
610
export function resizeAndPositionTimeIndicator() {
711
const time_indicator = document.getElementById("time-indicator");
812
if (time_indicator == null) return;
@@ -41,6 +45,12 @@ export function resizeAndPositionTimeIndicator() {
4145
}
4246
}
4347

48+
/*
49+
Component consisting of a rounded label containing the text "Now" and a 2px
50+
thick line stretching across the visible area of the timetable.
51+
52+
There should only be one TimeIndicator per page.
53+
*/
4454
function TimeIndicator() {
4555
return (
4656
<>

client/src/components/time_tag.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
import { getDurationMinutes } from "@/components/timetable";
22

3+
/*
4+
@prop start_time: string of form HH:MM:SS containing the start time to display
5+
@prop end_time: string of form HH:MM:SS containing the end time to display
6+
@prop display: (optional) string describing what to display in the tag,
7+
defaults to displaying the start and end times, "duration" to display duration,
8+
or "both" to display start and end times with duration in brackets.
9+
*/
310
interface TimeTagProps {
411
start_time: string;
512
end_time: string;
613
display?: string;
714
}
815

16+
/*
17+
A small round tag containing a clock icon and either the start and end times,
18+
the duration, or both, specified by the display prop.
19+
*/
920
function TimeTag({ start_time, end_time, display }: TimeTagProps) {
1021
const time_string =
1122
start_time.substring(0, 5) + "-" + end_time.substring(0, 5);

client/src/components/timetable.tsx

Lines changed: 120 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ReactElement, useEffect } from "react";
1+
import { useEffect } from "react";
22

33
import 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+
*/
1238
export 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+
*/
3968
export 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+
*/
4782
export 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+
*/
6099
export 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+
*/
66112
function 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+
*/
80132
export 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+
*/
86147
export 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+
*/
122193
export 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+
*/
170247
export 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+
*/
178259
interface 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

Comments
 (0)