diff --git a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.tsx b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.tsx index 09a613f6ce..4412d7df95 100644 --- a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.tsx +++ b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.tsx @@ -349,7 +349,10 @@ export const ChangeRequestOverview: FC = () => { - + { describe('changeRequestScheduleProps', () => { test('returns correct props for a pending schedule', () => { + const date = new Date(); const schedule = { - scheduledAt: new Date().toISOString(), + scheduledAt: date.toISOString(), status: 'pending' as const, }; - const time = 'some time string'; - - const { title, subtitle, color, reason } = getScheduleProps( - schedule, - time, - ); + const { title, subtitle, color, reason } = getScheduleProps(schedule); expect(title).toBe('Scheduled'); - expect(subtitle).toBe(`for ${time}`); expect(color).toBe('warning'); expect(reason).toBeNull(); + + render(subtitle); + screen.getByText('for'); + const timeElement = screen.getByRole('time'); + const datetime = timeElement.getAttribute('datetime'); + expect(new Date(datetime || 1)).toEqual(date); }); test('returns correct props for a failed schedule', () => { + const date = new Date(); const schedule = { - scheduledAt: new Date().toISOString(), + scheduledAt: date.toISOString(), status: 'failed' as const, reason: 'reason', failureReason: 'failure reason', }; - const time = 'some time string'; - - const { title, subtitle, color, reason } = getScheduleProps( - schedule, - time, - ); + const { title, subtitle, color, reason } = getScheduleProps(schedule); expect(title).toBe('Schedule failed'); - expect(subtitle).toBe(`at ${time}`); expect(color).toBe('error'); expect(reason).toBeTruthy(); + + render(subtitle); + screen.getByText('at'); + const timeElement = screen.getByRole('time'); + const datetime = timeElement.getAttribute('datetime'); + expect(new Date(datetime || 1)).toEqual(date); }); test('returns correct props for a suspended schedule', () => { + const date = new Date(); const schedule = { - scheduledAt: new Date().toISOString(), + scheduledAt: date.toISOString(), status: 'suspended' as const, reason: 'reason', }; - const time = 'some time string'; - - const { title, subtitle, color, reason } = getScheduleProps( - schedule, - time, - ); + const { title, subtitle, color, reason } = getScheduleProps(schedule); expect(title).toBe('Schedule suspended'); - expect(subtitle).toBe(`was ${time}`); expect(color).toBe('grey'); expect(reason).toBeTruthy(); + + render(subtitle); + screen.getByText('was'); + const timeElement = screen.getByRole('time'); + const datetime = timeElement.getAttribute('datetime'); + expect(new Date(datetime || 1)).toEqual(date); }); }); diff --git a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestTimeline/ChangeRequestTimeline.tsx b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestTimeline/ChangeRequestTimeline.tsx index 58a8fce25d..efc25510a7 100644 --- a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestTimeline/ChangeRequestTimeline.tsx +++ b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestTimeline/ChangeRequestTimeline.tsx @@ -1,7 +1,7 @@ -import type { FC } from 'react'; +import type { FC, ReactNode } from 'react'; import { Box, Paper, styled, Typography } from '@mui/material'; import Timeline from '@mui/lab/Timeline'; -import TimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem'; +import MuiTimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem'; import TimelineSeparator from '@mui/lab/TimelineSeparator'; import TimelineDot from '@mui/lab/TimelineDot'; import TimelineConnector from '@mui/lab/TimelineConnector'; @@ -9,16 +9,16 @@ import TimelineContent from '@mui/lab/TimelineContent'; import type { ChangeRequestSchedule, ChangeRequestState, + ChangeRequestType, } from '../../changeRequest.types'; import { HtmlTooltip } from '../../../common/HtmlTooltip/HtmlTooltip.tsx'; import ErrorIcon from '@mui/icons-material/Error'; -import { - type ILocationSettings, - useLocationSettings, -} from 'hooks/useLocationSettings'; -import { formatDateYMDHMS } from 'utils/formatDate'; +import { useLocationSettings } from 'hooks/useLocationSettings'; +import { formatDateYMDHM } from 'utils/formatDate.ts'; -export type ISuggestChangeTimelineProps = +export type ISuggestChangeTimelineProps = { + timestamps?: ChangeRequestType['stateTransitionTimestamps']; // todo: update with flag `timestampsInChangeRequestTimeline` +} & ( | { state: Exclude; schedule?: undefined; @@ -26,7 +26,13 @@ export type ISuggestChangeTimelineProps = | { state: 'Scheduled'; schedule: ChangeRequestSchedule; - }; + } +); + +const StyledTimelineContent = styled(TimelineContent)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', +})); const StyledPaper = styled(Paper)(({ theme }) => ({ marginTop: theme.spacing(2), @@ -89,6 +95,7 @@ export const determineColor = ( export const ChangeRequestTimeline: FC = ({ state, schedule, + timestamps, }) => { let data: ChangeRequestState[]; switch (state) { @@ -102,17 +109,24 @@ export const ChangeRequestTimeline: FC = ({ data = steps; } const activeIndex = data.findIndex((item) => item === state); - const { locationSettings } = useLocationSettings(); return ( {data.map((title, index) => { + const timestampComponent = + index <= activeIndex && timestamps?.[title] ? ( + @@ -145,30 +165,61 @@ export const ChangeRequestTimeline: FC = ({ ); }; -const createTimelineItem = ( - color: 'primary' | 'success' | 'grey' | 'error' | 'warning', - title: string, - shouldConnectToNextItem: boolean, - timelineDotProps: { [key: string]: string | undefined } = {}, -) => ( - - - - {shouldConnectToNextItem && } - - {title} - -); +const Time = styled(({ dateTime, ...props }: { dateTime: string }) => { + const { locationSettings } = useLocationSettings(); + const displayTime = formatDateYMDHM( + new Date(dateTime || ''), + locationSettings.locale, + ); + return ( + + ); +})(({ theme }) => ({ + color: theme.palette.text.secondary, + fontSize: theme.typography.body2.fontSize, +})); + +const TimelineItem = ({ + color, + title, + shouldConnectToNextItem, + timestamp, + timelineDotProps = {}, +}: { + color: 'primary' | 'success' | 'grey' | 'error' | 'warning'; + title: string; + shouldConnectToNextItem: boolean; + timestamp?: ReactNode; + timelineDotProps: { [key: string]: string | undefined }; +}) => { + return ( + + + + {shouldConnectToNextItem && } + + + {title} + {timestamp} + + + ); +}; + +export const getScheduleProps = (schedule: ChangeRequestSchedule) => { + const Subtitle = ({ prefix }: { prefix: string }) => ( + <> + {prefix}