mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: change request header (#2317)
* feat: change request header * fix: dom nesting paragraphs * fix: change path
This commit is contained in:
parent
6622346286
commit
2f1f9cecc2
@ -0,0 +1,37 @@
|
||||
import { styled } from '@mui/material';
|
||||
import { Avatar, Box, Card, Paper, Typography } from '@mui/material';
|
||||
|
||||
export const StyledPaper = styled(Paper)(({ theme }) => ({
|
||||
padding: theme.spacing(2, 4),
|
||||
borderRadius: `${theme.shape.borderRadiusLarge}px`,
|
||||
}));
|
||||
|
||||
export const StyledContainer = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 2,
|
||||
marginBottom: theme.spacing(2),
|
||||
}));
|
||||
|
||||
export const StyledInnerContainer = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing(1.5),
|
||||
}));
|
||||
|
||||
export const StyledHeader = styled(Typography)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginRight: theme.spacing(1),
|
||||
fontSize: theme.fontSizes.mainHeader,
|
||||
}));
|
||||
|
||||
export const StyledCard = styled(Card)(({ theme }) => ({
|
||||
padding: theme.spacing(0.5, 1),
|
||||
backgroundColor: theme.palette.tertiary.light,
|
||||
}));
|
||||
|
||||
export const StyledAvatar = styled(Avatar)(() => ({
|
||||
height: '30px',
|
||||
width: '30px',
|
||||
}));
|
@ -1,69 +1,60 @@
|
||||
import { Box } from '@mui/material';
|
||||
import { FC } from 'react';
|
||||
import { Avatar, Box, Card, Paper, Typography } from '@mui/material';
|
||||
import { PlaygroundResultChip } from 'component/playground/Playground/PlaygroundResultsTable/PlaygroundResultChip/PlaygroundResultChip';
|
||||
import { ReactComponent as ChangesAppliedIcon } from 'assets/icons/merge.svg';
|
||||
import { Typography } from '@mui/material';
|
||||
import TimeAgo from 'react-timeago';
|
||||
import { resolveChangeRequestStatusIcon } from 'component/changeRequest/changeRequest.utils';
|
||||
import { IChangeRequest } from 'component/changeRequest/changeRequest.types';
|
||||
import {
|
||||
StyledPaper,
|
||||
StyledContainer,
|
||||
StyledHeader,
|
||||
StyledInnerContainer,
|
||||
StyledAvatar,
|
||||
StyledCard,
|
||||
} from './ChangeRequestHeader.styles';
|
||||
|
||||
export const ChangeRequestHeader: FC<{ changeRequest: any }> = ({
|
||||
export const ChangeRequestHeader: FC<{ changeRequest: IChangeRequest }> = ({
|
||||
changeRequest,
|
||||
}) => {
|
||||
return (
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={theme => ({
|
||||
p: theme.spacing(2, 4),
|
||||
borderRadius: theme => `${theme.shape.borderRadiusLarge}px`,
|
||||
})}
|
||||
>
|
||||
<Box
|
||||
sx={theme => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 2,
|
||||
marginBottom: theme.spacing(2),
|
||||
})}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
variant="h1"
|
||||
>
|
||||
Change request
|
||||
<Typography variant="h1" component="p">
|
||||
#{changeRequest.id}
|
||||
</Typography>
|
||||
</Typography>
|
||||
<PlaygroundResultChip
|
||||
// icon={<ChangesAppliedIcon strokeWidth="0.25" />}
|
||||
label="Changes approved"
|
||||
enabled
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', verticalAlign: 'center', gap: 2 }}>
|
||||
<Typography sx={{ margin: 'auto 0' }}>
|
||||
<StyledPaper elevation={0}>
|
||||
<StyledContainer>
|
||||
<StyledHeader variant="h1">
|
||||
Change request #{changeRequest.id}
|
||||
</StyledHeader>
|
||||
{resolveChangeRequestStatusIcon(changeRequest.state)}
|
||||
</StyledContainer>
|
||||
<StyledInnerContainer>
|
||||
<Typography variant="body2" sx={{ margin: 'auto 0' }}>
|
||||
Created <TimeAgo date={new Date(changeRequest.createdAt)} />{' '}
|
||||
by
|
||||
</Typography>
|
||||
<Avatar src={changeRequest?.createdBy?.avatar} />
|
||||
<Card
|
||||
variant="outlined"
|
||||
sx={theme => ({
|
||||
padding: 1,
|
||||
backgroundColor: theme.palette.tertiary.light,
|
||||
})}
|
||||
>
|
||||
Environment:{' '}
|
||||
<Typography display="inline" fontWeight="bold">
|
||||
{changeRequest?.environment}
|
||||
</Typography>{' '}
|
||||
| Updates:{' '}
|
||||
<Typography display="inline" fontWeight="bold">
|
||||
{changeRequest?.features.length} feature toggles
|
||||
</Typography>
|
||||
</Card>
|
||||
</Box>
|
||||
</Paper>
|
||||
<StyledAvatar src={changeRequest?.createdBy?.imageUrl} />
|
||||
<Box>
|
||||
<StyledCard variant="outlined">
|
||||
<Typography variant="body2">
|
||||
Environment:{' '}
|
||||
<Typography
|
||||
display="inline"
|
||||
fontWeight="bold"
|
||||
variant="body2"
|
||||
component="span"
|
||||
>
|
||||
{changeRequest?.environment}
|
||||
</Typography>{' '}
|
||||
| Updates:{' '}
|
||||
<Typography
|
||||
variant="body2"
|
||||
display="inline"
|
||||
fontWeight="bold"
|
||||
component="span"
|
||||
>
|
||||
{changeRequest?.features.length} feature toggles
|
||||
</Typography>
|
||||
</Typography>
|
||||
</StyledCard>
|
||||
</Box>
|
||||
</StyledInnerContainer>
|
||||
</StyledPaper>
|
||||
);
|
||||
};
|
||||
|
@ -8,7 +8,7 @@ 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';
|
||||
import { ChangeRequestState } from '../../changeRequest.types';
|
||||
interface ISuggestChangeTimelineProps {
|
||||
state: ChangeRequestState;
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
export type ChangeRequestState =
|
||||
| 'Draft'
|
||||
| 'Approved'
|
||||
| 'In review'
|
||||
| 'Applied'
|
||||
| 'Cancelled';
|
@ -1,115 +1,23 @@
|
||||
import { VFC } from 'react';
|
||||
import { Chip, styled } from '@mui/material';
|
||||
import { colors } from 'themes/colors';
|
||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||
import { Check, CircleOutlined, Close } from '@mui/icons-material';
|
||||
import { resolveChangeRequestStatusIcon } from 'component/changeRequest/changeRequest.utils';
|
||||
import { ChangeRequestState } from 'component/changeRequest/changeRequest.types';
|
||||
|
||||
interface IChangeRequestStatusCellProps {
|
||||
value?: string | null;
|
||||
}
|
||||
|
||||
export enum ChangeRequestState {
|
||||
DRAFT = 'Draft',
|
||||
APPROVED = 'Approved',
|
||||
IN_REVIEW = 'In review',
|
||||
APPLIED = 'Applied',
|
||||
CANCELLED = 'Cancelled',
|
||||
REJECTED = 'Rejected',
|
||||
}
|
||||
|
||||
export const StyledChip = styled(Chip)(({ theme, icon }) => ({
|
||||
padding: theme.spacing(0, 1),
|
||||
height: 30,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
gap: theme.spacing(1, 1),
|
||||
['& .MuiChip-label']: {
|
||||
padding: 0,
|
||||
paddingLeft: Boolean(icon) ? theme.spacing(0.5) : 0,
|
||||
},
|
||||
}));
|
||||
|
||||
export const StyledRejectedChip = styled(StyledChip)(({ theme }) => ({
|
||||
border: `1px solid ${theme.palette.error.main}`,
|
||||
backgroundColor: colors.red['100'],
|
||||
['& .MuiChip-label']: {
|
||||
color: theme.palette.error.main,
|
||||
},
|
||||
['& .MuiChip-icon']: {
|
||||
color: theme.palette.error.main,
|
||||
},
|
||||
}));
|
||||
|
||||
export const StyledApprovedChip = styled(StyledChip)(({ theme }) => ({
|
||||
border: `1px solid ${theme.palette.success.main}`,
|
||||
backgroundColor: colors.green['100'],
|
||||
['& .MuiChip-label']: {
|
||||
color: theme.palette.success.main,
|
||||
},
|
||||
['& .MuiChip-icon']: {
|
||||
color: theme.palette.success.main,
|
||||
},
|
||||
}));
|
||||
|
||||
export const StyledReviewChip = styled(StyledChip)(({ theme }) => ({
|
||||
border: `1px solid ${theme.palette.primary.main}`,
|
||||
backgroundColor: colors.purple['100'],
|
||||
['& .MuiChip-label']: {
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
['& .MuiChip-icon']: {
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
}));
|
||||
|
||||
export const ChangeRequestStatusCell: VFC<IChangeRequestStatusCellProps> = ({
|
||||
value,
|
||||
}) => {
|
||||
const renderState = (state: string) => {
|
||||
switch (state) {
|
||||
case ChangeRequestState.IN_REVIEW:
|
||||
return (
|
||||
<StyledReviewChip
|
||||
label={'Review required'}
|
||||
icon={<CircleOutlined fontSize={'small'} />}
|
||||
/>
|
||||
);
|
||||
case ChangeRequestState.APPROVED:
|
||||
return (
|
||||
<StyledApprovedChip
|
||||
label={'Approved'}
|
||||
icon={<Check fontSize={'small'} />}
|
||||
/>
|
||||
);
|
||||
case ChangeRequestState.APPLIED:
|
||||
return (
|
||||
<StyledApprovedChip
|
||||
label={'Applied'}
|
||||
icon={<Check fontSize={'small'} />}
|
||||
/>
|
||||
);
|
||||
case ChangeRequestState.CANCELLED:
|
||||
return (
|
||||
<StyledRejectedChip
|
||||
label={'Cancelled'}
|
||||
icon={<Close fontSize={'small'} sx={{ mr: 8 }} />}
|
||||
/>
|
||||
);
|
||||
case ChangeRequestState.REJECTED:
|
||||
return (
|
||||
<StyledRejectedChip
|
||||
label={'Rejected'}
|
||||
icon={<Close fontSize={'small'} sx={{ mr: 8 }} />}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
const renderState = () => {
|
||||
if (!value) return null;
|
||||
return resolveChangeRequestStatusIcon(value as ChangeRequestState);
|
||||
};
|
||||
|
||||
if (!value) {
|
||||
return <TextCell />;
|
||||
}
|
||||
|
||||
return <TextCell>{renderState(value)}</TextCell>;
|
||||
return <TextCell>{renderState()}</TextCell>;
|
||||
};
|
||||
|
36
frontend/src/component/changeRequest/changeRequest.types.ts
Normal file
36
frontend/src/component/changeRequest/changeRequest.types.ts
Normal file
@ -0,0 +1,36 @@
|
||||
export type ChangeRequestState =
|
||||
| 'Draft'
|
||||
| 'Approved'
|
||||
| 'In review'
|
||||
| 'Applied'
|
||||
| 'Cancelled';
|
||||
|
||||
export interface IChangeRequest {
|
||||
id: number;
|
||||
environment: string;
|
||||
state: ChangeRequestState;
|
||||
project: string;
|
||||
createdBy: ICreatedBy;
|
||||
createdAt: string;
|
||||
features: IChangeRequestFeatures[];
|
||||
}
|
||||
|
||||
interface ICreatedBy {
|
||||
id: number;
|
||||
username: string;
|
||||
imageUrl: string;
|
||||
}
|
||||
|
||||
interface IChangeRequestFeatures {
|
||||
name: string;
|
||||
changes: IChangeRequestFeatureChanges[];
|
||||
}
|
||||
|
||||
interface IChangeRequestFeatureChanges {
|
||||
id: number;
|
||||
action: string;
|
||||
payload: unknown;
|
||||
createdAt: string;
|
||||
createdBy: ICreatedBy;
|
||||
warning?: string;
|
||||
}
|
40
frontend/src/component/changeRequest/changeRequest.utils.tsx
Normal file
40
frontend/src/component/changeRequest/changeRequest.utils.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { ChangeRequestState } from './changeRequest.types';
|
||||
import { Badge } from 'component/common/Badge/Badge';
|
||||
import { Check, CircleOutlined, Close } from '@mui/icons-material';
|
||||
|
||||
export const resolveChangeRequestStatusIcon = (state: ChangeRequestState) => {
|
||||
const reviewRequired = (
|
||||
<Badge color="secondary" icon={<CircleOutlined fontSize={'small'} />}>
|
||||
Review required
|
||||
</Badge>
|
||||
);
|
||||
switch (state) {
|
||||
case 'Draft':
|
||||
return reviewRequired;
|
||||
case 'In review':
|
||||
return reviewRequired;
|
||||
case 'Approved':
|
||||
return (
|
||||
<Badge color="success" icon={<Check fontSize={'small'} />}>
|
||||
Approved
|
||||
</Badge>
|
||||
);
|
||||
case 'Applied':
|
||||
return (
|
||||
<Badge color="success" icon={<Check fontSize={'small'} />}>
|
||||
Applied
|
||||
</Badge>
|
||||
);
|
||||
case 'Cancelled':
|
||||
return (
|
||||
<Badge
|
||||
color="error"
|
||||
icon={<Close fontSize={'small'} sx={{ mr: 8 }} />}
|
||||
>
|
||||
Cancelled
|
||||
</Badge>
|
||||
);
|
||||
default:
|
||||
return reviewRequired;
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user