2025-05-10 02:09:06 +02:00
|
|
|
/*!
|
|
|
|
* Copyright (c) 2024 PLANKA Software GmbH
|
|
|
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
|
|
|
*/
|
|
|
|
|
|
|
|
import truncate from 'lodash/truncate';
|
|
|
|
import React, { useCallback, useMemo } from 'react';
|
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import { useDispatch, useSelector } from 'react-redux';
|
|
|
|
import { useTranslation, Trans } from 'react-i18next';
|
|
|
|
import { Link } from 'react-router-dom';
|
|
|
|
import { Button } from 'semantic-ui-react';
|
|
|
|
|
|
|
|
import selectors from '../../../selectors';
|
|
|
|
import entryActions from '../../../entry-actions';
|
2025-05-30 22:01:29 +02:00
|
|
|
import { formatTextWithMentions } from '../../../utils/formatters';
|
2025-05-10 02:09:06 +02:00
|
|
|
import Paths from '../../../constants/Paths';
|
|
|
|
import { StaticUserIds } from '../../../constants/StaticUsers';
|
|
|
|
import { NotificationTypes } from '../../../constants/Enums';
|
2025-05-21 13:03:25 +02:00
|
|
|
import TimeAgo from '../../common/TimeAgo';
|
2025-05-10 02:09:06 +02:00
|
|
|
import UserAvatar from '../../users/UserAvatar';
|
|
|
|
|
|
|
|
import styles from './Item.module.scss';
|
|
|
|
|
|
|
|
const Item = React.memo(({ id, onClose }) => {
|
|
|
|
const selectNotificationById = useMemo(() => selectors.makeSelectNotificationById(), []);
|
|
|
|
const selectCreatorUserById = useMemo(() => selectors.makeSelectUserById(), []);
|
|
|
|
const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
|
|
|
|
|
|
|
|
const notification = useSelector((state) => selectNotificationById(state, id));
|
|
|
|
|
|
|
|
const creatorUser = useSelector((state) =>
|
|
|
|
selectCreatorUserById(state, notification.creatorUserId),
|
|
|
|
);
|
|
|
|
|
|
|
|
const card = useSelector((state) => selectCardById(state, notification.cardId));
|
|
|
|
|
|
|
|
const dispatch = useDispatch();
|
|
|
|
const [t] = useTranslation();
|
|
|
|
|
|
|
|
const handleDeleteClick = useCallback(() => {
|
|
|
|
dispatch(entryActions.deleteNotification(id));
|
|
|
|
}, [id, dispatch]);
|
|
|
|
|
|
|
|
const creatorUserName =
|
|
|
|
creatorUser.id === StaticUserIds.DELETED
|
|
|
|
? t(`common.${creatorUser.name}`, {
|
|
|
|
context: 'title',
|
|
|
|
})
|
|
|
|
: creatorUser.name;
|
|
|
|
|
|
|
|
const cardName = card ? card.name : notification.data.card.name;
|
|
|
|
|
|
|
|
let contentNode;
|
|
|
|
switch (notification.type) {
|
|
|
|
case NotificationTypes.MOVE_CARD: {
|
|
|
|
const { fromList, toList } = notification.data;
|
|
|
|
|
|
|
|
const fromListName = fromList.name || t(`common.${fromList.type}`);
|
|
|
|
const toListName = toList.name || t(`common.${toList.type}`);
|
|
|
|
|
|
|
|
contentNode = (
|
|
|
|
<Trans
|
|
|
|
i18nKey="common.userMovedCardFromListToList"
|
|
|
|
values={{
|
|
|
|
user: creatorUserName,
|
|
|
|
card: cardName,
|
|
|
|
fromList: fromListName,
|
|
|
|
toList: toListName,
|
|
|
|
}}
|
|
|
|
>
|
2025-05-21 13:03:25 +02:00
|
|
|
<span className={styles.author}>{creatorUserName}</span>
|
2025-05-10 02:09:06 +02:00
|
|
|
{' moved '}
|
|
|
|
<Link to={Paths.CARDS.replace(':id', notification.cardId)} onClick={onClose}>
|
|
|
|
{cardName}
|
|
|
|
</Link>
|
|
|
|
{' from '}
|
|
|
|
{fromListName}
|
|
|
|
{' to '}
|
|
|
|
{toListName}
|
|
|
|
</Trans>
|
|
|
|
);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case NotificationTypes.COMMENT_CARD: {
|
2025-05-30 22:01:29 +02:00
|
|
|
const commentText = truncate(formatTextWithMentions(notification.data.text));
|
2025-05-10 02:09:06 +02:00
|
|
|
|
|
|
|
contentNode = (
|
|
|
|
<Trans
|
|
|
|
i18nKey="common.userLeftNewCommentToCard"
|
|
|
|
values={{
|
|
|
|
user: creatorUserName,
|
|
|
|
comment: commentText,
|
|
|
|
card: cardName,
|
|
|
|
}}
|
|
|
|
>
|
2025-05-21 13:03:25 +02:00
|
|
|
<span className={styles.author}>{creatorUserName}</span>
|
2025-05-10 02:09:06 +02:00
|
|
|
{` left a new comment «${commentText}» to `}
|
|
|
|
<Link to={Paths.CARDS.replace(':id', notification.cardId)} onClick={onClose}>
|
|
|
|
{cardName}
|
|
|
|
</Link>
|
|
|
|
</Trans>
|
|
|
|
);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
2025-05-17 01:50:40 +02:00
|
|
|
case NotificationTypes.ADD_MEMBER_TO_CARD:
|
|
|
|
contentNode = (
|
|
|
|
<Trans
|
|
|
|
i18nKey="common.userAddedYouToCard"
|
|
|
|
values={{
|
|
|
|
user: creatorUserName,
|
|
|
|
card: cardName,
|
|
|
|
}}
|
|
|
|
>
|
2025-05-21 13:03:25 +02:00
|
|
|
<span className={styles.author}>{creatorUserName}</span>
|
2025-05-17 01:50:40 +02:00
|
|
|
{` added you to `}
|
|
|
|
<Link to={Paths.CARDS.replace(':id', notification.cardId)} onClick={onClose}>
|
|
|
|
{cardName}
|
|
|
|
</Link>
|
|
|
|
</Trans>
|
|
|
|
);
|
|
|
|
|
|
|
|
break;
|
2025-05-30 22:01:29 +02:00
|
|
|
case NotificationTypes.MENTION_IN_COMMENT: {
|
|
|
|
const commentText = truncate(formatTextWithMentions(notification.data.text));
|
|
|
|
|
|
|
|
contentNode = (
|
|
|
|
<Trans
|
|
|
|
i18nKey="common.userMentionedYouInCommentOnCard"
|
|
|
|
values={{
|
|
|
|
user: creatorUserName,
|
|
|
|
comment: commentText,
|
|
|
|
card: cardName,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<span className={styles.author}>{creatorUserName}</span>
|
|
|
|
{` mentioned you in «${commentText}» on `}
|
|
|
|
<Link to={Paths.CARDS.replace(':id', notification.cardId)} onClick={onClose}>
|
|
|
|
{cardName}
|
|
|
|
</Link>
|
|
|
|
</Trans>
|
|
|
|
);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
2025-05-10 02:09:06 +02:00
|
|
|
default:
|
|
|
|
contentNode = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className={styles.wrapper}>
|
|
|
|
<UserAvatar id={notification.creatorUserId} size="large" />
|
2025-05-21 13:03:25 +02:00
|
|
|
<span className={styles.content}>
|
|
|
|
<div>{contentNode}</div>
|
|
|
|
<span className={styles.date}>
|
|
|
|
<TimeAgo date={notification.createdAt} />
|
|
|
|
</span>
|
|
|
|
</span>
|
2025-05-10 02:09:06 +02:00
|
|
|
<Button
|
|
|
|
type="button"
|
|
|
|
icon="trash alternate outline"
|
|
|
|
className={styles.button}
|
|
|
|
onClick={handleDeleteClick}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
Item.propTypes = {
|
|
|
|
id: PropTypes.string.isRequired,
|
|
|
|
onClose: PropTypes.func.isRequired,
|
|
|
|
};
|
|
|
|
|
|
|
|
export default Item;
|