Skip to content

Commit

Permalink
Merge branch 'dev' into rn-1243-resubmit-surveys
Browse files Browse the repository at this point in the history
  • Loading branch information
alexd-bes committed Jul 9, 2024
2 parents 1366e62 + f65d4f9 commit 8e451a3
Show file tree
Hide file tree
Showing 28 changed files with 501 additions and 224 deletions.
10 changes: 8 additions & 2 deletions packages/admin-panel/src/layout/Breadcrumbs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ArrowBack } from '@material-ui/icons';
import { Breadcrumbs as MuiBreadcrumbs, IconButton, Typography } from '@material-ui/core';
import styled from 'styled-components';
import { generateTitle } from '../pages/resources/resourceName';
import { useLinkToPreviousSearchState } from '../utilities';

const Wrapper = styled.div`
display: flex;
Expand Down Expand Up @@ -44,17 +45,22 @@ export const Breadcrumbs = ({

const parentTitle = parent ? parent.title ?? generateTitle(parent.resourceName) : null;

const pathname = parent?.to || '/';

const { to, newState } = useLinkToPreviousSearchState(pathname);

return (
<Wrapper>
<BackButton component={Link} to={parent?.to || '/'} onClick={onClickLinks}>
<BackButton component={Link} to={to} onClick={onClickLinks} state={newState}>
<ArrowBack />
</BackButton>
<MuiBreadcrumbs separator="|">
<Breadcrumb
component={Link}
to={parent?.to || '/'}
to={to}
onClick={onClickLinks}
color="textPrimary"
state={newState}
>
{parentTitle}
</Breadcrumb>
Expand Down
12 changes: 1 addition & 11 deletions packages/admin-panel/src/pages/resources/ResourcePage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,6 @@ import { ResubmitSurveyResponseModal } from '../../surveyResponse/ResubmitSurvey
import { Breadcrumbs } from '../../layout';
import { useItemDetails } from '../../api/queries/useResourceDetails';

const TableComponent = ({ children }) => (
<div className="scroll-container">
<div>{children}</div>
</div>
);

TableComponent.propTypes = {
children: PropTypes.node.isRequired,
};

const useEndpoint = (endpoint, details, params) => {
if (!details && !params) return endpoint;

Expand Down Expand Up @@ -118,14 +108,14 @@ export const ResourcePage = ({
columns={accessibleColumns}
baseFilter={baseFilter}
defaultFilters={defaultFilters}
TableComponent={TableComponent}
defaultSorting={defaultSorting}
deleteConfig={deleteConfig}
detailUrl={path}
getHasNestedView={getHasNestedView}
getNestedViewLink={getNestedViewLink}
basePath={basePath}
actionLabel={actionLabel}
key={updatedEndpoint}
/>
<EditModal
onProcessDataForSave={onProcessDataForSave}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useEditFiles } from '../../../editor/useEditFiles';
import { FileUploadField } from '../../../widgets/InputField/FileUploadField';
import { FieldsEditor } from '../../../editor/FieldsEditor';
import { dismissEditor, loadEditor, resetEdits } from '../../../editor/actions';
import { useLinkToPreviousSearchState, useLinkWithSearchState } from '../../../utilities';

const Wrapper = styled.div`
overflow: hidden;
Expand Down Expand Up @@ -155,8 +156,13 @@ const EditSurveyPageComponent = withConnectedEditor(
]
: [];

// need to explicity state the path here because using '../../' doesn't apply the search state
const { to, newState } = useLinkToPreviousSearchState('/surveys');

