From 3eeab7e80b03a8ea8e7aed0b3d2988e92d4b2ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20G=C3=B3is?= Date: Tue, 14 Jan 2025 13:49:50 +0000 Subject: [PATCH] chore: new release plan template card (#9096) https://linear.app/unleash/issue/2-3125/improve-release-plan-template-cards Improves the release plan template cards. This PR introduces a new reusable `Card` component to help us render cards with the new design. The GroupCard is also adapted to use this new `Card` component in this PR, since that was the latest one to be upgraded, however other items like projects and integrations are not. We can migrate them to this new component at a later stage in separate PRs. ### Before ![image](https://github.com/user-attachments/assets/623454c7-77e9-4672-ad5b-cb6bd7cbf7f2) ### After ![image](https://github.com/user-attachments/assets/20bff73e-80d2-41b5-8f8b-de1c76e69caf) --- .../groups/GroupsList/GroupCard/GroupCard.tsx | 240 +++--------------- .../GroupCardActions.tsx | 0 .../GroupPopover/GroupPopover.tsx | 52 ---- .../GroupsList/GroupCard/GroupCardFooter.tsx | 78 ++++++ frontend/src/component/common/Card/Card.tsx | 112 ++++++++ .../ReleasePlanTemplateCard.tsx | 105 -------- .../ReleasePlanTemplateCard.tsx | 55 ++++ .../ReleasePlanTemplateCardActions.tsx | 138 ++++++++++ .../ReleasePlanTemplateCardFooter.tsx | 60 +++++ .../ReleasePlanTemplateCardMenu.tsx | 107 -------- .../ReleasePlanTemplateList.tsx | 2 +- 11 files changed, 483 insertions(+), 466 deletions(-) rename frontend/src/component/admin/groups/GroupsList/GroupCard/{GroupCardActions => }/GroupCardActions.tsx (100%) delete mode 100644 frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardAvatars/GroupPopover/GroupPopover.tsx create mode 100644 frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardFooter.tsx create mode 100644 frontend/src/component/common/Card/Card.tsx delete mode 100644 frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateCard.tsx create mode 100644 frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateCard/ReleasePlanTemplateCard.tsx create mode 100644 frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateCard/ReleasePlanTemplateCardActions.tsx create mode 100644 frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateCard/ReleasePlanTemplateCardFooter.tsx delete mode 100644 frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateCardMenu.tsx diff --git a/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCard.tsx b/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCard.tsx index f66d0e8821..02196145fd 100644 --- a/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCard.tsx +++ b/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCard.tsx @@ -1,21 +1,15 @@ -import { Box, Card, styled } from '@mui/material'; +import { styled } from '@mui/material'; import type { IGroup } from 'interfaces/group'; -import { Link, useNavigate } from 'react-router-dom'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { Badge } from 'component/common/Badge/Badge'; -import { GroupCardActions } from './GroupCardActions/GroupCardActions'; -import TopicOutlinedIcon from '@mui/icons-material/TopicOutlined'; +import { Link } from 'react-router-dom'; +import { GroupCardActions } from './GroupCardActions'; import { RoleBadge } from 'component/common/RoleBadge/RoleBadge'; import { useScimSettings } from 'hooks/api/getters/useScimSettings/useScimSettings'; -import { - AvatarComponent, - AvatarGroup, -} from 'component/common/AvatarGroup/AvatarGroup'; import GroupsIcon from '@mui/icons-material/GroupsOutlined'; import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { Highlighter } from 'component/common/Highlighter/Highlighter'; import { Truncator } from 'component/common/Truncator/Truncator'; -import { TooltipLink } from 'component/common/TooltipLink/TooltipLink'; +import { Card } from 'component/common/Card/Card'; +import { GroupCardFooter } from './GroupCardFooter'; const StyledCardLink = styled(Link)(({ theme }) => ({ color: 'inherit', @@ -27,54 +21,6 @@ const StyledCardLink = styled(Link)(({ theme }) => ({ pointer: 'cursor', })); -const StyledCard = styled(Card)(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - height: '100%', - boxShadow: 'none', - border: `1px solid ${theme.palette.divider}`, - [theme.breakpoints.down('sm')]: { - justifyContent: 'center', - }, - transition: 'background-color 0.2s ease-in-out', - backgroundColor: theme.palette.background.default, - '&:hover': { - backgroundColor: theme.palette.neutral.light, - }, - borderRadius: theme.shape.borderRadiusMedium, -})); - -const StyledCardBody = styled(Box)(({ theme }) => ({ - padding: theme.spacing(2), - display: 'flex', - flexFlow: 'column', - height: '100%', - position: 'relative', -})); - -const StyledCardBodyHeader = styled('div')(({ theme }) => ({ - display: 'flex', - gap: theme.spacing(1), - width: '100%', - alignItems: 'center', - justifyContent: 'space-between', -})); - -const StyledCardIconContainer = styled(Box)(({ theme }) => ({ - display: 'grid', - placeItems: 'center', - padding: theme.spacing(0.5), - alignSelf: 'baseline', - backgroundColor: theme.palette.secondary.light, - color: theme.palette.primary.main, - borderRadius: theme.shape.borderRadiusMedium, - '& > svg': { - height: theme.spacing(2), - width: theme.spacing(2), - }, -})); - const StyledCardTitle = styled('h3')(({ theme }) => ({ margin: 0, marginRight: 'auto', @@ -83,47 +29,6 @@ const StyledCardTitle = styled('h3')(({ theme }) => ({ lineHeight: '1.2', })); -const StyledCardDescription = styled('p')(({ theme }) => ({ - color: theme.palette.text.secondary, - fontSize: theme.fontSizes.smallBody, - marginTop: theme.spacing(2), -})); - -const StyledCardFooter = styled(Box)(({ theme }) => ({ - padding: theme.spacing(0, 2), - display: 'flex', - background: theme.palette.envAccordion.expanded, - boxShadow: theme.boxShadows.accordionFooter, - alignItems: 'center', - justifyContent: 'space-between', - borderTop: `1px solid ${theme.palette.divider}`, - minHeight: theme.spacing(6.25), -})); - -const StyledCardFooterSpan = styled('span')(({ theme }) => ({ - fontSize: theme.fontSizes.smallerBody, - color: theme.palette.text.secondary, - textWrap: 'nowrap', -})); - -const StyledAvatarComponent = styled(AvatarComponent)(({ theme }) => ({ - height: theme.spacing(2.5), - width: theme.spacing(2.5), - marginLeft: theme.spacing(-0.75), -})); - -const StyledProjectsTooltip = styled(Box)(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - gap: theme.spacing(1), - maxWidth: theme.spacing(25), -})); - -const StyledProjectBadge = styled(Badge)({ - cursor: 'pointer', - overflowWrap: 'anywhere', -}); - interface IGroupCardProps { group: IGroup; onEditUsers: (group: IGroup) => void; @@ -135,115 +40,48 @@ export const GroupCard = ({ onEditUsers, onRemoveGroup, }: IGroupCardProps) => { - const navigate = useNavigate(); - const { searchQuery } = useSearchHighlightContext(); const { settings: { enabled: scimEnabled }, } = useScimSettings(); + const isScimGroup = scimEnabled && Boolean(group.scimId); - return ( + const title = ( + + {group.name} + + ); + + const headerActions = ( <> - - - - - - - - - - {group.name} - - - - } - /> - onEditUsers(group)} - onRemove={() => onRemoveGroup(group)} - isScimGroup={isScimGroup} - /> - - - - {group.description} - - - } - /> - - - 0} - show={ - - } - elseShow={ - - This group has no users - - } - /> - 0} - show={ - - {group.projects.map((project) => ( - { - e.preventDefault(); - navigate( - `/projects/${project}/settings/access`, - ); - }} - color='secondary' - icon={} - > - {project} - - ))} - - } - > - - {group.projects.length} project - {group.projects.length !== 1 && 's'} - - - } - /> - - - + {group.rootRole && } + onEditUsers(group)} + onRemove={() => onRemoveGroup(group)} + isScimGroup={isScimGroup} + /> ); + + const body = group.description && ( + + {group.description} + + ); + + return ( + + } + headerActions={headerActions} + footer={} + > + {body} + + + ); }; diff --git a/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardActions/GroupCardActions.tsx b/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardActions.tsx similarity index 100% rename from frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardActions/GroupCardActions.tsx rename to frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardActions.tsx diff --git a/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardAvatars/GroupPopover/GroupPopover.tsx b/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardAvatars/GroupPopover/GroupPopover.tsx deleted file mode 100644 index bf028b1c00..0000000000 --- a/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardAvatars/GroupPopover/GroupPopover.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Popover, styled } from '@mui/material'; -import type { IGroupUser } from 'interfaces/group'; - -const StyledPopover = styled(Popover)(({ theme }) => ({ - pointerEvents: 'none', - '.MuiPaper-root': { - padding: theme.spacing(2), - }, -})); - -const StyledName = styled('div')(({ theme }) => ({ - color: theme.palette.text.secondary, - fontSize: theme.fontSizes.smallBody, - marginTop: theme.spacing(1), -})); - -interface IGroupPopoverProps { - user: Partial | undefined; - - open: boolean; - anchorEl: HTMLElement | null; - - onPopoverClose(event: React.MouseEvent): void; -} - -export const GroupPopover = ({ - user, - open, - anchorEl, - onPopoverClose, -}: IGroupPopoverProps) => { - return ( - - {user?.name || user?.username} -
{user?.description || user?.email}
-
- ); -}; diff --git a/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardFooter.tsx b/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardFooter.tsx new file mode 100644 index 0000000000..52813abe29 --- /dev/null +++ b/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardFooter.tsx @@ -0,0 +1,78 @@ +import type { IGroup } from 'interfaces/group'; +import { Badge } from 'component/common/Badge/Badge'; +import TopicOutlinedIcon from '@mui/icons-material/TopicOutlined'; +import { + AvatarComponent, + AvatarGroup, +} from 'component/common/AvatarGroup/AvatarGroup'; +import { TooltipLink } from 'component/common/TooltipLink/TooltipLink'; +import { Box, styled } from '@mui/material'; +import { useNavigate } from 'react-router-dom'; + +const StyledAvatarComponent = styled(AvatarComponent)(({ theme }) => ({ + height: theme.spacing(2.5), + width: theme.spacing(2.5), + marginLeft: theme.spacing(-0.75), +})); + +const StyledProjectsTooltip = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(1), + maxWidth: theme.spacing(25), +})); + +const StyledProjectBadge = styled(Badge)({ + cursor: 'pointer', + overflowWrap: 'anywhere', +}); + +interface IGroupCardFooterProps { + group: IGroup; +} + +export const GroupCardFooter = ({ group }: IGroupCardFooterProps) => { + const navigate = useNavigate(); + + return ( + <> + {group.users.length > 0 ? ( + + ) : ( + This group has no users + )} + {group.projects.length > 0 && ( + + {group.projects.map((project) => ( + { + e.preventDefault(); + navigate( + `/projects/${project}/settings/access`, + ); + }} + color='secondary' + icon={} + > + {project} + + ))} + + } + > + + {group.projects.length} project + {group.projects.length !== 1 && 's'} + + + )} + + ); +}; diff --git a/frontend/src/component/common/Card/Card.tsx b/frontend/src/component/common/Card/Card.tsx new file mode 100644 index 0000000000..5870ab7f46 --- /dev/null +++ b/frontend/src/component/common/Card/Card.tsx @@ -0,0 +1,112 @@ +import { styled, Card as MUICard, Box } from '@mui/material'; + +const StyledCard = styled(MUICard)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + height: '100%', + boxShadow: 'none', + border: `1px solid ${theme.palette.divider}`, + [theme.breakpoints.down('sm')]: { + justifyContent: 'center', + }, + transition: 'background-color 0.2s ease-in-out', + backgroundColor: theme.palette.background.default, + '&:hover': { + backgroundColor: theme.palette.neutral.light, + }, + borderRadius: theme.shape.borderRadiusMedium, +})); + +const StyledCardBody = styled(Box)(({ theme }) => ({ + padding: theme.spacing(2), + display: 'flex', + flexFlow: 'column', + height: '100%', + position: 'relative', +})); + +const StyledCardBodyHeader = styled(Box)(({ theme }) => ({ + display: 'flex', + gap: theme.spacing(1), + width: '100%', + alignItems: 'center', + justifyContent: 'space-between', + fontWeight: theme.typography.fontWeightRegular, + fontSize: theme.typography.body1.fontSize, + lineHeight: '1.2', +})); + +const StyledCardIconContainer = styled(Box)(({ theme }) => ({ + display: 'grid', + placeItems: 'center', + padding: theme.spacing(0.5), + alignSelf: 'baseline', + backgroundColor: theme.palette.secondary.light, + color: theme.palette.primary.main, + borderRadius: theme.shape.borderRadiusMedium, + '& > svg': { + height: theme.spacing(2), + width: theme.spacing(2), + }, +})); + +const StyledCardActions = styled(Box)(({ theme }) => ({ + display: 'flex', + gap: theme.spacing(1), + marginLeft: 'auto', +})); + +const StyledCardBodyContent = styled(Box)(({ theme }) => ({ + color: theme.palette.text.secondary, + fontSize: theme.fontSizes.smallBody, + marginTop: theme.spacing(2), +})); + +const StyledCardFooter = styled(Box)(({ theme }) => ({ + padding: theme.spacing(0, 2), + display: 'flex', + background: theme.palette.envAccordion.expanded, + boxShadow: theme.boxShadows.accordionFooter, + alignItems: 'center', + justifyContent: 'space-between', + borderTop: `1px solid ${theme.palette.divider}`, + minHeight: theme.spacing(6.25), + fontSize: theme.fontSizes.smallerBody, + color: theme.palette.text.secondary, + textWrap: 'nowrap', +})); + +interface ICardProps { + icon?: React.ReactNode; + title?: React.ReactNode; + headerActions?: React.ReactNode; + footer?: React.ReactNode; + children?: React.ReactNode; +} + +export const Card = ({ + icon, + title, + headerActions, + footer, + children, +}: ICardProps) => ( + + + + {icon && ( + {icon} + )} + {title} + {headerActions && ( + {headerActions} + )} + + {children && ( + {children} + )} + + {footer && {footer}} + +); diff --git a/frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateCard.tsx b/frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateCard.tsx deleted file mode 100644 index 00a658b5a1..0000000000 --- a/frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateCard.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import type { IReleasePlanTemplate } from 'interfaces/releasePlans'; -import { ReactComponent as ReleaseTemplateIcon } from 'assets/img/releaseTemplates.svg'; -import { styled, Typography } from '@mui/material'; -import { ReleasePlanTemplateCardMenu } from './ReleasePlanTemplateCardMenu'; -import { useNavigate } from 'react-router-dom'; -import { UserAvatar } from 'component/common/UserAvatar/UserAvatar'; -import useUserInfo from 'hooks/api/getters/useUserInfo/useUserInfo'; - -const StyledTemplateCard = styled('aside')(({ theme }) => ({ - height: '100%', - cursor: 'pointer', - '&:hover': { - transition: 'background-color 0.2s ease-in-out', - backgroundColor: theme.palette.neutral.light, - }, - overflow: 'hidden', -})); - -const TemplateCardHeader = styled('div')(({ theme }) => ({ - backgroundColor: theme.palette.primary.main, - padding: theme.spacing(2.5), - borderTopLeftRadius: theme.shape.borderRadiusLarge, - borderTopRightRadius: theme.shape.borderRadiusLarge, -})); - -const TemplateCardBody = styled('div')(({ theme }) => ({ - padding: theme.spacing(1.25), - border: `1px solid ${theme.palette.divider}`, - borderRadius: theme.shape.borderRadiusLarge, - borderTop: 'none', - borderTopLeftRadius: 0, - borderTopRightRadius: 0, - display: 'flex', - flexDirection: 'column', -})); - -const StyledCenter = styled('div')(({ theme }) => ({ - textAlign: 'center', -})); - -const StyledDiv = styled('div')(({ theme }) => ({ - display: 'flex', -})); - -const StyledCreatedBy = styled(Typography)(({ theme }) => ({ - color: theme.palette.text.secondary, - fontSize: theme.fontSizes.smallBody, - display: 'flex', - alignItems: 'center', - marginRight: 'auto', - gap: theme.spacing(1), -})); - -const StyledCreatedByAvatar = styled(UserAvatar)(({ theme }) => ({ - width: theme.spacing(3), - height: theme.spacing(3), -})); - -const StyledMenu = styled('div')(({ theme }) => ({ - marginLeft: theme.spacing(1), - marginTop: theme.spacing(-1), - marginBottom: theme.spacing(-1), - marginRight: theme.spacing(-1), - display: 'flex', - alignItems: 'center', -})); - -export const ReleasePlanTemplateCard = ({ - template, -}: { template: IReleasePlanTemplate }) => { - const navigate = useNavigate(); - const onClick = () => { - navigate(`/release-management/edit/${template.id}`); - }; - const { user: createdBy } = useUserInfo(`${template.createdByUserId}`); - - return ( - - - - - - - -
{template.name}
- - - Created by - - { - e.preventDefault(); - e.stopPropagation(); - }} - > - - - -
-
- ); -}; diff --git a/frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateCard/ReleasePlanTemplateCard.tsx b/frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateCard/ReleasePlanTemplateCard.tsx new file mode 100644 index 0000000000..ba7d32e0ae --- /dev/null +++ b/frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateCard/ReleasePlanTemplateCard.tsx @@ -0,0 +1,55 @@ +import type { IReleasePlanTemplate } from 'interfaces/releasePlans'; +import { ReactComponent as ReleaseTemplateIcon } from 'assets/img/releaseTemplates.svg'; +import { styled } from '@mui/material'; +import { Link } from 'react-router-dom'; +import { Card } from 'component/common/Card/Card'; +import { Truncator } from 'component/common/Truncator/Truncator'; +import { ReleasePlanTemplateCardActions } from './ReleasePlanTemplateCardActions'; +import { ReleasePlanTemplateCardFooter } from './ReleasePlanTemplateCardFooter'; + +const StyledCardLink = styled(Link)(({ theme }) => ({ + color: 'inherit', + textDecoration: 'none', + border: 'none', + padding: '0', + background: 'transparent', + fontFamily: theme.typography.fontFamily, + pointer: 'cursor', +})); + +const StyledCardTitle = styled('h3')(({ theme }) => ({ + margin: 0, + marginRight: 'auto', + fontWeight: theme.typography.fontWeightRegular, + fontSize: theme.typography.body1.fontSize, + lineHeight: '1.2', +})); + +export const ReleasePlanTemplateCard = ({ + template, +}: { template: IReleasePlanTemplate }) => ( + + } + title={ + + {template.name} + + } + headerActions={ + + } + footer={} + > + {template.description && ( + + {template.description} + + )} + + +); diff --git a/frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateCard/ReleasePlanTemplateCardActions.tsx b/frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateCard/ReleasePlanTemplateCardActions.tsx new file mode 100644 index 0000000000..00913ff12f --- /dev/null +++ b/frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateCard/ReleasePlanTemplateCardActions.tsx @@ -0,0 +1,138 @@ +import { useCallback, useState } from 'react'; +import { + IconButton, + Tooltip, + MenuItem, + ListItemText, + styled, + Popover, + MenuList, + ListItemIcon, + Typography, +} from '@mui/material'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import type { IReleasePlanTemplate } from 'interfaces/releasePlans'; +import { useReleasePlanTemplatesApi } from 'hooks/api/actions/useReleasePlanTemplatesApi/useReleasePlanTemplatesApi'; +import { useReleasePlanTemplates } from 'hooks/api/getters/useReleasePlanTemplates/useReleasePlanTemplates'; +import useToast from 'hooks/useToast'; +import { formatUnknownError } from 'utils/formatUnknownError'; +import { TemplateDeleteDialog } from '../TemplateDeleteDialog'; +import EditIcon from '@mui/icons-material/Edit'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { Link } from 'react-router-dom'; + +const StyledActions = styled('div')(({ theme }) => ({ + margin: theme.spacing(-1), + marginLeft: theme.spacing(-0.5), + display: 'flex', + justifyContent: 'center', + alignItems: 'center', +})); + +const StyledPopover = styled(Popover)(({ theme }) => ({ + borderRadius: theme.shape.borderRadiusLarge, + padding: theme.spacing(1, 1.5), +})); + +export const ReleasePlanTemplateCardActions = ({ + template, +}: { template: IReleasePlanTemplate }) => { + const [anchorEl, setAnchorEl] = useState(null); + const { deleteReleasePlanTemplate } = useReleasePlanTemplatesApi(); + const { refetch } = useReleasePlanTemplates(); + const { setToastData, setToastApiError } = useToast(); + const [deleteOpen, setDeleteOpen] = useState(false); + const deleteReleasePlan = useCallback(async () => { + try { + await deleteReleasePlanTemplate(template.id); + refetch(); + setToastData({ + type: 'success', + text: 'Release plan template deleted', + }); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); + } + }, [setToastApiError, refetch, setToastData, deleteReleasePlanTemplate]); + + const open = Boolean(anchorEl); + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + + const id = `release-plan-template-${template.id}-actions`; + const menuId = `${id}-menu`; + + return ( + { + e.preventDefault(); + e.stopPropagation(); + }} + > + + + + + + + + + + + + + + Edit template + + + + { + setDeleteOpen(true); + handleClose(); + }} + > + + + + + + Delete template + + + + + + + + ); +}; diff --git a/frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateCard/ReleasePlanTemplateCardFooter.tsx b/frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateCard/ReleasePlanTemplateCardFooter.tsx new file mode 100644 index 0000000000..bfb29eb346 --- /dev/null +++ b/frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateCard/ReleasePlanTemplateCardFooter.tsx @@ -0,0 +1,60 @@ +import type { IReleasePlanTemplate } from 'interfaces/releasePlans'; +import { Box, styled } from '@mui/material'; +import useUserInfo from 'hooks/api/getters/useUserInfo/useUserInfo'; +import theme from 'themes/theme'; +import { AvatarComponent } from 'component/common/AvatarGroup/AvatarGroup'; + +const StyledFooter = styled(Box)({ + display: 'flex', + alignItems: 'center', + gap: theme.spacing(1), +}); + +const StyledAvatar = styled(AvatarComponent)(({ theme }) => ({ + height: theme.spacing(3.5), + width: theme.spacing(3.5), + marginLeft: 0, +})); + +const StyledContainer = styled(Box)({ + display: 'flex', + flexDirection: 'column', +}); + +const StyledValue = styled('div')(({ theme }) => ({ + fontSize: theme.fontSizes.smallBody, + lineHeight: 1, + lineClamp: `1`, + WebkitLineClamp: 1, + display: '-webkit-box', + boxOrient: 'vertical', + textOverflow: 'ellipsis', + overflow: 'hidden', + alignItems: 'flex-start', + WebkitBoxOrient: 'vertical', + wordBreak: 'break-word', + maxWidth: '100%', + color: theme.palette.text.primary, +})); + +interface IReleasePlanTemplateCardFooterProps { + template: IReleasePlanTemplate; +} + +export const ReleasePlanTemplateCardFooter = ({ + template, +}: IReleasePlanTemplateCardFooterProps) => { + const { user: createdBy } = useUserInfo(`${template.createdByUserId}`); + + return ( + + + + Created by + + {createdBy.name || createdBy.username || createdBy.email} + + + + ); +}; diff --git a/frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateCardMenu.tsx b/frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateCardMenu.tsx deleted file mode 100644 index 2955cae0b3..0000000000 --- a/frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateCardMenu.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { useCallback, useState } from 'react'; -import { - IconButton, - Tooltip, - Menu, - MenuItem, - ListItemText, -} from '@mui/material'; -import MoreVertIcon from '@mui/icons-material/MoreVert'; -import type { IReleasePlanTemplate } from 'interfaces/releasePlans'; -import { useReleasePlanTemplatesApi } from 'hooks/api/actions/useReleasePlanTemplatesApi/useReleasePlanTemplatesApi'; -import { useReleasePlanTemplates } from 'hooks/api/getters/useReleasePlanTemplates/useReleasePlanTemplates'; -import useToast from 'hooks/useToast'; -import { formatUnknownError } from 'utils/formatUnknownError'; -import { TemplateDeleteDialog } from './TemplateDeleteDialog'; - -export const ReleasePlanTemplateCardMenu = ({ - template, - onClick, -}: { template: IReleasePlanTemplate; onClick: () => void }) => { - const [isMenuOpen, setIsMenuOpen] = useState(false); - const [anchorEl, setAnchorEl] = useState(null); - const { deleteReleasePlanTemplate } = useReleasePlanTemplatesApi(); - const { refetch } = useReleasePlanTemplates(); - const { setToastData, setToastApiError } = useToast(); - const [deleteOpen, setDeleteOpen] = useState(false); - const deleteReleasePlan = useCallback(async () => { - try { - await deleteReleasePlanTemplate(template.id); - refetch(); - setToastData({ - type: 'success', - text: 'Release plan template deleted', - }); - } catch (error: unknown) { - setToastApiError(formatUnknownError(error)); - } - }, [setToastApiError, refetch, setToastData, deleteReleasePlanTemplate]); - - const closeMenu = () => { - setIsMenuOpen(false); - setAnchorEl(null); - }; - - const handleMenuClick = (event: React.SyntheticEvent) => { - event.stopPropagation(); - if (isMenuOpen) { - closeMenu(); - } else { - setAnchorEl(event.currentTarget); - setIsMenuOpen(true); - } - }; - - return ( - <> - - - - - - - { - onClick(); - }} - > - Edit template - - { - setDeleteOpen(true); - closeMenu(); - }} - > - Delete template - - - - - ); -}; diff --git a/frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateList.tsx b/frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateList.tsx index 9ae7c1093c..767cec5dee 100644 --- a/frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateList.tsx +++ b/frontend/src/component/releases/ReleaseManagement/ReleasePlanTemplateList.tsx @@ -1,5 +1,5 @@ import { Grid } from '@mui/material'; -import { ReleasePlanTemplateCard } from './ReleasePlanTemplateCard'; +import { ReleasePlanTemplateCard } from './ReleasePlanTemplateCard/ReleasePlanTemplateCard'; import type { IReleasePlanTemplate } from 'interfaces/releasePlans'; interface ITemplateList {