1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-06-14 01:16:17 +02:00

Feat/review page timeline (#2310)

* fix: styling

* feat: overview timeline

* fix: rename types

* fix: pr comments
This commit is contained in:
Fredrik Strand Oseberg 2022-11-02 11:14:26 +01:00 committed by GitHub
parent 9fb431aab7
commit 9b10a8815b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 141 additions and 53 deletions

View File

@ -7,9 +7,9 @@ import { ChangeRequestReviewers } from './ChangeRequestReviewers/ChangeRequestRe
import { ChangeRequest } from '../ChangeRequest/ChangeRequest'; import { ChangeRequest } from '../ChangeRequest/ChangeRequest';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
import { ChangeRequestReviewStatus } from './ChangeRequestReviewStatus/ChangeRequestReviewStatus';
import useToast from 'hooks/useToast'; import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError'; import { formatUnknownError } from 'utils/formatUnknownError';
import { ChangeRequestReviewStatus } from './ChangeRequestReviewStatus/ChangeRequestReviewStatus';
export const ChangeRequestOverview: FC = () => { export const ChangeRequestOverview: FC = () => {
const projectId = useRequiredPathParam('projectId'); const projectId = useRequiredPathParam('projectId');
@ -46,7 +46,7 @@ export const ChangeRequestOverview: FC = () => {
flexDirection: 'column', flexDirection: 'column',
}} }}
> >
<ChangeRequestTimeline /> <ChangeRequestTimeline state={changeRequest.state} />
<ChangeRequestReviewers /> <ChangeRequestReviewers />
</Box> </Box>
<Paper <Paper
@ -66,7 +66,13 @@ export const ChangeRequestOverview: FC = () => {
})} })}
> >
<ChangeRequest changeRequest={changeRequest} /> <ChangeRequest changeRequest={changeRequest} />
<ChangeRequestReviewStatus approved={true} /> <ChangeRequestReviewStatus
approved={
changeRequest.state === 'Approved' ||
changeRequest.state === 'Applied'
}
/>
<Button <Button
variant="contained" variant="contained"
sx={{ marginTop: 2 }} sx={{ marginTop: 2 }}

View File

