feat: Persist closed state per card

This commit is contained in:
Maksim Eltyshev
2025-07-09 17:45:47 +02:00
parent 69c75a03b1
commit 709a0d1758
19 changed files with 163 additions and 71 deletions

View File

@@ -15,7 +15,7 @@ import { usePopup } from '../../../lib/popup';
import selectors from '../../../selectors';
import Paths from '../../../constants/Paths';
import { BoardMembershipRoles, CardTypes, ListTypes } from '../../../constants/Enums';
import { BoardMembershipRoles, CardTypes } from '../../../constants/Enums';
import ProjectContent from './ProjectContent';
import StoryContent from './StoryContent';
import InlineContent from './InlineContent';
@@ -108,10 +108,7 @@ const Card = React.memo(({ id, isInline }) => {
{/* 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,
)}
className={classNames(styles.content, card.isClosed && styles.contentDisabled)}
onClick={handleClick}
>
<Content cardId={id} />
@@ -126,12 +123,7 @@ const Card = React.memo(({ id, isInline }) => {
)}
</>
) : (
<span
className={classNames(
styles.content,
list.type === ListTypes.CLOSED && styles.contentDisabled,
)}
>
<span className={classNames(styles.content, card.isClosed && styles.contentDisabled)}>
<Content cardId={id} />
{colorLineNode}
</span>

View File

@@ -11,7 +11,7 @@ import { Icon } from 'semantic-ui-react';
import selectors from '../../../selectors';
import markdownToText from '../../../utils/markdown-to-text';
import { BoardViews, ListTypes } from '../../../constants/Enums';
import { BoardViews } from '../../../constants/Enums';
import UserAvatar from '../../users/UserAvatar';
import LabelChip from '../../labels/LabelChip';
@@ -54,8 +54,6 @@ const InlineContent = React.memo(({ cardId }) => {
[card.description],
);
const isInClosedList = list.type === ListTypes.CLOSED;
return (
<div className={styles.wrapper}>
<span className={styles.attachments}>
@@ -90,7 +88,7 @@ const InlineContent = React.memo(({ cardId }) => {
</span>
)}
<span
className={classNames(styles.attachments, styles.name, isInClosedList && styles.nameClosed)}
className={classNames(styles.attachments, styles.name, card.isClosed && styles.nameClosed)}
>
<div className={styles.hidable}>{card.name}</div>
</span>

View File

@@ -13,7 +13,7 @@ import selectors from '../../../selectors';
import entryActions from '../../../entry-actions';
import { startStopwatch, stopStopwatch } from '../../../utils/stopwatch';
import { isListArchiveOrTrash } from '../../../utils/record-helpers';
import { BoardMembershipRoles, BoardViews, ListTypes } from '../../../constants/Enums';
import { BoardMembershipRoles, BoardViews } from '../../../constants/Enums';
import TaskList from './TaskList';
import DueDateChip from '../DueDateChip';
import StopwatchChip from '../StopwatchChip';
@@ -110,8 +110,6 @@ const ProjectContent = React.memo(({ cardId }) => {
[cardId, card.stopwatch, dispatch],
);
const isInClosedList = list.type === ListTypes.CLOSED;
const hasInformation =
card.description ||
card.dueDate ||
@@ -147,9 +145,7 @@ const ProjectContent = React.memo(({ cardId }) => {
return (
<div className={styles.wrapper}>
<div className={classNames(styles.name, isInClosedList && styles.nameClosed)}>
{card.name}
</div>
<div className={classNames(styles.name, card.isClosed && styles.nameClosed)}>{card.name}</div>
{coverUrl && (
<div className={styles.coverWrapper}>
<img src={coverUrl} alt="" className={styles.cover} />
@@ -191,11 +187,7 @@ const ProjectContent = React.memo(({ cardId }) => {
)}
{card.dueDate && (
<span className={classNames(styles.attachment, styles.attachmentLeft)}>
<DueDateChip
value={card.dueDate}
size="tiny"
withStatus={!isInClosedList && !isListArchiveOrTrash(list)}
/>
<DueDateChip value={card.dueDate} size="tiny" withStatus={!card.isClosed} />
</span>
)}
{card.stopwatch && (

View File

@@ -11,7 +11,7 @@ import { Icon } from 'semantic-ui-react';
import selectors from '../../../selectors';
import markdownToText from '../../../utils/markdown-to-text';
import { BoardViews, ListTypes } from '../../../constants/Enums';
import { BoardViews } from '../../../constants/Enums';
import LabelChip from '../../labels/LabelChip';
import CustomFieldValueChip from '../../custom-field-values/CustomFieldValueChip';
@@ -76,8 +76,6 @@ const StoryContent = React.memo(({ cardId }) => {
[card.description],
);
const isInClosedList = list.type === ListTypes.CLOSED;
return (
<>
{coverUrl && (
@@ -107,7 +105,7 @@ const StoryContent = React.memo(({ cardId }) => {
))}
</span>
)}
<div className={classNames(styles.name, isInClosedList && styles.nameClosed)}>
<div className={classNames(styles.name, card.isClosed && styles.nameClosed)}>
{card.name}
</div>
{card.description && <div className={styles.descriptionText}>{descriptionText}</div>}

View File

@@ -9,7 +9,6 @@ import classNames from 'classnames';
import { useSelector } from 'react-redux';
import selectors from '../../../../selectors';
import { ListTypes } from '../../../../constants/Enums';
import Linkify from '../../../common/Linkify';
import styles from './Task.module.scss';
@@ -17,7 +16,6 @@ import styles from './Task.module.scss';
const Task = React.memo(({ id }) => {
const selectTaskById = useMemo(() => selectors.makeSelectTaskById(), []);
const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
const selectListById = useMemo(() => selectors.makeSelectListById(), []);
const task = useSelector((state) => selectTaskById(state, id));
@@ -33,14 +31,11 @@ const Task = React.memo(({ id }) => {
for (const [, cardId] of matches) {
const card = selectCardById(state, cardId);
if (card) {
const list = selectListById(state, card.listId);
if (list && list.type === ListTypes.CLOSED) {
return true;
}
if (card && card.isClosed) {
return true;
}
}
return false;
});

View File

@@ -9,7 +9,6 @@ import classNames from 'classnames';
import { useSelector } from 'react-redux';
import { Progress } from 'semantic-ui-react';
import { useToggle } from '../../../../lib/hooks';
import { ListTypes } from '../../../../constants/Enums';
import selectors from '../../../../selectors';
import Task from './Task';
@@ -18,7 +17,6 @@ import styles from './TaskList.module.scss';
const TaskList = React.memo(({ id }) => {
const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
const selectListById = useMemo(() => selectors.makeSelectListById(), []);
const selectTasksByTaskListId = useMemo(() => selectors.makeSelectTasksByTaskListId(), []);
const tasks = useSelector((state) => selectTasksByTaskListId(state, id));
@@ -37,12 +35,8 @@ const TaskList = React.memo(({ id }) => {
for (const [, cardId] of matches) {
const card = selectCardById(state, cardId);
if (card) {
const list = selectListById(state, card.listId);
if (list && list.type === ListTypes.CLOSED) {
return result + 1;
}
if (card && card.isClosed) {
return result + 1;
}
}

View File

@@ -442,18 +442,14 @@ const ProjectContent = React.memo(({ onClose }) => {
<DueDateChip
withStatusIcon
value={card.dueDate}
withStatus={
list.type !== ListTypes.CLOSED && !isInArchiveList && !isInTrashList
}
withStatus={!card.isClosed}
/>
</EditDueDatePopup>
) : (
<DueDateChip
withStatusIcon
value={card.dueDate}
withStatus={
list.type !== ListTypes.CLOSED && !isInArchiveList && !isInTrashList
}
withStatus={!card.isClosed}
/>
)}
</span>

View File

@@ -16,7 +16,7 @@ import selectors from '../../../../selectors';
import entryActions from '../../../../entry-actions';
import { usePopupInClosableContext } from '../../../../hooks';
import { isListArchiveOrTrash } from '../../../../utils/record-helpers';
import { BoardMembershipRoles, ListTypes } from '../../../../constants/Enums';
import { BoardMembershipRoles } from '../../../../constants/Enums';
import { ClosableContext } from '../../../../contexts';
import EditName from './EditName';
import SelectAssigneeStep from './SelectAssigneeStep';
@@ -41,14 +41,11 @@ const Task = React.memo(({ id, index }) => {
for (const [, cardId] of matches) {
const card = selectCardById(state, cardId);
if (card) {
const list = selectListById(state, card.listId);
if (list && list.type === ListTypes.CLOSED) {
return true;
}
if (card && card.isClosed) {
return true;
}
}
return false;
});

View File

@@ -14,7 +14,7 @@ import { useDidUpdate } from '../../../lib/hooks';
import selectors from '../../../selectors';
import { isListArchiveOrTrash } from '../../../utils/record-helpers';
import DroppableTypes from '../../../constants/DroppableTypes';
import { BoardMembershipRoles, ListTypes } from '../../../constants/Enums';
import { BoardMembershipRoles } from '../../../constants/Enums';
import { ClosableContext } from '../../../contexts';
import Task from './Task';
import AddTask from './AddTask';
@@ -44,12 +44,8 @@ const TaskList = React.memo(({ id }) => {
for (const [, cardId] of matches) {
const card = selectCardById(state, cardId);
if (card) {
const list = selectListById(state, card.listId);
if (list && list.type === ListTypes.CLOSED) {
return result + 1;
}
if (card && card.isClosed) {
return result + 1;
}
}