import { MouseEventHandler, ReactNode, useCallback, useEffect, useMemo, useState } from 'react' import { useApplication } from '../ApplicationProvider' import { Table, TableColumn, TableRow, TableSortBy } from './CommonTypes' type TableSortOptions = | { sortBy: TableSortBy sortReversed: boolean onSortChange: (sortBy: TableSortBy, reversed: boolean) => void } | { sortBy?: never sortReversed?: never onSortChange?: never } type TableSelectionOptions = | { enableRowSelection: boolean enableMultipleRowSelection?: boolean selectedRowIds?: string[] onRowSelectionChange?: (rowIds: string[]) => void selectionActions?: (selected: string[]) => ReactNode showSelectionActions?: boolean } | { enableRowSelection?: never enableMultipleRowSelection?: never selectedRowIds?: never onRowSelectionChange?: never selectionActions?: never showSelectionActions?: never } type TableRowOptions = { getRowId?: (data: Data) => string onRowDoubleClick?: (data: Data) => void onRowContextMenu?: (x: number, y: number, data: Data) => void rowActions?: (data: Data) => ReactNode } export type UseTableOptions = { data: Data[] columns: TableColumn[] } & TableRowOptions & TableSortOptions & TableSelectionOptions export function useTable({ data, columns, sortBy, sortReversed, onSortChange, getRowId, enableRowSelection, enableMultipleRowSelection, selectedRowIds, onRowSelectionChange, onRowDoubleClick, onRowContextMenu, rowActions, selectionActions, showSelectionActions, }: UseTableOptions): Table { const application = useApplication() const [selectedRows, setSelectedRows] = useState(selectedRowIds || []) useEffect(() => { if (selectedRowIds) { setSelectedRows(selectedRowIds) } }, [selectedRowIds]) useEffect(() => { if (onRowSelectionChange) { onRowSelectionChange(selectedRows) } }, [selectedRows, onRowSelectionChange]) const headers = useMemo( () => columns.map((column) => { return { name: column.name, isSorting: sortBy && sortBy === column.sortBy, sortBy: column.sortBy, sortReversed: sortReversed, onSortChange: () => { if (!onSortChange || !column.sortBy) { return } onSortChange(column.sortBy, sortBy === column.sortBy ? !sortReversed : false) }, } }), [columns, onSortChange, sortBy, sortReversed], ) const rows: TableRow[] = useMemo( () => data.map((rowData, index) => { const cells = columns.map((column) => { return column.cell(rowData) }) const id = getRowId ? getRowId(rowData) : index.toString() const row: TableRow = { id, isSelected: enableRowSelection ? selectedRows.includes(id) : false, cells, rowData, rowActions: rowActions ? rowActions(rowData) : undefined, } return row }), [columns, data, enableRowSelection, getRowId, rowActions, selectedRows], ) const handleRowClick = useCallback( (id: string) => { const handler: MouseEventHandler = (event) => { if (!enableRowSelection) { return } const isCmdOrCtrlPressed = application.keyboardService.isMac ? event.metaKey : event.ctrlKey if (isCmdOrCtrlPressed && enableMultipleRowSelection) { setSelectedRows((prev) => (prev.includes(id) ? prev.filter((rowId) => rowId !== id) : [...prev, id])) } else if (event.shiftKey && enableMultipleRowSelection) { const lastSelectedIndex = rows.findIndex((row) => row.id === selectedRows[selectedRows.length - 1]) const currentIndex = rows.findIndex((row) => row.id === id) const start = Math.min(lastSelectedIndex, currentIndex) const end = Math.max(lastSelectedIndex, currentIndex) const newSelectedRows = rows.slice(start, end + 1).map((row) => row.id) setSelectedRows(newSelectedRows) } else { setSelectedRows([id]) } } return handler }, [application.keyboardService.isMac, enableMultipleRowSelection, enableRowSelection, rows, selectedRows], ) const handleRowDoubleClick = useCallback( (id: string) => { const handler: MouseEventHandler = () => { if (!onRowDoubleClick) { return } const rowData = rows.find((row) => row.id === id)?.rowData if (rowData) { onRowDoubleClick(rowData) } } return handler }, [onRowDoubleClick, rows], ) const handleRowContextMenu = useCallback( (id: string) => { const handler: MouseEventHandler = (event) => { if (!onRowContextMenu) { return } event.preventDefault() const rowData = rows.find((row) => row.id === id)?.rowData if (rowData) { setSelectedRows([id]) onRowContextMenu(event.clientX, event.clientY, rowData) } } return handler }, [onRowContextMenu, rows], ) const table: Table = useMemo( () => ({ headers, rows, handleRowClick, handleRowDoubleClick, handleRowContextMenu, selectedRows, canSelectRows: enableRowSelection || false, selectionActions: selectionActions ? selectionActions(selectedRows) : undefined, showSelectionActions: showSelectionActions || false, }), [ enableRowSelection, handleRowClick, handleRowContextMenu, handleRowDoubleClick, headers, rows, selectedRows, selectionActions, showSelectionActions, ], ) return table }