@centreon/ui 24.11.2 → 24.11.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 +2 -3
- package/src/Dashboard/Dashboard.styles.ts +4 -3
- package/src/Dashboard/DashboardLayout.stories.tsx +1 -1
- package/src/Dashboard/Grid.tsx +17 -11
- package/src/Dashboard/Layout.tsx +56 -27
- package/src/FileDropZone/index.tsx +21 -23
- package/src/Form/CollapsibleGroup.tsx +3 -2
- package/src/Form/Form.cypress.spec.tsx +39 -0
- package/src/Form/Form.tsx +1 -0
- package/src/Form/Inputs/Autocomplete.tsx +27 -4
- package/src/Form/Inputs/ConnectedAutocomplete.tsx +20 -10
- package/src/Form/Inputs/File.tsx +69 -0
- package/src/Form/Inputs/Grid.tsx +30 -2
- package/src/Form/Inputs/Radio.tsx +12 -4
- package/src/Form/Inputs/Switch.tsx +10 -2
- package/src/Form/Inputs/Text.tsx +13 -4
- package/src/Form/Inputs/index.tsx +5 -2
- package/src/Form/Inputs/models.ts +18 -2
- package/src/Form/storiesData.tsx +15 -3
- package/src/Form/translatedLabels.ts +1 -0
- package/src/Graph/BarChart/BarChart.tsx +4 -1
- package/src/Graph/BarChart/ResponsiveBarChart.tsx +3 -2
- package/src/Graph/Chart/Chart.tsx +9 -2
- package/src/Graph/Chart/InteractiveComponents/AnchorPoint/useTickGraph.ts +2 -2
- package/src/Graph/Chart/InteractiveComponents/index.tsx +10 -2
- package/src/Graph/Chart/helpers/index.ts +5 -5
- package/src/Graph/Chart/index.tsx +7 -0
- package/src/Graph/Chart/models.ts +1 -0
- package/src/Graph/common/timeSeries/index.ts +15 -8
- package/src/InputField/Text/index.tsx +1 -1
- package/src/Listing/index.tsx +39 -27
- package/src/Listing/models.ts +8 -0
- package/src/MultiSelectEntries/index.tsx +0 -2
- package/src/PopoverMenu/index.tsx +9 -2
- package/src/SortableItems/index.tsx +1 -0
- package/src/ThemeProvider/index.tsx +1 -1
- package/src/ThemeProvider/palettes.ts +4 -4
- package/src/api/customFetch.ts +4 -1
- package/src/components/CrudPage/Actions/Actions.styles.ts +16 -0
- package/src/components/CrudPage/Actions/Actions.tsx +24 -0
- package/src/components/CrudPage/Actions/AddButton.tsx +23 -0
- package/src/components/CrudPage/Actions/Filters.tsx +25 -0
- package/src/components/CrudPage/Actions/Search.tsx +31 -0
- package/src/components/CrudPage/Actions/useSearch.tsx +24 -0
- package/src/components/CrudPage/Columns/Actions.tsx +88 -0
- package/src/components/CrudPage/CrudPage.cypress.spec.tsx +559 -0
- package/src/components/CrudPage/CrudPage.stories.tsx +278 -0
- package/src/components/CrudPage/CrudPageRoot.tsx +142 -0
- package/src/components/CrudPage/DeleteModal.tsx +77 -0
- package/src/components/CrudPage/Form/AddModal.tsx +35 -0
- package/src/components/CrudPage/Form/Buttons.tsx +98 -0
- package/src/components/CrudPage/Form/UpdateModal.tsx +60 -0
- package/src/components/CrudPage/Listing.tsx +63 -0
- package/src/components/CrudPage/atoms.ts +30 -0
- package/src/components/CrudPage/hooks/useDeleteItem.ts +53 -0
- package/src/components/CrudPage/hooks/useGetItem.ts +36 -0
- package/src/components/CrudPage/hooks/useGetItems.ts +67 -0
- package/src/components/CrudPage/hooks/useListingQueryKey.ts +31 -0
- package/src/components/CrudPage/index.tsx +7 -0
- package/src/components/CrudPage/models.ts +118 -0
- package/src/components/CrudPage/utils.ts +4 -0
- package/src/components/DataTable/DataTable.cypress.spec.tsx +2 -1
- package/src/components/DataTable/DataTable.stories.tsx +17 -0
- package/src/components/DataTable/DataTable.styles.ts +1 -1
- package/src/components/DataTable/EmptyState/DataTableEmptyState.styles.ts +3 -1
- package/src/components/DataTable/EmptyState/DataTableEmptyState.tsx +6 -0
- package/src/components/DataTable/Item/DataTableItem.styles.ts +28 -2
- package/src/components/DataTable/Item/DataTableItem.tsx +19 -4
- package/src/components/Layout/AreaIndicator.tsx +1 -1
- package/src/components/Layout/PageLayout/PageLayout.styles.ts +7 -2
- package/src/components/Layout/PageLayout/PageLayoutBody.tsx +1 -0
- package/src/components/Modal/Modal.styles.ts +1 -1
- package/src/components/Zoom/Zoom.tsx +2 -2
- package/src/components/Zoom/ZoomContent.tsx +2 -2
- package/src/components/index.ts +1 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
|
2
|
+
import { ColumnType } from '../../../';
|
|
3
|
+
import { MemoizedListing } from '../../Listing';
|
|
4
|
+
import Actions from './Actions/Actions';
|
|
5
|
+
import ColumnActions from './Columns/Actions';
|
|
6
|
+
import {
|
|
7
|
+
changeSortAtom,
|
|
8
|
+
limitAtom,
|
|
9
|
+
pageAtom,
|
|
10
|
+
sortFieldAtom,
|
|
11
|
+
sortOrderAtom
|
|
12
|
+
} from './atoms';
|
|
13
|
+
import { ListingProps } from './models';
|
|
14
|
+
|
|
15
|
+
const Listing = <TData extends { id: number; name: string }>({
|
|
16
|
+
rows,
|
|
17
|
+
total,
|
|
18
|
+
isLoading,
|
|
19
|
+
columns,
|
|
20
|
+
subItems,
|
|
21
|
+
labels,
|
|
22
|
+
filters
|
|
23
|
+
}: ListingProps<TData> & {
|
|
24
|
+
labels: {
|
|
25
|
+
search: string;
|
|
26
|
+
add: string;
|
|
27
|
+
};
|
|
28
|
+
}): JSX.Element => {
|
|
29
|
+
const [page, setPage] = useAtom(pageAtom);
|
|
30
|
+
const [limit, setLimit] = useAtom(limitAtom);
|
|
31
|
+
const sortOrder = useAtomValue(sortOrderAtom);
|
|
32
|
+
const sortField = useAtomValue(sortFieldAtom);
|
|
33
|
+
const changeSort = useSetAtom(changeSortAtom);
|
|
34
|
+
|
|
35
|
+
const listingColumns = columns.concat({
|
|
36
|
+
type: ColumnType.component,
|
|
37
|
+
id: 'actions',
|
|
38
|
+
label: '',
|
|
39
|
+
Component: ColumnActions,
|
|
40
|
+
width: 'min-content',
|
|
41
|
+
clickable: true
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<MemoizedListing
|
|
46
|
+
actions={<Actions labels={labels} filters={filters} />}
|
|
47
|
+
columns={listingColumns}
|
|
48
|
+
subItems={subItems}
|
|
49
|
+
loading={isLoading}
|
|
50
|
+
rows={rows}
|
|
51
|
+
currentPage={page}
|
|
52
|
+
onPaginate={setPage}
|
|
53
|
+
limit={limit}
|
|
54
|
+
onLimitChange={setLimit}
|
|
55
|
+
totalRows={total}
|
|
56
|
+
sortField={sortField}
|
|
57
|
+
sortOrder={sortOrder}
|
|
58
|
+
onSort={changeSort}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default Listing;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { atom } from 'jotai';
|
|
2
|
+
import { ItemToDelete } from './models';
|
|
3
|
+
|
|
4
|
+
export const pageAtom = atom(0);
|
|
5
|
+
export const limitAtom = atom(10);
|
|
6
|
+
export const searchAtom = atom('');
|
|
7
|
+
export const sortOrderAtom = atom('asc');
|
|
8
|
+
export const sortFieldAtom = atom('name');
|
|
9
|
+
export const openFormModalAtom = atom<number | 'add' | null>(null);
|
|
10
|
+
export const itemToDeleteAtom = atom<ItemToDelete | null>(null);
|
|
11
|
+
export const canDeleteSubItemsAtom = atom<boolean | undefined>(true);
|
|
12
|
+
export const formLabelButtonsAtom = atom({
|
|
13
|
+
add: {
|
|
14
|
+
cancel: 'Cancel',
|
|
15
|
+
confirm: 'Save'
|
|
16
|
+
},
|
|
17
|
+
update: {
|
|
18
|
+
cancel: 'Cancel',
|
|
19
|
+
confirm: 'Save'
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
export const askBeforeCloseFormModalAtom = atom(false);
|
|
23
|
+
|
|
24
|
+
export const changeSortAtom = atom(
|
|
25
|
+
null,
|
|
26
|
+
(_get, set, { sortOrder, sortField }) => {
|
|
27
|
+
set(sortOrderAtom, sortOrder);
|
|
28
|
+
set(sortFieldAtom, sortField);
|
|
29
|
+
}
|
|
30
|
+
);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Method,
|
|
3
|
+
ResponseError,
|
|
4
|
+
useMutationQuery,
|
|
5
|
+
useSnackbar
|
|
6
|
+
} from '@centreon/ui';
|
|
7
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
8
|
+
import { ReactElement } from 'react';
|
|
9
|
+
import { ItemToDelete } from '../models';
|
|
10
|
+
import { isAFunction } from '../utils';
|
|
11
|
+
|
|
12
|
+
interface UseDeleteItem {
|
|
13
|
+
isMutating: boolean;
|
|
14
|
+
deleteItem: (item: ItemToDelete) => Promise<object | ResponseError>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface UseDeleteItemProps {
|
|
18
|
+
deleteEndpoint: (item: ItemToDelete) => string;
|
|
19
|
+
listingQueryKey: string;
|
|
20
|
+
successMessage:
|
|
21
|
+
| ((item: ItemToDelete) => string | ReactElement)
|
|
22
|
+
| string
|
|
23
|
+
| ReactElement;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const useDeleteItem = ({
|
|
27
|
+
deleteEndpoint,
|
|
28
|
+
listingQueryKey,
|
|
29
|
+
successMessage
|
|
30
|
+
}: UseDeleteItemProps): UseDeleteItem => {
|
|
31
|
+
const queryClient = useQueryClient();
|
|
32
|
+
|
|
33
|
+
const { showSuccessMessage } = useSnackbar();
|
|
34
|
+
|
|
35
|
+
const { mutateAsync, isMutating } = useMutationQuery<object, ItemToDelete>({
|
|
36
|
+
getEndpoint: (_meta) => deleteEndpoint(_meta),
|
|
37
|
+
method: Method.DELETE,
|
|
38
|
+
onSuccess: (_data, { _meta }) => {
|
|
39
|
+
queryClient.invalidateQueries({ queryKey: [listingQueryKey] });
|
|
40
|
+
showSuccessMessage(
|
|
41
|
+
isAFunction(successMessage) ? successMessage(_meta) : successMessage
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const deleteItem = (item: ItemToDelete): Promise<object | ResponseError> =>
|
|
47
|
+
mutateAsync({ _meta: item });
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
isMutating,
|
|
51
|
+
deleteItem
|
|
52
|
+
};
|
|
53
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { equals, isNotNil } from 'ramda';
|
|
2
|
+
import { useFetchQuery } from '../../..';
|
|
3
|
+
import { GetItem } from '../models';
|
|
4
|
+
|
|
5
|
+
interface UseGetItem<TItemForm> {
|
|
6
|
+
initialValues?: TItemForm;
|
|
7
|
+
isLoading: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const useGetItem = <
|
|
11
|
+
TItem extends { id: number; name: string },
|
|
12
|
+
TItemForm
|
|
13
|
+
>({
|
|
14
|
+
id,
|
|
15
|
+
decoder,
|
|
16
|
+
baseEndpoint,
|
|
17
|
+
itemQueryKey,
|
|
18
|
+
adapter
|
|
19
|
+
}: GetItem<TItem, TItemForm> & {
|
|
20
|
+
id: number | 'add' | null;
|
|
21
|
+
}): UseGetItem<TItemForm> => {
|
|
22
|
+
const { data, isLoading } = useFetchQuery<TItem>({
|
|
23
|
+
getEndpoint: () => baseEndpoint(id),
|
|
24
|
+
getQueryKey: () => [itemQueryKey, id],
|
|
25
|
+
decoder,
|
|
26
|
+
queryOptions: {
|
|
27
|
+
enabled: isNotNil(id) && !equals('add', id),
|
|
28
|
+
suspense: false
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
initialValues: data ? adapter(data) : undefined,
|
|
34
|
+
isLoading
|
|
35
|
+
};
|
|
36
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useAtomValue } from 'jotai';
|
|
2
|
+
import { isEmpty } from 'ramda';
|
|
3
|
+
import { ListingModel, buildListingEndpoint } from '../../..';
|
|
4
|
+
import useFetchQuery from '../../../api/useFetchQuery';
|
|
5
|
+
import {
|
|
6
|
+
limitAtom,
|
|
7
|
+
pageAtom,
|
|
8
|
+
searchAtom,
|
|
9
|
+
sortFieldAtom,
|
|
10
|
+
sortOrderAtom
|
|
11
|
+
} from '../atoms';
|
|
12
|
+
import { UseGetItemsProps, UseGetItemsState } from '../models';
|
|
13
|
+
import { useListingQueryKey } from './useListingQueryKey';
|
|
14
|
+
|
|
15
|
+
export const useGetItems = <TData, TFilters>({
|
|
16
|
+
queryKeyName,
|
|
17
|
+
filtersAtom,
|
|
18
|
+
decoder,
|
|
19
|
+
getSearchParameters,
|
|
20
|
+
baseEndpoint
|
|
21
|
+
}: UseGetItemsProps<TData, TFilters>): UseGetItemsState<TData> => {
|
|
22
|
+
const queryKey = useListingQueryKey({ queryKeyName, filtersAtom });
|
|
23
|
+
|
|
24
|
+
const page = useAtomValue(pageAtom);
|
|
25
|
+
const limit = useAtomValue(limitAtom);
|
|
26
|
+
const search = useAtomValue(searchAtom);
|
|
27
|
+
const sortOrder = useAtomValue(sortOrderAtom);
|
|
28
|
+
const sortField = useAtomValue(sortFieldAtom);
|
|
29
|
+
const filters = useAtomValue(filtersAtom);
|
|
30
|
+
|
|
31
|
+
const { data, isLoading } = useFetchQuery<ListingModel<TData>>({
|
|
32
|
+
decoder,
|
|
33
|
+
getQueryKey: () => queryKey,
|
|
34
|
+
getEndpoint: () =>
|
|
35
|
+
buildListingEndpoint({
|
|
36
|
+
baseEndpoint,
|
|
37
|
+
parameters: {
|
|
38
|
+
page: page + 1,
|
|
39
|
+
limit,
|
|
40
|
+
sort: {
|
|
41
|
+
[sortField]: sortOrder
|
|
42
|
+
},
|
|
43
|
+
search: {
|
|
44
|
+
regex: {
|
|
45
|
+
fields: ['name'],
|
|
46
|
+
value: search
|
|
47
|
+
},
|
|
48
|
+
...getSearchParameters({ filters, search })
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}),
|
|
52
|
+
queryOptions: {
|
|
53
|
+
suspense: false
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const items = data?.result || [];
|
|
58
|
+
const hasItems = !!data;
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
items,
|
|
62
|
+
isDataEmpty: isEmpty(items),
|
|
63
|
+
hasItems,
|
|
64
|
+
isLoading,
|
|
65
|
+
total: data?.meta.total || 0
|
|
66
|
+
};
|
|
67
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useAtomValue } from 'jotai';
|
|
2
|
+
import {
|
|
3
|
+
limitAtom,
|
|
4
|
+
pageAtom,
|
|
5
|
+
searchAtom,
|
|
6
|
+
sortFieldAtom,
|
|
7
|
+
sortOrderAtom
|
|
8
|
+
} from '../atoms';
|
|
9
|
+
import { UseListingQueryKeyProps } from '../models';
|
|
10
|
+
|
|
11
|
+
export const useListingQueryKey = <TFilter>({
|
|
12
|
+
filtersAtom,
|
|
13
|
+
queryKeyName
|
|
14
|
+
}: UseListingQueryKeyProps<TFilter>): Array<string | number> => {
|
|
15
|
+
const page = useAtomValue(pageAtom);
|
|
16
|
+
const limit = useAtomValue(limitAtom);
|
|
17
|
+
const search = useAtomValue(searchAtom);
|
|
18
|
+
const sortOrder = useAtomValue(sortOrderAtom);
|
|
19
|
+
const sortField = useAtomValue(sortFieldAtom);
|
|
20
|
+
const filters = useAtomValue(filtersAtom);
|
|
21
|
+
|
|
22
|
+
return [
|
|
23
|
+
queryKeyName,
|
|
24
|
+
limit,
|
|
25
|
+
page,
|
|
26
|
+
search,
|
|
27
|
+
sortField,
|
|
28
|
+
sortOrder,
|
|
29
|
+
JSON.stringify(filters)
|
|
30
|
+
];
|
|
31
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { PrimitiveAtom } from 'jotai';
|
|
2
|
+
import { ReactElement } from 'react';
|
|
3
|
+
import { JsonDecoder } from 'ts.data.json';
|
|
4
|
+
import { Column, ListingModel, SearchParameter } from '../../..';
|
|
5
|
+
import { ListingSubItems } from '../../Listing/models';
|
|
6
|
+
|
|
7
|
+
interface CrudPageRootLabels {
|
|
8
|
+
title: string;
|
|
9
|
+
welcome: {
|
|
10
|
+
title: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
};
|
|
13
|
+
actions: {
|
|
14
|
+
create: string;
|
|
15
|
+
};
|
|
16
|
+
listing: {
|
|
17
|
+
search: string;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface UseListingQueryKeyProps<TFilters> {
|
|
22
|
+
queryKeyName: string;
|
|
23
|
+
filtersAtom: PrimitiveAtom<TFilters>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface UseGetItemsProps<TData, TFilters> {
|
|
27
|
+
queryKeyName: string;
|
|
28
|
+
filtersAtom: PrimitiveAtom<TFilters>;
|
|
29
|
+
decoder?: JsonDecoder.Decoder<ListingModel<TData>>;
|
|
30
|
+
baseEndpoint: string;
|
|
31
|
+
getSearchParameters: ({
|
|
32
|
+
search,
|
|
33
|
+
filters
|
|
34
|
+
}: { search: string; filters: TFilters }) => SearchParameter;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface UseGetItemsState<TData> {
|
|
38
|
+
items: Array<TData>;
|
|
39
|
+
hasItems: boolean;
|
|
40
|
+
isDataEmpty: boolean;
|
|
41
|
+
isLoading: boolean;
|
|
42
|
+
total: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface DeleteItem {
|
|
46
|
+
deleteEndpoint: (item: ItemToDelete) => string;
|
|
47
|
+
modalSize?: 'small' | 'medium' | 'large' | 'xlarge';
|
|
48
|
+
labels: {
|
|
49
|
+
successMessage:
|
|
50
|
+
| ((item: ItemToDelete) => string | ReactElement)
|
|
51
|
+
| string
|
|
52
|
+
| ReactElement;
|
|
53
|
+
title:
|
|
54
|
+
| ((item: ItemToDelete) => string | ReactElement)
|
|
55
|
+
| string
|
|
56
|
+
| ReactElement;
|
|
57
|
+
description:
|
|
58
|
+
| ((item: ItemToDelete) => string | ReactElement)
|
|
59
|
+
| string
|
|
60
|
+
| ReactElement;
|
|
61
|
+
cancel: string;
|
|
62
|
+
confirm: string;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface GetItem<TItem, TItemForm> {
|
|
67
|
+
baseEndpoint: (id: number) => string;
|
|
68
|
+
decoder?: JsonDecoder.Decoder<TItem>;
|
|
69
|
+
adapter: (item: TItem) => TItemForm;
|
|
70
|
+
itemQueryKey: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface Form<TItem, TItemForm> {
|
|
74
|
+
Form: (props: {
|
|
75
|
+
Buttons: () => JSX.Element;
|
|
76
|
+
initialValues?: TItemForm;
|
|
77
|
+
isLoading?: boolean;
|
|
78
|
+
}) => JSX.Element;
|
|
79
|
+
getItem: GetItem<TItem, TItemForm>;
|
|
80
|
+
modalSize?: 'small' | 'medium' | 'large' | 'xlarge';
|
|
81
|
+
labels: {
|
|
82
|
+
add: {
|
|
83
|
+
title: string;
|
|
84
|
+
cancel: string;
|
|
85
|
+
confirm: string;
|
|
86
|
+
};
|
|
87
|
+
update: {
|
|
88
|
+
title: string;
|
|
89
|
+
cancel: string;
|
|
90
|
+
confirm: string;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface ListingProps<TData> {
|
|
96
|
+
rows: Array<TData>;
|
|
97
|
+
total: number;
|
|
98
|
+
isLoading: boolean;
|
|
99
|
+
columns: Array<Column>;
|
|
100
|
+
subItems?: ListingSubItems & {
|
|
101
|
+
canDeleteSubItems?: boolean;
|
|
102
|
+
};
|
|
103
|
+
filters: JSX.Element;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface ItemToDelete {
|
|
107
|
+
id: number;
|
|
108
|
+
name: string;
|
|
109
|
+
parent?: { id: number; name: string };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface CrudPageRootProps<TData, TFilters, TItem, TItemForm>
|
|
113
|
+
extends UseGetItemsProps<TData, TFilters>,
|
|
114
|
+
Omit<ListingProps<TData>, 'rows' | 'total' | 'isLoading'> {
|
|
115
|
+
form: Form<TItem, TItemForm>;
|
|
116
|
+
labels: CrudPageRootLabels;
|
|
117
|
+
deleteItem: DeleteItem;
|
|
118
|
+
}
|
|
@@ -48,6 +48,23 @@ export const AsEmptyState: Story = {
|
|
|
48
48
|
}
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
+
export const AsEmptyStateWithDescription: Story = {
|
|
52
|
+
args: {
|
|
53
|
+
children: (
|
|
54
|
+
<DataTable.EmptyState
|
|
55
|
+
labels={{
|
|
56
|
+
actions: {
|
|
57
|
+
create: 'Create item'
|
|
58
|
+
},
|
|
59
|
+
title: 'No items found',
|
|
60
|
+
description: 'Description'
|
|
61
|
+
}}
|
|
62
|
+
/>
|
|
63
|
+
),
|
|
64
|
+
isEmpty: true
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
51
68
|
export const withFixedHeightContainer: Story = {
|
|
52
69
|
args: { ...Default.args },
|
|
53
70
|
render: (args) => (
|
|
@@ -8,7 +8,7 @@ const useStyles = makeStyles()((theme) => ({
|
|
|
8
8
|
},
|
|
9
9
|
display: 'grid',
|
|
10
10
|
gridGap: theme.spacing(2.5),
|
|
11
|
-
gridTemplateColumns: `repeat(auto-fill, ${theme.spacing(
|
|
11
|
+
gridTemplateColumns: `repeat(auto-fill, ${theme.spacing(53)})`
|
|
12
12
|
},
|
|
13
13
|
'&[data-variant="listing"]': {
|
|
14
14
|
height: '100%'
|
|
@@ -10,7 +10,6 @@ const useStyles = makeStyles()((theme) => ({
|
|
|
10
10
|
display: 'flex',
|
|
11
11
|
flexDirection: 'column',
|
|
12
12
|
gap: theme.spacing(4),
|
|
13
|
-
|
|
14
13
|
h2: {
|
|
15
14
|
color: theme.palette.text.primary,
|
|
16
15
|
font: 'normal normal 600 34px/36px Roboto',
|
|
@@ -20,6 +19,9 @@ const useStyles = makeStyles()((theme) => ({
|
|
|
20
19
|
justifyContent: 'center',
|
|
21
20
|
minHeight: '30vh',
|
|
22
21
|
width: '100%'
|
|
22
|
+
},
|
|
23
|
+
description: {
|
|
24
|
+
maxWidth: '65%'
|
|
23
25
|
}
|
|
24
26
|
}));
|
|
25
27
|
|
|
@@ -16,6 +16,7 @@ type ListEmptyStateProps = {
|
|
|
16
16
|
create: string;
|
|
17
17
|
};
|
|
18
18
|
title: string;
|
|
19
|
+
description?: string;
|
|
19
20
|
};
|
|
20
21
|
onCreate?: () => void;
|
|
21
22
|
};
|
|
@@ -34,6 +35,11 @@ const DataTableEmptyState = ({
|
|
|
34
35
|
data-testid="data-table-empty-state"
|
|
35
36
|
>
|
|
36
37
|
<MuiTypography variant="h2">{t(labels.title)}</MuiTypography>
|
|
38
|
+
{labels.description && (
|
|
39
|
+
<MuiTypography className={classes.description}>
|
|
40
|
+
{t(labels.description)}
|
|
41
|
+
</MuiTypography>
|
|
42
|
+
)}
|
|
37
43
|
<div className={classes.actions}>
|
|
38
44
|
{canCreate && (
|
|
39
45
|
<Button
|
|
@@ -6,6 +6,16 @@ const useStyles = makeStyles()((theme) => ({
|
|
|
6
6
|
flexDirection: 'row',
|
|
7
7
|
justifyContent: 'space-between'
|
|
8
8
|
},
|
|
9
|
+
cardActions: {
|
|
10
|
+
backgroundColor: theme.palette.background.paper,
|
|
11
|
+
bottom: 0,
|
|
12
|
+
position: 'absolute',
|
|
13
|
+
width: '100%'
|
|
14
|
+
},
|
|
15
|
+
cardContent: {
|
|
16
|
+
padding: theme.spacing(2),
|
|
17
|
+
zIndex: 1
|
|
18
|
+
},
|
|
9
19
|
dataTableItem: {
|
|
10
20
|
'& .MuiCardActionArea-root': {
|
|
11
21
|
alignItems: 'flex-start',
|
|
@@ -22,16 +32,32 @@ const useStyles = makeStyles()((theme) => ({
|
|
|
22
32
|
display: 'flex',
|
|
23
33
|
justifyContent: 'space-between'
|
|
24
34
|
},
|
|
35
|
+
'&:hover img[alt*="thumbnail"]': {
|
|
36
|
+
transform: 'scale(1.1)',
|
|
37
|
+
transformOrigin: 'center'
|
|
38
|
+
},
|
|
25
39
|
borderRadius: theme.shape.borderRadius,
|
|
26
40
|
display: 'flex',
|
|
27
41
|
flexDirection: 'column',
|
|
28
|
-
height: '
|
|
42
|
+
height: '250px',
|
|
29
43
|
justifyContent: 'space-between',
|
|
30
44
|
p: {
|
|
31
45
|
color: theme.palette.text.secondary,
|
|
32
46
|
letterSpacing: '0',
|
|
33
47
|
margin: '0'
|
|
34
|
-
}
|
|
48
|
+
},
|
|
49
|
+
position: 'relative'
|
|
50
|
+
},
|
|
51
|
+
description: {
|
|
52
|
+
maxHeight: '42px',
|
|
53
|
+
overflow: 'hidden'
|
|
54
|
+
},
|
|
55
|
+
thumbnail: {
|
|
56
|
+
height: theme.spacing(10.25),
|
|
57
|
+
objectFit: 'cover',
|
|
58
|
+
objectPosition: 'top',
|
|
59
|
+
transition: 'transform 150ms ease-out',
|
|
60
|
+
width: '100%'
|
|
35
61
|
}
|
|
36
62
|
}));
|
|
37
63
|
|
|
@@ -16,6 +16,7 @@ export interface DataTableItemProps {
|
|
|
16
16
|
hasActions?: boolean;
|
|
17
17
|
hasCardAction?: boolean;
|
|
18
18
|
onClick?: () => void;
|
|
19
|
+
thumbnail?: string | null;
|
|
19
20
|
title: string;
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -27,7 +28,8 @@ const DataTableItem = forwardRef(
|
|
|
27
28
|
hasCardAction = false,
|
|
28
29
|
hasActions = false,
|
|
29
30
|
onClick,
|
|
30
|
-
Actions
|
|
31
|
+
Actions,
|
|
32
|
+
thumbnail
|
|
31
33
|
}: DataTableItemProps,
|
|
32
34
|
ref
|
|
33
35
|
): ReactElement => {
|
|
@@ -46,15 +48,28 @@ const DataTableItem = forwardRef(
|
|
|
46
48
|
variant="outlined"
|
|
47
49
|
>
|
|
48
50
|
<ActionArea aria-label="view" onClick={() => onClick?.()}>
|
|
49
|
-
|
|
51
|
+
{thumbnail && (
|
|
52
|
+
<img
|
|
53
|
+
alt={`thumbnail-${title}-${description}`}
|
|
54
|
+
className={classes.thumbnail}
|
|
55
|
+
data-testId={`thumbnail-${title}-${description}`}
|
|
56
|
+
loading="lazy"
|
|
57
|
+
src={thumbnail}
|
|
58
|
+
/>
|
|
59
|
+
)}
|
|
60
|
+
<MuiCardContent className={classes.cardContent}>
|
|
50
61
|
<MuiTypography fontWeight={500} variant="h5">
|
|
51
62
|
{title}
|
|
52
63
|
</MuiTypography>
|
|
53
|
-
{description &&
|
|
64
|
+
{description && (
|
|
65
|
+
<MuiTypography className={classes.description}>
|
|
66
|
+
{description}
|
|
67
|
+
</MuiTypography>
|
|
68
|
+
)}
|
|
54
69
|
</MuiCardContent>
|
|
55
70
|
</ActionArea>
|
|
56
71
|
{hasActions && (
|
|
57
|
-
<MuiCardActions>
|
|
72
|
+
<MuiCardActions className={classes.cardActions}>
|
|
58
73
|
<span />
|
|
59
74
|
<span>{Actions}</span>
|
|
60
75
|
</MuiCardActions>
|
|
@@ -2,8 +2,10 @@ import { makeStyles } from 'tss-react/mui';
|
|
|
2
2
|
|
|
3
3
|
export const useStyles = makeStyles()((theme) => ({
|
|
4
4
|
pageLayout: {
|
|
5
|
+
height: '100%',
|
|
5
6
|
display: 'grid',
|
|
6
|
-
gridTemplateRows: '
|
|
7
|
+
gridTemplateRows: 'auto 1fr',
|
|
8
|
+
height: '100%',
|
|
7
9
|
overflow: 'hidden'
|
|
8
10
|
},
|
|
9
11
|
pageLayoutActions: {
|
|
@@ -22,8 +24,11 @@ export const useStyles = makeStyles()((theme) => ({
|
|
|
22
24
|
'&[data-has-background="true"]': {
|
|
23
25
|
backgroundColor: theme.palette.layout.body.background
|
|
24
26
|
},
|
|
27
|
+
'&[data-has-actions="true"]': {
|
|
28
|
+
gridTemplateRows: 'min-content auto',
|
|
29
|
+
},
|
|
25
30
|
display: 'grid',
|
|
26
|
-
gridTemplateRows: '
|
|
31
|
+
gridTemplateRows: 'auto',
|
|
27
32
|
overflow: 'hidden',
|
|
28
33
|
padding: theme.spacing(1.5, 3, 5)
|
|
29
34
|
},
|
|
@@ -3,10 +3,10 @@ import { Zoom as VisxZoom } from '@visx/zoom';
|
|
|
3
3
|
import { ParentSize } from '../..';
|
|
4
4
|
|
|
5
5
|
import ZoomContent from './ZoomContent';
|
|
6
|
-
import { MinimapPosition } from './models';
|
|
6
|
+
import type { MinimapPosition } from './models';
|
|
7
7
|
|
|
8
8
|
export interface ZoomProps {
|
|
9
|
-
children: JSX.Element | (({ width, height }) => JSX.Element);
|
|
9
|
+
children: JSX.Element | (({ width, height, transformMatrix }) => JSX.Element);
|
|
10
10
|
id?: number | string;
|
|
11
11
|
minimapPosition?: MinimapPosition;
|
|
12
12
|
scaleMax?: number;
|