@firecms/core 3.0.0-beta.14 → 3.0.0-beta.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +2 -1
  2. package/dist/components/HomePage/DefaultHomePage.d.ts +2 -15
  3. package/dist/components/HomePage/HomePageDnD.d.ts +76 -0
  4. package/dist/components/HomePage/NavigationCard.d.ts +3 -1
  5. package/dist/components/HomePage/NavigationCardBinding.d.ts +3 -2
  6. package/dist/components/HomePage/NavigationGroup.d.ts +7 -1
  7. package/dist/components/HomePage/RenameGroupDialog.d.ts +9 -0
  8. package/dist/core/EntityEditView.d.ts +3 -0
  9. package/dist/core/EntityEditViewFormActions.d.ts +1 -1
  10. package/dist/core/field_configs.d.ts +1 -1
  11. package/dist/form/EntityForm.d.ts +2 -1
  12. package/dist/form/EntityFormActions.d.ts +6 -2
  13. package/dist/form/field_bindings/ReferenceAsStringFieldBinding.d.ts +9 -0
  14. package/dist/form/index.d.ts +1 -0
  15. package/dist/hooks/useBuildNavigationController.d.ts +51 -2
  16. package/dist/index.es.js +2184 -1052
  17. package/dist/index.es.js.map +1 -1
  18. package/dist/index.umd.js +2181 -1049
  19. package/dist/index.umd.js.map +1 -1
  20. package/dist/types/analytics.d.ts +1 -1
  21. package/dist/types/collections.d.ts +8 -2
  22. package/dist/types/customization_controller.d.ts +8 -0
  23. package/dist/types/entity_actions.d.ts +46 -6
  24. package/dist/types/firecms.d.ts +8 -0
  25. package/dist/types/navigation.d.ts +21 -5
  26. package/dist/types/plugins.d.ts +20 -1
  27. package/dist/types/properties.d.ts +7 -0
  28. package/dist/types/property_config.d.ts +1 -1
  29. package/dist/types/side_entity_controller.d.ts +4 -0
  30. package/dist/util/icons.d.ts +2 -2
  31. package/dist/util/navigation_from_path.d.ts +4 -0
  32. package/dist/util/resolutions.d.ts +2 -1
  33. package/package.json +5 -5
  34. package/src/components/ConfirmationDialog.tsx +1 -0
  35. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +6 -0
  36. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +25 -3
  37. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +2 -2
  38. package/src/components/EntityCollectionView/EntityCollectionView.tsx +7 -4
  39. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +3 -2
  40. package/src/components/FireCMSLogo.tsx +7 -51
  41. package/src/components/HomePage/DefaultHomePage.tsx +491 -157
  42. package/src/components/HomePage/FavouritesView.tsx +3 -3
  43. package/src/components/HomePage/HomePageDnD.tsx +599 -0
  44. package/src/components/HomePage/NavigationCard.tsx +47 -38
  45. package/src/components/HomePage/NavigationCardBinding.tsx +10 -6
  46. package/src/components/HomePage/NavigationGroup.tsx +63 -29
  47. package/src/components/HomePage/RenameGroupDialog.tsx +117 -0
  48. package/src/components/UnsavedChangesDialog.tsx +6 -2
  49. package/src/components/common/default_entity_actions.tsx +25 -9
  50. package/src/components/common/useDataSourceTableController.tsx +2 -2
  51. package/src/core/DefaultDrawer.tsx +8 -8
  52. package/src/core/DrawerNavigationItem.tsx +1 -1
  53. package/src/core/EntityEditView.tsx +41 -6
  54. package/src/core/EntityEditViewFormActions.tsx +154 -29
  55. package/src/core/EntitySidePanel.tsx +5 -2
  56. package/src/core/FireCMS.tsx +2 -0
  57. package/src/core/field_configs.tsx +15 -1
  58. package/src/form/EntityForm.tsx +36 -4
  59. package/src/form/EntityFormActions.tsx +51 -9
  60. package/src/form/components/StorageItemPreview.tsx +1 -1
  61. package/src/form/components/StorageUploadProgress.tsx +3 -3
  62. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +4 -2
  63. package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +135 -0
  64. package/src/form/field_bindings/RepeatFieldBinding.tsx +0 -1
  65. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +12 -17
  66. package/src/form/index.tsx +1 -0
  67. package/src/hooks/useBuildNavigationController.tsx +273 -84
  68. package/src/internal/useBuildSideEntityController.tsx +7 -4
  69. package/src/preview/PropertyPreview.tsx +14 -0
  70. package/src/routes/FireCMSRoute.tsx +3 -3
  71. package/src/types/analytics.ts +3 -0
  72. package/src/types/collections.ts +8 -2
  73. package/src/types/customization_controller.tsx +9 -0
  74. package/src/types/entity_actions.tsx +57 -6
  75. package/src/types/firecms.tsx +9 -0
  76. package/src/types/navigation.ts +28 -6
  77. package/src/types/plugins.tsx +24 -1
  78. package/src/types/properties.ts +8 -0
  79. package/src/types/property_config.tsx +1 -0
  80. package/src/types/side_entity_controller.tsx +5 -0
  81. package/src/util/icons.tsx +22 -7
  82. package/src/util/join_collections.ts +3 -1
  83. package/src/util/navigation_from_path.ts +15 -5
  84. package/src/util/navigation_utils.ts +2 -2
  85. package/src/util/resolutions.ts +13 -1
