feat: Add ability to link tasks to cards
This commit is contained in:
@@ -3,45 +3,49 @@
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Icon } from 'semantic-ui-react';
|
||||
|
||||
import selectors from '../../../../selectors';
|
||||
import Paths from '../../../../constants/Paths';
|
||||
import Linkify from '../../../common/Linkify';
|
||||
|
||||
import styles from './Task.module.scss';
|
||||
|
||||
const Task = React.memo(({ id }) => {
|
||||
const selectTaskById = useMemo(() => selectors.makeSelectTaskById(), []);
|
||||
const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
|
||||
const selectLinkedCardById = useMemo(() => selectors.makeSelectCardById(), []);
|
||||
|
||||
const task = useSelector((state) => selectTaskById(state, id));
|
||||
|
||||
const isCompleted = useSelector((state) => {
|
||||
if (task.isCompleted) {
|
||||
return true;
|
||||
}
|
||||
const linkedCard = useSelector(
|
||||
(state) => task.linkedCardId && selectLinkedCardById(state, task.linkedCardId),
|
||||
);
|
||||
|
||||
const regex = /\/cards\/([^/]+)/g;
|
||||
const matches = task.name.matchAll(regex);
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const [, cardId] of matches) {
|
||||
const card = selectCardById(state, cardId);
|
||||
|
||||
if (card && card.isClosed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
const handleLinkClick = useCallback((event) => {
|
||||
event.stopPropagation();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<li className={classNames(styles.wrapper, isCompleted && styles.wrapperCompleted)}>
|
||||
<Linkify linkStopPropagation>{task.name}</Linkify>
|
||||
<li className={styles.wrapper}>
|
||||
{task.linkedCardId ? (
|
||||
<>
|
||||
<Icon name="exchange" size="small" className={styles.icon} />
|
||||
<span className={classNames(styles.name, task.isCompleted && styles.nameCompleted)}>
|
||||
<Link to={Paths.CARDS.replace(':id', task.linkedCardId)} onClick={handleLinkClick}>
|
||||
{linkedCard ? linkedCard.name : task.name}
|
||||
</Link>
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span className={classNames(styles.name, task.isCompleted && styles.nameCompleted)}>
|
||||
<Linkify linkStopPropagation>{task.name}</Linkify>
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -4,6 +4,21 @@
|
||||
*/
|
||||
|
||||
:global(#app) {
|
||||
.icon {
|
||||
color: rgba(9, 30, 66, 0.24);
|
||||
}
|
||||
|
||||
.name {
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.nameCompleted {
|
||||
color: #aaa;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
@@ -18,9 +33,4 @@
|
||||
left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapperCompleted {
|
||||
color: #aaa;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,32 +16,14 @@ import Task from './Task';
|
||||
import styles from './TaskList.module.scss';
|
||||
|
||||
const TaskList = React.memo(({ id }) => {
|
||||
const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
|
||||
const selectTasksByTaskListId = useMemo(() => selectors.makeSelectTasksByTaskListId(), []);
|
||||
|
||||
const tasks = useSelector((state) => selectTasksByTaskListId(state, id));
|
||||
|
||||
// TODO: move to selector?
|
||||
const completedTasksTotal = useSelector((state) =>
|
||||
tasks.reduce((result, task) => {
|
||||
if (task.isCompleted) {
|
||||
return result + 1;
|
||||
}
|
||||
|
||||
const regex = /\/cards\/([^/]+)/g;
|
||||
const matches = task.name.matchAll(regex);
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const [, cardId] of matches) {
|
||||
const card = selectCardById(state, cardId);
|
||||
|
||||
if (card && card.isClosed) {
|
||||
return result + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}, 0),
|
||||
const completedTasksTotal = useMemo(
|
||||
() => tasks.reduce((result, task) => (task.isCompleted ? result + 1 : result), 0),
|
||||
[tasks],
|
||||
);
|
||||
|
||||
const [isOpened, toggleOpened] = useToggle();
|
||||
|
||||
Reference in New Issue
Block a user