@centreon/ui 24.11.11 → 24.11.13

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.
Files changed (35) hide show
  1. package/package.json +1 -1
  2. package/src/Dashboard/Dashboard.styles.ts +1 -1
  3. package/src/Dashboard/Grid.tsx +2 -8
  4. package/src/Dashboard/Layout.tsx +40 -43
  5. package/src/Form/Inputs/Autocomplete.tsx +4 -2
  6. package/src/Form/Inputs/ConnectedAutocomplete.tsx +1 -1
  7. package/src/Form/Inputs/Radio.tsx +1 -1
  8. package/src/Form/Inputs/Text.tsx +1 -1
  9. package/src/Graph/BarChart/BarChart.cypress.spec.tsx +1 -1
  10. package/src/Graph/Chart/Chart.tsx +5 -2
  11. package/src/Graph/Chart/InteractiveComponents/index.tsx +1 -1
  12. package/src/Graph/Chart/index.tsx +1 -1
  13. package/src/Graph/Timeline/ResponsiveTimeline.tsx +147 -0
  14. package/src/Graph/Timeline/Timeline.cypress.spec.tsx +148 -0
  15. package/src/Graph/Timeline/Timeline.stories.tsx +91 -0
  16. package/src/Graph/Timeline/Timeline.tsx +14 -0
  17. package/src/Graph/Timeline/index.ts +1 -0
  18. package/src/Graph/Timeline/models.ts +20 -0
  19. package/src/Graph/Timeline/timeline.styles.ts +11 -0
  20. package/src/Graph/Timeline/translatedLabel.ts +6 -0
  21. package/src/Graph/Timeline/useTimeline.ts +90 -0
  22. package/src/Graph/common/utils.ts +2 -2
  23. package/src/Graph/index.ts +1 -0
  24. package/src/Icon/BusinessActivityIcon.tsx +12 -0
  25. package/src/Icon/ContainerIcon.tsx +20 -0
  26. package/src/Icon/DowntimeIcon.tsx +8 -1
  27. package/src/Icon/HostGroupIcon.tsx +8 -0
  28. package/src/Icon/MetaServiceIcon.tsx +12 -0
  29. package/src/Icon/ServiceGroupIcon.tsx +8 -0
  30. package/src/Icon/index.ts +7 -2
  31. package/src/Listing/EmptyResult/EmptyResult.tsx +1 -1
  32. package/src/Listing/index.tsx +39 -31
  33. package/src/Typography/EllipsisTypography.tsx +7 -3
  34. package/src/components/Layout/PageLayout/PageLayout.styles.ts +3 -3
  35. /package/src/Icon/{AcnowledgementIcon.tsx → AcknowledgementIcon.tsx} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@centreon/ui",
3
- "version": "24.11.11",
3
+ "version": "24.11.13",
4
4
  "description": "Centreon UI Components",
5
5
  "scripts": {
6
6
  "update:deps": "pnpx npm-check-updates -i --format group",
@@ -63,7 +63,7 @@ export const useDashboardLayoutStyles = makeStyles<boolean>()(
63
63
  opacity: 1
64
64
  },
65
65
  position: 'relative',
66
- height: '100%',
66
+ height: '100%'
67
67
  }
68
68
  })
69
69
  );
@@ -1,4 +1,4 @@
1
- import { MutableRefObject, ReactElement, useMemo } from 'react';
1
+ import { ReactElement, useMemo } from 'react';
2
2
 
3
3
  import { scaleLinear } from '@visx/scale';
4
4
  import { Grid as VisxGrid } from '@visx/visx';
@@ -13,15 +13,9 @@ interface Props {
13
13
  columns: number;
14
14
  height: number;
15
15
  width: number;
16
- containerRef: MutableRefObject<HTMLDivElement | null>;
17
16
  }
18
17
 
19
- const Grid = ({
20
- width,
21
- height,
22
- columns,
23
- containerRef
24
- }: Props): ReactElement => {
18
+ const Grid = ({ width, height, columns }: Props): ReactElement => {
25
19
  const theme = useTheme();
26
20
 
27
21
  const xScale = useMemo(
@@ -4,10 +4,7 @@ import { useSetAtom } from 'jotai';
4
4
  import GridLayout, { Layout, WidthProvider } from 'react-grid-layout';
5
5
  import 'react-grid-layout/css/styles.css';
6
6
 
7
- import {
8
- ParentSize,
9
- useMemoComponent
10
- } from '..';
7
+ import { ParentSize, useMemoComponent } from '..';
11
8
 
12
9
  import { Box } from '@mui/material';
13
10
  import { useDashboardLayoutStyles } from './Dashboard.styles';
@@ -33,10 +30,10 @@ const bottom = (layout: Array<Layout>): number => {
33
30
  layout.forEach((panel) => {
34
31
  bottomY = panel.y + panel.h;
35
32
  if (bottomY > max) max = bottomY;
36
- })
33
+ });
37
34
 
38
35
  return max;
39
- }
36
+ };
40
37
 
41
38
  const DashboardLayout = <T extends Layout>({
42
39
  children,
@@ -67,14 +64,10 @@ const DashboardLayout = <T extends Layout>({
67
64
  }, []);
68
65
 
69
66
  const containerHeight = useMemo((): number | undefined => {
70
- const nbRow = bottom(getLayout(layout));
71
- const containerPaddingY = 4
72
- return (
73
- nbRow * rowHeight +
74
- (nbRow - 1) * 20 +
75
- containerPaddingY * 2
76
- );
77
- }, [layout, rowHeight])
67
+ const nbRow = bottom(getLayout(layout));
68
+ const containerPaddingY = 4;
69
+ return nbRow * rowHeight + (nbRow - 1) * 20 + containerPaddingY * 2;
70
+ }, [layout, rowHeight]) ?? 0;
78
71
 
