* create report service

* write report from runtimeState

* use in UI

* clear report

* update types

* clear all from settings menu

* rearence rightclik menu

* refactor styling

* also report roll events

* ontime/under time is same colour

* refactor reporter

* add target to ontime-refetch

* remove menu

* fectch only on message from server

* memo useGetEventReport

* refactor

* use staleTime

* dont add to menu yet

* implement review

* clear all from menu

* fix merge

* start end show

* combine test for the go button text and action

* add report to menu

* extract csv utility

* report management

* unneeded async

* small refacort of triggerReportEntry

---------

Co-authored-by: Carlos Valente <carlosvalente@pm.me>
This commit is contained in:
Alex Christoffer Rasmussen
2025-02-23 17:02:09 +01:00
committed by GitHub
parent 21877e4bff
commit b6507c27a6
35 changed files with 542 additions and 72 deletions
@@ -22,7 +22,8 @@ export type EventItemActions =
| 'delete'
| 'clone'
| 'update'
| 'swap';
| 'swap'
| 'clear-report';
interface RundownEntryProps {
type: SupportedEvent;
@@ -175,7 +175,7 @@ export default function EventBlock(props: EventBlockProps) {
},
isDisabled: selectedEventId == null || selectedEventId === eventId,
},
{ withDivider: true, label: 'Clone', icon: IoDuplicateOutline, onClick: () => actionHandler('clone') },
{ withDivider: false, label: 'Clone', icon: IoDuplicateOutline, onClick: () => actionHandler('clone') },
{ withDivider: true, label: 'Delete', icon: IoTrash, onClick: () => actionHandler('delete') },
],
);
@@ -125,6 +125,7 @@ function EventBlockInner(props: EventBlockInnerProps) {
isLoaded={loaded}
totalGap={totalGap}
isLinkedAndNext={isNext && linkStart !== null}
duration={duration}
/>
)}
<div className={style.statusElements} id='block-status' data-ispublic={isPublic}>
@@ -8,7 +8,7 @@
border-radius: 2px;
&.over {
color: $playback-negative;
color: $ontime-delay-text;
}
&.under {
@@ -1,9 +1,12 @@
import { useMemo } from 'react';
import { Tooltip } from '@chakra-ui/react';
import { IoCheckmarkCircle } from '@react-icons/all-files/io5/IoCheckmarkCircle';
import { isPlaybackActive, MILLIS_PER_MINUTE, MILLIS_PER_SECOND } from 'ontime-utils';
import { usePlayback, useTimelineStatus } from '../../../../common/hooks/useSocket';
import useReport from '../../../../common/hooks-query/useReport';
import { cx } from '../../../../common/utils/styleUtils';
import { formatDuration } from '../../../../common/utils/time';
import { formatDuration, formatTime } from '../../../../common/utils/time';
import { tooltipDelayFast } from '../../../../ontimeConfig';
import style from './EventBlockChip.module.scss';
@@ -16,10 +19,11 @@ interface EventBlockChipProps {
className: string;
totalGap: number;
isLinkedAndNext: boolean;
duration: number;
}
export default function EventBlockChip(props: EventBlockChipProps) {
const { trueTimeStart, isPast, isLoaded, className, totalGap, isLinkedAndNext } = props;
const { trueTimeStart, isPast, isLoaded, className, totalGap, isLinkedAndNext, id, duration } = props;
const { playback } = usePlayback();
if (isLoaded) {
@@ -29,7 +33,7 @@ export default function EventBlockChip(props: EventBlockChipProps) {
const playbackActive = isPlaybackActive(playback);
if (!playbackActive || isPast) {
return null; //TODO: Event report will go here
return <EventReport className={className} id={id} duration={duration} />;
}
if (playbackActive) {
@@ -65,3 +69,55 @@ function EventUntil(props: EventUntilProps) {
return <div className={cx([style.chip, isDue && style.due])}>{timeUntilString}</div>;
}
interface EventReportProps {
className: string;
id: string;
duration: number;
}
function EventReport(props: EventReportProps) {
const { className, id, duration } = props;
const { data } = useReport();
const currentReport = data[id];
const [value, overUnderStyle, tooltip] = useMemo(() => {
if (!currentReport) {
return [null, 'none', ''];
}
const { startedAt, endedAt } = currentReport;
if (!startedAt || !endedAt) {
return [null, 'none', ''];
}
const actualDuration = endedAt - startedAt;
const difference = actualDuration - duration;
const absDifference = Math.abs(difference);
if (absDifference < MILLIS_PER_SECOND) {
return ['ontime', 'ontime', 'Event finished ontime'];
}
const isOver = difference > 0;
const fullTimeValue = formatTime(absDifference);
const tooltip = `Event ran ${isOver ? 'over' : 'under'} time by ${fullTimeValue}`;
const value = `${isOver ? '+' : '-'}${formatDuration(absDifference, absDifference > 2 * MILLIS_PER_MINUTE)}`;
return [value, isOver ? 'over' : 'under', tooltip];
}, [currentReport, duration]);
if (!value) {
return null;
}
return (
<Tooltip label={tooltip} openDelay={tooltipDelayFast}>
<div className={cx([style.chip, style[overUnderStyle], className])}>
{value === 'ontime' ? <IoCheckmarkCircle size='1.1rem' /> : value}
</div>
</Tooltip>
);
}