// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import React, {useCallback} from 'react' import {FormattedMessage} from 'react-intl' import {IPropertyOption, IPropertyTemplate, Board, BoardGroup} from '../../blocks/board' import {createBoardView, BoardView} from '../../blocks/boardView' import {Card} from '../../blocks/card' import {Constants, Permission} from '../../constants' import mutator from '../../mutator' import {Utils} from '../../utils' import {useAppDispatch} from '../../store/hooks' import {updateView} from '../../store/views' import {useHasCurrentBoardPermissions} from '../../hooks/permissions' import BoardPermissionGate from '../permissions/boardPermissionGate' import './table.scss' import HiddenCardCount from '../../components/hiddenCardCount/hiddenCardCount' import TableHeaders from './tableHeaders' import TableRows from './tableRows' import TableGroup from './tableGroup' import CalculationRow from './calculation/calculationRow' import {ColumnResizeProvider} from './tableColumnResizeContext' type Props = { selectedCardIds: string[] board: Board cards: Card[] activeView: BoardView views: BoardView[] visibleGroups: BoardGroup[] groupByProperty?: IPropertyTemplate readonly: boolean cardIdToFocusOnRender: string showCard: (cardId?: string) => void addCard: (groupByOptionId?: string) => Promise onCardClicked: (e: React.MouseEvent, card: Card) => void hiddenCardsCount: number showHiddenCardCountNotification: (show: boolean) => void } const Table = (props: Props): JSX.Element => { const {board, cards, activeView, visibleGroups, groupByProperty, views, hiddenCardsCount} = props const isManualSort = activeView.fields.sortOptions?.length === 0 const canEditBoardProperties = useHasCurrentBoardPermissions([Permission.ManageBoardProperties]) const canEditCards = useHasCurrentBoardPermissions([Permission.ManageBoardCards]) const dispatch = useAppDispatch() const resizeColumn = useCallback(async (columnId: string, width: number) => { const columnWidths = {...activeView.fields.columnWidths} const newWidth = Math.max(Constants.minColumnWidth, width) if (newWidth !== columnWidths[columnId]) { Utils.log(`Resize of column finished: prev=${columnWidths[columnId]}, new=${newWidth}`) columnWidths[columnId] = newWidth const newView = createBoardView(activeView) newView.fields.columnWidths = columnWidths try { dispatch(updateView(newView)) await mutator.updateBlock(board.id, newView, activeView, 'resize column') } catch { dispatch(updateView(activeView)) } } }, [activeView]) const hideGroup = useCallback((groupById: string): void => { const index: number = activeView.fields.collapsedOptionIds.indexOf(groupById) const newValue: string[] = [...activeView.fields.collapsedOptionIds] if (index > -1) { newValue.splice(index, 1) } else if (groupById !== '') { newValue.push(groupById) } const newView = createBoardView(activeView) newView.fields.collapsedOptionIds = newValue mutator.performAsUndoGroup(async () => { await mutator.updateBlock(board.id, newView, activeView, 'hide group') }) }, [activeView]) const onDropToGroupHeader = useCallback(async (option: IPropertyOption, dstOption?: IPropertyOption) => { if (dstOption) { Utils.log(`ondrop. Header target: ${dstOption.value}, source: ${option?.value}`) // Move option to new index const visibleOptionIds = visibleGroups.map((o) => o.option.id) const srcIndex = visibleOptionIds.indexOf(dstOption.id) const destIndex = visibleOptionIds.indexOf(option.id) visibleOptionIds.splice(srcIndex, 0, visibleOptionIds.splice(destIndex, 1)[0]) Utils.log(`ondrop. updated visibleoptionids: ${visibleOptionIds}`) await mutator.changeViewVisibleOptionIds(board.id, activeView.id, activeView.fields.visibleOptionIds, visibleOptionIds) } }, [activeView, visibleGroups]) const onDropToCard = useCallback((srcCard: Card, dstCard: Card) => { Utils.log(`onDropToCard: ${dstCard.title}`) onDropToGroup(srcCard, dstCard.fields.properties[activeView.fields.groupById!] as string, dstCard.id) }, [activeView.fields.groupById, cards]) const onDropToGroup = useCallback((srcCard: Card, groupID: string, dstCardID: string) => { Utils.log(`onDropToGroup: ${srcCard.title}`) const {selectedCardIds} = props const draggedCardIds = Array.from(new Set(selectedCardIds).add(srcCard.id)) const description = draggedCardIds.length > 1 ? `drag ${draggedCardIds.length} cards` : 'drag card' if (activeView.fields.groupById !== undefined) { const cardsById: { [key: string]: Card } = cards.reduce((acc: { [key: string]: Card }, card: Card): { [key: string]: Card } => { acc[card.id] = card return acc }, {}) const draggedCards: Card[] = draggedCardIds.map((o: string) => cardsById[o]) mutator.performAsUndoGroup(async () => { // Update properties of dragged cards const awaits = [] for (const draggedCard of draggedCards) { Utils.log(`draggedCard: ${draggedCard.title}, column: ${draggedCard.fields.properties}`) Utils.log(`droppedColumn: ${groupID}`) const oldOptionId = draggedCard.fields.properties[groupByProperty!.id] Utils.log(`ondrop. oldValue: ${oldOptionId}`) if (groupID !== oldOptionId) { awaits.push(mutator.changePropertyValue(board.id, draggedCard, groupByProperty!.id, groupID, description)) } } await Promise.all(awaits) }) } // Update dstCard order if (isManualSort) { let cardOrder = Array.from(new Set([...activeView.fields.cardOrder, ...cards.map((o) => o.id)])) if (dstCardID) { const isDraggingDown = cardOrder.indexOf(srcCard.id) <= cardOrder.indexOf(dstCardID) cardOrder = cardOrder.filter((id) => !draggedCardIds.includes(id)) let destIndex = cardOrder.indexOf(dstCardID) if (isDraggingDown) { destIndex += 1 } cardOrder.splice(destIndex, 0, ...draggedCardIds) } else { // Find index of first group item const firstCard = cards.find((card) => card.fields.properties[activeView.fields.groupById!] === groupID) if (firstCard) { const destIndex = cardOrder.indexOf(firstCard.id) cardOrder.splice(destIndex, 0, ...draggedCardIds) } else { // if not found, this is the only item in group. return } } mutator.performAsUndoGroup(async () => { await mutator.changeViewCardOrder(board.id, activeView.id, activeView.fields.cardOrder, cardOrder, description) }) } }, [activeView, cards, props.selectedCardIds, groupByProperty]) const propertyNameChanged = useCallback(async (option: IPropertyOption, text: string): Promise => { await mutator.changePropertyOptionValue(board.id, board.cardProperties, groupByProperty!, option, text) }, [board, groupByProperty]) return (
{/* Table rows */}
{activeView.fields.groupById && visibleGroups.map((group) => { return ( ) }) } {/* No Grouping, Rows, one per card */} {!activeView.fields.groupById && }
{/* Add New row */}
{!props.readonly && !activeView.fields.groupById &&
{ props.addCard('') }} >
}
{hiddenCardsCount > 0 && }
) } export default Table