@centreon/ui 24.4.2 → 24.4.4

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.2",
3
+ "version": "24.4.4",
4
4
  "description": "Centreon UI Components",
5
5
  "scripts": {
6
6
  "eslint": "eslint ./src --ext .js,.jsx,.ts,.tsx --max-warnings 0",
@@ -7,6 +7,7 @@ export const useHeatMapStyles = makeStyles()((theme) => ({
7
7
  heatMapTile: {
8
8
  alignItems: 'center',
9
9
  aspectRatio: '1 / 1',
10
+ borderRadius: theme.shape.borderRadius,
10
11
  display: 'flex',
11
12
  justifyContent: 'center',
12
13
  width: '100%'
@@ -1,7 +1,7 @@
1
1
  import { useMemo } from 'react';
2
2
 
3
3
  import { scaleLinear } from '@visx/scale';
4
- import { equals, gt, lt } from 'ramda';
4
+ import { T, equals, gt, lt } from 'ramda';
5
5
 
6
6
  import { Box } from '@mui/material';
7
7
 
@@ -20,7 +20,8 @@ const ResponsiveHeatMap = <TData,>({
20
20
  tiles,
21
21
  arrowClassName,
22
22
  tooltipContent,
23
- tileSizeFixed
23
+ tileSizeFixed,
24
+ displayTooltipCondition = T
24
25
  }: HeatMapProps<TData> & { width: number }): JSX.Element | null => {
25
26
  const { classes, cx } = useHeatMapStyles();
26
27
 
@@ -73,12 +74,15 @@ const ResponsiveHeatMap = <TData,>({
73
74
  tooltip: classes.heatMapTooltip
74
75
  }}
75
76
  followCursor={false}
76
- label={tooltipContent?.({
77
- backgroundColor,
78
- data,
79
- id,
80
- isSmallestSize
81
- })}
77
+ label={
78
+ displayTooltipCondition?.(data) &&
79
+ tooltipContent?.({
80
+ backgroundColor,
81
+ data,
82
+ id,
83
+ isSmallestSize
84
+ })
85
+ }
82
86
  position="right-start"
83
87
  >
84
88
  <div className={classes.heatMapTileContent}>
@@ -21,6 +21,7 @@ export interface HeatMapProps<TData> {
21
21
  data,
22
22
  isSmallestSize
23
23
  }: ChildrenProps<TData>) => ReactElement | boolean | null;
24
+ displayTooltipCondition?: (data: TData) => boolean;
24
25
  tileSizeFixed?: boolean;
25
26
  tiles: Array<Tile<TData>>;
26
27
  tooltipContent?: ({
@@ -1,6 +1,6 @@
1
1
  import { Dispatch, SetStateAction, useEffect } from 'react';
2
2
 
3
- import { compose, equals, prop, propEq, reject, sortBy, toLower } from 'ramda';
3
+ import { equals, propEq, reject } from 'ramda';
4
4
 
5
5
  import { Line } from '../../common/timeSeries/models';
6
6
 
@@ -51,9 +51,7 @@ const useFilterLines = ({
51
51
  return;
52
52
  }
53
53
 
54
- const sortedLines = sortBy(compose(toLower, prop('name')), filteredLines);
55
-
56
- setLinesGraph(sortedLines);
54
+ setLinesGraph(filteredLines);
57
55
  }, [lines, displayThreshold]);
58
56
 
59
57
  return { displayedLines, newLines: linesGraph ?? lines };
@@ -1,19 +1,19 @@
1
1
  import { Typography } from '@mui/material';
2
2
 
3
- import { useStyles } from './Legend.styles';
3
+ import { useLegendValueStyles } from './Legend.styles';
4
4
 
5
5
  interface Props {
6
6
  value?: string | null;
7
7
  }
8
8
 
9
9
  const InteractiveValue = ({ value }: Props): JSX.Element | null => {
10
- const { classes } = useStyles({});
10
+ const { classes } = useLegendValueStyles({});
11
11
  if (!value) {
12
12
  return null;
13
13
  }
14
14
 
15
15
  return (
16
- <Typography className={classes.legendValue} variant="h6">
16
+ <Typography className={classes.text} variant="h6">
17
17
  {value}
18
18
  </Typography>
19
19
  );
@@ -6,66 +6,112 @@ interface MakeStylesProps {
6
6
  limitLegendRows?: boolean;
7
7
  }
8
8
 
9
+ export const legendWidth = 21;
10
+ const legendItemHeight = 5.25;
11
+ const legendItemHeightCompact = 1.75;
12
+
9
13
  export const useStyles = makeStyles<MakeStylesProps>()(
10
14
  (theme, { limitLegendRows }) => ({
11
15
  highlight: {
12
16
  color: theme.typography.body1.color
13
17
  },
14
18
  item: {
15
- display: 'grid',
16
- gridTemplateColumns: 'min-content minmax(50px, 1fr)',
17
- marginBottom: theme.spacing(1)
19
+ minWidth: theme.spacing(legendWidth)
18
20
  },
19
21
  items: {
22
+ '&[data-mode="compact"]': {
23
+ gridAutoRows: theme.spacing(legendItemHeightCompact),
24
+ height: limitLegendRows
25
+ ? theme.spacing(legendItemHeightCompact * 2 + 1.5)
26
+ : 'unset'
27
+ },
28
+ columnGap: theme.spacing(3),
20
29
  display: 'grid',
21
- gridTemplateColumns: 'repeat(auto-fit, minmax(150px, max-content))',
22
- marginLeft: theme.spacing(0.5),
23
- maxHeight: limitLegendRows ? theme.spacing(14) : 'unset',
30
+ gridAutoRows: theme.spacing(legendItemHeight),
31
+ gridTemplateColumns: `repeat(auto-fit, ${theme.spacing(legendWidth)})`,
32
+ maxHeight: limitLegendRows
33
+ ? theme.spacing(legendItemHeight * 2 + 1)
34
+ : 'unset',
24
35
  overflowY: 'auto',
36
+ rowGap: theme.spacing(1),
25
37
  width: '100%'
26
38
  },
27
39
  legend: {
28
40
  marginLeft: margin.left,
29
41
  marginRight: margin.right,
30
- maxHeight: theme.spacing(24),
31
- overflowX: 'hidden',
32
- overflowY: 'auto'
42
+ overflow: 'hidden'
33
43
  },
34
- legendData: {
35
- display: 'flex',
36
- flexDirection: 'column'
44
+ minMaxAvgContainer: {
45
+ columnGap: theme.spacing(0.5),
46
+ display: 'grid',
47
+ gridTemplateColumns: 'repeat(2, min-content)',
48
+ whiteSpace: 'nowrap'
37
49
  },
38
- legendName: {
50
+ normal: {
51
+ color: theme.palette.text.primary
52
+ },
53
+ toggable: {
54
+ cursor: 'pointer'
55
+ }
56
+ })
57
+ );
58
+
59
+ interface StylesProps {
60
+ color?: string;
61
+ }
62
+
63
+ export const useLegendHeaderStyles = makeStyles<StylesProps>()(
64
+ (theme, { color }) => ({
65
+ container: {
39
66
  display: 'flex',
40
67
  flexDirection: 'row',
41
- justifyContent: 'start',
42
- marginRight: theme.spacing(0.5),
43
- overflow: 'hidden',
44
- textOverflow: 'ellipsis'
68
+ gap: theme.spacing(0.5),
69
+ width: '100%'
45
70
  },
46
- legendUnit: {
47
- justifyContent: 'start',
48
- marginLeft: 'auto',
49
- marginRight: theme.spacing(0.5),
50
- overflow: 'hidden',
51
- textOverflow: 'ellipsis'
71
+ disabled: {
72
+ color: theme.palette.text.disabled
52
73
  },
53
- legendValue: {
54
- fontWeight: theme.typography.body1.fontWeight
74
+ icon: {
75
+ backgroundColor: color,
76
+ borderRadius: theme.shape.borderRadius,
77
+ height: theme.spacing(1.5),
78
+ minWidth: theme.spacing(1.5),
79
+ width: theme.spacing(1.5)
80
+ },
81
+ legendName: {
82
+ '&[data-mode="compact"]': {
83
+ maxWidth: theme.spacing(legendWidth * 0.5)
84
+ },
85
+ maxWidth: theme.spacing(legendWidth * 0.75)
86
+ },
87
+ markerAndLegendName: {
88
+ alignItems: 'center',
89
+ display: 'flex',
90
+ flexDirection: 'row',
91
+ gap: theme.spacing(0.5)
55
92
  },
56
93
  minMaxAvgContainer: {
57
94
  columnGap: theme.spacing(0.5),
58
95
  display: 'grid',
59
- gridAutoRows: theme.spacing(2),
60
96
  gridTemplateColumns: 'repeat(2, min-content)',
61
97
  whiteSpace: 'nowrap'
62
98
  },
63
- minMaxAvgValue: { fontWeight: 600 },
64
- normal: {
65
- color: theme.palette.text.primary
66
- },
67
- toggable: {
68
- cursor: 'pointer'
99
+ text: {
100
+ fontWeight: theme.typography.fontWeightMedium,
101
+ lineHeight: 1
69
102
  }
70
103
  })
71
104
  );
105
+
106
+ export const useLegendContentStyles = makeStyles()((theme) => ({
107
+ minMaxAvgValue: { fontWeight: theme.typography.fontWeightMedium },
108
+ text: {
109
+ lineHeight: 0.9
110
+ }
111
+ }));
112
+
113
+ export const useLegendValueStyles = makeStyles()({
114
+ text: {
115
+ lineHeight: 1.4
116
+ }
117
+ });
@@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next';
2
2
 
3
3
  import { Typography } from '@mui/material';
4
4
 
5
- import { useStyles } from './Legend.styles';
5
+ import { useLegendContentStyles } from './Legend.styles';
6
6
 
7
7
  interface Props {
8
8
  data: string;
@@ -10,15 +10,21 @@ interface Props {
10
10
  }
11
11
 
12
12
  const LegendContent = ({ data, label }: Props): JSX.Element => {
13
- const { classes } = useStyles({});
13
+ const { classes, cx } = useLegendContentStyles();
14
14
 
15
15
  const { t } = useTranslation();
16
16
 
17
17
  return (
18
- <div data-testid={label}>
19
- <Typography variant="caption">{t(label)}: </Typography>
20
- <Typography className={classes.minMaxAvgValue} variant="caption">
21
- {data}
18
+ <div className={classes.text} data-testid={label}>
19
+ <Typography className={classes.text} component="span" variant="caption">
20
+ {t(label)}:{' '}
21
+ <Typography
22
+ className={cx(classes.minMaxAvgValue, classes.text)}
23
+ component="span"
24
+ variant="caption"
25
+ >
26
+ {data}
27
+ </Typography>
22
28
  </Typography>
23
29
  </div>
24
30
  );
@@ -1,43 +1,91 @@
1
1
  import { includes, isEmpty, split } from 'ramda';
2
2
 
3
- import { Tooltip, Typography } from '@mui/material';
3
+ import { Typography } from '@mui/material';
4
4
 
5
+ import { EllipsisTypography, formatMetricValue } from '../../..';
5
6
  import { Line } from '../../common/timeSeries/models';
7
+ import { Tooltip } from '../../../components';
6
8
 
7
- import { useStyles } from './Legend.styles';
9
+ import { useLegendHeaderStyles } from './Legend.styles';
10
+ import { LegendDisplayMode } from './models';
11
+ import LegendContent from './LegendContent';
8
12
 
9
13
  interface Props {
14
+ color: string;
15
+ disabled?: boolean;
10
16
  line: Line;
17
+ minMaxAvg?;
18
+ value?: string | null;
11
19
  }
12
20
 
13
- const LegendHeader = ({ line }: Props): JSX.Element => {
14
- const { classes } = useStyles({});
21
+ const LegendHeader = ({
22
+ line,
23
+ color,
24
+ disabled,
25
+ value,
26
+ minMaxAvg
27
+ }: Props): JSX.Element => {
28
+ const { classes, cx } = useLegendHeaderStyles({ color });
29
+
15
30
  const { unit, name, legend } = line;
16
31
 
17
32
  const legendName = legend || name;
18
- const unitName = ` (${unit})`;
33
+ const hasUnit = !isEmpty(unit);
34
+ const unitName = `(${unit})`;
19
35
  const metricName = includes('#', legendName)
20
36
  ? split('#')(legendName)[1]
21
37
  : legendName;
22
38
 
39
+ const getEndText = (): string => {
40
+ if (value) {
41
+ return `${value}${hasUnit ? ` ${unit}` : ''}`;
42
+ }
43
+
44
+ return hasUnit ? ` ${unitName}` : '';
45
+ };
46
+
23
47
  return (
24
- <div className={classes.legendName}>
25
- <Tooltip placement="top" title={legendName + unitName}>
26
- <Typography
27
- className={classes.legendName}
28
- component="p"
29
- variant="caption"
30
- >
31
- {metricName}
32
- </Typography>
33
- </Tooltip>
34
- <Typography
35
- className={classes.legendUnit}
36
- component="p"
37
- variant="caption"
48
+ <div className={classes.container}>
49
+ <Tooltip
50
+ followCursor={false}
51
+ label={
52
+ minMaxAvg ? (
53
+ <div>
54
+ <Typography>{`${legendName} ${unitName}`}</Typography>
55
+ <div className={classes.minMaxAvgContainer}>
56
+ {minMaxAvg.map(({ label, value: subValue }) => (
57
+ <LegendContent
58
+ data={formatMetricValue({
59
+ unit: line.unit,
60
+ value: subValue
61
+ })}
62
+ key={label}
63
+ label={label}
64
+ />
65
+ ))}
66
+ </div>
67
+ </div>
68
+ ) : (
69
+ `${legendName} ${unitName}`
70
+ )
71
+ }
72
+ placement="top"
38
73
  >
39
- {!isEmpty(line?.unit) && `(${line.unit})`}
40
- </Typography>
74
+ <div className={classes.markerAndLegendName}>
75
+ <div className={cx(classes.icon, { [classes.disabled]: disabled })} />
76
+ <EllipsisTypography
77
+ className={cx(classes.text, classes.legendName)}
78
+ data-mode={
79
+ value ? LegendDisplayMode.Compact : LegendDisplayMode.Normal
80
+ }
81
+ >
82
+ {metricName}
83
+ </EllipsisTypography>
84
+ </div>
85
+ </Tooltip>
86
+ {hasUnit && (
87
+ <Typography className={classes.text}>{getEndText()}</Typography>
88
+ )}
41
89
  </div>
42
90
  );
43
91
  };
@@ -16,8 +16,7 @@ import { timeValueAtom } from '../InteractiveComponents/interactionWithGraphAtom
16
16
  import InteractiveValue from './InteractiveValue';
17
17
  import { useStyles } from './Legend.styles';
18
18
  import LegendHeader from './LegendHeader';
19
- import LegendMarker from './Marker';
20
- import { GetMetricValueProps } from './models';
19
+ import { GetMetricValueProps, LegendDisplayMode } from './models';
21
20
  import useInteractiveValues from './useInteractiveValues';
22
21
  import useLegend from './useLegend';
23
22
  import LegendContent from './LegendContent';
@@ -29,6 +28,7 @@ interface Props {
29
28
  lines: Array<Line>;
30
29
  renderExtraComponent?: ReactNode;
31
30
  setLinesGraph: Dispatch<SetStateAction<Array<Line> | null>>;
31
+ shouldDisplayLegendInCompactMode: boolean;
32
32
  timeSeries: Array<TimeValue>;
33
33
  toggable?: boolean;
34
34
  xScale;
@@ -43,7 +43,8 @@ const MainLegend = ({
43
43
  renderExtraComponent,
44
44
  displayAnchor = true,
45
45
  setLinesGraph,
46
- xScale
46
+ xScale,
47
+ shouldDisplayLegendInCompactMode
47
48
  }: Props): JSX.Element => {
48
49
  const { classes, cx } = useStyles({ limitLegendRows });
49
50
  const theme = useTheme();
@@ -85,9 +86,13 @@ const MainLegend = ({
85
86
  selectMetricLine(metric_id);
86
87
  };
87
88
 
89
+ const mode = shouldDisplayLegendInCompactMode
90
+ ? LegendDisplayMode.Compact
91
+ : LegendDisplayMode.Normal;
92
+
88
93
  return (
89
94
  <div className={classes.legend}>
90
- <div className={classes.items}>
95
+ <div className={classes.items} data-mode={mode}>
91
96
  {displayedLines.map((line) => {
92
97
  const { color, display, highlight, metric_id } = line;
93
98
 
@@ -126,22 +131,37 @@ const MainLegend = ({
126
131
  onMouseEnter={(): void => highlightLine(metric_id)}
127
132
  onMouseLeave={(): void => clearHighlight()}
128
133
  >
129
- <LegendMarker color={markerColor} disabled={!display} />
130
- <div className={classes.legendData}>
131
- <LegendHeader line={line} />
132
- {displayAnchor && <InteractiveValue value={interactiveValue} />}
133
- {!interactiveValue && (
134
- <div className={classes.minMaxAvgContainer}>
135
- {minMaxAvg.map(({ label, value }) => (
136
- <LegendContent
137
- data={getMetricValue({ unit: line.unit, value })}
138
- key={label}
139
- label={label}
140
- />
141
- ))}
142
- </div>
143
- )}
144
- </div>
134
+ <LegendHeader
135
+ color={markerColor}
136
+ disabled={!display}
137
+ line={line}
138
+ minMaxAvg={
139
+ shouldDisplayLegendInCompactMode ? minMaxAvg : undefined
140
+ }
141
+ value={
142
+ shouldDisplayLegendInCompactMode
143
+ ? interactiveValue
144
+ : undefined
145
+ }
146
+ />
147
+ {!shouldDisplayLegendInCompactMode && (
148
+ <div>
149
+ {displayAnchor && (
150
+ <InteractiveValue value={interactiveValue} />
151
+ )}
152
+ {!interactiveValue && (
153
+ <div className={classes.minMaxAvgContainer}>
154
+ {minMaxAvg.map(({ label, value }) => (
155
+ <LegendContent
156
+ data={getMetricValue({ unit: line.unit, value })}
157
+ key={label}
158
+ label={label}
159
+ />
160
+ ))}
161
+ </div>
162
+ )}
163
+ </div>
164
+ )}
145
165
  </Box>
146
166
  );
147
167
  })}
@@ -152,8 +172,15 @@ const MainLegend = ({
152
172
  };
153
173
 
154
174
  const Legend = (props: Props): JSX.Element => {
155
- const { toggable, limitLegendRows, timeSeries, lines, base, displayAnchor } =
156
- props;
175
+ const {
176
+ toggable,
177
+ limitLegendRows,
178
+ timeSeries,
179
+ lines,
180
+ base,
181
+ displayAnchor,
182
+ shouldDisplayLegendInCompactMode
183
+ } = props;
157
184
  const timeValue = useAtomValue(timeValueAtom);
158
185
 
159
186
  return useMemoComponent({
@@ -165,7 +192,8 @@ const Legend = (props: Props): JSX.Element => {
165
192
  base,
166
193
  toggable,
167
194
  limitLegendRows,
168
- displayAnchor
195
+ displayAnchor,
196
+ shouldDisplayLegendInCompactMode
169
197
  ]
170
198
  });
171
199
  };
@@ -9,3 +9,8 @@ export interface GetMetricValueProps {
9
9
  unit: string;
10
10
  value: number | null;
11
11
  }
12
+
13
+ export enum LegendDisplayMode {
14
+ Compact = 'compact',
15
+ Normal = 'normal'
16
+ }
@@ -32,20 +32,19 @@ const useLegend = ({ lines, setLinesGraph }: Props): LegendActions => {
32
32
  find(propEq(metric_id, 'metric_id'), lines) as Line;
33
33
 
34
34
  const toggleMetricLine = (metric_id): void => {
35
- const line = getLineByMetric(metric_id);
35
+ const data = lines.map((line) => ({
36
+ ...line,
37
+ display: equals(line.metric_id, metric_id) ? !line.display : line.display
38
+ }));
36
39
 
37
- setLinesGraph([
38
- ...reject(propEq(metric_id, 'metric_id'), lines),
39
- { ...line, display: !line.display }
40
- ]);
40
+ setLinesGraph(data);
41
41
  };
42
42
 
43
43
  const highlightLine = (metric_id): void => {
44
- const fadedLines = map((line) => ({ ...line, highlight: false }), lines);
45
- const data = [
46
- ...reject(propEq(metric_id, 'metric_id'), fadedLines),
47
- { ...getLineByMetric(metric_id), highlight: true }
48
- ];
44
+ const data = lines.map((line) => ({
45
+ ...line,
46
+ highlight: equals(line.metric_id, metric_id)
47
+ }));
49
48
 
50
49
  setLinesGraph(data);
51
50
  };
@@ -1,7 +1,7 @@
1
1
  import { MutableRefObject, useMemo, useRef, useState } from 'react';
2
2
 
3
3
  import { Group, Tooltip } from '@visx/visx';
4
- import { flatten, isNil, pluck } from 'ramda';
4
+ import { flatten, gt, isNil, lte, pluck, reduce } from 'ramda';
5
5
 
6
6
  import { ClickAwayListener, Fade, Skeleton, useTheme } from '@mui/material';
7
7
 
@@ -33,6 +33,7 @@ import {
33
33
  import { useIntersection } from './useLineChartIntersection';
34
34
  import { CurveType } from './BasicComponents/Lines/models';
35
35
  import Thresholds from './BasicComponents/Thresholds';
36
+ import { legendWidth } from './Legend/Legend.styles';
36
37
 
37
38
  const extraMargin = 10;
38
39
 
@@ -161,6 +162,15 @@ const LineChart = ({
161
162
  const displayLegend = legend?.display ?? true;
162
163
  const displayTooltip = !isNil(tooltip?.renderComponent);
163
164
 
165
+ const legendItemsWidth = reduce(
166
+ (acc) => acc + legendWidth * 8 + 24,
167
+ 0,
168
+ displayedLines
169
+ );
170
+
171
+ const shouldDisplayLegendInCompactMode =
172
+ lte(graphWidth, 808) && gt(legendItemsWidth, graphWidth);
173
+
164
174
  if (!isInViewport) {
165
175
  return (
166
176
  <Skeleton
@@ -294,6 +304,7 @@ const LineChart = ({
294
304
  lines={newLines}
295
305
  renderExtraComponent={legend?.renderExtraComponent}
296
306
  setLinesGraph={setLinesGraph}
307
+ shouldDisplayLegendInCompactMode={shouldDisplayLegendInCompactMode}
297
308
  timeSeries={timeSeries}
298
309
  xScale={xScale}
299
310
  />
@@ -17,6 +17,7 @@ const renderMultiAutocompleteField = (): RenderResult =>
17
17
  render(
18
18
  <TestQueryProvider>
19
19
  <MultiConnectedAutocompleteField
20
+ baseEndpoint=""
20
21
  field="host.name"
21
22
  getEndpoint={getEndpoint}
22
23
  label={label}
@@ -45,6 +45,7 @@ const renderSingleConnectedAutocompleteField = (
45
45
  render(
46
46
  <TestQueryProvider>
47
47
  <SingleConnectedAutocompleteField
48
+ baseEndpoint=""
48
49
  field="host.name"
49
50
  getEndpoint={getEndpoint}
50
51
  label={label}
@@ -32,6 +32,7 @@ import useFetchQuery from '../../../../api/useFetchQuery';
32
32
 
33
33
  export interface ConnectedAutoCompleteFieldProps<TData> {
34
34
  allowUniqOption?: boolean;
35
+ baseEndpoint?: string;
35
36
  conditionField?: keyof SelectEntry;
36
37
  field: string;
37
38
  getEndpoint: ({ search, page }) => string;
@@ -60,6 +61,7 @@ const ConnectedAutocompleteField = (
60
61
  displayOptionThumbnail,
61
62
  queryKey,
62
63
  allowUniqOption,
64
+ baseEndpoint,
63
65
  ...props
64
66
  }: ConnectedAutoCompleteFieldProps<TData> &
65
67
  Omit<AutocompleteFieldProps, 'options'>): JSX.Element => {
@@ -87,6 +89,7 @@ const ConnectedAutocompleteField = (
87
89
  const { fetchQuery, isFetching, prefetchNextPage } = useFetchQuery<
88
90
  ListingModel<TData>
89
91
  >({
92
+ baseEndpoint,
90
93
  fetchHeaders: getRequestHeaders,
91
94
  getEndpoint: (params) => {
92
95
  return getEndpoint({
@@ -80,6 +80,7 @@ export type Props = {
80
80
  autoSizeCustomPadding?: number;
81
81
  autoSizeDefaultWidth?: number;
82
82
  className?: string;
83
+ containerClassName?: string;
83
84
  dataTestId: string;
84
85
  debounced?: boolean;
85
86
  displayErrorInTooltip?: boolean;
@@ -112,6 +113,7 @@ const TextField = forwardRef(
112
113
  autoSizeCustomPadding,
113
114
  defaultValue,
114
115
  required = false,
116
+ containerClassName,
115
117
  ...rest
116
118
  }: Props,
117
119
  ref: React.ForwardedRef<HTMLDivElement>
@@ -142,7 +144,10 @@ const TextField = forwardRef(
142
144
  }, [innerValue, debounced, defaultValue]);
143
145
 
144
146
  return (
145
- <Box sx={{ width: autoSize ? 'auto' : '100%' }}>
147
+ <Box
148
+ className={containerClassName}
149
+ sx={{ width: autoSize ? 'auto' : '100%' }}
150
+ >
146
151
  <Tooltip placement="top" title={tooltipTitle}>
147
152
  <MuiTextField
148
153
  data-testid={dataTestId}
@@ -143,7 +143,7 @@ declare module '@mui/material/Badge' {
143
143
  export const lightPalette: PaletteOptions = {
144
144
  action: {
145
145
  acknowledged: '#67532C',
146
- acknowledgedBackground: '#F5F1E9',
146
+ acknowledgedBackground: '#DFD2B9',
147
147
  activatedOpacity: 0.12,
148
148
  active: '#666666',
149
149
  disabled: '#999999',
@@ -153,7 +153,7 @@ export const lightPalette: PaletteOptions = {
153
153
  hover: 'rgba(0, 0, 0, 0.06)',
154
154
  hoverOpacity: 0.06,
155
155
  inDowntime: '#4B2352',
156
- inDowntimeBackground: '#F0E9F8',
156
+ inDowntimeBackground: '#E5D8F3',
157
157
  selected: 'rgba(102, 102, 102, 0.3)',
158
158
  selectedOpacity: 0.3
159
159
  },
@@ -283,7 +283,7 @@ export const lightPalette: PaletteOptions = {
283
283
  export const darkPalette: PaletteOptions = {
284
284
  action: {
285
285
  acknowledged: '#67532C',
286
- acknowledgedBackground: '#67532C',
286
+ acknowledgedBackground: '#745F35',
287
287
  activatedOpacity: 0.3,
288
288
  active: '#B5B5B5',
289
289
  disabled: '#999999',
@@ -293,7 +293,7 @@ export const darkPalette: PaletteOptions = {
293
293
  hover: 'rgba(255, 255, 255, 0.16)',
294
294
  hoverOpacity: 0.16,
295
295
  inDowntime: '#4B2352',
296
- inDowntimeBackground: '#4B2352',
296
+ inDowntimeBackground: '#512980',
297
297
  selected: 'rgba(255, 255, 255, 0.5)',
298
298
  selectedOpacity: 0.5
299
299
  },
@@ -52,7 +52,8 @@ export type Operator =
52
52
  | '$lk'
53
53
  | '$nk'
54
54
  | '$in'
55
- | '$ni';
55
+ | '$ni'
56
+ | '$rg';
56
57
 
57
58
  export type ConditionValue = {
58
59
  [value in Operator]?: string | Array<string>;
@@ -1,4 +1,4 @@
1
- import { equals } from 'ramda';
1
+ import { equals, isNil, startsWith } from 'ramda';
2
2
  import { JsonDecoder } from 'ts.data.json';
3
3
 
4
4
  import { Method } from './useMutationQuery';
@@ -22,6 +22,7 @@ export interface CatchErrorProps {
22
22
  }
23
23
 
24
24
  interface CustomFetchProps<T> {
25
+ baseEndpoint?: string;
25
26
  catchError?: (props: CatchErrorProps) => void;
26
27
  decoder?: JsonDecoder.Decoder<T>;
27
28
  defaultFailureMessage?: string;
@@ -42,10 +43,18 @@ export const customFetch = <T>({
42
43
  defaultFailureMessage = 'Something went wrong',
43
44
  isMutation = false,
44
45
  payload,
45
- method = 'GET'
46
+ method = 'GET',
47
+ baseEndpoint = './api/latest'
46
48
  }: CustomFetchProps<T>): Promise<T | ResponseError> => {
47
49
  const defaultOptions = { headers, method, signal };
48
50
 
51
+ const formattedEndpoint =
52
+ !isNil(baseEndpoint) &&
53
+ !startsWith(baseEndpoint, endpoint) &&
54
+ !startsWith('./api/internal.php', endpoint)
55
+ ? `${baseEndpoint}${endpoint}`
56
+ : endpoint;
57
+
49
58
  const options = isMutation
50
59
  ? {
51
60
  ...defaultOptions,
@@ -53,7 +62,7 @@ export const customFetch = <T>({
53
62
  }
54
63
  : defaultOptions;
55
64
 
56
- return fetch(endpoint, options)
65
+ return fetch(formattedEndpoint, options)
57
66
  .then((response) => {
58
67
  if (equals(response.status, 204)) {
59
68
  return {
@@ -1,4 +1,4 @@
1
- import { useEffect, useMemo } from 'react';
1
+ import { useEffect, useMemo, useRef } from 'react';
2
2
 
3
3
  import 'ulog';
4
4
  import {
@@ -10,13 +10,14 @@ import {
10
10
  } from '@tanstack/react-query';
11
11
  import { JsonDecoder } from 'ts.data.json';
12
12
  import anylogger from 'anylogger';
13
- import { has, includes, not, omit } from 'ramda';
13
+ import { has, includes, isNil, not, omit } from 'ramda';
14
14
 
15
15
  import { CatchErrorProps, customFetch, ResponseError } from '../customFetch';
16
16
  import useSnackbar from '../../Snackbar/useSnackbar';
17
17
  import { useDeepCompare } from '../../utils';
18
18
 
19
19
  export interface UseFetchQueryProps<T> {
20
+ baseEndpoint?: string;
20
21
  catchError?: (props: CatchErrorProps) => void;
21
22
  decoder?: JsonDecoder.Decoder<T>;
22
23
  defaultFailureMessage?: string;
@@ -57,13 +58,17 @@ const useFetchQuery = <T extends object>({
57
58
  fetchHeaders,
58
59
  isPaginated,
59
60
  queryOptions,
60
- httpCodesBypassErrorSnackbar = []
61
+ httpCodesBypassErrorSnackbar = [],
62
+ baseEndpoint
61
63
  }: UseFetchQueryProps<T>): UseFetchQueryState<T> => {
64
+ const dataRef = useRef<T | undefined>(undefined);
65
+
62
66
  const { showErrorMessage } = useSnackbar();
63
67
 
64
68
  const queryData = useQuery<T | ResponseError, Error>({
65
69
  queryFn: ({ signal }): Promise<T | ResponseError> =>
66
70
  customFetch<T>({
71
+ baseEndpoint,
67
72
  catchError,
68
73
  decoder,
69
74
  defaultFailureMessage,
@@ -96,6 +101,7 @@ const useFetchQuery = <T extends object>({
96
101
  queryClient.prefetchQuery({
97
102
  queryFn: ({ signal }): Promise<T | ResponseError> =>
98
103
  customFetch<T>({
104
+ baseEndpoint,
99
105
  catchError,
100
106
  decoder,
101
107
  defaultFailureMessage,
@@ -137,6 +143,7 @@ const useFetchQuery = <T extends object>({
137
143
  return queryClient.fetchQuery({
138
144
  queryFn: ({ signal }): Promise<T | ResponseError> =>
139
145
  customFetch<T>({
146
+ baseEndpoint,
140
147
  catchError,
141
148
  decoder,
142
149
  defaultFailureMessage,
@@ -154,6 +161,10 @@ const useFetchQuery = <T extends object>({
154
161
  [queryData.data]
155
162
  );
156
163
 
164
+ if (!isNil(data)) {
165
+ dataRef.current = data;
166
+ }
167
+
157
168
  const errorData = queryData.data as ResponseError | undefined;
158
169
 
159
170
  useEffect(() => {
@@ -171,7 +182,7 @@ const useFetchQuery = <T extends object>({
171
182
 
172
183
  return {
173
184
  ...omit(['data', 'error'], queryData),
174
- data,
185
+ data: dataRef.current,
175
186
  error: errorData?.isError ? omit(['isError'], errorData) : null,
176
187
  fetchQuery,
177
188
  prefetchNextPage,
@@ -23,6 +23,7 @@ export enum Method {
23
23
  }
24
24
 
25
25
  export type UseMutationQueryProps<T, TMeta> = {
26
+ baseEndpoint?: string;
26
27
  catchError?: (props: CatchErrorProps) => void;
27
28
  decoder?: JsonDecoder.Decoder<T>;
28
29
  defaultFailureMessage?: string;
@@ -68,7 +69,8 @@ const useMutationQuery = <T extends object, TMeta>({
68
69
  method,
69
70
  onMutate,
70
71
  onError,
71
- onSuccess
72
+ onSuccess,
73
+ baseEndpoint
72
74
  }: UseMutationQueryProps<T, TMeta>): UseMutationQueryState<T> => {
73
75
  const { showErrorMessage } = useSnackbar();
74
76
 
@@ -83,6 +85,7 @@ const useMutationQuery = <T extends object, TMeta>({
83
85
  const { _meta, ...payload } = _payload || {};
84
86
 
85
87
  return customFetch<T>({
88
+ baseEndpoint,
86
89
  catchError,
87
90
  decoder,
88
91
  defaultFailureMessage,
@@ -5,33 +5,42 @@ import { PrimitiveAtom, useAtom } from 'jotai';
5
5
  import { JsonDecoder } from 'ts.data.json';
6
6
 
7
7
  import {
8
+ QueryParameter,
8
9
  buildListingEndpoint,
9
10
  useFetchQuery,
10
11
  useIntersectionObserver
11
12
  } from '@centreon/ui';
12
13
 
13
14
  import type { Listing } from '../api/models';
14
-
15
- const limit = 100;
15
+ import { Parameters } from '../api/buildListingEndpoint/models';
16
16
 
17
17
  interface UseInfiniteScrollListing<T> {
18
18
  elementRef: (node) => void;
19
19
  elements: Array<T>;
20
20
  isLoading: boolean;
21
+ total?: number;
21
22
  }
22
23
 
23
24
  interface UseInfiniteScrollListingProps<T> {
24
- decoder: JsonDecoder.Decoder<Listing<T>>;
25
+ customQueryParameters?: Array<QueryParameter>;
26
+ decoder?: JsonDecoder.Decoder<Listing<T>>;
25
27
  endpoint: string;
28
+ limit?: number;
26
29
  pageAtom: PrimitiveAtom<number>;
30
+ parameters?: Parameters;
27
31
  queryKeyName: string;
32
+ suspense?: boolean;
28
33
  }
29
34
 
30
35
  export const useInfiniteScrollListing = <T>({
31
36
  queryKeyName,
32
37
  endpoint,
33
38
  decoder,
34
- pageAtom
39
+ pageAtom,
40
+ suspense = true,
41
+ parameters,
42
+ customQueryParameters,
43
+ limit = 100
35
44
  }: UseInfiniteScrollListingProps<T>): UseInfiniteScrollListing<T> => {
36
45
  const [maxPage, setMaxPage] = useState(1);
37
46
 
@@ -46,14 +55,15 @@ export const useInfiniteScrollListing = <T>({
46
55
  getEndpoint: (params) =>
47
56
  buildListingEndpoint({
48
57
  baseEndpoint: endpoint,
49
- parameters: { limit, page: params?.page || page }
58
+ customQueryParameters,
59
+ parameters: { limit, page: params?.page || page, ...parameters }
50
60
  }),
51
61
  getQueryKey: () => [queryKeyName, page],
52
62
  isPaginated: true,
53
63
  queryOptions: {
54
64
  refetchOnMount: false,
55
65
  refetchOnWindowFocus: false,
56
- suspense: equals(page, 1)
66
+ suspense: suspense && equals(page, 1)
57
67
  }
58
68
  });
59
69
 
@@ -98,9 +108,14 @@ export const useInfiniteScrollListing = <T>({
98
108
  });
99
109
  }, [data]);
100
110
 
111
+ useEffect(() => {
112
+ return () => setPage(1);
113
+ }, []);
114
+
101
115
  return {
102
116
  elementRef,
103
117
  elements: elements.current || [],
104
- isLoading
118
+ isLoading,
119
+ total: data?.meta.total
105
120
  };
106
121
  };
@@ -1,6 +1,7 @@
1
1
  import dayjs from 'dayjs';
2
2
  import humanizeDuration from 'humanize-duration';
3
3
  import { useAtomValue } from 'jotai';
4
+ import localizedFormat from 'dayjs/plugin/localizedFormat';
4
5
 
5
6
  import { userAtom } from '@centreon/ui-context';
6
7
 
@@ -20,6 +21,8 @@ export interface LocaleDateTimeFormat {
20
21
  toTime: (date: Date | string) => string;
21
22
  }
22
23
 
24
+ dayjs.extend(localizedFormat);
25
+
23
26
  const dateFormat = 'L';
24
27
  const timeFormat = 'LT';
25
28
  const dateTimeFormat = `${dateFormat} ${timeFormat}`;
@@ -1,43 +0,0 @@
1
- import { equals } from 'ramda';
2
- import { makeStyles } from 'tss-react/mui';
3
-
4
- export enum LegendMarkerVariant {
5
- 'dot',
6
- 'bar'
7
- }
8
-
9
- interface StylesProps {
10
- color?: string;
11
- variant: LegendMarkerVariant;
12
- }
13
-
14
- const useStyles = makeStyles<StylesProps>()((theme, { color, variant }) => ({
15
- disabled: {
16
- color: theme.palette.text.disabled
17
- },
18
- icon: {
19
- backgroundColor: color,
20
- borderRadius: equals(LegendMarkerVariant.dot, variant) ? '50%' : 0,
21
- height: equals(LegendMarkerVariant.dot, variant) ? 9 : '100%',
22
- marginRight: theme.spacing(0.5),
23
- width: 9
24
- }
25
- }));
26
-
27
- interface Props {
28
- color: string;
29
- disabled?: boolean;
30
- variant?: LegendMarkerVariant;
31
- }
32
-
33
- const LegendMarker = ({
34
- disabled,
35
- color,
36
- variant = LegendMarkerVariant.bar
37
- }: Props): JSX.Element => {
38
- const { classes, cx } = useStyles({ color, variant });
39
-
40
- return <div className={cx(classes.icon, { [classes.disabled]: disabled })} />;
41
- };
42
-
43
- export default LegendMarker;