Files
ontime/apps/client/src/views/timeline/TimelineEntry.tsx
T
2026-04-06 19:49:30 +02:00

171 lines
4.7 KiB
TypeScript

import { Day } from 'ontime-types';
import { CSSProperties, RefObject } from 'react';
import { useExpectedStartData, useTimer } from '../../common/hooks/useSocket';
import { getProgress } from '../../common/utils/getProgress';
import { alpha, cx } from '../../common/utils/styleUtils';
import { formatDuration, formatTime, getExpectedTimesFromExtendedEvent } from '../../common/utils/time';
import { useTranslation } from '../../translation/TranslationProvider';
import { getStatusLabel } from './timeline.utils';
import style from './Timeline.module.scss';
export type ProgressStatus = 'done' | 'live' | 'future';
interface TimelineEntryProps {
colour: string;
delay: number;
duration: number;
hasLink: boolean;
left: number;
status: ProgressStatus;
start: number;
dayOffset: Day;
totalGap: number;
isLinkedToLoaded: boolean;
title: string;
width: number;
cue: string;
ref?: RefObject<HTMLDivElement | null>;
}
const formatOptions = {
format12: 'h:mm a',
format24: 'HH:mm',
};
export function TimelineEntry({
colour,
delay,
duration,
hasLink,
left,
status,
start,
dayOffset,
totalGap,
isLinkedToLoaded,
title,
width,
cue,
ref,
}: TimelineEntryProps) {
const formattedStartTime = formatTime(start, formatOptions);
const formattedDuration = formatDuration(duration);
const delayedStart = start + delay;
const hasDelay = delay > 0;
const lighterColour = alpha(colour, 0.7);
const showTitle = width > 20;
const smallArea = width < 40;
return (
<div
ref={ref}
className={cx([style.column, smallArea && style.smallArea])}
style={
{
'--color': colour,
'--lighter': lighterColour ?? '',
left: `${left}px`,
width: `${width}px`,
} as CSSProperties
}
data-testid={cue}
>
{status === 'live' ? <ActiveBlock /> : <div data-status={status} className={style.timelineBlock} />}
<div
className={cx([style.content, width < 20 && style.hide, !hasLink && style.separeLeft])}
data-status={status}
style={
{
'--color': colour,
} as CSSProperties
}
>
<div className={style.maybeInline}>
<div className={cx([hasDelay && style.cross])}>{formattedStartTime}</div>
{hasDelay && <div className={style.delay}>{formatTime(delayedStart, formatOptions)}</div>}
{smallArea && (
<TimelineEntryStatus
delay={delay}
start={start}
dayOffset={dayOffset}
totalGap={totalGap}
isLinkedToLoaded={isLinkedToLoaded}
status={status}
/>
)}
{smallArea && <div className={style.title}>{title}</div>}
</div>
{showTitle && (
<>
{!smallArea && (
<TimelineEntryStatus
delay={delay}
start={start}
dayOffset={dayOffset}
totalGap={totalGap}
isLinkedToLoaded={isLinkedToLoaded}
status={status}
/>
)}
{!smallArea && <div className={style.title}>{title}</div>}
</>
)}
</div>
<div className={style.timeOverview} data-status={status}>
{status !== 'done' && <div className={style.duration}>{formattedDuration}</div>}
</div>
</div>
);
}
interface TimelineEntryStatusProps {
delay: number;
start: number;
dayOffset: Day;
totalGap: number;
isLinkedToLoaded: boolean;
status: ProgressStatus;
}
// extract component to isolate re-renders provoked by the clock changes
function TimelineEntryStatus({
delay,
start,
dayOffset,
totalGap,
isLinkedToLoaded,
status,
}: TimelineEntryStatusProps) {
const state = useExpectedStartData();
const { getLocalizedString } = useTranslation();
const { timeToStart } = getExpectedTimesFromExtendedEvent(
{ timeStart: start, delay, dayOffset, totalGap, isLinkedToLoaded, countToEnd: false, duration: 0 },
state,
);
let statusText = getStatusLabel(timeToStart, status);
if (statusText === 'live') {
statusText = getLocalizedString('timeline.live');
} else if (statusText === 'pending') {
statusText = getLocalizedString('timeline.due');
}
const isDue = status === 'future' && timeToStart <= 0;
return <div className={cx([style.status, isDue && style.due])}>{statusText}</div>;
}
/** Generates a block level progress bar */
function ActiveBlock() {
const { current, duration } = useTimer();
const progress = getProgress(current, duration);
return (
<div data-status='live' className={style.timelineBlock} style={{ '--progress': `${progress}%` } as CSSProperties} />
);
}