// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import React, {useEffect, useRef, useState, useMemo, useCallback} from 'react' import {FormattedMessage, useIntl} from 'react-intl' import {Card} from '../../blocks/card' import {Board, IPropertyTemplate} from '../../blocks/board' import {Constants} from '../../constants' import mutator from '../../mutator' import Button from '../../widgets/buttons/button' import Editable from '../../widgets/editable' import {useSortable} from '../../hooks/sortable' import {Utils} from '../../utils' import PropertyValueElement from '../propertyValueElement' import MenuWrapper from '../../widgets/menuWrapper' import IconButton from '../../widgets/buttons/iconButton' import CompassIcon from '../../widgets/icons/compassIcon' import OptionsIcon from '../../widgets/icons/options' import Tooltip from '../../widgets/tooltip' import ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../confirmationDialogBox' import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient' import CardActionsMenu from '../cardActionsMenu/cardActionsMenu' import {useColumnResize} from './tableColumnResizeContext' import './tableRow.scss' type Props = { board: Board columnWidths: Record isManualSort: boolean groupById?: string visiblePropertyIds: string[] collapsedOptionIds: string[] card: Card isSelected: boolean focusOnMount: boolean isLastCard: boolean showCard: (cardId?: string) => void readonly: boolean addCard: (groupByOptionId?: string) => Promise onClick?: (e: React.MouseEvent, card: Card) => void onDrop: (srcCard: Card, dstCard: Card) => void } const TableRow = (props: Props) => { const intl = useIntl() const {board, card, isManualSort, groupById, visiblePropertyIds, collapsedOptionIds} = props const titleRef = useRef<{ focus(selectAll?: boolean): void }>(null) const [title, setTitle] = useState(props.card.title || '') const isGrouped = Boolean(groupById) const [isDragging, isOver, cardRef] = useSortable('card', card, !props.readonly && (isManualSort || isGrouped), props.onDrop) const [showConfirmationDialogBox, setShowConfirmationDialogBox] = useState(false) const columnResize = useColumnResize() useEffect(() => { if (props.focusOnMount) { setTimeout(() => titleRef.current?.focus(), 10) } }, []) const onClick = useCallback((e: React.MouseEvent) => { props.onClick && props.onClick(e, card) }, [card, props.onClick]) const onSaveWithEnter = useCallback(() => { if (props.isLastCard) { props.addCard(groupById ? card.fields.properties[groupById!] as string : '') } }, [groupById && card.fields.properties[groupById!], props.isLastCard, props.addCard]) const onSave = useCallback((saveType) => { if (card.title !== title) { mutator.changeBlockTitle(props.board.id, card.id, card.title, title) if (saveType === 'onEnter') { onSaveWithEnter() } } }, [card.title, title, onSaveWithEnter, board.id, card.id]) const onTitleChange = useCallback((newTitle: string) => { setTitle(newTitle) }, [title, setTitle]) const visiblePropertyTemplates = useMemo(() => ( visiblePropertyIds.map((id) => board.cardProperties.find((t) => t.id === id)).filter((i) => i) as IPropertyTemplate[] ), [board.cardProperties, visiblePropertyIds]) let className = props.isSelected ? 'TableRow octo-table-row selected' : 'TableRow octo-table-row' if (isOver) { className += ' dragover' } if (isGrouped) { const groupID = groupById || '' let groupValue = card.fields.properties[groupID] as string || 'undefined' if (groupValue === 'undefined') { const template = board.cardProperties.find((p) => p.id === groupById) //templates.find((o) => o.id === groupById) if (template && template.type === 'createdBy') { groupValue = card.createdBy } else if (template && template.type === 'updatedBy') { groupValue = card.modifiedBy } } else if (Array.isArray(groupValue)) { groupValue = groupValue[0] } if (collapsedOptionIds.indexOf(groupValue) > -1) { className += ' hidden' } } if (props.readonly) { className += ' readonly' } const handleDeleteCard = useCallback(async () => { if (!card) { Utils.assertFailure() return } TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DeleteCard, {board: board.id, card: card.id}) await 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 handleDeleteButtonOnClick = useCallback(() => { // user trying to delete a card with blank name // but content present cannot be deleted without // confirmation dialog if (card?.title === '' && card?.fields.contentOrder.length === 0) { handleDeleteCard() return } setShowConfirmationDialogBox(true) }, [card.title, card.fields.contentOrder, handleDeleteCard]) return (
{!props.readonly && ( }/> )}
{/* Name / title */}
columnResize.updateRef(card.id, Constants.titleColumnId, ref)} >
{card.fields.icon}
setTitle(card.title || '')} readonly={props.readonly} spellCheck={true} />
{!props.readonly && ( } /> { mutator.duplicateCard( card.id, board.id, false, intl.formatMessage({id: 'TableRow.DuplicateCard', defaultMessage: 'duplicate card'}), false, {}, async (newCardId) => { props.showCard(newCardId) }, async () => { props.showCard(undefined) }, ) }} /> )}
{/* Columns, one per property */} {visiblePropertyTemplates.map((template) => { return (
columnResize.updateRef(card.id, template.id, ref)} >
) })} {showConfirmationDialogBox && }
) } export default React.memo(TableRow)