@rebasepro/admin 0.1.0 → 0.2.1

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 (242) hide show
  1. package/LICENSE +21 -6
  2. package/dist/{CollectionEditorDialog-MbvXGzEq.js → CollectionEditorDialog-BXIh2AXg.js} +40 -31
  3. package/dist/CollectionEditorDialog-BXIh2AXg.js.map +1 -0
  4. package/dist/{CollectionsStudioView-D9X6aiAr.js → CollectionsStudioView-jR8iz_ja.js} +6 -8
  5. package/dist/CollectionsStudioView-jR8iz_ja.js.map +1 -0
  6. package/dist/{ContentHomePage-CfVB1eUo.js → ContentHomePage-BQZWuOFb.js} +5 -7
  7. package/dist/ContentHomePage-BQZWuOFb.js.map +1 -0
  8. package/dist/{ExportCollectionAction-CUwJg4F9.js → ExportCollectionAction-CMdiiv1L.js} +36 -38
  9. package/dist/ExportCollectionAction-CMdiiv1L.js.map +1 -0
  10. package/dist/{ImportCollectionAction-DGa_SF_8.js → ImportCollectionAction-C05lE0IW.js} +5 -7
  11. package/dist/ImportCollectionAction-C05lE0IW.js.map +1 -0
  12. package/dist/{PropertyEditView-C4nlYmAc.js → PropertyEditView-BB5xjnhZ.js} +261 -165
  13. package/dist/PropertyEditView-BB5xjnhZ.js.map +1 -0
  14. package/dist/{RolesView-CNWxnR8e.js → RolesView-CULIHWZ9.js} +22 -11
  15. package/dist/RolesView-CULIHWZ9.js.map +1 -0
  16. package/dist/{UsersView-YiTIcXkA.js → UsersView-D7_AtJ44.js} +7 -71
  17. package/dist/UsersView-D7_AtJ44.js.map +1 -0
  18. package/dist/collection_editor/ui/collection_editor/LayoutModeSwitch.d.ts +2 -2
  19. package/dist/collection_editor/ui/collection_editor/properties/VectorPropertyField.d.ts +3 -0
  20. package/dist/collection_editor_ui.js +5 -5
  21. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +1 -1
  22. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +1 -1
  23. package/dist/components/EntityCollectionView/EntityCollectionListView.d.ts +18 -2
  24. package/dist/components/EntityCollectionView/FilterPresetsButton.d.ts +21 -0
  25. package/dist/components/EntityDetailView.d.ts +31 -0
  26. package/dist/components/EntityEditView.d.ts +3 -2
  27. package/dist/components/ReferenceTable/EntitySelectionTable.d.ts +1 -1
  28. package/dist/components/admin/CreationResultDialog.d.ts +5 -0
  29. package/dist/components/admin/RolesFilterSelect.d.ts +2 -0
  30. package/dist/components/admin/UserRolesSelectField.d.ts +2 -0
  31. package/dist/components/common/default_entity_actions.d.ts +7 -1
  32. package/dist/components/field_configs.d.ts +1 -1
  33. package/dist/components/index.d.ts +1 -0
  34. package/dist/data_import/utils/data.d.ts +1 -1
  35. package/dist/data_import/utils/file_headers.d.ts +6 -1
  36. package/dist/data_import/utils/file_to_json.d.ts +1 -11
  37. package/dist/data_import/utils/transforms.d.ts +11 -0
  38. package/dist/editor.js +2 -4
  39. package/dist/editor.js.map +1 -1
  40. package/dist/form/EntityForm.d.ts +1 -1
  41. package/dist/form/field_bindings/RelationFieldBinding.d.ts +1 -1
  42. package/dist/form/field_bindings/VectorFieldBinding.d.ts +11 -0
  43. package/dist/form/index.d.ts +1 -0
  44. package/dist/hooks/navigation/useResolvedViews.d.ts +2 -1
  45. package/dist/{index-CtzpHzMQ.js → index-BAM9KCmM.js} +4 -6
  46. package/dist/index-BAM9KCmM.js.map +1 -0
  47. package/dist/{index-DKlrVD1m.js → index-CoSNm3e3.js} +3 -3
  48. package/dist/index-CoSNm3e3.js.map +1 -0
  49. package/dist/{index-kHJXfLNI.js → index-D5OQhv-T.js} +3 -3
  50. package/dist/index-D5OQhv-T.js.map +1 -0
  51. package/dist/index.d.ts +4 -1
  52. package/dist/index.js +352 -148
  53. package/dist/index.js.map +1 -1
  54. package/dist/types/components/EntityFormActionsProps.d.ts +1 -1
  55. package/dist/types/components/EntityFormProps.d.ts +2 -1
  56. package/dist/types/fields.d.ts +3 -3
  57. package/dist/util/navigation_utils.d.ts +1 -1
  58. package/dist/{util-CwLmSpGp.js → util-DtbWD7LF.js} +5312 -2580
  59. package/dist/util-DtbWD7LF.js.map +1 -0
  60. package/package.json +45 -39
  61. package/src/collection_editor/ConfigControllerProvider.tsx +1 -1
  62. package/src/collection_editor/ui/AddKanbanColumnAction.tsx +12 -2
  63. package/src/collection_editor/ui/CollectionViewHeaderAction.tsx +1 -2
  64. package/src/collection_editor/ui/EditorCollectionAction.tsx +1 -2
  65. package/src/collection_editor/ui/EditorCollectionActionStart.tsx +1 -2
  66. package/src/collection_editor/ui/EditorEntityAction.tsx +1 -2
  67. package/src/collection_editor/ui/HomePageEditorCollectionAction.tsx +1 -2
  68. package/src/collection_editor/ui/NewCollectionButton.tsx +1 -2
  69. package/src/collection_editor/ui/NewCollectionCard.tsx +4 -6
  70. package/src/collection_editor/ui/PropertyAddColumnComponent.tsx +1 -2
  71. package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +10 -2
  72. package/src/collection_editor/ui/collection_editor/CollectionDetailsForm.tsx +18 -2
  73. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +22 -7
  74. package/src/collection_editor/ui/collection_editor/CollectionEditorWelcomeView.tsx +16 -2
  75. package/src/collection_editor/ui/collection_editor/CollectionJsonImportDialog.tsx +19 -9
  76. package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +13 -2
  77. package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +24 -2
  78. package/src/collection_editor/ui/collection_editor/CollectionRelationsTab.tsx +22 -3
  79. package/src/collection_editor/ui/collection_editor/CollectionStudioView.tsx +1 -2
  80. package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +11 -2
  81. package/src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx +12 -2
  82. package/src/collection_editor/ui/collection_editor/EntityActionsEditTab.tsx +16 -3
  83. package/src/collection_editor/ui/collection_editor/EnumForm.tsx +17 -2
  84. package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +18 -2
  85. package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +1 -2
  86. package/src/collection_editor/ui/collection_editor/KanbanConfigSection.tsx +1 -2
  87. package/src/collection_editor/ui/collection_editor/LayoutModeSwitch.tsx +17 -5
  88. package/src/collection_editor/ui/collection_editor/PropertyEditView.tsx +32 -6
  89. package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +7 -7
  90. package/src/collection_editor/ui/collection_editor/PropertyTree.tsx +14 -2
  91. package/src/collection_editor/ui/collection_editor/SubcollectionsEditTab.tsx +16 -2
  92. package/src/collection_editor/ui/collection_editor/ViewModeSwitch.tsx +9 -2
  93. package/src/collection_editor/ui/collection_editor/properties/BlockPropertyField.tsx +1 -2
  94. package/src/collection_editor/ui/collection_editor/properties/MapPropertyField.tsx +1 -2
  95. package/src/collection_editor/ui/collection_editor/properties/MarkdownPropertyField.tsx +9 -2
  96. package/src/collection_editor/ui/collection_editor/properties/StoragePropertyField.tsx +11 -2
  97. package/src/collection_editor/ui/collection_editor/properties/VectorPropertyField.tsx +34 -0
  98. package/src/collection_editor/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +15 -7
  99. package/src/collection_editor/ui/collection_editor/properties/conditions/ConditionsPanel.tsx +1 -2
  100. package/src/collection_editor/ui/collection_editor/properties/conditions/EnumConditionsEditor.tsx +15 -3
  101. package/src/collection_editor/ui/collection_editor/properties/conditions/property_paths.ts +1 -1
  102. package/src/collection_editor/ui/collection_editor/properties/validation/ValidationPanel.tsx +1 -2
  103. package/src/collection_editor/useLocalCollectionsConfigController.tsx +0 -2
  104. package/src/collection_editor/validateCollectionJson.ts +9 -9
  105. package/src/components/AdminModeSyncer.tsx +1 -1
  106. package/src/components/ArrayContainer.tsx +19 -15
  107. package/src/components/ClearFilterSortButton.tsx +1 -2
  108. package/src/components/CollectionEditorDialogs.tsx +1 -1
  109. package/src/components/DefaultAppBar.tsx +15 -3
  110. package/src/components/DefaultDrawer.tsx +3 -3
  111. package/src/components/DrawerNavigationGroup.tsx +1 -2
  112. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +19 -9
  113. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +2 -2
  114. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +1 -1
  115. package/src/components/EntityCollectionTable/fields/TableMultipleRelationField.tsx +1 -2
  116. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +1 -2
  117. package/src/components/EntityCollectionTable/fields/TableRelationField.tsx +1 -2
  118. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +1 -2
  119. package/src/components/EntityCollectionTable/fields/VirtualTableSelect.tsx +0 -1
  120. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +15 -27
  121. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +3 -4
  122. package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +1 -2
  123. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +3 -5
  124. package/src/components/EntityCollectionTable/table_bindings.tsx +51 -45
  125. package/src/components/EntityCollectionView/Board.tsx +1 -2
  126. package/src/components/EntityCollectionView/BoardColumn.tsx +9 -2
  127. package/src/components/EntityCollectionView/BoardColumnTitle.tsx +5 -4
  128. package/src/components/EntityCollectionView/EntityCard.tsx +2 -2
  129. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +18 -16
  130. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +16 -17
  131. package/src/components/EntityCollectionView/EntityCollectionListView.tsx +90 -21
  132. package/src/components/EntityCollectionView/EntityCollectionView.tsx +20 -11
  133. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +6 -7
  134. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +14 -5
  135. package/src/components/EntityCollectionView/FilterPresetsButton.tsx +292 -0
  136. package/src/components/EntityCollectionView/FiltersDialog.tsx +1 -2
  137. package/src/components/EntityCollectionView/SplitListView.tsx +76 -25
  138. package/src/components/EntityCollectionView/ViewModeToggle.tsx +20 -7
  139. package/src/components/EntityCollectionView/hooks/useKanbanDragAndDrop.ts +1 -1
  140. package/src/components/EntityCollectionView/useBoardDataController.tsx +74 -6
  141. package/src/components/EntityCollectionView/useEntityPreviewSlots.ts +1 -1
  142. package/src/components/EntityDetailView.tsx +619 -0
  143. package/src/components/EntityEditView.tsx +29 -10
  144. package/src/components/EntityEditViewFormActions.tsx +20 -7
  145. package/src/components/EntityPreview.tsx +14 -5
  146. package/src/components/EntitySidePanel.tsx +116 -62
  147. package/src/components/EntityView.tsx +1 -2
  148. package/src/components/HomePage/ContentHomePage.tsx +1 -1
  149. package/src/components/HomePage/FavouritesView.tsx +1 -2
  150. package/src/components/HomePage/NavigationCard.tsx +1 -2
  151. package/src/components/HomePage/NavigationCardBinding.tsx +1 -2
  152. package/src/components/HomePage/NavigationGroup.tsx +1 -2
  153. package/src/components/HomePage/SmallNavigationCard.tsx +1 -2
  154. package/src/components/PropertyIdCopyTooltip.tsx +1 -2
  155. package/src/components/RebaseAuthGate.tsx +2 -2
  156. package/src/components/RebaseNavigation.tsx +9 -7
  157. package/src/components/ReferenceTable/EntitySelectionTable.tsx +12 -8
  158. package/src/components/RelationSelector.tsx +34 -6
  159. package/src/components/SearchIconsView.tsx +10 -2
  160. package/src/components/SelectableTable/SelectableTable.tsx +4 -4
  161. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +1 -2
  162. package/src/components/SideDialogs.tsx +63 -38
  163. package/src/components/UserSelector.tsx +30 -6
  164. package/src/components/admin/CreationResultDialog.tsx +135 -0
  165. package/src/components/admin/RolesFilterSelect.tsx +45 -0
  166. package/src/components/admin/RolesView.tsx +53 -14
  167. package/src/components/admin/UserRolesSelectField.tsx +50 -0
  168. package/src/components/admin/UsersView.tsx +41 -124
  169. package/src/components/app/Scaffold.tsx +1 -2
  170. package/src/components/common/default_entity_actions.tsx +119 -12
  171. package/src/components/field_configs.tsx +39 -3
  172. package/src/components/history/EntityHistoryEntry.tsx +1 -2
  173. package/src/components/history/EntityHistoryView.tsx +1 -2
  174. package/src/components/index.ts +2 -0
  175. package/src/data_export/export/BasicExportAction.tsx +35 -38
  176. package/src/data_export/export/ExportCollectionAction.tsx +39 -40
  177. package/src/data_import/components/DataNewPropertiesMapping.tsx +15 -2
  178. package/src/data_import/components/ImportFileUpload.tsx +1 -2
  179. package/src/data_import/components/ImportNewPropertyFieldPreview.tsx +1 -2
  180. package/src/data_import/import/ImportCollectionAction.tsx +21 -8
  181. package/src/data_import/utils/data.ts +23 -5
  182. package/src/data_import/utils/file_headers.ts +13 -89
  183. package/src/data_import/utils/file_to_json.ts +43 -68
  184. package/src/data_import/utils/transforms.ts +47 -0
  185. package/src/editor/components/SlashCommandMenu.tsx +17 -2
  186. package/src/editor/components/editor-bubble-item.tsx +1 -1
  187. package/src/editor/extensions/Image/index.ts +1 -1
  188. package/src/editor/extensions/Image.ts +1 -1
  189. package/src/editor/selectors/color-selector.tsx +1 -2
  190. package/src/editor/selectors/link-selector.tsx +1 -2
  191. package/src/editor/selectors/node-selector.tsx +16 -2
  192. package/src/editor/selectors/text-buttons.tsx +1 -2
  193. package/src/editor/utils/prosemirror-utils.ts +1 -1
  194. package/src/form/EntityForm.tsx +16 -6
  195. package/src/form/EntityFormActions.tsx +11 -3
  196. package/src/form/PropertyFieldBinding.tsx +5 -12
  197. package/src/form/components/FieldHelperText.tsx +1 -2
  198. package/src/form/components/LocalChangesMenu.tsx +17 -2
  199. package/src/form/components/StorageItemPreview.tsx +1 -2
  200. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +1 -2
  201. package/src/form/field_bindings/KeyValueFieldBinding.tsx +17 -2
  202. package/src/form/field_bindings/MapFieldBinding.tsx +1 -1
  203. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +10 -3
  204. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +1 -2
  205. package/src/form/field_bindings/MultipleRelationFieldBinding.tsx +1 -2
  206. package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +7 -7
  207. package/src/form/field_bindings/RelationFieldBinding.tsx +150 -147
  208. package/src/form/field_bindings/RepeatFieldBinding.tsx +1 -1
  209. package/src/form/field_bindings/SelectFieldBinding.tsx +1 -2
  210. package/src/form/field_bindings/TextFieldBinding.tsx +10 -2
  211. package/src/form/field_bindings/VectorFieldBinding.tsx +202 -0
  212. package/src/form/index.tsx +1 -0
  213. package/src/form/validation.ts +54 -2
  214. package/src/hooks/navigation/useBuildNavigationStateController.tsx +2 -1
  215. package/src/hooks/navigation/useResolvedViews.tsx +30 -15
  216. package/src/hooks/navigation/useTopLevelNavigation.ts +1 -1
  217. package/src/index.ts +6 -0
  218. package/src/preview/PropertyPreview.tsx +1 -1
  219. package/src/preview/components/ImagePreview.tsx +1 -1
  220. package/src/preview/components/UrlComponentPreview.tsx +1 -2
  221. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +2 -2
  222. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +23 -24
  223. package/src/routes/RebaseRoute.tsx +64 -35
  224. package/src/types/components/EntityFormActionsProps.tsx +1 -1
  225. package/src/types/components/EntityFormProps.tsx +3 -1
  226. package/src/types/fields.tsx +4 -3
  227. package/src/util/navigation_utils.ts +4 -3
  228. package/src/util/previews.ts +1 -1
  229. package/src/util/property_utils.tsx +22 -6
  230. package/src/util/resolutions.ts +2 -2
  231. package/dist/CollectionEditorDialog-MbvXGzEq.js.map +0 -1
  232. package/dist/CollectionsStudioView-D9X6aiAr.js.map +0 -1
  233. package/dist/ContentHomePage-CfVB1eUo.js.map +0 -1
  234. package/dist/ExportCollectionAction-CUwJg4F9.js.map +0 -1
  235. package/dist/ImportCollectionAction-DGa_SF_8.js.map +0 -1
  236. package/dist/PropertyEditView-C4nlYmAc.js.map +0 -1
  237. package/dist/RolesView-CNWxnR8e.js.map +0 -1
  238. package/dist/UsersView-YiTIcXkA.js.map +0 -1
  239. package/dist/index-CtzpHzMQ.js.map +0 -1
  240. package/dist/index-DKlrVD1m.js.map +0 -1
  241. package/dist/index-kHJXfLNI.js.map +0 -1
  242. package/dist/util-CwLmSpGp.js.map +0 -1
