@centreon/ui 24.4.56 → 24.4.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@centreon/ui",
3
- "version": "24.4.56",
3
+ "version": "24.4.58",
4
4
  "description": "Centreon UI Components",
5
5
  "scripts": {
6
6
  "eslint": "eslint ./src --ext .js,.jsx,.ts,.tsx --max-warnings 0",
@@ -58,11 +58,6 @@ export const WithoutTitle: Story = {
58
58
  render: Template
59
59
  };
60
60
 
61
- export const WithPencentage: Story = {
62
- args: { data, title: 'hosts', unit: 'percentage' },
63
- render: Template
64
- };
65
-
66
61
  export const WithoutLegend: Story = {
67
62
  args: { data, displayLegend: false, title: 'hosts' },
68
63
 
@@ -74,6 +69,11 @@ export const withDisplayedValues: Story = {
74
69
  render: Template
75
70
  };
76
71
 
72
+ export const WithPencentage: Story = {
73
+ args: { data, displayValues: true, title: 'hosts', unit: 'percentage' },
74
+ render: Template
75
+ };
76
+
77
77
  export const WithTooltip: Story = {
78
78
  args: { TooltipContent, data, title: 'hosts' },
79
79
  render: Template
@@ -16,7 +16,7 @@ export const useBarStackStyles = makeStyles()((theme) => ({
16
16
  svgContainer: {
17
17
  alignItems: 'center',
18
18
  backgroundColor: theme.palette.background.panelGroups,
19
- borderRadius: theme.shape.borderRadius,
19
+ borderRadius: theme.spacing(1.25),
20
20
  display: 'flex',
21
21
  justifyContent: 'center'
22
22
  },
@@ -31,6 +31,7 @@ export const useBarStackStyles = makeStyles()((theme) => ({
31
31
  fontSize: theme.typography.h6.fontSize,
32
32
  fontWeight: theme.typography.fontWeightMedium,
33
33
  margin: 0,
34
- padding: 0
34
+ padding: 0,
35
+ textAlign: 'center'
35
36
  }
36
37
  }));
@@ -5,6 +5,7 @@ import { Group } from '@visx/group';
5
5
  import numeral from 'numeral';
6
6
  import { Text } from '@visx/text';
7
7
  import { useTranslation } from 'react-i18next';
8
+ import { equals } from 'ramda';
8
9
 
9
10
  import { Typography } from '@mui/material';
10
11
 
@@ -12,7 +13,7 @@ import { Tooltip } from '../../components';
12
13
  import { LegendProps } from '../Legend/models';
13
14
  import { Legend as LegendComponent } from '../Legend';
14
15
  import { getValueByUnit } from '../common/utils';
15
- import { labelNoDataFound } from '../translatedLabels';
16
+ import { labelNoDataFound as defaultlabelNoDataFound } from '../translatedLabels';
16
17
 
17
18
  import { BarStackProps } from './models';
18
19
  import { useBarStackStyles } from './BarStack.styles';
@@ -35,7 +36,8 @@ const BarStack = ({
35
36
  unit = 'number',
36
37
  displayValues,
37
38
  variant = 'vertical',
38
- legendDirection = 'column'
39
+ legendDirection = 'column',
40
+ labelNoDataFound = defaultlabelNoDataFound
39
41
  }: BarStackProps & { height: number; width: number }): JSX.Element => {
40
42
  const { t } = useTranslation();
41
43
  const { classes } = useBarStackStyles();
@@ -90,7 +92,7 @@ const BarStack = ({
90
92
  >
91
93
  {title && (
92
94
  <div className={classes.title} data-testid="Title" ref={titleRef}>
93
- {`${numeral(total).format('0a').toUpperCase()} `} {title}
95
+ {`${numeral(total).format('0a').toUpperCase()} `} {t(title)}
94
96
  </div>
95
97
  )}
96
98
  <div
@@ -117,6 +119,20 @@ const BarStack = ({
117
119
  {(barStacks) =>
118
120
  barStacks.map((barStack) =>
119
121
  barStack.bars.map((bar) => {
122
+ const shouldDisplayValues = (): boolean => {
123
+ if (bar.height < 10) {
124
+ return false;
125
+ }
126
+
127
+ return (
128
+ (equals(unit, 'number') && bar.width > 10) ||
129
+ (equals(unit, 'percentage') && bar.width > 25)
130
+ );
131
+ };
132
+
133
+ const TextX = bar.x + bar.width / 2;
134
+ const TextY = bar.y + bar.height / 2;
135
+
120
136
  const onClick = (): void => {
121
137
  onSingleBarClick?.(bar);
122
138
  };
@@ -144,40 +160,38 @@ const BarStack = ({
144
160
  isVerticalBar ? 'right-start' : 'bottom-start'
145
161
  }
146
162
  >
147
- <g data-testid={bar.key}>
163
+ <g data-testid={bar.key} onClick={onClick}>
148
164
  <rect
165
+ cursor="pointer"
149
166
  fill={bar.color}
150
167
  height={
151
168
  isVerticalBar ? bar.height - 1 : bar.height
152
169
  }
153
170
  key={`bar-stack-${barStack.index}-${bar.index}`}
154
- ry={5}
171
+ ry={10}
155
172
  width={isVerticalBar ? bar.width : bar.width - 1}
156
173
  x={bar.x}
157
174
  y={bar.y}
158
- onClick={onClick}
159
175
  />
160
- {displayValues &&
161
- bar.height > 10 &&
162
- bar.width > 10 && (
163
- <Text
164
- cursor="pointer"
165
- data-testid="value"
166
- fill="#000"
167
- fontSize={12}
168
- textAnchor="middle"
169
- verticalAnchor="middle"
170
- x={bar.x + bar.width / 2}
171
- y={bar.y + bar.height / 2}
172
- >
173
- {getValueByUnit({
174
- total,
175
- unit,
176
- value:
177
- barStack.bars[0].bar.data[barStack.key]
178
- })}
179
- </Text>
180
- )}
176
+ {displayValues && shouldDisplayValues() && (
177
+ <Text
178
+ cursor="pointer"
179
+ data-testid="value"
180
+ fill="#000"
181
+ fontSize={12}
182
+ fontWeight={600}
183
+ textAnchor="middle"
184
+ verticalAnchor="middle"
185
+ x={TextX}
186
+ y={TextY}
187
+ >
188
+ {getValueByUnit({
189
+ total,
190
+ unit,
191
+ value: barStack.bars[0].bar.data[barStack.key]
192
+ })}
193
+ </Text>
194
+ )}
181
195
  </g>
182
196
  </Tooltip>
183
197
  );
@@ -10,6 +10,7 @@ export type BarStackProps = {
10
10
  data: Array<BarType>;
11
11
  displayLegend?: boolean;
12
12
  displayValues?: boolean;
13
+ labelNoDataFound?: string;
13
14
  legendDirection?: 'row' | 'column';
14
15
  onSingleBarClick?: (barData) => void;
15
16
  size?: number;
@@ -55,7 +55,7 @@ const useResponsiveBarStack = ({
55
55
  const verticalGap = heightOfTitle > 0 ? 8 : 0;
56
56
 
57
57
  const svgWrapperWidth = isVerticalBar
58
- ? size + 24
58
+ ? size + 36
59
59
  : width - widthOfLegend - horizontalGap;
60
60
 
61
61
  const svgContainerSize = {
@@ -72,8 +72,7 @@ const useResponsiveBarStack = ({
72
72
 
73
73
  const yScale = isVerticalBar
74
74
  ? scaleLinear({
75
- domain: [0, total],
76
- nice: true
75
+ domain: [0, total]
77
76
  })
78
77
  : scaleBand({
79
78
  domain: [0, 0],
@@ -86,8 +85,7 @@ const useResponsiveBarStack = ({
86
85
  padding: 0
87
86
  })
88
87
  : scaleLinear({
89
- domain: [0, total],
90
- nice: true
88
+ domain: [0, total]
91
89
  });
92
90
 
93
91
  const keys = pluck('label', data);
@@ -13,7 +13,7 @@ import { Tooltip } from '../../components';
13
13
  import { Legend as LegendComponent } from '../Legend';
14
14
  import { LegendProps } from '../Legend/models';
15
15
  import { getValueByUnit } from '../common/utils';
16
- import { labelNoDataFound } from '../translatedLabels';
16
+ import { labelNoDataFound as defaultlabelNoDataFound } from '../translatedLabels';
17
17
 
18
18
  import { PieProps } from './models';
19
19
  import { usePieStyles } from './PieChart.styles';
@@ -54,7 +54,8 @@ const ResponsivePie = ({
54
54
  onArcClick,
55
55
  displayValues,
56
56
  TooltipContent,
57
- legendDirection = 'column'
57
+ legendDirection = 'column',
58
+ labelNoDataFound = defaultlabelNoDataFound
58
59
  }: PieProps & { height: number; width: number }): JSX.Element => {
59
60
  const { t } = useTranslation();
60
61
  const theme = useTheme();
@@ -109,7 +110,7 @@ const ResponsivePie = ({
109
110
  >
110
111
  {equals(variant, 'pie') && title && (
111
112
  <div className={classes.title} data-testid="Title" ref={titleRef}>
112
- {`${numeral(total).format('0a').toUpperCase()} `} {title}
113
+ {`${numeral(total).format('0a').toUpperCase()} `} {t(title)}
113
114
  </div>
114
115
  )}
115
116
  <div
@@ -178,6 +179,7 @@ const ResponsivePie = ({
178
179
  >
179
180
  <g data-testid={arc.data.label} onClick={onClick}>
180
181
  <path
182
+ cursor="pointer"
181
183
  d={pie.path(arc) as string}
182
184
  fill={arc.data.color}
183
185
  />
@@ -189,6 +191,7 @@ const ResponsivePie = ({
189
191
  dy=".33em"
190
192
  fill="#000"
191
193
  fontSize={12}
194
+ fontWeight={600}
192
195
  pointerEvents="none"
193
196
  textAnchor="middle"
194
197
  x={x}
@@ -224,7 +227,7 @@ const ResponsivePie = ({
224
227
  fill={theme.palette.text.primary}
225
228
  textAnchor="middle"
226
229
  >
227
- {title}
230
+ {t(title)}
228
231
  </Text>
229
232
  </>
230
233
  )}
@@ -11,6 +11,7 @@ export interface PieProps {
11
11
  displayLegend?: boolean;
12
12
  displayValues?: boolean;
13
13
  innerRadius?: number;
14
+ labelNoDataFound?: string;
14
15
  legendDirection?: 'row' | 'column';
15
16
  onArcClick?: (ardata) => void;
16
17
  title?: string;
@@ -41,12 +41,11 @@ export const useResponsivePie = ({
41
41
  const horizontalGap = widthOfLegend > 0 ? 16 : 0;
42
42
  const verticalGap = heightOfTitle > 0 ? 8 : 0;
43
43
 
44
- const svgWrapperWidth = width - widthOfLegend - horizontalGap;
45
-
46
44
  const svgContainerSize = Math.min(
47
45
  height - heightOfTitle - verticalGap,
48
46
  width - widthOfLegend - horizontalGap
49
47
  );
48
+ const svgWrapperWidth = svgContainerSize;
50
49
 
51
50
  const outerRadius = Math.min(32, svgContainerSize / 6);
52
51
 
@@ -1,3 +1,5 @@
1
+ import { useState } from 'react';
2
+
1
3
  import { Button, Typography } from '@mui/material';
2
4
 
3
5
  import { ListingVariant } from '@centreon/ui-context';
@@ -20,20 +22,20 @@ const LargeText = (): JSX.Element => (
20
22
  </Typography>
21
23
  );
22
24
 
23
- const generateSubItems = (parentIndex: number): Array<unknown> => {
25
+ const tenElements = new Array(10).fill(0);
26
+
27
+ const generateSubItems = (): Array<unknown> => {
24
28
  return tenElements.map((__, subIndex) => ({
25
29
  active: false,
26
- description: `Sub item ${subIndex + (parentIndex + 10) * 10} description`,
30
+ description: `Sub item ${subIndex * 10} description`,
27
31
  disableCheckbox: false,
28
32
  disableRow: false,
29
- id: subIndex + (parentIndex + 10) * 10,
30
- name: `Sub Item ${subIndex + (parentIndex + 10) * 10}`,
33
+ id: subIndex * 10,
34
+ name: `Sub Item ${subIndex * 10}`,
31
35
  selected: false
32
36
  }));
33
37
  };
34
38
 
35
- const tenElements = new Array(10).fill(0);
36
-
37
39
  const listingWithSubItems = tenElements.map((_, index) => ({
38
40
  active: false,
39
41
  description: `Entity ${index}`,
@@ -42,9 +44,22 @@ const listingWithSubItems = tenElements.map((_, index) => ({
42
44
  id: index,
43
45
  name: `E${index}`,
44
46
  selected: false,
45
- subItems: index % 2 === 0 ? generateSubItems(index) : undefined
47
+ subItems: index % 2 === 0 ? generateSubItems() : undefined
46
48
  }));
47
49
 
50
+ const listingWithSubItems3Rows = Array(3)
51
+ .fill(0)
52
+ .map((_, index) => ({
53
+ active: false,
54
+ description: `Entity ${index}`,
55
+ disableCheckbox: false,
56
+ disableRow: false,
57
+ id: index,
58
+ name: `E${index}`,
59
+ selected: false,
60
+ subItems: index % 2 === 0 ? generateSubItems() : undefined
61
+ }));
62
+
48
63
  const defaultColumn = [
49
64
  {
50
65
  getFormattedString: ({ name }): string => name,
@@ -112,26 +127,50 @@ const mountListingResponsive = (listingVariant: ListingVariant): void => {
112
127
  });
113
128
  };
114
129
 
115
- const mountListingForSubItems = (): void => {
130
+ interface TestComponentProps {
131
+ canCheckSubItems?: boolean;
132
+ isSmallListing?: boolean;
133
+ }
134
+
135
+ const TestComponent = ({
136
+ isSmallListing = false,
137
+ canCheckSubItems = false
138
+ }: TestComponentProps): JSX.Element => {
139
+ const [selectedRows, setSelectedRows] = useState([]);
140
+
141
+ return (
142
+ <Listing
143
+ checkable
144
+ columns={columnsWithSubItems}
145
+ currentPage={1}
146
+ limit={10}
147
+ rows={isSmallListing ? listingWithSubItems3Rows : listingWithSubItems}
148
+ selectedRows={selectedRows}
149
+ subItems={{
150
+ canCheckSubItems,
151
+ enable: true,
152
+ getRowProperty: () => 'subItems',
153
+ labelCollapse: 'Collapse',
154
+ labelExpand: 'Expand'
155
+ }}
156
+ totalRows={10}
157
+ onSelectRows={setSelectedRows}
158
+ />
159
+ );
160
+ };
161
+
162
+ const mountListingForSubItems = ({
163
+ isSmallListing = false,
164
+ canCheckSubItems = false
165
+ }: TestComponentProps): void => {
116
166
  cy.viewport('macbook-13');
117
167
 
118
168
  cy.mount({
119
169
  Component: (
120
170
  <div style={{ height: '100vh' }}>
121
- <Listing
122
- checkable
123
- columns={columnsWithSubItems}
124
- currentPage={1}
125
- limit={10}
126
- rows={listingWithSubItems}
127
- subItems={{
128
- canCheckSubItems: false,
129
- enable: true,
130
- getRowProperty: () => 'subItems',
131
- labelCollapse: 'Collapse',
132
- labelExpand: 'Expand'
133
- }}
134
- totalRows={10}
171
+ <TestComponent
172
+ canCheckSubItems={canCheckSubItems}
173
+ isSmallListing={isSmallListing}
135
174
  />
136
175
  </div>
137
176
  )
@@ -139,37 +178,107 @@ const mountListingForSubItems = (): void => {
139
178
  };
140
179
 
141
180
  describe('Listing', () => {
142
- it('expands the row when the corresponding icon si clicked', () => {
143
- mountListingForSubItems();
181
+ describe('Sub items', () => {
182
+ it('expands the row when the corresponding icon is clicked', () => {
183
+ mountListingForSubItems({});
184
+
185
+ cy.contains('E0').should('be.visible');
144
186
 
145
- cy.contains('E0').should('be.visible');
187
+ expandedItems.forEach((index) => {
188
+ const subItems = generateSubItems(index);
146
189
 
147
- expandedItems.forEach((index) => {
148
- const subItems = generateSubItems(index);
190
+ cy.findByLabelText(`Expand ${index}`).click();
149
191
 
150
- cy.findByLabelText(`Expand ${index}`).click();
192
+ cy.findByLabelText(`Collapse ${index}`).should('exist');
151
193
 
152
- cy.findByLabelText(`Collapse ${index}`).should('exist');
194
+ subItems.forEach(({ name, description }) => {
195
+ cy.contains(name).should('exist');
196
+ cy.contains(description).should('exist');
197
+ });
153
198
 
154
- subItems.forEach(({ name, description }) => {
155
- cy.contains(name).should('exist');
156
- cy.contains(description).should('exist');
199
+ cy.findByLabelText(`Collapse ${index}`).click();
157
200
  });
201
+
202
+ cy.makeSnapshot();
158
203
  });
159
204
 
160
- cy.makeSnapshot();
161
- });
205
+ it('collapses the row when the corresponding icon is clicked', () => {
206
+ mountListingForSubItems({});
162
207
 
163
- it('collapses the row when the corresponding icon si clicked', () => {
164
- mountListingForSubItems();
208
+ cy.findByLabelText(`Expand 0`).click();
165
209
 
166
- cy.contains('Sub item 100').should('be.visible');
210
+ cy.contains('Sub item 10').should('be.visible');
167
211
 
168
- cy.findByLabelText('Collapse 0').click();
212
+ cy.findByLabelText('Collapse 0').click();
169
213
 
170
- cy.contains('Sub item 100').should('not.exist');
214
+ cy.contains('Sub item 10').should('not.exist');
171
215
 
172
- cy.makeSnapshot();
216
+ cy.makeSnapshot();
217
+ });
218
+
219
+ it('displays only one row as hovered when mutilple rows are expanded and a sub item row is hovered', () => {
220
+ mountListingForSubItems({ isSmallListing: true });
221
+
222
+ cy.findByLabelText('Expand 2').click();
223
+ cy.findByLabelText('Expand 0').click();
224
+
225
+ cy.findByLabelText('Collapse 0').should('be.visible');
226
+ cy.findByLabelText('Collapse 2').should('be.visible');
227
+
228
+ cy.contains('Sub Item 0').realHover();
229
+
230
+ cy.get('[data-isHovered="true"]').should('have.length', 1);
231
+ cy.get('[data-isHovered="true"]').contains('Sub Item 0').should('exist');
232
+
233
+ cy.findByLabelText('Collapse 0').click();
234
+ cy.findByLabelText('Collapse 2').click();
235
+ });
236
+
237
+ it('selects displayed rows when a row is selected and another row is selected with the shift key', () => {
238
+ mountListingForSubItems({ canCheckSubItems: true, isSmallListing: true });
239
+
240
+ cy.findByLabelText('Expand 0').click();
241
+
242
+ cy.findByLabelText('Collapse 0').should('be.visible');
243
+
244
+ cy.findAllByLabelText('Select row 0').eq(0).click();
245
+ cy.get('body').type('{shift}', { release: false });
246
+ cy.findByLabelText('Select row 50').eq(0).click();
247
+
248
+ cy.findAllByLabelText('Select row 0').eq(0).should('be.checked');
249
+ cy.findAllByLabelText('Select row 0').eq(1).should('be.checked');
250
+ cy.findByLabelText('Select row 10').should('be.checked');
251
+ cy.findByLabelText('Select row 20').should('be.checked');
252
+ cy.findByLabelText('Select row 30').should('be.checked');
253
+ cy.findByLabelText('Select row 40').should('be.checked');
254
+ cy.findByLabelText('Select row 50').should('be.checked');
255
+
256
+ cy.findByLabelText('Collapse 0').click();
257
+ });
258
+
259
+ it('selects displayed rows when the corresponding checkbox is clicked', () => {
260
+ mountListingForSubItems({ canCheckSubItems: true, isSmallListing: true });
261
+
262
+ cy.findByLabelText('Expand 0').click();
263
+
264
+ cy.findByLabelText('Collapse 0').should('be.visible');
265
+
266
+ cy.findAllByLabelText('Select all').eq(0).click();
267
+
268
+ cy.findAllByLabelText('Select row 0').eq(0).should('be.checked');
269
+ tenElements.forEach((_, index) => {
270
+ if (index === 0) {
271
+ cy.findAllByLabelText('Select row 0').eq(1).should('be.checked');
272
+
273
+ return;
274
+ }
275
+ cy.findByLabelText(`Select row ${index * 10}`).should('be.checked');
276
+ });
277
+ cy.findByLabelText('Select row 1').should('be.checked');
278
+ cy.findByLabelText('Select row 2').should('be.checked');
279
+
280
+ cy.findByLabelText('Collapse 0').click();
281
+ });
173
282
  });
174
283
 
175
284
  it('displays the last column on several lines in compact mode when the isResponsive prop is set', () => {
@@ -197,7 +197,7 @@ const Row = memo<RowProps>(
197
197
  }
198
198
  );
199
199
 
200
- const IntersectionRow = (props: Props): JSX.Element => {
200
+ const IntersectionRow = ({ isHovered, ...rest }: Props): JSX.Element => {
201
201
  const rowRef = useRef<HTMLDivElement | null>(null);
202
202
  const theme = useTheme();
203
203
  const { isInViewport, setElement } = useViewportIntersection({
@@ -214,8 +214,12 @@ const IntersectionRow = (props: Props): JSX.Element => {
214
214
  }, [getFirstCellElement()]);
215
215
 
216
216
  return (
217
- <div className={classes.intersectionRow} ref={rowRef}>
218
- <Row {...props} isInViewport={isInViewport} />
217
+ <div
218
+ className={classes.intersectionRow}
219
+ data-isHovered={isHovered}
220
+ ref={rowRef}
221
+ >
222
+ <Row {...rest} isHovered={isHovered} isInViewport={isInViewport} />
219
223
  </div>
220
224
  );
221
225
  };
@@ -18,7 +18,6 @@ import {
18
18
  map,
19
19
  not,
20
20
  pick,
21
- pluck,
22
21
  prop,
23
22
  propEq,
24
23
  reduce,
@@ -58,6 +57,8 @@ import { SkeletonLoader } from './Row/SkeletonLoaderRows';
58
57
  import { ListingHeader } from './Header';
59
58
  import { subItemsPivotsAtom } from './tableAtoms';
60
59
 
60
+ const subItemPrefixKey = 'listing';
61
+
61
62
  const getVisibleColumns = ({
62
63
  columnConfiguration,
63
64
  columns
@@ -140,7 +141,7 @@ const defaultColumnConfiguration = {
140
141
 
141
142
  export const performanceRowsLimit = 60;
142
143
 
143
- const Listing = <TRow extends { id: RowId }>({
144
+ const Listing = <TRow extends { id: RowId; internalListingParentId?: RowId }>({
144
145
  customListingComponent,
145
146
  displayCustomListing,
146
147
  limit = 10,
@@ -211,6 +212,21 @@ const Listing = <TRow extends { id: RowId }>({
211
212
 
212
213
  const subItemsPivots = useAtomValue(subItemsPivotsAtom);
213
214
 
215
+ const allSubItemIds = React.useMemo(
216
+ () =>
217
+ reduce<TRow | number, Array<string | number>>(
218
+ (acc, row) => [
219
+ ...acc,
220
+ ...(row[subItems?.getRowProperty() || ''] || []).map(
221
+ ({ id }) => `${subItemPrefixKey}_${getId(row)}_${id}`
222
+ )
223
+ ],
224
+ [],
225
+ rows
226
+ ),
227
+ [rows, subItems]
228
+ );
229
+
214
230
  const rowsToDisplay = React.useMemo(
215
231
  () =>
216
232
  subItems?.enable
@@ -220,7 +236,14 @@ const Listing = <TRow extends { id: RowId }>({
220
236
  row[subItems.getRowProperty()] &&
221
237
  subItemsPivots.includes(row.id)
222
238
  ) {
223
- return [...acc, row, ...row[subItems.getRowProperty()]];
239
+ return [
240
+ ...acc,
241
+ row,
242
+ ...row[subItems.getRowProperty()].map((subRow) => ({
243
+ ...subRow,
244
+ internalListingParentId: row.id
245
+ }))
246
+ ];
224
247
  }
225
248
 
226
249
  return [...acc, row];
@@ -232,6 +255,24 @@ const Listing = <TRow extends { id: RowId }>({
232
255
  [rows, subItemsPivots, subItems]
233
256
  );
234
257
 
258
+ const getSubItemRowId = React.useCallback((row: TRow) => {
259
+ return `${subItemPrefixKey}_${row.internalListingParentId}_${row.id}`;
260
+ }, []);
261
+
262
+ const getIsSubItem = React.useCallback(
263
+ (row: TRow) => {
264
+ return allSubItemIds.includes(getSubItemRowId(row));
265
+ },
266
+ [allSubItemIds]
267
+ );
268
+
269
+ const getRowId = React.useCallback(
270
+ (row: TRow) => {
271
+ return getIsSubItem(row) ? getSubItemRowId(row) : getId(row);
272
+ },
273
+ [allSubItemIds]
274
+ );
275
+
235
276
  const { classes } = useListingStyles({
236
277
  dataStyle,
237
278
  getGridTemplateColumn,
@@ -255,7 +296,7 @@ const Listing = <TRow extends { id: RowId }>({
255
296
  event.target.checked &&
256
297
  event.target.getAttribute('data-indeterminate') === 'false'
257
298
  ) {
258
- onSelectRows(reject(disableRowCheckCondition, rows));
299
+ onSelectRows(reject(disableRowCheckCondition, rowsToDisplay));
259
300
  setLastSelectionIndex(null);
260
301
 
261
302
  return;
@@ -327,7 +368,11 @@ const Listing = <TRow extends { id: RowId }>({
327
368
  const selectRowsWithShiftKey = (selectedRowIndex: number): void => {
328
369
  const lastSelectedIndex = lastSelectionIndex as number;
329
370
  if (isNil(shiftKeyDownRowPivot)) {
330
- const selectedRowsFromTheStart = slice(0, selectedRowIndex + 1, rows);
371
+ const selectedRowsFromTheStart = slice(
372
+ 0,
373
+ selectedRowIndex + 1,
374
+ rowsToDisplay
375
+ );
331
376
 
332
377
  onSelectRows(reject(disableRowCheckCondition, selectedRowsFromTheStart));
333
378
 
@@ -336,7 +381,10 @@ const Listing = <TRow extends { id: RowId }>({
336
381
 
337
382
  const selectedRowsIndex = map(
338
383
  (row) =>
339
- findIndex((listingRow) => equals(getId(row), getId(listingRow)), rows),
384
+ findIndex(
385
+ (listingRow) => equals(getId(row), getId(listingRow)),
386
+ rowsToDisplay
387
+ ),
340
388
  selectedRows
341
389
  ).sort(subtract);
342
390
 
@@ -344,7 +392,7 @@ const Listing = <TRow extends { id: RowId }>({
344
392
  const newSelection = slice(
345
393
  selectedRowIndex,
346
394
  (lastSelectionIndex as number) + 1,
347
- rows
395
+ rowsToDisplay
348
396
  );
349
397
  onSelectRows(
350
398
  reject(
@@ -363,7 +411,11 @@ const Listing = <TRow extends { id: RowId }>({
363
411
  return;
364
412
  }
365
413
 
366
- const newSelection = slice(lastSelectedIndex, selectedRowIndex + 1, rows);
414
+ const newSelection = slice(
415
+ lastSelectedIndex,
416
+ selectedRowIndex + 1,
417
+ rowsToDisplay
418
+ );
367
419
  onSelectRows(
368
420
  reject(
369
421
  disableRowCheckCondition,
@@ -387,7 +439,7 @@ const Listing = <TRow extends { id: RowId }>({
387
439
 
388
440
  const selectedRowIndex = findIndex(
389
441
  (listingRow) => equals(getId(row), getId(listingRow)),
390
- rows
442
+ rowsToDisplay
391
443
  );
392
444
 
393
445
  if (isShiftKeyDown) {
@@ -414,10 +466,10 @@ const Listing = <TRow extends { id: RowId }>({
414
466
  };
415
467
 
416
468
  const hoverRow = (row): void => {
417
- if (equals(hoveredRowId, getId(row))) {
469
+ if (equals(hoveredRowId, getRowId(row))) {
418
470
  return;
419
471
  }
420
- setHoveredRowId(getId(row));
472
+ setHoveredRowId(getRowId(row));
421
473
  };
422
474
 
423
475
  const clearHoveredRow = (): void => {
@@ -428,8 +480,6 @@ const Listing = <TRow extends { id: RowId }>({
428
480
  return selectedRowsInclude(row);
429
481
  };
430
482
 
431
- const emptyRows = limit - Math.min(limit, totalRows - currentPage * limit);
432
-
433
483
  const changeLimit = (updatedLimit: string): void => {
434
484
  onLimitChange?.(Number(updatedLimit));
435
485
  };
@@ -450,19 +500,6 @@ const Listing = <TRow extends { id: RowId }>({
450
500
 
451
501
  const areColumnsEditable = not(isNil(onSelectColumns));
452
502
 
453
- const allSubItemIds = React.useMemo(
454
- () =>
455
- reduce<TRow | number, Array<string | number>>(
456
- (acc, row) => [
457
- ...acc,
458
- ...pluck('id', row[subItems?.getRowProperty() || ''] || [])
459
- ],
460
- [],
461
- rows
462
- ),
463
- [rows, subItems]
464
- );
465
-
466
503
  return (
467
504
  <div className={classes.listingContainer}>
468
505
  {loading && rows.length > 0 && (
@@ -534,7 +571,7 @@ const Listing = <TRow extends { id: RowId }>({
534
571
  listingVariant={listingVariant}
535
572
  memoProps={headerMemoProps}
536
573
  predefinedRowsSelection={predefinedRowsSelection}
537
- rowCount={limit - emptyRows}
574
+ rowCount={rowsToDisplay.length}
538
575
  selectedRowCount={selectedRows.length}
539
576
  sortField={sortField}
540
577
  sortOrder={sortOrder}
@@ -551,8 +588,10 @@ const Listing = <TRow extends { id: RowId }>({
551
588
  >
552
589
  {rowsToDisplay.map((row, index) => {
553
590
  const isRowSelected = isSelected(row);
554
- const isRowHovered = equals(hoveredRowId, getId(row));
555
- const isSubItem = allSubItemIds.includes(row.id);
591
+ const isSubItem = allSubItemIds.includes(
592
+ getSubItemRowId(row)
593
+ );
594
+ const isRowHovered = equals(hoveredRowId, getRowId(row));
556
595
 
557
596
  return (
558
597
  <ListingRow
@@ -569,7 +608,7 @@ const Listing = <TRow extends { id: RowId }>({
569
608
  key={
570
609
  gte(limit, performanceRowsLimit)
571
610
  ? `row_${index}`
572
- : getId(row)
611
+ : getRowId(row)
573
612
  }
574
613
  lastSelectionIndex={lastSelectionIndex}
575
614
  limit={limit}
@@ -580,9 +619,13 @@ const Listing = <TRow extends { id: RowId }>({
580
619
  subItemsPivots={subItemsPivots}
581
620
  tabIndex={-1}
582
621
  visibleColumns={visibleColumns}
583
- onClick={(): void => {
584
- onRowClick(row);
585
- }}
622
+ onClick={
623
+ isSubItem
624
+ ? undefined
625
+ : (): void => {
626
+ onRowClick(row);
627
+ }
628
+ }
586
629
  onFocus={(): void => hoverRow(row)}
587
630
  onMouseOver={(): void => hoverRow(row)}
588
631
  >
@@ -53,6 +53,7 @@ const useStyleTable = ({
53
53
  const checkbox = checkable ? 'fit-content(1rem) ' : ''; // SelectAction (checkbox) cell adjusts to content
54
54
 
55
55
  const columnTemplate = currentVisibleColumns
56
+ ?.filter((column) => column)
56
57
  ?.map(({ width, shortLabel }) => {
57
58
  if (!isNil(shortLabel)) {
58
59
  return 'min-content';