diff --git a/packages/admin-panel/src/common.jsx b/packages/admin-panel/src/common.jsx index 702d01b6d9..21547a4046 100644 --- a/packages/admin-panel/src/common.jsx +++ b/packages/admin-panel/src/common.jsx @@ -149,7 +149,6 @@ export const DATA_ELEMENT_FIELDS = [ { Header: 'Permission Groups', source: 'permission_groups', - type: 'tooltip', editConfig: { optionsEndpoint: 'permissionGroups', optionLabelKey: 'name', diff --git a/packages/admin-panel/src/routes/entities/entities.js b/packages/admin-panel/src/routes/entities/entities.js index 30e29b3005..1b4c0252e7 100644 --- a/packages/admin-panel/src/routes/entities/entities.js +++ b/packages/admin-panel/src/routes/entities/entities.js @@ -19,7 +19,6 @@ export const FIELDS = { name: { Header: 'Name', source: 'name', - type: 'tooltip', }, type: { Header: 'Type', diff --git a/packages/admin-panel/src/routes/externalData/externalDatabaseConnections.js b/packages/admin-panel/src/routes/externalData/externalDatabaseConnections.js index 0a6385cef5..6cc4dd5918 100644 --- a/packages/admin-panel/src/routes/externalData/externalDatabaseConnections.js +++ b/packages/admin-panel/src/routes/externalData/externalDatabaseConnections.js @@ -14,7 +14,6 @@ const FIELDS = [ { Header: 'Code', source: 'code', - type: 'tooltip', }, { Header: 'Name', @@ -54,7 +53,8 @@ const COLUMNS = [ { Header: 'Test', type: 'testDatabaseConnection', - colWidth: '6.5rem', + width: 90, + disableResizing: true, }, { Header: 'Delete', diff --git a/packages/admin-panel/src/routes/projects/projects.js b/packages/admin-panel/src/routes/projects/projects.js index d0ef480dc3..feb653beee 100644 --- a/packages/admin-panel/src/routes/projects/projects.js +++ b/packages/admin-panel/src/routes/projects/projects.js @@ -14,12 +14,10 @@ const DEFAULT_FIELDS = [ { Header: 'Description', source: 'description', - type: 'tooltip', }, { Header: 'Dashboard', source: 'dashboard_group_name', - type: 'tooltip', editConfig: { optionsEndpoint: 'dashboards', optionLabelKey: 'name', diff --git a/packages/admin-panel/src/routes/surveys/questions.js b/packages/admin-panel/src/routes/surveys/questions.js index 1984282f9e..a95195dfae 100644 --- a/packages/admin-panel/src/routes/surveys/questions.js +++ b/packages/admin-panel/src/routes/surveys/questions.js @@ -18,17 +18,14 @@ const QUESTION_FIELDS = [ { Header: 'Name', source: 'name', - type: 'tooltip', }, { Header: 'Question', source: 'text', - type: 'tooltip', }, { Header: 'Legacy options', source: 'options', - type: 'tooltip', editConfig: { type: 'jsonArray', }, @@ -36,12 +33,10 @@ const QUESTION_FIELDS = [ { Header: 'Detail', source: 'detail', - type: 'tooltip', }, { Header: 'Hook', source: 'hook', - type: 'tooltip', }, { Header: 'Option set ID', diff --git a/packages/admin-panel/src/routes/surveys/surveyResponses.js b/packages/admin-panel/src/routes/surveys/surveyResponses.js index c3e130663f..6c84fae093 100644 --- a/packages/admin-panel/src/routes/surveys/surveyResponses.js +++ b/packages/admin-panel/src/routes/surveys/surveyResponses.js @@ -22,14 +22,12 @@ const surveyName = { Header: 'Survey', source: 'survey.name', editable: false, - type: 'tooltip', }; const surveyId = { Header: 'Survey ID', source: 'survey.id', editable: false, - type: 'tooltip', show: false, }; @@ -42,7 +40,6 @@ const assessorName = { const date = { Header: 'Date of survey', source: 'end_time', - type: 'tooltip', accessor: row => moment(row.end_time).local().format('ddd, MMM Do YYYY, HH:mm:ss ZZ'), filterable: false, editable: false, @@ -51,7 +48,6 @@ const date = { const dateOfData = { Header: 'Date of data', source: 'data_time', - type: 'tooltip', accessor: row => moment.parseZone(row.data_time).format('ddd, MMM Do YYYY, HH:mm:ss'), filterable: false, editConfig: { @@ -63,7 +59,6 @@ const dateOfData = { const approvalStatus = { Header: 'Approval status', source: 'approval_status', - type: 'tooltip', }; const entityName = { @@ -121,12 +116,10 @@ export const ANSWER_COLUMNS = [ Header: 'Question', source: 'question.text', editable: false, - type: 'tooltip', }, { Header: 'Answer', source: 'text', - type: 'tooltip', }, ]; diff --git a/packages/admin-panel/src/routes/surveys/surveys.js b/packages/admin-panel/src/routes/surveys/surveys.js index 52d4887a54..6f7886fb4b 100644 --- a/packages/admin-panel/src/routes/surveys/surveys.js +++ b/packages/admin-panel/src/routes/surveys/surveys.js @@ -37,7 +37,6 @@ const SURVEY_FIELDS = { name: { Header: 'Name', source: 'name', - type: 'tooltip', editConfig: { maxLength: 50, secondaryLabel: 'Max 50 characters', @@ -244,7 +243,6 @@ const QUESTION_FIELDS = [ { Header: 'Code', source: 'question.code', - type: 'tooltip', editable: false, }, { @@ -254,27 +252,22 @@ const QUESTION_FIELDS = [ { Header: 'Name', source: 'question.name', - type: 'tooltip', }, { Header: 'Question', source: 'question.text', - type: 'tooltip', }, { Header: 'Detail', source: 'question.detail', - type: 'tooltip', }, { Header: 'Question label', source: 'question_label', - type: 'tooltip', }, { Header: 'Detail label', source: 'detail_label', - type: 'tooltip', }, ]; diff --git a/packages/admin-panel/src/routes/surveys/syncGroups.js b/packages/admin-panel/src/routes/surveys/syncGroups.js index aba11ba834..a2f8b3bace 100644 --- a/packages/admin-panel/src/routes/surveys/syncGroups.js +++ b/packages/admin-panel/src/routes/surveys/syncGroups.js @@ -68,7 +68,7 @@ const COLUMNS = [ source: 'sync_status', filterable: false, disableSortBy: true, - colWidth: '12rem', + width: 200, actionConfig: { syncStatusEndpoint: 'dataServiceSyncGroups/{id}', latestSyncLogEndpoint: 'dataServiceSyncGroups/{id}/logs?limit=1', diff --git a/packages/admin-panel/src/routes/users/accessRequests.js b/packages/admin-panel/src/routes/users/accessRequests.js index 3c0b5d5bee..43d0258810 100644 --- a/packages/admin-panel/src/routes/users/accessRequests.js +++ b/packages/admin-panel/src/routes/users/accessRequests.js @@ -11,7 +11,6 @@ const USER_FIELDS = [ { Header: 'Email address', source: 'user_account.email', - type: 'tooltip', }, { Header: 'First name', @@ -50,7 +49,6 @@ const ACCESS_REQUEST_FIELDS = [ { Header: 'Message', source: 'message', - type: 'tooltip', editable: false, }, { @@ -98,7 +96,6 @@ const USER_COLUMNS = [ Header: 'Message', source: 'message', bulkAccessor: rows => rows.map(row => (row.message ? row.message : 'blank')).join(', '), - type: 'tooltip', editable: false, }, { diff --git a/packages/admin-panel/src/routes/users/users.jsx b/packages/admin-panel/src/routes/users/users.jsx index b895898698..20e5da65d7 100644 --- a/packages/admin-panel/src/routes/users/users.jsx +++ b/packages/admin-panel/src/routes/users/users.jsx @@ -38,7 +38,6 @@ const EDIT_FIELDS = [ { Header: 'Email address', source: 'email', - type: 'tooltip', }, { Header: 'Phone number', @@ -51,12 +50,10 @@ const EDIT_FIELDS = [ { Header: 'Employer', source: 'employer', - type: 'tooltip', }, { Header: 'Verified', source: 'verified_email', - type: 'tooltip', editConfig: { options: [ { @@ -94,12 +91,10 @@ const COLUMNS = [ { Header: 'Email Address', source: 'email', - type: 'tooltip', }, { Header: 'Employer', source: 'employer', - type: 'tooltip', }, { Header: 'Verified', diff --git a/packages/admin-panel/src/routes/visualisations/dashboardItems.jsx b/packages/admin-panel/src/routes/visualisations/dashboardItems.jsx index 7f392a6834..6636988650 100644 --- a/packages/admin-panel/src/routes/visualisations/dashboardItems.jsx +++ b/packages/admin-panel/src/routes/visualisations/dashboardItems.jsx @@ -15,12 +15,10 @@ const FIELDS = [ { Header: 'Code', source: 'code', - type: 'tooltip', }, { Header: 'Report code', source: 'report_code', - type: 'tooltip', }, { Header: 'Config', diff --git a/packages/admin-panel/src/routes/visualisations/dashboardMailingLists.js b/packages/admin-panel/src/routes/visualisations/dashboardMailingLists.js index cc692dbe5a..02904df3a0 100644 --- a/packages/admin-panel/src/routes/visualisations/dashboardMailingLists.js +++ b/packages/admin-panel/src/routes/visualisations/dashboardMailingLists.js @@ -110,7 +110,6 @@ const ENTRY_FIELDS = [ { Header: 'Email', source: 'dashboard_mailing_list_entry.email', - type: 'tooltip', }, { Header: 'Subscribed', diff --git a/packages/admin-panel/src/routes/visualisations/dashboards.js b/packages/admin-panel/src/routes/visualisations/dashboards.js index 859625603c..8f26d3a049 100644 --- a/packages/admin-panel/src/routes/visualisations/dashboards.js +++ b/packages/admin-panel/src/routes/visualisations/dashboards.js @@ -13,7 +13,6 @@ const FIELDS = [ { Header: 'Code', source: 'code', - type: 'tooltip', }, { Header: 'Name', diff --git a/packages/admin-panel/src/routes/visualisations/dataTables.js b/packages/admin-panel/src/routes/visualisations/dataTables.js index 033b6435aa..3824bfab57 100644 --- a/packages/admin-panel/src/routes/visualisations/dataTables.js +++ b/packages/admin-panel/src/routes/visualisations/dataTables.js @@ -16,7 +16,6 @@ const FIELDS = [ { Header: 'Code', source: 'code', - type: 'tooltip', }, { Header: 'Description', @@ -35,7 +34,6 @@ const FIELDS = [ { Header: 'Permission groups', source: 'permission_groups', - type: 'tooltip', Filter: ArrayFilter, Cell: ({ value }) => prettyArray(value), editConfig: { diff --git a/packages/admin-panel/src/routes/visualisations/legacyReports.js b/packages/admin-panel/src/routes/visualisations/legacyReports.js index 7b8aec48b8..02b634a8ac 100644 --- a/packages/admin-panel/src/routes/visualisations/legacyReports.js +++ b/packages/admin-panel/src/routes/visualisations/legacyReports.js @@ -9,12 +9,10 @@ const FIELDS = [ { Header: 'Code', source: 'code', - type: 'tooltip', }, { Header: 'Data builder', source: 'data_builder', - type: 'tooltip', }, { Header: 'Data builder config', diff --git a/packages/admin-panel/src/routes/visualisations/mapOverlayGroupRelations.js b/packages/admin-panel/src/routes/visualisations/mapOverlayGroupRelations.js index b97d30b6b2..ba07e79bcf 100644 --- a/packages/admin-panel/src/routes/visualisations/mapOverlayGroupRelations.js +++ b/packages/admin-panel/src/routes/visualisations/mapOverlayGroupRelations.js @@ -11,7 +11,7 @@ const FIELDS = [ { Header: 'Map overlay group code', source: 'map_overlay_group.code', - type: 'tooltip', + editConfig: { optionsEndpoint: 'mapOverlayGroups', optionLabelKey: 'map_overlay_group.code', @@ -22,7 +22,7 @@ const FIELDS = [ { Header: 'Child ID', source: 'child_id', - type: 'tooltip', + editConfig: { optionsEndpoint: 'mapOverlays', optionLabelKey: 'mapOverlay.id', @@ -35,7 +35,7 @@ const FIELDS = [ Header: 'Child type', width: 160, source: 'child_type', - type: 'tooltip', + editConfig: { options: [ { @@ -52,7 +52,6 @@ const FIELDS = [ { Header: 'Sort order', source: 'sort_order', - type: 'tooltip', }, ]; diff --git a/packages/admin-panel/src/routes/visualisations/mapOverlayGroups.js b/packages/admin-panel/src/routes/visualisations/mapOverlayGroups.js index df6d724147..ec5291c371 100644 --- a/packages/admin-panel/src/routes/visualisations/mapOverlayGroups.js +++ b/packages/admin-panel/src/routes/visualisations/mapOverlayGroups.js @@ -16,12 +16,10 @@ const FIELDS = [ { Header: 'Code', source: 'code', - type: 'tooltip', }, { Header: 'Name', source: 'name', - type: 'tooltip', }, ]; @@ -42,7 +40,7 @@ export const RELATION_FIELDS = [ { Header: 'Child ID', source: 'child_id', - type: 'tooltip', + editConfig: { optionsEndpoint: 'mapOverlays', optionLabelKey: 'mapOverlay.id', @@ -53,7 +51,7 @@ export const RELATION_FIELDS = [ { Header: 'Child type', source: 'child_type', - type: 'tooltip', + editConfig: { options: [ { @@ -70,7 +68,6 @@ export const RELATION_FIELDS = [ { Header: 'Sort order', source: 'sort_order', - type: 'tooltip', }, ]; diff --git a/packages/admin-panel/src/routes/visualisations/mapOverlays.jsx b/packages/admin-panel/src/routes/visualisations/mapOverlays.jsx index 9196ffe71e..3bd59b3648 100644 --- a/packages/admin-panel/src/routes/visualisations/mapOverlays.jsx +++ b/packages/admin-panel/src/routes/visualisations/mapOverlays.jsx @@ -18,13 +18,11 @@ const FIELDS = [ { Header: 'Code', source: 'code', - type: 'tooltip', }, { Header: 'Name', source: 'name', width: 140, - type: 'tooltip', }, { Header: 'Permission group', diff --git a/packages/admin-panel/src/routes/visualisations/socialFeed.jsx b/packages/admin-panel/src/routes/visualisations/socialFeed.jsx index 2143d5313e..144ed1e11f 100644 --- a/packages/admin-panel/src/routes/visualisations/socialFeed.jsx +++ b/packages/admin-panel/src/routes/visualisations/socialFeed.jsx @@ -36,7 +36,7 @@ const FIELDS = [ { Header: 'Creation date', source: 'creation_date', - type: 'tooltip', + accessor: row => moment(row.creation_date).local().toString(), editConfig: { type: 'datetime-local', diff --git a/packages/admin-panel/src/table/DataFetchingTable/Cells.jsx b/packages/admin-panel/src/table/DataFetchingTable/Cells.jsx index a1b972e5cc..4a533fbd06 100644 --- a/packages/admin-panel/src/table/DataFetchingTable/Cells.jsx +++ b/packages/admin-panel/src/table/DataFetchingTable/Cells.jsx @@ -9,16 +9,11 @@ import styled from 'styled-components'; import { TableCell as MuiTableCell } from '@material-ui/core'; import { Link } from 'react-router-dom'; -const BUTTON_COLUMN_WIDTH = '4.5rem'; - const Cell = styled(MuiTableCell)` - vertical-align: middle; font-size: 0.75rem; padding: 0; - max-width: ${({ $isButtonColumn }) => ($isButtonColumn ? BUTTON_COLUMN_WIDTH : '0')}; - width: ${({ $isButtonColumn }) => ($isButtonColumn ? BUTTON_COLUMN_WIDTH : 'auto')}; border: none; - height: 1px; // need this to make the cell content fill the height of the cell + position: relative; &:first-child { padding-inline-start: 1.5rem; } @@ -42,8 +37,6 @@ const CellContentWrapper = styled.div` } line-height: 1.5; - - ${({ $width }) => $width && `width: ${$width}`}; `; // Flex does not support ellipsis so we need to have another container to handle the ellipsis @@ -51,7 +44,7 @@ const CellContentContainer = styled.div` white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - flex: 1; + width: 100%; `; const HeaderCell = styled(Cell)` @@ -69,33 +62,53 @@ const HeaderCell = styled(Cell)` .MuiTableSortLabel-active .MuiTableSortLabel-icon { opacity: 1; } - // apply a min width to the button column to prevent it from shrinking the filter input too much - ${({ $isButtonColumn }) => !$isButtonColumn && `min-width: 9rem;`}; + ${CellContentContainer} { + width: ${({ $canResize }) => ($canResize ? 'calc(100% - 2rem)' : '100%')}; + } +`; + +const ColResize = styled.div.attrs({ + onClick: e => { + // suppress other events when resizing + e.preventDefault(); + e.stopPropagation(); + }, +})` + width: 2rem; + height: 100%; + position: absolute; + right: 0; + top: 0; + cursor: col-resize; + z-index: 1; `; -export const HeaderDisplayCell = ({ children, isButtonColumn, width, ...props }) => { +export const HeaderDisplayCell = ({ children, canResize, getResizerProps, ...props }) => { return ( - + {children} + {canResize && } ); }; HeaderDisplayCell.propTypes = { children: PropTypes.node, - isButtonColumn: PropTypes.bool, width: PropTypes.string, + canResize: PropTypes.bool, + getResizerProps: PropTypes.func, }; HeaderDisplayCell.defaultProps = { - isButtonColumn: false, width: null, children: null, + canResize: false, + getResizerProps: () => {}, }; -export const TableCell = ({ children, width, isButtonColumn, url }) => { +export const TableCell = ({ children, width, isButtonColumn, url, ...props }) => { return ( - + {children} diff --git a/packages/admin-panel/src/table/DataFetchingTable/DataFetchingTable.jsx b/packages/admin-panel/src/table/DataFetchingTable/DataFetchingTable.jsx index e2193ba01f..7b0d643523 100644 --- a/packages/admin-panel/src/table/DataFetchingTable/DataFetchingTable.jsx +++ b/packages/admin-panel/src/table/DataFetchingTable/DataFetchingTable.jsx @@ -2,10 +2,10 @@ * Tupaia * Copyright (c) 2017 - 2024 Beyond Essential Systems Pty Ltd */ -import React, { useEffect } from 'react'; +import React, { memo, useEffect, useMemo } from 'react'; import styled from 'styled-components'; import { connect } from 'react-redux'; -import { useTable, usePagination, useSortBy } from 'react-table'; +import { useTable, usePagination, useSortBy, useResizeColumns, useFlexLayout } from 'react-table'; import { TableHead, TableContainer as MuiTableContainer, @@ -57,6 +57,9 @@ const TableContainer = styled(MuiTableContainer)` z-index: 2; background-color: ${({ theme }) => theme.palette.background.paper}; } + tr { + display: flex; + .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline { border-color: ${({ theme }) => theme.palette.primary.main}; } @@ -83,242 +86,272 @@ const MessageWrapper = styled.div` background-color: rgba(255, 255, 255, 0.5); `; -const DataFetchingTableComponent = ({ - columns, - data = [], - numberOfPages, - pageSize, - pageIndex = 0, - onPageChange, - onPageSizeChange, - initialiseTable, - nestingLevel, - filters, - sorting, - isChangingDataOnServer, - errorMessage, - onRefreshData, - confirmActionMessage, - onConfirmAction, - onCancelAction, - deleteConfig, - onFilteredChange, - totalRecords, - isFetchingData, - onSortedChange, - detailUrl, - getHasNestedView, - endpoint, - getNestedViewLink, - baseFilter, - basePath, - resourceName, - defaultSorting, -}) => { - const { - getTableProps, - getTableBodyProps, - headerGroups, - prepareRow, - rows, - pageCount, - gotoPage, - setPageSize, - visibleColumns, - setSortBy, - // Get the state from the instance - state: { pageIndex: tablePageIndex, pageSize: tablePageSize, sortBy: tableSorting }, - } = useTable( - { - columns, - data, - initialState: { - pageIndex, - pageSize, - sortBy: sorting, - hiddenColumns: columns.filter(column => column.show === false).map(column => column.id), - }, - manualPagination: true, - pageCount: numberOfPages, - manualSortBy: true, - }, - useSortBy, - usePagination, - ); +const formatColumnForReactTable = (originalColumn, reduxId) => { + const { source, type, actionConfig, filterable, ...restOfColumn } = originalColumn; + const id = source || type; + return { + id, + accessor: id?.includes('.') ? row => row[source] : id, // react-table doesn't like .'s + actionConfig, + reduxId, + type, + disableSortBy: !source, // disable action columns from being sortable + filterable: filterable !== false, + ...generateConfigForColumnType(type, actionConfig, reduxId), // Add custom Cell/width/etc. + ...restOfColumn, + }; +}; - // Listen for changes in pagination and use the state to fetch our new data - useEffect(() => { - onPageChange(tablePageIndex); - }, [tablePageIndex]); +const DataFetchingTableComponent = memo( + ({ + columns, + data = [], + numberOfPages, + pageSize, + pageIndex = 0, + onPageChange, + onPageSizeChange, + initialiseTable, + nestingLevel, + filters, + sorting, + isChangingDataOnServer, + errorMessage, + onRefreshData, + confirmActionMessage, + onConfirmAction, + onCancelAction, + deleteConfig, + onFilteredChange, + totalRecords, + isFetchingData, + onSortedChange, + detailUrl, + getHasNestedView, + endpoint, + getNestedViewLink, + baseFilter, + basePath, + resourceName, + defaultSorting, + }) => { + const formattedColumns = useMemo( + () => columns.map(column => formatColumnForReactTable(column)), + [JSON.stringify(columns)], + ); - useEffect(() => { - onPageSizeChange(tablePageSize); - gotoPage(0); - }, [tablePageSize]); + const memoisedData = useMemo(() => data, [JSON.stringify(data)]); - useEffect(() => { - onSortedChange(tableSorting); - gotoPage(0); - }, [tableSorting]); + const { + getTableProps, + getTableBodyProps, + headerGroups, + prepareRow, + rows, + pageCount, + gotoPage, + setPageSize, + visibleColumns, + setSortBy, + // Get the state from the instance + state: { pageIndex: tablePageIndex, pageSize: tablePageSize, sortBy: tableSorting }, + } = useTable( + { + columns: formattedColumns, + data: memoisedData, + initialState: { + pageIndex, + pageSize, + sortBy: sorting, + hiddenColumns: columns.filter(column => column.show === false).map(column => column.id), + }, + manualPagination: true, + pageCount: numberOfPages, + manualSortBy: true, + }, + useSortBy, + usePagination, + useFlexLayout, + useResizeColumns, + ); + + // Listen for changes in pagination and use the state to fetch our new data + useEffect(() => { + onPageChange(tablePageIndex); + }, [tablePageIndex]); + + useEffect(() => { + onPageSizeChange(tablePageSize); + gotoPage(0); + }, [tablePageSize]); - useEffect(() => { - onRefreshData(); - }, [filters, pageIndex, pageSize, sorting]); + useEffect(() => { + onSortedChange(tableSorting); + gotoPage(0); + }, [tableSorting]); - useEffect(() => { - if (!isChangingDataOnServer && !errorMessage) { + useEffect(() => { onRefreshData(); - } - }, [errorMessage, isChangingDataOnServer]); + }, [filters, pageIndex, pageSize, sorting]); - // initial render/re-render when endpoint changes - useEffect(() => { - if (nestingLevel === 0) { - // Page-level filters only apply to top-level data tables - const params = queryString.parse(location.search); // set filters from query params - const parsedFilters = params.filters ? JSON.parse(params.filters) : undefined; - initialiseTable(parsedFilters); - } else { - initialiseTable(); - } - gotoPage(0); - setSortBy(defaultSorting ?? []); // reset sorting when table is re-initialised - }, [endpoint, JSON.stringify(baseFilter)]); + useEffect(() => { + if (!isChangingDataOnServer && !errorMessage) { + onRefreshData(); + } + }, [errorMessage, isChangingDataOnServer]); - const onChangeFilters = newFilters => { - onFilteredChange(newFilters); - gotoPage(0); - }; + // initial render/re-render when endpoint changes + useEffect(() => { + if (nestingLevel === 0) { + // Page-level filters only apply to top-level data tables + const params = queryString.parse(location.search); // set filters from query params + const parsedFilters = params.filters ? JSON.parse(params.filters) : undefined; + initialiseTable(parsedFilters); + } else { + initialiseTable(); + } + gotoPage(0); + setSortBy(defaultSorting ?? []); // reset sorting when table is re-initialised + }, [endpoint, JSON.stringify(baseFilter)]); - const isLoading = isFetchingData || isChangingDataOnServer; + const onChangeFilters = newFilters => { + onFilteredChange(newFilters); + gotoPage(0); + }; - const displayFilterRow = visibleColumns.some(column => column.filterable !== false); - const { singular = 'record' } = resourceName; - return ( - - {errorMessage && {errorMessage}} - {isLoading && ( - - Loading - - )} - {data.length === 0 && !isLoading && ( - - No data to display - - )} - - - - {headerGroups.map(({ getHeaderGroupProps, headers }, index) => ( - // eslint-disable-next-line react/no-array-index-key - - {headers.map( - ( - { - getHeaderProps, - render, - isSorted, - isSortedDesc, - getSortByToggleProps, - canSort, - isButtonColumn, - }, - i, - ) => { - return ( - - {render('Header')} - {canSort && ( - - )} - - ); - }, - )} - - ))} - - {displayFilterRow && - visibleColumns.map(column => { - return ( - - ); - })} - - - - {rows.map((row, index) => { - prepareRow(row); - return ( + const isLoading = isFetchingData || isChangingDataOnServer; + + const displayFilterRow = visibleColumns.some(column => column.filterable !== false); + + const { singular = 'record' } = resourceName; + + return ( + + {errorMessage && {errorMessage}} + {isLoading && ( + + Loading + + )} + {data.length === 0 && !isLoading && ( + + No data to display + + )} + +
+ + {headerGroups.map(({ getHeaderGroupProps, headers }, index) => ( // eslint-disable-next-line react/no-array-index-key - - {row.cells.map(({ getCellProps, render }, i) => { + + {headers.map( + ( + { + getHeaderProps, + render, + isSorted, + isSortedDesc, + getSortByToggleProps, + canSort, + getResizerProps, + canResize, + }, + i, + ) => { + return ( + + {render('Header')} + {canSort && ( + + )} + + ); + }, + )} + + ))} + + {displayFilterRow && + visibleColumns.map(column => { return ( - - {render('Cell')} - + ); })} - - ); - })} - -
-
- + + + + {rows.map((row, index) => { + prepareRow(row); + return ( + // eslint-disable-next-line react/no-array-index-key + + {row.cells.map(({ getCellProps, render }, i) => { + const col = visibleColumns[i]; + return ( + + {render('Cell')} + + ); + })} + + ); + })} + + + + - -
- ); -}; + + + ); + }, +); DataFetchingTableComponent.propTypes = { columns: PropTypes.arrayOf(PropTypes.shape({})).isRequired, @@ -377,9 +410,8 @@ DataFetchingTableComponent.defaultProps = { defaultSorting: [], }; -const mapStateToProps = (state, { columns, reduxId, ...ownProps }) => ({ +const mapStateToProps = (state, { reduxId, ...ownProps }) => ({ isFetchingData: getIsFetchingData(state, reduxId), - columns: columns.map(originalColumn => formatColumnForReactTable(originalColumn, reduxId)), isChangingDataOnServer: getIsChangingDataOnServer(state), ...ownProps, ...getTableState(state, reduxId), @@ -423,22 +455,6 @@ const mergeProps = (stateProps, { dispatch, ...dispatchProps }, ownProps) => { }; }; -const formatColumnForReactTable = (originalColumn, reduxId) => { - const { source, type, actionConfig, filterable, ...restOfColumn } = originalColumn; - const id = source || type; - return { - id, - accessor: id?.includes('.') ? row => row[source] : id, // react-table doesn't like .'s - actionConfig, - reduxId, - type, - disableSortBy: !source, // disable action columns from being sortable - filterable: filterable !== false, - ...generateConfigForColumnType(type, actionConfig, reduxId), // Add custom Cell/width/etc. - ...restOfColumn, - }; -}; - export const DataFetchingTable = connect( mapStateToProps, mapDispatchToProps, diff --git a/packages/admin-panel/src/table/DataFetchingTable/FilterCell.jsx b/packages/admin-panel/src/table/DataFetchingTable/FilterCell.jsx index 7c99d412cb..c0ad5ad4f4 100644 --- a/packages/admin-panel/src/table/DataFetchingTable/FilterCell.jsx +++ b/packages/admin-panel/src/table/DataFetchingTable/FilterCell.jsx @@ -40,7 +40,7 @@ const FilterWrapper = styled.div` } `; -export const FilterCell = ({ column, filters, onFilteredChange, width, isButtonColumn }) => { +export const FilterCell = ({ column, filters, onFilteredChange, ...props }) => { const { id, Filter } = column; const existingFilter = filters?.find(f => f.id === id); const handleUpdate = value => { @@ -50,10 +50,10 @@ export const FilterCell = ({ column, filters, onFilteredChange, width, isButtonC onFilteredChange(updatedFilters); }; - if (!column.filterable) - return ; + if (!column.filterable) return ; + return ( - + {Filter ? ( @@ -78,11 +78,4 @@ FilterCell.propTypes = { Filter: PropTypes.func, filterable: PropTypes.bool, }).isRequired, - width: PropTypes.number, - isButtonColumn: PropTypes.bool, -}; - -FilterCell.defaultProps = { - width: null, - isButtonColumn: false, }; diff --git a/packages/admin-panel/src/table/columnTypes/Tooltip.jsx b/packages/admin-panel/src/table/columnTypes/Tooltip.jsx index 926d9ea5e6..8b3eed13c8 100644 --- a/packages/admin-panel/src/table/columnTypes/Tooltip.jsx +++ b/packages/admin-panel/src/table/columnTypes/Tooltip.jsx @@ -3,7 +3,7 @@ * Copyright (c) 2018 Beyond Essential Systems Pty Ltd */ -import React from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import PropTypes from 'prop-types'; import { Tooltip as TooltipComponent } from '@tupaia/ui-components'; import styled from 'styled-components'; @@ -14,28 +14,61 @@ const Content = styled.div` text-overflow: ellipsis; `; -export const Tooltip = ({ value }) => ( - - {value} - -); +const useTooltip = column => { + const [displayTooltip, setDisplayTooltip] = useState(true); + const contentRef = useRef(null); + const { width } = column; + + useEffect(() => { + if (contentRef.current) { + const { scrollWidth } = contentRef.current; + + setDisplayTooltip(scrollWidth >= width - 18); // 18 allows for padding of the cell + } + }, [width, contentRef.current]); + + return { displayTooltip, contentRef }; +}; + +export const Tooltip = ({ value, column }) => { + const { displayTooltip, contentRef } = useTooltip(column); + + if (!displayTooltip) { + return {value}; + } + + return ( + + {value} + + ); +}; Tooltip.propTypes = { value: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.string]), + column: PropTypes.object.isRequired, }; Tooltip.defaultProps = { value: '', }; -export const JSONTooltip = ({ value }) => ( - {JSON.stringify(value, null, 1)}}> - {JSON.stringify(value)} - -); +export const JSONTooltip = ({ value, column }) => { + const { displayTooltip, contentRef } = useTooltip(column); + + if (!displayTooltip) { + return {JSON.stringify(value)}; + } + return ( + {JSON.stringify(value, null, 1)}}> + {JSON.stringify(value)} + + ); +}; JSONTooltip.propTypes = { value: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.string]), + column: PropTypes.object.isRequired, }; JSONTooltip.defaultProps = { diff --git a/packages/admin-panel/src/table/columnTypes/generateConfigForColumnType.jsx b/packages/admin-panel/src/table/columnTypes/generateConfigForColumnType.jsx index 62450aac4e..a597e7e4bd 100644 --- a/packages/admin-panel/src/table/columnTypes/generateConfigForColumnType.jsx +++ b/packages/admin-panel/src/table/columnTypes/generateConfigForColumnType.jsx @@ -23,6 +23,8 @@ const BUTTON_COLUMN_OPTIONS = { filterable: false, disableSortBy: true, isButtonColumn: true, + disableResizing: true, + width: 90, }; const CUSTOM_CELL_COMPONENTS = { @@ -51,7 +53,7 @@ const BUTTON_COLUMN_TYPES = [ 'bulkEdit', ]; -export const generateConfigForColumnType = (type, actionConfig, reduxId) => { +export const generateConfigForColumnType = (type = 'tooltip', actionConfig, reduxId) => { const CustomCellComponent = CUSTOM_CELL_COMPONENTS[type]; if (!CustomCellComponent) { return {}; diff --git a/packages/lesmis/src/views/AdminPanel/routes/entities/getEntitiesPageConfig.js b/packages/lesmis/src/views/AdminPanel/routes/entities/getEntitiesPageConfig.js index 49bba7c9de..946d09579c 100644 --- a/packages/lesmis/src/views/AdminPanel/routes/entities/getEntitiesPageConfig.js +++ b/packages/lesmis/src/views/AdminPanel/routes/entities/getEntitiesPageConfig.js @@ -20,7 +20,7 @@ export const getEntitiesPageConfig = translate => { { Header: translate('admin.name'), source: 'name', - type: 'tooltip', + Filter: getColumnFilter(translate), }, { diff --git a/packages/lesmis/src/views/AdminPanel/routes/surveyResponses/getSurveyResponsePageConfig.js b/packages/lesmis/src/views/AdminPanel/routes/surveyResponses/getSurveyResponsePageConfig.js index 59d736a5ed..4140717aaa 100644 --- a/packages/lesmis/src/views/AdminPanel/routes/surveyResponses/getSurveyResponsePageConfig.js +++ b/packages/lesmis/src/views/AdminPanel/routes/surveyResponses/getSurveyResponsePageConfig.js @@ -21,7 +21,7 @@ export const getSurveyResponsePageConfig = (translate, path, adminUrl) => { Header: translate('admin.survey'), source: 'survey.name', editable: false, - type: 'tooltip', + Filter: getColumnFilter(translate), }; @@ -35,7 +35,7 @@ export const getSurveyResponsePageConfig = (translate, path, adminUrl) => { const date = { Header: translate('admin.dateOfSurvey'), source: 'end_time', - type: 'tooltip', + accessor: row => moment(row.end_time).local().format('ddd, MMM Do YYYY, HH:mm:ss ZZ'), filterable: false, editable: false, @@ -44,7 +44,7 @@ export const getSurveyResponsePageConfig = (translate, path, adminUrl) => { const dateOfData = { Header: translate('admin.dateOfData'), source: 'data_time', - type: 'tooltip', + accessor: row => moment.parseZone(row.data_time).format('ddd, MMM Do YYYY, HH:mm:ss'), filterable: false, editConfig: { diff --git a/packages/lesmis/src/views/AdminPanel/routes/surveys/getSurveysPageConfig.js b/packages/lesmis/src/views/AdminPanel/routes/surveys/getSurveysPageConfig.js index 97fb155e3e..95f9c7442a 100644 --- a/packages/lesmis/src/views/AdminPanel/routes/surveys/getSurveysPageConfig.js +++ b/packages/lesmis/src/views/AdminPanel/routes/surveys/getSurveysPageConfig.js @@ -143,7 +143,7 @@ export const getSurveysPageConfig = translate => { { Header: translate('admin.code'), source: 'question.code', - type: 'tooltip', + editable: false, }, { @@ -153,27 +153,22 @@ export const getSurveysPageConfig = translate => { { Header: translate('admin.name'), source: 'question.name', - type: 'tooltip', }, { Header: translate('admin.question'), source: 'question.text', - type: 'tooltip', }, { Header: translate('admin.detail'), source: 'question.detail', - type: 'tooltip', }, { Header: translate('admin.questionLabel'), source: 'question_label', - type: 'tooltip', }, { Header: translate('admin.detailLabel'), source: 'detail_label', - type: 'tooltip', }, ]; diff --git a/packages/lesmis/src/views/AdminPanel/routes/surveys/getSyncGroupsPageConfig.js b/packages/lesmis/src/views/AdminPanel/routes/surveys/getSyncGroupsPageConfig.js index 17c8a67249..76b473c47e 100644 --- a/packages/lesmis/src/views/AdminPanel/routes/surveys/getSyncGroupsPageConfig.js +++ b/packages/lesmis/src/views/AdminPanel/routes/surveys/getSyncGroupsPageConfig.js @@ -69,7 +69,7 @@ export const getSyncGroupsPageConfig = translate => { source: 'sync_status', filterable: false, disableSortBy: true, - colWidth: 180, + width: 200, actionConfig: { syncStatusEndpoint: 'dataServiceSyncGroups/{id}', latestSyncLogEndpoint: 'dataServiceSyncGroups/{id}/logs?limit=1', diff --git a/packages/lesmis/src/views/AdminPanel/routes/users/getUsersPageConfig.jsx b/packages/lesmis/src/views/AdminPanel/routes/users/getUsersPageConfig.jsx index bd26bbfa64..a8edd2ae22 100644 --- a/packages/lesmis/src/views/AdminPanel/routes/users/getUsersPageConfig.jsx +++ b/packages/lesmis/src/views/AdminPanel/routes/users/getUsersPageConfig.jsx @@ -29,7 +29,7 @@ export const getUsersPageConfig = translate => { { Header: translate('admin.emailAddress'), source: 'email', - type: 'tooltip', + Filter: getColumnFilter(translate), }, { @@ -45,7 +45,7 @@ export const getUsersPageConfig = translate => { { Header: translate('admin.employer'), source: 'employer', - type: 'tooltip', + Filter: getColumnFilter(translate), }, ]; @@ -63,7 +63,7 @@ export const getUsersPageConfig = translate => { { Header: translate('admin.verified'), source: 'verified_email', - type: 'tooltip', + editConfig: { options: [ { diff --git a/packages/lesmis/src/views/AdminPanel/routes/visualisations/getDashboardItemsPageConfig.jsx b/packages/lesmis/src/views/AdminPanel/routes/visualisations/getDashboardItemsPageConfig.jsx index 619d5bf9d1..79fe75142f 100644 --- a/packages/lesmis/src/views/AdminPanel/routes/visualisations/getDashboardItemsPageConfig.jsx +++ b/packages/lesmis/src/views/AdminPanel/routes/visualisations/getDashboardItemsPageConfig.jsx @@ -21,13 +21,13 @@ export const getDashboardItemsPageConfig = (translate, adminUrl, isBESAdmin) => { Header: translate('admin.code'), source: 'code', - type: 'tooltip', + Filter: getColumnFilter(translate), }, { Header: translate('admin.reportCode'), source: 'report_code', - type: 'tooltip', + Filter: getColumnFilter(translate), }, { diff --git a/packages/lesmis/src/views/AdminPanel/routes/visualisations/getDashboardsPageConfig.js b/packages/lesmis/src/views/AdminPanel/routes/visualisations/getDashboardsPageConfig.js index f91e95b4e4..601f65f3a3 100644 --- a/packages/lesmis/src/views/AdminPanel/routes/visualisations/getDashboardsPageConfig.js +++ b/packages/lesmis/src/views/AdminPanel/routes/visualisations/getDashboardsPageConfig.js @@ -17,7 +17,7 @@ export const getDashboardsPageConfig = translate => { { Header: translate('admin.code'), source: 'code', - type: 'tooltip', + Filter: getColumnFilter(translate), }, { diff --git a/packages/lesmis/src/views/AdminPanel/routes/visualisations/getMapOverlayGroupRelationsPageConfig.js b/packages/lesmis/src/views/AdminPanel/routes/visualisations/getMapOverlayGroupRelationsPageConfig.js index 6ab0746086..be9cbc4151 100644 --- a/packages/lesmis/src/views/AdminPanel/routes/visualisations/getMapOverlayGroupRelationsPageConfig.js +++ b/packages/lesmis/src/views/AdminPanel/routes/visualisations/getMapOverlayGroupRelationsPageConfig.js @@ -16,7 +16,7 @@ export const getMapOverlayGroupRelationsPageConfig = translate => { { Header: translate('admin.code'), source: 'map_overlay_group.code', - type: 'tooltip', + Filter: getColumnFilter(translate), editConfig: { optionsEndpoint: 'mapOverlayGroups', @@ -28,7 +28,7 @@ export const getMapOverlayGroupRelationsPageConfig = translate => { { Header: translate('admin.childId'), source: 'child_id', - type: 'tooltip', + Filter: getColumnFilter(translate), editConfig: { optionsEndpoint: 'mapOverlays', @@ -42,7 +42,7 @@ export const getMapOverlayGroupRelationsPageConfig = translate => { Header: translate('admin.childType'), width: 160, source: 'child_type', - type: 'tooltip', + Filter: getColumnFilter(translate), editConfig: { options: [ @@ -60,7 +60,7 @@ export const getMapOverlayGroupRelationsPageConfig = translate => { { Header: translate('admin.sortOrder'), source: 'sort_order', - type: 'tooltip', + Filter: getColumnFilter(translate), }, ]; diff --git a/packages/lesmis/src/views/AdminPanel/routes/visualisations/getMapOverlayGroupsPageConfig.js b/packages/lesmis/src/views/AdminPanel/routes/visualisations/getMapOverlayGroupsPageConfig.js index 0e31bdb3cb..c8601558e2 100644 --- a/packages/lesmis/src/views/AdminPanel/routes/visualisations/getMapOverlayGroupsPageConfig.js +++ b/packages/lesmis/src/views/AdminPanel/routes/visualisations/getMapOverlayGroupsPageConfig.js @@ -12,14 +12,14 @@ export const getMapOverlayGroupsPageConfig = translate => { { Header: translate('admin.code'), source: 'code', - type: 'tooltip', + Filter: getColumnFilter(translate), }, { Header: translate('admin.name'), source: 'name', width: 140, - type: 'tooltip', + Filter: getColumnFilter(translate), }, ]; @@ -28,7 +28,7 @@ export const getMapOverlayGroupsPageConfig = translate => { { Header: 'ID', source: 'id', - type: 'tooltip', + Filter: getColumnFilter(translate), }, ...EDIT_FIELDS, @@ -51,7 +51,7 @@ export const getMapOverlayGroupsPageConfig = translate => { { Header: translate('admin.childId'), source: 'child_id', - type: 'tooltip', + Filter: getColumnFilter(translate), editConfig: { optionsEndpoint: 'mapOverlays', @@ -63,7 +63,7 @@ export const getMapOverlayGroupsPageConfig = translate => { { Header: translate('admin.childType'), source: 'child_type', - type: 'tooltip', + Filter: getColumnFilter(translate), editConfig: { options: [ @@ -81,7 +81,7 @@ export const getMapOverlayGroupsPageConfig = translate => { { Header: translate('admin.sortOrder'), source: 'sort_order', - type: 'tooltip', + Filter: getColumnFilter(translate), }, ]; diff --git a/packages/lesmis/src/views/AdminPanel/routes/visualisations/getMapOverlaysPageConfig.jsx b/packages/lesmis/src/views/AdminPanel/routes/visualisations/getMapOverlaysPageConfig.jsx index a7fa5ef442..b263cd36d4 100644 --- a/packages/lesmis/src/views/AdminPanel/routes/visualisations/getMapOverlaysPageConfig.jsx +++ b/packages/lesmis/src/views/AdminPanel/routes/visualisations/getMapOverlaysPageConfig.jsx @@ -20,14 +20,14 @@ export const getMapOverlaysPageConfig = (translate, adminUrl, isBESAdmin) => { { Header: translate('admin.code'), source: 'code', - type: 'tooltip', + Filter: getColumnFilter(translate), }, { Header: translate('admin.name'), source: 'name', width: 140, - type: 'tooltip', + Filter: getColumnFilter(translate), }, { @@ -95,7 +95,7 @@ export const getMapOverlaysPageConfig = (translate, adminUrl, isBESAdmin) => { Header: translate('admin.reportCode'), source: 'report_code', width: 140, - type: 'tooltip', + Filter: getColumnFilter(translate), }, {