Skip to content

Commit

Permalink
feat: reject timeline state (#4517)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwasniew authored Aug 16, 2023
1 parent f561cfa commit 8a7f488
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 86 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React from 'react';
import { screen } from '@testing-library/react';
import { render } from 'utils/testRenderer';
import { ChangeRequestTimeline, determineColor } from './ChangeRequestTimeline';
import { ChangeRequestState } from '../../changeRequest.types';

test('cancelled timeline shows all states', () => {
render(<ChangeRequestTimeline state={'Cancelled'} />);

expect(screen.getByText('Draft')).toBeInTheDocument();
expect(screen.getByText('In review')).toBeInTheDocument();
expect(screen.getByText('Approved')).toBeInTheDocument();
expect(screen.getByText('Applied')).toBeInTheDocument();
});

test('approved timeline shows all states', () => {
render(<ChangeRequestTimeline state={'Approved'} />);

expect(screen.getByText('Draft')).toBeInTheDocument();
expect(screen.getByText('In review')).toBeInTheDocument();
expect(screen.getByText('Approved')).toBeInTheDocument();
expect(screen.getByText('Applied')).toBeInTheDocument();
});

test('applied timeline shows all states', () => {
render(<ChangeRequestTimeline state={'Applied'} />);

expect(screen.getByText('Draft')).toBeInTheDocument();
expect(screen.getByText('In review')).toBeInTheDocument();
expect(screen.getByText('Approved')).toBeInTheDocument();
expect(screen.getByText('Applied')).toBeInTheDocument();
});

test('rejected timeline shows all states', () => {
render(<ChangeRequestTimeline state={'Rejected'} />);

expect(screen.getByText('Draft')).toBeInTheDocument();
expect(screen.getByText('In review')).toBeInTheDocument();
expect(screen.getByText('Rejected')).toBeInTheDocument();
expect(screen.queryByText('Approved')).not.toBeInTheDocument();
expect(screen.queryByText('Applied')).not.toBeInTheDocument();
});

const irrelevantIndex = -99; // Using a number that's unlikely to be a valid index

test('returns grey for Cancelled state regardless of displayed stage', () => {
const stages: ChangeRequestState[] = [
'Draft',
'In review',
'Approved',
'Applied',
'Rejected',
];
stages.forEach(stage => {
expect(
determineColor('Cancelled', irrelevantIndex, stage, irrelevantIndex)
).toBe('grey');
});
});

test('returns error for Rejected stage in Rejected state', () => {
expect(
determineColor('Rejected', irrelevantIndex, 'Rejected', irrelevantIndex)
).toBe('error');
});

test('returns success for stages other than Rejected in Rejected state', () => {
expect(
determineColor('Rejected', irrelevantIndex, 'Draft', irrelevantIndex)
).toBe('success');
expect(
determineColor(
'Rejected',
irrelevantIndex,
'In review',
irrelevantIndex
)
).toBe('success');
});

test('returns success for stages at or before activeIndex', () => {
expect(determineColor('In review', 1, 'Draft', 0)).toBe('success');
expect(determineColor('In review', 1, 'In review', 1)).toBe('success');
});

test('returns primary for stages right after activeIndex', () => {
expect(determineColor('In review', 1, 'Approved', 2)).toBe('primary');
});

test('returns grey for stages two or more steps after activeIndex', () => {
expect(determineColor('Draft', 0, 'Approved', 2)).toBe('grey');
expect(determineColor('Draft', 0, 'Applied', 3)).toBe('grey');
});
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
import { FC } from 'react';
import { styled } from '@mui/material';
import { Box, Paper } from '@mui/material';
import { Box, Paper, styled } from '@mui/material';
import Timeline from '@mui/lab/Timeline';
import TimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem';
import TimelineSeparator from '@mui/lab/TimelineSeparator';
import TimelineDot from '@mui/lab/TimelineDot';
import TimelineConnector from '@mui/lab/TimelineConnector';
import TimelineContent from '@mui/lab/TimelineContent';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ChangeRequestState } from '../../changeRequest.types';

interface ISuggestChangeTimelineProps {
state: ChangeRequestState;
}
interface ITimelineData {
title: string;
active: boolean;
}

