Fix(ui): Task assignee new styling (#21049)

* Task asignee new styling

* Task asignee new styling

* fixed styling of asignee

* removed unused prop

* replaced position of edit button in tasktab

* fixed sonar test

* renamed props

* fixed comments

* fixed comments

* fixed alignment issue of owner label

* removed zIndex from owner label

* removed unnecessery classes
This commit is contained in:
Dhruv Parmar 2025-05-14 11:46:30 +05:30 committed by GitHub
parent 2465ce391e
commit 7fed86cc99
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 281 additions and 138 deletions

View File

@ -18,7 +18,6 @@ import { isEmpty, isEqual, isUndefined, lowerCase } from 'lodash';
import React, { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import { ReactComponent as AssigneesIcon } from '../../../assets/svg/ic-assignees.svg';
import { ReactComponent as TaskCloseIcon } from '../../../assets/svg/ic-close-task.svg';
import { ReactComponent as TaskOpenIcon } from '../../../assets/svg/ic-open-task.svg';
import { ReactComponent as ReplyIcon } from '../../../assets/svg/ic-reply-2.svg';
@ -359,8 +358,8 @@ const TaskFeedCard = ({
? 'task-card-assignee'
: ''
}`}>
<AssigneesIcon height={20} width={20} />
<OwnerLabel
isAssignee
avatarSize={24}
isCompactView={false}
owners={feed?.task?.assignees}

View File

@ -75,7 +75,9 @@
color: @primary-heading-color;
max-width: 200px;
}
.owner-label-container {
max-width: 148px;
}
.domain-link-text.render-domain-lebel-style {
color: @grey-700;
display: inline-block;

View File

@ -48,7 +48,6 @@ import React, {
} from 'react';
import { useTranslation } from 'react-i18next';
import { Link, useHistory } from 'react-router-dom';
import { ReactComponent as EditIcon } from '../../../../assets/svg/edit-new.svg';
import { ReactComponent as AssigneesIcon } from '../../../../assets/svg/ic-assignees.svg';
import { ReactComponent as TaskCloseIcon } from '../../../../assets/svg/ic-close-task.svg';
import { ReactComponent as TaskOpenIcon } from '../../../../assets/svg/ic-open-task.svg';
@ -435,6 +434,8 @@ export const TaskTabNew = ({
const [hasAddedComment, setHasAddedComment] = useState<boolean>(false);
const [recentComment, setRecentComment] = useState<string>('');
const shouldEditAssignee =
(isCreator || hasEditAccess) && !isTaskClosed && owners.length === 0;
const onSave = () => {
postFeed(comment, taskThread?.id ?? '')
.catch(() => {
@ -838,6 +839,12 @@ export const TaskTabNew = ({
);
setUsersList(filterData);
} catch (error) {
showErrorToast(
error as AxiosError,
t('server.entity-fetch-error', {
entity: t('label.assignee'),
})
);
setUsersList([]);
}
}, []);
@ -858,6 +865,10 @@ export const TaskTabNew = ({
setTaskAction(latestAction);
}, [latestAction]);
const handleEditClick = () => {
setIsEditAssignee(true);
};
const taskHeader = isTaskTestCaseResult ? (
<TaskTabIncidentManagerHeaderNew thread={taskThread} />
) : (
@ -937,13 +948,13 @@ export const TaskTabNew = ({
</Form>
) : (
<>
<Col className="flex items-center gap-2 text-grey-muted" span={8}>
<Col className="flex gap-2 text-grey-muted" span={8}>
<AssigneesIcon height={16} />
<Typography.Text className="incident-manager-details-label @grey-8">
{t('label.assignee-plural')}
</Typography.Text>
</Col>
<Col className="flex items-center gap-2" span={16}>
<Col className="flex gap-2" span={16}>
{taskThread?.task?.assignees?.length === 1 ? (
<div className="d-flex items-center gap-2">
<UserPopOverCard
@ -965,24 +976,15 @@ export const TaskTabNew = ({
</div>
) : (
<OwnerLabel
isAssignee
avatarSize={24}
hasPermission={shouldEditAssignee}
isCompactView={false}
owners={taskThread?.task?.assignees}
showLabel={false}
onEditClick={handleEditClick}
/>
)}
{(isCreator || hasEditAccess) &&
!isTaskClosed &&
owners.length === 0 ? (
<Button
className="flex-center p-0 h-auto"
data-testid="edit-assignees"
icon={<EditIcon width="14px" />}
size="small"
type="text"
onClick={() => setIsEditAssignee(true)}
/>
) : null}
</Col>
</>
)}

View File

@ -10,113 +10,112 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import (reference) '../../styles/variables.less';
@import (reference) '../../styles/variables.less';
.navbar-container {
display: flex;
align-items: center;
justify-content: space-between;
> div {
height: 40px;
.ant-input-wrapper.ant-input-group,
.ant-input-affix-wrapper {
height: 40px;
}
}
.search-container {
width: 35vw;
}
.app-user-icon {
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
.username {
font-weight: 500;
line-height: 21px;
}
}
}
.domain-dropdown-menu {
max-height: 400px;
overflow-y: auto;
}
.slide-in-top {
animation: slide-in-top 1s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
}
@keyframes slide-in-top {
0% {
transform: translate(-50%, 100%);
}
100% {
transform: translate(-50%, 0);
}
}
.ant-btn.domain-nav-btn {
background-color: @white;
border: 1px solid @grey-15;
color: @text-color;
height: 40px;
.domain-text {
max-width: 15vw;
color: inherit;
}
&:hover,
&:focus {
background-color: @primary-button-background;
border: 1px solid @alert-info-icon-bg-1;
color: @text-color;
}
&.domain-active {
color: @primary-color;
border: 1px solid @primary-color;
background-color: @primary-button-background;
}
}
.ant-alert.ant-alert-info.refresh-alert {
width: 470px;
bottom: 30px;
align-items: center;
z-index: 9999;
margin: 0 auto;
box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.12);
border-radius: 10px;
border: 1px solid @text-color;
background: @text-color;
color: @white;
position: fixed;
left: 50%;
transform: translateX(-50%);
.ant-alert-message {
color: @white;
}
.ant-alert-description {
color: @grey-4;
}
.ant-alert-content {
border-right: 1px solid @white;
}
.ant-btn {
font-weight: 700;
color: @white;
}
}
.navbar-container {
display: flex;
align-items: center;
justify-content: space-between;
> div {
height: 40px;
.ant-input-wrapper.ant-input-group,
.ant-input-affix-wrapper {
height: 40px;
}
}
.search-container {
width: 35vw;
}
.app-user-icon {
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
.username {
font-weight: 500;
line-height: 21px;
}
}
}
.domain-dropdown-menu {
max-height: 400px;
overflow-y: auto;
}
.slide-in-top {
animation: slide-in-top 1s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
}
@keyframes slide-in-top {
0% {
transform: translate(-50%, 100%);
}
100% {
transform: translate(-50%, 0);
}
}
.ant-btn.domain-nav-btn {
background-color: @white;
border: 1px solid @grey-15;
color: @text-color;
height: 40px;
.domain-text {
max-width: 15vw;
color: inherit;
}
&:hover,
&:focus {
background-color: @primary-button-background;
border: 1px solid @alert-info-icon-bg-1;
color: @text-color;
}
&.domain-active {
color: @primary-color;
border: 1px solid @primary-color;
background-color: @primary-button-background;
}
}
.ant-alert.ant-alert-info.refresh-alert {
width: 470px;
bottom: 30px;
align-items: center;
z-index: 9999;
margin: 0 auto;
box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.12);
border-radius: 10px;
border: 1px solid @text-color;
background: @text-color;
color: @white;
position: fixed;
left: 50%;
transform: translateX(-50%);
.ant-alert-message {
color: @white;
}
.ant-alert-description {
color: @grey-4;
}
.ant-alert-content {
border-right: 1px solid @white;
}
.ant-btn {
font-weight: 700;
color: @white;
}
}

View File

@ -13,17 +13,20 @@
import Icon from '@ant-design/icons';
import { Typography } from 'antd';
import React from 'react';
import { ReactComponent as AssigneesIcon } from '../../../assets/svg/ic-assignees.svg';
import { ReactComponent as IconTeamsGrey } from '../../../assets/svg/teams-grey.svg';
import { OwnerType } from '../../../enums/user.enum';
import { EntityReference } from '../../../generated/entity/data/table';
import { getEntityName } from '../../../utils/EntityUtils';
import ProfilePicture from '../ProfilePicture/ProfilePicture';
import './owner-avtar.less';
interface OwnerAvatarProps {
owner: EntityReference;
isCompactView: boolean;
inheritedIcon?: React.ReactNode;
avatarSize?: number;
isCompactView?: boolean;
inheritedIcon?: React.ReactNode;
isAssignee?: boolean;
}
export const OwnerAvatar: React.FC<OwnerAvatarProps> = ({
@ -31,9 +34,49 @@ export const OwnerAvatar: React.FC<OwnerAvatarProps> = ({
isCompactView,
inheritedIcon,
avatarSize = 32,
isAssignee,
}) => {
const displayName = getEntityName(owner);
if (isAssignee) {
return (
<div className="flex w-max-full items-center gap-2">
{owner.type === OwnerType.TEAM ? (
<div className="d-flex gap-2 multi-team-container w-max-full items-center">
<Icon
className="owner-team-icon"
component={AssigneesIcon}
data-testid={!isCompactView && getEntityName(owner)}
/>
<Typography.Text className="text-sm" ellipsis={{ tooltip: true }}>
{displayName}
</Typography.Text>
</div>
) : (
<div
className="owner-avatar-icon"
data-testid={!isCompactView && getEntityName(owner)}
key={owner.id}
style={{ flexBasis: `${avatarSize}px` }}>
<ProfilePicture
displayName={displayName}
key="profile-picture"
name={owner.name ?? ''}
type="circle"
width={isCompactView ? '24' : `${avatarSize}`}
/>
{inheritedIcon && (
<div className="inherited-icon-styling flex-center">
{inheritedIcon}
</div>
)}
</div>
)}
</div>
);
}
return owner.type === OwnerType.TEAM ? (
<div className="d-flex gap-2 w-max-full items-center">
<Icon

View File

@ -16,6 +16,12 @@
color: @grey-700;
}
.multi-team-container {
background-color: @grey-9;
border-radius: 16px;
padding: 4px 12px;
}
.avatar-group {
display: flex;
align-items: center;

View File

@ -26,6 +26,7 @@ interface OwnerItemProps {
className?: string;
ownerDisplayName?: ReactNode;
avatarSize?: number;
isAssignee?: boolean;
}
export const OwnerItem: React.FC<OwnerItemProps> = ({
@ -34,6 +35,7 @@ export const OwnerItem: React.FC<OwnerItemProps> = ({
className,
ownerDisplayName,
avatarSize = 32,
isAssignee,
}) => {
const displayName = getEntityName(owner);
const ownerPath = getOwnerPath(owner);
@ -78,6 +80,7 @@ export const OwnerItem: React.FC<OwnerItemProps> = ({
<OwnerAvatar
avatarSize={avatarSize}
inheritedIcon={inheritedIcon}
isAssignee={isAssignee}
isCompactView={isCompactView}
owner={owner}
/>
@ -91,6 +94,7 @@ export const OwnerItem: React.FC<OwnerItemProps> = ({
<OwnerAvatar
avatarSize={avatarSize}
inheritedIcon={inheritedIcon}
isAssignee={isAssignee}
isCompactView={isCompactView}
owner={owner}
/>

View File

@ -11,11 +11,12 @@
* limitations under the License.
*/
import { Typography } from 'antd';
import { Button, Typography } from 'antd';
import classNames from 'classnames';
import { reverse } from 'lodash';
import React, { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as EditIcon } from '../../../assets/svg/edit-new.svg';
import { OwnerType } from '../../../enums/user.enum';
import { EntityReference } from '../../../generated/entity/type';
import { NoOwnerFound } from '../NoOwner/NoOwnerFound';
@ -41,6 +42,8 @@ export const OwnerLabel = ({
tooltipText,
isCompactView = true, // renders owner profile followed by its name
avatarSize = 32,
isAssignee = false,
onEditClick,
}: OwnerLabelProps) => {
const { t } = useTranslation();
const [showAllOwners, setShowAllOwners] = useState(false);
@ -87,9 +90,92 @@ export const OwnerLabel = ({
className,
]);
const showMultipleTypeTeam = owners.filter(
(owner) => owner.type === OwnerType.TEAM
);
const showMultipleTypeVisibleUser = owners
.filter((owner) => owner.type === OwnerType.USER)
.slice(0, maxVisibleOwners)
.reverse();
const showMultipleTypeRemainingUser = owners
.filter((owner) => owner.type === OwnerType.USER)
.slice(maxVisibleOwners);
const renderMultipleType = useMemo(() => {
return (
<div className="flex-wrap w-max-full d-flex relative items-center">
<div className="flex w-full gap-2 flex-wrap relative">
{showMultipleTypeTeam.map((owner, index) => (
<div className="w-max-full" key={owner.id}>
<OwnerItem
avatarSize={avatarSize}
className={className}
isAssignee={isAssignee}
isCompactView={isCompactView}
owner={owner}
ownerDisplayName={ownerDisplayName?.[index]}
/>
</div>
))}
<div className="flex">
<div className="flex relative m-l-xs justify-end flex-row-reverse">
{showMultipleTypeVisibleUser.map((owner, index) => (
<div className="relative" key={owner.id}>
<OwnerItem
avatarSize={avatarSize}
className={className}
isAssignee={isAssignee}
isCompactView={isCompactView}
owner={owner}
ownerDisplayName={ownerDisplayName?.[index]}
/>
</div>
))}
</div>
{showMultipleTypeRemainingUser.length > 0 && (
<div className="m-l-xs">
<OwnerReveal
avatarSize={isCompactView ? 24 : avatarSize}
isCompactView={false}
isDropdownOpen={isDropdownOpen}
owners={showMultipleTypeRemainingUser}
remainingCount={showMultipleTypeRemainingUser.length}
setIsDropdownOpen={setIsDropdownOpen}
setShowAllOwners={setShowAllOwners}
showAllOwners={showAllOwners}
/>
</div>
)}
{hasPermission && (
<Button
className="p-0 flex-center h-auto"
data-testid="edit-assignees"
icon={<EditIcon width="14px" />}
type="text"
onClick={onEditClick}
/>
)}
</div>
</div>
</div>
);
}, [
showMultipleTypeTeam,
showMultipleTypeVisibleUser,
showMultipleTypeRemainingUser,
avatarSize,
className,
isCompactView,
ownerDisplayName,
hasPermission,
onEditClick,
isDropdownOpen,
owners,
setIsDropdownOpen,
setShowAllOwners,
showAllOwners,
]);
const ownerElements = useMemo(() => {
const hasOwners = owners && owners.length > 0;
// Show all owners when "more" is clicked, regardless of view mode
const visibleOwners = showAllOwners
? owners
@ -118,19 +204,23 @@ export const OwnerLabel = ({
);
}
if (isAssignee) {
return renderMultipleType;
}
return (
<div
className={classNames({
'owner-label-container d-flex flex-col items-start flex-start':
'owner-label-container w-full d-flex flex-col items-start flex-start':
!isCompactView,
'd-flex owner-label-heading gap-2 items-center': isCompactView,
})}
data-testid="owner-label">
{ownerElementsNonCompactView}
<div className="d-flex items-center flex-center">
<div className="d-flex w-max-full items-center flex-center">
<div
className={classNames(
'avatar-group w-full d-flex relative items-center m-l-xss',
'avatar-group w-full d-flex relative items-center m-l-xss',
{
'gap-2 flex-wrap': isCompactView,
'flex-row-reverse': !isCompactView,

View File

@ -29,4 +29,6 @@ export interface OwnerLabelProps {
tooltipText?: string;
isCompactView?: boolean;
avatarSize?: number;
isAssignee?: boolean;
onEditClick?: () => void;
}

View File

@ -127,7 +127,3 @@
}
}
}
.owner-label-container {
max-width: 148px;
}