+
+
+ {order.map((eventId) => {
+ // for now we dont render delays and blocks
+ const event = rundown[eventId];
+ if (!isOntimeEvent(event)) {
+ return null;
+ }
+
+ // keep track of progress of rundown
+ if (eventStatus === 'live') {
+ eventStatus = 'future';
+ }
+ if (eventId === selectedEventId) {
+ eventStatus = 'live';
+ }
+
+ // we need to offset the start to account for midnight
+ if (!hasTimelinePassedMidnight) {
+ hasTimelinePassedMidnight = previousEventStartTime !== null && event.timeStart < previousEventStartTime;
+ }
+ const normalisedStart = hasTimelinePassedMidnight ? event.timeStart + dayInMs : event.timeStart;
+ previousEventStartTime = normalisedStart;
+
+ const { left: elementLeftPosition, width: elementWidth } = getElementPosition(
+ startHour,
+ endHour,
+ normalisedStart,
+ event.duration,
+ screenWidth,
+ );
+ const estimatedRightPosition = elementLeftPosition + getEstimatedWidth(event.title);
+ const laneLevel = getLaneLevel(rightMostElements, elementLeftPosition);
+
+ if (rightMostElements[laneLevel] === undefined || rightMostElements[laneLevel] < estimatedRightPosition) {
+ rightMostElements[laneLevel] = estimatedRightPosition;
+ }
+
+ return (
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/apps/client/src/features/timeline/Timeline/TimelineEntry.tsx b/apps/client/src/features/timeline/Timeline/TimelineEntry.tsx
new file mode 100644
index 000000000..5e155b30b
--- /dev/null
+++ b/apps/client/src/features/timeline/Timeline/TimelineEntry.tsx
@@ -0,0 +1,78 @@
+import { useClock } from '../../../common/hooks/useSocket';
+import { cx } from '../../../common/utils/styleUtils';
+import { formatDuration, formatTime } from '../../../common/utils/time';
+
+import { getStatusLabel } from './timelineUtils';
+
+import style from './Timeline.module.scss';
+
+export type ProgressStatus = 'finished' | 'live' | 'future';
+
+interface TimelineEntry {
+ colour: string;
+ duration: number;
+ isLast: boolean;
+ lane: number;
+ left: number;
+ status: ProgressStatus;
+ start: number;
+ title: string;
+ width: number;
+}
+
+const laneHeight = 120;
+const formatOptions = {
+ format12: 'hh:mm a',
+ format24: 'HH:mm',
+};
+
+export function TimelineEntry(props: TimelineEntry) {
+ const { colour, duration, isLast, lane, left, status, start, title, width } = props;
+
+ const formattedStartTime = formatTime(start, formatOptions);
+ const formattedDuration = `Dur ${formatDuration(duration)}`;
+
+ const contentClasses = cx([style.entryContent, isLast && style.lastElement]);
+
+ return (
+ <>
+