Merge remote-tracking branch 'origin/main' into feature/occurrence-country-or-continent-filter

# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.

Co-authored-by: karilint <19990676+karilint@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-04-17 07:07:18 +00:00
committed by GitHub
co-authored by karilint
4 changed files with 127 additions and 0 deletions
@@ -0,0 +1,20 @@
# Table column visibility (and exports)
Most tables in the application let you **show/hide columns**. This helps when working on smaller screens and when
exporting data.
## Where to find it
- In a table header, use the **column visibility** button (next to the export/download button).
- Some columns may be always visible (they are not hideable).
## What it does
- Hiding a column only affects the current table view.
- Exported data follows the **current column visibility**: hide columns you do not want included before exporting.
## Tips
- If a table is wide, hide non-essential columns to reduce horizontal scrolling.
- Use the table help (`?`) for reminders about sorting, filtering, and export behavior.
@@ -110,6 +110,7 @@ type DetailTabTableReadSelectProps<T extends MRT_RowData> = {
clickableRows?: boolean
enableColumnFilterModes?: boolean
paginationPlacement?: 'top' | 'bottom' | 'both'
tableContainerMaxHeight?: string | number
}
type DetailTabTableEditProps<T extends MRT_RowData> = {
@@ -126,6 +127,7 @@ type DetailTabTableEditProps<T extends MRT_RowData> = {
renderRowActions?: MRT_TableOptions<T>['renderRowActions']
muiTableBodyRowProps?: MRT_TableOptions<T>['muiTableBodyRowProps']
tableName?: string
tableContainerMaxHeight?: string | number
}
type DetailTabTableProps<T extends MRT_RowData> = DetailTabTableReadSelectProps<T> | DetailTabTableEditProps<T>
@@ -150,6 +152,7 @@ export const DetailTabTable = <T extends MRT_RowData>(props: DetailTabTableProps
enableColumnFilterModes,
paginationPlacement,
isError,
tableContainerMaxHeight = '60vh',
} = props
const resolvedVisibleColumns: MRT_VisibilityState = visibleColumns
@@ -177,6 +180,7 @@ export const DetailTabTable = <T extends MRT_RowData>(props: DetailTabTableProps
enableColumnFilterModes={enableColumnFilterModes}
paginationPlacement={paginationPlacement}
isError={isError}
tableContainerMaxHeight={tableContainerMaxHeight}
/>
)
}
@@ -194,6 +198,7 @@ const DetailTabEditableTable = <T extends MRT_RowData>({
renderRowActions,
muiTableBodyRowProps,
tableName = 'table',
tableContainerMaxHeight = '60vh',
}: DetailTabTableEditProps<T>) => {
const [pagination, setPagination] = useState<MRT_PaginationState>(paginationState ?? defaultEditPagination)
@@ -214,6 +219,13 @@ const DetailTabEditableTable = <T extends MRT_RowData>({
enableRowActions,
renderRowActions,
muiTableBodyRowProps,
muiTableContainerProps: {
sx: {
maxHeight: tableContainerMaxHeight,
overflowY: 'auto',
},
},
enableStickyHeader: true,
})
return (
@@ -73,6 +73,7 @@ export const TableView = <T extends MRT_RowData>({
isError,
error,
paginationPlacement,
tableContainerMaxHeight,
}: {
data: T[] | undefined
columns: MRT_ColumnDef<T>[]
@@ -96,6 +97,7 @@ export const TableView = <T extends MRT_RowData>({
filterFns?: Record<string, MRT_FilterFn<T>>
renderRowActionExtras?: ({ row }: { row: MRT_Row<T> }) => ReactNode
paginationPlacement?: 'top' | 'bottom' | 'both'
tableContainerMaxHeight?: string | number
}) => {
const location = useLocation()
const {
@@ -252,6 +254,15 @@ export const TableView = <T extends MRT_RowData>({
columns: columns,
data: data || [],
muiTableBodyRowProps: clickableRows || selectorFn ? muiTableBodyRowProps : undefined,
muiTableContainerProps: tableContainerMaxHeight
? {
sx: {
maxHeight: tableContainerMaxHeight,
overflowY: 'auto',
},
}
: undefined,
enableStickyHeader: Boolean(tableContainerMaxHeight),
state: {
columnFilters,
showColumnFilters: true,
@@ -0,0 +1,84 @@
import { describe, expect, it, jest } from '@jest/globals'
import '@testing-library/jest-dom'
import { fireEvent, render, screen } from '@testing-library/react'
import { MemoryRouter } from 'react-router-dom'
import { PageContextProvider } from '@/components/Page'
import { TableToolBar } from '@/components/TableView/TableToolBar'
jest.mock('@/components/CrossSearch/CrossSearchExportMenuItem', () => ({
CrossSearchExportMenuItem: ({ handleClose }: { handleClose: () => void }) => (
<button type="button" onClick={handleClose}>
Cross search export
</button>
),
}))
jest.mock('../../components/CrossSearch/CrossSearchExportMenuItem', () => ({
CrossSearchExportMenuItem: ({ handleClose }: { handleClose: () => void }) => (
<button type="button" onClick={handleClose}>
Cross search export
</button>
),
}))
const exportRows = jest.fn()
jest.mock('@/components/TableView/helpers', () => ({
exportRows: (...args: unknown[]) => exportRows(...args),
}))
jest.mock('../../components/TableView/helpers', () => ({
exportRows: (...args: unknown[]) => exportRows(...args),
}))
const renderToolbar = (props?: Partial<React.ComponentProps<typeof TableToolBar>>) => {
const table = {} as never
const tableName = 'Test table'
return render(
<MemoryRouter>
<PageContextProvider
editRights={{}}
idFieldName="id"
viewName="test"
createTitle={() => ''}
createSubtitle={() => ''}
>
<TableToolBar table={table} tableName={tableName} hideLeftButtons={true} {...props} />
</PageContextProvider>
</MemoryRouter>
)
}
describe('TableToolBar', () => {
it('renders a column visibility button', () => {
renderToolbar()
expect(screen.getByRole('button', { name: /show\/hide columns/i })).toBeTruthy()
})
it('exports table rows from the export menu', () => {
renderToolbar()
const exportButton = document.querySelector('#export-button')
expect(exportButton).toBeTruthy()
fireEvent.click(exportButton as HTMLElement)
fireEvent.click(screen.getByRole('menuitem', { name: /export table/i }))
expect(exportRows).toHaveBeenCalledTimes(1)
})
it('renders the cross-search export menu item when enabled', () => {
renderToolbar({ isCrossSearchTable: true })
const exportButton = document.querySelector('#export-button')
expect(exportButton).toBeTruthy()
fireEvent.click(exportButton as HTMLElement)
expect(screen.getByRole('button', { name: /cross search export/i })).toBeTruthy()
expect(screen.queryByRole('menuitem', { name: /export table/i })).toBeNull()
})
})