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', + })} + + + + + {t('action.editTitle', { + context: 'title', + })} + + + {t('action.addCard', { + context: 'title', + })} + + + {t('action.deleteList', { + context: 'title', + })} + + + {t('action.sortList', { + 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} >