@axinom/mosaic-ui 0.51.0-rc.9 → 0.52.0-rc.0
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/dist/components/Explorer/ConditionalSplit/ConditionalSplit.d.ts +8 -0
- package/dist/components/Explorer/ConditionalSplit/ConditionalSplit.d.ts.map +1 -0
- package/dist/components/Explorer/Explorer.d.ts +3 -1
- package/dist/components/Explorer/Explorer.d.ts.map +1 -1
- package/dist/components/Explorer/Explorer.model.d.ts +18 -1
- package/dist/components/Explorer/Explorer.model.d.ts.map +1 -1
- package/dist/components/Explorer/QuickEdit/QuickEditContext.d.ts +11 -0
- package/dist/components/Explorer/QuickEdit/QuickEditContext.d.ts.map +1 -0
- package/dist/components/Explorer/QuickEdit/useQuickEdit.d.ts +22 -0
- package/dist/components/Explorer/QuickEdit/useQuickEdit.d.ts.map +1 -0
- package/dist/components/Explorer/{InMemoryDataProvider.d.ts → helpers/InMemoryDataProvider.d.ts} +3 -3
- package/dist/components/Explorer/helpers/InMemoryDataProvider.d.ts.map +1 -0
- package/dist/components/Explorer/helpers/useActions.d.ts +31 -0
- package/dist/components/Explorer/helpers/useActions.d.ts.map +1 -0
- package/dist/components/Explorer/{useDataProvider.d.ts → helpers/useDataProvider.d.ts} +6 -6
- package/dist/components/Explorer/helpers/useDataProvider.d.ts.map +1 -0
- package/dist/components/Explorer/helpers/useFilters.d.ts +21 -0
- package/dist/components/Explorer/helpers/useFilters.d.ts.map +1 -0
- package/dist/components/Explorer/helpers/useStationMessage.d.ts +17 -0
- package/dist/components/Explorer/helpers/useStationMessage.d.ts.map +1 -0
- package/dist/components/Explorer/index.d.ts +2 -1
- package/dist/components/Explorer/index.d.ts.map +1 -1
- package/dist/components/FormStation/Create/Create.d.ts.map +1 -1
- package/dist/components/FormStation/FormStation.d.ts +4 -1
- package/dist/components/FormStation/FormStation.d.ts.map +1 -1
- package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts +1 -0
- package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts.map +1 -1
- package/dist/components/FormStation/SaveOnDemand/SaveOnDemand.d.ts +11 -0
- package/dist/components/FormStation/SaveOnDemand/SaveOnDemand.d.ts.map +1 -0
- package/dist/components/FormStation/helpers/useDataProvider.d.ts.map +1 -1
- package/dist/components/Icons/Icons.d.ts.map +1 -1
- package/dist/components/Icons/Icons.models.d.ts +28 -24
- package/dist/components/Icons/Icons.models.d.ts.map +1 -1
- package/dist/components/List/List.d.ts +1 -1
- package/dist/components/List/List.d.ts.map +1 -1
- package/dist/components/List/List.model.d.ts +4 -0
- package/dist/components/List/List.model.d.ts.map +1 -1
- package/dist/components/PageHeader/PageHeader.d.ts.map +1 -1
- package/dist/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts.map +1 -1
- package/dist/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts +2 -1
- package/dist/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts.map +1 -1
- package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts +1 -1
- package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts.map +1 -1
- package/dist/components/PageHeader/helpers/useElementWidthObserver.d.ts +6 -0
- package/dist/components/PageHeader/helpers/useElementWidthObserver.d.ts.map +1 -0
- package/dist/index.es.js +4 -4
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/initialize.d.ts +1 -1
- package/dist/initialize.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/components/EmptyStation/EmptyStation.spec.tsx +24 -0
- package/src/components/Explorer/ConditionalSplit/ConditionalSplit.tsx +23 -0
- package/src/components/Explorer/Explorer.model.ts +19 -1
- package/src/components/Explorer/Explorer.scss +4 -0
- package/src/components/Explorer/Explorer.spec.tsx +28 -3
- package/src/components/Explorer/Explorer.stories.tsx +90 -5
- package/src/components/Explorer/Explorer.tsx +149 -185
- package/src/components/Explorer/NavigationExplorer/NavigationExplorer.spec.tsx +26 -0
- package/src/components/Explorer/NavigationExplorer/NavigationExplorer.stories.tsx +2 -2
- package/src/components/Explorer/QuickEdit/QuickEditContext.tsx +16 -0
- package/src/components/Explorer/QuickEdit/useQuickEdit.spec.tsx +461 -0
- package/src/components/Explorer/QuickEdit/useQuickEdit.tsx +169 -0
- package/src/components/Explorer/SelectionExplorer/SelectionExplorer.spec.tsx +6 -0
- package/src/components/Explorer/SelectionExplorer/SelectionExplorer.stories.tsx +2 -2
- package/src/components/Explorer/{InMemoryDataProvider.ts → helpers/InMemoryDataProvider.ts} +4 -4
- package/src/components/Explorer/helpers/useActions.ts +203 -0
- package/src/components/Explorer/{useDataProvider.tsx → helpers/useDataProvider.tsx} +11 -11
- package/src/components/Explorer/helpers/useFilters.tsx +77 -0
- package/src/components/Explorer/{useStationMessage.tsx → helpers/useStationMessage.tsx} +8 -6
- package/src/components/Explorer/index.ts +10 -6
- package/src/components/FormStation/Create/Create.tsx +1 -0
- package/src/components/FormStation/FormStation.spec.tsx +62 -73
- package/src/components/FormStation/FormStation.tsx +31 -15
- package/src/components/FormStation/FormStationHeader/FormStationHeader.tsx +38 -18
- package/src/components/FormStation/SaveOnDemand/SaveOnDemand.tsx +55 -0
- package/src/components/FormStation/helpers/useDataProvider.ts +1 -8
- package/src/components/Icons/Icons.models.ts +4 -0
- package/src/components/Icons/Icons.tsx +78 -0
- package/src/components/InlineMenu/InlineMenu.spec.tsx +18 -0
- package/src/components/List/List.model.ts +5 -0
- package/src/components/List/List.tsx +29 -5
- package/src/components/List/ListRow/ListRow.spec.tsx +0 -10
- package/src/components/List/ListRow/ListRow.tsx +1 -1
- package/src/components/PageHeader/PageHeader.scss +1 -2
- package/src/components/PageHeader/PageHeader.stories.tsx +6 -2
- package/src/components/PageHeader/PageHeader.tsx +10 -16
- package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.model.ts +1 -0
- package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.scss +7 -0
- package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.tsx +1 -0
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.spec.tsx +19 -7
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.tsx +19 -12
- package/src/components/PageHeader/helpers/useElementWidthObserver.tsx +30 -0
- package/src/initialize.ts +2 -2
- package/dist/components/Explorer/InMemoryDataProvider.d.ts.map +0 -1
- package/dist/components/Explorer/useDataProvider.d.ts.map +0 -1
- package/dist/components/Explorer/useStationMessage.d.ts +0 -15
- package/dist/components/Explorer/useStationMessage.d.ts.map +0 -1
- /package/src/components/Explorer/{InMemoryDataProvider.spec.ts → helpers/InMemoryDataProvider.spec.ts} +0 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { showNotification } from '../../../initialize';
|
|
3
|
+
import { Data } from '../../../types';
|
|
4
|
+
import { ErrorTypeToStationError } from '../../../utils/ErrorTypeToStationError';
|
|
5
|
+
import { IconName } from '../../Icons';
|
|
6
|
+
import { ItemSelectEventArgs } from '../../List';
|
|
7
|
+
import { PageHeaderActionItemProps } from '../../PageHeader';
|
|
8
|
+
import {
|
|
9
|
+
PageHeaderActionProps,
|
|
10
|
+
PageHeaderActionType,
|
|
11
|
+
PageHeaderJsActionProps,
|
|
12
|
+
isPageHeaderNavigationAction,
|
|
13
|
+
} from '../../PageHeader/PageHeaderAction';
|
|
14
|
+
import { ErrorType } from '../../models';
|
|
15
|
+
import { ExplorerBulkAction, ItemSelection } from '../Explorer.model';
|
|
16
|
+
import { ResultCounts } from './useDataProvider';
|
|
17
|
+
import { StationMessage } from './useStationMessage';
|
|
18
|
+
|
|
19
|
+
interface UseActionsProps<T extends Data> {
|
|
20
|
+
actions?: PageHeaderActionProps[];
|
|
21
|
+
bulkActions?: ExplorerBulkAction<T>[];
|
|
22
|
+
quickEditAction?: PageHeaderActionItemProps;
|
|
23
|
+
openBulkActionsOnStart: boolean | undefined;
|
|
24
|
+
filtersVisible: boolean;
|
|
25
|
+
hasFilters: boolean;
|
|
26
|
+
resultCount: ResultCounts;
|
|
27
|
+
activeFilterCount: number;
|
|
28
|
+
itemSelection: ItemSelectEventArgs<T>;
|
|
29
|
+
getBulkActionSelection: () => ItemSelection<T>;
|
|
30
|
+
onReloadData: () => void;
|
|
31
|
+
setStationMessage: (
|
|
32
|
+
value: React.SetStateAction<StationMessage | undefined>,
|
|
33
|
+
) => void;
|
|
34
|
+
onBulkActionsToggled: (expanded: boolean) => void;
|
|
35
|
+
setIsBulkOpen: (value: React.SetStateAction<boolean>) => void;
|
|
36
|
+
toggleFiltersVisible: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface UseActionsReturnType {
|
|
40
|
+
readonly actions: PageHeaderActionItemProps[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const useActions = <T extends Data>({
|
|
44
|
+
getBulkActionSelection,
|
|
45
|
+
onReloadData,
|
|
46
|
+
setStationMessage,
|
|
47
|
+
actions,
|
|
48
|
+
itemSelection,
|
|
49
|
+
onBulkActionsToggled,
|
|
50
|
+
openBulkActionsOnStart,
|
|
51
|
+
resultCount,
|
|
52
|
+
setIsBulkOpen,
|
|
53
|
+
hasFilters,
|
|
54
|
+
filtersVisible,
|
|
55
|
+
activeFilterCount,
|
|
56
|
+
toggleFiltersVisible,
|
|
57
|
+
quickEditAction,
|
|
58
|
+
bulkActions,
|
|
59
|
+
}: UseActionsProps<T>): UseActionsReturnType => {
|
|
60
|
+
const bulkActionItems: PageHeaderJsActionProps[] = useMemo(
|
|
61
|
+
() =>
|
|
62
|
+
(bulkActions ?? []).map((action) => ({
|
|
63
|
+
...action,
|
|
64
|
+
onClick: async () => {
|
|
65
|
+
if (action.showStartedNotification !== false) {
|
|
66
|
+
showNotification({
|
|
67
|
+
title: `Bulk Action '${action.label}' Started`,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const result = await action.onClick(getBulkActionSelection());
|
|
73
|
+
if (result) {
|
|
74
|
+
const message = errMsg(result);
|
|
75
|
+
showNotification({
|
|
76
|
+
title:
|
|
77
|
+
typeof message.title === 'string'
|
|
78
|
+
? message.title
|
|
79
|
+
: 'An error occurred',
|
|
80
|
+
body: message.body,
|
|
81
|
+
options: {
|
|
82
|
+
type: 'error',
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
} else {
|
|
86
|
+
if (action.reloadData) {
|
|
87
|
+
onReloadData();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
setStationMessage(
|
|
92
|
+
errMsg(
|
|
93
|
+
error,
|
|
94
|
+
'An error occurred when trying to execute the bulk operation.',
|
|
95
|
+
),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
})),
|
|
100
|
+
[bulkActions, getBulkActionSelection, onReloadData, setStationMessage],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const pageHeaderActions: PageHeaderActionItemProps[] = useMemo(() => {
|
|
104
|
+
const headerActions: PageHeaderActionItemProps[] = [];
|
|
105
|
+
|
|
106
|
+
if (hasFilters) {
|
|
107
|
+
headerActions.push({
|
|
108
|
+
label:
|
|
109
|
+
activeFilterCount > 0 ? `Filters (${activeFilterCount})` : 'Filters',
|
|
110
|
+
icon: IconName.Filters,
|
|
111
|
+
kind: 'action',
|
|
112
|
+
actionType: filtersVisible
|
|
113
|
+
? PageHeaderActionType.Active
|
|
114
|
+
: PageHeaderActionType.Context,
|
|
115
|
+
onClick: async () => {
|
|
116
|
+
toggleFiltersVisible();
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
headerActions.push({ kind: 'spacer' });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (bulkActions && bulkActions.length > 0) {
|
|
124
|
+
headerActions.push({
|
|
125
|
+
label: 'Bulk Actions',
|
|
126
|
+
icon: IconName.Bulk,
|
|
127
|
+
kind: 'group',
|
|
128
|
+
actions: bulkActionItems,
|
|
129
|
+
openActionsGroupOnStart: openBulkActionsOnStart,
|
|
130
|
+
onActionsGroupToggled: async (isOpen) => {
|
|
131
|
+
setIsBulkOpen(isOpen);
|
|
132
|
+
onBulkActionsToggled(isOpen);
|
|
133
|
+
},
|
|
134
|
+
groupActionsDisabled:
|
|
135
|
+
itemSelection.items?.length === 0 || resultCount?.filtered === 0,
|
|
136
|
+
});
|
|
137
|
+
headerActions.push({ kind: 'spacer' });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (quickEditAction) {
|
|
141
|
+
headerActions.push(quickEditAction);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (actions && actions.length > 0) {
|
|
145
|
+
headerActions.push({ kind: 'spacer' });
|
|
146
|
+
|
|
147
|
+
actions?.forEach((action) => {
|
|
148
|
+
headerActions.push({
|
|
149
|
+
...(isPageHeaderNavigationAction(action)
|
|
150
|
+
? action
|
|
151
|
+
: {
|
|
152
|
+
...action,
|
|
153
|
+
onClick: async () => {
|
|
154
|
+
try {
|
|
155
|
+
const result = await action.onClick();
|
|
156
|
+
if (result) {
|
|
157
|
+
setStationMessage(errMsg(result));
|
|
158
|
+
}
|
|
159
|
+
} catch (error) {
|
|
160
|
+
setStationMessage(
|
|
161
|
+
errMsg(
|
|
162
|
+
error,
|
|
163
|
+
'An error occurred when trying to execute the operation.',
|
|
164
|
+
),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
}),
|
|
169
|
+
kind: 'action',
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return headerActions;
|
|
175
|
+
}, [
|
|
176
|
+
actions,
|
|
177
|
+
activeFilterCount,
|
|
178
|
+
bulkActionItems,
|
|
179
|
+
bulkActions,
|
|
180
|
+
filtersVisible,
|
|
181
|
+
hasFilters,
|
|
182
|
+
itemSelection.items?.length,
|
|
183
|
+
onBulkActionsToggled,
|
|
184
|
+
openBulkActionsOnStart,
|
|
185
|
+
quickEditAction,
|
|
186
|
+
resultCount?.filtered,
|
|
187
|
+
setIsBulkOpen,
|
|
188
|
+
setStationMessage,
|
|
189
|
+
toggleFiltersVisible,
|
|
190
|
+
]);
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
actions: pageHeaderActions,
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const errMsg = (err: unknown | ErrorType, msg?: string): StationMessage => {
|
|
198
|
+
return {
|
|
199
|
+
...ErrorTypeToStationError(err as ErrorType, msg),
|
|
200
|
+
canClose: true,
|
|
201
|
+
type: 'error',
|
|
202
|
+
};
|
|
203
|
+
};
|
|
@@ -7,19 +7,20 @@ import {
|
|
|
7
7
|
useRef,
|
|
8
8
|
useState,
|
|
9
9
|
} from 'react';
|
|
10
|
-
import { useUpdatingRef } from '
|
|
11
|
-
import { Data } from '
|
|
12
|
-
import { ErrorTypeToStationError } from '
|
|
13
|
-
import { FilterValues } from '
|
|
14
|
-
import { SortData } from '
|
|
15
|
-
import { ErrorType } from '
|
|
10
|
+
import { useUpdatingRef } from '../../../hooks/useUpdatingRef/useUpdatingRef';
|
|
11
|
+
import { Data } from '../../../types/data';
|
|
12
|
+
import { ErrorTypeToStationError } from '../../../utils/ErrorTypeToStationError';
|
|
13
|
+
import { FilterValues } from '../../Filters/Filters.model';
|
|
14
|
+
import { SortData } from '../../List/List.model';
|
|
15
|
+
import { ErrorType } from '../../models';
|
|
16
16
|
import {
|
|
17
17
|
ExplorerDataProvider,
|
|
18
18
|
ExplorerDataProviderConnection,
|
|
19
|
-
} from '
|
|
19
|
+
} from '../Explorer.model';
|
|
20
20
|
import { StationMessage } from './useStationMessage';
|
|
21
21
|
|
|
22
22
|
interface DataProviderReturnType<T> {
|
|
23
|
+
readonly data: T[];
|
|
23
24
|
readonly isLoading: boolean;
|
|
24
25
|
readonly resultCount: ResultCounts;
|
|
25
26
|
readonly hasMoreData: boolean;
|
|
@@ -27,7 +28,6 @@ interface DataProviderReturnType<T> {
|
|
|
27
28
|
readonly onSortChanged: (sort: SortData<T>) => void;
|
|
28
29
|
readonly onFiltersChange: (filters: FilterValues<T>) => void;
|
|
29
30
|
readonly onRequestMoreData: () => void;
|
|
30
|
-
readonly data: T[];
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export interface ResultCounts {
|
|
@@ -38,12 +38,12 @@ export interface ResultCounts {
|
|
|
38
38
|
interface DataProviderArgumentType<T extends Data> {
|
|
39
39
|
dataProvider: ExplorerDataProvider<T>;
|
|
40
40
|
explorerRef: React.ForwardedRef<ExplorerDataProviderConnection<T>>;
|
|
41
|
-
setStationMessage: React.Dispatch<
|
|
42
|
-
React.SetStateAction<StationMessage | undefined>
|
|
43
|
-
>;
|
|
44
41
|
defaultSortOrder?: SortData<T>;
|
|
45
42
|
filters?: FilterValues<T>;
|
|
46
43
|
keyProperty?: keyof T;
|
|
44
|
+
setStationMessage: React.Dispatch<
|
|
45
|
+
React.SetStateAction<StationMessage | undefined>
|
|
46
|
+
>;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
export function useDataProvider<T extends Data>({
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { useExpand } from '../../../hooks';
|
|
3
|
+
import { Data } from '../../../types';
|
|
4
|
+
import { FilterType, FilterValues } from '../../Filters';
|
|
5
|
+
import { Filters } from '../../Filters/Filters';
|
|
6
|
+
import { getState, storeState } from '../../Utils/State/GlobalState';
|
|
7
|
+
import { ExplorerStateOptions } from '../Explorer.model';
|
|
8
|
+
|
|
9
|
+
interface UseFiltersProps<T extends Data> {
|
|
10
|
+
stationKey: string;
|
|
11
|
+
globalStateOptions: ExplorerStateOptions;
|
|
12
|
+
filterOptions?: FilterType<T>[];
|
|
13
|
+
defaultFilterValues?: FilterValues<T>;
|
|
14
|
+
onFiltersChange: (filters: FilterValues<T>) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface UseFiltersReturnType<T extends Data> {
|
|
18
|
+
readonly Filters: JSX.Element;
|
|
19
|
+
readonly activeFilters: FilterValues<T>;
|
|
20
|
+
readonly filtersVisible: boolean;
|
|
21
|
+
readonly toggleFiltersVisible: () => void;
|
|
22
|
+
readonly collapseFilters: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const useFilters = <T extends Data>({
|
|
26
|
+
stationKey,
|
|
27
|
+
globalStateOptions,
|
|
28
|
+
filterOptions,
|
|
29
|
+
defaultFilterValues,
|
|
30
|
+
onFiltersChange,
|
|
31
|
+
}: UseFiltersProps<T>): UseFiltersReturnType<T> => {
|
|
32
|
+
const { isExpanded, toggleExpanded, collapse } = useExpand(true);
|
|
33
|
+
const [activeFilters, setActiveFilters] = useState<FilterValues<T>>(
|
|
34
|
+
getState<FilterValues<T>>(stationKey, 'filters') ??
|
|
35
|
+
defaultFilterValues ??
|
|
36
|
+
{},
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (
|
|
41
|
+
globalStateOptions.filters &&
|
|
42
|
+
activeFilters !== defaultFilterValues &&
|
|
43
|
+
Object.keys(activeFilters).length > 0
|
|
44
|
+
) {
|
|
45
|
+
storeState(stationKey, 'filters', activeFilters);
|
|
46
|
+
} else {
|
|
47
|
+
storeState(stationKey, 'filters', undefined);
|
|
48
|
+
}
|
|
49
|
+
}, [
|
|
50
|
+
activeFilters,
|
|
51
|
+
defaultFilterValues,
|
|
52
|
+
globalStateOptions.filters,
|
|
53
|
+
stationKey,
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
const FilterComponent = (
|
|
57
|
+
<>
|
|
58
|
+
{isExpanded && (
|
|
59
|
+
<Filters<T>
|
|
60
|
+
options={filterOptions}
|
|
61
|
+
defaultValues={activeFilters}
|
|
62
|
+
onFiltersChange={(args) => {
|
|
63
|
+
onFiltersChange(args);
|
|
64
|
+
setActiveFilters(args);
|
|
65
|
+
}}
|
|
66
|
+
/>
|
|
67
|
+
)}
|
|
68
|
+
</>
|
|
69
|
+
);
|
|
70
|
+
return {
|
|
71
|
+
Filters: FilterComponent,
|
|
72
|
+
activeFilters,
|
|
73
|
+
filtersVisible: isExpanded,
|
|
74
|
+
toggleFiltersVisible: toggleExpanded,
|
|
75
|
+
collapseFilters: collapse,
|
|
76
|
+
};
|
|
77
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
|
-
import { MessageBar, MessageBarProps } from '
|
|
2
|
+
import { MessageBar, MessageBarProps } from '../../MessageBar/MessageBar';
|
|
3
3
|
|
|
4
4
|
export interface StationMessage {
|
|
5
5
|
type: MessageBarProps['type'];
|
|
@@ -9,13 +9,15 @@ export interface StationMessage {
|
|
|
9
9
|
onRetry?: MessageBarProps['onRetry'];
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
interface UseStationMessageReturnType {
|
|
13
|
+
readonly StationMessage: JSX.Element;
|
|
14
|
+
readonly stationMessage: StationMessage | undefined;
|
|
15
|
+
readonly setStationMessage: React.Dispatch<
|
|
15
16
|
React.SetStateAction<StationMessage | undefined>
|
|
16
17
|
>;
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const useStationMessage = (): UseStationMessageReturnType => {
|
|
19
21
|
const [stationMessage, setStationMessage] = useState<StationMessage>();
|
|
20
22
|
|
|
21
23
|
const StationMessage = (
|
|
@@ -6,17 +6,21 @@ export {
|
|
|
6
6
|
ItemSelection,
|
|
7
7
|
PageIdentifier,
|
|
8
8
|
} from './Explorer.model';
|
|
9
|
-
export {
|
|
10
|
-
createInMemoryDataProvider,
|
|
11
|
-
findAnywhereInString,
|
|
12
|
-
findAnywhereInStringCaseInsensitive,
|
|
13
|
-
findExact,
|
|
14
|
-
} from './InMemoryDataProvider';
|
|
15
9
|
export {
|
|
16
10
|
NavigationExplorer,
|
|
17
11
|
NavigationExplorerProps,
|
|
18
12
|
} from './NavigationExplorer/NavigationExplorer';
|
|
13
|
+
export {
|
|
14
|
+
QuickEditContext,
|
|
15
|
+
QuickEditContextType,
|
|
16
|
+
} from './QuickEdit/QuickEditContext';
|
|
19
17
|
export {
|
|
20
18
|
SelectionExplorer,
|
|
21
19
|
SelectionExplorerProps,
|
|
22
20
|
} from './SelectionExplorer/SelectionExplorer';
|
|
21
|
+
export {
|
|
22
|
+
createInMemoryDataProvider,
|
|
23
|
+
findAnywhereInString,
|
|
24
|
+
findAnywhereInStringCaseInsensitive,
|
|
25
|
+
findExact,
|
|
26
|
+
} from './helpers/InMemoryDataProvider';
|
|
@@ -5,7 +5,7 @@ import { act } from 'react-dom/test-utils';
|
|
|
5
5
|
import { MemoryRouter, Route } from 'react-router-dom';
|
|
6
6
|
import * as Yup from 'yup';
|
|
7
7
|
import { noop } from '../../helpers/utils';
|
|
8
|
-
import {
|
|
8
|
+
import { initializeUi } from '../../initialize';
|
|
9
9
|
import { ActionData, Actions } from '../Actions';
|
|
10
10
|
import { Action } from '../Actions/Action';
|
|
11
11
|
import { MessageBar } from '../MessageBar/MessageBar';
|
|
@@ -31,7 +31,7 @@ const defaultProps = {
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
// Temporarily disable console.log() from FormStation until proper error handling
|
|
34
|
-
jest.spyOn(console, 'log').mockImplementation(() => null);
|
|
34
|
+
// jest.spyOn(console, 'log').mockImplementation(() => null);
|
|
35
35
|
jest.spyOn(console, 'error').mockImplementation(() => null);
|
|
36
36
|
|
|
37
37
|
const MakeDirty: React.FC = () => {
|
|
@@ -45,8 +45,28 @@ const MakeDirty: React.FC = () => {
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
describe('Details', () => {
|
|
48
|
+
global.ResizeObserver = jest.fn().mockImplementation(() => ({
|
|
49
|
+
observe: jest.fn(),
|
|
50
|
+
unobserve: jest.fn(),
|
|
51
|
+
disconnect: jest.fn(),
|
|
52
|
+
}));
|
|
53
|
+
|
|
48
54
|
beforeEach(() => {
|
|
49
55
|
jest.clearAllMocks();
|
|
56
|
+
|
|
57
|
+
initializeUi({
|
|
58
|
+
showNotification: () => {
|
|
59
|
+
// not implemented
|
|
60
|
+
return -1;
|
|
61
|
+
},
|
|
62
|
+
addIndicator: () => {
|
|
63
|
+
return -1;
|
|
64
|
+
},
|
|
65
|
+
removeIndicator: noop,
|
|
66
|
+
on: () => noop,
|
|
67
|
+
setTitle: noop,
|
|
68
|
+
setSaveIndicator: noop,
|
|
69
|
+
});
|
|
50
70
|
});
|
|
51
71
|
|
|
52
72
|
it('renders the component without crashing', () => {
|
|
@@ -260,7 +280,7 @@ describe('Details', () => {
|
|
|
260
280
|
expect(header.prop('title')).toBe('default');
|
|
261
281
|
});
|
|
262
282
|
|
|
263
|
-
describe('Reset and
|
|
283
|
+
describe('Reset Save and Cancel operations', () => {
|
|
264
284
|
const ChangeValue: React.FC = () => {
|
|
265
285
|
const context = useFormikContext();
|
|
266
286
|
useEffect(() => {
|
|
@@ -298,13 +318,49 @@ describe('Details', () => {
|
|
|
298
318
|
});
|
|
299
319
|
|
|
300
320
|
const headerActions = wrapper.find(PageHeaderAction);
|
|
301
|
-
expect(headerActions).toHaveLength(
|
|
321
|
+
expect(headerActions).toHaveLength(2);
|
|
302
322
|
|
|
303
323
|
headerActions.at(0).simulate('click');
|
|
304
324
|
|
|
305
325
|
expect(value).toBe('initial');
|
|
306
326
|
});
|
|
307
327
|
|
|
328
|
+
it('allows saving of a dirty form', async () => {
|
|
329
|
+
let path: string;
|
|
330
|
+
|
|
331
|
+
const wrapper = mount(
|
|
332
|
+
<MemoryRouter>
|
|
333
|
+
<FormStation
|
|
334
|
+
{...defaultProps}
|
|
335
|
+
initialData={{ loading: false, data: { something: 'initial' } }}
|
|
336
|
+
>
|
|
337
|
+
<ChangeValue />
|
|
338
|
+
</FormStation>
|
|
339
|
+
<Route
|
|
340
|
+
path="*"
|
|
341
|
+
render={({ location }) => {
|
|
342
|
+
path = location.pathname;
|
|
343
|
+
return null;
|
|
344
|
+
}}
|
|
345
|
+
/>
|
|
346
|
+
</MemoryRouter>,
|
|
347
|
+
);
|
|
348
|
+
await act(async () => {
|
|
349
|
+
wrapper.update();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const headerActions = wrapper.find(PageHeaderAction);
|
|
353
|
+
expect(headerActions).toHaveLength(2);
|
|
354
|
+
|
|
355
|
+
headerActions.at(1).simulate('click');
|
|
356
|
+
|
|
357
|
+
await act(async () => {
|
|
358
|
+
wrapper.update();
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
expect(path!).toBe('/');
|
|
362
|
+
});
|
|
363
|
+
|
|
308
364
|
it('allows cancellation if wanted', async () => {
|
|
309
365
|
let path: string;
|
|
310
366
|
|
|
@@ -336,9 +392,9 @@ describe('Details', () => {
|
|
|
336
392
|
});
|
|
337
393
|
|
|
338
394
|
const headerActions = wrapper.find(PageHeaderAction);
|
|
339
|
-
expect(headerActions).toHaveLength(
|
|
395
|
+
expect(headerActions).toHaveLength(3);
|
|
340
396
|
|
|
341
|
-
headerActions.at(
|
|
397
|
+
headerActions.at(2).simulate('click');
|
|
342
398
|
|
|
343
399
|
await act(async () => {
|
|
344
400
|
wrapper.update();
|
|
@@ -652,72 +708,5 @@ describe('Details', () => {
|
|
|
652
708
|
isDisabled = action.prop('action').isDisabled;
|
|
653
709
|
expect(isDisabled).toBe(false);
|
|
654
710
|
});
|
|
655
|
-
|
|
656
|
-
it('sets the global busy state when submitting', async () => {
|
|
657
|
-
jest.useFakeTimers();
|
|
658
|
-
const spy = jest.fn();
|
|
659
|
-
const sampleActions = mockActions(spy);
|
|
660
|
-
const onSubmit = (): Promise<{ id: number }> =>
|
|
661
|
-
new Promise((resolve) =>
|
|
662
|
-
setTimeout(() => {
|
|
663
|
-
resolve({ id: 3 });
|
|
664
|
-
}, 1000),
|
|
665
|
-
);
|
|
666
|
-
|
|
667
|
-
const wrapper = mount(
|
|
668
|
-
<MemoryRouter>
|
|
669
|
-
<FormStation
|
|
670
|
-
{...defaultProps}
|
|
671
|
-
actions={sampleActions}
|
|
672
|
-
saveData={onSubmit}
|
|
673
|
-
initialData={{ loading: false, data: {} }}
|
|
674
|
-
>
|
|
675
|
-
<MakeDirty />
|
|
676
|
-
</FormStation>
|
|
677
|
-
</MemoryRouter>,
|
|
678
|
-
);
|
|
679
|
-
expect(setSaveIndicator).toHaveBeenNthCalledWith(
|
|
680
|
-
1,
|
|
681
|
-
SaveIndicatorType.Inactive,
|
|
682
|
-
); // 1. inactive
|
|
683
|
-
expect(setSaveIndicator).toHaveBeenNthCalledWith(
|
|
684
|
-
3,
|
|
685
|
-
SaveIndicatorType.Dirty,
|
|
686
|
-
); // 3. dirty
|
|
687
|
-
|
|
688
|
-
// submit form
|
|
689
|
-
const actionSelected = wrapper
|
|
690
|
-
.find(Action)
|
|
691
|
-
.prop('action').onActionSelected;
|
|
692
|
-
|
|
693
|
-
await act(async () => {
|
|
694
|
-
actionSelected && actionSelected();
|
|
695
|
-
});
|
|
696
|
-
|
|
697
|
-
// place form into 'submitting' state
|
|
698
|
-
await act(async () => {
|
|
699
|
-
jest.advanceTimersByTime(500);
|
|
700
|
-
});
|
|
701
|
-
|
|
702
|
-
wrapper.update();
|
|
703
|
-
|
|
704
|
-
expect(setSaveIndicator).toHaveBeenNthCalledWith(
|
|
705
|
-
4,
|
|
706
|
-
SaveIndicatorType.Saving,
|
|
707
|
-
);
|
|
708
|
-
|
|
709
|
-
// complete form submission
|
|
710
|
-
await act(async () => {
|
|
711
|
-
jest.runAllTimers();
|
|
712
|
-
});
|
|
713
|
-
wrapper.update();
|
|
714
|
-
|
|
715
|
-
expect(setSaveIndicator).toHaveBeenNthCalledWith(
|
|
716
|
-
5,
|
|
717
|
-
SaveIndicatorType.Inactive,
|
|
718
|
-
);
|
|
719
|
-
|
|
720
|
-
console.warn((setSaveIndicator as jest.Mock).mock.calls);
|
|
721
|
-
});
|
|
722
711
|
});
|
|
723
712
|
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
2
|
import { Formik, FormikValues } from 'formik';
|
|
3
|
-
import React, { PropsWithChildren } from 'react';
|
|
3
|
+
import React, { PropsWithChildren, useContext } from 'react';
|
|
4
4
|
import { OptionalObjectSchema } from 'yup/lib/object';
|
|
5
5
|
import { Data } from '../../types/data';
|
|
6
|
+
import { QuickEditContext } from '../Explorer/QuickEdit/QuickEditContext';
|
|
6
7
|
import { StationMessage } from '../models';
|
|
7
8
|
import {
|
|
8
9
|
FormActionData,
|
|
@@ -14,6 +15,7 @@ import classes from './FormStation.scss';
|
|
|
14
15
|
import { FormStationAction } from './FormStationActions';
|
|
15
16
|
import { FormStationContentWrapper } from './FormStationContentWrapper';
|
|
16
17
|
import { FormStationHeader } from './FormStationHeader';
|
|
18
|
+
import { SaveOnDemand } from './SaveOnDemand/SaveOnDemand';
|
|
17
19
|
import { SaveOnNavigate } from './SaveOnNavigate/SaveOnNavigate';
|
|
18
20
|
import { useDataProvider } from './helpers/useDataProvider';
|
|
19
21
|
import { useValidationError } from './helpers/useValidationError';
|
|
@@ -30,10 +32,12 @@ export interface FormStationProps<
|
|
|
30
32
|
subtitle?: string;
|
|
31
33
|
/** If set to true, the actions panel is shown, even if no actions are defined. */
|
|
32
34
|
alwaysShowActionsPanel?: boolean;
|
|
33
|
-
|
|
35
|
+
/** An array of the actions that should be executable on the page */
|
|
34
36
|
actions?: FormActionData<TValues, TSubmitResponse>[];
|
|
35
37
|
/** The width of the Actions container (as CSS width) */
|
|
36
38
|
actionsWidth?: string;
|
|
39
|
+
/** If set to true, the save header action is shown. (default: true) */
|
|
40
|
+
showSaveHeaderAction?: boolean;
|
|
37
41
|
/**
|
|
38
42
|
* An object containing the initial data of the form.
|
|
39
43
|
*/
|
|
@@ -85,9 +89,12 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
|
|
|
85
89
|
stationMessage,
|
|
86
90
|
className = '',
|
|
87
91
|
setTabTitle = true,
|
|
92
|
+
showSaveHeaderAction = true,
|
|
88
93
|
}: PropsWithChildren<
|
|
89
94
|
FormStationProps<TValues, TSubmitResponse>
|
|
90
95
|
>): JSX.Element => {
|
|
96
|
+
const quickEditContext = useContext(QuickEditContext);
|
|
97
|
+
|
|
91
98
|
const {
|
|
92
99
|
onSubmit,
|
|
93
100
|
stationError,
|
|
@@ -127,33 +134,42 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
|
|
|
127
134
|
cancelNavigationUrl={cancelNavigationUrl}
|
|
128
135
|
className={classes.header}
|
|
129
136
|
setTabTitle={setTabTitle}
|
|
137
|
+
showSaveHeaderAction={showSaveHeaderAction}
|
|
130
138
|
/>
|
|
131
139
|
<SaveOnNavigate
|
|
132
140
|
isSubmitting={isFormSubmitting}
|
|
133
141
|
onNavigationCancelled={setValidationError}
|
|
134
142
|
/>
|
|
143
|
+
{quickEditContext && (
|
|
144
|
+
<SaveOnDemand
|
|
145
|
+
isSubmitting={isFormSubmitting}
|
|
146
|
+
onFormInvalid={setValidationError}
|
|
147
|
+
registerSaveCallback={quickEditContext.registerSaveCallback}
|
|
148
|
+
/>
|
|
149
|
+
)}
|
|
135
150
|
<FormStationContentWrapper
|
|
136
151
|
stationMessage={stationMessage}
|
|
137
152
|
edgeToEdgeContent={edgeToEdgeContent}
|
|
138
|
-
infoPanel={infoPanel}
|
|
153
|
+
infoPanel={!quickEditContext ? infoPanel : undefined}
|
|
139
154
|
initialData={initialData}
|
|
140
155
|
stationError={stationError}
|
|
141
156
|
setStationError={setStationError}
|
|
142
157
|
>
|
|
143
158
|
{children}
|
|
144
159
|
</FormStationContentWrapper>
|
|
145
|
-
{(alwaysShowActionsPanel || (actions?.length ?? 0) > 0) &&
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
160
|
+
{(alwaysShowActionsPanel || (actions?.length ?? 0) > 0) &&
|
|
161
|
+
!quickEditContext && (
|
|
162
|
+
<FormStationAction<TValues, TSubmitResponse>
|
|
163
|
+
actions={actions}
|
|
164
|
+
width={actionsWidth}
|
|
165
|
+
validationSchema={validationSchema}
|
|
166
|
+
setStationError={setStationError}
|
|
167
|
+
setValidationError={setValidationError}
|
|
168
|
+
submitResponse={lastSubmittedResponse}
|
|
169
|
+
alwaysSubmitBeforeAction={alwaysSubmitBeforeAction}
|
|
170
|
+
className={classes.actionsPanel}
|
|
171
|
+
/>
|
|
172
|
+
)}
|
|
157
173
|
</>
|
|
158
174
|
</Formik>
|
|
159
175
|
</div>
|