diff --git a/package.json b/package.json
index 85bd11c32..3fa151c72 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"eslint:fix": "npx eslint --fix",
"eslint:check": "npx eslint . --ext .ts,.tsx",
"prettier:fix": "prettier --write .",
+ "prettier:fix:file": "prettier --write",
"prettier:check": "prettier --check .",
"confd": "node ./confd/generate-config.js",
"confd:prod": "node ./confd/generate-config.js --environment production",
diff --git a/src/App.dark-theme.css b/src/App.dark-theme.css
index 6ab1dba06..e89d57a6d 100644
--- a/src/App.dark-theme.css
+++ b/src/App.dark-theme.css
@@ -125,12 +125,12 @@
}
.dark-theme .ag-theme-alpine-dark .ag-row-selected {
- border-radius: 100px;
- color: var(--mdc-theme-text-primary-on-dark, #fff);
+ border-radius: 10px;
+ color: var(--mdc-theme-text-primary-on-background);
}
.dark-theme .ag-theme-alpine-dark .ag-row-hover {
- border-radius: 100px;
+ border-radius: 10px;
background-color: var(--ag-row-hover-color);
}
diff --git a/src/common/components/grid/cell-renderer/__snapshots__/details-expander.cell-renderer.spec.tsx.snap b/src/common/components/grid/cell-renderer/__snapshots__/details-expander.cell-renderer.spec.tsx.snap
index 4c1f0eb17..3c9f985c0 100644
--- a/src/common/components/grid/cell-renderer/__snapshots__/details-expander.cell-renderer.spec.tsx.snap
+++ b/src/common/components/grid/cell-renderer/__snapshots__/details-expander.cell-renderer.spec.tsx.snap
@@ -1,7 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AgGrid DetailsExpanderRenderer component renders correctly 1`] = `
-
+
+
+
`;
diff --git a/src/common/components/grid/cell-renderer/details-expander.cell-renderer.spec.tsx b/src/common/components/grid/cell-renderer/details-expander.cell-renderer.spec.tsx
index e95899991..09f5464db 100644
--- a/src/common/components/grid/cell-renderer/details-expander.cell-renderer.spec.tsx
+++ b/src/common/components/grid/cell-renderer/details-expander.cell-renderer.spec.tsx
@@ -1,16 +1,7 @@
-import React from 'react';
import { shallow } from 'enzyme';
-import {
- ICellRendererParams,
- Column,
- RowNode,
- GridApi,
- ColumnApi,
- IRowNode,
-} from 'ag-grid-community';
+import { ICellRendererParams, RowNode } from 'ag-grid-community';
// eslint-disable-next-line
import '../../../../__mocks__/confEnvShim';
-import { DETAILS_ROW_ID_SUFFIX } from '../grid';
import { DetailsExpanderRenderer } from './details-expander.cell-renderer';
const ID = '1';
@@ -22,18 +13,18 @@ const mockDataBase: ICellRendererParams = {
setValue: () => {},
formatValue: () => {},
data: {
- isVisible: true,
+ isDetailsExpanded: false,
id: ID,
},
- node: new RowNode(),
+ node: {
+ ...new RowNode(),
+ setDataValue: (propName: string, val: any) => {},
+ } as any,
colDef: {},
$scope: null,
rowIndex: 1,
api: {
- onFilterChanged: () => {},
- getRowNode: (id: string): IRowNode | undefined => {
- return undefined;
- },
+ resetRowHeights: () => {},
},
context: null,
refreshCell: () => {},
@@ -54,27 +45,21 @@ describe('AgGrid DetailsExpanderRenderer component', () => {
expect(wrapper).toMatchSnapshot();
});
- it('when component clicked detail row was found in grid rowdata', () => {
+ it('when component clicked, isDetailsExpanded is toggled on the current node', () => {
const mockData = {
...mockDataBase,
};
- // eslint-disable-next-line
- jest.spyOn(mockData.api, 'onFilterChanged').mockImplementation(() => {});
- const spyGetRonNode = jest.spyOn(mockData.api, 'getRowNode').mockImplementation(() => {
- const val = new RowNode();
- // eslint-disable-next-line
- val.setDataValue = (propName, val) => {};
- val.data = {
- isVisible: false,
- };
- return val;
- });
+ const spySetDataValue = jest.spyOn(mockData.node, 'setDataValue').mockImplementation(() => {});
+ const spyResetRowHeights = jest
+ .spyOn(mockData.api, 'resetRowHeights')
+ .mockImplementation(() => {});
const wrapper = shallow();
const iconContainer = wrapper.find('CollapseButton');
iconContainer.simulate('click');
- expect(spyGetRonNode).toHaveBeenCalledWith(`${ID}${DETAILS_ROW_ID_SUFFIX}`);
+ expect(spySetDataValue).toHaveBeenCalledWith('isDetailsExpanded', true);
+ expect(spyResetRowHeights).toHaveBeenCalledTimes(1);
});
});
diff --git a/src/common/components/grid/cell-renderer/details-expander.cell-renderer.tsx b/src/common/components/grid/cell-renderer/details-expander.cell-renderer.tsx
index 325743fbb..4425115eb 100644
--- a/src/common/components/grid/cell-renderer/details-expander.cell-renderer.tsx
+++ b/src/common/components/grid/cell-renderer/details-expander.cell-renderer.tsx
@@ -1,27 +1,78 @@
-import React from 'react';
+import React, { useLayoutEffect, useState } from 'react';
import { ICellRendererParams } from 'ag-grid-community';
+import { Box } from '@map-colonies/react-components';
import { CollapseButton } from '../../collapse-button/collapse.button';
-import { DETAILS_ROW_ID_SUFFIX, IGridRowDataDetailsExt } from '../grid';
+import {
+ DEFAULT_DETAILS_ROW_HEIGHT,
+ DEFAULT_NORMAL_ROW_HEIGHT,
+ IGridRowDataDetailsExt,
+} from '../grid';
import './details-expander.cell-renderer.css';
interface DetailsExpanderRendererProps extends ICellRendererParams {
+ detailsComponent: React.ComponentType;
detailsRowCellRendererPresencePredicate?: (data: any) => boolean;
+ normalRowHeight?: number;
+ detailsRowHeight?: number;
}
export const DetailsExpanderRenderer: React.FC = (
props
): JSX.Element | null => {
- const shouldRenderBtn = props.detailsRowCellRendererPresencePredicate?.(props.data) ?? true;
+ const {
+ detailsRowCellRendererPresencePredicate,
+ detailsComponent,
+ normalRowHeight = DEFAULT_NORMAL_ROW_HEIGHT,
+ detailsRowHeight = DEFAULT_DETAILS_ROW_HEIGHT,
+ ...rendererParams
+ } = props;
+
+ const [overlayStyle, setOverlayStyle] = useState(null);
+ const [isDetailsExpanded, setIsDetailsExpanded] = useState(
+ (props.data as IGridRowDataDetailsExt).isDetailsExpanded ?? false
+ );
+
+ const shouldRenderBtn = detailsRowCellRendererPresencePredicate?.(props.data) ?? true;
const handleCollapseExpand = (): void => {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- const rowNode = props.api.getRowNode(`${props.data?.id as string}${DETAILS_ROW_ID_SUFFIX}`);
- const isVisible = (rowNode?.data as IGridRowDataDetailsExt).isVisible;
- rowNode?.setDataValue('isVisible', !isVisible);
- props.api.onFilterChanged();
+ const newVal = !isDetailsExpanded;
+ setIsDetailsExpanded(newVal);
+ props.node.setDataValue('isDetailsExpanded', newVal);
+ props.api.resetRowHeights();
};
- if (shouldRenderBtn) return ;
- return null;
+ useLayoutEffect(() => {
+ if (!isDetailsExpanded) {
+ setOverlayStyle(null);
+ return;
+ }
+
+ const cell = props.eGridCell as HTMLElement;
+ const row = cell.parentElement as HTMLElement | null;
+ if (row) {
+ setOverlayStyle({
+ position: 'absolute',
+ top: normalRowHeight,
+ left: -cell.offsetLeft,
+ width: row.offsetWidth,
+ height: detailsRowHeight,
+ overflow: 'hidden',
+ zIndex: 2,
+ display: 'flex',
+ backgroundColor: 'var(--ag-background-color)',
+ });
+ }
+ }, [isDetailsExpanded, normalRowHeight, detailsRowHeight]);
+
+ return (
+
+ {shouldRenderBtn && }
+ {isDetailsExpanded && overlayStyle && (
+
+
+
+ )}
+
+ );
};
diff --git a/src/common/components/grid/grid.tsx b/src/common/components/grid/grid.tsx
index 4875d2b41..012a6ab96 100644
--- a/src/common/components/grid/grid.tsx
+++ b/src/common/components/grid/grid.tsx
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { CSSProperties, useCallback, useEffect, useState } from 'react';
+import { omit } from 'lodash';
import { AgGridReact } from 'ag-grid-react';
import {
GridReadyEvent as AgGridReadyEvent,
@@ -16,7 +17,6 @@ import {
RowClickedEvent,
AllCommunityModule,
ModuleRegistry,
- IsFullWidthRowParams,
} from 'ag-grid-community';
import { useTheme } from '@map-colonies/react-core';
import { Box } from '@map-colonies/react-components';
@@ -31,9 +31,9 @@ import 'ag-grid-community/styles/ag-theme-alpine.css';
// All Community Features
ModuleRegistry.registerModules([AllCommunityModule]);
-const DEFAULT_DETAILS_ROW_HEIGHT = 150;
+export const DEFAULT_DETAILS_ROW_HEIGHT = 234;
+export const DEFAULT_NORMAL_ROW_HEIGHT = 42;
const EXPANDER_COLUMN_WIDTH = 60;
-export const DETAILS_ROW_ID_SUFFIX = '_details';
interface GridComponentProps {
gridOptions?: GridComponentOptions;
@@ -54,6 +54,7 @@ export interface GridRowDragEndEvent extends RowDragEndEvent {}
export interface GridRowSelectedEvent extends RowSelectedEvent {}
export interface GridRowClickedEvent extends RowClickedEvent {}
export interface GridValueFormatterParams extends ValueFormatterParams {}
+
export interface GridComponentOptions extends GridOptions {
detailsRowCellRenderer?: string;
detailsRowHeight?: number;
@@ -63,10 +64,15 @@ export interface GridComponentOptions extends GridOptions {
};
}
+export const GRID_COMPONENT_OPTIONS_OWNED_KEYS = [
+ 'detailsRowCellRenderer',
+ 'detailsRowHeight',
+ 'detailsRowExpanderPosition',
+ 'context.detailsRowCellRendererPresencePredicate',
+] as const;
+
export interface IGridRowDataDetailsExt {
- rowHeight: number;
- fullWidth: boolean;
- isVisible: boolean;
+ isDetailsExpanded: boolean;
}
export interface IRowPosition {
@@ -86,71 +92,59 @@ export const GridComponent: React.FC = (props) => {
const { detailsRowExpanderPosition, ...restGridOptions } =
props.gridOptions as GridComponentOptions;
+ const detailsRowCellRenderer = props.gridOptions?.detailsRowCellRenderer;
+
+ const detailsComponent = detailsRowCellRenderer
+ ? (props.gridOptions?.components as Record)?.[detailsRowCellRenderer]
+ : undefined;
+ const normalRowHeight = restGridOptions.rowHeight ?? DEFAULT_NORMAL_ROW_HEIGHT;
+ const detailsRowHeight = props.gridOptions?.detailsRowHeight ?? DEFAULT_DETAILS_ROW_HEIGHT;
+
+ const expanderColumnDef = {
+ headerName: '',
+ width: EXPANDER_COLUMN_WIDTH,
+ cellRenderer: 'detailsExpanderRenderer',
+ suppressMovable: true,
+ sortable: false,
+ cellStyle: { overflow: 'visible' },
+ cellRendererParams: {
+ detailsComponent,
+ detailsRowCellRendererPresencePredicate:
+ props.gridOptions?.context?.detailsRowCellRendererPresencePredicate,
+ normalRowHeight,
+ detailsRowHeight,
+ },
+ };
+
const gridOptionsFromProps: GridComponentOptions = {
...restGridOptions,
columnDefs: [
{
- field: 'isVisible',
+ field: 'isDetailsExpanded',
hide: true,
},
props.gridOptions?.detailsRowExpanderPosition === 'start' &&
props.gridOptions.detailsRowCellRenderer !== undefined
- ? {
- headerName: '',
- width: EXPANDER_COLUMN_WIDTH,
- cellRenderer: 'detailsExpanderRenderer',
- suppressMovable: true,
- sortable: false,
- cellRendererParams: {
- detailsRowCellRendererPresencePredicate:
- props.gridOptions.context?.detailsRowCellRendererPresencePredicate,
- },
- }
+ ? expanderColumnDef
: {
hide: true,
},
...(props.gridOptions?.columnDefs as []),
props.gridOptions?.detailsRowExpanderPosition !== 'start' &&
props.gridOptions?.detailsRowCellRenderer !== undefined
- ? {
- headerName: '',
- width: EXPANDER_COLUMN_WIDTH,
- cellRenderer: 'detailsExpanderRenderer',
- suppressMovable: true,
- sortable: false,
- cellRendererParams: {
- detailsRowCellRendererPresencePredicate:
- props.gridOptions.context?.detailsRowCellRendererPresencePredicate,
- },
- }
+ ? expanderColumnDef
: {
hide: true,
},
],
getRowHeight:
- props.gridOptions?.detailsRowCellRenderer !== undefined
- ? (params: RowHeightParams): number => {
- return (params.data as IGridRowDataDetailsExt).rowHeight;
- }
- : undefined,
- isExternalFilterPresent:
- props.gridOptions?.detailsRowCellRenderer !== undefined ? (): boolean => true : undefined,
- doesExternalFilterPass:
- props.gridOptions?.detailsRowCellRenderer !== undefined
- ? (node): boolean => {
- return (node.data as IGridRowDataDetailsExt).isVisible;
- //return gridOptions.api.getValue("isVisible", node.rowNode);
+ detailsRowCellRenderer !== undefined
+ ? (params: RowHeightParams): number | undefined => {
+ return (params.data as IGridRowDataDetailsExt).isDetailsExpanded
+ ? normalRowHeight + detailsRowHeight
+ : restGridOptions.rowHeight;
}
: undefined,
- isFullWidthRow:
- props.gridOptions?.detailsRowCellRenderer !== undefined
- ? (params: IsFullWidthRowParams): boolean => {
- // checked the fullWidth attribute that was set while creating the data
- return (params.rowNode.data as IGridRowDataDetailsExt).fullWidth;
- }
- : undefined,
- fullWidthCellRenderer: props.gridOptions?.detailsRowCellRenderer ?? undefined,
-
components: {
...(props.gridOptions?.components as { [key: string]: any }),
detailsExpanderRenderer: useCallback(DetailsExpanderRenderer, []),
@@ -165,15 +159,16 @@ export const GridComponent: React.FC = (props) => {
},
};
- const { detailsRowCellRenderer, detailsRowHeight, ...gridOptions } = gridOptionsFromProps;
+ // Strip custom props before passing to ag-Grid (which doesn't know about them)
+ const gridOptions = omit(gridOptionsFromProps, GRID_COMPONENT_OPTIONS_OWNED_KEYS);
- const getIsVisible = (id: string): boolean => {
+ const getIsDetailsExpanded = (id: string): boolean => {
let res = false;
gridApi?.forEachNode((node) => {
const nodeData = node.data as Record;
if (nodeData.id === id) {
- res = nodeData.isVisible as boolean;
+ res = nodeData.isDetailsExpanded as boolean;
}
});
return res;
@@ -182,20 +177,11 @@ export const GridComponent: React.FC = (props) => {
useEffect(() => {
const result: any[] = [];
if (props.gridOptions?.detailsRowCellRenderer !== undefined) {
- props.rowData?.forEach((element, idx) => {
+ props.rowData?.forEach((element) => {
const rowElement: Record = element as Record;
-
result.push({
...rowElement,
- isVisible: true,
- });
- result.push({
- ...rowElement,
- fullWidth: true,
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- id: `${rowElement.id as string}${DETAILS_ROW_ID_SUFFIX}`,
- isVisible: getIsVisible(`${rowElement.id as string}${DETAILS_ROW_ID_SUFFIX}`),
- rowHeight: props.gridOptions?.detailsRowHeight ?? DEFAULT_DETAILS_ROW_HEIGHT,
+ isDetailsExpanded: getIsDetailsExpanded(rowElement.id as string),
});
});
} else {
@@ -259,9 +245,12 @@ export const GridComponent: React.FC = (props) => {
setIsRowFound?.(true);
goToRowAndFocus(gridApi, row);
- const rowNode = gridApi.getRowNode(`${id as unknown as string}${DETAILS_ROW_ID_SUFFIX}`);
- rowNode?.setDataValue('isVisible', true);
- gridApi.onFilterChanged();
+ const rowNode = gridApi.getRowNode(id);
+ if (rowNode) {
+ rowNode.setDataValue('isDetailsExpanded', true);
+ gridApi.refreshCells({ rowNodes: [rowNode], force: true });
+ }
+ gridApi.resetRowHeights();
};
const agGridThemeOverrides = GridThemes.getTheme(theme);
diff --git a/src/common/i18n/helpers/helpers.ts b/src/common/i18n/helpers/helpers.ts
new file mode 100644
index 000000000..6fb34a986
--- /dev/null
+++ b/src/common/i18n/helpers/helpers.ts
@@ -0,0 +1,3 @@
+export const isRtl = (locale: string) => {
+ return locale === 'he';
+};
diff --git a/src/discrete-layer/components/job-manager/cell-renderer/job-details/job-details.cell-renderer.css b/src/discrete-layer/components/job-manager/cell-renderer/job-details/job-details.cell-renderer.css
index 3a8e89a23..f6ae4a691 100644
--- a/src/discrete-layer/components/job-manager/cell-renderer/job-details/job-details.cell-renderer.css
+++ b/src/discrete-layer/components/job-manager/cell-renderer/job-details/job-details.cell-renderer.css
@@ -10,11 +10,11 @@
.jobDetailsContainer {
overflow-y: auto;
- height: 230px;
- width: 1300px;
- margin: 0px 35px;
+ height: 228px;
+ width: 100%;
+ margin: unset;
+ margin-right: 2px;
background: var(--ag-selected-details-row-background);
- padding-left: 20px;
}
body[dir='rtl'] .jobDetailsContainer {
@@ -41,11 +41,13 @@ body[dir='rtl'] .jobDetailsContainer {
z-index: 1;
text-align: left;
position: sticky;
+ display: flex;
+ align-items: center;
font-size: var(--ag-font-size);
top: 0;
background: var(--ag-selected-details-row-background);
width: 100%;
- height: 100%;
+ height: 60%;
padding: 12px 0;
border-bottom: solid 1px var(--mdc-theme-text-secondary-on-dark);
}
diff --git a/src/discrete-layer/components/job-manager/cell-renderer/job-details/job-details.cell-renderer.tsx b/src/discrete-layer/components/job-manager/cell-renderer/job-details/job-details.cell-renderer.tsx
index 15bfdd6d8..7ab7f00c6 100644
--- a/src/discrete-layer/components/job-manager/cell-renderer/job-details/job-details.cell-renderer.tsx
+++ b/src/discrete-layer/components/job-manager/cell-renderer/job-details/job-details.cell-renderer.tsx
@@ -8,7 +8,6 @@ import { IconButton, Tooltip, Typography } from '@map-colonies/react-core';
import { Box } from '@map-colonies/react-components';
import { Copy } from '../../../../../common/components/copy/copy';
import { AutoDirectionBox } from '../../../../../common/components/auto-direction-box/auto-direction-box.component';
-import { DETAILS_ROW_ID_SUFFIX } from '../../../../../common/components/grid';
import { Loading } from '../../../../../common/components/tree/statuses/loading';
import { relativeDateFormatter, dateFormatter } from '../../../../../common/helpers/formatters';
import {
@@ -207,7 +206,7 @@ export const JobDetailsRenderer: React.FC = observer((props
const store = useStore();
const [propsWithJobParams, setPropsWithJobParams] = useState(props);
- const jobId = (props.data as JobModelType).id.replace(DETAILS_ROW_ID_SUFFIX, '');
+ const jobId = (props.data as JobModelType).id;
const { data } = useQuery((store) =>
store.queryJob({
diff --git a/src/discrete-layer/components/job-manager/cell-renderer/job-details/job-details.header.tsx b/src/discrete-layer/components/job-manager/cell-renderer/job-details/job-details.header.tsx
index ddaa098d3..1406d7dcd 100644
--- a/src/discrete-layer/components/job-manager/cell-renderer/job-details/job-details.header.tsx
+++ b/src/discrete-layer/components/job-manager/cell-renderer/job-details/job-details.header.tsx
@@ -5,7 +5,6 @@ import { Box } from '@map-colonies/react-components';
import { Copy } from '../../../../../common/components/copy/copy';
import { AutoDirectionBox } from '../../../../../common/components/auto-direction-box/auto-direction-box.component';
import TooltippedValue from '../../../../../common/components/form/tooltipped.value';
-import { DETAILS_ROW_ID_SUFFIX } from '../../../../../common/components/grid';
import { JobModelType, Status } from '../../../../models';
import './job-details.header.css';
@@ -51,7 +50,7 @@ export const JobDetailsHeader: React.FC = ({
detailsRow: {
id: {
label: getDetailsTranslation('internalId'),
- value: id.replace(DETAILS_ROW_ID_SUFFIX, ''),
+ value: id,
},
externalId: {
label: getDetailsTranslation('extetnalId'),
diff --git a/src/discrete-layer/components/job-manager/cell-renderer/job-details/job-details.raster-job-data.tsx b/src/discrete-layer/components/job-manager/cell-renderer/job-details/job-details.raster-job-data.tsx
index 08b9d0536..497bcd6c8 100644
--- a/src/discrete-layer/components/job-manager/cell-renderer/job-details/job-details.raster-job-data.tsx
+++ b/src/discrete-layer/components/job-manager/cell-renderer/job-details/job-details.raster-job-data.tsx
@@ -11,7 +11,6 @@ import {
useTheme,
} from '@map-colonies/react-core';
import { AutoDirectionBox } from '../../../../../common/components/auto-direction-box/auto-direction-box.component';
-import { DETAILS_ROW_ID_SUFFIX } from '../../../../../common/components/grid';
import { Hyperlink } from '../../../../../common/components/hyperlink/hyperlink';
import { useEnums } from '../../../../../common/hooks/useEnum.hook';
import { Domain } from '../../../../../common/models/domain';
@@ -75,7 +74,7 @@ export const JobDetailsRasterJobData: React.FC = (
try {
const result = await store.queryFindTasks({
params: {
- jobId: jobData.id.replace(DETAILS_ROW_ID_SUFFIX, ''),
+ jobId: jobData.id,
type: 'validation',
},
});
diff --git a/src/discrete-layer/components/job-manager/cell-renderer/job-details/job.details.export-job-data.css b/src/discrete-layer/components/job-manager/cell-renderer/job-details/job.details.export-job-data.css
index 1a2796431..2a3ce5632 100644
--- a/src/discrete-layer/components/job-manager/cell-renderer/job-details/job.details.export-job-data.css
+++ b/src/discrete-layer/components/job-manager/cell-renderer/job-details/job.details.export-job-data.css
@@ -1,6 +1,6 @@
#exportJobData.jobDataContainer {
flex-direction: column;
- gap: 10px;
+ height: 40%;
}
#exportJobData.jobDataContainer .jobDescription {
@@ -26,7 +26,7 @@
#exportJobData .linkContainer {
display: flex;
- align-items: flex-end;
+ align-items: center;
}
#exportJobData .jobDataLink {
diff --git a/src/discrete-layer/components/job-manager/grids/job-manager-grid.common.tsx b/src/discrete-layer/components/job-manager/grids/job-manager-grid.common.tsx
index f66b735dc..b8dc3a3e8 100644
--- a/src/discrete-layer/components/job-manager/grids/job-manager-grid.common.tsx
+++ b/src/discrete-layer/components/job-manager/grids/job-manager-grid.common.tsx
@@ -353,7 +353,8 @@ const JobManagerGrid: React.FC = (props) => {
return (params.data as JobModelType).id;
},
detailsRowCellRenderer: 'detailsRenderer',
- detailsRowHeight: 230,
+ detailsRowHeight: 234,
+ rowHeight: 42,
detailsRowExpanderPosition: 'start',
overlayNoRowsTemplate: intl.formatMessage({
id: 'results.nodata',