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',