@@ -1,4 +1,5 @@
1
1
  import { ArrowForwardIcon, Card, cls, Markdown, Typography, } from "@firecms/ui";
2
+ import React from "react"; // Import React
2
3
 
3
4
  export type NavigationCardProps = {
4
5
  name: string,
@@ -6,64 +7,72 @@ export type NavigationCardProps = {
6
7
  actions: React.ReactNode;
7
8
  icon: React.ReactNode;
8
9
  onClick?: () => void,
10
+ shrink?: boolean
9
11
  };
10
12
 
11
- export function NavigationCard({
13
+ // Wrap the component with React.memo
14
+ export const NavigationCard = React.memo(function NavigationCard({
12
15
  name,
13
16
  description,
14
17
  icon,
15
18
  actions,
16
19
  onClick,
20
+ shrink
17
21
  }: NavigationCardProps) {
18
22
 
19
- return (<Card
20
- className={cls("h-full p-4 cursor-pointer min-h-[230px]")}
21
- onClick={() => {
22
- onClick?.();
23
- }}>
23
+ return (
24
+ <Card
25
+ className={cls(
26
+ "h-full p-4 cursor-pointer min-h-[230px] transition-all duration-200 ease-in-out",
27
+ shrink && "w-full max-w-full min-h-0 scale-75"
28
+ )}
29
+ onClick={() => {
30
+ onClick?.();
31
+ }}
32
+ >
33
+
34
+ <div className="flex flex-col items-start h-full">
35
+ <div
36
+ className="flex-grow w-full">
24
37
 
25
- <div className="flex flex-col items-start h-full">
26
- <div
27
- className="flex-grow w-full">
38
+ <div
39
+ className="h-10 flex items-center w-full justify-between text-surface-300 dark:text-surface-600">
28
40
 
29
- <div
30
- className="h-10 flex items-center w-full justify-between text-surface-300 dark:text-surface-600">
41
+ {icon}
31
42
 
32
- {icon}
43
+ <div
44
+ className="flex items-center gap-1"
45
+ onClick={(event: React.MouseEvent) => {
46
+ event.preventDefault();
47
+ event.stopPropagation();
48
+ }}>
33
49
 
34
- <div
35
- className="flex items-center gap-1"
36
- onClick={(event: React.MouseEvent) => {
37
- event.preventDefault();
38
- event.stopPropagation();
39
- }}>
50
+ {actions}
40
51
 
41
- {actions}
52
+ </div>
42
53
 
43
54
  </div>
44
55
 
45
- </div>
46
-
47
- <Typography gutterBottom variant="h5"
48
- component="h2">
49
- {name}
50
- </Typography>
56
+ <Typography gutterBottom variant="h5"
57
+ component="h2">
58
+ {name}
59
+ </Typography>
51
60
 
52
- {description && <Typography variant="body2"
53
- color="secondary"
54
- component="div">
55
- <Markdown source={description} size={"small"}/>
56
- </Typography>}
57
- </div>
61
+ {description && <Typography variant="body2"
62
+ color="secondary"
63
+ component="div">
64
+ <Markdown source={description} size={"small"}/>
65
+ </Typography>}
66
+ </div>
58
67
 
59
- <div style={{ alignSelf: "flex-end" }}>
68
+ <div style={{ alignSelf: "flex-end" }}>
60
69
 
61
- <div className={"p-4"}>
62
- <ArrowForwardIcon className="text-primary"/>
70
+ <div className={"p-4"}>
71
+ <ArrowForwardIcon className="text-primary"/>
72
+ </div>
63
73
  </div>
64
- </div>
65
74
 
66
- </div>
75
+ </div>
67
76
 
68
- </Card>)
69
- }
77
+ </Card>)
78
+ });
@@ -1,7 +1,7 @@
1
1
  import { useNavigate } from "react-router-dom";
2
2
 
3
3
  import { useCustomizationController, useFireCMSContext } from "../../hooks";
4
- import { PluginHomePageActionsProps, TopNavigationEntry } from "../../types";
4
+ import { NavigationEntry, PluginHomePageActionsProps } from "../../types";
5
5
  import { IconForView } from "../../util";
6
6
  import { useUserConfigurationPersistence } from "../../hooks/useUserConfigurationPersistence";
7
7
  import { IconButton, StarIcon } from "@firecms/ui";
@@ -30,9 +30,11 @@ export function NavigationCardBinding({
30
30
  name,
31
31
  description,
32
32
  onClick,
33
- type
34
- }: TopNavigationEntry & {
35
- onClick?: () => void
33
+ type,
34
+ shrink
35
+ }: NavigationEntry & {
36
+ onClick?: () => void,
37
+ shrink?: boolean // <-- add shrink prop type
36
38
  }) {
37
39
 
38
40
  const userConfigurationPersistence = useUserConfigurationPersistence();
@@ -93,7 +95,7 @@ export function NavigationCardBinding({
93
95
  if (type === "admin") {
94
96
  return <SmallNavigationCard icon={collectionIcon}
95
97
  name={name}
96
- url={url}/>
98
+ url={url}/>;
97
99
  }
98
100
 
99
101
  return <NavigationCard
@@ -109,5 +111,7 @@ export function NavigationCardBinding({
109
111
  [path, ...(userConfigurationPersistence.recentlyVisitedPaths ?? []).filter(p => p !== path)]
110
112
  );
111
113
  }
112
- }}/>;
114
+ }}
115
+ shrink={shrink}
116
+ />;
113
117
  }
