/*! * Copyright (c) 2024 PLANKA Software GmbH * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md */ import React, { useCallback, useMemo } from 'react'; import PropTypes from 'prop-types'; import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; import { Menu } from 'semantic-ui-react'; import { Popup } from '../../../lib/custom-ui'; import selectors from '../../../selectors'; import entryActions from '../../../entry-actions'; import { useSteps } from '../../../hooks'; import { isListArchiveOrTrash } from '../../../utils/record-helpers'; import { BoardMembershipRoles, CardTypes, ListTypes } from '../../../constants/Enums'; import SelectCardTypeStep from '../SelectCardTypeStep'; import EditDueDateStep from '../EditDueDateStep'; import EditStopwatchStep from '../EditStopwatchStep'; import MoveCardStep from '../MoveCardStep'; import ConfirmationStep from '../../common/ConfirmationStep'; import BoardMembershipsStep from '../../board-memberships/BoardMembershipsStep'; import LabelsStep from '../../labels/LabelsStep'; import styles from './ActionsStep.module.scss'; const StepTypes = { EDIT_TYPE: 'EDIT_TYPE', USERS: 'USERS', LABELS: 'LABELS', EDIT_DUE_DATE: 'EDIT_DUE_DATE', EDIT_STOPWATCH: 'EDIT_STOPWATCH', MOVE: 'MOVE', ARCHIVE: 'ARCHIVE', DELETE: 'DELETE', }; const ActionsStep = React.memo(({ cardId, onNameEdit, onClose }) => { const selectCardById = useMemo(() => selectors.makeSelectCardById(), []); const selectListById = useMemo(() => selectors.makeSelectListById(), []); const selectPrevListById = useMemo(() => selectors.makeSelectListById(), []); const selectUserIdsByCardId = useMemo(() => selectors.makeSelectUserIdsByCardId(), []); const selectLabelIdsByCardId = useMemo(() => selectors.makeSelectLabelIdsByCardId(), []); const board = useSelector(selectors.selectCurrentBoard); const card = useSelector((state) => selectCardById(state, cardId)); const list = useSelector((state) => selectListById(state, card.listId)); // TODO: check availability? const prevList = useSelector( (state) => card.prevListId && selectPrevListById(state, card.prevListId), ); const userIds = useSelector((state) => selectUserIdsByCardId(state, cardId)); const labelIds = useSelector((state) => selectLabelIdsByCardId(state, cardId)); const { canEditType, canEditName, canEditDueDate, canEditStopwatch, canDuplicate, canMove, canRestore, canArchive, canDelete, canUseMembers, canUseLabels, } = useSelector((state) => { const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state); const isEditor = !!boardMembership && boardMembership.role === BoardMembershipRoles.EDITOR; if (isListArchiveOrTrash(list)) { return { canEditType: false, canEditName: false, canEditDueDate: false, canEditStopwatch: false, canDuplicate: false, canMove: false, canRestore: isEditor, canArchive: isEditor, canDelete: isEditor, canUseMembers: false, canUseLabels: false, }; } return { canEditType: isEditor, canEditName: isEditor, canEditDueDate: isEditor, canEditStopwatch: isEditor, canDuplicate: isEditor, canMove: isEditor, canRestore: null, canArchive: isEditor, canDelete: isEditor, canUseMembers: isEditor, canUseLabels: isEditor, }; }, shallowEqual); const dispatch = useDispatch(); const [t] = useTranslation(); const [step, openStep, handleBack] = useSteps(); const handleTypeSelect = useCallback( (type) => { dispatch( entryActions.updateCard(cardId, { type, }), ); }, [cardId, dispatch], ); const handleDuplicateClick = useCallback(() => { dispatch( entryActions.duplicateCard(cardId, { name: `${card.name} (${t('common.copy', { context: 'inline', })})`, }), ); onClose(); }, [cardId, onClose, card.name, dispatch, t]); const handleRestoreClick = useCallback(() => { dispatch(entryActions.moveCard(cardId, card.prevListId, undefined, true)); }, [cardId, card.prevListId, dispatch]); const handleArchiveConfirm = useCallback(() => { dispatch(entryActions.moveCardToArchive(cardId)); }, [cardId, dispatch]); const isInTrashList = list.type === ListTypes.TRASH; const handleDeleteConfirm = useCallback(() => { if (isInTrashList) { dispatch(entryActions.deleteCard(cardId)); } else { dispatch(entryActions.moveCardToTrash(cardId)); } }, [cardId, isInTrashList, dispatch]); const handleUserSelect = useCallback( (userId) => { dispatch(entryActions.addUserToCard(userId, cardId)); }, [cardId, dispatch], ); const handleUserDeselect = useCallback( (userId) => { dispatch(entryActions.removeUserFromCard(userId, cardId)); }, [cardId, dispatch], ); const handleLabelSelect = useCallback( (labelId) => { dispatch(entryActions.addLabelToCard(labelId, cardId)); }, [cardId, dispatch], ); const handleLabelDeselect = useCallback( (labelId) => { dispatch(entryActions.removeLabelFromCard(labelId, cardId)); }, [cardId, dispatch], ); const handleEditNameClick = useCallback(() => { onNameEdit(); onClose(); }, [onNameEdit, onClose]); const handleEditTypeClick = useCallback(() => { openStep(StepTypes.EDIT_TYPE); }, [openStep]); const handleUsersClick = useCallback(() => { openStep(StepTypes.USERS); }, [openStep]); const handleLabelsClick = useCallback(() => { openStep(StepTypes.LABELS); }, [openStep]); const handleEditDueDateClick = useCallback(() => { openStep(StepTypes.EDIT_DUE_DATE); }, [openStep]); const handleEditStopwatchClick = useCallback(() => { openStep(StepTypes.EDIT_STOPWATCH); }, [openStep]); const handleMoveClick = useCallback(() => { openStep(StepTypes.MOVE); }, [openStep]); const handleArchiveClick = useCallback(() => { openStep(StepTypes.ARCHIVE); }, [openStep]); const handleDeleteClick = useCallback(() => { openStep(StepTypes.DELETE); }, [openStep]); if (step) { switch (step.type) { case StepTypes.EDIT_TYPE: return ( ); case StepTypes.USERS: return ( ); case StepTypes.LABELS: return ( ); case StepTypes.EDIT_DUE_DATE: return ; case StepTypes.EDIT_STOPWATCH: return ; case StepTypes.MOVE: return ; case StepTypes.ARCHIVE: return ( ); case StepTypes.DELETE: return ( ); default: } } return ( <> {t('common.cardActions', { context: 'title', })} {canEditName && ( {t('action.editTitle', { context: 'title', })} )} {!board.limitCardTypesToDefaultOne && canEditType && ( {t('action.editType', { context: 'title', })} )} {card.type === CardTypes.PROJECT && canUseMembers && ( {t('common.members', { context: 'title', })} )} {canUseLabels && ( {t('common.labels', { context: 'title', })} )} {card.type === CardTypes.STORY && canUseMembers && ( {t('common.members', { context: 'title', })} )} {card.type === CardTypes.PROJECT && canEditDueDate && ( {t('action.editDueDate', { context: 'title', })} )} {card.type === CardTypes.PROJECT && canEditStopwatch && ( {t('action.editStopwatch', { context: 'title', })} )} {canDuplicate && ( {t('action.duplicateCard', { context: 'title', })} )} {canMove && ( {t('action.moveCard', { context: 'title', })} )} {prevList && canRestore && ( {t('action.restoreToList', { context: 'title', list: prevList.name || t(`common.${prevList.type}`), })} )} {list.type !== ListTypes.ARCHIVE && canArchive && ( {t('action.archiveCard', { context: 'title', })} )} {canDelete && ( {isInTrashList ? t('action.deleteForever', { context: 'title', }) : t('action.deleteCard', { context: 'title', })} )} ); }); ActionsStep.propTypes = { cardId: PropTypes.string.isRequired, onNameEdit: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired, }; export default ActionsStep;