79
72
  useEffect(() => {
80
73
  window.addEventListener('resize', resize);
@@ -86,36 +79,40 @@ const DashboardLayout = <T extends Layout>({
86
79
 
87
80
  return useMemoComponent({
88
81
  Component: (
89
- <Box ref={dashboardContainerRef} sx={{ overflowY: 'auto', overflowX: 'hidden' }}>
90
- <ParentSize>
91
- {({ width, height }): JSX.Element => (
92
- <Box className={classes.container}>
93
- {displayGrid && (
94
- <Grid
95
- columns={columns}
96
- height={(containerHeight || 0) > height ? containerHeight : height}
97
- width={width}
98
- containerRef={dashboardContainerRef}
99
- />
100
- )}
101
- <ReactGridLayout
102
- cols={columns}
103
- containerPadding={[4, 0]}
104
- layout={getLayout(layout)}
105
- margin={[20, 20]}
106
- resizeHandles={['s', 'e', 'se']}
107
- rowHeight={rowHeight}
82
+ <Box
83
+ ref={dashboardContainerRef}
84
+ sx={{ overflowY: 'auto', overflowX: 'hidden' }}
85
+ >
86
+ <ParentSize>
87
+ {({ width, height }): JSX.Element => (
88
+ <Box className={classes.container}>
89
+ {displayGrid && (
90
+ <Grid
91
+ columns={columns}
92
+ height={
93
+ containerHeight > height ? containerHeight : height
94
+ }
108
95
  width={width}
109
- onLayoutChange={changeLayout}
110
- onResizeStart={startResize}
111
- onResizeStop={stopResize}
112
- >
113
- {children}
114
- </ReactGridLayout>
115
- </Box>
116
- )}
117
- </ParentSize>
118
- </Box>
96
+ />
97
+ )}
98
+ <ReactGridLayout
99
+ cols={columns}
100
+ containerPadding={[4, 0]}
101
+ layout={getLayout(layout)}
102
+ margin={[20, 20]}
103
+ resizeHandles={['s', 'e', 'se']}
104
+ rowHeight={rowHeight}
105
+ width={width}
106
+ onLayoutChange={changeLayout}
107
+ onResizeStart={startResize}
108
+ onResizeStop={stopResize}
109
+ >
110
+ {children}
111
+ </ReactGridLayout>
112
+ </Box>
113
+ )}
114
+ </ParentSize>
115
+ </Box>
119
116
  ),
120
117
  memoProps: [columns, layout, displayGrid, isStatic, ...additionalMemoProps]
121
118
  });
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useMemo, useState } from 'react';
2
2
 
3
3
  import { FormikValues, useFormikContext } from 'formik';
4
- import { equals, isNil, map, not, path, prop, type } from 'ramda';
4
+ import { path, equals, isNil, map, not, prop, type } from 'ramda';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
7
  import { FormHelperText, Stack } from '@mui/material';
@@ -188,7 +188,9 @@ const Autocomplete = ({
188
188
  value={getValues() ?? null}
189
189
  onChange={changeValues}
190
190
  onTextChange={textChange}
191
- style={{ width: autocomplete?.fullWidth ?? true ? 'auto' : '180px' }}
191
+ style={{
192
+ width: (autocomplete?.fullWidth ?? true) ? 'auto' : '180px'
193
+ }}
192
194
  />
193
195
  {inputErrors && (
194
196
  <Stack>
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useMemo } from 'react';
2
2
 
3
3
  import { FormikValues, useFormikContext } from 'formik';
4
- import { equals, isEmpty, path, propEq, reject, split } from 'ramda';
4
+ import { path, equals, isEmpty, propEq, reject, split } from 'ramda';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
7
  import {
@@ -1,5 +1,5 @@
1
1
  import { FormikValues, useFormikContext } from 'formik';
2
- import { equals, includes, path, split, type } from 'ramda';
2
+ import { path, equals, includes, split, type } from 'ramda';
3
3
  import { useTranslation } from 'react-i18next';
4
4
 
5
5
  import {
@@ -2,11 +2,11 @@ import { ChangeEvent, useCallback, useState } from 'react';
2
2
 
3
3
  import { FormikValues, useFormikContext } from 'formik';
4
4
  import {
5
+ path,
5
6
  equals,
6
7
  gt,
7
8
  isEmpty,
8
9
  not,
9
- path,
10
10
  split,
11
11
  type as variableType
12
12
  } from 'ramda';
@@ -30,7 +30,7 @@ const initialize = ({
30
30
  tooltip,
31
31
  axis,
32
32
  orientation,
33
- barStyle,
33
+ barStyle
34
34
  }: Pick<
35
35
  BarChartProps,
36
36
  'data' | 'legend' | 'axis' | 'barStyle' | 'orientation' | 'tooltip' | 'start'
@@ -62,14 +62,17 @@ interface Props extends LineChartProps {
62
62
  };
63
63
  }
64
64
 
65
- const filterLines = (lines: Array<Line>, displayThreshold: boolean): Array<Line> => {
65
+ const filterLines = (
66
+ lines: Array<Line>,
67
+ displayThreshold: boolean
68
+ ): Array<Line> => {
66
69
  if (!displayThreshold) {
67
70
  return lines;
68
71
  }
69
72
  const lineOriginMetric = findLineOfOriginMetricThreshold(lines);
70
73
 
71
74
  if (isEmpty(lineOriginMetric)) {
72
- return lines;
75
+ return lines;
73
76
  }
74
77
 
75
78
  const findLinesUpperLower = lines.map((line) =>
@@ -79,7 +79,7 @@ interface Props {
79
79
  transformMatrix?: {
80
80
  fx?: (pointX: number) => number;
81
81
  fy?: (pointY: number) => number;
82
- }
82
+ };
83
83
  }
84
84
 
85
85
  const InteractionWithGraph = ({
@@ -37,7 +37,7 @@ interface Props extends Partial<LineChartProps> {
37
37
  transformMatrix?: {
38
38
  fx?: (pointX: number) => number;
39
39
  fy?: (pointY: number) => number;
40
- }
40
+ };
41
41
  }
42
42
 
43
43
  const WrapperChart = ({
@@ -0,0 +1,147 @@
1
+ import {
2
+ dateTimeFormat,
3
+ getXAxisTickFormat,
4
+ useLocaleDateTimeFormat
5
+ } from '@centreon/ui';
6
+
7
+ import { Typography, useTheme } from '@mui/material';
8
+
9
+ import dayjs from 'dayjs';
10
+
11
+ import { userAtom } from '@centreon/ui-context';
12
+ import { Axis } from '@visx/visx';
13
+
14
+ import { scaleTime } from '@visx/scale';
15
+ import { BarRounded } from '@visx/shape';
16
+ import { useAtomValue } from 'jotai';
17
+ import { equals } from 'ramda';
18
+ import { useCallback } from 'react';
19
+ import { Tooltip } from '../../components';
20
+ import { margins } from '../common/margins';
21
+ import type { TimelineProps } from './models';
22
+ import { useStyles } from './timeline.styles';
23
+ import { useTimeline } from './useTimeline';
24
+
25
+ interface Props extends TimelineProps {
26
+ width: number;
27
+ height: number;
28
+ }
29
+
30
+ const axisPadding = 4;
31
+
32
+ const Timeline = ({
33
+ data,
34
+ startDate,
35
+ endDate,
36
+ width,
37
+ height,
38
+ TooltipContent,
39
+ tooltipClassName
40
+ }: Props) => {
41
+ const { classes, cx } = useStyles();
42
+ const { format } = useLocaleDateTimeFormat();
43
+ const { timezone } = useAtomValue(userAtom);
44
+
45
+ const theme = useTheme();
46
+
47
+ const xScale = scaleTime({
48
+ domain: [new Date(startDate), new Date(endDate)],
49
+ range: [margins.left, width - margins.right],
50
+ clamp: true
51
+ });
52
+
53
+ const numTicks = Math.min(Math.ceil(width / 82), 12);
54
+
55
+ const { getTimeDifference } = useTimeline();
56
+
57
+ const getFormattedStart = useCallback(
58
+ (start) =>
59
+ format({
60
+ date: dayjs(start).tz(timezone).toDate(),
61
+ formatString: dateTimeFormat
62
+ }),
63
+ [dateTimeFormat, timezone]
64
+ );
65
+
66
+ const getFormattedEnd = useCallback(
67
+ (end) =>
68
+ format({
69
+ date: dayjs(end).tz(timezone).toDate(),
70
+ formatString: dateTimeFormat
71
+ }),
72
+ [dateTimeFormat, timezone]
73
+ );
74
+
75
+ return (
76
+ <svg width={width} height={height + axisPadding}>
77
+ {data.map(({ start, end, color }, idx) => (
78
+ <Tooltip
79
+ hasCaret
80
+ classes={{
81
+ tooltip: cx(classes.tooltip, tooltipClassName)
82
+ }}
83
+ followCursor={false}
84
+ key={`rect-${start}--${end}`}
85
+ label={
86
+ TooltipContent ? (
87
+ <TooltipContent
88
+ start={getFormattedStart(start)}
89
+ end={getFormattedEnd(end)}
90
+ color={color}
91
+ duration={getTimeDifference({
92
+ start: dayjs(start),
93
+ end: dayjs(end)
94
+ })}
95
+ />
96
+ ) : (
97
+ <div style={{ color }}>
98
+ <Typography variant="body2">
99
+ {getTimeDifference({ start: dayjs(start), end: dayjs(end) })}
100
+ </Typography>
101
+ <Typography variant="body2">{`${format({ date: start, formatString: 'L LT' })} - ${format({ date: end, formatString: 'L LT' })}`}</Typography>
102
+ </div>
103
+ )
104
+ }
105
+ position="top"
106
+ >
107
+ <g>
108
+ <BarRounded
109
+ x={xScale(dayjs(start).tz(timezone))}
110
+ y={0}
111
+ width={
112
+ xScale(dayjs(end).tz(timezone)) -
113
+ xScale(dayjs(start).tz(timezone))
114
+ }
115
+ height={height - margins.bottom}
116
+ fill={color}
117
+ left={equals(idx, 0)}
118
+ radius={4}
119
+ right={equals(idx, data.length - 1)}
120
+ />
121
+ </g>
122
+ </Tooltip>
123
+ ))}
124
+
125
+ <Axis.AxisBottom
126
+ top={height - margins.bottom + axisPadding}
127
+ scale={xScale}
128
+ numTicks={numTicks}
129
+ tickFormat={(value) =>
130
+ format({
131
+ date: new Date(value),
132
+ formatString: getXAxisTickFormat({ end: endDate, start: startDate })
133
+ })
134
+ }
135
+ stroke={theme.palette.text.primary}
136
+ tickStroke={theme.palette.text.primary}
137
+ tickLabelProps={() => ({
138
+ fill: theme.palette.text.primary,
139
+ fontSize: theme.typography.caption.fontSize,
140
+ textAnchor: 'middle'
141
+ })}
142
+ />
143
+ </svg>
144
+ );
145
+ };
146
+
147
+ export default Timeline;
@@ -0,0 +1,148 @@
1
+ import { userAtom } from '@centreon/ui-context';
2
+ import { Provider, createStore } from 'jotai';
3
+ import Timeline from './Timeline';
4
+ import { Tooltip } from './models';
5
+
6
+ const data = [
7
+ {
8
+ start: '2024-09-09T10:57:42+02:00',
9
+ end: '2024-09-09T11:15:00+02:00',
10
+ color: 'green'
11
+ },
12
+ {
13
+ start: '2024-09-09T11:15:00+02:00',
14
+ end: '2024-09-09T11:30:00+02:00',
15
+ color: 'red'
16
+ },
17
+ {
18
+ start: '2024-09-09T11:30:00+02:00',
19
+ end: '2024-09-09T11:45:00+02:00',
20
+ color: 'gray'
21
+ },
22
+ {
23
+ start: '2024-09-09T11:45:00+02:00',
24
+ end: '2024-09-09T12:00:00+02:00',
25
+ color: 'green'
26
+ },
27
+ {
28
+ start: '2024-09-09T12:00:00+02:00',
29
+ end: '2024-09-09T12:20:00+02:00',
30
+ color: 'red'
31
+ },
32
+ {
33
+ start: '2024-09-09T12:20:00+02:00',
34
+ end: '2024-09-09T12:40:00+02:00',
35
+ color: 'gray'
36
+ },
37
+ {
38
+ start: '2024-09-09T12:40:00+02:00',
39
+ end: '2024-09-09T12:57:42+02:00',
40
+ color: 'green'
41
+ }
42
+ ];
43
+
44
+ const startDate = '2024-09-09T10:57:42+02:00';
45
+ const endDate = '2024-09-09T12:57:42+02:00';
46
+
47
+ const TooltipContent = ({ start, end, color, duration }: Tooltip) => (
48
+ <div
49
+ data-testid="tooltip-content"
50
+ style={{
51
+ display: 'flex',
52
+ flexDirection: 'column',
53
+ justifyContent: 'center',
54
+ alignItems: 'center',
55
+ gap: '10px',
56
+ padding: '5px'
57
+ }}
58
+ >
59
+ <span>{start}</span>
60
+ <span>{end}</span>
61
+ <span>{color}</span>
62
+ <span>{duration}</span>
63
+ </div>
64
+ );
65
+
66
+ const store = createStore();
67
+ store.set(userAtom, { timezone: 'Europe/Paris', locale: 'en' });
68
+
69
+ const initialize = (displayDefaultTooltip = true): void => {
70
+ cy.mount({
71
+ Component: (
72
+ <Provider store={store}>
73
+ <div
74
+ style={{
75
+ height: '100px',
76
+ width: '70%'
77
+ }}
78
+ >
79
+ <Timeline
80
+ data={data}
81
+ startDate={startDate}
82
+ endDate={endDate}
83
+ TooltipContent={displayDefaultTooltip ? undefined : TooltipContent}
84
+ />
85
+ </div>
86
+ </Provider>
87
+ )
88
+ });
89
+ };
90
+
91
+ describe('Timeline', () => {
92
+ it('checks that the correct number of bars are rendered', () => {
93
+ initialize();
94
+
95
+ cy.get('path').should('have.length', data.length);
96
+
97
+ cy.makeSnapshot();
98
+ });
99
+
100
+ it('checks that each bar has the correct color', () => {
101
+ initialize();
102
+
103
+ data.forEach(({ color }, index) => {
104
+ cy.get('path').eq(index).should('have.attr', 'fill', color);
105
+ });
106
+ });
107
+
108
+ it('displays tooltip with correct information when hovered over a bar', () => {
109
+ initialize(false);
110
+
111
+ cy.get('path').first().trigger('mouseover');
112
+
113
+ cy.get('[data-testid="tooltip-content"]').within(() => {
114
+ cy.contains('09/09/2024 10:57 AM').should('be.visible');
115
+ cy.contains('09/09/2024 11:15 AM').should('be.visible');
116
+ cy.contains('green').should('be.visible');
117
+ cy.contains('17 minutes').should('be.visible');
118
+ });
119
+
120
+ cy.makeSnapshot();
121
+ });
122
+
123
+ it('displays the default tooltip with correct information when hovered over a bar', () => {
124
+ initialize();
125
+
126
+ cy.get('path').first().trigger('mouseover');
127
+
128
+ cy.get('[role="tooltip"]').within(() => {
129
+ cy.contains('09/09/2024 10:57 AM')
130
+ .should('be.visible')
131
+ .and('have.css', 'color', 'rgb(0, 128, 0)');
132
+ cy.contains('09/09/2024 11:15 AM')
133
+ .should('be.visible')
134
+ .and('have.css', 'color', 'rgb(0, 128, 0)');
135
+ cy.contains('17 minutes')
136
+ .should('be.visible')
137
+ .and('have.css', 'color', 'rgb(0, 128, 0)');
138
+ });
139
+
140
+ cy.makeSnapshot();
141
+ });
142
+
143
+ it('displays correct tick labels on the x-axis', () => {
144
+ initialize();
145
+
146
+ cy.get('.visx-axis-bottom .visx-axis-tick').first().contains('11:00');
147
+ });
148
+ });
@@ -0,0 +1,91 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+
3
+ import { Typography } from '@mui/material';
4
+ import Timeline from './Timeline';
5
+
6
+ const data = [
7
+ {
8
+ start: '2024-09-25T21:00:42+01:00',
9
+ end: '2024-09-25T21:15:00+01:00',
10
+ color: 'gray'
11
+ },
12
+ {
13
+ start: '2024-09-25T21:15:00+01:00',
14
+ end: '2024-09-25T21:54:00+01:00',
15
+ color: 'green'
16
+ },
17
+ {
18
+ start: '2024-09-25T21:54:00+01:00',
19
+ end: '2024-09-25T22:30:00+01:00',
20
+ color: 'red'
21
+ }
22
+ ];
23
+
24
+ const startDate = '2024-09-25T21:00:42+01:00';
25
+ const endDate = '2024-09-25T22:30:00+01:00';
26
+
27
+ const Template = (args): JSX.Element => {
28
+ return (
29
+ <div style={{ width: '700px', height: '100px' }}>
30
+ <Timeline {...args} />
31
+ </div>
32
+ );
33
+ };
34
+
35
+ const meta: Meta<typeof Timeline> = {
36
+ component: Timeline,
37
+ parameters: {
38
+ chromatic: {
39
+ delay: 1000
40
+ }
41
+ },
42
+ render: Template
43
+ };
44
+
45
+ export default meta;
46
+ type Story = StoryObj<typeof Timeline>;
47
+
48
+ export const Normal: Story = {
49
+ args: {
50
+ data,
51
+ startDate,
52
+ endDate
53
+ }
54
+ };
55
+
56
+ export const WithoutData: Story = {
57
+ args: {
58
+ data: [],
59
+ startDate,
60
+ endDate
61
+ }
62
+ };
63
+
64
+ export const WithSmallerTimeRangeThanData: Story = {
65
+ args: {
66
+ data,
67
+ startDate,
68
+ endDate: '2024-09-25T22:00:00+01:00'
69
+ }
70
+ };
71
+
72
+ export const WithCustomTooltip: Story = {
73
+ args: {
74
+ data,
75
+ startDate,
76
+ endDate,
77
+ TooltipContent: ({ duration, color }) => (
78
+ <div style={{ display: 'flex', flexDirection: 'row', gap: '8px' }}>
79
+ <div
80
+ style={{
81
+ backgroundColor: color,
82
+ width: '20px',
83
+ height: '20px',
84
+ borderRadius: '4px'
85
+ }}
86
+ />
87
+ <Typography>{duration}</Typography>
88
+ </div>
89
+ )
90
+ }
91
+ };
@@ -0,0 +1,14 @@
1
+ import { ParentSize } from '../..';
2
+
3
+ import ResponsiveTimeline from './ResponsiveTimeline';
4
+ import type { TimelineProps } from './models';
5
+
6
+ const Timeline = (props: TimelineProps): JSX.Element => (
7
+ <ParentSize>
8
+ {({ width, height }) => (
9
+ <ResponsiveTimeline {...props} height={height} width={width} />
10
+ )}
11
+ </ParentSize>
12
+ );
13
+
14
+ export default Timeline;
@@ -0,0 +1 @@
1
+ export { default as Timeline } from './Timeline';
@@ -0,0 +1,20 @@
1
+ export interface Data {
2
+ start: string;
3
+ end: string;
4
+ color: string;
5
+ }
6
+
7
+ export interface Tooltip {
8
+ start: string;
9
+ end: string;
10
+ color: string;
11
+ duration: string;
12
+ }
13
+
14
+ export interface TimelineProps {
15
+ data: Array<Data>;
16
+ startDate: string;
17
+ endDate: string;
18
+ TooltipContent?: (props: Tooltip) => JSX.Element;
19
+ tooltipClassName?: string;
20
+ }
@@ -0,0 +1,11 @@
1
+ import { makeStyles } from 'tss-react/mui';
2
+
3
+ export const useStyles = makeStyles()((theme) => ({
4
+ tooltip: {
5
+ backgroundColor: theme.palette.background.paper,
6
+ color: theme.palette.text.primary,
7
+ padding: theme.spacing(1),
8
+ boxShadow: theme.shadows[3],
9
+ maxWidth: 'none'
10
+ }
11
+ }));
@@ -0,0 +1,6 @@
1
+ export const labelYear = 'year';
2
+ export const labelMonth = 'month';
3
+ export const labelDay = 'day';
4
+ export const labelHour = 'hour';
5
+ export const labelMinutes = 'minutes';
6
+ export const labelMinute = 'minute';
@@ -0,0 +1,90 @@
1
+ import dayjs, { Dayjs } from 'dayjs';
2
+
3
+ import { usePluralizedTranslation } from '@centreon/ui';
4
+ import { lt } from 'ramda';
5
+ import { useCallback } from 'react';
6
+ import {
7
+ labelDay,
8
+ labelHour,
9
+ labelMinute,
10
+ labelMonth,
11
+ labelYear
12
+ } from './translatedLabel';
13
+
14
+ interface StartEndProps {
15
+ start: Dayjs;
16
+ end: Dayjs;
17
+ }
18
+
19
+ interface GetWidthProps extends StartEndProps {
20
+ timezone: string;
21
+ xScale;
22
+ }
23
+
24
+ interface UseTimelineState {
25
+ getTimeDifference: (props: StartEndProps) => string;
26
+ getWidth: (props: GetWidthProps) => number;
27
+ }
28
+
29
+ export const useTimeline = (): UseTimelineState => {
30
+ const { pluralizedT } = usePluralizedTranslation();
31
+
32
+ const getTimeDifference = useCallback(
33
+ ({ start, end }: StartEndProps): string => {
34
+ const diffInMilliseconds = end.diff(start);
35
+ const diffDuration = dayjs.duration(diffInMilliseconds);
36
+
37
+ const timeUnits = [
38
+ {
39
+ value: diffDuration.years(),
40
+ unit: pluralizedT({ label: labelYear, count: diffDuration.years() })
41
+ },
42
+ {
43
+ value: diffDuration.months(),
44
+ unit: pluralizedT({ label: labelMonth, count: diffDuration.months() })
45
+ },
46
+ {
47
+ value: diffDuration.days(),
48
+ unit: pluralizedT({ label: labelDay, count: diffDuration.days() })
49
+ },
50
+ {
51
+ value: diffDuration.hours(),
52
+ unit: pluralizedT({ label: labelHour, count: diffDuration.hours() })
53
+ },
54
+ {
55
+ value: diffDuration.minutes(),
56
+ unit: pluralizedT({
57
+ label: labelMinute,
58
+ count: diffDuration.minutes()
59
+ })
60
+ }
61
+ ];
62
+
63
+ const readableUnits = timeUnits
64
+ .filter((unit) => unit.value > 0)
65
+ .map((unit) => `${unit.value} ${unit.unit}`);
66
+
67
+ return readableUnits.slice(0, 2).join(', ');
68
+ },
69
+ []
70
+ );
71
+
72
+ const getWidth = useCallback(
73
+ ({ start, end, timezone, xScale }: GetWidthProps): number => {
74
+ const baseWidth =
75
+ xScale(dayjs(end).tz(timezone)) - xScale(dayjs(start).tz(timezone));
76
+
77
+ if (Number.isNaN(baseWidth) || lt(baseWidth, 0)) {
78
+ return 0;
79
+ }
80
+
81
+ return baseWidth;
82
+ },
83
+ []
84
+ );
85
+
86
+ return {
87
+ getTimeDifference,
88
+ getWidth
89
+ };
90
+ };
@@ -8,11 +8,11 @@ import {
8
8
  gte,
9
9
  head,
10
10
  isNil,
11
+ last,
11
12
  length,
12
13
  lt,
13
14
  lte,
14
- pluck,
15
- last
15
+ pluck
16
16
  } from 'ramda';
17
17
 
18
18
  import { Theme, darken, getLuminance, lighten } from '@mui/material';
@@ -9,6 +9,7 @@ export { Text as GraphText } from './Text';
9
9
  export { HeatMap } from './HeatMap';
10
10
  export { BarStack } from './BarStack';
11
11
  export { PieChart } from './PieChart';
12
+ export { Timeline } from './Timeline';
12
13
  export * from './Tree';
13
14
  export type { LineChartData } from './common/models';
14
15
  export * from './common/timeSeries';
@@ -0,0 +1,12 @@
1
+ import AccountTreeIcon from '@mui/icons-material/AccountTree';
2
+ import { SvgIconProps } from '@mui/material';
3
+
4
+ import BaseIcon from './BaseIcon';
5
+
6
+ export const BusinessActivityIcon = (props: SvgIconProps): JSX.Element => (
7
+ <BaseIcon
8
+ Icon={AccountTreeIcon}
9
+ dataTestId="BusinessActivityIcon"
10
+ {...props}
11
+ />
12
+ );
@@ -0,0 +1,20 @@
1
+ import { SvgIconProps } from '@mui/material';
2
+
3
+ import BaseIcon from './BaseIcon';
4
+
5
+ const icon = (
6
+ <g>
7
+ <path d="M301 745.9L28 662.4c-7.4-2.3-12.5-9.2-12.5-16.9V270.4c0-5.7 2.7-10.9 7.3-14.3 4.6-3.3 10.4-4.2 15.8-2.5l273.1 88.5c7.3 2.4 12.2 9.1 12.2 16.8V729c0 5.6-2.7 10.9-7.2 14.2-3.1 2.3-6.8 3.5-10.5 3.5-1.8-.1-3.5-.3-5.2-.8zm5.2-16.9l5.2-16.9-5.2 16.9zM50.8 632.4l237.8 72.7V371.7l-237.8-77v337.7z" />
8
+ <path d="M295.7 743.2c-4.5-3.3-7.2-8.6-7.2-14.2V358.8c0-7.6 4.9-14.4 12.2-16.8l273.1-88.5c5.4-1.8 11.3-.8 15.8 2.5 4.6 3.4 7.3 8.7 7.3 14.3v375.1c0 7.8-5 14.6-12.5 16.9l-273.1 83.4-5.2-16.9 5.2 16.9c-1.7.5-3.4.8-5.2.8-3.6.1-7.3-1-10.4-3.3zm28.2-371.5V705l237.8-72.7V294.7l-237.8 77zM16.3 275.9c-3-9.3 2.1-19.3 11.4-22.3L300.8 165c9.2-3 19.2 2.1 22.2 11.3 3 9.3-2.1 19.3-11.3 22.2L38.6 287.2c-1.8.6-3.7.9-5.5.9-7.4-.1-14.3-4.8-16.8-12.2z" />
9
+ <path d="M573.9 287.2l-273-88.5c-9.3-3-14.4-13-11.4-22.2 3-9.3 13-14.4 22.2-11.3l273.1 88.5c9.3 3 14.4 13 11.4 22.3-2.4 7.4-9.4 12.2-16.8 12.2-1.9-.1-3.7-.5-5.5-1zm-131.3 42.5l-273.1-88.5c-9.3-3-14.4-13-11.4-22.2 3-9.3 12.9-14.4 22.3-11.4l273 88.5c9.3 3 14.4 13 11.4 22.3-2.4 7.4-9.3 12.2-16.8 12.2-1.7 0-3.5-.3-5.4-.9z" />
10
+ </g>
11
+ );
12
+
13
+ export const ContainerIcon = (props: SvgIconProps): JSX.Element => (
14
+ <BaseIcon
15
+ {...props}
16
+ dataTestId="ContainerIcon"
17
+ Icon={icon}
18
+ viewBox="6 156 600 600"
19
+ />
20
+ );
@@ -10,5 +10,12 @@ const icon = (
10
10
  );
11
11
 
12
12
  export const DowntimeIcon = (props: SvgIconProps): JSX.Element => (
13
- <BaseIcon {...props} Icon={icon} height="24" viewBox="0 0 24 24" width="24" />
13
+ <BaseIcon
14
+ {...props}
15
+ dataTestId="DowntimeIcon"
16
+ Icon={icon}
17
+ height="24"
18
+ viewBox="0 0 24 24"
19
+ width="24"
20
+ />
14
21
  );
@@ -0,0 +1,8 @@
1
+ import BusinessIcon from '@mui/icons-material/Business';
2
+ import { SvgIconProps } from '@mui/material';
3
+
4
+ import BaseIcon from './BaseIcon';
5
+
6
+ export const HostGroupIcon = (props: SvgIconProps): JSX.Element => (
7
+ <BaseIcon Icon={BusinessIcon} dataTestId="HostGroupIcon" {...props} />
8
+ );
@@ -0,0 +1,12 @@
1
+ import SettingsInputSvideoIcon from '@mui/icons-material/SettingsInputSvideo';
2
+ import { SvgIconProps } from '@mui/material';
3
+
4
+ import BaseIcon from './BaseIcon';
5
+
6
+ export const MetaServiceIcon = (props: SvgIconProps): JSX.Element => (
7
+ <BaseIcon
8
+ Icon={SettingsInputSvideoIcon}
9
+ dataTestId="MetaServiceIcon"
10
+ {...props}
11
+ />
12
+ );
@@ -0,0 +1,8 @@
1
+ import LinearScaleIcon from '@mui/icons-material/LinearScale';
2
+ import { SvgIconProps } from '@mui/material';
3
+
4
+ import BaseIcon from './BaseIcon';
5
+
6
+ export const ServiceGroupIcon = (props: SvgIconProps): JSX.Element => (
7
+ <BaseIcon Icon={LinearScaleIcon} dataTestId="ServiceGroupIcon" {...props} />
8
+ );
package/src/Icon/index.ts CHANGED
@@ -1,4 +1,9 @@
1
+ export { DowntimeIcon } from './DowntimeIcon';
2
+ export { AcknowledgementIcon } from './AcknowledgementIcon';
1
3
  export { HostIcon } from './HostIcon';
2
4
  export { ServiceIcon } from './ServiceIcon';
3
- export { DowntimeIcon } from './DowntimeIcon';
4
- export { AcknowledgementIcon } from './AcnowledgementIcon';
5
+ export { BusinessActivityIcon } from './BusinessActivityIcon';
6
+ export { HostGroupIcon } from './HostGroupIcon';
7
+ export { ServiceGroupIcon } from './ServiceGroupIcon';
8
+ export { MetaServiceIcon } from './MetaServiceIcon';
9
+ export { ContainerIcon } from './ContainerIcon';
@@ -1,7 +1,7 @@
1
1
  import { EmptyRow } from '../Row/EmptyRow';
2
2
 
3
3
  interface EmptyResultProps {
4
- label: string;
4
+ label: string | JSX.Element;
5
5
  }
6
6
 
7
7
  const EmptyResult = ({ label }: EmptyResultProps): JSX.Element => (
@@ -61,7 +61,7 @@ import {
61
61
  SortOrder
62
62
  } from './models';
63
63
  import { subItemsPivotsAtom } from './tableAtoms';
64
- import { labelNoResultFound } from './translatedLabels';
64
+ import { labelNoResultFound as defaultLabelNoResultFound } from './translatedLabels';
65
65
  import useStyleTable from './useStyleTable';
66
66
 
67
67
  const subItemPrefixKey = 'listing';
@@ -140,6 +140,7 @@ export interface Props<TRow> {
140
140
  viewerModeConfiguration?: ViewerModeConfiguration;
141
141
  widthToMoveTablePagination?: number;
142
142
  isActionBarVisible: boolean;
143
+ labelNoResultFound?: string | JSX.Element;
143
144
  }
144
145
 
145
146
  const defaultColumnConfiguration = {
@@ -199,7 +200,8 @@ const Listing = <
199
200
  labelCollapse: 'Collapse',
200
201
  labelExpand: 'Expand'
201
202
  },
202
- isActionBarVisible = true
203
+ isActionBarVisible = true,
204
+ labelNoResultFound = defaultLabelNoResultFound
203
205
  }: Props<TRow>): JSX.Element => {
204
206
  const currentVisibleColumns = getVisibleColumns({
205
207
  columnConfiguration,
@@ -526,33 +528,32 @@ const Listing = <
526
528
  className={classes.container}
527
529
  ref={containerRef as RefObject<HTMLDivElement>}
528
530
  >
529
- {
530
- isActionBarVisible &&
531
- <div
532
- className={classes.actionBar}
533
- ref={actionBarRef as RefObject<HTMLDivElement>}
534
- >
535
- <ListingActionBar
536
- actions={actions}
537
- actionsBarMemoProps={actionsBarMemoProps}
538
- columnConfiguration={columnConfiguration}
539
- columns={columns}
540
- currentPage={currentPage}
541
- customPaginationClassName={customPaginationClassName}
542
- limit={limit}
543
- listingVariant={listingVariant}
544
- moveTablePagination={moveTablePagination}
545
- paginated={paginated}
546
- totalRows={totalRows}
547
- viewerModeConfiguration={viewerModeConfiguration}
548
- widthToMoveTablePagination={widthToMoveTablePagination}
549
- onLimitChange={changeLimit}
550
- onPaginate={onPaginate}
551
- onResetColumns={onResetColumns}
552
- onSelectColumns={onSelectColumns}
553
- />
554
- </div>
555
- }
531
+ {isActionBarVisible && (
532
+ <div
533
+ className={classes.actionBar}
534
+ ref={actionBarRef as RefObject<HTMLDivElement>}
535
+ >
536
+ <ListingActionBar
537
+ actions={actions}
538
+ actionsBarMemoProps={actionsBarMemoProps}
539
+ columnConfiguration={columnConfiguration}
540
+ columns={columns}
541
+ currentPage={currentPage}
542
+ customPaginationClassName={customPaginationClassName}
543
+ limit={limit}
544
+ listingVariant={listingVariant}
545
+ moveTablePagination={moveTablePagination}
546
+ paginated={paginated}
547
+ totalRows={totalRows}
548
+ viewerModeConfiguration={viewerModeConfiguration}
549
+ widthToMoveTablePagination={widthToMoveTablePagination}
550
+ onLimitChange={changeLimit}
551
+ onPaginate={onPaginate}
552
+ onResetColumns={onResetColumns}
553
+ onSelectColumns={onSelectColumns}
554
+ />
555
+ </div>
556
+ )}
556
557
 
557
558
  <ParentSize
558
559
  parentSizeStyles={{
@@ -704,7 +705,11 @@ const Listing = <
704
705
  (loading ? (
705
706
  <SkeletonLoader rows={limit} />
706
707
  ) : (
707
- <EmptyResult label={t(labelNoResultFound)} />
708
+ <EmptyResult
709
+ label={
710
+ labelNoResultFound || t(defaultLabelNoResultFound)
711
+ }
712
+ />
708
713
  ))}
709
714
  </TableBody>
710
715
  </Table>
@@ -740,6 +745,7 @@ export const MemoizedListing = <TRow extends { id: string | number }>({
740
745
  moveTablePagination,
741
746
  widthToMoveTablePagination,
742
747
  listingVariant,
748
+ labelNoResultFound,
743
749
  ...props
744
750
  }: MemoizedListingProps<TRow>): JSX.Element =>
745
751
  useMemoComponent({
@@ -762,6 +768,7 @@ export const MemoizedListing = <TRow extends { id: string | number }>({
762
768
  sortOrder={sortOrder}
763
769
  totalRows={totalRows}
764
770
  widthToMoveTablePagination={widthToMoveTablePagination}
771
+ labelNoResultFound={labelNoResultFound}
765
772
  {...props}
766
773
  />
767
774
  ),
@@ -784,7 +791,8 @@ export const MemoizedListing = <TRow extends { id: string | number }>({
784
791
  sortOrder,
785
792
  sortField,
786
793
  innerScrollDisabled,
787
- listingVariant
794
+ listingVariant,
795
+ labelNoResultFound
788
796
  ]
789
797
  });
790
798
 
@@ -1,11 +1,15 @@
1
- import { forwardRef, type ForwardedRef } from 'react';
1
+ import { type ForwardedRef, forwardRef } from 'react';
2
2
 
3
3
  import { Box, Typography, type TypographyProps } from '@mui/material';
4
4
 
5
5
  const EllipsisTypography = forwardRef(
6
6
  (
7
- { containerClassname, ...props }: TypographyProps & { containerClassname?: string },
8
- ref?: ForwardedRef<HTMLSpanElement>) => {
7
+ {
8
+ containerClassname,
9
+ ...props
10
+ }: TypographyProps & { containerClassname?: string },
11
+ ref?: ForwardedRef<HTMLSpanElement>
12
+ ) => {
9
13
  return (
10
14
  <Box className={containerClassname} sx={{ width: '100%' }}>
11
15
  <Typography
@@ -4,8 +4,8 @@ export const useStyles = makeStyles()((theme) => ({
4
4
  pageLayout: {
5
5
  display: 'grid',
6
6
  gridTemplateRows: 'auto 1fr',
7
- height: '100%',
8
- overflow: 'hidden'
7
+ overflow: 'hidden',
8
+ height: '100%'
9
9
  },
10
10
  pageLayoutActions: {
11
11
  '& > span': {
@@ -24,7 +24,7 @@ export const useStyles = makeStyles()((theme) => ({
24
24
  backgroundColor: theme.palette.layout.body.background
25
25
  },
26
26
  '&[data-has-actions="true"]': {
27
- gridTemplateRows: 'min-content auto',
27
+ gridTemplateRows: 'min-content auto'
28
28
  },
29
29
  display: 'grid',
30
30
  gridTemplateRows: 'auto',