@axinom/mosaic-ui 0.61.0 → 0.62.0-rc.2
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/Accordion/Accordion.d.ts.map +1 -1
- package/dist/components/Explorer/BulkEdit/BulkEdit.model.d.ts +22 -0
- package/dist/components/Explorer/BulkEdit/BulkEdit.model.d.ts.map +1 -0
- package/dist/components/Explorer/BulkEdit/BulkEditContext.d.ts +7 -0
- package/dist/components/Explorer/BulkEdit/BulkEditContext.d.ts.map +1 -0
- package/dist/components/Explorer/BulkEdit/FormFieldsConfigConverter.d.ts +10 -0
- package/dist/components/Explorer/BulkEdit/FormFieldsConfigConverter.d.ts.map +1 -0
- package/dist/components/Explorer/BulkEdit/GenerateMutation.d.ts +4 -0
- package/dist/components/Explorer/BulkEdit/GenerateMutation.d.ts.map +1 -0
- package/dist/components/Explorer/BulkEdit/index.d.ts +6 -0
- package/dist/components/Explorer/BulkEdit/index.d.ts.map +1 -0
- package/dist/components/Explorer/BulkEdit/useBulkEdit.d.ts +16 -0
- package/dist/components/Explorer/BulkEdit/useBulkEdit.d.ts.map +1 -0
- package/dist/components/Explorer/Explorer.d.ts +4 -1
- package/dist/components/Explorer/Explorer.d.ts.map +1 -1
- package/dist/components/Explorer/Explorer.model.d.ts +13 -0
- package/dist/components/Explorer/Explorer.model.d.ts.map +1 -1
- package/dist/components/Explorer/QuickEdit/useQuickEdit.d.ts.map +1 -1
- package/dist/components/Explorer/helpers/useActions.d.ts +3 -1
- package/dist/components/Explorer/helpers/useActions.d.ts.map +1 -1
- package/dist/components/Explorer/helpers/useSubtitle.d.ts +13 -0
- package/dist/components/Explorer/helpers/useSubtitle.d.ts.map +1 -0
- package/dist/components/Explorer/index.d.ts +1 -0
- package/dist/components/Explorer/index.d.ts.map +1 -1
- package/dist/components/FieldSelection/FieldSelection.d.ts +7 -0
- package/dist/components/FieldSelection/FieldSelection.d.ts.map +1 -0
- package/dist/components/FieldSelection/index.d.ts +2 -0
- package/dist/components/FieldSelection/index.d.ts.map +1 -0
- package/dist/components/FormStation/FormStation.d.ts.map +1 -1
- package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts.map +1 -1
- package/dist/components/Icons/Icons.d.ts.map +1 -1
- package/dist/components/Icons/Icons.models.d.ts +47 -46
- package/dist/components/Icons/Icons.models.d.ts.map +1 -1
- package/dist/components/List/List.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/helpers/testing.d.ts +3 -1
- package/dist/helpers/testing.d.ts.map +1 -1
- 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/package.json +6 -2
- package/src/components/Accordion/Accordion.tsx +13 -11
- package/src/components/Explorer/BulkEdit/BulkEdit.model.ts +21 -0
- package/src/components/Explorer/BulkEdit/BulkEditContext.tsx +11 -0
- package/src/components/Explorer/BulkEdit/FormFieldsConfigConverter.spec.tsx +162 -0
- package/src/components/Explorer/BulkEdit/FormFieldsConfigConverter.tsx +45 -0
- package/src/components/Explorer/BulkEdit/GenerateMutation.spec.tsx +141 -0
- package/src/components/Explorer/BulkEdit/GenerateMutation.tsx +90 -0
- package/src/components/Explorer/BulkEdit/index.ts +8 -0
- package/src/components/Explorer/BulkEdit/useBulkEdit.tsx +132 -0
- package/src/components/Explorer/Explorer.model.ts +14 -0
- package/src/components/Explorer/Explorer.stories.tsx +82 -0
- package/src/components/Explorer/Explorer.tsx +48 -57
- package/src/components/Explorer/QuickEdit/useQuickEdit.tsx +3 -1
- package/src/components/Explorer/helpers/useActions.ts +21 -5
- package/src/components/Explorer/helpers/useFilters.spec.tsx +140 -0
- package/src/components/Explorer/helpers/useStationMessage.spec.tsx +91 -0
- package/src/components/Explorer/helpers/useSubtitle.spec.tsx +115 -0
- package/src/components/Explorer/helpers/useSubtitle.tsx +52 -0
- package/src/components/Explorer/index.ts +10 -0
- package/src/components/FieldSelection/FieldSelection.scss +18 -0
- package/src/components/FieldSelection/FieldSelection.spec.tsx +62 -0
- package/src/components/FieldSelection/FieldSelection.stories.tsx +30 -0
- package/src/components/FieldSelection/FieldSelection.tsx +154 -0
- package/src/components/FieldSelection/index.ts +1 -0
- package/src/components/FormStation/FormStation.tsx +8 -4
- package/src/components/FormStation/FormStationHeader/FormStationHeader.tsx +22 -3
- package/src/components/Icons/Icons.models.ts +1 -0
- package/src/components/Icons/Icons.tsx +17 -0
- package/src/components/List/List.tsx +11 -0
- package/src/components/index.ts +1 -0
|
@@ -5,6 +5,7 @@ import { IconName } from '../Icons';
|
|
|
5
5
|
import { SortData } from '../List';
|
|
6
6
|
import { PageHeaderJsActionProps } from '../PageHeader/PageHeaderAction/PageHeaderAction.model';
|
|
7
7
|
import { ErrorType } from '../models';
|
|
8
|
+
import { BulkEditConfig } from './BulkEdit';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Item selection can have two modes:
|
|
@@ -123,3 +124,16 @@ export interface QuickEditRegistration<T> {
|
|
|
123
124
|
*/
|
|
124
125
|
generateDetailsLink?: ((item: T) => LocationDescriptor<unknown>) | false;
|
|
125
126
|
}
|
|
127
|
+
|
|
128
|
+
export interface BulkEditRegistration<T extends Data> {
|
|
129
|
+
/** The label of the action. */
|
|
130
|
+
label: string;
|
|
131
|
+
/** Optional built in icon. This prop also accepts an img src. */
|
|
132
|
+
icon?: IconName | string;
|
|
133
|
+
/** Component to render. This will override the component that is generated. */
|
|
134
|
+
component?: JSX.Element;
|
|
135
|
+
/** Bulk Edit Configuration */
|
|
136
|
+
config?: BulkEditConfig;
|
|
137
|
+
/** Function which will be called when save button is clicked */
|
|
138
|
+
saveData: (data: T, items: ItemSelection<T>) => Promise<void>;
|
|
139
|
+
}
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from '../FormStation';
|
|
22
22
|
import { IconName } from '../Icons';
|
|
23
23
|
import { ListSelectMode } from '../List';
|
|
24
|
+
import { generateBulkEditMutation } from './BulkEdit/GenerateMutation';
|
|
24
25
|
import { Explorer } from './Explorer';
|
|
25
26
|
import { QuickEditContext } from './QuickEdit/QuickEditContext';
|
|
26
27
|
import {
|
|
@@ -471,6 +472,7 @@ const QuickEditComponent2: React.FC = () => {
|
|
|
471
472
|
export const QuickEdit: StoryObj<ExplorerStoryType> = {
|
|
472
473
|
args: {
|
|
473
474
|
...Default.args,
|
|
475
|
+
generateItemLink: (item) => `/details/${item.id}`,
|
|
474
476
|
stationKey: 'StoryBookExplorer_QuickEdit',
|
|
475
477
|
quickEditRegistrations: [
|
|
476
478
|
{
|
|
@@ -484,3 +486,83 @@ export const QuickEdit: StoryObj<ExplorerStoryType> = {
|
|
|
484
486
|
],
|
|
485
487
|
},
|
|
486
488
|
};
|
|
489
|
+
|
|
490
|
+
const BulkEditImagesAsyncFormFieldsConfig = {
|
|
491
|
+
mutation: 'bulkEditImagesAsync',
|
|
492
|
+
keys: {
|
|
493
|
+
add: 'relatedEntitiesToAdd',
|
|
494
|
+
remove: 'relatedEntitiesToRemove',
|
|
495
|
+
set: 'set',
|
|
496
|
+
filter: 'filter',
|
|
497
|
+
},
|
|
498
|
+
fields: {
|
|
499
|
+
imagesTagsAdd: {
|
|
500
|
+
type: [
|
|
501
|
+
{
|
|
502
|
+
name: 'String!',
|
|
503
|
+
},
|
|
504
|
+
],
|
|
505
|
+
label: 'Images Tags ( Add )',
|
|
506
|
+
originalFieldName: 'imagesTags',
|
|
507
|
+
action: 'relatedEntitiesToAdd',
|
|
508
|
+
},
|
|
509
|
+
imagesTagsRemove: {
|
|
510
|
+
type: [
|
|
511
|
+
{
|
|
512
|
+
name: 'String!',
|
|
513
|
+
},
|
|
514
|
+
],
|
|
515
|
+
label: 'Images Tags ( Remove )',
|
|
516
|
+
originalFieldName: 'imagesTags',
|
|
517
|
+
action: 'relatedEntitiesToRemove',
|
|
518
|
+
},
|
|
519
|
+
altText: {
|
|
520
|
+
type: 'String',
|
|
521
|
+
label: 'Alt Text',
|
|
522
|
+
originalFieldName: 'altText',
|
|
523
|
+
action: 'set',
|
|
524
|
+
},
|
|
525
|
+
focalX: {
|
|
526
|
+
type: 'BigFloat',
|
|
527
|
+
label: 'Focal X',
|
|
528
|
+
originalFieldName: 'focalX',
|
|
529
|
+
action: 'set',
|
|
530
|
+
},
|
|
531
|
+
focalY: {
|
|
532
|
+
type: 'BigFloat',
|
|
533
|
+
label: 'Focal Y',
|
|
534
|
+
originalFieldName: 'focalY',
|
|
535
|
+
action: 'set',
|
|
536
|
+
},
|
|
537
|
+
isArchived: {
|
|
538
|
+
type: 'Boolean',
|
|
539
|
+
label: 'Is Archived',
|
|
540
|
+
originalFieldName: 'isArchived',
|
|
541
|
+
action: 'set',
|
|
542
|
+
},
|
|
543
|
+
title: {
|
|
544
|
+
type: 'String',
|
|
545
|
+
label: 'Title',
|
|
546
|
+
originalFieldName: 'title',
|
|
547
|
+
action: 'set',
|
|
548
|
+
},
|
|
549
|
+
},
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
export const BulkEdit: StoryObj<ExplorerStoryType> = {
|
|
553
|
+
args: {
|
|
554
|
+
...Default.args,
|
|
555
|
+
stationKey: 'StoryBookExplorer_QuickEdit',
|
|
556
|
+
bulkEditRegistration: {
|
|
557
|
+
label: 'Bulk Edit',
|
|
558
|
+
config: BulkEditImagesAsyncFormFieldsConfig,
|
|
559
|
+
saveData: async (data, items) => {
|
|
560
|
+
// eslint-disable-next-line no-console
|
|
561
|
+
console.log(
|
|
562
|
+
generateBulkEditMutation(BulkEditImagesAsyncFormFieldsConfig, data),
|
|
563
|
+
);
|
|
564
|
+
action('Save')(data, items);
|
|
565
|
+
},
|
|
566
|
+
},
|
|
567
|
+
},
|
|
568
|
+
};
|
|
@@ -24,8 +24,10 @@ import {
|
|
|
24
24
|
import { PageHeader, PageHeaderActionProps } from '../PageHeader';
|
|
25
25
|
import { getState, storeState } from '../Utils/State/GlobalState';
|
|
26
26
|
import { ErrorType } from '../models';
|
|
27
|
+
import { useBulkEdit } from './BulkEdit/';
|
|
27
28
|
import { ConditionalSplit } from './ConditionalSplit/ConditionalSplit';
|
|
28
29
|
import {
|
|
30
|
+
BulkEditRegistration,
|
|
29
31
|
ExplorerBulkAction,
|
|
30
32
|
ExplorerDataProvider,
|
|
31
33
|
ExplorerDataProviderConnection,
|
|
@@ -36,9 +38,10 @@ import {
|
|
|
36
38
|
import classes from './Explorer.scss';
|
|
37
39
|
import { useQuickEdit } from './QuickEdit/useQuickEdit';
|
|
38
40
|
import { useActions } from './helpers/useActions';
|
|
39
|
-
import {
|
|
41
|
+
import { useDataProvider } from './helpers/useDataProvider';
|
|
40
42
|
import { useFilters } from './helpers/useFilters';
|
|
41
43
|
import { StationMessage, useStationMessage } from './helpers/useStationMessage';
|
|
44
|
+
import { useSubtitle } from './helpers/useSubtitle';
|
|
42
45
|
|
|
43
46
|
export interface ExplorerProps<T extends Data> {
|
|
44
47
|
/** Title shown in page header */
|
|
@@ -135,6 +138,10 @@ export interface ExplorerProps<T extends Data> {
|
|
|
135
138
|
/** Quick Edit Registrations */
|
|
136
139
|
quickEditRegistrations?: QuickEditRegistration<T>[];
|
|
137
140
|
|
|
141
|
+
/** Bulk Edit Registration
|
|
142
|
+
* @experimental The feature Bulk Edit and it's underlying implementation is still in beta. */
|
|
143
|
+
bulkEditRegistration?: BulkEditRegistration<T>;
|
|
144
|
+
|
|
138
145
|
/**
|
|
139
146
|
* When set, this function is used to generate the link that the user should be navigated to for each item.
|
|
140
147
|
*
|
|
@@ -211,6 +218,7 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
|
|
|
211
218
|
setTabTitle = true,
|
|
212
219
|
|
|
213
220
|
quickEditRegistrations,
|
|
221
|
+
bulkEditRegistration,
|
|
214
222
|
|
|
215
223
|
onItemClicked,
|
|
216
224
|
onBulkActionsToggled = noop,
|
|
@@ -292,7 +300,7 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
|
|
|
292
300
|
generateItemLink,
|
|
293
301
|
});
|
|
294
302
|
|
|
295
|
-
const { mode, setIsBulkOpen } = useListSelectionMode(
|
|
303
|
+
const { mode: listSelectionMode, setIsBulkOpen } = useListSelectionMode(
|
|
296
304
|
selectionMode,
|
|
297
305
|
openBulkActionsOnStart,
|
|
298
306
|
isQuickEditMode,
|
|
@@ -342,14 +350,31 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
|
|
|
342
350
|
async (item: T) => {
|
|
343
351
|
if (isQuickEditMode) {
|
|
344
352
|
await changeSelectedItem(item);
|
|
345
|
-
}
|
|
346
|
-
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (onItemClicked !== undefined) {
|
|
356
|
+
onItemClicked(item, listSelectionMode);
|
|
347
357
|
}
|
|
348
358
|
},
|
|
349
|
-
[isQuickEditMode, onItemClicked, changeSelectedItem,
|
|
359
|
+
[isQuickEditMode, onItemClicked, changeSelectedItem, listSelectionMode],
|
|
350
360
|
);
|
|
351
361
|
|
|
352
|
-
const {
|
|
362
|
+
const {
|
|
363
|
+
BulkEditContextProvider,
|
|
364
|
+
BulkEditComponent,
|
|
365
|
+
isBulkEditMode,
|
|
366
|
+
bulkEditAction,
|
|
367
|
+
closeBulkEdit,
|
|
368
|
+
} = useBulkEdit({
|
|
369
|
+
bulkEditRegistration,
|
|
370
|
+
getBulkActionSelection,
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
const { resultsTitle } = useSubtitle<T>(
|
|
374
|
+
listSelectionMode,
|
|
375
|
+
itemSelection,
|
|
376
|
+
resultCount,
|
|
377
|
+
);
|
|
353
378
|
|
|
354
379
|
const errAction = 'An error occurred when trying to execute the operation.';
|
|
355
380
|
|
|
@@ -379,6 +404,7 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
|
|
|
379
404
|
actions,
|
|
380
405
|
bulkActions,
|
|
381
406
|
quickEditAction,
|
|
407
|
+
bulkEditAction,
|
|
382
408
|
openBulkActionsOnStart,
|
|
383
409
|
filtersVisible,
|
|
384
410
|
hasFilters: filterOptions !== undefined,
|
|
@@ -391,10 +417,21 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
|
|
|
391
417
|
onBulkActionsToggled,
|
|
392
418
|
setIsBulkOpen,
|
|
393
419
|
toggleFiltersVisible,
|
|
420
|
+
closeBulkEdit,
|
|
394
421
|
});
|
|
395
422
|
|
|
423
|
+
useEffect(() => {
|
|
424
|
+
if (listSelectionMode !== ListSelectMode.Multi) {
|
|
425
|
+
setItemSelection({ mode: 'SINGLE_ITEMS', items: [] });
|
|
426
|
+
listRef.current?.resetSelection();
|
|
427
|
+
}
|
|
428
|
+
}, [listSelectionMode]);
|
|
429
|
+
|
|
396
430
|
return (
|
|
397
|
-
<ConditionalSplit
|
|
431
|
+
<ConditionalSplit
|
|
432
|
+
shouldSplit={isQuickEditMode || isBulkEditMode}
|
|
433
|
+
minSecondarySize="500px"
|
|
434
|
+
>
|
|
398
435
|
<div
|
|
399
436
|
className={clsx(
|
|
400
437
|
classes.container,
|
|
@@ -440,7 +477,7 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
|
|
|
440
477
|
showActionButton={
|
|
441
478
|
Boolean(generateItemLink) || Boolean(onItemClicked)
|
|
442
479
|
} // or hard code to `true`?
|
|
443
|
-
selectionMode={
|
|
480
|
+
selectionMode={listSelectionMode}
|
|
444
481
|
enableSelectAll={enableSelectAll}
|
|
445
482
|
enableSelectAllDeselect={enableSelectAll && !hasMoreData}
|
|
446
483
|
loadingTriggerOffset={loadingTriggerOffset}
|
|
@@ -460,6 +497,9 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
|
|
|
460
497
|
{isQuickEditMode && (
|
|
461
498
|
<QuickEditContextProvider>{quickEditStation}</QuickEditContextProvider>
|
|
462
499
|
)}
|
|
500
|
+
{isBulkEditMode && (
|
|
501
|
+
<BulkEditContextProvider>{BulkEditComponent}</BulkEditContextProvider>
|
|
502
|
+
)}
|
|
463
503
|
</ConditionalSplit>
|
|
464
504
|
);
|
|
465
505
|
});
|
|
@@ -495,55 +535,6 @@ const useListSelectionMode = (
|
|
|
495
535
|
return { mode, setIsBulkOpen } as const;
|
|
496
536
|
};
|
|
497
537
|
|
|
498
|
-
/**
|
|
499
|
-
* Sets PageHeader subtitle
|
|
500
|
-
* @param mode current ListSelectMode
|
|
501
|
-
* @param itemSelection current item selection
|
|
502
|
-
* @param results total results
|
|
503
|
-
*/
|
|
504
|
-
function useSubtitle<T extends Data>(
|
|
505
|
-
mode: ListSelectMode,
|
|
506
|
-
itemSelection: ItemSelectEventArgs<T>,
|
|
507
|
-
results?: ResultCounts,
|
|
508
|
-
): {
|
|
509
|
-
readonly resultsTitle: string;
|
|
510
|
-
} {
|
|
511
|
-
let resultsTitle = ''; // default to an empty string while results is being fetched
|
|
512
|
-
|
|
513
|
-
if (results !== undefined) {
|
|
514
|
-
switch (true) {
|
|
515
|
-
case results.total === 1 && results.filtered === 1:
|
|
516
|
-
resultsTitle = `Showing 1 Element`;
|
|
517
|
-
break;
|
|
518
|
-
case results.total === 1 && results.filtered === 0:
|
|
519
|
-
resultsTitle = `Showing 0 of 1 Element`;
|
|
520
|
-
break;
|
|
521
|
-
case results.filtered === results.total:
|
|
522
|
-
resultsTitle = `Showing all of ${results.total} Elements`;
|
|
523
|
-
break;
|
|
524
|
-
case results.filtered === undefined:
|
|
525
|
-
resultsTitle = `Showing ${results.total} Elements`;
|
|
526
|
-
break;
|
|
527
|
-
default:
|
|
528
|
-
resultsTitle = `Showing ${results.filtered} of ${results.total} Elements`;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
// Append Selected items if list selection is in Multi mode
|
|
532
|
-
if (mode === ListSelectMode.Multi) {
|
|
533
|
-
if (itemSelection.mode === 'SINGLE_ITEMS') {
|
|
534
|
-
resultsTitle = `${resultsTitle}, Selected: ${
|
|
535
|
-
itemSelection.items?.length ?? 0
|
|
536
|
-
}`;
|
|
537
|
-
} else {
|
|
538
|
-
// Show filtered results as selected results if 'SELECT_ALL' is active
|
|
539
|
-
resultsTitle = `${resultsTitle}, Selected: ${results.filtered}`;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
return { resultsTitle } as const;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
538
|
const errMsg = (err: unknown | ErrorType, msg?: string): StationMessage => {
|
|
548
539
|
return {
|
|
549
540
|
...ErrorTypeToStationError(err as ErrorType, msg),
|
|
@@ -127,7 +127,9 @@ export const useQuickEdit = <T extends Data>({
|
|
|
127
127
|
setIsQuickEditMode(isOpen);
|
|
128
128
|
setCurrentRegistration(isOpen ? registrations[0] : undefined);
|
|
129
129
|
collapseFilters();
|
|
130
|
-
|
|
130
|
+
if (isOpen) {
|
|
131
|
+
listRef.current?.selectIndex(0);
|
|
132
|
+
}
|
|
131
133
|
}
|
|
132
134
|
},
|
|
133
135
|
actions: registrations.map((registration) => ({
|
|
@@ -20,6 +20,7 @@ interface UseActionsProps<T extends Data> {
|
|
|
20
20
|
actions?: PageHeaderActionProps[];
|
|
21
21
|
bulkActions?: ExplorerBulkAction<T>[];
|
|
22
22
|
quickEditAction?: PageHeaderActionItemProps;
|
|
23
|
+
bulkEditAction?: ExplorerBulkAction<T>;
|
|
23
24
|
openBulkActionsOnStart: boolean | undefined;
|
|
24
25
|
filtersVisible: boolean;
|
|
25
26
|
hasFilters: boolean;
|
|
@@ -34,6 +35,7 @@ interface UseActionsProps<T extends Data> {
|
|
|
34
35
|
onBulkActionsToggled: (expanded: boolean) => void;
|
|
35
36
|
setIsBulkOpen: (value: React.SetStateAction<boolean>) => void;
|
|
36
37
|
toggleFiltersVisible: () => void;
|
|
38
|
+
closeBulkEdit: () => void;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
interface UseActionsReturnType {
|
|
@@ -56,10 +58,12 @@ export const useActions = <T extends Data>({
|
|
|
56
58
|
toggleFiltersVisible,
|
|
57
59
|
quickEditAction,
|
|
58
60
|
bulkActions,
|
|
61
|
+
closeBulkEdit,
|
|
62
|
+
bulkEditAction,
|
|
59
63
|
}: UseActionsProps<T>): UseActionsReturnType => {
|
|
60
64
|
const bulkActionItems: PageHeaderJsActionProps[] = useMemo(
|
|
61
|
-
() =>
|
|
62
|
-
(bulkActions ?? []).map((action) => ({
|
|
65
|
+
() => [
|
|
66
|
+
...(bulkActions ?? []).map((action) => ({
|
|
63
67
|
...action,
|
|
64
68
|
onClick: async () => {
|
|
65
69
|
if (action.showStartedNotification !== false) {
|
|
@@ -97,7 +101,15 @@ export const useActions = <T extends Data>({
|
|
|
97
101
|
}
|
|
98
102
|
},
|
|
99
103
|
})),
|
|
100
|
-
|
|
104
|
+
...(bulkEditAction ? [bulkEditAction] : []),
|
|
105
|
+
],
|
|
106
|
+
[
|
|
107
|
+
bulkActions,
|
|
108
|
+
bulkEditAction,
|
|
109
|
+
getBulkActionSelection,
|
|
110
|
+
onReloadData,
|
|
111
|
+
setStationMessage,
|
|
112
|
+
],
|
|
101
113
|
);
|
|
102
114
|
|
|
103
115
|
const pageHeaderActions: PageHeaderActionItemProps[] = useMemo(() => {
|
|
@@ -120,7 +132,7 @@ export const useActions = <T extends Data>({
|
|
|
120
132
|
});
|
|
121
133
|
}
|
|
122
134
|
|
|
123
|
-
if (
|
|
135
|
+
if (bulkActionItems.length > 0) {
|
|
124
136
|
headerActions.push({ kind: 'spacer' });
|
|
125
137
|
|
|
126
138
|
headerActions.push({
|
|
@@ -132,6 +144,10 @@ export const useActions = <T extends Data>({
|
|
|
132
144
|
onActionsGroupToggled: async (isOpen) => {
|
|
133
145
|
setIsBulkOpen(isOpen);
|
|
134
146
|
onBulkActionsToggled(isOpen);
|
|
147
|
+
|
|
148
|
+
if (!isOpen) {
|
|
149
|
+
closeBulkEdit();
|
|
150
|
+
}
|
|
135
151
|
},
|
|
136
152
|
groupActionsDisabled:
|
|
137
153
|
itemSelection.items?.length === 0 || resultCount?.filtered === 0,
|
|
@@ -179,7 +195,7 @@ export const useActions = <T extends Data>({
|
|
|
179
195
|
actions,
|
|
180
196
|
activeFilterCount,
|
|
181
197
|
bulkActionItems,
|
|
182
|
-
|
|
198
|
+
closeBulkEdit,
|
|
183
199
|
filtersVisible,
|
|
184
200
|
hasFilters,
|
|
185
201
|
itemSelection.items?.length,
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react-hooks';
|
|
2
|
+
import { Data } from '../../../types';
|
|
3
|
+
import { FilterType, FilterValues } from '../../Filters';
|
|
4
|
+
import { getState, storeState } from '../../Utils/State/GlobalState';
|
|
5
|
+
import { useFilters } from './useFilters';
|
|
6
|
+
|
|
7
|
+
// jest.mock('../../../hooks', () => ({
|
|
8
|
+
// useExpand: jest.fn(),
|
|
9
|
+
// }));
|
|
10
|
+
|
|
11
|
+
jest.mock('../../Utils/State/GlobalState', () => ({
|
|
12
|
+
getState: jest.fn(),
|
|
13
|
+
storeState: jest.fn(),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
describe('useFilters', () => {
|
|
17
|
+
const stationKey = 'testStation';
|
|
18
|
+
const globalStateOptions = { filters: true };
|
|
19
|
+
const filterOptions: FilterType<Data>[] = [];
|
|
20
|
+
const defaultFilterValues = { filter1: 'value1' };
|
|
21
|
+
const onFiltersChange = jest.fn();
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
// (useExpand as jest.Mock).mockReturnValue({
|
|
25
|
+
// isExpanded: true,
|
|
26
|
+
// toggleExpanded: jest.fn(),
|
|
27
|
+
// collapse: jest.fn(),
|
|
28
|
+
// });
|
|
29
|
+
(getState as jest.Mock).mockReturnValue(undefined);
|
|
30
|
+
jest.clearAllMocks();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should initialize with default filter values', () => {
|
|
34
|
+
const { result } = renderHook(() =>
|
|
35
|
+
useFilters({
|
|
36
|
+
stationKey,
|
|
37
|
+
globalStateOptions,
|
|
38
|
+
filterOptions,
|
|
39
|
+
defaultFilterValues,
|
|
40
|
+
onFiltersChange,
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
expect(result.current.activeFilters).toEqual(defaultFilterValues);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should toggle filter visibility', () => {
|
|
48
|
+
const { result } = renderHook(() =>
|
|
49
|
+
useFilters({
|
|
50
|
+
stationKey,
|
|
51
|
+
globalStateOptions,
|
|
52
|
+
filterOptions,
|
|
53
|
+
defaultFilterValues,
|
|
54
|
+
onFiltersChange,
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
act(() => {
|
|
59
|
+
result.current.toggleFiltersVisible();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(result.current.filtersVisible).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should collapse filters', () => {
|
|
66
|
+
const { result } = renderHook(() =>
|
|
67
|
+
useFilters({
|
|
68
|
+
stationKey,
|
|
69
|
+
globalStateOptions,
|
|
70
|
+
filterOptions,
|
|
71
|
+
defaultFilterValues,
|
|
72
|
+
onFiltersChange,
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
act(() => {
|
|
77
|
+
result.current.collapseFilters();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(result.current.filtersVisible).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should update filters and call onFiltersChange', () => {
|
|
84
|
+
const newFilters: FilterValues<any> = { filter1: 'newValue' };
|
|
85
|
+
const { result } = renderHook(() =>
|
|
86
|
+
useFilters({
|
|
87
|
+
stationKey,
|
|
88
|
+
globalStateOptions,
|
|
89
|
+
filterOptions,
|
|
90
|
+
defaultFilterValues,
|
|
91
|
+
onFiltersChange,
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
act(() => {
|
|
96
|
+
result.current.Filters.props.children.props.onFiltersChange(newFilters);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(result.current.activeFilters).toEqual(newFilters);
|
|
100
|
+
expect(onFiltersChange).toHaveBeenCalledWith(newFilters);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should store state when filters change', () => {
|
|
104
|
+
const newFilters: FilterValues<any> = { filter1: 'newValue' };
|
|
105
|
+
const { result } = renderHook(() =>
|
|
106
|
+
useFilters({
|
|
107
|
+
stationKey,
|
|
108
|
+
globalStateOptions,
|
|
109
|
+
filterOptions,
|
|
110
|
+
defaultFilterValues,
|
|
111
|
+
onFiltersChange,
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
act(() => {
|
|
116
|
+
result.current.Filters.props.children.props.onFiltersChange(newFilters);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(storeState).toHaveBeenCalledWith(stationKey, 'filters', newFilters);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should clear state when filters are empty', () => {
|
|
123
|
+
const emptyFilters: FilterValues<any> = {};
|
|
124
|
+
const { result } = renderHook(() =>
|
|
125
|
+
useFilters({
|
|
126
|
+
stationKey,
|
|
127
|
+
globalStateOptions,
|
|
128
|
+
filterOptions,
|
|
129
|
+
defaultFilterValues,
|
|
130
|
+
onFiltersChange,
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
act(() => {
|
|
135
|
+
result.current.Filters.props.children.props.onFiltersChange(emptyFilters);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(storeState).toHaveBeenCalledWith(stationKey, 'filters', undefined);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { act, renderHook } from '@testing-library/react-hooks';
|
|
3
|
+
import { MessageBar } from '../../MessageBar/MessageBar';
|
|
4
|
+
import { StationMessage, useStationMessage } from './useStationMessage';
|
|
5
|
+
|
|
6
|
+
jest.mock('../../MessageBar/MessageBar', () => ({
|
|
7
|
+
MessageBar: jest.fn(() => null),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
describe('useStationMessage', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should initialize with undefined station message', () => {
|
|
16
|
+
const { result } = renderHook(() => useStationMessage());
|
|
17
|
+
|
|
18
|
+
expect(result.current.stationMessage).toBeUndefined();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should set a station message', () => {
|
|
22
|
+
const { result } = renderHook(() => useStationMessage());
|
|
23
|
+
const message: StationMessage = {
|
|
24
|
+
type: 'error',
|
|
25
|
+
title: 'Error',
|
|
26
|
+
body: 'An error occurred',
|
|
27
|
+
canClose: true,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
act(() => {
|
|
31
|
+
result.current.setStationMessage(message);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
expect(result.current.stationMessage).toEqual(message);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should clear the station message when onClose is called', () => {
|
|
38
|
+
const { result } = renderHook(() => useStationMessage());
|
|
39
|
+
const message: StationMessage = {
|
|
40
|
+
type: 'error',
|
|
41
|
+
title: 'Error',
|
|
42
|
+
body: 'An error occurred',
|
|
43
|
+
canClose: true,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
act(() => {
|
|
47
|
+
result.current.setStationMessage(message);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
act(() => {
|
|
51
|
+
result.current.StationMessage.props.children.props.onClose();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(result.current.stationMessage).toBeUndefined();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should render MessageBar with correct props', () => {
|
|
58
|
+
const { result } = renderHook(() => useStationMessage());
|
|
59
|
+
const message: StationMessage = {
|
|
60
|
+
type: 'error',
|
|
61
|
+
title: 'Error',
|
|
62
|
+
body: 'An error occurred',
|
|
63
|
+
canClose: true,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
act(() => {
|
|
67
|
+
result.current.setStationMessage(message);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
render(result.current.StationMessage);
|
|
71
|
+
|
|
72
|
+
expect(MessageBar).toHaveBeenCalledWith(
|
|
73
|
+
expect.objectContaining({
|
|
74
|
+
type: message.type,
|
|
75
|
+
title: message.title,
|
|
76
|
+
onClose: expect.any(Function),
|
|
77
|
+
onRetry: message.onRetry,
|
|
78
|
+
children: message.body,
|
|
79
|
+
}),
|
|
80
|
+
{},
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should not render MessageBar when stationMessage is undefined', () => {
|
|
85
|
+
const { result } = renderHook(() => useStationMessage());
|
|
86
|
+
|
|
87
|
+
render(result.current.StationMessage);
|
|
88
|
+
|
|
89
|
+
expect(MessageBar).not.toHaveBeenCalled();
|
|
90
|
+
});
|
|
91
|
+
});
|