diff --git a/grafana-plugin/src/containers/Rotation/Rotation.module.css b/grafana-plugin/src/containers/Rotation/Rotation.module.css index dac98383..20be10ea 100644 --- a/grafana-plugin/src/containers/Rotation/Rotation.module.css +++ b/grafana-plugin/src/containers/Rotation/Rotation.module.css @@ -73,21 +73,18 @@ .slots--tutorial { position: absolute; + background: rgba(61, 113, 217, 0.15); } .pointer { position: absolute; top: -9px; + transition: left 500ms ease; } .tutorial-slot { width: 175px; height: 28px; - background: rgba(61, 113, 217, 0.15); - - /* opacity: 0.15; */ - - /* background: var(--background-primary); */ border-radius: 2px; margin: 0 1px; padding: 4px; diff --git a/grafana-plugin/src/containers/Rotation/Rotation.tsx b/grafana-plugin/src/containers/Rotation/Rotation.tsx index 82effbfe..66641862 100644 --- a/grafana-plugin/src/containers/Rotation/Rotation.tsx +++ b/grafana-plugin/src/containers/Rotation/Rotation.tsx @@ -5,7 +5,7 @@ import cn from 'classnames/bind'; import dayjs from 'dayjs'; import ScheduleSlot from 'containers/ScheduleSlot/ScheduleSlot'; -import { Schedule, Event } from 'models/schedule/schedule.types'; +import { Schedule, Event, RotationFormLiveParams } from 'models/schedule/schedule.types'; import { Timezone } from 'models/timezone/timezone.types'; import { getLabel } from './Rotation.helpers'; @@ -26,6 +26,7 @@ interface RotationProps { onClick?: (moment: dayjs.Dayjs) => void; days?: number; transparent?: boolean; + tutorialParams?: RotationFormLiveParams; } const Rotation: FC = (props) => { @@ -40,6 +41,7 @@ const Rotation: FC = (props) => { onClick, days = 7, transparent = false, + tutorialParams, } = props; const [animate, _setAnimate] = useState(true); @@ -74,7 +76,7 @@ const Rotation: FC = (props) => { return (
- {/**/} + {tutorialParams && } {events ? ( events.length ? (
= (props) => { - const { startMoment, days = 7 /* shiftStart, shiftEnd, rotationStart*/ } = props; - - const shiftStart = dayjs(startMoment); - const shiftEnd = dayjs(startMoment).add(1, 'days'); - const rotationStart = dayjs(startMoment).add(1, 'days'); + const { startMoment, days = 7, shiftStart, shiftEnd, rotationStart, focusElementName } = props; const duration = shiftEnd.diff(shiftStart, 'seconds'); const events = useMemo(() => { - const events = []; - for (let i = 0; i < days; i++) { - events.push({ - start: dayjs(shiftStart).add(i, 'days'), - end: dayjs(shiftStart).add(duration, 'seconds').add(i, 'days'), - }); + return [ + { + start: dayjs(shiftStart), + end: dayjs(shiftStart).add(duration, 'seconds'), + }, + ]; + }, [shiftStart, duration]); + + const base = 60 * 60 * 24 * days; + + const pointerX = useMemo(() => { + if (focusElementName === undefined) { + return undefined; } - return events; - }, []); - const base = 60 * 60 * 24 * 7; + const moment = props[focusElementName]; + const firstEvent = events[0]; + const diff = dayjs(moment).diff(firstEvent.start, 'seconds'); - const diff = dayjs(rotationStart).diff(startMoment, 'seconds'); - - const currentTimeX = diff / base; + return diff / base; + }, [focusElementName, events, rotationStart]); const x = useMemo(() => { if (!events || !events.length) { @@ -57,20 +56,29 @@ const RotationTutorial: FC = (props) => { return (
- + {events.map((event, index) => { const duration = event.end.diff(event.start, 'seconds'); const width = duration / base; - return ; + return ( + + ); })}
); }; -const TutorialSlot = (props: { style: React.CSSProperties }) => { - const { style } = props; +const TutorialSlot = (props: { style: React.CSSProperties; active: boolean }) => { + const { style, active } = props; - return
; + return
; }; const Pointer = (props: { className: string; style: React.CSSProperties }) => { diff --git a/grafana-plugin/src/containers/RotationForm/DateTimePicker.tsx b/grafana-plugin/src/containers/RotationForm/DateTimePicker.tsx index bc265215..a2d6d7ee 100644 --- a/grafana-plugin/src/containers/RotationForm/DateTimePicker.tsx +++ b/grafana-plugin/src/containers/RotationForm/DateTimePicker.tsx @@ -12,6 +12,8 @@ interface UserTooltipProps { onChange: (value: dayjs.Dayjs) => void; disabled?: boolean; minMoment?: dayjs.Dayjs; + onFocus?: () => void; + onBlur?: () => void; } const toDate = (moment: dayjs.Dayjs, timezone: Timezone) => { @@ -28,7 +30,7 @@ const toDate = (moment: dayjs.Dayjs, timezone: Timezone) => { }; const DateTimePicker = (props: UserTooltipProps) => { - const { value: propValue, minMoment, timezone, onChange, disabled } = props; + const { value: propValue, minMoment, timezone, onChange, disabled, onFocus, onBlur } = props; const value = useMemo(() => toDate(propValue, timezone), [propValue, timezone]); @@ -66,8 +68,12 @@ const DateTimePicker = (props: UserTooltipProps) => { return ( - - +
+ +
+
+ +
); }; diff --git a/grafana-plugin/src/containers/RotationForm/RotationForm.tsx b/grafana-plugin/src/containers/RotationForm/RotationForm.tsx index 82b1e707..61be6987 100644 --- a/grafana-plugin/src/containers/RotationForm/RotationForm.tsx +++ b/grafana-plugin/src/containers/RotationForm/RotationForm.tsx @@ -233,6 +233,27 @@ const RotationForm: FC = observer((props) => { const isFormValid = useMemo(() => userGroups.some((group) => group.length), [userGroups]); + const [focusElementName, setFocusElementName] = useState(undefined); + + const getFocusHandler = (elementName: string) => { + return () => { + setFocusElementName(elementName); + }; + }; + + const handleBlur = useCallback(() => { + setFocusElementName(undefined); + }, []); + + useEffect(() => { + store.scheduleStore.setRotationFormLiveParams({ + rotationStart, + shiftStart, + shiftEnd, + focusElementName, + }); + }, [params, focusElementName]); + return ( = observer((props) => { value={rotationStart} onChange={setRotationStart} timezone={currentTimezone} + onFocus={getFocusHandler('rotationStart')} + onBlur={handleBlur} /> = observer((props) => { } > - + = observer((props) => { } > - +
{ startMoment={startMoment} currentTimezone={currentTimezone} transparent={isPreview} + tutorialParams={isPreview && store.scheduleStore.rotationFormLiveParams} /> ))} diff --git a/grafana-plugin/src/models/schedule/schedule.ts b/grafana-plugin/src/models/schedule/schedule.ts index cee1deaa..c7984a81 100644 --- a/grafana-plugin/src/models/schedule/schedule.ts +++ b/grafana-plugin/src/models/schedule/schedule.ts @@ -14,7 +14,17 @@ import { splitToLayers, splitToShiftsAndFillGaps, } from './schedule.helpers'; -import { Rotation, RotationType, Schedule, ScheduleEvent, Shift, Event, Layer, ShiftEvents } from './schedule.types'; +import { + Rotation, + RotationType, + Schedule, + ScheduleEvent, + Shift, + Event, + Layer, + ShiftEvents, + RotationFormLiveParams, +} from './schedule.types'; export class ScheduleStore extends BaseStore { @observable @@ -57,6 +67,9 @@ export class ScheduleStore extends BaseStore { @observable overridePreview?: Array<{ shiftId: Shift['id']; isPreview?: boolean; events: Event[] }>; + @observable + rotationFormLiveParams: RotationFormLiveParams = undefined; + @observable scheduleToScheduleEvents: { [id: string]: ScheduleEvent[]; @@ -187,6 +200,10 @@ export class ScheduleStore extends BaseStore { return response; } + setRotationFormLiveParams(params: RotationFormLiveParams) { + this.rotationFormLiveParams = params; + } + async updateRotationPreview( scheduleId: Schedule['id'], shiftId: Shift['id'] | 'new', @@ -227,6 +244,7 @@ export class ScheduleStore extends BaseStore { this.finalPreview = undefined; this.rotationPreview = undefined; this.overridePreview = undefined; + this.rotationFormLiveParams = undefined; } async updateRotation(shiftId: Shift['id'], params: Partial) { diff --git a/grafana-plugin/src/models/schedule/schedule.types.ts b/grafana-plugin/src/models/schedule/schedule.types.ts index ffbec8dc..8b703377 100644 --- a/grafana-plugin/src/models/schedule/schedule.types.ts +++ b/grafana-plugin/src/models/schedule/schedule.types.ts @@ -1,3 +1,5 @@ +import dayjs from 'dayjs'; + import { GrafanaTeam } from 'models/grafana_team/grafana_team.types'; import { SlackChannel } from 'models/slack_channel/slack_channel.types'; import { User } from 'models/user/user.types'; @@ -9,6 +11,13 @@ export enum ScheduleType { 'API', } +export interface RotationFormLiveParams { + rotationStart: dayjs.Dayjs; + shiftStart: dayjs.Dayjs; + shiftEnd: dayjs.Dayjs; + focusElementName: string; +} + export interface Schedule { id: string; ical_url_primary: string;