@@ -1,39 +1,73 @@
1
- import { PropsWithChildren } from "react";
2
- import { useUserConfigurationPersistence } from "../../hooks/useUserConfigurationPersistence";
3
- import { ExpandablePanel, Typography } from "@firecms/ui";
1
+ import React, { PropsWithChildren, useState } from "react";
2
+ import { cls, EditIcon, IconButton, Typography } from "@firecms/ui";
4
3
 
5
4
  export function NavigationGroup({
6
5
  children,
7
- group
6
+ group,
7
+ minimised,
8
+ isPreview,
9
+ isPotentialCardDropTarget,
10
+ onEditGroup, // New prop to handle editing
11
+ dndDisabled // New prop to disable editing when D&D is off
8
12
  }: PropsWithChildren<{
9
- group: string | undefined
13
+ group: string | undefined,
14
+ minimised?: boolean,
15
+ isPreview?: boolean,
16
+ isPotentialCardDropTarget?: boolean,
17
+ onEditGroup?: (groupName: string) => void; // Callback to open dialog
18
+ dndDisabled?: boolean; // Added dndDisabled prop
10
19
  }>) {
11
- const userConfigurationPersistence = useUserConfigurationPersistence();
12
- return (
13
- <ExpandablePanel
14
- invisible={true}
15
- titleClassName={"font-medium text-sm text-surface-600 dark:text-surface-400"}
16
- innerClassName={"py-4"}
17
- initiallyExpanded={!(userConfigurationPersistence?.collapsedGroups ?? []).includes(group ?? "ungrouped")}
18
- onExpandedChange={expanded => {
19
- if (userConfigurationPersistence) {
20
20
 
21
- if (!expanded) {
22
- const paths = (userConfigurationPersistence.collapsedGroups ?? []).concat(group ?? "ungrouped");
23
- userConfigurationPersistence.setCollapsedGroups(paths);
24
- } else {
25
- userConfigurationPersistence.setCollapsedGroups((userConfigurationPersistence.collapsedGroups ?? []).filter(g => g !== (group ?? "ungrouped")));
26
- }
27
- }
28
- }}
29
- title={<Typography color={"secondary"}
30
- className="font-medium ml-1">
31
- {group?.toUpperCase() ?? "Views".toUpperCase()}
32
- </Typography>}>
21
+ const [isHovered, setIsHovered] = useState(false);
22
+ const currentGroupName = group ?? "Views";
23
+
24
+ return (
25
+ <div className={cls(
26
+ !isPotentialCardDropTarget ? "my-10" : "my-6",
27
+ "transition-all duration-200 ease-in-out"
28
+ )}
29
+ >
30
+ <div className={`flex items-center ${isPreview ? "px-1 py-0.5 m-0" : "ml-3.5 mt-6"} `}
33
31
 
34
- <div className="mb-8">
35
- {children}
32
+ onMouseEnter={() => setIsHovered(true)}
33
+ onMouseLeave={() => setIsHovered(false)}>
34
+ <Typography
35
+ variant={isPreview ? "body2" : "caption"}
36
+ component={"h2"}
37
+ color="secondary"
38
+ // Minimal padding and no margin for preview title
39
+ className={`${isPreview ? "px-1 py-0.5" : "ml-3.5"} font-medium uppercase text-sm text-surface-600 dark:text-surface-400`}
40
+ >
41
+ {currentGroupName}
42
+ </Typography>
43
+ {!isPreview && onEditGroup && !dndDisabled && (
44
+ <IconButton
45
+ size="smallest"
46
+ onClick={(e) => {
47
+ e.stopPropagation(); // Prevent other click events
48
+ onEditGroup(currentGroupName);
49
+ }}
50
+ className={cls("ml-2 ", isHovered ? "opacity-100" : "opacity-0", "transition-opacity duration-100")}
51
+ >
52
+ <EditIcon size="smallest"/>
53
+ </IconButton>
54
+ )}
36
55
  </div>
37
- </ExpandablePanel>
56
+
57
+ {isPreview ? (
58
+ children
59
+ ) : minimised ? (
60
+ // For minimised view in the main list
61
+ <div className={cls("mt-4 p-8 bg-surface-accent-200 dark:bg-surface-accent-800 rounded-lg")}
62
+ style={{ minHeight: "50px" }}>
63
+ </div>
64
+ ) : (
65
+ // If highlighted, the parent div already has padding, so children (NavigationGroupDroppable) don't need extra margin top as much.
66
+ // The inner content of NavigationGroupDroppable will define its own padding if needed when active.
67
+ <div className={cls("mt-4", !minimised ? "pt-0" : "")}>
68
+ {children}
69
+ </div>
70
+ )}
71
+ </div>
38
72
  );
39
73
  }
@@ -0,0 +1,117 @@
1
+ import React, { useState, useEffect, useRef } from "react";
2
+ import { Button, Dialog, DialogActions, DialogContent, DialogTitle, TextField } from "@firecms/ui";
3
+
4
+ interface RenameGroupDialogProps {
5
+ open: boolean;
6
+ initialName: string;
7
+ existingGroupNames: string[]; // Names of other existing groups to check for duplicates
8
+ onClose: () => void;
9
+ onRename: (newName: string) => void;
10
+ }
11
+
12
+ export function RenameGroupDialog({ open, initialName, existingGroupNames, onClose, onRename }: RenameGroupDialogProps) {
13
+ const [name, setName] = useState(initialName);
14
+ const [error, setError] = useState<string | null>(null);
15
+ const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement | null>(null); // Create a ref for the input
16
+
17
+ useEffect(() => {
18
+ if (open) {
19
+ setName(initialName);
20
+ setError(null);
21
+ // Focus and select text when dialog opens
22
+ setTimeout(() => { // setTimeout to ensure the input is rendered and focusable
23
+ if (inputRef.current) {
24
+ inputRef.current.focus();
25
+ inputRef.current.select();
26
+ }
27
+ }, 100);
28
+ }
29
+ }, [initialName, open]);
30
+
31
+ const handleNameChange = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
32
+ const newName = event.target.value;
33
+ setName(newName);
34
+ if (!newName.trim()) {
35
+ setError("Group name cannot be empty.");
36
+ } else if (existingGroupNames.includes(newName.trim())) {
37
+ setError("This group name already exists.");
38
+ } else {
39
+ setError(null);
40
+ }
41
+ };
42
+
43
+ const handleSave = () => {
44
+ const trimmedName = name.trim();
45
+ if (!trimmedName) {
46
+ setError("Group name cannot be empty.");
47
+ return;
48
+ }
49
+ if (existingGroupNames.includes(trimmedName)) {
50
+ setError("This group name already exists.");
51
+ return;
52
+ }
53
+ if (!error) {
54
+ onRename(trimmedName);
55
+ onClose();
56
+ }
57
+ };
58
+
59
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
60
+ if (event.key === "Enter") {
61
+ event.preventDefault(); // Prevent default form submission behavior
62
+ const trimmedName = name.trim();
63
+ // We need to check the error state directly as well,
64
+ // because the error state might not have updated if the user types and immediately hits enter.
65
+ let currentError = null;
66
+ if (!trimmedName) {
67
+ currentError = "Group name cannot be empty.";
68
+ } else if (existingGroupNames.includes(trimmedName)) {
69
+ currentError = "This group name already exists.";
70
+ }
71
+
72
+ if (!currentError && trimmedName) {
73
+ handleSave();
74
+ } else if (currentError) {
75
+ setError(currentError); // Ensure error is displayed if trying to submit with Enter
76
+ }
77
+ }
78
+ };
79
+
80
+ const handleClose = () => {
81
+ setName(initialName);
82
+ setError(null);
83
+ onClose();
84
+ };
85
+
86
+ if (!open) return null;
87
+
88
+ return (
89
+ <Dialog open={open} onOpenChange={onClose}>
90
+ <DialogTitle>Rename Group</DialogTitle>
91
+ <DialogContent>
92
+ <TextField
93
+ inputRef={inputRef} // Pass the ref to the TextField
94
+ label="Group Name"
95
+ value={name}
96
+ onChange={handleNameChange}
97
+ onKeyDown={handleKeyDown} // Added onKeyDown handler
98
+ error={!!error}
99
+ aria-describedby={error ? "group-name-error" : undefined}
100
+ />
101
+ {error && <p id="group-name-error" style={{ display: "none" }}>{error}</p>}
102
+ </DialogContent>
103
+ <DialogActions>
104
+ <Button onClick={onClose}
105
+ color={"primary"}
106
+ variant="text">
107
+ Cancel
108
+ </Button>
109
+ <Button onClick={handleSave}
110
+ color={"primary"}
111
+ disabled={!!error || !name.trim()}>
112
+ Save
113
+ </Button>
114
+ </DialogActions>
115
+ </Dialog>
116
+ );
117
+ }
@@ -34,8 +34,12 @@ export function UnsavedChangesDialog({
34
34
 
35
35
  </DialogContent>
36
36
  <DialogActions>
37
- <Button variant="text" onClick={handleCancel} autoFocus> Cancel </Button>
38
- <Button onClick={handleOk}> Ok </Button>
37
+ <Button variant="text"
38
+ color={"primary"}
39
+ onClick={handleCancel} autoFocus> Cancel </Button>
40
+ <Button
41
+ color={"primary"}
42
+ onClick={handleOk}> Ok </Button>
39
43
  </DialogActions>
40
44
  </Dialog>
41
45
  );
@@ -5,20 +5,26 @@ import { addRecentId } from "../EntityCollectionView/utils";
5
5
  import { navigateToEntity, resolveDefaultSelectedView } from "../../util";
6
6
 
7
7
  export const editEntityAction: EntityAction = {
8
- icon: <EditIcon/>,
8
+ icon: <EditIcon size={"small"}/>,
9
9
  key: "edit",
10
10
  name: "Edit",
11
11
  collapsed: false,
12
+ isEnabled: ({ entity }) => Boolean(entity),
12
13
  onClick({
13
14
  entity,
14
15
  collection,
15
16
  fullPath,
17
+ fullIdPath,
16
18
  context,
17
19
  highlightEntity,
18
20
  unhighlightEntity,
19
21
  openEntityMode
20
22
  }): Promise<void> {
21
23
 
24
+ if (!entity) {
25
+ throw new Error("INTERNAL: editEntityAction: Entity is undefined");
26
+ }
27
+
22
28
  highlightEntity?.(entity);
23
29
 
24
30
  context.analyticsController?.onAnalyticsEvent?.("entity_click", {
@@ -30,7 +36,8 @@ export const editEntityAction: EntityAction = {
30
36
  addRecentId(collection.id, entity.id);
31
37
  }
32
38
 
33
- const path = collection?.collectionGroup ? collection.id : (fullPath ?? collection?.id ?? entity.path);
39
+ const path = collection?.collectionGroup ? entity.path : (fullPath ?? collection?.path ?? entity.path);
40
+ const newFullIdPath = collection?.collectionGroup ? collection.id : (fullIdPath ?? collection?.id ?? entity.path);
34
41
  const defaultSelectedView = resolveDefaultSelectedView(
35
42
  collection ? collection.defaultSelectedView : undefined,
36
43
  {
@@ -43,7 +50,7 @@ export const editEntityAction: EntityAction = {
43
50
  collection,
44
51
  entityId: entity.id,
45
52
  path,
46
- fullIdPath: path,
53
+ fullIdPath: newFullIdPath,
47
54
  sideEntityController: context.sideEntityController,
48
55
  onClose: () => unhighlightEntity?.(entity),
49
56
  navigation: context.navigation,
@@ -55,9 +62,10 @@ export const editEntityAction: EntityAction = {
55
62
  }
56
63
 
57
64
  export const copyEntityAction: EntityAction = {
58
- icon: <FileCopyIcon/>,
65
+ icon: <FileCopyIcon size={"small"}/>,
59
66
  name: "Copy",
60
67
  key: "copy",
68
+ isEnabled: ({ entity }) => Boolean(entity),
61
69
  onClick({
62
70
  entity,
63
71
  collection,
@@ -67,19 +75,23 @@ export const copyEntityAction: EntityAction = {
67
75
  unhighlightEntity,
68
76
  openEntityMode
69
77
  }): Promise<void> {
78
+ if (!entity) {
79
+ throw new Error("INTERNAL: copyEntityAction: Entity is undefined");
80
+ }
70
81
  highlightEntity?.(entity);
71
82
  context.analyticsController?.onAnalyticsEvent?.("copy_entity_click", {
72
83
  path: entity.path,
73
84
  entityId: entity.id
74
85
  });
75
86
 
76
- const path = collection?.collectionGroup ? collection.id : (fullPath ?? collection?.id ?? entity.path);
87
+ const path = collection?.collectionGroup ? collection.path : (fullPath ?? collection?.path ?? entity.path);
88
+ const fullIdPath = collection?.collectionGroup ? collection.id : (fullPath ?? collection?.id ?? entity.path);
77
89
  navigateToEntity({
78
90
  openEntityMode,
79
91
  collection,
80
92
  entityId: entity.id,
81
93
  path,
82
- fullIdPath: path,
94
+ fullIdPath,
83
95
  copy: true,
84
96
  sideEntityController: context.sideEntityController,
85
97
  onClose: () => unhighlightEntity?.(entity),
@@ -91,9 +103,10 @@ export const copyEntityAction: EntityAction = {
91
103
  }
92
104
 
93
105
  export const deleteEntityAction: EntityAction = {
94
- icon: <DeleteIcon/>,
106
+ icon: <DeleteIcon size={"small"}/>,
95
107
  name: "Delete",
96
108
  key: "delete",
109
+ isEnabled: ({ entity }) => Boolean(entity),
97
110
  onClick({
98
111
  entity,
99
112
  fullPath,
@@ -101,8 +114,11 @@ export const deleteEntityAction: EntityAction = {
101
114
  context,
102
115
  selectionController,
103
116
  onCollectionChange,
104
- sideEntityController
117
+ navigateBack
105
118
  }): Promise<void> {
119
+ if (!entity) {
120
+ throw new Error("INTERNAL: deleteEntityAction: Entity is undefined");
121
+ }
106
122
  const { closeDialog } = context.dialogsController.open({
107
123
  key: "delete_entity_dialog_" + entity.id,
108
124
  Component: ({ open }) => {
@@ -120,7 +136,7 @@ export const deleteEntityAction: EntityAction = {
120
136
  });
121
137
  selectionController?.setSelectedEntities(selectionController.selectedEntities.filter(e => e.id !== entity.id));
122
138
  onCollectionChange?.();
123
- sideEntityController?.close();
139
+ navigateBack?.();
124
140
  }}
125
141
  onClose={closeDialog}/>;
126
142
  }
@@ -134,7 +134,7 @@ export function useDataSourceTableController<M extends Record<string, any> = any
134
134
  } = parseFilterAndSort(window.location.search);
135
135
 
136
136
  const [filterValues, setFilterValues] = React.useState<FilterValues<Extract<keyof M, string>> | undefined>(forceFilter ?? (updateUrl ? initialFilterUrl : undefined) ?? initialFilter ?? undefined);
137
- const [sortBy, setSortBy] = React.useState<[Extract<keyof M, string>, "asc" | "desc"] | undefined>(initialSortUrl ?? initialSortInternal);
137
+ const [sortBy, setSortBy] = React.useState<[Extract<keyof M, string>, "asc" | "desc"] | undefined>((updateUrl ? initialSortUrl : undefined) ?? initialSortInternal);
138
138
 
139
139
  useUpdateUrl(filterValues, sortBy, searchString, updateUrl);
140
140
 
@@ -334,7 +334,7 @@ function encodeFilterAndSort(filterValues?: FilterValues<string>, sortBy?: [stri
334
334
  }
335
335
  if (encodedValue !== undefined) {
336
336
  entries[encodeURIComponent(`${key}_op`)] = encodeURIComponent(op);
337
- entries[encodeURIComponent(`${key}_value`)] = encodeURIComponent(encodedValue.toString());
337
+ entries[encodeURIComponent(`${key}_value`)] = encodedValue ? encodeURIComponent(encodedValue.toString()) : "null";
338
338
  }
339
339
  }
340
340
  });
@@ -3,7 +3,7 @@ import React, { useCallback } from "react";
3
3
  import { useLargeLayout, useNavigationController } from "../hooks";
4
4
 
5
5
  import { Link, useNavigate } from "react-router-dom";
6
- import { CMSAnalyticsEvent, TopNavigationEntry, TopNavigationResult } from "../types";
6
+ import { CMSAnalyticsEvent, NavigationEntry, NavigationResult } from "../types";
7
7
  import { IconForView } from "../util";
8
8
  import { cls, IconButton, Menu, MenuItem, MoreVertIcon, Tooltip, Typography } from "@firecms/ui";
9
9
  import { useAnalyticsController } from "../hooks/useAnalyticsController";
@@ -45,7 +45,7 @@ export function DefaultDrawer({
45
45
  const {
46
46
  navigationEntries,
47
47
  groups
48
- }: TopNavigationResult = navigation.topLevelNavigation;
48
+ }: NavigationResult = navigation.topLevelNavigation;
49
49
 
50
50
  const adminViews = navigationEntries.filter(e => e.type === "admin") ?? [];
51
51
  const groupsWithoutAdmin = groups.filter(g => g !== "Admin");
@@ -63,7 +63,7 @@ export function DefaultDrawer({
63
63
  </div>;
64
64
  }, [drawerOpen]);
65
65
 
66
- const onClick = (view: TopNavigationEntry) => {
66
+ const onClick = (view: NavigationEntry) => {
67
67
  const eventName: CMSAnalyticsEvent = view.type === "collection"
68
68
  ? "drawer_navigate_to_collection"
69
69
  : (view.type === "view" ? "drawer_navigate_to_view" : "unmapped_event");
@@ -90,9 +90,9 @@ export function DefaultDrawer({
90
90
  {buildGroupHeader(group)}
91
91
  {Object.values(navigationEntries)
92
92
  .filter(e => e.group === group)
93
- .map((view, index) =>
93
+ .map((view) =>
94
94
  <DrawerNavigationItem
95
- key={`navigation_${index}`}
95
+ key={view.id}
96
96
  icon={<IconForView collectionOrView={view.collection ?? view.view}
97
97
  size={"small"}/>}
98
98
  tooltipsOpen={tooltipsOpen}
@@ -128,13 +128,13 @@ export function DefaultDrawer({
128
128
  </div>}
129
129
  </IconButton>}
130
130
  >
131
- {adminViews.map((entry, index) =>
131
+ {adminViews.map((entry) =>
132
132
  <MenuItem
133
133
  onClick={(event) => {
134
134
  event.preventDefault();
135
- navigate(entry.path);
135
+ navigate(entry.url); // Consistent use of entry.url for navigation
136
136
  }}
137
- key={`navigation_${index}`}>
137
+ key={entry.id}>
138
138
  {<IconForView collectionOrView={entry.view}/>}
139
139
  {entry.name}
140
140
  </MenuItem>)}
@@ -38,7 +38,7 @@ export function DrawerNavigationItem({
38
38
  "flex flex-row items-center mr-8",
39
39
  // "transition-all ease-in-out delay-100 duration-300",
40
40
  // drawerOpen ? "w-full" : "w-18",
41
- drawerOpen ? "pl-4 h-12" : "pl-4 h-11",
41
+ drawerOpen ? "pl-4 h-10" : "pl-4 h-9",
42
42
  "font-semibold text-xs",
43
43
  isActive ? "bg-surface-accent-200 bg-opacity-60 dark:bg-surface-800 dark:bg-opacity-50" : ""
44
44
  )}