const navigateBack = () => {
navigate('../../');
navigate(to, {
state: newState,
});
resetEditorToDefaultState();
};
const handleSave = () => {
Expand Down
9 changes: 8 additions & 1 deletion packages/admin-panel/src/routes/users/permissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,14 @@ const EntityField = {
};
export const PERMISSIONS_ENDPOINT = 'userEntityPermissions';
export const PERMISSIONS_COLUMNS = [
EntityField,
{
Header: 'Entity',
source: 'entity.name',
editConfig: {
optionsEndpoint: 'entities',
baseFilter: { type: 'country' },
},
},
{
Header: 'Permission group',
source: 'permission_group.name',
Expand Down
4 changes: 1 addition & 3 deletions packages/admin-panel/src/routes/users/users.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,7 @@ const CREATE_CONFIG = {
optionsEndpoint: 'countries',
optionLabelKey: 'name',
optionValueKey: 'name',
labelTooltip: 'Select the country to grant this user access to',
type: 'checkboxList',
pageSize: 'ALL',
secondaryLabel: 'Select the country to grant this user access to',
},
},
{
Expand Down
9 changes: 8 additions & 1 deletion packages/admin-panel/src/table/DataFetchingTable/Cells.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { useLinkWithSearchState } from '../../utilities';

// Flex does not support ellipsis so we need to have another container to handle the ellipsis
const CellContentContainer = styled.div`
Expand Down Expand Up @@ -59,8 +60,14 @@ export const DisplayCell = ({
return basePath ? `${basePath}${formattedUrl}` : formattedUrl;
};
const url = generateLink();
const { to, newState } = useLinkWithSearchState(url);
return (
<CellContentContainer to={url} as={url ? CellLink : 'div'} $isButtonColumn={isButtonColumn}>
<CellContentContainer
to={to}
as={url ? CellLink : 'div'}
$isButtonColumn={isButtonColumn}
state={newState}
>
{children}
</CellContentContainer>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,23 @@ import React, { memo, useEffect, useMemo } from 'react';
import styled from 'styled-components';
import { connect } from 'react-redux';
import { Typography } from '@material-ui/core';
import queryString from 'query-string';
import PropTypes from 'prop-types';
import { Alert, FilterableTable } from '@tupaia/ui-components';
import { generateConfigForColumnType } from '../columnTypes';
import { getIsFetchingData, getTableState } from '../selectors';
import { getIsChangingDataOnServer } from '../../dataChangeListener';
import {
cancelAction,
changeFilters,
changePage,
changePageSize,
changeSorting,
clearError,
confirmAction,
refreshData,
} from '../actions';
import { ConfirmDeleteModal } from '../../widgets';
import { useColumnFilters } from './useColumnFilters';
import { DisplayCell } from './Cells';
import { getEditorState } from '../../editor/selectors';

const ErrorAlert = styled(Alert).attrs({
severity: 'error',
Expand Down Expand Up @@ -91,9 +90,6 @@ const DataFetchingTableComponent = memo(
pageIndex = 0,
onPageChange,
onPageSizeChange,
initialiseTable,
nestingLevel,
filters,
sorting,
isChangingDataOnServer,
errorMessage,
Expand All @@ -102,18 +98,17 @@ const DataFetchingTableComponent = memo(
onConfirmAction,
onCancelAction,
deleteConfig,
onFilteredChange,
totalRecords,
isFetchingData,
onSortedChange,
detailUrl,
getHasNestedView,
endpoint,
getNestedViewLink,
baseFilter,
basePath,
resourceName,
actionLabel = 'Action',
actionLabel,
defaultFilters,
editorState,
}) => {
const formattedColumns = useMemo(() => {
const cols = columns.map(column => formatColumnForReactTable(column));
Expand Down Expand Up @@ -143,7 +138,7 @@ const DataFetchingTableComponent = memo(
const buttonWidths = buttonColumns.reduce((acc, { width }) => acc + (width || 60), 0);
// Group all button columns into a single column so they can be displayed together under a single header
const singleButtonColumn = {
Header: actionLabel,
Header: actionLabel || 'Action',
maxWidth: buttonWidths,
width: buttonWidths,
// eslint-disable-next-line react/prop-types
Expand All @@ -164,27 +159,25 @@ const DataFetchingTableComponent = memo(
return [...nonButtonColumns, singleButtonColumn];
}, [JSON.stringify(columns)]);

// Listen for changes in filters in the URL and refresh the data accordingly
const { filters, onChangeFilters } = useColumnFilters(defaultFilters);

useEffect(() => {
if (!isChangingDataOnServer && !errorMessage) {
onRefreshData();
}
}, [errorMessage, isChangingDataOnServer]);
// if the page index is already 0, we can just refresh the data
if (pageIndex === 0) {
onRefreshData(filters, sorting, pageIndex, pageSize);
// if the page index is not 0, we need to reset it to 0, which will trigger a refresh
} else onPageChange(0);
}, [JSON.stringify(filters), JSON.stringify(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();
}
}, [endpoint, JSON.stringify(baseFilter)]);
onRefreshData(filters, sorting, pageIndex, pageSize);
}, [pageSize, pageIndex]);

useEffect(() => {
onRefreshData();
}, [filters, pageIndex, pageSize, JSON.stringify(sorting)]);
if (editorState?.isOpen) return;
onRefreshData(filters, sorting, pageIndex, pageSize);
}, [editorState?.isOpen]);

const isLoading = isFetchingData || isChangingDataOnServer;

Expand Down Expand Up @@ -212,7 +205,7 @@ const DataFetchingTableComponent = memo(
pageSize={pageSize}
sorting={sorting}
numberOfPages={numberOfPages}
onChangeFilters={onFilteredChange}
onChangeFilters={onChangeFilters}
filters={filters}
hiddenColumns={columns
.filter(column => column.show === false)
Expand Down Expand Up @@ -248,56 +241,53 @@ DataFetchingTableComponent.propTypes = {
confirmActionMessage: PropTypes.string,
errorMessage: PropTypes.string,
data: PropTypes.arrayOf(PropTypes.shape({})),
filters: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
isFetchingData: PropTypes.bool.isRequired,
isChangingDataOnServer: PropTypes.bool.isRequired,
numberOfPages: PropTypes.number,
onCancelAction: PropTypes.func.isRequired,
onConfirmAction: PropTypes.func.isRequired,
onFilteredChange: PropTypes.func.isRequired,
onPageChange: PropTypes.func.isRequired,
onPageSizeChange: PropTypes.func.isRequired,
onRefreshData: PropTypes.func.isRequired,
onSortedChange: PropTypes.func.isRequired,
initialiseTable: PropTypes.func.isRequired,
pageIndex: PropTypes.number.isRequired,
pageSize: PropTypes.number.isRequired,
sorting: PropTypes.array.isRequired,
nestingLevel: PropTypes.number,
deleteConfig: PropTypes.object,
totalRecords: PropTypes.number,
detailUrl: PropTypes.string,
getHasNestedView: PropTypes.func,
endpoint: PropTypes.string.isRequired,
getNestedViewLink: PropTypes.func,
baseFilter: PropTypes.object,
basePath: PropTypes.string,
resourceName: PropTypes.object,
actionLabel: PropTypes.string,
defaultFilters: PropTypes.array,
editorState: PropTypes.object,
};

DataFetchingTableComponent.defaultProps = {
confirmActionMessage: null,
data: [],
errorMessage: '',
numberOfPages: 0,
nestingLevel: 0,
deleteConfig: {},
totalRecords: 0,
detailUrl: '',
getHasNestedView: null,
getNestedViewLink: null,
baseFilter: null,
basePath: '',
resourceName: {},
actionLabel: 'Action',
defaultFilters: [],
editorState: {},
};

const mapStateToProps = (state, { reduxId, ...ownProps }) => ({
isFetchingData: getIsFetchingData(state, reduxId),
isChangingDataOnServer: getIsChangingDataOnServer(state),
...ownProps,
...getTableState(state, reduxId),
editorState: getEditorState(state),
});

const mapDispatchToProps = (dispatch, { reduxId }) => ({
Expand All @@ -308,34 +298,25 @@ const mapDispatchToProps = (dispatch, { reduxId }) => ({
onPageSizeChange: (newPageSize, newPageIndex) =>
dispatch(changePageSize(reduxId, newPageSize, newPageIndex)),
onSortedChange: newSorting => dispatch(changeSorting(reduxId, newSorting)),
onFilteredChange: newFilters => dispatch(changeFilters(reduxId, newFilters)),
});

const mergeProps = (stateProps, { dispatch, ...dispatchProps }, ownProps) => {
const {
baseFilter = {},
defaultFilters = [],
defaultSorting = [],
endpoint,
columns,
reduxId,
...restOfOwnProps
} = ownProps;
const onRefreshData = () =>
dispatch(refreshData(reduxId, endpoint, columns, baseFilter, stateProps));
const initialiseTable = (filters = defaultFilters) => {
dispatch(changePageSize(reduxId, 20, 0));
dispatch(changeSorting(reduxId, defaultSorting));
dispatch(changeFilters(reduxId, filters)); // will trigger a data fetch afterwards
dispatch(clearError());
};
const { baseFilter = {}, endpoint, columns, reduxId, ...restOfOwnProps } = ownProps;
const onRefreshData = (filters, sorting, pageIndex, pageSize) =>
dispatch(
refreshData(reduxId, endpoint, columns, baseFilter, filters, sorting, {
...stateProps,
pageIndex,
pageSize,
}),
);

return {
reduxId,
...restOfOwnProps,
...stateProps,
...dispatchProps,
onRefreshData,
initialiseTable,
};
};

Expand Down
Loading

0 comments on commit 8e451a3

Please sign in to comment.