refactor: simplify time-to-end (#828)

* refactor: simplify time-to-end

* refactor: rundown metadata

* refactor: handle overflow in UI

* refactor: show gaps between days

---------

Co-authored-by: Alex Christoffer Rasmussen <ac@omnivox.dk>
This commit is contained in:
Carlos Valente
2024-03-18 17:24:11 +01:00
committed by GitHub
parent 4e6b833e10
commit a91a8a6358
19 changed files with 445 additions and 144 deletions
@@ -86,16 +86,17 @@
grid-area: 2 / 2 / 2 / 4 ;
}
.tag {
color: $label-gray;
font-size: calc(1rem - 2px);
margin-right: 0.25rem;
}
.time {
color: $section-white;
font-size: $text-body-size;
}
.tag {
color: $label-gray;
font-size: 13px;
}
.rolltag {
color: $ontime-roll;
font-size: $text-body-size;
@@ -1,6 +1,6 @@
import { Tooltip } from '@chakra-ui/react';
import { Playback } from 'ontime-types';
import { millisToMinutes, millisToSeconds, millisToString } from 'ontime-utils';
import { dayInMs, millisToMinutes, millisToSeconds, millisToString } from 'ontime-utils';
import { setPlayback, useTimer } from '../../../../common/hooks/useSocket';
import { tooltipDelayMid } from '../../../../ontimeConfig';
@@ -17,9 +17,10 @@ export default function PlaybackTimer(props: PlaybackTimerProps) {
const { playback } = props;
const timer = useTimer();
// TODO: checkout typescript in utilities
const started = millisToString(timer.startedAt);
const finish = millisToString(timer.expectedFinish);
const expectedFinish = timer.expectedFinish !== null ? timer.expectedFinish % dayInMs : null;
const finish = millisToString(expectedFinish);
const isRolling = playback === Playback.Roll;
const isStopped = playback === Playback.Stop;
const isWaiting = timer.secondaryTimer !== null && timer.secondaryTimer > 0 && timer.current === null;
@@ -72,11 +73,11 @@ export default function PlaybackTimer(props: PlaybackTimerProps) {
) : (
<>
<div className={style.start}>
<span className={style.tag}>Started at </span>
<span className={style.tag}>Started at</span>
<span className={style.time}>{started}</span>
</div>
<div className={style.finish}>
<span className={style.tag}>Finish at </span>
<span className={style.tag}>Expect end</span>
<span className={style.time}>{finish}</span>
</div>
</>
+22 -3
View File
@@ -1,5 +1,6 @@
import { useMemo } from 'react';
import { MaybeNumber } from 'ontime-types';
import { millisToString } from 'ontime-utils';
import { dayInMs, millisToString } from 'ontime-utils';
import ErrorBoundary from '../../common/components/error-boundary/ErrorBoundary';
import { useRuntimeOverview, useRuntimePlaybackOverview } from '../../common/hooks/useSocket';
@@ -19,9 +20,27 @@ function formatedTime(time: MaybeNumber) {
return millisToString(time, { fallback: timerPlaceholder });
}
function calculateEndAndDaySpan(end: MaybeNumber): [MaybeNumber, number] {
let maybeEnd = end;
let maybeDaySpan = 0;
if (end !== null) {
if (end > dayInMs) {
maybeEnd = end % dayInMs;
maybeDaySpan = Math.floor(end / dayInMs);
}
}
return [maybeEnd, maybeDaySpan];
}
export default function Overview() {
const { plannedEnd, plannedStart, actualStart, expectedEnd } = useRuntimeOverview();
const [maybePlannedEnd, maybePlannedDaySpan] = useMemo(() => calculateEndAndDaySpan(plannedEnd), [plannedEnd]);
const plannedEndText = formatedTime(maybePlannedEnd);
const [maybeExpectedEnd, maybeExpectedDaySpan] = useMemo(() => calculateEndAndDaySpan(expectedEnd), [expectedEnd]);
const expectedEndText = formatedTime(maybeExpectedEnd);
return (
<div className={style.overview}>
<ErrorBoundary>
@@ -32,8 +51,8 @@ export default function Overview() {
</div>
<RuntimeOverview />
<div className={style.column}>
<TimeRow label='Planned end' value={formatedTime(plannedEnd)} className={style.end} />
<TimeRow label='Expected end' value={formatedTime(expectedEnd)} className={style.end} />
<TimeRow label='Planned end' value={plannedEndText} className={style.end} daySpan={maybePlannedDaySpan} />
<TimeRow label='Expected end' value={expectedEndText} className={style.end} daySpan={maybeExpectedDaySpan} />
</div>
</ErrorBoundary>
</div>
@@ -20,8 +20,7 @@
flex-direction: column;
.label {
line-height: 0.9em;
line-height: 0.9em;
}
}
@@ -29,6 +28,7 @@
display: flex;
align-items: center;
gap: 0.5rem;
height: 2.25em;
.label {
text-align: right;
@@ -38,3 +38,12 @@
font-size: 1.25rem;
}
}
.daySpan {
&::after {
content: "*";
vertical-align: super;
font-size: 0.75em;
color: $blue-500;
}
}
@@ -1,3 +1,5 @@
import { Tooltip } from '@chakra-ui/react';
import { cx } from '../../../common/utils/styleUtils';
import style from './TimeLayout.module.scss';
@@ -5,6 +7,7 @@ import style from './TimeLayout.module.scss';
interface TimeLayoutProps {
label: string;
value: string;
daySpan?: number;
className?: string;
}
@@ -17,11 +20,17 @@ export function TimeColumn({ label, value, className }: TimeLayoutProps) {
);
}
export function TimeRow({ label, value, className }: TimeLayoutProps) {
export function TimeRow({ label, value, daySpan, className }: TimeLayoutProps) {
return (
<div className={style.row}>
<span className={style.label}>{label}</span>
<span className={cx([style.clock, className])}>{value}</span>
{daySpan ? (
<Tooltip label={`Event spans over ${daySpan + 1} days`}>
<span className={cx([style.clock, style.daySpan, className])}>{value}</span>
</Tooltip>
) : (
<span className={cx([style.clock, className])}>{value}</span>
)}
</div>
);
}
@@ -1,5 +1,5 @@
import { MaybeNumber } from 'ontime-types';
import { millisToString, removeLeadingZero, removeTrailingZero } from 'ontime-utils';
import { dayInMs, millisToString, removeLeadingZero, removeTrailingZero } from 'ontime-utils';
export function formatDelay(timeStart: number, delay: number): string | undefined {
if (!delay) return;
@@ -23,10 +23,13 @@ export function formatOverlap(
if (previousStart && timeStart < previousEnd) {
const overlap = timeEnd - previousStart;
if (overlap <= 0) return;
const overlapString = removeLeadingZero(millisToString(Math.abs(overlap)));
return `Overlap ${overlapString}`;
if (overlap > 0) {
const overlapString = removeLeadingZero(millisToString(Math.abs(overlap)));
return `Overlap ${overlapString}`;
}
const gap = timeStart + dayInMs - previousEnd;
const gapString = removeLeadingZero(millisToString(Math.abs(gap)));
return `Gap ${gapString} (next day)`;
}
const overlapString = removeLeadingZero(millisToString(Math.abs(overlap)));
@@ -20,20 +20,29 @@ describe('formatOverlap()', () => {
});
it('handles events the day after, without overlap', () => {
const previousStart = new Date(0).setUTCHours(11).valueOf();
const previousEnd = new Date(0).setUTCHours(12).valueOf();
const timeStart = new Date(0).setUTCHours(6).valueOf();
const timeEnd = new Date(0).setUTCHours(10).valueOf();
const previousStart = new Date(0).setUTCHours(11);
const previousEnd = new Date(0).setUTCHours(12);
const timeStart = new Date(0).setUTCHours(6);
const timeEnd = new Date(0).setUTCHours(10);
const result = formatOverlap(previousStart, previousEnd, timeStart, timeEnd);
expect(result).toBeUndefined();
expect(result).toBe('Gap 18:00:00 (next day)');
});
it('handles events the day after, with overlap', () => {
const previousStart = new Date(0).setUTCHours(9).valueOf();
const previousEnd = new Date(0).setUTCHours(10).valueOf();
const timeStart = new Date(0).setUTCHours(6).valueOf();
const timeEnd = new Date(0).setUTCHours(11).valueOf();
const previousStart = new Date(0).setUTCHours(9);
const previousEnd = new Date(0).setUTCHours(10);
const timeStart = new Date(0).setUTCHours(6);
const timeEnd = new Date(0).setUTCHours(11);
const result = formatOverlap(previousStart, previousEnd, timeStart, timeEnd);
expect(result).toBe('Overlap 02:00:00');
});
it('handles events the day after, with gap', () => {
const previousStart = new Date(0).setUTCHours(17);
const previousEnd = new Date(0).setUTCHours(23);
const timeStart = new Date(0).setUTCHours(9);
const timeEnd = new Date(0).setUTCHours(11);
const result = formatOverlap(previousStart, previousEnd, timeStart, timeEnd);
expect(result).toBe('Gap 10:00:00 (next day)');
});
});
@@ -109,12 +109,7 @@ const TimeInputFlow = (props: EventBlockTimerProps) => {
{overMidnight && (
<div className={style.timerNote}>
<Tooltip
label='Over midnight: end time is before start'
openDelay={tooltipDelayFast}
variant='ontime-ondark'
shouldWrapChildren
>
<Tooltip label='Over midnight' openDelay={tooltipDelayFast} variant='ontime-ondark' shouldWrapChildren>
<IoAlertCircleOutline />
</Tooltip>
</div>