mirror of
https://github.com/plankanban/planka.git
synced 2025-07-18 20:59:44 +02:00
feat: Persist closed state per card
This commit is contained in:
parent
69c75a03b1
commit
709a0d1758
19 changed files with 163 additions and 71 deletions
|
@ -15,7 +15,7 @@ import { usePopup } from '../../../lib/popup';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
import Paths from '../../../constants/Paths';
|
import Paths from '../../../constants/Paths';
|
||||||
import { BoardMembershipRoles, CardTypes, ListTypes } from '../../../constants/Enums';
|
import { BoardMembershipRoles, CardTypes } from '../../../constants/Enums';
|
||||||
import ProjectContent from './ProjectContent';
|
import ProjectContent from './ProjectContent';
|
||||||
import StoryContent from './StoryContent';
|
import StoryContent from './StoryContent';
|
||||||
import InlineContent from './InlineContent';
|
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,
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
|
||||||
jsx-a11y/no-static-element-interactions */}
|
jsx-a11y/no-static-element-interactions */}
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(styles.content, card.isClosed && styles.contentDisabled)}
|
||||||
styles.content,
|
|
||||||
list.type === ListTypes.CLOSED && styles.contentDisabled,
|
|
||||||
)}
|
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<Content cardId={id} />
|
<Content cardId={id} />
|
||||||
|
@ -126,12 +123,7 @@ const Card = React.memo(({ id, isInline }) => {
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<span
|
<span className={classNames(styles.content, card.isClosed && styles.contentDisabled)}>
|
||||||
className={classNames(
|
|
||||||
styles.content,
|
|
||||||
list.type === ListTypes.CLOSED && styles.contentDisabled,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Content cardId={id} />
|
<Content cardId={id} />
|
||||||
{colorLineNode}
|
{colorLineNode}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { Icon } from 'semantic-ui-react';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
import markdownToText from '../../../utils/markdown-to-text';
|
import markdownToText from '../../../utils/markdown-to-text';
|
||||||
import { BoardViews, ListTypes } from '../../../constants/Enums';
|
import { BoardViews } from '../../../constants/Enums';
|
||||||
import UserAvatar from '../../users/UserAvatar';
|
import UserAvatar from '../../users/UserAvatar';
|
||||||
import LabelChip from '../../labels/LabelChip';
|
import LabelChip from '../../labels/LabelChip';
|
||||||
|
|
||||||
|
@ -54,8 +54,6 @@ const InlineContent = React.memo(({ cardId }) => {
|
||||||
[card.description],
|
[card.description],
|
||||||
);
|
);
|
||||||
|
|
||||||
const isInClosedList = list.type === ListTypes.CLOSED;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
<span className={styles.attachments}>
|
<span className={styles.attachments}>
|
||||||
|
@ -90,7 +88,7 @@ const InlineContent = React.memo(({ cardId }) => {
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<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>
|
<div className={styles.hidable}>{card.name}</div>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -13,7 +13,7 @@ import selectors from '../../../selectors';
|
||||||
import entryActions from '../../../entry-actions';
|
import entryActions from '../../../entry-actions';
|
||||||
import { startStopwatch, stopStopwatch } from '../../../utils/stopwatch';
|
import { startStopwatch, stopStopwatch } from '../../../utils/stopwatch';
|
||||||
import { isListArchiveOrTrash } from '../../../utils/record-helpers';
|
import { isListArchiveOrTrash } from '../../../utils/record-helpers';
|
||||||
import { BoardMembershipRoles, BoardViews, ListTypes } from '../../../constants/Enums';
|
import { BoardMembershipRoles, BoardViews } from '../../../constants/Enums';
|
||||||
import TaskList from './TaskList';
|
import TaskList from './TaskList';
|
||||||
import DueDateChip from '../DueDateChip';
|
import DueDateChip from '../DueDateChip';
|
||||||
import StopwatchChip from '../StopwatchChip';
|
import StopwatchChip from '../StopwatchChip';
|
||||||
|
@ -110,8 +110,6 @@ const ProjectContent = React.memo(({ cardId }) => {
|
||||||
[cardId, card.stopwatch, dispatch],
|
[cardId, card.stopwatch, dispatch],
|
||||||
);
|
);
|
||||||
|
|
||||||
const isInClosedList = list.type === ListTypes.CLOSED;
|
|
||||||
|
|
||||||
const hasInformation =
|
const hasInformation =
|
||||||
card.description ||
|
card.description ||
|
||||||
card.dueDate ||
|
card.dueDate ||
|
||||||
|
@ -147,9 +145,7 @@ const ProjectContent = React.memo(({ cardId }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
<div className={classNames(styles.name, isInClosedList && styles.nameClosed)}>
|
<div className={classNames(styles.name, card.isClosed && styles.nameClosed)}>{card.name}</div>
|
||||||
{card.name}
|
|
||||||
</div>
|
|
||||||
{coverUrl && (
|
{coverUrl && (
|
||||||
<div className={styles.coverWrapper}>
|
<div className={styles.coverWrapper}>
|
||||||
<img src={coverUrl} alt="" className={styles.cover} />
|
<img src={coverUrl} alt="" className={styles.cover} />
|
||||||
|
@ -191,11 +187,7 @@ const ProjectContent = React.memo(({ cardId }) => {
|
||||||
)}
|
)}
|
||||||
{card.dueDate && (
|
{card.dueDate && (
|
||||||
<span className={classNames(styles.attachment, styles.attachmentLeft)}>
|
<span className={classNames(styles.attachment, styles.attachmentLeft)}>
|
||||||
<DueDateChip
|
<DueDateChip value={card.dueDate} size="tiny" withStatus={!card.isClosed} />
|
||||||
value={card.dueDate}
|
|
||||||
size="tiny"
|
|
||||||
withStatus={!isInClosedList && !isListArchiveOrTrash(list)}
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{card.stopwatch && (
|
{card.stopwatch && (
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { Icon } from 'semantic-ui-react';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
import markdownToText from '../../../utils/markdown-to-text';
|
import markdownToText from '../../../utils/markdown-to-text';
|
||||||
import { BoardViews, ListTypes } from '../../../constants/Enums';
|
import { BoardViews } from '../../../constants/Enums';
|
||||||
import LabelChip from '../../labels/LabelChip';
|
import LabelChip from '../../labels/LabelChip';
|
||||||
import CustomFieldValueChip from '../../custom-field-values/CustomFieldValueChip';
|
import CustomFieldValueChip from '../../custom-field-values/CustomFieldValueChip';
|
||||||
|
|
||||||
|
@ -76,8 +76,6 @@ const StoryContent = React.memo(({ cardId }) => {
|
||||||
[card.description],
|
[card.description],
|
||||||
);
|
);
|
||||||
|
|
||||||
const isInClosedList = list.type === ListTypes.CLOSED;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{coverUrl && (
|
{coverUrl && (
|
||||||
|
@ -107,7 +105,7 @@ const StoryContent = React.memo(({ cardId }) => {
|
||||||
))}
|
))}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<div className={classNames(styles.name, isInClosedList && styles.nameClosed)}>
|
<div className={classNames(styles.name, card.isClosed && styles.nameClosed)}>
|
||||||
{card.name}
|
{card.name}
|
||||||
</div>
|
</div>
|
||||||
{card.description && <div className={styles.descriptionText}>{descriptionText}</div>}
|
{card.description && <div className={styles.descriptionText}>{descriptionText}</div>}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import classNames from 'classnames';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import selectors from '../../../../selectors';
|
import selectors from '../../../../selectors';
|
||||||
import { ListTypes } from '../../../../constants/Enums';
|
|
||||||
import Linkify from '../../../common/Linkify';
|
import Linkify from '../../../common/Linkify';
|
||||||
|
|
||||||
import styles from './Task.module.scss';
|
import styles from './Task.module.scss';
|
||||||
|
@ -17,7 +16,6 @@ import styles from './Task.module.scss';
|
||||||
const Task = React.memo(({ id }) => {
|
const Task = React.memo(({ id }) => {
|
||||||
const selectTaskById = useMemo(() => selectors.makeSelectTaskById(), []);
|
const selectTaskById = useMemo(() => selectors.makeSelectTaskById(), []);
|
||||||
const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
|
const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
|
||||||
const selectListById = useMemo(() => selectors.makeSelectListById(), []);
|
|
||||||
|
|
||||||
const task = useSelector((state) => selectTaskById(state, id));
|
const task = useSelector((state) => selectTaskById(state, id));
|
||||||
|
|
||||||
|
@ -33,14 +31,11 @@ const Task = React.memo(({ id }) => {
|
||||||
for (const [, cardId] of matches) {
|
for (const [, cardId] of matches) {
|
||||||
const card = selectCardById(state, cardId);
|
const card = selectCardById(state, cardId);
|
||||||
|
|
||||||
if (card) {
|
if (card && card.isClosed) {
|
||||||
const list = selectListById(state, card.listId);
|
return true;
|
||||||
|
|
||||||
if (list && list.type === ListTypes.CLOSED) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ import classNames from 'classnames';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { Progress } from 'semantic-ui-react';
|
import { Progress } from 'semantic-ui-react';
|
||||||
import { useToggle } from '../../../../lib/hooks';
|
import { useToggle } from '../../../../lib/hooks';
|
||||||
import { ListTypes } from '../../../../constants/Enums';
|
|
||||||
|
|
||||||
import selectors from '../../../../selectors';
|
import selectors from '../../../../selectors';
|
||||||
import Task from './Task';
|
import Task from './Task';
|
||||||
|
@ -18,7 +17,6 @@ import styles from './TaskList.module.scss';
|
||||||
|
|
||||||
const TaskList = React.memo(({ id }) => {
|
const TaskList = React.memo(({ id }) => {
|
||||||
const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
|
const selectCardById = useMemo(() => selectors.makeSelectCardById(), []);
|
||||||
const selectListById = useMemo(() => selectors.makeSelectListById(), []);
|
|
||||||
const selectTasksByTaskListId = useMemo(() => selectors.makeSelectTasksByTaskListId(), []);
|
const selectTasksByTaskListId = useMemo(() => selectors.makeSelectTasksByTaskListId(), []);
|
||||||
|
|
||||||
const tasks = useSelector((state) => selectTasksByTaskListId(state, id));
|
const tasks = useSelector((state) => selectTasksByTaskListId(state, id));
|
||||||
|
@ -37,12 +35,8 @@ const TaskList = React.memo(({ id }) => {
|
||||||
for (const [, cardId] of matches) {
|
for (const [, cardId] of matches) {
|
||||||
const card = selectCardById(state, cardId);
|
const card = selectCardById(state, cardId);
|
||||||
|
|
||||||
if (card) {
|
if (card && card.isClosed) {
|
||||||
const list = selectListById(state, card.listId);
|
return result + 1;
|
||||||
|
|
||||||
if (list && list.type === ListTypes.CLOSED) {
|
|
||||||
return result + 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -442,18 +442,14 @@ const ProjectContent = React.memo(({ onClose }) => {
|
||||||
<DueDateChip
|
<DueDateChip
|
||||||
withStatusIcon
|
withStatusIcon
|
||||||
value={card.dueDate}
|
value={card.dueDate}
|
||||||
withStatus={
|
withStatus={!card.isClosed}
|
||||||
list.type !== ListTypes.CLOSED && !isInArchiveList && !isInTrashList
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</EditDueDatePopup>
|
</EditDueDatePopup>
|
||||||
) : (
|
) : (
|
||||||
<DueDateChip
|
<DueDateChip
|
||||||
withStatusIcon
|
withStatusIcon
|
||||||
value={card.dueDate}
|
value={card.dueDate}
|
||||||
withStatus={
|
withStatus={!card.isClosed}
|
||||||
list.type !== ListTypes.CLOSED && !isInArchiveList && !isInTrashList
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -16,7 +16,7 @@ import selectors from '../../../../selectors';
|
||||||
import entryActions from '../../../../entry-actions';
|
import entryActions from '../../../../entry-actions';
|
||||||
import { usePopupInClosableContext } from '../../../../hooks';
|
import { usePopupInClosableContext } from '../../../../hooks';
|
||||||
import { isListArchiveOrTrash } from '../../../../utils/record-helpers';
|
import { isListArchiveOrTrash } from '../../../../utils/record-helpers';
|
||||||
import { BoardMembershipRoles, ListTypes } from '../../../../constants/Enums';
|
import { BoardMembershipRoles } from '../../../../constants/Enums';
|
||||||
import { ClosableContext } from '../../../../contexts';
|
import { ClosableContext } from '../../../../contexts';
|
||||||
import EditName from './EditName';
|
import EditName from './EditName';
|
||||||
import SelectAssigneeStep from './SelectAssigneeStep';
|
import SelectAssigneeStep from './SelectAssigneeStep';
|
||||||
|
@ -41,14 +41,11 @@ const Task = React.memo(({ id, index }) => {
|
||||||
for (const [, cardId] of matches) {
|
for (const [, cardId] of matches) {
|
||||||
const card = selectCardById(state, cardId);
|
const card = selectCardById(state, cardId);
|
||||||
|
|
||||||
if (card) {
|
if (card && card.isClosed) {
|
||||||
const list = selectListById(state, card.listId);
|
return true;
|
||||||
|
|
||||||
if (list && list.type === ListTypes.CLOSED) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { useDidUpdate } from '../../../lib/hooks';
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
import { isListArchiveOrTrash } from '../../../utils/record-helpers';
|
import { isListArchiveOrTrash } from '../../../utils/record-helpers';
|
||||||
import DroppableTypes from '../../../constants/DroppableTypes';
|
import DroppableTypes from '../../../constants/DroppableTypes';
|
||||||
import { BoardMembershipRoles, ListTypes } from '../../../constants/Enums';
|
import { BoardMembershipRoles } from '../../../constants/Enums';
|
||||||
import { ClosableContext } from '../../../contexts';
|
import { ClosableContext } from '../../../contexts';
|
||||||
import Task from './Task';
|
import Task from './Task';
|
||||||
import AddTask from './AddTask';
|
import AddTask from './AddTask';
|
||||||
|
@ -44,12 +44,8 @@ const TaskList = React.memo(({ id }) => {
|
||||||
for (const [, cardId] of matches) {
|
for (const [, cardId] of matches) {
|
||||||
const card = selectCardById(state, cardId);
|
const card = selectCardById(state, cardId);
|
||||||
|
|
||||||
if (card) {
|
if (card && card.isClosed) {
|
||||||
const list = selectListById(state, card.listId);
|
return result + 1;
|
||||||
|
|
||||||
if (list && list.type === ListTypes.CLOSED) {
|
|
||||||
return result + 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ export default class extends BaseModel {
|
||||||
description: attr(),
|
description: attr(),
|
||||||
dueDate: attr(),
|
dueDate: attr(),
|
||||||
stopwatch: attr(),
|
stopwatch: attr(),
|
||||||
|
isClosed: attr(),
|
||||||
commentsTotal: attr({
|
commentsTotal: attr({
|
||||||
getDefault: () => 0,
|
getDefault: () => 0,
|
||||||
}),
|
}),
|
||||||
|
@ -554,6 +555,7 @@ export default class extends BaseModel {
|
||||||
description: this.description,
|
description: this.description,
|
||||||
dueDate: this.dueDate,
|
dueDate: this.dueDate,
|
||||||
stopwatch: this.stopwatch,
|
stopwatch: this.stopwatch,
|
||||||
|
isClosed: this.isClosed,
|
||||||
...data,
|
...data,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -101,7 +101,6 @@ export default class extends BaseModel {
|
||||||
case ActionTypes.LIST_CREATE:
|
case ActionTypes.LIST_CREATE:
|
||||||
case ActionTypes.LIST_CREATE_HANDLE:
|
case ActionTypes.LIST_CREATE_HANDLE:
|
||||||
case ActionTypes.LIST_UPDATE__SUCCESS:
|
case ActionTypes.LIST_UPDATE__SUCCESS:
|
||||||
case ActionTypes.LIST_UPDATE_HANDLE:
|
|
||||||
case ActionTypes.LIST_SORT__SUCCESS:
|
case ActionTypes.LIST_SORT__SUCCESS:
|
||||||
case ActionTypes.LIST_CARDS_MOVE__SUCCESS:
|
case ActionTypes.LIST_CARDS_MOVE__SUCCESS:
|
||||||
case ActionTypes.LIST_CLEAR__SUCCESS:
|
case ActionTypes.LIST_CLEAR__SUCCESS:
|
||||||
|
@ -117,10 +116,60 @@ export default class extends BaseModel {
|
||||||
List.withId(payload.localId).delete();
|
List.withId(payload.localId).delete();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case ActionTypes.LIST_UPDATE:
|
case ActionTypes.LIST_UPDATE: {
|
||||||
List.withId(payload.id).update(payload.data);
|
const listModel = List.withId(payload.id);
|
||||||
|
|
||||||
|
let isClosed;
|
||||||
|
if (payload.data.type) {
|
||||||
|
if (payload.data.type === ListTypes.CLOSED) {
|
||||||
|
if (listModel.type === ListTypes.ACTIVE) {
|
||||||
|
isClosed = true;
|
||||||
|
}
|
||||||
|
} else if (listModel.type === ListTypes.CLOSED) {
|
||||||
|
isClosed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listModel.update(payload.data);
|
||||||
|
|
||||||
|
if (isClosed !== undefined) {
|
||||||
|
listModel.cards.toModelArray().forEach((cardModel) => {
|
||||||
|
cardModel.update({
|
||||||
|
isClosed,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
case ActionTypes.LIST_UPDATE_HANDLE: {
|
||||||
|
const listModel = List.withId(payload.list.id);
|
||||||
|
|
||||||
|
if (listModel) {
|
||||||
|
let isClosed;
|
||||||
|
if (payload.list.type === ListTypes.CLOSED) {
|
||||||
|
if (listModel.type === ListTypes.ACTIVE) {
|
||||||
|
isClosed = true;
|
||||||
|
}
|
||||||
|
} else if (listModel.type === ListTypes.CLOSED) {
|
||||||
|
isClosed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
listModel.update(prepareList(payload.list));
|
||||||
|
|
||||||
|
if (isClosed !== undefined) {
|
||||||
|
listModel.cards.toModelArray().forEach((cardModel) => {
|
||||||
|
cardModel.update({
|
||||||
|
isClosed,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
List.upsert(prepareList(payload.list));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
case ActionTypes.LIST_SORT:
|
case ActionTypes.LIST_SORT:
|
||||||
List.withId(payload.id).sortCards(payload.data);
|
List.withId(payload.id).sortCards(payload.data);
|
||||||
|
|
||||||
|
|
|
@ -137,6 +137,7 @@ export function* createCard(listId, data, autoOpen) {
|
||||||
id: localId,
|
id: localId,
|
||||||
boardId: list.boardId,
|
boardId: list.boardId,
|
||||||
creatorUserId: currentUserMembership.userId,
|
creatorUserId: currentUserMembership.userId,
|
||||||
|
isClosed: list.type === ListTypes.CLOSED,
|
||||||
},
|
},
|
||||||
autoOpen,
|
autoOpen,
|
||||||
),
|
),
|
||||||
|
@ -225,6 +226,8 @@ export function* handleCardCreate(card) {
|
||||||
|
|
||||||
export function* updateCard(id, data) {
|
export function* updateCard(id, data) {
|
||||||
let prevListId;
|
let prevListId;
|
||||||
|
let isClosed;
|
||||||
|
|
||||||
if (data.listId) {
|
if (data.listId) {
|
||||||
const list = yield select(selectors.selectListById, data.listId);
|
const list = yield select(selectors.selectListById, data.listId);
|
||||||
|
|
||||||
|
@ -238,6 +241,14 @@ export function* updateCard(id, data) {
|
||||||
} else if (prevList.type === ListTypes.ARCHIVE) {
|
} else if (prevList.type === ListTypes.ARCHIVE) {
|
||||||
prevListId = null;
|
prevListId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (card.isClosed) {
|
||||||
|
if (list.type === ListTypes.ACTIVE) {
|
||||||
|
isClosed = false;
|
||||||
|
}
|
||||||
|
} else if (list.type === ListTypes.CLOSED) {
|
||||||
|
isClosed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
yield put(
|
yield put(
|
||||||
|
@ -246,6 +257,9 @@ export function* updateCard(id, data) {
|
||||||
...(prevListId !== undefined && {
|
...(prevListId !== undefined && {
|
||||||
prevListId,
|
prevListId,
|
||||||
}),
|
}),
|
||||||
|
...(isClosed !== undefined && {
|
||||||
|
isClosed,
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,7 @@ module.exports = {
|
||||||
name: trelloCard.name,
|
name: trelloCard.name,
|
||||||
description: trelloCard.desc || null,
|
description: trelloCard.desc || null,
|
||||||
dueDate: trelloCard.due,
|
dueDate: trelloCard.due,
|
||||||
|
isClosed: trelloCard.dueComplete,
|
||||||
listChangedAt: new Date().toISOString(),
|
listChangedAt: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,10 @@ module.exports = {
|
||||||
delete values.position;
|
delete values.position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (values.list.type === List.Types.CLOSED) {
|
||||||
|
values.isClosed = true;
|
||||||
|
}
|
||||||
|
|
||||||
const card = await Card.qm.createOne({
|
const card = await Card.qm.createOne({
|
||||||
...values,
|
...values,
|
||||||
boardId: values.board.id,
|
boardId: values.board.id,
|
||||||
|
|
|
@ -91,6 +91,7 @@ module.exports = {
|
||||||
'description',
|
'description',
|
||||||
'dueDate',
|
'dueDate',
|
||||||
'stopwatch',
|
'stopwatch',
|
||||||
|
'isClosed',
|
||||||
]),
|
]),
|
||||||
...values,
|
...values,
|
||||||
creatorUserId: values.creatorUser.id,
|
creatorUserId: values.creatorUser.id,
|
||||||
|
|
|
@ -378,8 +378,6 @@ module.exports = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (values.list) {
|
if (values.list) {
|
||||||
values.listChangedAt = new Date().toISOString();
|
|
||||||
|
|
||||||
if (values.board || inputs.list.type === List.Types.TRASH) {
|
if (values.board || inputs.list.type === List.Types.TRASH) {
|
||||||
values.prevListId = null;
|
values.prevListId = null;
|
||||||
} else if (sails.helpers.lists.isArchiveOrTrash(values.list)) {
|
} else if (sails.helpers.lists.isArchiveOrTrash(values.list)) {
|
||||||
|
@ -387,6 +385,16 @@ module.exports = {
|
||||||
} else if (inputs.list.type === List.Types.ARCHIVE) {
|
} else if (inputs.list.type === List.Types.ARCHIVE) {
|
||||||
values.prevListId = null;
|
values.prevListId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inputs.record.isClosed) {
|
||||||
|
if (values.list.type === List.Types.ACTIVE) {
|
||||||
|
values.isClosed = false;
|
||||||
|
}
|
||||||
|
} else if (values.list.type === List.Types.CLOSED) {
|
||||||
|
values.isClosed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
values.listChangedAt = new Date().toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
card = await Card.qm.updateOne(inputs.record.id, values);
|
card = await Card.qm.updateOne(inputs.record.id, values);
|
||||||
|
|
|
@ -33,6 +33,28 @@ module.exports = {
|
||||||
async fn(inputs) {
|
async fn(inputs) {
|
||||||
const { values } = inputs;
|
const { values } = inputs;
|
||||||
|
|
||||||
|
if (values.type) {
|
||||||
|
let isClosed;
|
||||||
|
if (values.type === List.Types.CLOSED) {
|
||||||
|
if (inputs.record.type === List.Types.ACTIVE) {
|
||||||
|
isClosed = true;
|
||||||
|
}
|
||||||
|
} else if (inputs.record.type === List.Types.CLOSED) {
|
||||||
|
isClosed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_.isUndefined(isClosed)) {
|
||||||
|
await Card.qm.update(
|
||||||
|
{
|
||||||
|
listId: inputs.record.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isClosed,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!_.isUndefined(values.position)) {
|
if (!_.isUndefined(values.position)) {
|
||||||
const lists = await sails.helpers.boards.getFiniteListsById(
|
const lists = await sails.helpers.boards.getFiniteListsById(
|
||||||
inputs.board.id,
|
inputs.board.id,
|
||||||
|
|
|
@ -48,6 +48,11 @@ module.exports = {
|
||||||
stopwatch: {
|
stopwatch: {
|
||||||
type: 'json',
|
type: 'json',
|
||||||
},
|
},
|
||||||
|
isClosed: {
|
||||||
|
type: 'boolean',
|
||||||
|
defaultsTo: false,
|
||||||
|
columnName: 'is_closed',
|
||||||
|
},
|
||||||
commentsTotal: {
|
commentsTotal: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
defaultsTo: 0,
|
defaultsTo: 0,
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.up = async (knex) => {
|
||||||
|
await knex.schema.alterTable('card', (table) => {
|
||||||
|
/* Columns */
|
||||||
|
|
||||||
|
table.boolean('is_closed').notNullable().default(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex.raw(`
|
||||||
|
UPDATE card
|
||||||
|
SET is_closed = TRUE
|
||||||
|
FROM list
|
||||||
|
WHERE card.list_id = list.id AND list.type = 'closed';
|
||||||
|
`);
|
||||||
|
|
||||||
|
return knex.schema.alterTable('card', (table) => {
|
||||||
|
table.boolean('is_closed').notNullable().alter();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = (knex) =>
|
||||||
|
knex.schema.table('card', (table) => {
|
||||||
|
table.dropColumn('is_closed');
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue