diff --git a/App/FeatureSet/Dashboard/src/Components/Dashboard/Toolbar/DashboardToolbar.tsx b/App/FeatureSet/Dashboard/src/Components/Dashboard/Toolbar/DashboardToolbar.tsx index e1a2ef154d..c71ce2be6a 100644 --- a/App/FeatureSet/Dashboard/src/Components/Dashboard/Toolbar/DashboardToolbar.tsx +++ b/App/FeatureSet/Dashboard/src/Components/Dashboard/Toolbar/DashboardToolbar.tsx @@ -1,6 +1,13 @@ import IconProp from "Common/Types/Icon/IconProp"; import Button, { ButtonSize, ButtonStyleType } from "Common/UI/Components/Button/Button"; -import React, { FunctionComponent, ReactElement, useState } from "react"; +import React, { + FunctionComponent, + ReactElement, + useCallback, + useEffect, + useRef, + useState, +} from "react"; import DashboardMode from "Common/Types/Dashboard/DashboardMode"; import MoreMenu from "Common/UI/Components/MoreMenu/MoreMenu"; import MoreMenuItem from "Common/UI/Components/MoreMenu/MoreMenuItem"; @@ -9,6 +16,7 @@ import RangeStartAndEndDateTime from "Common/Types/Time/RangeStartAndEndDateTime import RangeStartAndEndDateView from "Common/UI/Components/Date/RangeStartAndEndDateView"; import DashboardViewConfig, { AutoRefreshInterval, + getAutoRefreshIntervalInMs, getAutoRefreshIntervalLabel, } from "Common/Types/Dashboard/DashboardViewConfig"; import DashboardVariable from "Common/Types/Dashboard/DashboardVariable"; @@ -41,6 +49,107 @@ export interface ComponentProps { onResetZoom?: (() => void) | undefined; } +interface CountdownCircleProps { + durationMs: number; + size: number; + strokeWidth: number; + label: string; + isRefreshing: boolean; +} + +const CountdownCircle: FunctionComponent = ( + props: CountdownCircleProps, +): ReactElement => { + const [progress, setProgress] = useState(0); + const startTimeRef: React.MutableRefObject = useRef( + Date.now(), + ); + const animationFrameRef: React.MutableRefObject = + useRef(null); + + const animate: () => void = useCallback(() => { + const elapsed: number = Date.now() - startTimeRef.current; + const newProgress: number = Math.min(elapsed / props.durationMs, 1); + setProgress(newProgress); + + if (newProgress < 1) { + animationFrameRef.current = requestAnimationFrame(animate); + } else { + // Reset when complete + startTimeRef.current = Date.now(); + animationFrameRef.current = requestAnimationFrame(animate); + } + }, [props.durationMs]); + + useEffect(() => { + startTimeRef.current = Date.now(); + animationFrameRef.current = requestAnimationFrame(animate); + + return () => { + if (animationFrameRef.current) { + cancelAnimationFrame(animationFrameRef.current); + } + }; + }, [props.durationMs, animate]); + + // Reset on refresh + useEffect(() => { + if (props.isRefreshing) { + startTimeRef.current = Date.now(); + } + }, [props.isRefreshing]); + + const radius: number = (props.size - props.strokeWidth) / 2; + const circumference: number = 2 * Math.PI * radius; + const strokeDashoffset: number = circumference * (1 - progress); + const center: number = props.size / 2; + + // Calculate remaining seconds + const remainingMs: number = props.durationMs * (1 - progress); + const remainingSec: number = Math.ceil(remainingMs / 1000); + + return ( +
+
+ + {/* Background circle */} + + {/* Progress circle */} + + +
+ {remainingSec} +
+
+ {props.label} +
+ ); +}; + const DashboardToolbar: FunctionComponent = ( props: ComponentProps, ): ReactElement => { @@ -56,6 +165,12 @@ const DashboardToolbar: FunctionComponent = ( props.dashboardViewConfig.components.length > 0, ); + const isAutoRefreshActive: boolean = + props.autoRefreshInterval !== AutoRefreshInterval.OFF; + const autoRefreshMs: number | null = getAutoRefreshIntervalInMs( + props.autoRefreshInterval, + ); + return (
= ( }} > {/* Main toolbar row */} -
+
{/* Left: Icon + Title + Description */}
@@ -86,13 +201,6 @@ const DashboardToolbar: FunctionComponent = ( Editing )} - {props.isRefreshing && - props.autoRefreshInterval !== AutoRefreshInterval.OFF && ( - - - Refreshing - - )}
{props.dashboardDescription && !isEditMode && (

@@ -102,8 +210,8 @@ const DashboardToolbar: FunctionComponent = (

- {/* Right: Time range + Variables + Actions */} -
+ {/* Right: Time range + Auto-refresh + Variables + Actions */} +
{/* Time range + variables (view mode only) */} {hasComponents && !isEditMode && ( <> @@ -116,7 +224,7 @@ const DashboardToolbar: FunctionComponent = ( variables={props.variables} onVariableValueChange={props.onVariableValueChange} /> -
+
)} @@ -126,152 +234,177 @@ const DashboardToolbar: FunctionComponent = ( props.onStartAndEndDateChange(startAndEndDate); }} /> + +
+ + {/* Auto-refresh section */} +
+ {isAutoRefreshActive && autoRefreshMs ? ( +
+ +
+ ) : null} + + + {Object.values(AutoRefreshInterval).map( + (interval: AutoRefreshInterval) => { + const isSelected: boolean = + interval === props.autoRefreshInterval; + return ( + { + props.onAutoRefreshIntervalChange(interval); + }} + /> + ); + }, + )} + +
+ +
+ + {/* Reset Zoom button */} + {props.canResetZoom && props.onResetZoom && ( + <> +
- ) : ( -
- {/* Reset Zoom button */} - {props.canResetZoom && props.onResetZoom && ( -
- )} - +
)} {isSaving && (