@rebasepro/admin 0.12.1-canary.gf5f1d39 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{CollectionEditorDialog-Dpc75wIZ.js → CollectionEditorDialog-CjXkWZ64.js} +75 -62
- package/dist/{CollectionEditorDialog-Dpc75wIZ.js.map → CollectionEditorDialog-CjXkWZ64.js.map} +1 -1
- package/dist/{PropertyEditView-DH3XZC2x.js → PropertyEditView-W8I3YV89.js} +36 -16
- package/dist/{PropertyEditView-DH3XZC2x.js.map → PropertyEditView-W8I3YV89.js.map} +1 -1
- package/dist/{RouterCollectionsStudioView-BZLq4MGp.js → RouterCollectionsStudioView-BsLTNoqX.js} +5 -5
- package/dist/{RouterCollectionsStudioView-BZLq4MGp.js.map → RouterCollectionsStudioView-BsLTNoqX.js.map} +1 -1
- package/dist/collection_editor/serializable_types.d.ts +4 -1
- package/dist/collection_editor/ui/collection_editor/CollectionRelationsTab.d.ts +1 -0
- package/dist/collection_editor/useLocalCollectionsConfigController.d.ts +8 -0
- package/dist/collection_editor_ui.js +5 -5
- package/dist/collection_editor_ui.js.map +1 -1
- package/dist/components/CollectionTableBinding/column_utils.d.ts +17 -0
- package/dist/components/CollectionViewBinding/CollectionViewStartActions.d.ts +7 -2
- package/dist/components/CollectionViewBinding/SortButton.d.ts +24 -0
- package/dist/components/CollectionViewBinding/SplitListCloseButton.d.ts +10 -0
- package/dist/components/CollectionViewBinding/SplitViewContext.d.ts +23 -0
- package/dist/components/CollectionViewBinding/hooks/useKanbanDragAndDrop.d.ts +26 -0
- package/dist/components/DetailViewBinding.d.ts +5 -0
- package/dist/components/EditFormActions.d.ts +12 -1
- package/dist/components/EditViewBinding.d.ts +6 -1
- package/dist/components/EntityIdentityBar.d.ts +53 -0
- package/dist/components/EntityInspector.d.ts +35 -0
- package/dist/components/EntityPreviewNesting.d.ts +25 -0
- package/dist/components/EntityViewBinding.d.ts +9 -0
- package/dist/components/InlineEntityPreview.d.ts +47 -0
- package/dist/components/history/EntityHistoryView.d.ts +4 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/editor.js +18 -10
- package/dist/editor.js.map +1 -1
- package/dist/{export-BzIpUdSK.js → export-J79rMoNj.js} +2 -2
- package/dist/{export-BzIpUdSK.js.map → export-J79rMoNj.js.map} +1 -1
- package/dist/form/EntityForm.d.ts +1 -1
- package/dist/form/EntityFormActions.d.ts +11 -1
- package/dist/form/PropertyFieldBinding.d.ts +1 -1
- package/dist/form/components/FieldBlock.d.ts +30 -0
- package/dist/form/components/FormRail.d.ts +18 -0
- package/dist/form/components/FormSections.d.ts +16 -0
- package/dist/form/components/index.d.ts +3 -2
- package/dist/form/components/useRailVisible.d.ts +24 -0
- package/dist/form/field_bindings/BinaryFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/DateTimeFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/GeopointFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/MultiSelectFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/ReadOnlyFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/SelectFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/SwitchFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/TextFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/UserSelectFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/VectorFieldBinding.d.ts +1 -1
- package/dist/{history-g9688IaK.js → history-D7L_JlLO.js} +35 -33
- package/dist/history-D7L_JlLO.js.map +1 -0
- package/dist/hooks/navigation/utils.d.ts +16 -0
- package/dist/hooks/useBuildSidePanel.d.ts +14 -0
- package/dist/hooks/useEntityDisplayTitle.d.ts +18 -0
- package/dist/hooks/useHistory.d.ts +7 -0
- package/dist/hooks/useRecordActions.d.ts +17 -0
- package/dist/{import-BjUG-cIY.js → import-BvYl--U4.js} +2 -2
- package/dist/{import-BjUG-cIY.js.map → import-BvYl--U4.js.map} +1 -1
- package/dist/index.js +106 -35
- package/dist/index.js.map +1 -1
- package/dist/markdown-CklalUUk.js.map +1 -1
- package/dist/preview/components/InlineEntityListPreview.d.ts +15 -0
- package/dist/preview/index.d.ts +1 -0
- package/dist/preview/property_previews/ArrayOfReferencesPreview.d.ts +1 -1
- package/dist/preview/property_previews/ArrayOfRelationsPreview.d.ts +1 -1
- package/dist/types/components/EntityFormActionsProps.d.ts +0 -1
- package/dist/types/components/EntityFormProps.d.ts +0 -5
- package/dist/types/fields.d.ts +24 -0
- package/dist/{util-B5fJm1FA.js → util-GiwPgxnL.js} +2106 -1117
- package/dist/util-GiwPgxnL.js.map +1 -0
- package/package.json +32 -34
- package/src/collection_editor/ConfigControllerProvider.tsx +1 -1
- package/src/collection_editor/serializable_types.ts +4 -3
- package/src/collection_editor/serializable_utils.ts +51 -17
- package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +65 -22
- package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +38 -30
- package/src/collection_editor/ui/collection_editor/CollectionRelationsTab.tsx +3 -1
- package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/properties/RelationPropertyField.tsx +16 -0
- package/src/collection_editor/useLocalCollectionsConfigController.tsx +127 -15
- package/src/collection_editor/validateCollectionJson.ts +3 -3
- package/src/components/AdminModeSyncer.tsx +1 -1
- package/src/components/CollectionTableBinding/PropertyTableCell.tsx +10 -2
- package/src/components/CollectionTableBinding/column_utils.tsx +59 -2
- package/src/components/CollectionTableBinding/table_bindings.tsx +2 -1
- package/src/components/CollectionViewBinding/CollectionBoardViewBinding.tsx +23 -16
- package/src/components/CollectionViewBinding/CollectionViewActions.tsx +7 -1
- package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +9 -2
- package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +47 -12
- package/src/components/CollectionViewBinding/SortButton.tsx +189 -0
- package/src/components/CollectionViewBinding/SplitListCloseButton.tsx +33 -0
- package/src/components/CollectionViewBinding/SplitListView.tsx +18 -3
- package/src/components/CollectionViewBinding/SplitViewContext.tsx +34 -0
- package/src/components/CollectionViewBinding/hooks/useKanbanDragAndDrop.ts +48 -9
- package/src/components/CollectionViewBinding/useBoardDataController.tsx +80 -25
- package/src/components/CollectionViewBinding/usePreviewSlots.ts +18 -6
- package/src/components/DefaultAppBar.tsx +1 -1
- package/src/components/DefaultDrawer.tsx +1 -1
- package/src/components/DetailViewBinding.tsx +112 -148
- package/src/components/DrawerNavigationItem.tsx +1 -1
- package/src/components/EditFormActions.tsx +54 -129
- package/src/components/EditViewBinding.tsx +202 -99
- package/src/components/EntityIdentityBar.tsx +288 -0
- package/src/components/EntityInspector.tsx +137 -0
- package/src/components/EntityPreviewBinding.tsx +6 -2
- package/src/components/EntityPreviewNesting.tsx +50 -0
- package/src/components/EntityViewBinding.tsx +35 -32
- package/src/components/HomePage/FavouritesView.tsx +1 -1
- package/src/components/HomePage/NavigationCardBinding.tsx +1 -1
- package/src/components/HomePage/SmallNavigationCard.tsx +1 -1
- package/src/components/InlineEntityPreview.tsx +149 -0
- package/src/components/RebaseLayout.tsx +1 -1
- package/src/components/RebaseNavigation.tsx +45 -5
- package/src/components/RebaseRouteDefs.tsx +1 -1
- package/src/components/SelectableTable/SelectableTable.tsx +2 -2
- package/src/components/SidePanelBinding.tsx +19 -13
- package/src/components/UserSelector.tsx +12 -6
- package/src/components/common/default_entity_actions.tsx +4 -3
- package/src/components/history/EntityHistoryEntry.tsx +28 -7
- package/src/components/history/EntityHistoryView.tsx +29 -24
- package/src/components/history/LastEditedByIndicator.tsx +4 -3
- package/src/components/index.ts +2 -0
- package/src/data_import/utils/get_import_inference_type.ts +6 -0
- package/src/form/EntityForm.tsx +267 -179
- package/src/form/EntityFormActions.tsx +50 -99
- package/src/form/EntityFormBinding.tsx +4 -2
- package/src/form/PropertyFieldBinding.tsx +4 -0
- package/src/form/components/FieldBlock.tsx +119 -0
- package/src/form/components/FormRail.tsx +125 -0
- package/src/form/components/FormSections.tsx +118 -0
- package/src/form/components/index.tsx +3 -2
- package/src/form/components/useRailVisible.tsx +46 -0
- package/src/form/field_bindings/BinaryFieldBinding.tsx +3 -2
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +3 -2
- package/src/form/field_bindings/GeopointFieldBinding.tsx +3 -2
- package/src/form/field_bindings/MapFieldBinding.tsx +1 -2
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +3 -2
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +2 -1
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +2 -1
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +2 -1
- package/src/form/field_bindings/RelationFieldBinding.tsx +6 -4
- package/src/form/field_bindings/RepeatFieldBinding.tsx +4 -1
- package/src/form/field_bindings/SelectFieldBinding.tsx +3 -2
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +31 -8
- package/src/form/field_bindings/SwitchFieldBinding.tsx +7 -3
- package/src/form/field_bindings/TextFieldBinding.tsx +18 -16
- package/src/form/field_bindings/UserSelectFieldBinding.tsx +3 -2
- package/src/form/field_bindings/VectorFieldBinding.tsx +2 -1
- package/src/hooks/navigation/useBuildUrlController.tsx +1 -1
- package/src/hooks/navigation/useTopLevelNavigation.ts +18 -7
- package/src/hooks/navigation/utils.ts +20 -0
- package/src/hooks/useBuildSideDialogsController.tsx +2 -2
- package/src/hooks/useBuildSidePanel.tsx +22 -61
- package/src/hooks/useEntityDisplayTitle.tsx +60 -0
- package/src/hooks/useHistory.ts +20 -10
- package/src/hooks/useRecordActions.tsx +69 -0
- package/src/hooks/useResolvedUsers.ts +10 -5
- package/src/preview/PropertyPreview.tsx +48 -22
- package/src/preview/components/InlineEntityListPreview.tsx +42 -0
- package/src/preview/components/ReferencePreview.tsx +34 -14
- package/src/preview/components/RelationPreview.tsx +34 -14
- package/src/preview/index.ts +1 -0
- package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +24 -1
- package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +25 -1
- package/src/routes/RebaseRoute.tsx +2 -2
- package/src/types/components/EntityFormActionsProps.tsx +0 -1
- package/src/types/components/EntityFormProps.tsx +0 -6
- package/src/types/fields.tsx +27 -0
- package/dist/form/components/FormEntry.d.ts +0 -6
- package/dist/form/components/FormLayout.d.ts +0 -5
- package/dist/history-g9688IaK.js.map +0 -1
- package/dist/util-B5fJm1FA.js.map +0 -1
- package/src/form/components/FormEntry.tsx +0 -22
- package/src/form/components/FormLayout.tsx +0 -16
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { et as useHistory, hn as PropertyPreview, pr as getPropertyInPath } from "./util-GiwPgxnL.js";
|
|
2
|
+
import * as React$1 from "react";
|
|
2
3
|
import { useEffect, useRef, useState } from "react";
|
|
3
4
|
import { ArrowLeftIcon, Chip, ErrorBoundary, HistoryIcon, IconButton, Label, Tooltip, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
|
|
4
|
-
import { ConfirmationDialog, useAuthController, useSnackbarController } from "@rebasepro/app";
|
|
5
|
+
import { ConfirmationDialog, useAuthController, useSnackbarController, useTranslation } from "@rebasepro/app";
|
|
5
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
7
|
import { getValueInPath } from "@rebasepro/utils";
|
|
7
8
|
//#region src/components/history/UserChip.tsx
|
|
@@ -82,16 +83,25 @@ function PreviousValueView({ previousValueInPath, childProperty, propertyKey })
|
|
|
82
83
|
*/
|
|
83
84
|
function EntityHistoryEntry({ actions, hover, collection, size, entry }) {
|
|
84
85
|
const authController = useAuthController();
|
|
85
|
-
const changedFields =
|
|
86
|
+
const changedFields = React$1.useMemo(() => {
|
|
87
|
+
const all = entry.changed_fields;
|
|
88
|
+
if (!all || !collection) return all;
|
|
89
|
+
return all.filter((key) => {
|
|
90
|
+
const property = getPropertyInPath(collection.properties, key);
|
|
91
|
+
if (!property) return true;
|
|
92
|
+
if ("isId" in property && property.isId) return false;
|
|
93
|
+
return !(property.type === "date" && "autoValue" in property && property.autoValue);
|
|
94
|
+
});
|
|
95
|
+
}, [entry.changed_fields, collection]);
|
|
86
96
|
const previousValues = entry.previous_values;
|
|
87
97
|
const updatedOn = new Date(entry.updated_at);
|
|
88
98
|
const updatedBy = entry.updated_by;
|
|
89
99
|
const currentUser = authController.user;
|
|
90
100
|
const userDisplay = updatedBy === currentUser?.uid ? currentUser : void 0;
|
|
91
101
|
return /* @__PURE__ */ jsxs("div", {
|
|
92
|
-
className: "w-full flex flex-col gap-2
|
|
102
|
+
className: "@container/histentry w-full flex flex-col gap-2",
|
|
93
103
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
94
|
-
className: "
|
|
104
|
+
className: "flex items-center flex-wrap gap-2",
|
|
95
105
|
children: [
|
|
96
106
|
/* @__PURE__ */ jsx(Typography, {
|
|
97
107
|
variant: "body2",
|
|
@@ -126,14 +136,14 @@ function EntityHistoryEntry({ actions, hover, collection, size, entry }) {
|
|
|
126
136
|
children: typeof valueInPath === "string" ? valueInPath : JSON.stringify(valueInPath)
|
|
127
137
|
});
|
|
128
138
|
return /* @__PURE__ */ jsxs("div", {
|
|
129
|
-
className: "flex w-full my-1
|
|
139
|
+
className: "flex flex-col @sm/histentry:flex-row @sm/histentry:items-baseline gap-x-4 gap-y-0.5 w-full my-1.5",
|
|
130
140
|
children: [/* @__PURE__ */ jsx(Typography, {
|
|
131
141
|
variant: "caption",
|
|
132
142
|
color: "secondary",
|
|
133
|
-
className: "
|
|
143
|
+
className: "shrink-0 break-all @sm/histentry:w-40 @sm/histentry:text-right @sm/histentry:break-normal @sm/histentry:truncate",
|
|
134
144
|
children: key
|
|
135
145
|
}), /* @__PURE__ */ jsxs("div", {
|
|
136
|
-
className: "w-
|
|
146
|
+
className: "min-w-0 flex-1 flex flex-col items-start gap-0.5",
|
|
137
147
|
children: [previousValueInPath !== void 0 && !deepEqual(previousValueInPath, valueInPath) && /* @__PURE__ */ jsx(PreviousValueView, {
|
|
138
148
|
previousValueInPath,
|
|
139
149
|
childProperty,
|
|
@@ -144,7 +154,6 @@ function EntityHistoryEntry({ actions, hover, collection, size, entry }) {
|
|
|
144
154
|
}), (!changedFields || changedFields.length === 0) && /* @__PURE__ */ jsx(Typography, {
|
|
145
155
|
variant: "caption",
|
|
146
156
|
color: "secondary",
|
|
147
|
-
className: "ml-4",
|
|
148
157
|
children: entry.action === "create" ? "Entity created" : entry.action === "delete" ? "Entity deleted" : "No field changes recorded"
|
|
149
158
|
})]
|
|
150
159
|
})]
|
|
@@ -157,7 +166,8 @@ function EntityHistoryEntry({ actions, hover, collection, size, entry }) {
|
|
|
157
166
|
* Entity history tab view. Shows a paginated list of entity revisions
|
|
158
167
|
* fetched from the backend API. Supports infinite scroll and revert.
|
|
159
168
|
*/
|
|
160
|
-
function EntityHistoryView({ entity, collection, formContext }) {
|
|
169
|
+
function EntityHistoryView({ entity, collection, formContext, refreshToken }) {
|
|
170
|
+
const { t } = useTranslation();
|
|
161
171
|
const snackbarController = useSnackbarController();
|
|
162
172
|
useAuthController();
|
|
163
173
|
const dirty = formContext?.formex.dirty;
|
|
@@ -167,7 +177,8 @@ function EntityHistoryView({ entity, collection, formContext }) {
|
|
|
167
177
|
slug,
|
|
168
178
|
entityId,
|
|
169
179
|
enabled: !!entityId,
|
|
170
|
-
pageSize: 10
|
|
180
|
+
pageSize: 10,
|
|
181
|
+
refreshToken
|
|
171
182
|
});
|
|
172
183
|
const [revertHistoryId, setRevertHistoryId] = useState();
|
|
173
184
|
const [isReverting, setIsReverting] = useState(false);
|
|
@@ -194,7 +205,7 @@ function EntityHistoryView({ entity, collection, formContext }) {
|
|
|
194
205
|
]);
|
|
195
206
|
if (!entity) return /* @__PURE__ */ jsx("div", {
|
|
196
207
|
className: "flex items-center justify-center h-full",
|
|
197
|
-
children: /* @__PURE__ */ jsx(Label, { children: "History is only available for existing
|
|
208
|
+
children: /* @__PURE__ */ jsx(Label, { children: t("history_new_entity") ?? "History is only available for existing records" })
|
|
198
209
|
});
|
|
199
210
|
async function doRevert(historyId) {
|
|
200
211
|
setIsReverting(true);
|
|
@@ -207,13 +218,13 @@ function EntityHistoryView({ entity, collection, formContext }) {
|
|
|
207
218
|
touched: {}
|
|
208
219
|
});
|
|
209
220
|
snackbarController.open({
|
|
210
|
-
message: "Reverted to selected version",
|
|
221
|
+
message: t("history_reverted") ?? "Reverted to selected version",
|
|
211
222
|
type: "info"
|
|
212
223
|
});
|
|
213
224
|
} catch (error) {
|
|
214
225
|
console.error("Error reverting entity:", error);
|
|
215
226
|
snackbarController.open({
|
|
216
|
-
message: "Error reverting entity",
|
|
227
|
+
message: t("history_revert_error") ?? "Error reverting entity",
|
|
217
228
|
type: "error"
|
|
218
229
|
});
|
|
219
230
|
} finally {
|
|
@@ -223,17 +234,12 @@ function EntityHistoryView({ entity, collection, formContext }) {
|
|
|
223
234
|
const revertEntry = revertHistoryId ? entries.find((e) => e.id === revertHistoryId) : void 0;
|
|
224
235
|
return /* @__PURE__ */ jsxs("div", {
|
|
225
236
|
ref: containerRef,
|
|
226
|
-
className: cls("relative flex-1 h-full overflow-auto w-full flex flex-col gap-4 p-
|
|
237
|
+
className: cls("relative flex-1 h-full overflow-auto w-full flex flex-col gap-4 p-4 sm:p-5"),
|
|
227
238
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
228
239
|
className: "flex flex-col gap-2 max-w-6xl mx-auto w-full",
|
|
229
240
|
children: [
|
|
230
|
-
/* @__PURE__ */ jsx(Typography, {
|
|
231
|
-
variant: "h5",
|
|
232
|
-
className: "mt-24 ml-4",
|
|
233
|
-
children: "History"
|
|
234
|
-
}),
|
|
235
241
|
isLoading && entries.length === 0 && /* @__PURE__ */ jsx("div", {
|
|
236
|
-
className: "flex flex-col gap-4
|
|
242
|
+
className: "flex flex-col gap-4",
|
|
237
243
|
children: [
|
|
238
244
|
1,
|
|
239
245
|
2,
|
|
@@ -243,27 +249,23 @@ function EntityHistoryView({ entity, collection, formContext }) {
|
|
|
243
249
|
children: [/* @__PURE__ */ jsx("div", { className: "h-4 w-48 bg-surface-200 dark:bg-surface-700 rounded" }), /* @__PURE__ */ jsx("div", { className: "h-12 w-full bg-surface-100 dark:bg-surface-900 rounded-lg border border-surface-200 dark:border-surface-700" })]
|
|
244
250
|
}, i))
|
|
245
251
|
}),
|
|
246
|
-
!isLoading && entries.length === 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Label, {
|
|
247
|
-
className: "ml-4 mt-8",
|
|
248
|
-
children: "No history available"
|
|
249
|
-
}), /* @__PURE__ */ jsx(Typography, {
|
|
252
|
+
!isLoading && entries.length === 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Label, { children: t("history_empty") ?? "No history available" }), /* @__PURE__ */ jsx(Typography, {
|
|
250
253
|
variant: "caption",
|
|
251
|
-
|
|
252
|
-
children: "When you save a entity, a new version is created and stored in the history."
|
|
254
|
+
children: t("history_empty_description") ?? "When you save a record, a new version is created and stored in the history."
|
|
253
255
|
})] }),
|
|
254
256
|
entries.map((entry) => /* @__PURE__ */ jsx("div", {
|
|
255
|
-
className: "flex flex-
|
|
257
|
+
className: "flex flex-col gap-2 w-full",
|
|
256
258
|
children: /* @__PURE__ */ jsx(EntityHistoryEntry, {
|
|
257
259
|
entry,
|
|
258
260
|
collection,
|
|
259
261
|
size: "medium",
|
|
260
262
|
actions: /* @__PURE__ */ jsx(Tooltip, {
|
|
261
|
-
title: "Revert to this version",
|
|
263
|
+
title: t("history_revert") ?? "Revert to this version",
|
|
262
264
|
className: "m-2 grow-0 self-start",
|
|
263
265
|
children: /* @__PURE__ */ jsx(IconButton, {
|
|
264
266
|
onClick: () => {
|
|
265
267
|
if (dirty) snackbarController.open({
|
|
266
|
-
message: "Please save or discard your changes before reverting",
|
|
268
|
+
message: t("history_revert_dirty") ?? "Please save or discard your changes before reverting",
|
|
267
269
|
type: "warning"
|
|
268
270
|
});
|
|
269
271
|
else setRevertHistoryId(entry.id);
|
|
@@ -276,7 +278,7 @@ function EntityHistoryView({ entity, collection, formContext }) {
|
|
|
276
278
|
entries.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
277
279
|
ref: loadMoreRef,
|
|
278
280
|
className: "py-4 text-center",
|
|
279
|
-
children: [isLoading && /* @__PURE__ */ jsx(Label, { children: "
|
|
281
|
+
children: [isLoading && /* @__PURE__ */ jsx(Label, { children: t("loading") }), !hasMore && entries.length > 10 && /* @__PURE__ */ jsx(Label, { children: t("history_no_more") ?? "No more history" })]
|
|
280
282
|
})
|
|
281
283
|
]
|
|
282
284
|
}), /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(ConfirmationDialog, {
|
|
@@ -287,7 +289,7 @@ function EntityHistoryView({ entity, collection, formContext }) {
|
|
|
287
289
|
onCancel: () => setRevertHistoryId(void 0),
|
|
288
290
|
title: /* @__PURE__ */ jsx(Typography, {
|
|
289
291
|
variant: "subtitle2",
|
|
290
|
-
children: "Revert data to this version?"
|
|
292
|
+
children: t("history_revert_title") ?? "Revert data to this version?"
|
|
291
293
|
}),
|
|
292
294
|
body: revertEntry ? /* @__PURE__ */ jsx("div", {
|
|
293
295
|
className: "p-4",
|
|
@@ -308,4 +310,4 @@ function EntityHistoryView({ entity, collection, formContext }) {
|
|
|
308
310
|
//#endregion
|
|
309
311
|
export { EntityHistoryView };
|
|
310
312
|
|
|
311
|
-
//# sourceMappingURL=history-
|
|
313
|
+
//# sourceMappingURL=history-D7L_JlLO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history-D7L_JlLO.js","names":[],"sources":["../src/components/history/UserChip.tsx","../src/components/history/EntityHistoryEntry.tsx","../src/components/history/EntityHistoryView.tsx"],"sourcesContent":["import { User } from \"@rebasepro/types\";\nimport { Chip, Tooltip } from \"@rebasepro/ui\";\n\nexport function UserChip({ user }: { user: User }) {\n return (\n <Tooltip title={user.email ?? user.uid}>\n <Chip size={\"small\"} className={\"flex items-center\"}>\n {user.photoURL && <img\n className={\"rounded-full w-6 h-6 mr-2\"}\n src={user.photoURL} alt={user.displayName ?? \"User picture\"}/>}\n <span>{user.displayName ?? user.email ?? user.uid}</span>\n </Chip>\n </Tooltip>\n );\n}\n","\nimport type { Property } from \"@rebasepro/types\";\nimport * as React from \"react\";\n\nimport { ArrowLeftIcon, Chip, cls, defaultBorderMixin, iconSize, Tooltip, Typography } from \"@rebasepro/ui\";\nimport { PreviewSize } from \"../../types/components/PropertyPreviewProps\";\nimport { getPropertyInPath } from \"../../util/property_utils\";\nimport { PropertyPreview } from \"../../preview/PropertyPreview\";\nimport { SkeletonPropertyComponent } from \"../../preview/property_previews/SkeletonPropertyComponent\";\nimport { useAuthController } from \"@rebasepro/app\";\nimport { UserChip } from \"./UserChip\";\nimport { HistoryEntryData } from \"../../hooks\";\nimport { getValueInPath } from \"@rebasepro/utils\";\nimport type { AdminCollection } from \"@rebasepro/admin-types\";\n\n/**\n * Recursive deep equality for primitives, arrays and plain objects.\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a == null || b == null) return a === b;\n if (typeof a !== typeof b) return false;\n if (typeof a !== \"object\") return false;\n\n if (Array.isArray(a)) {\n if (!Array.isArray(b) || a.length !== (b as unknown[]).length) return false;\n return a.every((item, i) => deepEqual(item, (b as unknown[])[i]));\n }\n\n if (Array.isArray(b)) return false;\n\n const aObj = a as Record<string, unknown>;\n const bObj = b as Record<string, unknown>;\n const aKeys = Object.keys(aObj);\n const bKeys = Object.keys(bObj);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every(key => key in bObj && deepEqual(aObj[key], bObj[key]));\n}\n\nexport type EntityHistoryEntryProps = {\n size: PreviewSize;\n actions?: React.ReactNode;\n collection?: AdminCollection;\n hover?: boolean;\n entry: HistoryEntryData;\n onClick?: (e: React.SyntheticEvent) => void;\n};\n\nfunction PreviousValueView({\n previousValueInPath,\n childProperty,\n propertyKey\n}: {\n previousValueInPath: unknown;\n childProperty: Property;\n propertyKey: string;\n}) {\n if (typeof previousValueInPath === \"string\" || typeof previousValueInPath === \"number\") {\n return <Typography variant={\"caption\"} color={\"secondary\"} className=\"line-through\">\n {previousValueInPath}\n </Typography>;\n } else if (typeof previousValueInPath === \"boolean\") {\n return <Typography variant={\"caption\"} color={\"secondary\"} className=\"line-through\">\n {previousValueInPath ? \"true\" : \"false\"}\n </Typography>;\n } else {\n return <Tooltip\n side={\"left\"}\n title={<div className={\"flex flex-col gap-2\"}>\n <Typography variant={\"caption\"} color={\"secondary\"}>\n Previous value\n </Typography>\n <PropertyPreview\n propertyKey={propertyKey as string}\n value={previousValueInPath as never}\n property={childProperty as Property}\n size={\"small\"}/>\n </div>}>\n <ArrowLeftIcon size={iconSize.smallest} color={\"disabled\"} className={\"mb-1\"}/>\n </Tooltip>\n }\n}\n\n/**\n * Displays a single entity history revision entry.\n * Adapted from the entity_history plugin — now reads from backend API data.\n */\nexport function EntityHistoryEntry({\n actions,\n hover,\n collection,\n size,\n entry\n}: EntityHistoryEntryProps) {\n\n const authController = useAuthController();\n\n // The backend reports the whole row as changed, which includes the columns\n // that cannot meaningfully change: a timestamp it stamps itself on every\n // write, and the primary key, which is what identifies the row being\n // revised. Every revision was listing `updated_at: <now>` and `id: <the id\n // in the title bar>` around the one field the user actually edited.\n const changedFields = React.useMemo(() => {\n const all = entry.changed_fields;\n if (!all || !collection) return all;\n return all.filter(key => {\n const property = getPropertyInPath(collection.properties, key);\n if (!property) return true;\n if (\"isId\" in property && property.isId) return false;\n return !(property.type === \"date\" && \"autoValue\" in property && property.autoValue);\n });\n }, [entry.changed_fields, collection]);\n\n const previousValues = entry.previous_values;\n const updatedOn = new Date(entry.updated_at);\n const updatedBy = entry.updated_by;\n\n // Resolve user display\n const currentUser = authController.user;\n const userDisplay = updatedBy === currentUser?.uid\n ? currentUser\n : undefined;\n\n return <div className={\"@container/histentry w-full flex flex-col gap-2\"}>\n <div className={\"flex items-center flex-wrap gap-2\"}>\n <Typography variant={\"body2\"} color={\"secondary\"}>\n {updatedOn.toLocaleString()}\n </Typography>\n <Chip size={\"small\"}>\n {entry.action}\n </Chip>\n {!userDisplay && updatedBy && <Chip size={\"small\"}>{updatedBy}</Chip>}\n {userDisplay && <UserChip user={userDisplay}/>}\n </div>\n <div\n className={cls(\n \"bg-white dark:bg-surface-900\",\n \"min-h-[44px]\",\n \"w-full\",\n \"items-center\",\n hover ? \"hover:bg-surface-accent-50 dark:hover:bg-surface-800\" : \"\",\n size === \"small\" ? \"p-1\" : \"px-2 py-1\",\n \"flex border rounded-lg\",\n defaultBorderMixin\n )}>\n\n {actions}\n\n <div className={\"flex flex-col grow w-full m-1 shrink min-w-0\"}>\n\n {changedFields && collection && changedFields.map((key: string) => {\n const childProperty = getPropertyInPath(collection.properties, key);\n const valueInPath = entry.values ? getValueInPath(entry.values, key) : undefined;\n const previousValueInPath = previousValues ? getValueInPath(previousValues, key) : undefined;\n\n const element = childProperty\n ? <PropertyPreview\n propertyKey={key}\n value={valueInPath as never}\n property={childProperty as Property}\n size={\"small\"}/>\n : <Typography variant={\"body2\"}>\n {typeof valueInPath === \"string\" ? valueInPath : JSON.stringify(valueInPath)}\n </Typography>;\n\n // The key column used to be `min-w-[200px] w-1/5` beside a\n // `w-4/5` value — 200px of label and 80% of the row, which\n // adds up to more than the row in anything narrower than a\n // full page. In the inspector that pushed every value off\n // the right edge and wrapped ids one character per line.\n // It stacks until the entry itself is wide enough.\n return (\n <div key={\"ref_prev_\" + key}\n className=\"flex flex-col @sm/histentry:flex-row @sm/histentry:items-baseline gap-x-4 gap-y-0.5 w-full my-1.5\">\n <Typography variant={\"caption\"}\n color={\"secondary\"}\n className=\"shrink-0 break-all @sm/histentry:w-40 @sm/histentry:text-right @sm/histentry:break-normal @sm/histentry:truncate\">\n {key}\n </Typography>\n <div className=\"min-w-0 flex-1 flex flex-col items-start gap-0.5\">\n {previousValueInPath !== undefined && !deepEqual(previousValueInPath, valueInPath) &&\n <PreviousValueView previousValueInPath={previousValueInPath}\n childProperty={childProperty as Property}\n propertyKey={key}/>\n }\n {element}\n </div>\n </div>\n );\n })}\n\n {(!changedFields || changedFields.length === 0) && (\n <Typography variant={\"caption\"} color={\"secondary\"}>\n {entry.action === \"create\" ? \"Entity created\" :\n entry.action === \"delete\" ? \"Entity deleted\" : \"No field changes recorded\"}\n </Typography>\n )}\n\n </div>\n\n </div>\n </div>;\n}\n","\nimport type { EntityCustomViewParams } from \"@rebasepro/admin-types\";\nimport { useRef, useEffect } from \"react\";\nimport { cls, IconButton, Label, Tooltip, Typography } from \"@rebasepro/ui\";\nimport { ErrorBoundary, HistoryIcon } from \"@rebasepro/ui\";\nimport { EntityHistoryEntry } from \"./EntityHistoryEntry\";\nimport { useSnackbarController, useAuthController, useTranslation } from \"@rebasepro/app\";\nimport { ConfirmationDialog } from \"@rebasepro/app\";\nimport { useState } from \"react\";\nimport { useHistory } from \"../../hooks/useHistory\";\n\n/**\n * Entity history tab view. Shows a paginated list of entity revisions\n * fetched from the backend API. Supports infinite scroll and revert.\n */\nexport function EntityHistoryView<M extends Record<string, unknown>>({\n entity,\n collection,\n formContext,\n refreshToken\n}: EntityCustomViewParams<M> & {\n /** Bumped once per save, so the list picks up the revision it just made. */\n refreshToken?: number;\n}) {\n\n const { t } = useTranslation();\n const snackbarController = useSnackbarController();\n const authController = useAuthController();\n const dirty = formContext?.formex.dirty;\n\n const slug = collection.slug;\n const entityId = entity?.id;\n\n const {\n entries,\n isLoading,\n hasMore,\n loadMore,\n revert\n } = useHistory({\n slug,\n entityId,\n enabled: !!entityId,\n pageSize: 10,\n refreshToken\n });\n\n const [revertHistoryId, setRevertHistoryId] = useState<string | undefined>();\n const [isReverting, setIsReverting] = useState(false);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const loadMoreRef = useRef<HTMLDivElement>(null);\n\n // Intersection observer for infinite scroll\n useEffect(() => {\n const currentContainer = containerRef.current;\n const currentLoadMore = loadMoreRef.current;\n\n if (!currentContainer || !currentLoadMore || !hasMore || isLoading) return;\n\n const observer = new IntersectionObserver(\n (observerEntries) => {\n if (observerEntries[0].isIntersecting && hasMore && !isLoading) {\n loadMore();\n }\n },\n {\n root: currentContainer,\n rootMargin: \"0px 0px 200px 0px\",\n threshold: 0.01\n }\n );\n\n observer.observe(currentLoadMore);\n\n return () => observer.disconnect();\n }, [hasMore, isLoading, entries.length, loadMore]);\n\n if (!entity) {\n return <div className=\"flex items-center justify-center h-full\">\n <Label>{t(\"history_new_entity\") ?? \"History is only available for existing records\"}</Label>\n </div>;\n }\n\n async function doRevert(historyId: string) {\n setIsReverting(true);\n try {\n const revertedValues = await revert(historyId);\n setRevertHistoryId(undefined);\n\n // Reset the form with the reverted values so the UI updates\n // immediately without requiring a page refresh.\n if (formContext?.formex?.resetForm && revertedValues) {\n formContext.formex.resetForm({\n values: revertedValues as M,\n submitCount: 0,\n touched: {}\n });\n }\n\n snackbarController.open({\n message: t(\"history_reverted\") ?? \"Reverted to selected version\",\n type: \"info\"\n });\n } catch (error) {\n console.error(\"Error reverting entity:\", error);\n snackbarController.open({\n message: t(\"history_revert_error\") ?? \"Error reverting entity\",\n type: \"error\"\n });\n } finally {\n setIsReverting(false);\n }\n }\n\n const revertEntry = revertHistoryId\n ? entries.find(e => e.id === revertHistoryId)\n : undefined;\n\n // `p-8` plus a `mt-24` heading were sized for a full-width tab that no\n // longer exists. In the inspector panel that was 96px of nothing above the\n // first revision, and 32px of gutter on each side of a 448px column.\n return <div\n ref={containerRef}\n className={cls(\"relative flex-1 h-full overflow-auto w-full flex flex-col gap-4 p-4 sm:p-5\")}>\n <div className=\"flex flex-col gap-2 max-w-6xl mx-auto w-full\">\n\n {isLoading && entries.length === 0 && (\n <div className=\"flex flex-col gap-4\">\n {[1, 2, 3].map(i => (\n <div key={i} className=\"flex flex-col gap-2 animate-pulse\">\n <div className=\"h-4 w-48 bg-surface-200 dark:bg-surface-700 rounded\"/>\n <div className=\"h-12 w-full bg-surface-100 dark:bg-surface-900 rounded-lg border border-surface-200 dark:border-surface-700\"/>\n </div>\n ))}\n </div>\n )}\n\n {!isLoading && entries.length === 0 && <>\n <Label>\n {t(\"history_empty\") ?? \"No history available\"}\n </Label>\n <Typography variant={\"caption\"}>\n {t(\"history_empty_description\") ?? \"When you save a record, a new version is created and stored in the history.\"}\n </Typography>\n </>}\n\n {entries.map((entry) => (\n <div key={entry.id} className=\"flex flex-col gap-2 w-full\">\n <EntityHistoryEntry\n entry={entry}\n collection={collection}\n size={\"medium\"}\n actions={\n <Tooltip title={t(\"history_revert\") ?? \"Revert to this version\"}\n className={\"m-2 grow-0 self-start\"}>\n <IconButton\n onClick={() => {\n if (dirty) {\n snackbarController.open({\n message: t(\"history_revert_dirty\") ?? \"Please save or discard your changes before reverting\",\n type: \"warning\"\n });\n } else {\n setRevertHistoryId(entry.id);\n }\n }}>\n <HistoryIcon/>\n </IconButton>\n </Tooltip>\n }\n />\n </div>\n ))}\n\n {/* Load more sentinel */}\n {entries.length > 0 && (\n <div ref={loadMoreRef} className=\"py-4 text-center\">\n {isLoading && <Label>{t(\"loading\")}</Label>}\n {!hasMore && entries.length > 10 && <Label>{t(\"history_no_more\") ?? \"No more history\"}</Label>}\n </div>\n )}\n </div>\n\n <ErrorBoundary>\n <ConfirmationDialog\n open={Boolean(revertHistoryId)}\n onAccept={() => {\n if (revertHistoryId) doRevert(revertHistoryId);\n }}\n onCancel={() => setRevertHistoryId(undefined)}\n title={<Typography variant={\"subtitle2\"}>{t(\"history_revert_title\") ?? \"Revert data to this version?\"}</Typography>}\n body={revertEntry\n ? <div className=\"p-4\">\n <Typography variant={\"caption\"} color={\"secondary\"}>\n This will save the entity with the values from{\" \"}\n {new Date(revertEntry.updated_at).toLocaleString()}.\n A new history entry will be created for the revert.\n </Typography>\n </div>\n : null\n }\n />\n </ErrorBoundary>\n </div>;\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import type { AppView, RebasePlugin, NavigationGroupMapping, AdminCollection } from "@rebasepro/admin-types";
|
|
2
2
|
export declare const NAVIGATION_DEFAULT_GROUP_NAME = "Views";
|
|
3
3
|
export declare const NAVIGATION_ADMIN_GROUP_NAME = "Admin";
|
|
4
|
+
/**
|
|
5
|
+
* Group names that sink to the bottom of the drawer on their name alone.
|
|
6
|
+
*
|
|
7
|
+
* A magic string, and it stays one for compatibility: apps have been relying on
|
|
8
|
+
* `group: "Settings"` landing last since before there was a flag, and taking it
|
|
9
|
+
* away would silently reshuffle their navigation.
|
|
10
|
+
*
|
|
11
|
+
* It is a bad way to ask for the behaviour, though — it is invisible from the
|
|
12
|
+
* `group?: string` type, and it is defeated by the most ordinary edit there is:
|
|
13
|
+
* translating the label. `group: "Ajustes"` is the same group to a reader and a
|
|
14
|
+
* different one to this comparison, so the ordering just quietly stops
|
|
15
|
+
* happening. Set {@link AppView.pinToBottom} instead, which says so.
|
|
16
|
+
*/
|
|
17
|
+
export declare const NAVIGATION_BOTTOM_GROUP_NAMES: readonly ["Admin", "Settings"];
|
|
18
|
+
/** Whether a group sinks to the bottom by virtue of its name. @see NAVIGATION_BOTTOM_GROUP_NAMES */
|
|
19
|
+
export declare function isBottomPinnedGroupName(groupName?: string): boolean;
|
|
4
20
|
export declare function getGroup(collectionOrView: AdminCollection | AppView): string;
|
|
5
21
|
export declare function computeNavigationGroups({ navigationGroupMappings, collections, views, plugins }: {
|
|
6
22
|
navigationGroupMappings?: NavigationGroupMapping[];
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import type { CustomizationController, SidePanelBindingProps, SideDialogsController, SidePanelController, UrlController, NavigationStateController, AdminCollection } from "@rebasepro/admin-types";
|
|
2
2
|
import { CollectionRegistryController } from "@rebasepro/types";
|
|
3
3
|
import { AuthController } from "@rebasepro/admin-types";
|
|
4
|
+
/**
|
|
5
|
+
* How wide the side panel opens.
|
|
6
|
+
*
|
|
7
|
+
* One width for the whole panel, whatever tab is showing. It used to depend on
|
|
8
|
+
* the selected tab — the form got one width and a subcollection or custom view
|
|
9
|
+
* got `calc(55vw + 768px)` — so the panel physically resized under the cursor
|
|
10
|
+
* every time you switched tabs. And the "form" width was derived from how
|
|
11
|
+
* deeply nested the collection's *properties* were, which is not a thing anyone
|
|
12
|
+
* can predict from looking at the panel.
|
|
13
|
+
*
|
|
14
|
+
* Now: `width` prop, then the collection's `sideDialogWidth`, then a default
|
|
15
|
+
* sized for the content the panel actually has to hold — the form column plus
|
|
16
|
+
* its metadata rail — capped so it never eats the whole window.
|
|
17
|
+
*/
|
|
4
18
|
export declare function getEntityViewWidth(props: SidePanelBindingProps<any>, small: boolean, customizationController: CustomizationController): string;
|
|
5
19
|
export declare const useBuildSidePanel: (collectionRegistryController: CollectionRegistryController, urlController: UrlController, navigationStateController: NavigationStateController, sideDialogsController: SideDialogsController, authController: AuthController) => SidePanelController;
|
|
6
20
|
export declare function buildSidePanelsFromUrl(path: string, collections: AdminCollection[], newFlag: boolean): SidePanelBindingProps<any>[];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AdminCollection } from "@rebasepro/admin-types";
|
|
2
|
+
import type { EntityStatus } from "@rebasepro/types";
|
|
3
|
+
export interface UseEntityDisplayTitleParams<M extends Record<string, unknown>> {
|
|
4
|
+
collection: AdminCollection<M>;
|
|
5
|
+
values?: Record<string, unknown>;
|
|
6
|
+
entityId?: string | number;
|
|
7
|
+
status: EntityStatus;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The human name for a record, for the identity bar and the breadcrumb.
|
|
11
|
+
*
|
|
12
|
+
* The guard on `status` is the point. The title resolver walks candidate
|
|
13
|
+
* properties and returns the first non-empty one, and on a brand new entity the
|
|
14
|
+
* only populated values are the property defaults — so creating a blog post
|
|
15
|
+
* opened a page headed **draft**, the default of its `status` enum. A record
|
|
16
|
+
* that does not exist yet has no name; it has an intent.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useEntityDisplayTitle<M extends Record<string, unknown>>({ collection, values, entityId, status }: UseEntityDisplayTitleParams<M>): string;
|
|
@@ -23,4 +23,11 @@ export declare function useHistory(params: {
|
|
|
23
23
|
entityId: string | number | undefined;
|
|
24
24
|
enabled?: boolean;
|
|
25
25
|
pageSize?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Bumped by the caller when the record is saved. A save is the one event
|
|
28
|
+
* that adds a revision, and nothing else here would notice it: without
|
|
29
|
+
* this the open panel keeps showing the list it fetched when it opened,
|
|
30
|
+
* so the revision you just created never appears.
|
|
31
|
+
*/
|
|
32
|
+
refreshToken?: number;
|
|
26
33
|
}): UseEntityHistoryResult;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { EntityAction, AdminCollection } from "@rebasepro/admin-types";
|
|
2
|
+
import type { Entity } from "@rebasepro/types";
|
|
3
|
+
export interface UseRecordActionsParams<M extends Record<string, unknown>> {
|
|
4
|
+
collection: AdminCollection<M>;
|
|
5
|
+
path: string;
|
|
6
|
+
entity?: Entity<M>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The actions you can perform *on* a record — copy, delete, and whatever the
|
|
10
|
+
* collection adds — resolved against the current user's permissions.
|
|
11
|
+
*
|
|
12
|
+
* Extracted from the form's footer so the identity bar can own them. They are
|
|
13
|
+
* not ways to leave the form, so they never belonged next to Save; putting them
|
|
14
|
+
* in the bar's overflow menu is what let the footer disappear instead of
|
|
15
|
+
* becoming a full-width strip holding two icon buttons.
|
|
16
|
+
*/
|
|
17
|
+
export declare function useRecordActions<M extends Record<string, unknown>>({ collection, path, entity }: UseRecordActionsParams<M>): EntityAction[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { $t as CollectionTableBinding, Ct as convertDataToEntity, Dt as useImportConfig, Et as getInferenceType,
|
|
2
|
+
import { $t as CollectionTableBinding, Ct as convertDataToEntity, Dt as useImportConfig, Et as getInferenceType, Ln as useCollectionRegistryController, Nt as PropertyConfigBadge, Ot as ImportSaveInProgress, St as DataNewPropertiesMapping, Zt as useSelectionController, fr as getPropertiesWithPropertiesOrder, k as getFieldConfig, kt as ImportFileUpload, pr as getPropertyInPath } from "./util-GiwPgxnL.js";
|
|
3
3
|
import React, { useCallback, useEffect } from "react";
|
|
4
4
|
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Select, SelectItem, Tooltip, Typography, UploadIcon, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
|
|
5
5
|
import { useAuthController, useCustomizationController, useSnackbarController } from "@rebasepro/app";
|
|
@@ -325,4 +325,4 @@ var import_exports = /* @__PURE__ */ __exportAll({
|
|
|
325
325
|
//#endregion
|
|
326
326
|
export { PropertySelectEntry as i, ImportCollectionAction as n, ImportDataPreview as r, import_exports as t };
|
|
327
327
|
|
|
328
|
-
//# sourceMappingURL=import-
|
|
328
|
+
//# sourceMappingURL=import-BvYl--U4.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-BjUG-cIY.js","names":[],"sources":["../src/data_import/import/ImportCollectionAction.tsx","../src/data_import/import/index.ts"],"sourcesContent":["import React, { useCallback, useEffect } from \"react\";\nimport {\n useAuthController,\n useCustomizationController,\n useSnackbarController\n} from \"@rebasepro/app\";\nimport { getPropertiesWithPropertiesOrder, getPropertyInPath } from \"../../util\";\nimport { Properties, Property, User } from \"@rebasepro/types\";\nimport { CollectionActionsProps, AdminCollection } from \"@rebasepro/admin-types\";\nimport { getFieldConfig } from \"../../components/field_configs\";\nimport { PropertyConfigBadge } from \"../../components/PropertyConfigBadge\";\nimport { useSelectionController } from \"../../components/CollectionViewBinding/useSelectionController\";\nimport { CollectionTableBinding } from \"../../components/CollectionTableBinding/CollectionTableBinding\";\nimport { useCollectionRegistryController } from \"../../hooks\";\nimport {\n Button,\n cls,\n defaultBorderMixin,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n IconButton,\n iconSize,\n Select,\n SelectItem,\n Tooltip,\n Typography,\n UploadIcon\n} from \"@rebasepro/ui\";\nimport { buildEntityPropertiesFromData } from \"@rebasepro/inference\";\nimport { useImportConfig } from \"../hooks\";\nimport { convertDataToEntity, getInferenceType } from \"../utils\";\nimport { DataNewPropertiesMapping } from \"../components/DataNewPropertiesMapping\";\nimport { ImportFileUpload } from \"../components/ImportFileUpload\";\nimport { ImportSaveInProgress } from \"../components/ImportSaveInProgress\";\nimport { ImportConfig } from \"../types\";\nimport { slugify } from \"@rebasepro/utils\";\n\ntype ImportState = \"initial\" | \"mapping\" | \"preview\" | \"import_data_saving\";\n\nexport function ImportCollectionAction<M extends Record<string, unknown>, USER extends User>({\n collection,\n path,\n onAnalyticsEvent\n}: CollectionActionsProps<M, USER> & {\n onAnalyticsEvent?: (event: string, params?: any) => void;\n}\n) {\n\n const snackbarController = useSnackbarController();\n\n const [open, setOpen] = React.useState(false);\n\n const [step, setStep] = React.useState<ImportState>(\"initial\");\n\n const importConfig = useImportConfig();\n\n const handleClickOpen = useCallback(() => {\n setOpen(true);\n onAnalyticsEvent?.(\"import_open\");\n setStep(\"initial\");\n }, [onAnalyticsEvent]);\n\n const handleClose = useCallback(() => {\n setOpen(false);\n }, [setOpen]);\n\n const onMappingComplete = useCallback(() => {\n onAnalyticsEvent?.(\"import_mapping_complete\");\n setStep(\"preview\");\n }, [onAnalyticsEvent]);\n\n const onPreviewComplete = useCallback(() => {\n onAnalyticsEvent?.(\"import_data_save\");\n setStep(\"import_data_saving\");\n }, [onAnalyticsEvent]);\n\n const onDataAdded = async (data: object[]) => {\n importConfig.setImportData(data);\n\n if (data.length > 0) {\n const originProperties = await buildEntityPropertiesFromData(data, getInferenceType);\n importConfig.setOriginProperties(originProperties as Properties);\n\n const headersMapping = buildHeadersMappingFromData(data, collection?.properties);\n importConfig.setHeadersMapping(headersMapping);\n const firstKey = Object.keys(headersMapping)?.[0];\n if (firstKey?.includes(\"id\") || firstKey?.includes(\"key\")) {\n importConfig.setIdColumn(firstKey);\n }\n }\n setTimeout(() => {\n onAnalyticsEvent?.(\"import_data_added\");\n setStep(\"mapping\");\n }, 100);\n };\n\n const properties = getPropertiesWithPropertiesOrder(collection.properties, collection.propertiesOrder as Extract<keyof M, string>[]);\n\n const propertiesAndLevel = Object.entries(properties)\n .flatMap(([key, property]) => getPropertiesAndLevel(key, property, 0));\n const propertiesOrder = (collection.propertiesOrder ?? Object.keys(collection.properties)) as Extract<keyof M, string>[];\n\n return <>\n\n <Tooltip title={\"Import\"}\n asChild={true}>\n <IconButton\n size={\"small\"}\n color={\"primary\"} onClick={handleClickOpen}>\n <UploadIcon size={iconSize.small}/>\n </IconButton>\n </Tooltip>\n\n <Dialog open={open}\n fullWidth={step !== \"initial\"}\n fullHeight={step !== \"initial\"}\n maxWidth={step === \"initial\" ? \"lg\" : \"7xl\"}>\n\n <DialogTitle variant={\"h6\"} hidden={step === \"preview\"}>Import data</DialogTitle>\n\n <DialogContent className={\"h-full flex flex-col gap-4 my-4\"} fullHeight={step === \"preview\"}>\n\n {step === \"initial\" && <>\n <Typography variant={\"body2\"}>Upload a CSV, Excel or JSON file and map it to your existing\n schema</Typography>\n <ImportFileUpload onDataAdded={onDataAdded}/>\n </>}\n\n {step === \"mapping\" && <>\n <DataNewPropertiesMapping importConfig={importConfig}\n destinationProperties={properties}\n buildPropertyView={({\n isIdColumn,\n property,\n propertyKey,\n importKey\n }) => {\n return <PropertyTreeSelect\n selectedPropertyKey={propertyKey ?? \"\"}\n properties={properties}\n propertiesAndLevel={propertiesAndLevel}\n isIdColumn={isIdColumn}\n onIdSelected={() => {\n importConfig.setIdColumn(importKey);\n }}\n onPropertySelected={(newPropertyKey) => {\n\n onAnalyticsEvent?.(\"import_mapping_field_updated\");\n const newHeadersMapping: Record<string, string | null> = Object.entries(importConfig.headersMapping)\n .map(([currentImportKey, currentPropertyKey]) => {\n if (currentPropertyKey === newPropertyKey) {\n return { [currentImportKey]: null };\n }\n if (currentImportKey === importKey) {\n return { [currentImportKey]: newPropertyKey };\n }\n return { [currentImportKey]: currentPropertyKey };\n })\n .reduce((acc, curr) => ({ ...acc,\n...curr }), {});\n importConfig.setHeadersMapping(newHeadersMapping as Record<string, string>);\n\n if (newPropertyKey === importConfig.idColumn) {\n importConfig.setIdColumn(undefined);\n }\n\n }}\n />;\n }}/>\n </>}\n\n {step === \"preview\" && <ImportDataPreview importConfig={importConfig}\n properties={properties}\n propertiesOrder={propertiesOrder}/>}\n\n {step === \"import_data_saving\" && importConfig &&\n <ImportSaveInProgress importConfig={importConfig}\n collection={collection as AdminCollection}\n path={path}\n onImportSuccess={(importedCollection) => {\n handleClose();\n snackbarController.open({\n type: \"info\",\n message: \"Data imported successfully\"\n });\n }}\n />}\n\n </DialogContent>\n <DialogActions>\n\n {step === \"mapping\" && <Button\n onClick={() => setStep(\"initial\")}\n variant={\"text\"}>\n Back\n </Button>}\n\n {step === \"preview\" && <Button\n onClick={() => setStep(\"mapping\")}\n variant={\"text\"}>\n Back\n </Button>}\n\n <Button onClick={handleClose}\n variant={\"text\"}>\n Cancel\n </Button>\n\n {step === \"mapping\" && <Button variant=\"filled\"\n color={\"primary\"}\n onClick={onMappingComplete}>\n Next\n </Button>}\n\n {step === \"preview\" && <Button variant=\"filled\"\n color={\"primary\"}\n onClick={onPreviewComplete}>\n Save data\n </Button>}\n\n </DialogActions>\n </Dialog>\n\n </>;\n}\n\nconst internalIDValue = \"__internal_id__\";\n\nfunction PropertyTreeSelect({\n selectedPropertyKey,\n properties,\n onPropertySelected,\n onIdSelected,\n propertiesAndLevel,\n isIdColumn\n}: {\n selectedPropertyKey: string | null;\n properties: Record<string, Property>;\n onPropertySelected: (propertyKey: string | null) => void;\n onIdSelected: () => void;\n propertiesAndLevel: PropertyAndLevel[];\n isIdColumn?: boolean;\n}) {\n\n const selectedProperty = selectedPropertyKey ? getPropertyInPath(properties, selectedPropertyKey) : null;\n\n const renderValue = useCallback((selectedPropertyKey: string) => {\n\n if (selectedPropertyKey === internalIDValue) {\n return <Typography variant={\"body2\"} className={\"p-4\"}>Use this column as ID</Typography>;\n }\n\n if (!selectedPropertyKey || !selectedProperty) {\n return <Typography variant={\"body2\"} color=\"disabled\" className={\"p-4\"}>Do not import this\n property</Typography>;\n }\n\n return <PropertySelectEntry propertyKey={selectedPropertyKey}\n property={selectedProperty as Property}/>;\n }, [selectedProperty]);\n\n const onSelectValueChange = (value: string) => {\n if (value === internalIDValue) {\n onIdSelected();\n onPropertySelected(null);\n } else if (value === \"__do_not_import\") {\n onPropertySelected(null);\n } else {\n onPropertySelected(value);\n }\n };\n\n return <Select value={isIdColumn ? internalIDValue : (selectedPropertyKey ?? undefined)}\n fullWidth={true}\n onValueChange={onSelectValueChange}\n renderValue={renderValue}>\n\n <SelectItem value={\"__do_not_import\"}>\n <Typography variant={\"body2\"} color={\"disabled\"} className={\"p-4\"}>Do not import this property</Typography>\n </SelectItem>\n\n <SelectItem value={internalIDValue}>\n <Typography variant={\"body2\"} className={\"p-4\"}>Use this column as ID</Typography>\n </SelectItem>\n\n {propertiesAndLevel.map(({\n property,\n level,\n propertyKey\n }) => {\n return <SelectItem value={propertyKey}\n key={propertyKey}\n disabled={property.type === \"map\"}>\n <PropertySelectEntry propertyKey={propertyKey}\n property={property}\n level={level}/>\n </SelectItem>;\n })}\n\n </Select>;\n}\n\ntype PropertyAndLevel = {\n property: Property,\n level: number,\n propertyKey: string\n};\n\nfunction getPropertiesAndLevel(key: string, property: Property, level: number): PropertyAndLevel[] {\n const properties: PropertyAndLevel[] = [];\n properties.push({\n property,\n level,\n propertyKey: key\n });\n if (property.type === \"map\" && property.properties) {\n Object.entries(property.properties).forEach(([childKey, value]) => {\n properties.push(...getPropertiesAndLevel(`${key}.${childKey}`, value as Property, level + 1));\n });\n }\n return properties;\n}\n\nexport function PropertySelectEntry({\n propertyKey,\n property,\n level = 0\n}: {\n propertyKey: string;\n property: Property;\n level?: number;\n}) {\n\n const { propertyConfigs } = useCustomizationController();\n const widget = getFieldConfig(property, propertyConfigs);\n\n return <div\n className=\"flex flex-row w-full text-start items-center h-full\">\n\n {new Array(level).fill(0).map((_, index) =>\n <div className={cls(defaultBorderMixin, \"ml-8 border-l h-12\")} key={index}/>)}\n\n <div className={\"m-4\"}>\n <Tooltip title={widget?.name}>\n <PropertyConfigBadge propertyConfig={widget}/>\n </Tooltip>\n </div>\n\n <div className={\"flex flex-col grow p-2 pl-2\"}>\n <Typography variant=\"body1\"\n component=\"span\"\n className=\"grow pr-2\">\n {property.name\n ? property.name\n : \"\\u00a0\"\n }\n </Typography>\n\n <Typography className=\" pr-2\"\n variant={\"body2\"}\n component=\"span\"\n color=\"secondary\">\n {propertyKey}\n </Typography>\n </div>\n\n </div>;\n\n}\n\nexport function ImportDataPreview<M extends Record<string, unknown>>({\n importConfig,\n properties,\n propertiesOrder\n}: {\n importConfig: ImportConfig,\n properties: Properties,\n propertiesOrder: Extract<keyof M, string>[],\n}) {\n const authController = useAuthController();\n const collectionRegistry = useCollectionRegistryController();\n useEffect(() => {\n const mappedData = importConfig.importData.map(d => convertDataToEntity(\n authController,\n collectionRegistry,\n d,\n importConfig.idColumn,\n importConfig.headersMapping,\n properties,\n \"TEMP_PATH\",\n importConfig.defaultValues\n ));\n importConfig.setEntities(mappedData);\n }, []);\n\n const selectionController = useSelectionController();\n\n return <CollectionTableBinding\n title={<div>\n <Typography variant={\"subtitle2\"}>Imported data preview</Typography>\n <Typography variant={\"caption\"}>Entities with the same id will be overwritten</Typography>\n </div>}\n tableController={{\n data: importConfig.entities,\n dataLoading: false,\n noMoreToLoad: false\n }}\n enablePopupIcon={false}\n endAdornment={<div className={\"h-12\"}/>}\n filterable={false}\n sortable={false}\n openEntityMode={\"full_screen\"}\n selectionController={selectionController}\n properties={properties}/>\n\n}\n\nfunction buildHeadersMappingFromData(objArr: object[], properties?: Properties) {\n const headersMapping: Record<string, string> = {};\n objArr.filter(Boolean).forEach((obj) => {\n Object.keys(obj).forEach((key) => {\n const child = (obj as Record<string, unknown>)[key];\n if (child != null && typeof child === \"object\" && !Array.isArray(child)) {\n const childProperty = properties?.[key];\n const childProperties = childProperty && \"properties\" in childProperty ? childProperty.properties : undefined;\n const childHeadersMapping = buildHeadersMappingFromData([child as object], childProperties);\n Object.entries(childHeadersMapping).forEach(([subKey, mapping]) => {\n headersMapping[`${key}.${subKey}`] = `${key}.${mapping}`;\n });\n }\n\n if (!properties) {\n headersMapping[key] = key;\n } else if (key in properties) {\n headersMapping[key] = key;\n } else {\n const slug = slugify(key);\n if (slug in properties) {\n headersMapping[key] = slug;\n } else {\n headersMapping[key] = key;\n }\n }\n\n });\n });\n return headersMapping;\n}\n","export * from \"./ImportCollectionAction\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"import-BvYl--U4.js","names":[],"sources":["../src/data_import/import/ImportCollectionAction.tsx","../src/data_import/import/index.ts"],"sourcesContent":["import React, { useCallback, useEffect } from \"react\";\nimport {\n useAuthController,\n useCustomizationController,\n useSnackbarController\n} from \"@rebasepro/app\";\nimport { getPropertiesWithPropertiesOrder, getPropertyInPath } from \"../../util\";\nimport { Properties, Property, User } from \"@rebasepro/types\";\nimport { CollectionActionsProps, AdminCollection } from \"@rebasepro/admin-types\";\nimport { getFieldConfig } from \"../../components/field_configs\";\nimport { PropertyConfigBadge } from \"../../components/PropertyConfigBadge\";\nimport { useSelectionController } from \"../../components/CollectionViewBinding/useSelectionController\";\nimport { CollectionTableBinding } from \"../../components/CollectionTableBinding/CollectionTableBinding\";\nimport { useCollectionRegistryController } from \"../../hooks\";\nimport {\n Button,\n cls,\n defaultBorderMixin,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n IconButton,\n iconSize,\n Select,\n SelectItem,\n Tooltip,\n Typography,\n UploadIcon\n} from \"@rebasepro/ui\";\nimport { buildEntityPropertiesFromData } from \"@rebasepro/inference\";\nimport { useImportConfig } from \"../hooks\";\nimport { convertDataToEntity, getInferenceType } from \"../utils\";\nimport { DataNewPropertiesMapping } from \"../components/DataNewPropertiesMapping\";\nimport { ImportFileUpload } from \"../components/ImportFileUpload\";\nimport { ImportSaveInProgress } from \"../components/ImportSaveInProgress\";\nimport { ImportConfig } from \"../types\";\nimport { slugify } from \"@rebasepro/utils\";\n\ntype ImportState = \"initial\" | \"mapping\" | \"preview\" | \"import_data_saving\";\n\nexport function ImportCollectionAction<M extends Record<string, unknown>, USER extends User>({\n collection,\n path,\n onAnalyticsEvent\n}: CollectionActionsProps<M, USER> & {\n onAnalyticsEvent?: (event: string, params?: any) => void;\n}\n) {\n\n const snackbarController = useSnackbarController();\n\n const [open, setOpen] = React.useState(false);\n\n const [step, setStep] = React.useState<ImportState>(\"initial\");\n\n const importConfig = useImportConfig();\n\n const handleClickOpen = useCallback(() => {\n setOpen(true);\n onAnalyticsEvent?.(\"import_open\");\n setStep(\"initial\");\n }, [onAnalyticsEvent]);\n\n const handleClose = useCallback(() => {\n setOpen(false);\n }, [setOpen]);\n\n const onMappingComplete = useCallback(() => {\n onAnalyticsEvent?.(\"import_mapping_complete\");\n setStep(\"preview\");\n }, [onAnalyticsEvent]);\n\n const onPreviewComplete = useCallback(() => {\n onAnalyticsEvent?.(\"import_data_save\");\n setStep(\"import_data_saving\");\n }, [onAnalyticsEvent]);\n\n const onDataAdded = async (data: object[]) => {\n importConfig.setImportData(data);\n\n if (data.length > 0) {\n const originProperties = await buildEntityPropertiesFromData(data, getInferenceType);\n importConfig.setOriginProperties(originProperties as Properties);\n\n const headersMapping = buildHeadersMappingFromData(data, collection?.properties);\n importConfig.setHeadersMapping(headersMapping);\n const firstKey = Object.keys(headersMapping)?.[0];\n if (firstKey?.includes(\"id\") || firstKey?.includes(\"key\")) {\n importConfig.setIdColumn(firstKey);\n }\n }\n setTimeout(() => {\n onAnalyticsEvent?.(\"import_data_added\");\n setStep(\"mapping\");\n }, 100);\n };\n\n const properties = getPropertiesWithPropertiesOrder(collection.properties, collection.propertiesOrder as Extract<keyof M, string>[]);\n\n const propertiesAndLevel = Object.entries(properties)\n .flatMap(([key, property]) => getPropertiesAndLevel(key, property, 0));\n const propertiesOrder = (collection.propertiesOrder ?? Object.keys(collection.properties)) as Extract<keyof M, string>[];\n\n return <>\n\n <Tooltip title={\"Import\"}\n asChild={true}>\n <IconButton\n size={\"small\"}\n color={\"primary\"} onClick={handleClickOpen}>\n <UploadIcon size={iconSize.small}/>\n </IconButton>\n </Tooltip>\n\n <Dialog open={open}\n fullWidth={step !== \"initial\"}\n fullHeight={step !== \"initial\"}\n maxWidth={step === \"initial\" ? \"lg\" : \"7xl\"}>\n\n <DialogTitle variant={\"h6\"} hidden={step === \"preview\"}>Import data</DialogTitle>\n\n <DialogContent className={\"h-full flex flex-col gap-4 my-4\"} fullHeight={step === \"preview\"}>\n\n {step === \"initial\" && <>\n <Typography variant={\"body2\"}>Upload a CSV, Excel or JSON file and map it to your existing\n schema</Typography>\n <ImportFileUpload onDataAdded={onDataAdded}/>\n </>}\n\n {step === \"mapping\" && <>\n <DataNewPropertiesMapping importConfig={importConfig}\n destinationProperties={properties}\n buildPropertyView={({\n isIdColumn,\n property,\n propertyKey,\n importKey\n }) => {\n return <PropertyTreeSelect\n selectedPropertyKey={propertyKey ?? \"\"}\n properties={properties}\n propertiesAndLevel={propertiesAndLevel}\n isIdColumn={isIdColumn}\n onIdSelected={() => {\n importConfig.setIdColumn(importKey);\n }}\n onPropertySelected={(newPropertyKey) => {\n\n onAnalyticsEvent?.(\"import_mapping_field_updated\");\n const newHeadersMapping: Record<string, string | null> = Object.entries(importConfig.headersMapping)\n .map(([currentImportKey, currentPropertyKey]) => {\n if (currentPropertyKey === newPropertyKey) {\n return { [currentImportKey]: null };\n }\n if (currentImportKey === importKey) {\n return { [currentImportKey]: newPropertyKey };\n }\n return { [currentImportKey]: currentPropertyKey };\n })\n .reduce((acc, curr) => ({ ...acc,\n...curr }), {});\n importConfig.setHeadersMapping(newHeadersMapping as Record<string, string>);\n\n if (newPropertyKey === importConfig.idColumn) {\n importConfig.setIdColumn(undefined);\n }\n\n }}\n />;\n }}/>\n </>}\n\n {step === \"preview\" && <ImportDataPreview importConfig={importConfig}\n properties={properties}\n propertiesOrder={propertiesOrder}/>}\n\n {step === \"import_data_saving\" && importConfig &&\n <ImportSaveInProgress importConfig={importConfig}\n collection={collection as AdminCollection}\n path={path}\n onImportSuccess={(importedCollection) => {\n handleClose();\n snackbarController.open({\n type: \"info\",\n message: \"Data imported successfully\"\n });\n }}\n />}\n\n </DialogContent>\n <DialogActions>\n\n {step === \"mapping\" && <Button\n onClick={() => setStep(\"initial\")}\n variant={\"text\"}>\n Back\n </Button>}\n\n {step === \"preview\" && <Button\n onClick={() => setStep(\"mapping\")}\n variant={\"text\"}>\n Back\n </Button>}\n\n <Button onClick={handleClose}\n variant={\"text\"}>\n Cancel\n </Button>\n\n {step === \"mapping\" && <Button variant=\"filled\"\n color={\"primary\"}\n onClick={onMappingComplete}>\n Next\n </Button>}\n\n {step === \"preview\" && <Button variant=\"filled\"\n color={\"primary\"}\n onClick={onPreviewComplete}>\n Save data\n </Button>}\n\n </DialogActions>\n </Dialog>\n\n </>;\n}\n\nconst internalIDValue = \"__internal_id__\";\n\nfunction PropertyTreeSelect({\n selectedPropertyKey,\n properties,\n onPropertySelected,\n onIdSelected,\n propertiesAndLevel,\n isIdColumn\n}: {\n selectedPropertyKey: string | null;\n properties: Record<string, Property>;\n onPropertySelected: (propertyKey: string | null) => void;\n onIdSelected: () => void;\n propertiesAndLevel: PropertyAndLevel[];\n isIdColumn?: boolean;\n}) {\n\n const selectedProperty = selectedPropertyKey ? getPropertyInPath(properties, selectedPropertyKey) : null;\n\n const renderValue = useCallback((selectedPropertyKey: string) => {\n\n if (selectedPropertyKey === internalIDValue) {\n return <Typography variant={\"body2\"} className={\"p-4\"}>Use this column as ID</Typography>;\n }\n\n if (!selectedPropertyKey || !selectedProperty) {\n return <Typography variant={\"body2\"} color=\"disabled\" className={\"p-4\"}>Do not import this\n property</Typography>;\n }\n\n return <PropertySelectEntry propertyKey={selectedPropertyKey}\n property={selectedProperty as Property}/>;\n }, [selectedProperty]);\n\n const onSelectValueChange = (value: string) => {\n if (value === internalIDValue) {\n onIdSelected();\n onPropertySelected(null);\n } else if (value === \"__do_not_import\") {\n onPropertySelected(null);\n } else {\n onPropertySelected(value);\n }\n };\n\n return <Select value={isIdColumn ? internalIDValue : (selectedPropertyKey ?? undefined)}\n fullWidth={true}\n onValueChange={onSelectValueChange}\n renderValue={renderValue}>\n\n <SelectItem value={\"__do_not_import\"}>\n <Typography variant={\"body2\"} color={\"disabled\"} className={\"p-4\"}>Do not import this property</Typography>\n </SelectItem>\n\n <SelectItem value={internalIDValue}>\n <Typography variant={\"body2\"} className={\"p-4\"}>Use this column as ID</Typography>\n </SelectItem>\n\n {propertiesAndLevel.map(({\n property,\n level,\n propertyKey\n }) => {\n return <SelectItem value={propertyKey}\n key={propertyKey}\n disabled={property.type === \"map\"}>\n <PropertySelectEntry propertyKey={propertyKey}\n property={property}\n level={level}/>\n </SelectItem>;\n })}\n\n </Select>;\n}\n\ntype PropertyAndLevel = {\n property: Property,\n level: number,\n propertyKey: string\n};\n\nfunction getPropertiesAndLevel(key: string, property: Property, level: number): PropertyAndLevel[] {\n const properties: PropertyAndLevel[] = [];\n properties.push({\n property,\n level,\n propertyKey: key\n });\n if (property.type === \"map\" && property.properties) {\n Object.entries(property.properties).forEach(([childKey, value]) => {\n properties.push(...getPropertiesAndLevel(`${key}.${childKey}`, value as Property, level + 1));\n });\n }\n return properties;\n}\n\nexport function PropertySelectEntry({\n propertyKey,\n property,\n level = 0\n}: {\n propertyKey: string;\n property: Property;\n level?: number;\n}) {\n\n const { propertyConfigs } = useCustomizationController();\n const widget = getFieldConfig(property, propertyConfigs);\n\n return <div\n className=\"flex flex-row w-full text-start items-center h-full\">\n\n {new Array(level).fill(0).map((_, index) =>\n <div className={cls(defaultBorderMixin, \"ml-8 border-l h-12\")} key={index}/>)}\n\n <div className={\"m-4\"}>\n <Tooltip title={widget?.name}>\n <PropertyConfigBadge propertyConfig={widget}/>\n </Tooltip>\n </div>\n\n <div className={\"flex flex-col grow p-2 pl-2\"}>\n <Typography variant=\"body1\"\n component=\"span\"\n className=\"grow pr-2\">\n {property.name\n ? property.name\n : \"\\u00a0\"\n }\n </Typography>\n\n <Typography className=\" pr-2\"\n variant={\"body2\"}\n component=\"span\"\n color=\"secondary\">\n {propertyKey}\n </Typography>\n </div>\n\n </div>;\n\n}\n\nexport function ImportDataPreview<M extends Record<string, unknown>>({\n importConfig,\n properties,\n propertiesOrder\n}: {\n importConfig: ImportConfig,\n properties: Properties,\n propertiesOrder: Extract<keyof M, string>[],\n}) {\n const authController = useAuthController();\n const collectionRegistry = useCollectionRegistryController();\n useEffect(() => {\n const mappedData = importConfig.importData.map(d => convertDataToEntity(\n authController,\n collectionRegistry,\n d,\n importConfig.idColumn,\n importConfig.headersMapping,\n properties,\n \"TEMP_PATH\",\n importConfig.defaultValues\n ));\n importConfig.setEntities(mappedData);\n }, []);\n\n const selectionController = useSelectionController();\n\n return <CollectionTableBinding\n title={<div>\n <Typography variant={\"subtitle2\"}>Imported data preview</Typography>\n <Typography variant={\"caption\"}>Entities with the same id will be overwritten</Typography>\n </div>}\n tableController={{\n data: importConfig.entities,\n dataLoading: false,\n noMoreToLoad: false\n }}\n enablePopupIcon={false}\n endAdornment={<div className={\"h-12\"}/>}\n filterable={false}\n sortable={false}\n openEntityMode={\"full_screen\"}\n selectionController={selectionController}\n properties={properties}/>\n\n}\n\nfunction buildHeadersMappingFromData(objArr: object[], properties?: Properties) {\n const headersMapping: Record<string, string> = {};\n objArr.filter(Boolean).forEach((obj) => {\n Object.keys(obj).forEach((key) => {\n const child = (obj as Record<string, unknown>)[key];\n if (child != null && typeof child === \"object\" && !Array.isArray(child)) {\n const childProperty = properties?.[key];\n const childProperties = childProperty && \"properties\" in childProperty ? childProperty.properties : undefined;\n const childHeadersMapping = buildHeadersMappingFromData([child as object], childProperties);\n Object.entries(childHeadersMapping).forEach(([subKey, mapping]) => {\n headersMapping[`${key}.${subKey}`] = `${key}.${mapping}`;\n });\n }\n\n if (!properties) {\n headersMapping[key] = key;\n } else if (key in properties) {\n headersMapping[key] = key;\n } else {\n const slug = slugify(key);\n if (slug in properties) {\n headersMapping[key] = slug;\n } else {\n headersMapping[key] = key;\n }\n }\n\n });\n });\n return headersMapping;\n}\n","export * from \"./ImportCollectionAction\";\n"],"mappings":"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"}
|