mirror of
https://github.com/plankanban/planka.git
synced 2025-07-24 07:39:44 +02:00
152 lines
4.3 KiB
JavaScript
Executable file
152 lines
4.3 KiB
JavaScript
Executable file
/*!
|
|
* Copyright (c) 2024 PLANKA Software GmbH
|
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
|
*/
|
|
|
|
import upperFirst from 'lodash/upperFirst';
|
|
import camelCase from 'lodash/camelCase';
|
|
import React, { useCallback, useMemo, useState } from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import classNames from 'classnames';
|
|
import { useDispatch, useSelector } from 'react-redux';
|
|
import { Button, Icon } from 'semantic-ui-react';
|
|
import { push } from '../../../lib/redux-router';
|
|
import { usePopup } from '../../../lib/popup';
|
|
|
|
import selectors from '../../../selectors';
|
|
import Paths from '../../../constants/Paths';
|
|
import { BoardMembershipRoles, CardTypes, ListTypes } from '../../../constants/Enums';
|
|
import ProjectContent from './ProjectContent';
|
|
import StoryContent from './StoryContent';
|
|
import InlineContent from './InlineContent';
|
|
import EditName from './EditName';
|
|
import ActionsStep from './ActionsStep';
|
|
|
|
import styles from './Card.module.scss';
|
|
import globalStyles from '../../../styles.module.scss';
|
|
|
|
const Card = React.memo(({ id, isInline }) => {
|
|
const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
|
|
const selectIsCardWithIdRecent = useMemo(() => selectors.makeSelectIsCardWithIdRecent(), []);
|
|
const selectListById = useMemo(() => selectors.makeSelectListById(), []);
|
|
|
|
const card = useSelector((state) => selectCardById(state, id));
|
|
const list = useSelector((state) => selectListById(state, card.listId));
|
|
|
|
const isHighlightedAsRecent = useSelector((state) => {
|
|
const { turnOffRecentCardHighlighting } = selectors.selectCurrentUser(state);
|
|
|
|
if (turnOffRecentCardHighlighting) {
|
|
return false;
|
|
}
|
|
|
|
return selectIsCardWithIdRecent(state, id);
|
|
});
|
|
|
|
const canUseActions = useSelector((state) => {
|
|
const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
|
|
return !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR;
|
|
});
|
|
|
|
const dispatch = useDispatch();
|
|
const [isEditNameOpened, setIsEditNameOpened] = useState(false);
|
|
|
|
const handleClick = useCallback(() => {
|
|
if (document.activeElement) {
|
|
document.activeElement.blur();
|
|
}
|
|
|
|
dispatch(push(Paths.CARDS.replace(':id', id)));
|
|
}, [id, dispatch]);
|
|
|
|
const handleNameEdit = useCallback(() => {
|
|
setIsEditNameOpened(true);
|
|
}, []);
|
|
|
|
const handleEditNameClose = useCallback(() => {
|
|
setIsEditNameOpened(false);
|
|
}, []);
|
|
|
|
const ActionsPopup = usePopup(ActionsStep);
|
|
|
|
if (isEditNameOpened) {
|
|
return <EditName cardId={id} onClose={handleEditNameClose} />;
|
|
}
|
|
|
|
let Content;
|
|
if (isInline) {
|
|
Content = InlineContent;
|
|
} else {
|
|
switch (card.type) {
|
|
case CardTypes.PROJECT:
|
|
Content = ProjectContent;
|
|
|
|
break;
|
|
case CardTypes.STORY:
|
|
Content = StoryContent;
|
|
|
|
break;
|
|
default:
|
|
}
|
|
}
|
|
|
|
const colorLineNode = list.color && (
|
|
<div
|
|
className={classNames(
|
|
styles.colorLine,
|
|
globalStyles[`background${upperFirst(camelCase(list.color))}`],
|
|
)}
|
|
/>
|
|
);
|
|
|
|
return (
|
|
<div
|
|
className={classNames(styles.wrapper, isHighlightedAsRecent && styles.wrapperRecent, 'card')}
|
|
>
|
|
{card.isPersisted ? (
|
|
<>
|
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
|
|
jsx-a11y/no-static-element-interactions */}
|
|
<div
|
|
className={classNames(
|
|
styles.content,
|
|
list.type === ListTypes.CLOSED && styles.contentDisabled,
|
|
)}
|
|
onClick={handleClick}
|
|
>
|
|
<Content cardId={id} />
|
|
{colorLineNode}
|
|
</div>
|
|
{canUseActions && (
|
|
<ActionsPopup cardId={id} onNameEdit={handleNameEdit}>
|
|
<Button className={styles.actionsButton}>
|
|
<Icon fitted name="pencil" size="small" />
|
|
</Button>
|
|
</ActionsPopup>
|
|
)}
|
|
</>
|
|
) : (
|
|
<span
|
|
className={classNames(
|
|
styles.content,
|
|
list.type === ListTypes.CLOSED && styles.contentDisabled,
|
|
)}
|
|
>
|
|
<Content cardId={id} />
|
|
{colorLineNode}
|
|
</span>
|
|
)}
|
|
</div>
|
|
);
|
|
});
|
|
|
|
Card.propTypes = {
|
|
id: PropTypes.string.isRequired,
|
|
isInline: PropTypes.bool,
|
|
};
|
|
|
|
Card.defaultProps = {
|
|
isInline: false,
|
|
};
|
|
|
|
export default Card;
|