diff --git a/client/src/actions/cards.js b/client/src/actions/cards.js
index 0b0be6b..7e44f3c 100644
--- a/client/src/actions/cards.js
+++ b/client/src/actions/cards.js
@@ -121,6 +121,33 @@ const handleCardDelete = (card) => ({
},
});
+const copyCard = (id) => ({
+ type: ActionTypes.CARD_COPY,
+ payload: {
+ id,
+ },
+});
+
+copyCard.success = (card) => ({
+ type: ActionTypes.CARD_COPY__SUCCESS,
+ payload: {
+ card,
+ },
+});
+
+copyCard.failure = (id, error) => ({
+ type: ActionTypes.CARD_COPY__FAILURE,
+ payload: {
+ id,
+ error,
+ },
+});
+
+const handleCardCopy = (card) => ({
+ type: ActionTypes.CARD_COPY_HANDLE,
+ payload: {
+ card,
+
const filterText = (boardId, text) => ({
type: ActionTypes.TEXT_FILTER_IN_CURRENT_BOARD,
payload: {
@@ -137,5 +164,7 @@ export default {
duplicateCard,
deleteCard,
handleCardDelete,
+ copyCard,
+ handleCardCopy,
filterText,
};
diff --git a/client/src/actions/lists.js b/client/src/actions/lists.js
index 4f85864..99c8d0b 100644
--- a/client/src/actions/lists.js
+++ b/client/src/actions/lists.js
@@ -121,6 +121,35 @@ const handleListDelete = (list) => ({
},
});
+const sortList = (id) => ({
+ type: ActionTypes.LIST_SORT,
+ payload: {
+ id,
+ },
+});
+
+sortList.success = (list) => ({
+ type: ActionTypes.LIST_SORT__SUCCESS,
+ payload: {
+ list,
+ },
+});
+
+sortList.failure = (id, error) => ({
+ type: ActionTypes.LIST_SORT__FAILURE,
+ payload: {
+ id,
+ error,
+ },
+});
+
+const handleListSort = (list) => ({
+ type: ActionTypes.LIST_SORT_HANDLE,
+ payload: {
+ list,
+ },
+});
+
export default {
createList,
handleListCreate,
@@ -130,4 +159,6 @@ export default {
handleListSort,
deleteList,
handleListDelete,
+ sortList,
+ handleListSort,
};
diff --git a/client/src/api/cards.js b/client/src/api/cards.js
index 568ca8e..5b87b87 100755
--- a/client/src/api/cards.js
+++ b/client/src/api/cards.js
@@ -41,6 +41,12 @@ const createCard = (listId, data, headers) =>
item: transformCard(body.item),
}));
+const copyCard = (listId, data, headers) =>
+ socket.post(`/lists/${listId}/cards`, transformCardData(data), headers).then((body) => ({
+ ...body,
+ item: transformCard(body.item),
+ }));
+
const getCard = (id, headers) =>
socket.get(`/cards/${id}`, undefined, headers).then((body) => ({
...body,
@@ -91,4 +97,5 @@ export default {
makeHandleCardCreate,
makeHandleCardUpdate,
makeHandleCardDelete,
+ copyCard,
};
diff --git a/client/src/components/Card/ActionsStep.jsx b/client/src/components/Card/ActionsStep.jsx
index bd9db9e..150052b 100644
--- a/client/src/components/Card/ActionsStep.jsx
+++ b/client/src/components/Card/ActionsStep.jsx
@@ -12,6 +12,7 @@ import DueDateEditStep from '../DueDateEditStep';
import StopwatchEditStep from '../StopwatchEditStep';
import CardMoveStep from '../CardMoveStep';
import DeleteStep from '../DeleteStep';
+import CardCopyStep from '../CardCopyStep';
import styles from './ActionsStep.module.scss';
@@ -22,6 +23,7 @@ const StepTypes = {
EDIT_STOPWATCH: 'EDIT_STOPWATCH',
MOVE: 'MOVE',
DELETE: 'DELETE',
+ COPY: 'COPY',
};
const ActionsStep = React.memo(
@@ -48,9 +50,35 @@ const ActionsStep = React.memo(
onLabelMove,
onLabelDelete,
onClose,
+ onCopyCard,
}) => {
const [t] = useTranslation();
const [step, openStep, handleBack] = useSteps();
+ // prepare defaultPath data for copying card
+ const defaultPath = {};
+ defaultPath.id = card.id;
+ defaultPath.boardId = card.boardId;
+ defaultPath.projectId = card.projectId;
+ defaultPath.listId = card.listId;
+ defaultPath.cardId = card.id;
+ defaultPath.name = card.name;
+ if (card.dueDate !== null) {
+ defaultPath.dueDate = card.dueDate;
+ }
+ if (card.dueDate === null || card.dueDate === undefined) {
+ // eslint-disable-next-line no-param-reassign
+ if (defaultPath.dueDate) {
+ delete defaultPath.dueDate;
+ }
+ }
+ defaultPath.stopwatch = card.stopwatch;
+ defaultPath.labels = card.labels;
+ defaultPath.boardMemberships = boardMemberships;
+ defaultPath.currentLabelIds = currentLabelIds;
+ defaultPath.currentUserIds = currentUserIds;
+ defaultPath.tasks = card.tasks;
+ defaultPath.users = card.users;
+ defaultPath.description = card.description;
const handleEditNameClick = useCallback(() => {
onNameEdit();
@@ -77,11 +105,16 @@ const ActionsStep = React.memo(
openStep(StepTypes.MOVE);
}, [openStep]);
+ const handleCopyClick = useCallback(() => {
+ openStep(StepTypes.COPY);
+ }, [openStep]);
+
const handleDuplicateClick = useCallback(() => {
onDuplicate();
onClose();
}, [onDuplicate, onClose]);
+
const handleDeleteClick = useCallback(() => {
openStep(StepTypes.DELETE);
}, [openStep]);
@@ -170,6 +203,18 @@ const ActionsStep = React.memo(
onBack={handleBack}
/>
);
+ case StepTypes.COPY:
+ return (
+
+ );
default:
}
}
@@ -223,6 +268,11 @@ const ActionsStep = React.memo(
context: 'title',
})}
+
+ {t('action.copyCard', {
+ context: 'title',
+ })}
+
>
@@ -255,6 +305,7 @@ ActionsStep.propTypes = {
onLabelMove: PropTypes.func.isRequired,
onLabelDelete: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
+ onCopyCard: PropTypes.func.isRequired,
};
export default ActionsStep;
diff --git a/client/src/components/Card/Card.jsx b/client/src/components/Card/Card.jsx
index 4adddbc..ba14cd8 100755
--- a/client/src/components/Card/Card.jsx
+++ b/client/src/components/Card/Card.jsx
@@ -21,6 +21,7 @@ import styles from './Card.module.scss';
const Card = React.memo(
({
id,
+ description,
index,
name,
dueDate,
@@ -52,6 +53,7 @@ const Card = React.memo(
onLabelUpdate,
onLabelMove,
onLabelDelete,
+ onCopyCard,
}) => {
const nameEdit = useRef(null);
@@ -171,6 +173,13 @@ const Card = React.memo(
{canEdit && (
+
+
+
+
+
+
{
const [t] = useTranslation();
-
const [path, handleFieldChange] = useForm(() => ({
projectId: null,
boardId: null,
diff --git a/client/src/components/List/ActionsStep.jsx b/client/src/components/List/ActionsStep.jsx
index bc04826..c3f25ef 100755
--- a/client/src/components/List/ActionsStep.jsx
+++ b/client/src/components/List/ActionsStep.jsx
@@ -7,6 +7,7 @@ import { Popup } from '../../lib/custom-ui';
import { useSteps } from '../../hooks';
import ListSortStep from '../ListSortStep';
import DeleteStep from '../DeleteStep';
+import SortStep from '../SortStep';
import styles from './ActionsStep.module.scss';
@@ -15,19 +16,90 @@ const StepTypes = {
SORT: 'SORT',
};
-const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onSort, onDelete, onClose }) => {
- const [t] = useTranslation();
- const [step, openStep, handleBack] = useSteps();
+const ActionsStep = React.memo(
+ ({ onNameEdit, onCardAdd, onDelete, onClose, onSort, selectedOption, setSelectedOption }) => {
+ const [t] = useTranslation();
+ const [step, openStep, handleBack] = useSteps();
- const handleEditNameClick = useCallback(() => {
- onNameEdit();
- onClose();
- }, [onNameEdit, onClose]);
+ const handleEditNameClick = useCallback(() => {
+ onNameEdit();
+ onClose();
+ }, [onNameEdit, onClose]);
+
+ const handleAddCardClick = useCallback(() => {
+ onCardAdd();
+ onClose();
+ }, [onCardAdd, onClose]);
+
+ const handleDeleteClick = useCallback(() => {
+ openStep(StepTypes.DELETE);
+ }, [openStep]);
+
+ const handleSortClick = useCallback(() => {
+ openStep(StepTypes.SORT);
+ }, [openStep]);
- const handleAddCardClick = useCallback(() => {
- onCardAdd();
- onClose();
- }, [onCardAdd, onClose]);
+ if (step && step.type === StepTypes.DELETE) {
+ return (
+
+ );
+ }
+
+ if (step && step.type === StepTypes.SORT) {
+ return (
+
+ );
+ }
+
+ return (
+ <>
+
+ {t('common.listActions', {
+ context: 'title',
+ })}
+
+
+
+
+ >
+ );
+ },
+);
const handleSortClick = useCallback(() => {
openStep(StepTypes.SORT);
@@ -107,6 +179,9 @@ ActionsStep.propTypes = {
onDelete: PropTypes.func.isRequired,
onSort: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
+ onSort: PropTypes.func.isRequired,
+ selectedOption: PropTypes.string.isRequired,
+ setSelectedOption: PropTypes.func.isRequired,
};
export default ActionsStep;
diff --git a/client/src/components/List/List.jsx b/client/src/components/List/List.jsx
index 25c85e5..a249f21 100755
--- a/client/src/components/List/List.jsx
+++ b/client/src/components/List/List.jsx
@@ -4,7 +4,7 @@ import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { Draggable, Droppable } from 'react-beautiful-dnd';
import { Button, Icon } from 'semantic-ui-react';
-import { usePopup } from '../../lib/popup';
+import { usePopup, closePopup } from '../../lib/popup';
import DroppableTypes from '../../constants/DroppableTypes';
import CardContainer from '../../containers/CardContainer';
@@ -30,6 +30,7 @@ const List = React.memo(
}) => {
const [t] = useTranslation();
const [isAddCardOpened, setIsAddCardOpened] = useState(false);
+ const [selectedOption, setSelectedOption] = useState('name');
const nameEdit = useRef(null);
const listWrapper = useRef(null);
@@ -51,11 +52,11 @@ const List = React.memo(
const handleAddCardClick = useCallback(() => {
setIsAddCardOpened(true);
- }, []);
+ }, [setIsAddCardOpened]);
const handleAddCardClose = useCallback(() => {
setIsAddCardOpened(false);
- }, []);
+ }, [setIsAddCardOpened]);
const handleNameEdit = useCallback(() => {
nameEdit.current.open();
@@ -63,7 +64,12 @@ const List = React.memo(
const handleCardAdd = useCallback(() => {
setIsAddCardOpened(true);
- }, []);
+ }, [setIsAddCardOpened]);
+
+ const onSort = useCallback(() => {
+ onUpdate({ selectedOption: document.querySelector('input[name="sort"]:checked').value });
+ closePopup();
+ }, [onUpdate]);
useEffect(() => {
if (isAddCardOpened) {
@@ -126,6 +132,9 @@ const List = React.memo(
onCardAdd={handleCardAdd}
onDelete={onDelete}
onSort={onSort}
+
+ selectedOption={selectedOption}
+ setSelectedOption={setSelectedOption}
>