@centreon/ui 26.7.1 → 26.7.3
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 +1 -1
- package/src/Dashboard/DashboardLayout.stories.tsx +13 -11
- package/src/InputField/Select/Autocomplete/Connected/index.stories.tsx +1 -0
- package/src/InputField/Select/Autocomplete/Connected/index.test.tsx +110 -109
- package/src/InputField/Text/index.stories.tsx +7 -1
- package/src/Listing/ActionBar/Pagination.tsx +2 -1
- package/src/Listing/ActionBar/index.tsx +102 -14
- package/src/Listing/index.tsx +19 -2
- package/src/Listing/translatedLabels.ts +4 -0
- package/src/api/customFetch.ts +7 -2
- package/src/api/useFetchQuery/index.test.ts +6 -1
- package/src/index.ts +2 -1
package/package.json
CHANGED
|
@@ -72,17 +72,19 @@ const DashboardTemplate = ({
|
|
|
72
72
|
header,
|
|
73
73
|
layout = dashboardLayout
|
|
74
74
|
}: DashboardTemplateProps): ReactElement => (
|
|
75
|
-
<
|
|
76
|
-
{layout
|
|
77
|
-
|
|
78
|
-
{
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
75
|
+
<div className="h-150 overflow-y-auto">
|
|
76
|
+
<DashboardLayout.Layout<CustomLayout> layout={layout}>
|
|
77
|
+
{layout.map(({ i, content, shouldUseFluidTypography }) => (
|
|
78
|
+
<DashboardLayout.Item header={header} id={i} key={i}>
|
|
79
|
+
{shouldUseFluidTypography ? (
|
|
80
|
+
<FluidTypography text={content} />
|
|
81
|
+
) : (
|
|
82
|
+
<Typography>{content}</Typography>
|
|
83
|
+
)}
|
|
84
|
+
</DashboardLayout.Item>
|
|
85
|
+
))}
|
|
86
|
+
</DashboardLayout.Layout>
|
|
87
|
+
</div>
|
|
86
88
|
);
|
|
87
89
|
|
|
88
90
|
const meta: Meta<typeof DashboardTemplate> = {
|
|
@@ -36,6 +36,7 @@ const getEndpoint = ({ endpoint, parameters }): string =>
|
|
|
36
36
|
|
|
37
37
|
const mockSearch = (page: number): object => ({
|
|
38
38
|
delay: 1000,
|
|
39
|
+
headers: { 'Content-Type': 'application/json' },
|
|
39
40
|
method: 'GET',
|
|
40
41
|
response: (request): Listing<SelectEntry> => {
|
|
41
42
|
const { searchParams } = request;
|
|
@@ -1,129 +1,130 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} from
|
|
11
|
-
import buildListingEndpoint from
|
|
2
|
+
act,
|
|
3
|
+
fireEvent,
|
|
4
|
+
getFetchCall,
|
|
5
|
+
mockResponse,
|
|
6
|
+
type RenderResult,
|
|
7
|
+
render,
|
|
8
|
+
resetMocks,
|
|
9
|
+
waitFor
|
|
10
|
+
} from '../../../../../test/testRenderer';
|
|
11
|
+
import buildListingEndpoint from '../../../../api/buildListingEndpoint';
|
|
12
12
|
import type {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from
|
|
16
|
-
import TestQueryProvider from
|
|
13
|
+
BuildListingEndpointParameters,
|
|
14
|
+
ConditionsSearchParameter
|
|
15
|
+
} from '../../../../api/buildListingEndpoint/models';
|
|
16
|
+
import TestQueryProvider from '../../../../api/TestQueryProvider';
|
|
17
|
+
import SingleConnectedAutocompleteField from './Single';
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const label = "Connected Autocomplete";
|
|
21
|
-
const placeholder = "Type here...";
|
|
19
|
+
const label = 'Connected Autocomplete';
|
|
20
|
+
const placeholder = 'Type here...';
|
|
22
21
|
|
|
23
22
|
const optionsData = {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
meta: {
|
|
24
|
+
limit: 2,
|
|
25
|
+
page: 1,
|
|
26
|
+
total: 20
|
|
27
|
+
},
|
|
28
|
+
result: [
|
|
29
|
+
{ id: 0, name: 'My Option 1' },
|
|
30
|
+
{ id: 1, name: 'My Option 2' }
|
|
31
|
+
]
|
|
33
32
|
};
|
|
34
33
|
|
|
35
|
-
const baseEndpoint =
|
|
34
|
+
const baseEndpoint = 'endpoint';
|
|
36
35
|
|
|
37
36
|
const getEndpoint = (
|
|
38
|
-
|
|
37
|
+
parameters: BuildListingEndpointParameters['parameters']
|
|
39
38
|
): string => {
|
|
40
|
-
|
|
39
|
+
return buildListingEndpoint({ baseEndpoint, parameters });
|
|
41
40
|
};
|
|
42
41
|
|
|
43
42
|
interface Props {
|
|
44
|
-
|
|
43
|
+
searchConditions?: Array<ConditionsSearchParameter>;
|
|
45
44
|
}
|
|
46
45
|
|
|
46
|
+
const mockParams = { headers: { 'Content-Type': 'application/json' } };
|
|
47
|
+
|
|
47
48
|
const renderSingleConnectedAutocompleteField = (
|
|
48
|
-
|
|
49
|
+
{ searchConditions }: Props = { searchConditions: undefined }
|
|
49
50
|
): RenderResult =>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
51
|
+
render(
|
|
52
|
+
<TestQueryProvider>
|
|
53
|
+
<SingleConnectedAutocompleteField
|
|
54
|
+
baseEndpoint=""
|
|
55
|
+
field="host.name"
|
|
56
|
+
getEndpoint={getEndpoint}
|
|
57
|
+
label={label}
|
|
58
|
+
placeholder="Type here..."
|
|
59
|
+
searchConditions={searchConditions}
|
|
60
|
+
/>
|
|
61
|
+
</TestQueryProvider>
|
|
62
|
+
);
|
|
62
63
|
|
|
63
64
|
describe(SingleConnectedAutocompleteField, () => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
resetMocks();
|
|
67
|
+
mockResponse({ data: optionsData, options: mockParams });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('populates options with the first page result from the endpoint request', async () => {
|
|
71
|
+
const { getByLabelText, getByText } =
|
|
72
|
+
renderSingleConnectedAutocompleteField();
|
|
73
|
+
|
|
74
|
+
fireEvent.click(getByLabelText('Open'));
|
|
75
|
+
|
|
76
|
+
expect(getFetchCall(0)).toEqual(`${baseEndpoint}?page=1`);
|
|
77
|
+
|
|
78
|
+
await waitFor(() => {
|
|
79
|
+
expect(getByText('My Option 1')).toBeInTheDocument();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('populates options with the first page result from the endpoint request after typing something in input field', async () => {
|
|
84
|
+
const { getByLabelText, getByPlaceholderText } =
|
|
85
|
+
renderSingleConnectedAutocompleteField();
|
|
86
|
+
|
|
87
|
+
act(() => {
|
|
88
|
+
fireEvent.click(getByLabelText('Open'));
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
await waitFor(() => {
|
|
92
|
+
expect(getFetchCall(0)).toEqual(`${baseEndpoint}?page=1`);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
await waitFor(() => {
|
|
96
|
+
expect(getFetchCall(1)).toEqual(`${baseEndpoint}?page=2`);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
fireEvent.change(getByPlaceholderText(placeholder), {
|
|
100
|
+
target: { value: 'My Option 2' }
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await waitFor(() => {
|
|
104
|
+
expect(decodeURIComponent(getFetchCall(2) as string)).toEqual(
|
|
105
|
+
'endpoint?page=1&search={"$and":[{"$and":[{"host.name":{"$lk":"%My Option 2%"}}]}]}'
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('adds search conditions to the endpoint request when the corresponding prop is passed', async () => {
|
|
111
|
+
const { getByLabelText } = renderSingleConnectedAutocompleteField({
|
|
112
|
+
searchConditions: [
|
|
113
|
+
{
|
|
114
|
+
field: 'parent_name',
|
|
115
|
+
value: {
|
|
116
|
+
$eq: 'Centreon-Server'
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
fireEvent.click(getByLabelText('Open'));
|
|
123
|
+
|
|
124
|
+
await waitFor(() => {
|
|
125
|
+
expect(getFetchCall(0)).toEqual(
|
|
126
|
+
`${baseEndpoint}?page=1&search=%7B%22%24and%22%3A%5B%7B%22%24or%22%3A%5B%7B%22parent_name%22%3A%7B%22%24eq%22%3A%22Centreon-Server%22%7D%7D%5D%7D%5D%7D`
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
129
130
|
});
|
|
@@ -104,7 +104,13 @@ export const autoSizeWithEndAdornment = (): JSX.Element => (
|
|
|
104
104
|
autoSize
|
|
105
105
|
autoSizeCustomPadding={10}
|
|
106
106
|
autoSizeDefaultWidth={60}
|
|
107
|
-
EndAdornment={AbcIcon}
|
|
108
107
|
placeholder="Auto size"
|
|
108
|
+
textFieldSlotsAndSlotProps={{
|
|
109
|
+
slotProps: {
|
|
110
|
+
input: {
|
|
111
|
+
endAdornment: <AbcIcon />
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}}
|
|
109
115
|
/>
|
|
110
116
|
);
|
|
@@ -23,7 +23,8 @@ const MemoizedPagination = memo(
|
|
|
23
23
|
equals(prevProps.page, nextProps.page) &&
|
|
24
24
|
equals(prevProps.count, nextProps.count) &&
|
|
25
25
|
equals(prevProps.labelRowsPerPage, nextProps.labelRowsPerPage) &&
|
|
26
|
-
equals(prevProps.className, nextProps.className)
|
|
26
|
+
equals(prevProps.className, nextProps.className) &&
|
|
27
|
+
prevProps.labelDisplayedRows === nextProps.labelDisplayedRows
|
|
27
28
|
);
|
|
28
29
|
|
|
29
30
|
export default MemoizedPagination;
|
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
|
2
2
|
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
|
|
3
|
+
import CircularProgress from '@mui/material/CircularProgress';
|
|
3
4
|
import Divider from '@mui/material/Divider';
|
|
5
|
+
import Tooltip from '@mui/material/Tooltip';
|
|
4
6
|
|
|
5
7
|
import { ListingVariant, userAtom } from '@centreon/ui-context';
|
|
6
8
|
|
|
7
9
|
import { useAtomValue } from 'jotai';
|
|
8
10
|
import { equals, isEmpty, isNil, not, pick } from 'ramda';
|
|
11
|
+
import {
|
|
12
|
+
type ChangeEvent,
|
|
13
|
+
type MouseEvent,
|
|
14
|
+
type ReactNode,
|
|
15
|
+
useCallback
|
|
16
|
+
} from 'react';
|
|
9
17
|
import { useTranslation } from 'react-i18next';
|
|
10
18
|
import { makeStyles } from 'tss-react/mui';
|
|
11
19
|
|
|
12
20
|
import { IconButton, type ListingProps } from '../..';
|
|
13
21
|
import { useMemoComponent } from '../../utils';
|
|
14
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
labelApproximateCount,
|
|
24
|
+
labelApproximateCountTooltip,
|
|
25
|
+
labelComputingExactCount,
|
|
26
|
+
labelOf,
|
|
27
|
+
labelRowsPerPage
|
|
28
|
+
} from '../translatedLabels';
|
|
15
29
|
import ColumnMultiSelect from './ColumnMultiSelect';
|
|
16
30
|
import StyledPagination from './Pagination';
|
|
17
31
|
import PaginationActions from './PaginationActions';
|
|
@@ -87,6 +101,9 @@ type Props = Pick<
|
|
|
87
101
|
| 'customPaginationClassName'
|
|
88
102
|
| 'listingVariant'
|
|
89
103
|
| 'viewerModeConfiguration'
|
|
104
|
+
| 'approximateTotalRows'
|
|
105
|
+
| 'onApproximateCountClick'
|
|
106
|
+
| 'isApproximateCountLoading'
|
|
90
107
|
>;
|
|
91
108
|
|
|
92
109
|
const MemoListingActionBar = ({
|
|
@@ -106,7 +123,10 @@ const MemoListingActionBar = ({
|
|
|
106
123
|
widthToMoveTablePagination = 550,
|
|
107
124
|
actionsBarMemoProps = [],
|
|
108
125
|
viewerModeConfiguration,
|
|
109
|
-
listingVariant
|
|
126
|
+
listingVariant,
|
|
127
|
+
approximateTotalRows = false,
|
|
128
|
+
onApproximateCountClick,
|
|
129
|
+
isApproximateCountLoading = false
|
|
110
130
|
}: Props): JSX.Element => {
|
|
111
131
|
const marginWidthTableListing = 30;
|
|
112
132
|
const { classes, cx } = useStyles({
|
|
@@ -118,28 +138,88 @@ const MemoListingActionBar = ({
|
|
|
118
138
|
const { themeMode } = useAtomValue(userAtom);
|
|
119
139
|
|
|
120
140
|
const changeRowPerPage = (
|
|
121
|
-
event:
|
|
141
|
+
event: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>
|
|
122
142
|
): void => {
|
|
123
143
|
onLimitChange?.(event.target.value);
|
|
124
144
|
onPaginate?.(0);
|
|
125
145
|
};
|
|
126
146
|
|
|
127
147
|
const changePage = (
|
|
128
|
-
_:
|
|
148
|
+
_: MouseEvent<HTMLButtonElement> | null,
|
|
129
149
|
value: number
|
|
130
150
|
): void => {
|
|
131
151
|
onPaginate?.(value);
|
|
132
152
|
};
|
|
133
153
|
|
|
134
|
-
const labelDisplayedRows = (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
154
|
+
const labelDisplayedRows = useCallback(
|
|
155
|
+
({
|
|
156
|
+
from,
|
|
157
|
+
to,
|
|
158
|
+
count
|
|
159
|
+
}: {
|
|
160
|
+
count: number;
|
|
161
|
+
from: number;
|
|
162
|
+
to: number;
|
|
163
|
+
}): ReactNode => {
|
|
164
|
+
const range = `${from}-${to} ${t(labelOf)} `;
|
|
165
|
+
|
|
166
|
+
if (!approximateTotalRows) {
|
|
167
|
+
return `${range}${count}`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (isApproximateCountLoading) {
|
|
171
|
+
return (
|
|
172
|
+
<span
|
|
173
|
+
style={{ alignItems: 'center', display: 'inline-flex', gap: 4 }}
|
|
174
|
+
>
|
|
175
|
+
{range}
|
|
176
|
+
<CircularProgress size={10} />
|
|
177
|
+
<span>{t(labelComputingExactCount)}</span>
|
|
178
|
+
</span>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const approximateLabel = (
|
|
183
|
+
<button
|
|
184
|
+
onClick={onApproximateCountClick}
|
|
185
|
+
style={{
|
|
186
|
+
background: 'none',
|
|
187
|
+
border: 'none',
|
|
188
|
+
color: 'inherit',
|
|
189
|
+
cursor: onApproximateCountClick ? 'pointer' : 'default',
|
|
190
|
+
font: 'inherit',
|
|
191
|
+
fontWeight: 600,
|
|
192
|
+
padding: 0,
|
|
193
|
+
textDecoration: onApproximateCountClick
|
|
194
|
+
? 'underline dotted'
|
|
195
|
+
: 'none'
|
|
196
|
+
}}
|
|
197
|
+
type="button"
|
|
198
|
+
>
|
|
199
|
+
{t(labelApproximateCount)}
|
|
200
|
+
</button>
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<span>
|
|
205
|
+
{range}
|
|
206
|
+
{onApproximateCountClick ? (
|
|
207
|
+
<Tooltip title={t(labelApproximateCountTooltip)}>
|
|
208
|
+
{approximateLabel}
|
|
209
|
+
</Tooltip>
|
|
210
|
+
) : (
|
|
211
|
+
approximateLabel
|
|
212
|
+
)}
|
|
213
|
+
</span>
|
|
214
|
+
);
|
|
215
|
+
},
|
|
216
|
+
[
|
|
217
|
+
approximateTotalRows,
|
|
218
|
+
isApproximateCountLoading,
|
|
219
|
+
onApproximateCountClick,
|
|
220
|
+
t
|
|
221
|
+
]
|
|
222
|
+
);
|
|
143
223
|
|
|
144
224
|
return useMemoComponent({
|
|
145
225
|
Component: (
|
|
@@ -233,6 +313,8 @@ const MemoListingActionBar = ({
|
|
|
233
313
|
),
|
|
234
314
|
columnConfiguration,
|
|
235
315
|
customPaginationClassName,
|
|
316
|
+
approximateTotalRows,
|
|
317
|
+
isApproximateCountLoading,
|
|
236
318
|
...actionsBarMemoProps
|
|
237
319
|
]
|
|
238
320
|
});
|
|
@@ -255,7 +337,10 @@ const ListingActionBar = ({
|
|
|
255
337
|
widthToMoveTablePagination,
|
|
256
338
|
customPaginationClassName,
|
|
257
339
|
listingVariant,
|
|
258
|
-
viewerModeConfiguration
|
|
340
|
+
viewerModeConfiguration,
|
|
341
|
+
approximateTotalRows,
|
|
342
|
+
onApproximateCountClick,
|
|
343
|
+
isApproximateCountLoading
|
|
259
344
|
}: Props): JSX.Element | null => {
|
|
260
345
|
if (
|
|
261
346
|
not(paginated) &&
|
|
@@ -269,13 +354,16 @@ const ListingActionBar = ({
|
|
|
269
354
|
<MemoListingActionBar
|
|
270
355
|
actions={actions}
|
|
271
356
|
actionsBarMemoProps={actionsBarMemoProps}
|
|
357
|
+
approximateTotalRows={approximateTotalRows}
|
|
272
358
|
columnConfiguration={columnConfiguration}
|
|
273
359
|
columns={columns}
|
|
274
360
|
currentPage={currentPage}
|
|
275
361
|
customPaginationClassName={customPaginationClassName}
|
|
362
|
+
isApproximateCountLoading={isApproximateCountLoading}
|
|
276
363
|
limit={limit}
|
|
277
364
|
listingVariant={listingVariant}
|
|
278
365
|
moveTablePagination={moveTablePagination}
|
|
366
|
+
onApproximateCountClick={onApproximateCountClick}
|
|
279
367
|
onLimitChange={onLimitChange}
|
|
280
368
|
onPaginate={onPaginate}
|
|
281
369
|
onResetColumns={onResetColumns}
|
package/src/Listing/index.tsx
CHANGED
|
@@ -131,6 +131,9 @@ export interface Props<TRow> {
|
|
|
131
131
|
labelExpand: string;
|
|
132
132
|
};
|
|
133
133
|
totalRows?: number;
|
|
134
|
+
approximateTotalRows?: boolean;
|
|
135
|
+
onApproximateCountClick?: () => void;
|
|
136
|
+
isApproximateCountLoading?: boolean;
|
|
134
137
|
viewerModeConfiguration?: ViewerModeConfiguration;
|
|
135
138
|
widthToMoveTablePagination?: number;
|
|
136
139
|
isActionBarVisible?: boolean;
|
|
@@ -193,7 +196,10 @@ const Listing = <
|
|
|
193
196
|
labelExpand: 'Expand'
|
|
194
197
|
},
|
|
195
198
|
isActionBarVisible = true,
|
|
196
|
-
labelNoResultFound = defaultLabelNoResultFound
|
|
199
|
+
labelNoResultFound = defaultLabelNoResultFound,
|
|
200
|
+
approximateTotalRows = false,
|
|
201
|
+
onApproximateCountClick,
|
|
202
|
+
isApproximateCountLoading = false
|
|
197
203
|
}: Props<TRow>): JSX.Element => {
|
|
198
204
|
const currentVisibleColumns = getVisibleColumns({
|
|
199
205
|
columnConfiguration,
|
|
@@ -537,13 +543,16 @@ const Listing = <
|
|
|
537
543
|
<ListingActionBar
|
|
538
544
|
actions={actions}
|
|
539
545
|
actionsBarMemoProps={actionsBarMemoProps}
|
|
546
|
+
approximateTotalRows={approximateTotalRows}
|
|
540
547
|
columnConfiguration={columnConfiguration}
|
|
541
548
|
columns={columns}
|
|
542
549
|
currentPage={currentPage}
|
|
543
550
|
customPaginationClassName={customPaginationClassName}
|
|
551
|
+
isApproximateCountLoading={isApproximateCountLoading}
|
|
544
552
|
limit={limit}
|
|
545
553
|
listingVariant={listingVariant}
|
|
546
554
|
moveTablePagination={moveTablePagination}
|
|
555
|
+
onApproximateCountClick={onApproximateCountClick}
|
|
547
556
|
onLimitChange={changeLimit}
|
|
548
557
|
onPaginate={onPaginate}
|
|
549
558
|
onResetColumns={onResetColumns}
|
|
@@ -756,21 +765,27 @@ export const MemoizedListing = <TRow extends { id: string | number }>({
|
|
|
756
765
|
widthToMoveTablePagination,
|
|
757
766
|
listingVariant,
|
|
758
767
|
labelNoResultFound,
|
|
768
|
+
approximateTotalRows,
|
|
769
|
+
onApproximateCountClick,
|
|
770
|
+
isApproximateCountLoading,
|
|
759
771
|
...props
|
|
760
772
|
}: MemoizedListingProps<TRow>): JSX.Element =>
|
|
761
773
|
useMemoComponent({
|
|
762
774
|
Component: (
|
|
763
775
|
<Listing
|
|
776
|
+
approximateTotalRows={approximateTotalRows}
|
|
764
777
|
checkable={checkable}
|
|
765
778
|
columnConfiguration={columnConfiguration}
|
|
766
779
|
columns={columns}
|
|
767
780
|
currentPage={currentPage}
|
|
768
781
|
innerScrollDisabled={innerScrollDisabled}
|
|
782
|
+
isApproximateCountLoading={isApproximateCountLoading}
|
|
769
783
|
labelNoResultFound={labelNoResultFound}
|
|
770
784
|
limit={limit}
|
|
771
785
|
listingVariant={listingVariant}
|
|
772
786
|
loading={loading}
|
|
773
787
|
moveTablePagination={moveTablePagination}
|
|
788
|
+
onApproximateCountClick={onApproximateCountClick}
|
|
774
789
|
paginated={paginated}
|
|
775
790
|
rowColorConditions={rowColorConditions}
|
|
776
791
|
rows={rows}
|
|
@@ -801,7 +816,9 @@ export const MemoizedListing = <TRow extends { id: string | number }>({
|
|
|
801
816
|
sortField,
|
|
802
817
|
innerScrollDisabled,
|
|
803
818
|
listingVariant,
|
|
804
|
-
labelNoResultFound
|
|
819
|
+
labelNoResultFound,
|
|
820
|
+
approximateTotalRows,
|
|
821
|
+
isApproximateCountLoading
|
|
805
822
|
]
|
|
806
823
|
});
|
|
807
824
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export const labelTo = 'to';
|
|
2
2
|
export const labelNoResultFound = 'No result found';
|
|
3
3
|
export const labelOf = 'of';
|
|
4
|
+
export const labelApproximateCount = '1,000+';
|
|
5
|
+
export const labelApproximateCountTooltip =
|
|
6
|
+
'More than 1,000 resources match your search. Click to continue.';
|
|
7
|
+
export const labelComputingExactCount = 'Loading...';
|
|
4
8
|
export const labelRowsPerPage = 'Rows per page';
|
|
5
9
|
export const labelFirstPage = 'First page';
|
|
6
10
|
export const labelLastPage = 'Last page';
|
package/src/api/customFetch.ts
CHANGED
|
@@ -74,8 +74,13 @@ export const customFetch = <T>({
|
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
.
|
|
77
|
+
const responseParsingMethod = response.headers
|
|
78
|
+
.get('Content-Type')
|
|
79
|
+
?.startsWith('text')
|
|
80
|
+
? 'text'
|
|
81
|
+
: 'json';
|
|
82
|
+
|
|
83
|
+
return response[responseParsingMethod]()
|
|
79
84
|
.then((data) => {
|
|
80
85
|
if (!response.ok) {
|
|
81
86
|
const defaultError = {
|
|
@@ -37,6 +37,8 @@ const renderFetchQuery = <T extends object>(
|
|
|
37
37
|
wrapper: TestQueryProvider
|
|
38
38
|
}) as RenderHookResult<UseFetchQueryState<T>, unknown>;
|
|
39
39
|
|
|
40
|
+
const mockParams = { headers: { 'Content-Type': 'application/json' } };
|
|
41
|
+
|
|
40
42
|
describe('useFetchQuery', () => {
|
|
41
43
|
beforeEach(() => {
|
|
42
44
|
mockedShowErrorMessage.mockReset();
|
|
@@ -57,7 +59,7 @@ describe('useFetchQuery', () => {
|
|
|
57
59
|
});
|
|
58
60
|
|
|
59
61
|
it('retrieves data from an endpoint', async () => {
|
|
60
|
-
fetchMock.once(JSON.stringify(user));
|
|
62
|
+
fetchMock.once(JSON.stringify(user), mockParams);
|
|
61
63
|
const { result } = renderFetchQuery<User>({
|
|
62
64
|
getEndpoint: () => '/endpoint',
|
|
63
65
|
getQueryKey: () => ['queryKey']
|
|
@@ -70,6 +72,7 @@ describe('useFetchQuery', () => {
|
|
|
70
72
|
|
|
71
73
|
it("shows an error from the API via the Snackbar and inside the browser's console", async () => {
|
|
72
74
|
fetchMock.once(JSON.stringify({ code: 2, message: 'custom message' }), {
|
|
75
|
+
...mockParams,
|
|
73
76
|
status: 400
|
|
74
77
|
});
|
|
75
78
|
|
|
@@ -109,6 +112,7 @@ describe('useFetchQuery', () => {
|
|
|
109
112
|
|
|
110
113
|
it('shows a default failure message via the Snackbar as fallback', async () => {
|
|
111
114
|
fetchMock.once(JSON.stringify({}), {
|
|
115
|
+
...mockParams,
|
|
112
116
|
status: 400
|
|
113
117
|
});
|
|
114
118
|
|
|
@@ -134,6 +138,7 @@ describe('useFetchQuery', () => {
|
|
|
134
138
|
|
|
135
139
|
it('does not show any message via the Snackbar when the httpCodesBypassErrorSnackbar is passed', async () => {
|
|
136
140
|
fetchMock.once(JSON.stringify({}), {
|
|
141
|
+
...mockParams,
|
|
137
142
|
status: 400
|
|
138
143
|
});
|
|
139
144
|
|
package/src/index.ts
CHANGED
|
@@ -31,7 +31,8 @@ export type { CatchErrorProps, ResponseError } from './api/customFetch';
|
|
|
31
31
|
export { customFetch } from './api/customFetch';
|
|
32
32
|
export type {
|
|
33
33
|
Listing as ListingModel,
|
|
34
|
-
ListingMap as ListingMapModel
|
|
34
|
+
ListingMap as ListingMapModel,
|
|
35
|
+
ListingMeta
|
|
35
36
|
} from './api/models';
|
|
36
37
|
export { client, default as QueryProvider } from './api/QueryProvider';
|
|
37
38
|
export { default as TestQueryProvider } from './api/TestQueryProvider';
|