@ -3,7 +3,7 @@ import { Cancel, CheckCircle } from '@mui/icons-material';
import { Box, Typography, Divider } from '@mui/material'; import { Box, Typography, Divider } from '@mui/material';
const styledComponentPropCheck = () => (prop: string) => const styledComponentPropCheck = () => (prop: string) =>
prop !== 'color' && prop !== 'sx'; prop !== 'color' && prop !== 'sx' && prop !== 'approved';
export const StyledFlexAlignCenterBox = styled(Box)(({ theme }) => ({ export const StyledFlexAlignCenterBox = styled(Box)(({ theme }) => ({
display: 'flex', display: 'flex',

View File

@ -12,7 +12,6 @@ import {
StyledReviewTitle, StyledReviewTitle,
StyledDivider, StyledDivider,
} from './ChangeRequestReviewStatus.styles'; } from './ChangeRequestReviewStatus.styles';
interface ISuggestChangeReviewsStatusProps { interface ISuggestChangeReviewsStatusProps {
approved: boolean; approved: boolean;
} }
@ -30,13 +29,11 @@ export const ChangeRequestReviewStatus: FC<
/> />
</StyledButtonContainer> </StyledButtonContainer>
<StyledReviewStatusContainer approved={approved}> <StyledReviewStatusContainer approved={approved}>
<StyledFlexAlignCenterBox>
<ConditionallyRender <ConditionallyRender
condition={approved} condition={approved}
show={<Approved approved={approved} />} show={<Approved approved={approved} />}
elseShow={<ReviewRequired approved={approved} />} elseShow={<ReviewRequired approved={approved} />}
/> />
</StyledFlexAlignCenterBox>
</StyledReviewStatusContainer> </StyledReviewStatusContainer>
</StyledOuterContainer> </StyledOuterContainer>
); );

View File

@ -1,4 +1,5 @@
import { FC } from 'react'; import { FC } from 'react';
import { styled } from '@mui/material';
import { Box, Paper } from '@mui/material'; import { Box, Paper } from '@mui/material';
import Timeline from '@mui/lab/Timeline'; import Timeline from '@mui/lab/Timeline';
import TimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem'; import TimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem';
@ -6,47 +7,123 @@ import TimelineSeparator from '@mui/lab/TimelineSeparator';
import TimelineDot from '@mui/lab/TimelineDot'; import TimelineDot from '@mui/lab/TimelineDot';
import TimelineConnector from '@mui/lab/TimelineConnector'; import TimelineConnector from '@mui/lab/TimelineConnector';
import TimelineContent from '@mui/lab/TimelineContent'; 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;
}
export const ChangeRequestTimeline: FC = () => { const StyledPaper = styled(Paper)(({ theme }) => ({
return (
<Paper
elevation={0}
sx={theme => ({
marginTop: theme.spacing(2), marginTop: theme.spacing(2),
borderRadius: theme => `${theme.shape.borderRadiusLarge}px`, borderRadius: `${theme.shape.borderRadiusLarge}px`,
})} }));
>
<Box sx={theme => ({ padding: theme.spacing(2) })}> const StyledBox = styled(Box)(({ theme }) => ({
<Timeline padding: theme.spacing(2),
sx={{ marginBottom: `-${theme.spacing(4)}`,
}));
const StyledTimeline = styled(Timeline)(() => ({
[`& .${timelineItemClasses.root}:before`]: { [`& .${timelineItemClasses.root}:before`]: {
flex: 0, flex: 0,
padding: 0, padding: 0,
}, },
}} }));
>
<TimelineItem> export const ChangeRequestTimeline: FC<ISuggestChangeTimelineProps> = ({
<TimelineSeparator> state,
<TimelineDot color="success" /> }) => {
<TimelineConnector color="success" /> const createTimeLineData = (state: ChangeRequestState): ITimelineData[] => {
</TimelineSeparator> const steps: ChangeRequestState[] = [
<TimelineContent>Draft</TimelineContent> 'Draft',
</TimelineItem> 'In review',
<TimelineItem> 'Approved',
<TimelineSeparator> 'Applied',
<TimelineDot color="success" /> ];
<TimelineConnector />
</TimelineSeparator> return steps.map(step => ({
<TimelineContent>Approved</TimelineContent> title: step,
</TimelineItem> active: step === state,
<TimelineItem> }));
<TimelineSeparator> };
<TimelineDot />
</TimelineSeparator> const renderTimeline = () => {
<TimelineContent>Applied</TimelineContent> const data = createTimeLineData(state);
</TimelineItem> const index = data.findIndex(item => item.active);
</Timeline> const activeIndex: number | null = index !== -1 ? index : null;
</Box>
</Paper> if (state === 'Cancelled') {
return createCancelledTimeline(data);
}
return createTimeline(data, activeIndex);
};
return (
<StyledPaper elevation={0}>
<StyledBox>
<StyledTimeline>{renderTimeline()}</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',
title: string,
connector: JSX.Element,
timelineDotProps: { [key: string]: string } = {}
) => {
return (
<TimelineItem key={title}>
<TimelineSeparator>
<TimelineDot color={color} {...timelineDotProps} />
{connector}
</TimelineSeparator>
<TimelineContent>{title}</TimelineContent>
</TimelineItem>
); );
}; };

View File

@ -0,0 +1,6 @@
export type ChangeRequestState =
| 'Draft'
| 'Approved'
| 'In review'
| 'Applied'
| 'Cancelled';

View File

@ -84,6 +84,8 @@ export const FeatureView = () => {
return <FeatureNotFound />; return <FeatureNotFound />;
} }
console.log(uiConfig?.flags);
return ( return (
<MainLayout <MainLayout
ref={ref} ref={ref}

View File

@ -124,7 +124,7 @@ const Project = () => {
<MainLayout <MainLayout
ref={ref} ref={ref}
subheader={ subheader={
!uiConfig?.flags?.changeRequests ? ( uiConfig?.flags?.changeRequests ? (
<DraftBanner project={projectId} /> <DraftBanner project={projectId} />
) : null ) : null
} }