const StyledPaper = styled(Paper)(({ theme }) => ({
marginTop: theme.spacing(2),
Expand All @@ -34,96 +29,83 @@ const StyledTimeline = styled(Timeline)(() => ({
},
}));

const steps: ChangeRequestState[] = [
'Draft',
'In review',
'Approved',
'Applied',
];
const rejectedSteps: ChangeRequestState[] = ['Draft', 'In review', 'Rejected'];

export const determineColor = (
changeRequestState: ChangeRequestState,
changeRequestStateIndex: number,
displayStage: ChangeRequestState,
displayStageIndex: number
) => {
if (changeRequestState === 'Cancelled') return 'grey';
if (changeRequestState === 'Rejected')
return displayStage === 'Rejected' ? 'error' : 'success';
if (
changeRequestStateIndex !== -1 &&
changeRequestStateIndex >= displayStageIndex
)
return 'success';
if (changeRequestStateIndex + 1 === displayStageIndex) return 'primary';
return 'grey';
};

export const ChangeRequestTimeline: FC<ISuggestChangeTimelineProps> = ({
state,
}) => {
const createTimeLineData = (state: ChangeRequestState): ITimelineData[] => {
const steps: ChangeRequestState[] = [
'Draft',
'In review',
'Approved',
'Applied',
];

return steps.map(step => ({
title: step,
active: step === state,
}));
};

const renderTimeline = () => {
const data = createTimeLineData(state);
const index = data.findIndex(item => item.active);
const activeIndex: number | null = index !== -1 ? index : null;

if (state === 'Cancelled') {
return createCancelledTimeline(data);
}

return createTimeline(data, activeIndex);
};
const data = state === 'Rejected' ? rejectedSteps : steps;
const activeIndex = data.findIndex(item => item === state);

return (
<StyledPaper elevation={0}>
<StyledBox>
<StyledTimeline>{renderTimeline()}</StyledTimeline>
<StyledTimeline>
{data.map((title, index) => {
const color = determineColor(
state,
activeIndex,
title,
index
);
let timelineDotProps = {};

// Only add the outlined variant if it's the next step after the active one, but not for 'Draft' in 'Cancelled' state
if (
activeIndex + 1 === index &&
!(state === 'Cancelled' && title === 'Draft')
) {
timelineDotProps = { variant: 'outlined' };
}

return createTimelineItem(
color,
title,
index < data.length - 1,
timelineDotProps
);
})}
</StyledTimeline>
</StyledBox>
</StyledPaper>
);
};

const createTimeline = (data: ITimelineData[], activeIndex: number | null) => {
return data.map(({ title }, index) => {
const shouldConnectToNextItem = index < data.length - 1;

const connector = (
<ConditionallyRender
condition={shouldConnectToNextItem}
show={<TimelineConnector />}
/>
);

if (activeIndex !== null && activeIndex >= index) {
return createTimelineItem('success', title, connector);
}

if (activeIndex !== null && activeIndex + 1 === index) {
return createTimelineItem('primary', title, connector, {
variant: 'outlined',
});
}

return createTimelineItem('grey', title, connector);
});
};

const createCancelledTimeline = (data: ITimelineData[]) => {
return data.map(({ title }, index) => {
const shouldConnectToNextItem = index < data.length - 1;

const connector = (
<ConditionallyRender
condition={shouldConnectToNextItem}
show={<TimelineConnector />}
/>
);
return createTimelineItem('grey', title, connector);
});
};

const createTimelineItem = (
color: 'primary' | 'success' | 'grey',
color: 'primary' | 'success' | 'grey' | 'error',
title: string,
connector: JSX.Element,
timelineDotProps: { [key: string]: string } = {}
) => {
return (
<TimelineItem key={title}>
<TimelineSeparator>
<TimelineDot color={color} {...timelineDotProps} />
{connector}
</TimelineSeparator>
<TimelineContent>{title}</TimelineContent>
</TimelineItem>
);
};
shouldConnectToNextItem: boolean,
timelineDotProps: { [key: string]: string | undefined } = {}
) => (
<TimelineItem key={title}>
<TimelineSeparator>
<TimelineDot color={color} {...timelineDotProps} />
{shouldConnectToNextItem && <TimelineConnector />}
</TimelineSeparator>
<TimelineContent>{title}</TimelineContent>
</TimelineItem>
);

0 comments on commit 8a7f488

Please sign in to comment.