kanban / webapp /src /components /table /table.tsx
Leon4gr45's picture
Upload folder using huggingface_hub
13555f3 verified
// 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<void>
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<void> => {
await mutator.changePropertyOptionValue(board.id, board.cardProperties, groupByProperty!, option, text)
}, [board, groupByProperty])
return (
<div className='Table'>
<ColumnResizeProvider
columnWidths={activeView.fields.columnWidths}
onResizeColumn={resizeColumn}
>
<div className='octo-table-body'>
<TableHeaders
board={board}
cards={cards}
activeView={activeView}
views={views}
readonly={props.readonly || !canEditBoardProperties}
/>
{/* Table rows */}
<div className='table-row-container'>
{activeView.fields.groupById &&
visibleGroups.map((group) => {
return (
<TableGroup
key={group.option.id}
board={board}
activeView={activeView}
groupByProperty={groupByProperty}
group={group}
readonly={props.readonly || !canEditCards}
selectedCardIds={props.selectedCardIds}
cardIdToFocusOnRender={props.cardIdToFocusOnRender}
hideGroup={hideGroup}
addCard={props.addCard}
showCard={props.showCard}
propertyNameChanged={propertyNameChanged}
onCardClicked={props.onCardClicked}
onDropToGroupHeader={onDropToGroupHeader}
onDropToCard={onDropToCard}
onDropToGroup={onDropToGroup}
/>)
})
}
{/* No Grouping, Rows, one per card */}
{!activeView.fields.groupById &&
<TableRows
board={board}
activeView={activeView}
cards={cards}
selectedCardIds={props.selectedCardIds}
readonly={props.readonly || !canEditCards}
cardIdToFocusOnRender={props.cardIdToFocusOnRender}
showCard={props.showCard}
addCard={props.addCard}
onCardClicked={props.onCardClicked}
onDrop={onDropToCard}
/>
}
</div>
{/* Add New row */}
<div className='octo-table-footer'>
{!props.readonly && !activeView.fields.groupById &&
<BoardPermissionGate permissions={[Permission.ManageBoardCards]}>
<div
className='octo-table-cell'
onClick={() => {
props.addCard('')
}}
>
<FormattedMessage
id='TableComponent.plus-new'
defaultMessage='+ New'
/>
</div>
</BoardPermissionGate>
}
</div>
<CalculationRow
board={board}
cards={cards}
activeView={activeView}
readonly={props.readonly || !canEditBoardProperties}
/>
</div>
</ColumnResizeProvider>
{hiddenCardsCount > 0 &&
<HiddenCardCount
showHiddenCardNotification={props.showHiddenCardCountNotification}
hiddenCardsCount={hiddenCardsCount}
/>}
</div>
)
}
export default Table