mirror of
https://github.com/mattermost/focalboard.git
synced 2025-02-01 19:14:35 +02:00
[GH-3947]: Added confirmation dialog when deleting a card in calendar/galley view (#3996)
Co-authored-by: Paul Esch-Laurent <paul.esch-laurent@mattermost.com> Co-authored-by: Rajat Dabade <rajat.dabade@mattermost.com>
This commit is contained in:
parent
3479e02657
commit
ed3197ca62
@ -396,7 +396,7 @@
|
||||
"rhs-boards.no-boards-linked-to-channel": "No boards are linked to {channelName} yet",
|
||||
"rhs-boards.no-boards-linked-to-channel-description": "Boards is a project management tool that helps define, organize, track and manage work across teams, using a familiar kanban board view.",
|
||||
"rhs-boards.unlink-board": "Unlink board",
|
||||
"rhs-boards.unlink-board1": "Unlink board Hello",
|
||||
"rhs-boards.unlink-board1": "Unlink board",
|
||||
"rhs-channel-boards-header.title": "Boards",
|
||||
"share-board.publish": "Publish",
|
||||
"share-board.share": "Share",
|
||||
@ -420,4 +420,4 @@
|
||||
"tutorial_tip.ok": "Next",
|
||||
"tutorial_tip.out": "Opt out of these tips.",
|
||||
"tutorial_tip.seen": "Seen this before?"
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useMemo} from 'react'
|
||||
import React, {useCallback, useMemo, useState} from 'react'
|
||||
import {useIntl} from 'react-intl'
|
||||
|
||||
import FullCalendar, {EventChangeArg, EventInput, EventContentArg, DayCellContentArg} from '@fullcalendar/react'
|
||||
@ -21,6 +21,7 @@ import PropertyValueElement from '../propertyValueElement'
|
||||
import {Constants, Permission} from '../../constants'
|
||||
import {useHasCurrentBoardPermissions} from '../../hooks/permissions'
|
||||
import CardBadges from '../cardBadges'
|
||||
import ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../confirmationDialogBox'
|
||||
|
||||
import './fullcalendar.scss'
|
||||
import MenuWrapper from '../../widgets/menuWrapper'
|
||||
@ -74,6 +75,8 @@ const CalendarFullView = (props: Props): JSX.Element|null => {
|
||||
const {board, cards, activeView, dateDisplayProperty, readonly} = props
|
||||
const isSelectable = !readonly
|
||||
const canAddCards = useHasCurrentBoardPermissions([Permission.ManageBoardCards])
|
||||
const [showConfirmationDialogBox, setShowConfirmationDialogBox] = useState<boolean>(false)
|
||||
const [cardItem, setCardItem] = useState<Card>()
|
||||
|
||||
const visiblePropertyTemplates = useMemo(() => (
|
||||
board.cardProperties.filter((template: IPropertyTemplate) => activeView.fields.visiblePropertyIds.includes(template.id))
|
||||
@ -114,53 +117,79 @@ const CalendarFullView = (props: Props): JSX.Element|null => {
|
||||
|
||||
const visibleBadges = activeView.fields.visiblePropertyIds.includes(Constants.badgesColumnId)
|
||||
|
||||
const openConfirmationDialogBox = (card: Card) => {
|
||||
setShowConfirmationDialogBox(true)
|
||||
setCardItem(card)
|
||||
}
|
||||
|
||||
const handleDeleteCard = useCallback(() => {
|
||||
if (!cardItem) {
|
||||
return
|
||||
}
|
||||
mutator.deleteBlock(cardItem, 'delete card')
|
||||
}, [cardItem, board.id])
|
||||
|
||||
const confirmDialogProps: ConfirmationDialogBoxProps = useMemo(() => {
|
||||
return {
|
||||
heading: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-heading', defaultMessage: 'Confirm card delete!'}),
|
||||
confirmButtonText: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-button-text', defaultMessage: 'Delete'}),
|
||||
onConfirm: handleDeleteCard,
|
||||
onClose: () => {
|
||||
setShowConfirmationDialogBox(false)
|
||||
},
|
||||
}
|
||||
}, [handleDeleteCard])
|
||||
|
||||
const renderEventContent = (eventProps: EventContentArg): JSX.Element|null => {
|
||||
const {event} = eventProps
|
||||
const card = cards.find((o) => o.id === event.id) || cards[0]
|
||||
|
||||
return (
|
||||
<div
|
||||
className='EventContent'
|
||||
onClick={() => props.showCard(event.id)}
|
||||
>
|
||||
{!props.readonly &&
|
||||
<MenuWrapper
|
||||
className='optionsMenu'
|
||||
stopPropagationOnToggle={true}
|
||||
<>
|
||||
<div
|
||||
className='EventContent'
|
||||
onClick={() => props.showCard(event.id)}
|
||||
>
|
||||
<IconButton icon={<OptionsIcon/>}/>
|
||||
<CardActionsMenu
|
||||
cardId={card.id}
|
||||
onClickDelete={() => mutator.deleteBlock(card, 'delete card')}
|
||||
onClickDuplicate={() => {
|
||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DuplicateCard, {board: board.id, card: card.id})
|
||||
mutator.duplicateCard(card.id, board.id)
|
||||
}}
|
||||
/>
|
||||
</MenuWrapper>}
|
||||
<div className='octo-icontitle'>
|
||||
{ event.extendedProps.icon ? <div className='octo-icon'>{event.extendedProps.icon}</div> : undefined }
|
||||
<div
|
||||
className='fc-event-title'
|
||||
key='__title'
|
||||
>{event.title || intl.formatMessage({id: 'CalendarCard.untitled', defaultMessage: 'Untitled'})}</div>
|
||||
</div>
|
||||
{visiblePropertyTemplates.map((template) => (
|
||||
<Tooltip
|
||||
key={template.id}
|
||||
title={template.name}
|
||||
{!props.readonly &&
|
||||
<MenuWrapper
|
||||
className='optionsMenu'
|
||||
stopPropagationOnToggle={true}
|
||||
>
|
||||
<PropertyValueElement
|
||||
board={board}
|
||||
readOnly={true}
|
||||
card={card}
|
||||
propertyTemplate={template}
|
||||
showEmptyPlaceholder={false}
|
||||
<IconButton icon={<OptionsIcon/>}/>
|
||||
<CardActionsMenu
|
||||
cardId={card.id}
|
||||
onClickDelete={() => openConfirmationDialogBox(card)}
|
||||
onClickDuplicate={() => {
|
||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DuplicateCard, {board: board.id, card: card.id})
|
||||
mutator.duplicateCard(card.id, board.id)
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
))}
|
||||
{visibleBadges &&
|
||||
<CardBadges card={card}/> }
|
||||
</div>
|
||||
</MenuWrapper>}
|
||||
<div className='octo-icontitle'>
|
||||
{ event.extendedProps.icon ? <div className='octo-icon'>{event.extendedProps.icon}</div> : undefined }
|
||||
<div
|
||||
className='fc-event-title'
|
||||
key='__title'
|
||||
>{event.title || intl.formatMessage({id: 'CalendarCard.untitled', defaultMessage: 'Untitled'})}</div>
|
||||
</div>
|
||||
{visiblePropertyTemplates.map((template) => (
|
||||
<Tooltip
|
||||
key={template.id}
|
||||
title={template.name}
|
||||
>
|
||||
<PropertyValueElement
|
||||
board={board}
|
||||
readOnly={true}
|
||||
card={card}
|
||||
propertyTemplate={template}
|
||||
showEmptyPlaceholder={false}
|
||||
/>
|
||||
</Tooltip>
|
||||
))}
|
||||
{visibleBadges &&
|
||||
<CardBadges card={card}/> }
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -251,6 +280,7 @@ const CalendarFullView = (props: Props): JSX.Element|null => {
|
||||
selectMirror={true}
|
||||
select={onNewEvent}
|
||||
/>
|
||||
{showConfirmationDialogBox && <ConfirmationDialogBox dialogBox={confirmDialogProps}/>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -904,6 +904,77 @@ exports[`src/components/gallery/GalleryCard without block content return Gallery
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="Dialog dialog-back confirmation-dialog-box"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
/>
|
||||
<div
|
||||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="toolbar"
|
||||
>
|
||||
<div>
|
||||
<h1
|
||||
class="dialog-title"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="toolbar--right"
|
||||
>
|
||||
<button
|
||||
aria-label="Close dialog"
|
||||
title="Close dialog"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-close CloseIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="box-area"
|
||||
title="Confirmation Dialog Box"
|
||||
>
|
||||
<h3
|
||||
class="text-heading5"
|
||||
>
|
||||
Confirm card delete!
|
||||
</h3>
|
||||
<div
|
||||
class="sub-text"
|
||||
/>
|
||||
<div
|
||||
class="action-buttons"
|
||||
>
|
||||
<button
|
||||
title="Cancel"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Cancel
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
title="Delete"
|
||||
type="submit"
|
||||
>
|
||||
<span>
|
||||
Delete
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
@ -159,8 +159,6 @@ describe('src/components/gallery/GalleryCard', () => {
|
||||
const buttonDelete = screen.getByRole('button', {name: 'Delete'})
|
||||
userEvent.click(buttonDelete)
|
||||
expect(container).toMatchSnapshot()
|
||||
expect(mockedMutator.deleteBlock).toBeCalledTimes(1)
|
||||
expect(mockedMutator.deleteBlock).toBeCalledWith(card, 'delete card')
|
||||
})
|
||||
|
||||
test('return GalleryCard and duplicate card', () => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React, {useMemo} from 'react'
|
||||
import {FormattedMessage} from 'react-intl'
|
||||
import React, {useMemo, useState, useCallback} from 'react'
|
||||
import {useIntl, FormattedMessage} from 'react-intl'
|
||||
|
||||
import {Board, IPropertyTemplate} from '../../blocks/board'
|
||||
import {Card} from '../../blocks/card'
|
||||
@ -22,6 +22,7 @@ import PropertyValueElement from '../propertyValueElement'
|
||||
import './galleryCard.scss'
|
||||
import CardBadges from '../cardBadges'
|
||||
import CardActionsMenu from '../cardActionsMenu/cardActionsMenu'
|
||||
import ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../confirmationDialogBox'
|
||||
|
||||
type Props = {
|
||||
board: Board
|
||||
@ -37,12 +38,29 @@ type Props = {
|
||||
}
|
||||
|
||||
const GalleryCard = (props: Props) => {
|
||||
const intl = useIntl()
|
||||
const {card, board} = props
|
||||
const [isDragging, isOver, cardRef] = useSortable('card', card, props.isManualSort && !props.readonly, props.onDrop)
|
||||
const contents = useAppSelector(getCardContents(card.id))
|
||||
const [showConfirmationDialogBox, setShowConfirmationDialogBox] = useState<boolean>(false)
|
||||
|
||||
const visiblePropertyTemplates = props.visiblePropertyTemplates || []
|
||||
|
||||
const handleDeleteCard = useCallback(() => {
|
||||
mutator.deleteBlock(card, 'delete card')
|
||||
}, [card, board.id])
|
||||
|
||||
const confirmDialogProps: ConfirmationDialogBoxProps = useMemo(() => {
|
||||
return {
|
||||
heading: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-heading', defaultMessage: 'Confirm card delete!'}),
|
||||
confirmButtonText: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-button-text', defaultMessage: 'Delete'}),
|
||||
onConfirm: handleDeleteCard,
|
||||
onClose: () => {
|
||||
setShowConfirmationDialogBox(false)
|
||||
},
|
||||
}
|
||||
}, [handleDeleteCard])
|
||||
|
||||
const image: ContentBlock|undefined = useMemo(() => {
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
if (Array.isArray(contents[i])) {
|
||||
@ -60,97 +78,100 @@ const GalleryCard = (props: Props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
onClick={(e: React.MouseEvent) => props.onClick(e, card)}
|
||||
style={{opacity: isDragging ? 0.5 : 1}}
|
||||
ref={cardRef}
|
||||
>
|
||||
{!props.readonly &&
|
||||
<MenuWrapper
|
||||
className='optionsMenu'
|
||||
stopPropagationOnToggle={true}
|
||||
>
|
||||
<IconButton icon={<OptionsIcon/>}/>
|
||||
<CardActionsMenu
|
||||
cardId={card!.id}
|
||||
onClickDelete={() => mutator.deleteBlock(card, 'delete card')}
|
||||
onClickDuplicate={() => {
|
||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DuplicateCard, {board: board.id, card: card.id})
|
||||
mutator.duplicateCard(card.id, board.id)
|
||||
}}
|
||||
/>
|
||||
</MenuWrapper>
|
||||
}
|
||||
<>
|
||||
<div
|
||||
className={className}
|
||||
onClick={(e: React.MouseEvent) => props.onClick(e, card)}
|
||||
style={{opacity: isDragging ? 0.5 : 1}}
|
||||
ref={cardRef}
|
||||
>
|
||||
{!props.readonly &&
|
||||
<MenuWrapper
|
||||
className='optionsMenu'
|
||||
stopPropagationOnToggle={true}
|
||||
>
|
||||
<IconButton icon={<OptionsIcon/>}/>
|
||||
<CardActionsMenu
|
||||
cardId={card!.id}
|
||||
onClickDelete={() => setShowConfirmationDialogBox(true)}
|
||||
onClickDuplicate={() => {
|
||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DuplicateCard, {board: board.id, card: card.id})
|
||||
mutator.duplicateCard(card.id, board.id)
|
||||
}}
|
||||
/>
|
||||
</MenuWrapper>
|
||||
}
|
||||
|
||||
{image &&
|
||||
<div className='gallery-image'>
|
||||
<ImageElement block={image}/>
|
||||
</div>}
|
||||
{!image &&
|
||||
<CardDetailProvider card={card}>
|
||||
<div className='gallery-item'>
|
||||
{contents.map((block) => {
|
||||
if (Array.isArray(block)) {
|
||||
return block.map((b) => (
|
||||
{image &&
|
||||
<div className='gallery-image'>
|
||||
<ImageElement block={image}/>
|
||||
</div>}
|
||||
{!image &&
|
||||
<CardDetailProvider card={card}>
|
||||
<div className='gallery-item'>
|
||||
{contents.map((block) => {
|
||||
if (Array.isArray(block)) {
|
||||
return block.map((b) => (
|
||||
<ContentElement
|
||||
key={b.id}
|
||||
block={b}
|
||||
readonly={true}
|
||||
cords={{x: 0}}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
||||
return (
|
||||
<ContentElement
|
||||
key={b.id}
|
||||
block={b}
|
||||
key={block.id}
|
||||
block={block}
|
||||
readonly={true}
|
||||
cords={{x: 0}}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
||||
return (
|
||||
<ContentElement
|
||||
key={block.id}
|
||||
block={block}
|
||||
readonly={true}
|
||||
cords={{x: 0}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</CardDetailProvider>}
|
||||
{props.visibleTitle &&
|
||||
<div className='gallery-title'>
|
||||
{ card.fields.icon ? <div className='octo-icon'>{card.fields.icon}</div> : undefined }
|
||||
<div
|
||||
key='__title'
|
||||
className='octo-titletext'
|
||||
>
|
||||
{card.title ||
|
||||
<FormattedMessage
|
||||
id='KanbanCard.untitled'
|
||||
defaultMessage='Untitled'
|
||||
/>}
|
||||
</div>
|
||||
</div>}
|
||||
{visiblePropertyTemplates.length > 0 &&
|
||||
<div className='gallery-props'>
|
||||
{visiblePropertyTemplates.map((template) => (
|
||||
<Tooltip
|
||||
key={template.id}
|
||||
title={template.name}
|
||||
placement='top'
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</CardDetailProvider>}
|
||||
{props.visibleTitle &&
|
||||
<div className='gallery-title'>
|
||||
{ card.fields.icon ? <div className='octo-icon'>{card.fields.icon}</div> : undefined }
|
||||
<div
|
||||
key='__title'
|
||||
className='octo-titletext'
|
||||
>
|
||||
<PropertyValueElement
|
||||
board={board}
|
||||
readOnly={true}
|
||||
card={card}
|
||||
propertyTemplate={template}
|
||||
showEmptyPlaceholder={false}
|
||||
/>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>}
|
||||
{props.visibleBadges &&
|
||||
<CardBadges
|
||||
card={card}
|
||||
className='gallery-badges'
|
||||
/>}
|
||||
</div>
|
||||
{card.title ||
|
||||
<FormattedMessage
|
||||
id='KanbanCard.untitled'
|
||||
defaultMessage='Untitled'
|
||||
/>}
|
||||
</div>
|
||||
</div>}
|
||||
{visiblePropertyTemplates.length > 0 &&
|
||||
<div className='gallery-props'>
|
||||
{visiblePropertyTemplates.map((template) => (
|
||||
<Tooltip
|
||||
key={template.id}
|
||||
title={template.name}
|
||||
placement='top'
|
||||
>
|
||||
<PropertyValueElement
|
||||
board={board}
|
||||
readOnly={true}
|
||||
card={card}
|
||||
propertyTemplate={template}
|
||||
showEmptyPlaceholder={false}
|
||||
/>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>}
|
||||
{props.visibleBadges &&
|
||||
<CardBadges
|
||||
card={card}
|
||||
className='gallery-badges'
|
||||
/>}
|
||||
</div>
|
||||
{showConfirmationDialogBox && <ConfirmationDialogBox dialogBox={confirmDialogProps}/>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user