@@ -0,0 +1,619 @@
1
+ import type { ComponentRef, EntityCollection, EntityCustomViewParams, EntityDetailViewConfig } from "@rebasepro/types";
2
+ import type { FormContext } from "../types/fields";
3
+ import type { PluginFormActionProps } from "@rebasepro/types";
4
+ import React, { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
5
+ import { Entity, EntityStatus, Property } from "@rebasepro/types";
6
+ import { PluginProviderStack, resolveComponentRef } from "@rebasepro/core";
7
+
8
+ import { EntityCollectionView, EntityView } from "../components";
9
+ import { CircularProgressCenter, iconSize } from "@rebasepro/ui";
10
+ import {
11
+ Alert,
12
+ Button,
13
+ CenteredView,
14
+ cls,
15
+ CodeIcon,
16
+ defaultBorderMixin,
17
+ HistoryIcon,
18
+ IconButton,
19
+ Maximize2Icon,
20
+ PencilIcon,
21
+ Skeleton,
22
+ Tab,
23
+ Tabs,
24
+ Tooltip,
25
+ Typography
26
+ } from "@rebasepro/ui";
27
+ import { ErrorBoundary } from "@rebasepro/ui";
28
+ import { ErrorView, createFormexStub, usePermissions, useTranslation } from "@rebasepro/core";
29
+ import {
30
+ getSubcollections,
31
+ removeInitialAndTrailingSlashes,
32
+ resolveDefaultSelectedView
33
+ } from "@rebasepro/common";
34
+ import { resolvedSelectedEntityView } from "../util/resolutions";
35
+ import {
36
+ useCustomizationController,
37
+ useEntityFetch,
38
+ useRebaseContext,
39
+ useLargeLayout,
40
+ useSlot
41
+ } from "@rebasepro/core";
42
+ import { useUrlController, useCollectionRegistryController } from "../index";
43
+ import { useNavigate } from "react-router-dom";
44
+ import { getValueInPath } from "@rebasepro/utils";
45
+ import { getEntityTitlePropertyKey } from "../util/previews";
46
+ import { EntityJsonPreview } from "../components/EntityJsonPreview";
47
+
48
+ const EntityHistoryView = lazy(() => import("../components/history").then(m => ({ default: m.EntityHistoryView })));
49
+
50
+ export const MAIN_TAB_VALUE = "__main_##Q$SC^#S6";
51
+ export const JSON_TAB_VALUE = "__json";
52
+ export const HISTORY_TAB_VALUE = "__rebase_history";
53
+
54
+ export type BarActionsParams = {
55
+ values: object,
56
+ status: EntityStatus,
57
+ path: string,
58
+ entityId?: string | number;
59
+ };
60
+
61
+ export type OnTabChangeParams<M extends Record<string, unknown>> = {
62
+ path: string;
63
+ entityId?: string | number;
64
+ selectedTab?: string;
65
+ collection: EntityCollection<M>;
66
+ };
67
+
68
+ export interface EntityDetailViewProps<M extends Record<string, unknown> = Record<string, unknown>> {
69
+ path: string;
70
+ collection: EntityCollection<M>;
71
+ entityId: string | number;
72
+ selectedTab?: string;
73
+ parentCollectionSlugs: string[];
74
+ parentEntityIds: string[];
75
+ onTabChange?: (props: OnTabChangeParams<M>) => void;
76
+ onEditClick?: () => void;
77
+ layout?: "side_panel" | "full_screen" | "split" | "dialog";
78
+ barActions?: (params: BarActionsParams) => React.ReactNode;
79
+ }
80
+
81
+ export function EntityDetailView<M extends Record<string, unknown>>({
82
+ entityId,
83
+ ...props
84
+ }: EntityDetailViewProps<M>) {
85
+
86
+ const {
87
+ entity,
88
+ dataLoading,
89
+ dataLoadingError
90
+ } = useEntityFetch<M>({
91
+ path: props.path,
92
+ entityId: entityId,
93
+ collection: props.collection,
94
+ useCache: false
95
+ });
96
+
97
+ if (!dataLoading && dataLoadingError) {
98
+ return <CenteredView>
99
+ <ErrorView error={dataLoadingError} />
100
+ </CenteredView>;
101
+ }
102
+
103
+ if (!dataLoading && !entity) {
104
+ return <CenteredView>
105
+ <Typography variant="label">Entity not found</Typography>
106
+ </CenteredView>;
107
+ }
108
+
109
+ return <EntityDetailViewInner<M>
110
+ {...props}
111
+ entityId={entityId}
112
+ entity={entity}
113
+ dataLoading={dataLoading}
114
+ />;
115
+ }
116
+
117
+ function EntityDetailViewInner<M extends Record<string, unknown>>({
118
+ path,
119
+ entityId,
120
+ selectedTab: selectedTabProp,
121
+ collection,
122
+ parentCollectionSlugs,
123
+ parentEntityIds,
124
+ onTabChange,
125
+ onEditClick,
126
+ entity,
127
+ dataLoading,
128
+ layout = "full_screen",
129
+ barActions
130
+ }: EntityDetailViewProps<M> & {
131
+ entity?: Entity<M>,
132
+ dataLoading: boolean,
133
+ }) {
134
+ const { t } = useTranslation();
135
+ const context = useRebaseContext();
136
+ const urlController = useUrlController();
137
+ const navigate = useNavigate();
138
+ const customizationController = useCustomizationController();
139
+ const plugins = customizationController.plugins;
140
+ const collectionRegistryController = useCollectionRegistryController();
141
+ const { canEdit: canEditHook } = usePermissions();
142
+
143
+ const canEdit = useMemo(() => {
144
+ return entity ? canEditHook(collection, path, entity) : false;
145
+ }, [canEditHook, entity, collection, path]);
146
+
147
+ const [usedEntity, setUsedEntity] = useState<Entity<M> | undefined>(entity);
148
+ useEffect(() => {
149
+ if (entity) setUsedEntity(entity);
150
+ }, [entity]);
151
+
152
+ const defaultSelectedView = useMemo(() => resolveDefaultSelectedView(
153
+ collection.defaultSelectedView,
154
+ { status: "existing", entityId }
155
+ ), [collection, entityId]);
156
+
157
+ const [selectedTab, setSelectedTab] = useState<string>(selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE);
158
+ useEffect(() => {
159
+ const target = selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE;
160
+ if (target !== selectedTab) {
161
+ setSelectedTab(target);
162
+ }
163
+ }, [selectedTabProp, defaultSelectedView]);
164
+
165
+ const subcollections = getSubcollections(collection).filter(c => !c.hideFromNavigation);
166
+ const subcollectionsCount = subcollections?.length ?? 0;
167
+ const customViews = collection.entityViews ?? [];
168
+ const customViewsCount = customViews?.length ?? 0;
169
+ const includeJsonView = collection.includeJsonView === undefined ? true : collection.includeJsonView;
170
+ const includeHistoryView = Boolean(collection.history);
171
+ const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0 || includeJsonView || includeHistoryView;
172
+
173
+ const {
174
+ resolvedEntityViews,
175
+ selectedEntityView
176
+ } = resolvedSelectedEntityView(customViews, customizationController, selectedTab, canEdit);
177
+
178
+ const mainViewVisible = selectedTab === MAIN_TAB_VALUE;
179
+
180
+ // Track which custom view tabs have been visited
181
+ const mountedTabsRef = useRef<Set<string>>(new Set());
182
+ if (selectedTab) {
183
+ mountedTabsRef.current.add(selectedTab);
184
+ }
185
+
186
+ // Read-only form context for custom entity views
187
+ const readOnlyFormContext = useMemo<FormContext<M> | undefined>(() => {
188
+ if (!entityId) return undefined;
189
+ const formexStub = createFormexStub<M>(usedEntity?.values ?? {} as M);
190
+ return {
191
+ entityId,
192
+ disabled: true,
193
+ readOnly: true,
194
+ openEntityMode: layout,
195
+ status: "existing",
196
+ values: usedEntity?.values ?? ({} as M),
197
+ setFieldValue: () => {
198
+ throw new Error("Cannot update values in read-only detail view");
199
+ },
200
+ save: () => {
201
+ throw new Error("Cannot save in read-only detail view");
202
+ },
203
+ collection,
204
+ path,
205
+ entity: usedEntity,
206
+ savingError: undefined,
207
+ formex: formexStub
208
+ };
209
+ }, [entityId, layout, usedEntity, collection, path]);
210
+
211
+ // Plugin slots
212
+ const formActionTopProps: PluginFormActionProps = useMemo(() => ({
213
+ entityId,
214
+ parentCollectionSlugs,
215
+ parentEntityIds,
216
+ path,
217
+ status: "existing",
218
+ collection: collection!,
219
+ context,
220
+ formContext: readOnlyFormContext as FormContext<Record<string, unknown>> | undefined,
221
+ openEntityMode: layout,
222
+ disabled: true
223
+ }), [entityId, parentCollectionSlugs, parentEntityIds, path, collection, context, readOnlyFormContext, layout]);
224
+ const pluginActionsTop = useSlot("form.actions.top", formActionTopProps);
225
+
226
+ // Detail view config from collection
227
+ const detailViewConfig = (collection as EntityCollection<M> & { detailView?: EntityDetailViewConfig<M> }).detailView;
228
+
229
+ // Title resolution
230
+ const titlePropertyKey = getEntityTitlePropertyKey(collection, customizationController.propertyConfigs);
231
+ const title = (usedEntity?.values && titlePropertyKey ? getValueInPath(usedEntity.values, titlePropertyKey) as React.ReactNode : undefined)
232
+ ?? collection.singularName
233
+ ?? collection.name;
234
+
235
+ // Non-action custom views
236
+ const nonActionCustomViews = useMemo(() =>
237
+ resolvedEntityViews.filter(e => !e.includeActions),
238
+ [resolvedEntityViews]
239
+ );
240
+
241
+ const customViewsView = customViews && nonActionCustomViews
242
+ .map((customView) => {
243
+ if (!customView) return null;
244
+ const Builder = resolveComponentRef<EntityCustomViewParams>(customView.Builder);
245
+ if (!Builder) return null;
246
+ if (!entityId) return null;
247
+
248
+ const isActive = selectedTab === customView.key;
249
+ const hasBeenMounted = mountedTabsRef.current.has(customView.key);
250
+ if (!isActive && !hasBeenMounted) return null;
251
+
252
+ return <div
253
+ className={cls(defaultBorderMixin,
254
+ "relative flex-1 w-full h-full overflow-auto",
255
+ { "hidden": !isActive }
256
+ )}
257
+ key={`custom_view_${customView.key}`}
258
+ role="tabpanel">
259
+ <ErrorBoundary>
260
+ <Suspense fallback={<CircularProgressCenter />}>
261
+ {readOnlyFormContext && <Builder
262
+ collection={collection}
263
+ parentCollectionSlugs={parentCollectionSlugs}
264
+ parentEntityIds={parentEntityIds}
265
+ entity={usedEntity}
266
+ modifiedValues={usedEntity?.values}
267
+ formContext={readOnlyFormContext as FormContext<Record<string, unknown>>}
268
+ />}
269
+ </Suspense>
270
+ </ErrorBoundary>
271
+ </div>;
272
+ }).filter(Boolean);
273
+
274
+ const globalLoading = dataLoading && !usedEntity;
275
+
276
+ // JSON view
277
+ const jsonTabMounted = mountedTabsRef.current.has(JSON_TAB_VALUE);
278
+ const jsonView = (selectedTab === JSON_TAB_VALUE || jsonTabMounted) ? <div
279
+ className={cls("relative flex-1 h-full overflow-auto w-full",
280
+ { "hidden": selectedTab !== JSON_TAB_VALUE })}
281
+ key={"json_view"}
282
+ role="tabpanel">
283
+ <ErrorBoundary>
284
+ <EntityJsonPreview values={usedEntity?.values ?? {}} />
285
+ </ErrorBoundary>
286
+ </div> : null;
287
+
288
+ // History view
289
+ const historyView = includeHistoryView && selectedTab === HISTORY_TAB_VALUE ? <div
290
+ className={"relative flex-1 h-full overflow-auto w-full"}
291
+ key={"history_view"}
292
+ role="tabpanel">
293
+ <ErrorBoundary>
294
+ <Suspense fallback={<CircularProgressCenter />}>
295
+ <EntityHistoryView
296
+ collection={collection}
297
+ entity={usedEntity}
298
+ formContext={readOnlyFormContext as FormContext<Record<string, unknown>>}
299
+ modifiedValues={usedEntity?.values}
300
+ />
301
+ </Suspense>
302
+ </ErrorBoundary>
303
+ </div> : null;
304
+
305
+ // Subcollection views
306
+ const subCollectionsViews = subcollections && subcollections.map((subcollection) => {
307
+ const subcollectionId = subcollection.slug;
308
+ const newFullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollection.slug)}` : undefined;
309
+
310
+ if (selectedTab !== subcollectionId) return null;
311
+ return (
312
+ <div
313
+ className={"relative flex-1 h-full overflow-auto w-full"}
314
+ key={`subcol_${subcollectionId}`}
315
+ role="tabpanel">
316
+ {globalLoading && <CircularProgressCenter />}
317
+ {!globalLoading &&
318
+ (usedEntity && newFullPath
319
+ ? <EntityCollectionView
320
+ path={newFullPath}
321
+ parentCollectionSlugs={[...parentCollectionSlugs, collection.slug]}
322
+ parentEntityIds={[...parentEntityIds, String(usedEntity?.id)]}
323
+ updateUrl={false}
324
+ {...subcollection}
325
+ openEntityMode={layout} />
326
+ : <div className="flex items-center justify-center w-full h-full p-3">
327
+ <Typography variant={"label"}>
328
+ {t("save_entity_before_subcollections") ?? "You need to save your entity before adding additional collections"}
329
+ </Typography>
330
+ </div>)
331
+ }
332
+ </div>
333
+ );
334
+ }).filter(Boolean);
335
+
336
+ const onSideTabClick = useCallback((value: string) => {
337
+ setSelectedTab(value);
338
+ onTabChange?.({
339
+ path,
340
+ entityId,
341
+ selectedTab: value === MAIN_TAB_VALUE ? undefined : value,
342
+ collection
343
+ });
344
+ }, [onTabChange, path, entityId, collection]);
345
+
346
+ const propertyDetailView = () => {
347
+ // Allow full override via detailView.Builder
348
+ if (detailViewConfig?.Builder) {
349
+ const CustomBuilder = resolveComponentRef(detailViewConfig.Builder);
350
+ if (CustomBuilder && usedEntity) {
351
+ return <CustomBuilder
352
+ collection={collection}
353
+ entity={usedEntity}
354
+ path={path}
355
+ onEditClick={onEditClick ?? (() => {})}
356
+ />;
357
+ }
358
+ }
359
+
360
+ const HeaderComponent = detailViewConfig?.Header ? resolveComponentRef(detailViewConfig.Header) : null;
361
+ const FooterComponent = detailViewConfig?.Footer ? resolveComponentRef(detailViewConfig.Footer) : null;
362
+
363
+ return (
364
+ <>
365
+ {HeaderComponent && usedEntity && <HeaderComponent
366
+ collection={collection}
367
+ entity={usedEntity}
368
+ path={path}
369
+ onEditClick={onEditClick ?? (() => {})}
370
+ />}
371
+
372
+ {usedEntity && <EntityView
373
+ entity={usedEntity}
374
+ collection={collection}
375
+ path={path}
376
+ />}
377
+
378
+ {FooterComponent && usedEntity && <FooterComponent
379
+ collection={collection}
380
+ entity={usedEntity}
381
+ path={path}
382
+ onEditClick={onEditClick ?? (() => {})}
383
+ />}
384
+ </>
385
+ );
386
+ };
387
+
388
+ // Tabs
389
+ const subcollectionTabs = subcollections && subcollections.map((subcollection) =>
390
+ <Tab
391
+ className="text-sm min-w-[90px]"
392
+ value={subcollection.slug}
393
+ key={`entity_detail_collection_tab_${subcollection.name}`}>
394
+ {subcollection.name}
395
+ </Tab>
396
+ );
397
+
398
+ const customViewTabsStart = resolvedEntityViews.filter(view => view.position === "start")
399
+ .map((view) =>
400
+ <Tab
401
+ className={!view.tabComponent ? "text-sm min-w-[90px]" : undefined}
402
+ value={view.key}
403
+ key={`entity_detail_collection_tab_${view.name}`}>
404
+ {view.tabComponent ?? view.name}
405
+ </Tab>
406
+ );
407
+ const customViewTabsEnd = resolvedEntityViews.filter(view => !view.position || view.position === "end")
408
+ .map((view) =>
409
+ <Tab
410
+ className={!view.tabComponent ? "text-sm min-w-[90px]" : undefined}
411
+ value={view.key}
412
+ key={`entity_detail_collection_tab_${view.name}`}>
413
+ {view.tabComponent ?? view.name}
414
+ </Tab>
415
+ );
416
+
417
+ const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews || layout === "side_panel" || layout === "dialog";
418
+
419
+ const fullScreenButton = !barActions && (layout === "side_panel" || layout === "split" || layout === "dialog") && entityId ? (
420
+ <Tooltip title={"Open full screen"}>
421
+ <IconButton
422
+ size="small"
423
+ onClick={() => {
424
+ const entityUrl = urlController.buildUrlCollectionPath(`${path}/${entityId}`);
425
+ navigate(`${entityUrl}#full`);
426
+ }}
427
+ >
428
+ <Maximize2Icon size={iconSize.smallest} />
429
+ </IconButton>
430
+ </Tooltip>
431
+ ) : null;
432
+
433
+ // Edit button — only rendered when the user has edit permissions
434
+ const editButton = canEdit && onEditClick ? (
435
+ <Button
436
+ variant="filled"
437
+ color="primary"
438
+ size="small"
439
+ startIcon={<PencilIcon size={iconSize.smallest} />}
440
+ onClick={onEditClick}>
441
+ {t("edit_entity")}
442
+ </Button>
443
+ ) : null;
444
+
445
+ // Main content view with title and properties
446
+ const mainView = <div
447
+ className={cls(
448
+ "flex-1 flex flex-row w-full overflow-y-auto justify-center",
449
+ !mainViewVisible ? "hidden" : ""
450
+ )}>
451
+ <div
452
+ className={cls("relative flex flex-row max-w-4xl lg:max-w-3xl xl:max-w-4xl 2xl:max-w-6xl w-full h-fit")}>
453
+ <div className={cls(
454
+ "flex flex-col w-full",
455
+ layout === "dialog"
456
+ ? "pt-4 pb-12 px-6 sm:px-8"
457
+ : "pt-12 pb-16 px-4 sm:px-8 md:px-10"
458
+ )}>
459
+ {/* Title and entity path */}
460
+ <div className={"w-full flex flex-col items-start my-4 lg:my-6"}>
461
+ <div className="flex items-center justify-between w-full">
462
+ <Typography
463
+ className={cls("my-4 grow line-clamp-1", collection.hideIdFromForm ? "mb-6" : "")}
464
+ variant={"h4"}>
465
+ {title}
466
+ </Typography>
467
+ {editButton}
468
+ </div>
469
+
470
+ <Alert color={"base"} outerClassName={"w-full"} size={"small"}>
471
+ <code
472
+ className={"text-xs select-all text-text-secondary dark:text-text-secondary-dark"}>
473
+ {usedEntity?.path ?? path}/{entityId}
474
+ </code>
475
+ </Alert>
476
+ </div>
477
+
478
+ {/* Property detail display */}
479
+ <div className="mt-12 flex flex-col gap-8">
480
+ {propertyDetailView()}
481
+ </div>
482
+
483
+ <div className="h-16" />
484
+ </div>
485
+
486
+ {/* Side action bar for large screens */}
487
+ {canEdit && onEditClick && layout === "full_screen" && <div
488
+ className={cls(
489
+ "overflow-auto h-full hidden @6xl:flex flex-col gap-2 w-80 2xl:w-96 px-4 py-16 sticky top-0 border-l",
490
+ defaultBorderMixin
491
+ )}>
492
+ <Button
493
+ fullWidth={true}
494
+ variant="filled"
495
+ color="primary"
496
+ startIcon={<PencilIcon size={iconSize.small} />}
497
+ onClick={onEditClick}>
498
+ {t("edit_entity")}
499
+ </Button>
500
+ </div>}
501
+ </div>
502
+ </div>;
503
+
504
+ let result = <div className="relative flex flex-col h-full w-full bg-white dark:bg-surface-800">
505
+
506
+ {shouldShowTopBar && <div
507
+ className={cls("h-[52px] items-center flex overflow-hidden w-full border-b pl-2 pr-2 flex bg-surface-50 dark:bg-surface-900", defaultBorderMixin)}>
508
+
509
+ {fullScreenButton}
510
+
511
+ {barActions?.({
512
+ path,
513
+ entityId,
514
+ values: usedEntity?.values ?? {},
515
+ status: "existing"
516
+ })}
517
+
518
+ {pluginActionsTop}
519
+
520
+ {hasAdditionalViews && <div className={"flex-1 flex justify-end min-w-0 shrink-0"}>
521
+ <Tabs
522
+ className={"!w-fit max-w-full"}
523
+ value={selectedTab}
524
+ onValueChange={(value) => {
525
+ onSideTabClick(value);
526
+ }}>
527
+
528
+ {includeJsonView && <Tab
529
+ disabled={!hasAdditionalViews}
530
+ value={JSON_TAB_VALUE}
531
+ className={"text-sm"}>
532
+ <CodeIcon size={iconSize.small} />
533
+ </Tab>}
534
+
535
+ {includeHistoryView && <Tab
536
+ disabled={!hasAdditionalViews}
537
+ value={HISTORY_TAB_VALUE}
538
+ className={"text-sm"}>
539
+ <HistoryIcon size={iconSize.small} />
540
+ </Tab>}
541
+
542
+ <Tab
543
+ disabled={!hasAdditionalViews}
544
+ value={MAIN_TAB_VALUE}
545
+ className={"text-sm min-w-[90px]"}>
546
+ {collection.singularName ?? collection.name}
547
+ </Tab>
548
+
549
+ {customViewTabsStart}
550
+ {customViewTabsEnd}
551
+ {subcollectionTabs}
552
+ </Tabs>
553
+ </div>}
554
+ </div>}
555
+
556
+ {globalLoading
557
+ ? <DetailViewSkeleton collection={collection} />
558
+ : mainView}
559
+
560
+ {jsonView}
561
+ {historyView}
562
+ {customViewsView}
563
+ {subCollectionsViews}
564
+
565
+ </div>;
566
+
567
+ if (plugins && plugins.length > 0) {
568
+ result = (
569
+ <PluginProviderStack
570
+ plugins={plugins}
571
+ scope="form"
572
+ scopeProps={{
573
+ status: "existing",
574
+ path,
575
+ collection,
576
+ entity: usedEntity,
577
+ context,
578
+ formContext: readOnlyFormContext
579
+ }}>
580
+ {result}
581
+ </PluginProviderStack>
582
+ );
583
+ }
584
+
585
+ return result;
586
+ }
587
+
588
+ function DetailViewSkeleton({ collection }: { collection: EntityCollection<Record<string, unknown>> }) {
589
+ return (
590
+ <div className="flex-1 flex flex-row w-full overflow-y-auto justify-center">
591
+ <div className="relative flex flex-row max-w-4xl lg:max-w-3xl xl:max-w-4xl 2xl:max-w-6xl w-full h-fit">
592
+ <div className="flex flex-col w-full pt-12 pb-16 px-4 sm:px-8 md:px-10">
593
+ <div className="w-full flex flex-col items-start my-4 lg:my-6">
594
+ <div className={`py-1 my-4 w-2/3 ${collection.hideIdFromForm ? "mb-6" : ""}`}>
595
+ <Skeleton height={28} className="w-full rounded-md" />
596
+ </div>
597
+ <Skeleton height={32} className="w-full rounded-md" />
598
+ </div>
599
+ <div className="mt-12 flex flex-col gap-8">
600
+ <div className="flex flex-wrap gap-x-4 w-full space-y-8">
601
+ <div className="relative w-full">
602
+ <Skeleton height={60} className="w-full rounded-md" />
603
+ </div>
604
+ <div className="relative w-full">
605
+ <Skeleton height={60} className="w-full rounded-md" />
606
+ </div>
607
+ <div className="relative w-full">
608
+ <Skeleton height={60} className="w-full rounded-md" />
609
+ </div>
610
+ <div className="relative w-full">
611
+ <Skeleton height={60} className="w-full rounded-md" />
612
+ </div>
613
+ </div>
614
+ </div>
615
+ </div>
616
+ </div>
617
+ </div>
618
+ );
619
+ }