@firecms/core 3.0.1 → 3.1.0-canary.1df3b2c
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/README.md +1 -1
- package/dist/components/AIIcon.d.ts +16 -0
- package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +7 -1
- package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +1 -1
- package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +14 -0
- package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +6 -0
- package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +5 -4
- package/dist/components/EntityCollectionTable/internal/EntityTableCell.d.ts +6 -0
- package/dist/components/EntityCollectionView/Board.d.ts +2 -0
- package/dist/components/EntityCollectionView/BoardColumn.d.ts +42 -0
- package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +9 -0
- package/dist/components/EntityCollectionView/BoardSortableList.d.ts +14 -0
- package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +26 -0
- package/dist/components/EntityCollectionView/EntityCard.d.ts +19 -0
- package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +20 -0
- package/dist/components/EntityCollectionView/EntityCollectionCardView.d.ts +31 -0
- package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +2 -2
- package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +7 -3
- package/dist/components/EntityCollectionView/FiltersDialog.d.ts +14 -0
- package/dist/components/EntityCollectionView/ViewModeToggle.d.ts +49 -0
- package/dist/components/EntityCollectionView/board_types.d.ts +105 -0
- package/dist/components/EntityCollectionView/useBoardDataController.d.ts +60 -0
- package/dist/components/SelectableTable/SelectableTable.d.ts +5 -1
- package/dist/components/SelectableTable/filters/DateTimeFilterField.d.ts +2 -1
- package/dist/components/VirtualTable/VirtualTableCell.d.ts +6 -0
- package/dist/components/VirtualTable/VirtualTableHeader.d.ts +2 -0
- package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +11 -0
- package/dist/components/VirtualTable/fields/VirtualTableDateField.d.ts +1 -0
- package/dist/components/VirtualTable/types.d.ts +2 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/contexts/index.d.ts +10 -0
- package/dist/core/DrawerNavigationGroup.d.ts +45 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/form/validation.d.ts +3 -2
- package/dist/hooks/useBreadcrumbsController.d.ts +16 -0
- package/dist/hooks/useCollapsedGroups.d.ts +4 -1
- package/dist/index.es.js +5239 -1590
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +5233 -1585
- package/dist/index.umd.js.map +1 -1
- package/dist/preview/PropertyPreviewProps.d.ts +5 -0
- package/dist/preview/components/DatePreview.d.ts +13 -3
- package/dist/preview/components/ImagePreview.d.ts +5 -1
- package/dist/preview/components/StorageThumbnail.d.ts +2 -1
- package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
- package/dist/preview/property_previews/ArrayOfStorageComponentsPreview.d.ts +1 -1
- package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
- package/dist/preview/property_previews/SkeletonPropertyComponent.d.ts +1 -1
- package/dist/types/collections.d.ts +42 -2
- package/dist/types/datasource.d.ts +0 -1
- package/dist/types/plugins.d.ts +46 -1
- package/dist/types/properties.d.ts +259 -4
- package/dist/util/__tests__/conditions.test.d.ts +1 -0
- package/dist/util/__tests__/objects.test.d.ts +1 -0
- package/dist/util/conditions.d.ts +26 -0
- package/dist/util/entities.d.ts +1 -2
- package/dist/util/index.d.ts +2 -1
- package/dist/util/property_utils.d.ts +2 -1
- package/dist/util/resolutions.d.ts +1 -1
- package/package.json +10 -7
- package/src/app/Scaffold.tsx +14 -15
- package/src/components/AIIcon.tsx +39 -0
- package/src/components/ArrayContainer.tsx +1 -4
- package/src/components/ClearFilterSortButton.tsx +19 -16
- package/src/components/ConfirmationDialog.tsx +0 -2
- package/src/components/DeleteEntityDialog.tsx +2 -4
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +74 -41
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +130 -79
- package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +121 -104
- package/src/components/EntityCollectionTable/PropertyTableCell.tsx +132 -103
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +20 -42
- package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +90 -49
- package/src/components/EntityCollectionView/Board.tsx +324 -0
- package/src/components/EntityCollectionView/BoardColumn.tsx +158 -0
- package/src/components/EntityCollectionView/BoardColumnTitle.tsx +45 -0
- package/src/components/EntityCollectionView/BoardSortableList.tsx +172 -0
- package/src/components/EntityCollectionView/EntityBoardCard.tsx +212 -0
- package/src/components/EntityCollectionView/EntityCard.tsx +231 -0
- package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +713 -0
- package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +244 -0
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +485 -203
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +31 -19
- package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +84 -15
- package/src/components/EntityCollectionView/FiltersDialog.tsx +249 -0
- package/src/components/EntityCollectionView/ViewModeToggle.tsx +202 -0
- package/src/components/EntityCollectionView/board_types.ts +113 -0
- package/src/components/EntityCollectionView/useBoardDataController.tsx +490 -0
- package/src/components/ErrorTooltip.tsx +2 -1
- package/src/components/HomePage/DefaultHomePage.tsx +47 -10
- package/src/components/HomePage/HomePageDnD.tsx +56 -41
- package/src/components/HomePage/NavigationCard.tsx +20 -18
- package/src/components/HomePage/NavigationGroup.tsx +17 -16
- package/src/components/HomePage/RenameGroupDialog.tsx +0 -2
- package/src/components/HomePage/SmallNavigationCard.tsx +10 -9
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -10
- package/src/components/ReferenceWidget.tsx +2 -4
- package/src/components/SelectableTable/SelectableTable.tsx +75 -67
- package/src/components/SelectableTable/filters/BooleanFilterField.tsx +7 -6
- package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +39 -40
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +38 -38
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +49 -58
- package/src/components/UnsavedChangesDialog.tsx +0 -2
- package/src/components/UserDisplay.tsx +4 -4
- package/src/components/VirtualTable/VirtualTable.tsx +170 -19
- package/src/components/VirtualTable/VirtualTableCell.tsx +18 -2
- package/src/components/VirtualTable/VirtualTableHeader.tsx +20 -11
- package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +158 -42
- package/src/components/VirtualTable/VirtualTableProps.tsx +14 -1
- package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
- package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +3 -0
- package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +17 -4
- package/src/components/VirtualTable/types.tsx +2 -0
- package/src/components/common/useColumnsIds.tsx +95 -3
- package/src/components/index.tsx +4 -0
- package/src/contexts/BreacrumbsContext.tsx +15 -8
- package/src/contexts/index.ts +10 -0
- package/src/core/DefaultAppBar.tsx +39 -26
- package/src/core/DefaultDrawer.tsx +42 -56
- package/src/core/DrawerNavigationGroup.tsx +118 -0
- package/src/core/DrawerNavigationItem.tsx +4 -3
- package/src/core/EntityEditView.tsx +41 -43
- package/src/core/SideDialogs.tsx +4 -2
- package/src/core/index.tsx +1 -0
- package/src/form/PropertyFieldBinding.tsx +58 -43
- package/src/form/components/StorageItemPreview.tsx +2 -1
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +0 -1
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +17 -16
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +0 -1
- package/src/form/field_bindings/MapFieldBinding.tsx +69 -67
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +21 -17
- package/src/form/field_bindings/TextFieldBinding.tsx +71 -35
- package/src/form/validation.ts +245 -160
- package/src/hooks/useBreadcrumbsController.tsx +18 -0
- package/src/hooks/useBuildNavigationController.tsx +42 -19
- package/src/hooks/useCollapsedGroups.ts +12 -4
- package/src/internal/useBuildDataSource.ts +69 -34
- package/src/internal/useBuildSideDialogsController.tsx +11 -8
- package/src/internal/useBuildSideEntityController.tsx +2 -4
- package/src/internal/useRestoreScroll.tsx +26 -14
- package/src/preview/PropertyPreview.tsx +40 -32
- package/src/preview/PropertyPreviewProps.tsx +6 -0
- package/src/preview/components/DatePreview.tsx +72 -4
- package/src/preview/components/EmptyValue.tsx +1 -1
- package/src/preview/components/ImagePreview.tsx +37 -21
- package/src/preview/components/StorageThumbnail.tsx +16 -12
- package/src/preview/components/UrlComponentPreview.tsx +28 -25
- package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +9 -7
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +11 -9
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +26 -24
- package/src/preview/property_previews/SkeletonPropertyComponent.tsx +61 -56
- package/src/routes/CustomCMSRoute.tsx +1 -0
- package/src/routes/FireCMSRoute.tsx +26 -13
- package/src/types/collections.ts +48 -3
- package/src/types/datasource.ts +54 -56
- package/src/types/plugins.tsx +51 -1
- package/src/types/properties.ts +347 -27
- package/src/util/__tests__/conditions.test.ts +506 -0
- package/src/util/__tests__/objects.test.ts +196 -0
- package/src/util/callbacks.ts +6 -3
- package/src/util/collections.ts +51 -6
- package/src/util/conditions.ts +339 -0
- package/src/util/entities.ts +28 -29
- package/src/util/entity_cache.ts +2 -1
- package/src/util/index.ts +2 -1
- package/src/util/objects.ts +31 -13
- package/src/util/{references.ts → previews.ts} +14 -0
- package/src/util/property_utils.tsx +36 -10
- package/src/util/resolutions.ts +57 -55
- /package/dist/util/{references.d.ts → previews.d.ts} +0 -0
|
@@ -2,8 +2,19 @@ import React from "react";
|
|
|
2
2
|
|
|
3
3
|
import { canCreateEntity, canDeleteEntity } from "../../util";
|
|
4
4
|
import { useAuthController, useCustomizationController, useFireCMSContext, useLargeLayout } from "../../hooks";
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
CollectionActionsProps,
|
|
7
|
+
EntityCollection,
|
|
8
|
+
EntityTableController,
|
|
9
|
+
SelectionController
|
|
10
|
+
} from "../../types";
|
|
11
|
+
import {
|
|
12
|
+
AddIcon,
|
|
13
|
+
Button,
|
|
14
|
+
DeleteIcon,
|
|
15
|
+
IconButton,
|
|
16
|
+
Tooltip
|
|
17
|
+
} from "@firecms/ui";
|
|
7
18
|
import { toArray } from "../../util/arrays";
|
|
8
19
|
import { ErrorBoundary } from "../ErrorBoundary";
|
|
9
20
|
|
|
@@ -17,21 +28,21 @@ export type EntityCollectionViewActionsProps<M extends Record<string, any>> = {
|
|
|
17
28
|
onMultipleDeleteClick: () => void;
|
|
18
29
|
selectionController: SelectionController<M>;
|
|
19
30
|
tableController: EntityTableController<M>;
|
|
20
|
-
collectionEntitiesCount
|
|
31
|
+
collectionEntitiesCount?: number;
|
|
21
32
|
}
|
|
22
33
|
|
|
23
34
|
export function EntityCollectionViewActions<M extends Record<string, any>>({
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
collection,
|
|
36
|
+
relativePath,
|
|
37
|
+
parentCollectionIds,
|
|
38
|
+
onNewClick,
|
|
39
|
+
onMultipleDeleteClick,
|
|
40
|
+
selectionEnabled,
|
|
41
|
+
path,
|
|
42
|
+
selectionController,
|
|
43
|
+
tableController,
|
|
44
|
+
collectionEntitiesCount,
|
|
45
|
+
}: EntityCollectionViewActionsProps<M>) {
|
|
35
46
|
|
|
36
47
|
const context = useFireCMSContext();
|
|
37
48
|
|
|
@@ -49,7 +60,7 @@ export function EntityCollectionViewActions<M extends Record<string, any>>({
|
|
|
49
60
|
? <Button
|
|
50
61
|
id={`add_entity_${path}`}
|
|
51
62
|
onClick={onNewClick}
|
|
52
|
-
startIcon={<AddIcon/>}
|
|
63
|
+
startIcon={<AddIcon size={"small"} />}
|
|
53
64
|
variant="filled"
|
|
54
65
|
color="primary">
|
|
55
66
|
Add {collection.singularName ?? collection.name}
|
|
@@ -60,7 +71,7 @@ export function EntityCollectionViewActions<M extends Record<string, any>>({
|
|
|
60
71
|
variant="filled"
|
|
61
72
|
color="primary"
|
|
62
73
|
>
|
|
63
|
-
<AddIcon/>
|
|
74
|
+
<AddIcon size={"small"} />
|
|
64
75
|
</Button>);
|
|
65
76
|
|
|
66
77
|
const multipleDeleteEnabled = canDeleteEntity(collection, authController, path, null);
|
|
@@ -71,7 +82,7 @@ export function EntityCollectionViewActions<M extends Record<string, any>>({
|
|
|
71
82
|
? <Button
|
|
72
83
|
variant={"text"}
|
|
73
84
|
disabled={!(selectedEntities?.length) || !multipleDeleteEnabled}
|
|
74
|
-
startIcon={<DeleteIcon size={"small"}/>}
|
|
85
|
+
startIcon={<DeleteIcon size={"small"} />}
|
|
75
86
|
onClick={onMultipleDeleteClick}
|
|
76
87
|
color={"primary"}
|
|
77
88
|
className="lg:w-20"
|
|
@@ -83,7 +94,7 @@ export function EntityCollectionViewActions<M extends Record<string, any>>({
|
|
|
83
94
|
color={"primary"}
|
|
84
95
|
disabled={!(selectedEntities?.length) || !multipleDeleteEnabled}
|
|
85
96
|
onClick={onMultipleDeleteClick}>
|
|
86
|
-
<DeleteIcon size={"small"}/>
|
|
97
|
+
<DeleteIcon size={"small"} />
|
|
87
98
|
</IconButton>;
|
|
88
99
|
multipleDeleteButton =
|
|
89
100
|
<Tooltip
|
|
@@ -92,6 +103,7 @@ export function EntityCollectionViewActions<M extends Record<string, any>>({
|
|
|
92
103
|
</Tooltip>
|
|
93
104
|
}
|
|
94
105
|
|
|
106
|
+
|
|
95
107
|
const actionProps: CollectionActionsProps = {
|
|
96
108
|
path,
|
|
97
109
|
relativePath,
|
|
@@ -116,7 +128,7 @@ export function EntityCollectionViewActions<M extends Record<string, any>>({
|
|
|
116
128
|
actions.push(...toArray(plugin.collectionView?.CollectionActions)
|
|
117
129
|
.map((Action, j) => (
|
|
118
130
|
<ErrorBoundary key={`plugin_actions_${i}_${j}`}>
|
|
119
|
-
<Action {...actionProps} {...plugin.collectionView?.collectionActionsProps}/>
|
|
131
|
+
<Action {...actionProps} {...plugin.collectionView?.collectionActionsProps} />
|
|
120
132
|
</ErrorBoundary>
|
|
121
133
|
)));
|
|
122
134
|
}
|
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { useCustomizationController, useFireCMSContext } from "../../hooks";
|
|
3
|
-
import {
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { useCustomizationController, useFireCMSContext, useLargeLayout } from "../../hooks";
|
|
3
|
+
import {
|
|
4
|
+
CollectionActionsProps,
|
|
5
|
+
EntityCollection,
|
|
6
|
+
EntityTableController,
|
|
7
|
+
ResolvedProperty,
|
|
8
|
+
SelectionController
|
|
9
|
+
} from "../../types";
|
|
4
10
|
import { toArray } from "../../util/arrays";
|
|
5
11
|
import { ErrorBoundary } from "../ErrorBoundary";
|
|
6
12
|
import { ClearFilterSortButton } from "../ClearFilterSortButton";
|
|
13
|
+
import { FiltersDialog } from "./FiltersDialog";
|
|
14
|
+
import { Badge, Button, cls, FilterListIcon, IconButton, Tooltip } from "@firecms/ui";
|
|
7
15
|
|
|
8
16
|
export type EntityCollectionViewStartActionsProps<M extends Record<string, any>> = {
|
|
9
17
|
collection: EntityCollection<M>;
|
|
@@ -12,23 +20,38 @@ export type EntityCollectionViewStartActionsProps<M extends Record<string, any>>
|
|
|
12
20
|
parentCollectionIds: string[];
|
|
13
21
|
selectionController: SelectionController<M>;
|
|
14
22
|
tableController: EntityTableController<M>;
|
|
15
|
-
collectionEntitiesCount
|
|
23
|
+
collectionEntitiesCount?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Resolved properties from the collection for the filters dialog
|
|
26
|
+
*/
|
|
27
|
+
resolvedProperties?: Record<string, ResolvedProperty>;
|
|
16
28
|
}
|
|
17
29
|
|
|
18
30
|
export function EntityCollectionViewStartActions<M extends Record<string, any>>({
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
31
|
+
collection,
|
|
32
|
+
relativePath,
|
|
33
|
+
parentCollectionIds,
|
|
34
|
+
path,
|
|
35
|
+
selectionController,
|
|
36
|
+
tableController,
|
|
37
|
+
collectionEntitiesCount,
|
|
38
|
+
resolvedProperties
|
|
39
|
+
}: EntityCollectionViewStartActionsProps<M>) {
|
|
27
40
|
|
|
28
41
|
const context = useFireCMSContext();
|
|
29
|
-
|
|
30
42
|
const customizationController = useCustomizationController();
|
|
31
43
|
const plugins = customizationController.plugins ?? [];
|
|
44
|
+
const largeLayout = useLargeLayout();
|
|
45
|
+
|
|
46
|
+
// Filters dialog state
|
|
47
|
+
const [filtersDialogOpen, setFiltersDialogOpen] = useState(false);
|
|
48
|
+
|
|
49
|
+
// Count active filters (excluding force filters)
|
|
50
|
+
const filterValues = tableController.filterValues;
|
|
51
|
+
const forceFilter = collection.forceFilter;
|
|
52
|
+
const activeFilterCount = filterValues
|
|
53
|
+
? Object.keys(filterValues).filter(key => !forceFilter || !(key in forceFilter)).length
|
|
54
|
+
: 0;
|
|
32
55
|
|
|
33
56
|
const actionProps: CollectionActionsProps = {
|
|
34
57
|
path,
|
|
@@ -40,11 +63,44 @@ export function EntityCollectionViewStartActions<M extends Record<string, any>>(
|
|
|
40
63
|
tableController,
|
|
41
64
|
collectionEntitiesCount
|
|
42
65
|
};
|
|
66
|
+
|
|
67
|
+
// Filters button
|
|
68
|
+
const filtersButton = resolvedProperties && tableController.setFilterValues && (
|
|
69
|
+
<Tooltip title="Filters"
|
|
70
|
+
key={"filters_tooltip"}>
|
|
71
|
+
<Badge
|
|
72
|
+
color="primary"
|
|
73
|
+
invisible={activeFilterCount === 0}
|
|
74
|
+
>
|
|
75
|
+
{largeLayout ? (
|
|
76
|
+
<Button
|
|
77
|
+
variant="text"
|
|
78
|
+
size="small"
|
|
79
|
+
onClick={() => setFiltersDialogOpen(true)}
|
|
80
|
+
startIcon={<FilterListIcon size="small" />}
|
|
81
|
+
className={cls(activeFilterCount > 0 && "text-primary")}
|
|
82
|
+
>
|
|
83
|
+
Filters{activeFilterCount > 0 ? ` (${activeFilterCount})` : ""}
|
|
84
|
+
</Button>
|
|
85
|
+
) : (
|
|
86
|
+
<IconButton
|
|
87
|
+
size="small"
|
|
88
|
+
onClick={() => setFiltersDialogOpen(true)}
|
|
89
|
+
className={cls(activeFilterCount > 0 && "text-primary")}
|
|
90
|
+
>
|
|
91
|
+
<FilterListIcon size="small" />
|
|
92
|
+
</IconButton>
|
|
93
|
+
)}
|
|
94
|
+
</Badge>
|
|
95
|
+
</Tooltip>
|
|
96
|
+
);
|
|
97
|
+
|
|
43
98
|
const actions: React.ReactNode[] = [
|
|
99
|
+
filtersButton,
|
|
44
100
|
<ClearFilterSortButton
|
|
45
101
|
key={"clear_filter"}
|
|
46
102
|
tableController={tableController}
|
|
47
|
-
enabled={!collection.forceFilter}/>
|
|
103
|
+
enabled={!collection.forceFilter} />
|
|
48
104
|
];
|
|
49
105
|
|
|
50
106
|
if (plugins) {
|
|
@@ -53,7 +109,7 @@ export function EntityCollectionViewStartActions<M extends Record<string, any>>(
|
|
|
53
109
|
actions.push(...toArray(plugin.collectionView?.CollectionActionsStart)
|
|
54
110
|
.map((Action, j) => (
|
|
55
111
|
<ErrorBoundary key={`plugin_actions_${i}_${j}`}>
|
|
56
|
-
<Action {...actionProps} {...plugin.collectionView?.collectionActionsStartProps}/>
|
|
112
|
+
<Action {...actionProps} {...plugin.collectionView?.collectionActionsStartProps} />
|
|
57
113
|
</ErrorBoundary>
|
|
58
114
|
)));
|
|
59
115
|
}
|
|
@@ -63,6 +119,19 @@ export function EntityCollectionViewStartActions<M extends Record<string, any>>(
|
|
|
63
119
|
return (
|
|
64
120
|
<>
|
|
65
121
|
{actions}
|
|
122
|
+
|
|
123
|
+
{/* Filters Dialog */}
|
|
124
|
+
{resolvedProperties && tableController.setFilterValues && (
|
|
125
|
+
<FiltersDialog
|
|
126
|
+
open={filtersDialogOpen}
|
|
127
|
+
onOpenChange={setFiltersDialogOpen}
|
|
128
|
+
properties={resolvedProperties}
|
|
129
|
+
filterValues={tableController.filterValues}
|
|
130
|
+
setFilterValues={(filterValues) => tableController.setFilterValues?.(filterValues ?? {})}
|
|
131
|
+
forceFilter={collection.forceFilter}
|
|
132
|
+
/>
|
|
133
|
+
)}
|
|
66
134
|
</>
|
|
67
135
|
);
|
|
68
136
|
}
|
|
137
|
+
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import React, { useCallback, useMemo, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
FilterValues,
|
|
4
|
+
ResolvedProperty,
|
|
5
|
+
WhereFilterOp
|
|
6
|
+
} from "../../types";
|
|
7
|
+
import {
|
|
8
|
+
Button,
|
|
9
|
+
cls,
|
|
10
|
+
defaultBorderMixin,
|
|
11
|
+
Dialog,
|
|
12
|
+
DialogActions,
|
|
13
|
+
DialogContent,
|
|
14
|
+
DialogTitle,
|
|
15
|
+
FilterListIcon,
|
|
16
|
+
Typography
|
|
17
|
+
} from "@firecms/ui";
|
|
18
|
+
import { StringNumberFilterField } from "../SelectableTable/filters/StringNumberFilterField";
|
|
19
|
+
import { BooleanFilterField } from "../SelectableTable/filters/BooleanFilterField";
|
|
20
|
+
import { DateTimeFilterField } from "../SelectableTable/filters/DateTimeFilterField";
|
|
21
|
+
import { ReferenceFilterField } from "../SelectableTable/filters/ReferenceFilterField";
|
|
22
|
+
import { VirtualTableWhereFilterOp } from "../VirtualTable";
|
|
23
|
+
import { enumToObjectEntries } from "../../util";
|
|
24
|
+
|
|
25
|
+
export interface FiltersDialogProps {
|
|
26
|
+
open: boolean;
|
|
27
|
+
onOpenChange: (open: boolean) => void;
|
|
28
|
+
properties: Record<string, ResolvedProperty>;
|
|
29
|
+
filterValues: FilterValues<any> | undefined;
|
|
30
|
+
setFilterValues: (filterValues?: FilterValues<any>) => void;
|
|
31
|
+
forceFilter?: FilterValues<any>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Dialog that displays all filterable properties from a collection
|
|
36
|
+
* and allows setting filter values for each.
|
|
37
|
+
*/
|
|
38
|
+
export function FiltersDialog({
|
|
39
|
+
open,
|
|
40
|
+
onOpenChange,
|
|
41
|
+
properties,
|
|
42
|
+
filterValues,
|
|
43
|
+
setFilterValues,
|
|
44
|
+
forceFilter
|
|
45
|
+
}: FiltersDialogProps) {
|
|
46
|
+
// Local state for filters being edited
|
|
47
|
+
const [localFilters, setLocalFilters] = useState<FilterValues<any>>(filterValues ?? {});
|
|
48
|
+
|
|
49
|
+
// Track hidden state for reference fields (when reference dialog is open)
|
|
50
|
+
const [hiddenFields, setHiddenFields] = useState<Record<string, boolean>>({});
|
|
51
|
+
|
|
52
|
+
// Reset local state when dialog opens
|
|
53
|
+
React.useEffect(() => {
|
|
54
|
+
if (open) {
|
|
55
|
+
setLocalFilters(filterValues ?? {});
|
|
56
|
+
}
|
|
57
|
+
}, [open, filterValues]);
|
|
58
|
+
|
|
59
|
+
// Get list of filterable properties
|
|
60
|
+
const filterableProperties = useMemo(() => {
|
|
61
|
+
return Object.entries(properties).filter(([key, property]) => {
|
|
62
|
+
if (!property) return false;
|
|
63
|
+
// Force filter properties should not be editable
|
|
64
|
+
if (forceFilter && key in forceFilter) return false;
|
|
65
|
+
// Check if property type is filterable
|
|
66
|
+
const baseProperty = property.dataType === "array" ? property.of : property;
|
|
67
|
+
if (!baseProperty) return false;
|
|
68
|
+
return ["string", "number", "boolean", "date", "reference"].includes(baseProperty.dataType);
|
|
69
|
+
});
|
|
70
|
+
}, [properties, forceFilter]);
|
|
71
|
+
|
|
72
|
+
const handleFilterChange = useCallback((propertyKey: string, value?: [VirtualTableWhereFilterOp, any]) => {
|
|
73
|
+
setLocalFilters(prev => {
|
|
74
|
+
const newFilters = { ...prev };
|
|
75
|
+
if (value) {
|
|
76
|
+
newFilters[propertyKey] = value as [WhereFilterOp, any];
|
|
77
|
+
} else {
|
|
78
|
+
delete newFilters[propertyKey];
|
|
79
|
+
}
|
|
80
|
+
return newFilters;
|
|
81
|
+
});
|
|
82
|
+
}, []);
|
|
83
|
+
|
|
84
|
+
const handleApply = useCallback(() => {
|
|
85
|
+
const hasFilters = Object.keys(localFilters).length > 0;
|
|
86
|
+
setFilterValues(hasFilters ? { ...localFilters, ...forceFilter } : (forceFilter || undefined));
|
|
87
|
+
onOpenChange(false);
|
|
88
|
+
}, [localFilters, setFilterValues, forceFilter, onOpenChange]);
|
|
89
|
+
|
|
90
|
+
const handleClearAll = useCallback(() => {
|
|
91
|
+
setLocalFilters({});
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
94
|
+
const setHiddenForField = useCallback((propertyKey: string, hidden: boolean) => {
|
|
95
|
+
setHiddenFields(prev => ({
|
|
96
|
+
...prev,
|
|
97
|
+
[propertyKey]: hidden
|
|
98
|
+
}));
|
|
99
|
+
}, []);
|
|
100
|
+
|
|
101
|
+
// Check if any reference field's dialog is currently open (should hide this dialog)
|
|
102
|
+
const isAnyFieldHidden = Object.values(hiddenFields).some(hidden => hidden);
|
|
103
|
+
const activeFilterCount = Object.keys(localFilters).length;
|
|
104
|
+
|
|
105
|
+
const renderFilterField = useCallback((propertyKey: string, property: ResolvedProperty) => {
|
|
106
|
+
const isArray = property.dataType === "array";
|
|
107
|
+
const baseProperty: ResolvedProperty = isArray ? property.of : property;
|
|
108
|
+
|
|
109
|
+
if (!baseProperty) return null;
|
|
110
|
+
|
|
111
|
+
const filterValue = localFilters[propertyKey] as [VirtualTableWhereFilterOp, any] | undefined;
|
|
112
|
+
const setValue = (value?: [VirtualTableWhereFilterOp, any]) => handleFilterChange(propertyKey, value);
|
|
113
|
+
|
|
114
|
+
if (baseProperty.dataType === "reference") {
|
|
115
|
+
return (
|
|
116
|
+
<ReferenceFilterField
|
|
117
|
+
value={filterValue}
|
|
118
|
+
setValue={setValue}
|
|
119
|
+
name={propertyKey}
|
|
120
|
+
isArray={isArray}
|
|
121
|
+
path={baseProperty.path}
|
|
122
|
+
title={property.name}
|
|
123
|
+
includeId={baseProperty.includeId}
|
|
124
|
+
previewProperties={baseProperty.previewProperties}
|
|
125
|
+
hidden={hiddenFields[propertyKey] ?? false}
|
|
126
|
+
setHidden={(hidden) => setHiddenForField(propertyKey, hidden)}
|
|
127
|
+
/>
|
|
128
|
+
);
|
|
129
|
+
} else if (baseProperty.dataType === "number" || baseProperty.dataType === "string") {
|
|
130
|
+
const enumValues = baseProperty.enumValues ? enumToObjectEntries(baseProperty.enumValues) : undefined;
|
|
131
|
+
return (
|
|
132
|
+
<StringNumberFilterField
|
|
133
|
+
value={filterValue}
|
|
134
|
+
setValue={setValue}
|
|
135
|
+
name={propertyKey}
|
|
136
|
+
dataType={baseProperty.dataType}
|
|
137
|
+
isArray={isArray}
|
|
138
|
+
enumValues={enumValues}
|
|
139
|
+
title={property.name}
|
|
140
|
+
/>
|
|
141
|
+
);
|
|
142
|
+
} else if (baseProperty.dataType === "boolean") {
|
|
143
|
+
return (
|
|
144
|
+
<BooleanFilterField
|
|
145
|
+
value={filterValue}
|
|
146
|
+
setValue={setValue}
|
|
147
|
+
name={propertyKey}
|
|
148
|
+
title={property.name}
|
|
149
|
+
/>
|
|
150
|
+
);
|
|
151
|
+
} else if (baseProperty.dataType === "date") {
|
|
152
|
+
return (
|
|
153
|
+
<DateTimeFilterField
|
|
154
|
+
value={filterValue}
|
|
155
|
+
setValue={setValue}
|
|
156
|
+
name={propertyKey}
|
|
157
|
+
mode={baseProperty.mode}
|
|
158
|
+
isArray={isArray}
|
|
159
|
+
title={property.name}
|
|
160
|
+
/>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return null;
|
|
165
|
+
}, [localFilters, handleFilterChange, hiddenFields, setHiddenForField]);
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<Dialog
|
|
169
|
+
open={open}
|
|
170
|
+
onOpenChange={onOpenChange}
|
|
171
|
+
maxWidth="3xl"
|
|
172
|
+
fullWidth
|
|
173
|
+
containerClassName={isAnyFieldHidden ? "hidden" : undefined}
|
|
174
|
+
>
|
|
175
|
+
<DialogTitle className="flex items-center gap-2">
|
|
176
|
+
<Typography variant="h6">Filters</Typography>
|
|
177
|
+
{activeFilterCount > 0 && (
|
|
178
|
+
<span className="ml-2 px-2 py-0.5 text-xs rounded-full bg-primary text-white">
|
|
179
|
+
{activeFilterCount}
|
|
180
|
+
</span>
|
|
181
|
+
)}
|
|
182
|
+
</DialogTitle>
|
|
183
|
+
|
|
184
|
+
<DialogContent >
|
|
185
|
+
{filterableProperties.length === 0 ? (
|
|
186
|
+
<Typography color="secondary" className="py-8 text-center">
|
|
187
|
+
No filterable properties available
|
|
188
|
+
</Typography>
|
|
189
|
+
) : (
|
|
190
|
+
<table className="w-full border-collapse">
|
|
191
|
+
<tbody>
|
|
192
|
+
{filterableProperties.map(([propertyKey, property], index) => {
|
|
193
|
+
const hasFilter = propertyKey in localFilters;
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<tr key={propertyKey} className={cls(
|
|
197
|
+
index > 0 && "border-t",
|
|
198
|
+
defaultBorderMixin
|
|
199
|
+
)}>
|
|
200
|
+
{/* Property name on the left */}
|
|
201
|
+
<td className="py-3 pr-4 align-middle w-[160px]">
|
|
202
|
+
<Typography
|
|
203
|
+
variant="body2"
|
|
204
|
+
className={cls(
|
|
205
|
+
"font-medium",
|
|
206
|
+
hasFilter && "text-primary"
|
|
207
|
+
)}
|
|
208
|
+
>
|
|
209
|
+
{property.name || propertyKey}
|
|
210
|
+
</Typography>
|
|
211
|
+
</td>
|
|
212
|
+
|
|
213
|
+
{/* Filter field on the right */}
|
|
214
|
+
<td className="py-3">
|
|
215
|
+
{renderFilterField(propertyKey, property)}
|
|
216
|
+
</td>
|
|
217
|
+
</tr>
|
|
218
|
+
);
|
|
219
|
+
})}
|
|
220
|
+
</tbody>
|
|
221
|
+
</table>
|
|
222
|
+
)}
|
|
223
|
+
</DialogContent>
|
|
224
|
+
|
|
225
|
+
<DialogActions>
|
|
226
|
+
<Button
|
|
227
|
+
variant="text"
|
|
228
|
+
onClick={handleClearAll}
|
|
229
|
+
disabled={activeFilterCount === 0}
|
|
230
|
+
>
|
|
231
|
+
Clear all
|
|
232
|
+
</Button>
|
|
233
|
+
<div className="flex-grow" />
|
|
234
|
+
<Button
|
|
235
|
+
variant="text"
|
|
236
|
+
onClick={() => onOpenChange(false)}
|
|
237
|
+
>
|
|
238
|
+
Cancel
|
|
239
|
+
</Button>
|
|
240
|
+
<Button
|
|
241
|
+
variant="filled"
|
|
242
|
+
onClick={handleApply}
|
|
243
|
+
>
|
|
244
|
+
Apply filters
|
|
245
|
+
</Button>
|
|
246
|
+
</DialogActions>
|
|
247
|
+
</Dialog>
|
|
248
|
+
);
|
|
249
|
+
}
|