@firecms/core 3.0.1 → 3.1.0-canary.9e89e98
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/components/AIIcon.d.ts +16 -0
- package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +7 -1
- package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +1 -1
- package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +14 -0
- package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +6 -0
- package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +5 -4
- package/dist/components/EntityCollectionTable/internal/EntityTableCell.d.ts +6 -0
- package/dist/components/EntityCollectionView/Board.d.ts +2 -0
- package/dist/components/EntityCollectionView/BoardColumn.d.ts +42 -0
- package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +9 -0
- package/dist/components/EntityCollectionView/BoardSortableList.d.ts +14 -0
- package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +26 -0
- package/dist/components/EntityCollectionView/EntityCard.d.ts +19 -0
- package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +20 -0
- package/dist/components/EntityCollectionView/EntityCollectionCardView.d.ts +31 -0
- package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +2 -2
- package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +7 -3
- package/dist/components/EntityCollectionView/FiltersDialog.d.ts +14 -0
- package/dist/components/EntityCollectionView/ViewModeToggle.d.ts +44 -0
- package/dist/components/EntityCollectionView/board_types.d.ts +105 -0
- package/dist/components/EntityCollectionView/useBoardDataController.d.ts +60 -0
- package/dist/components/SelectableTable/SelectableTable.d.ts +5 -1
- package/dist/components/SelectableTable/filters/DateTimeFilterField.d.ts +2 -1
- package/dist/components/VirtualTable/VirtualTableCell.d.ts +6 -0
- package/dist/components/VirtualTable/VirtualTableHeader.d.ts +2 -0
- package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +11 -0
- package/dist/components/VirtualTable/fields/VirtualTableDateField.d.ts +1 -0
- package/dist/components/VirtualTable/types.d.ts +2 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/contexts/index.d.ts +10 -0
- package/dist/core/DrawerNavigationGroup.d.ts +45 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/form/validation.d.ts +3 -2
- package/dist/hooks/useBreadcrumbsController.d.ts +16 -0
- package/dist/hooks/useCollapsedGroups.d.ts +4 -1
- package/dist/index.es.js +5185 -1561
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +5179 -1556
- package/dist/index.umd.js.map +1 -1
- package/dist/preview/PropertyPreviewProps.d.ts +5 -0
- package/dist/preview/components/DatePreview.d.ts +13 -3
- package/dist/preview/components/ImagePreview.d.ts +5 -1
- package/dist/preview/components/StorageThumbnail.d.ts +2 -1
- package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
- package/dist/preview/property_previews/ArrayOfStorageComponentsPreview.d.ts +1 -1
- package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
- package/dist/preview/property_previews/SkeletonPropertyComponent.d.ts +1 -1
- package/dist/types/collections.d.ts +50 -2
- package/dist/types/datasource.d.ts +0 -1
- package/dist/types/plugins.d.ts +46 -1
- package/dist/types/properties.d.ts +259 -4
- package/dist/util/__tests__/conditions.test.d.ts +1 -0
- package/dist/util/__tests__/objects.test.d.ts +1 -0
- package/dist/util/conditions.d.ts +26 -0
- package/dist/util/entities.d.ts +1 -2
- package/dist/util/index.d.ts +2 -1
- package/dist/util/property_utils.d.ts +2 -1
- package/dist/util/resolutions.d.ts +1 -1
- package/package.json +10 -7
- package/src/app/Scaffold.tsx +14 -15
- package/src/components/AIIcon.tsx +39 -0
- package/src/components/ArrayContainer.tsx +1 -4
- package/src/components/ClearFilterSortButton.tsx +19 -16
- package/src/components/ConfirmationDialog.tsx +0 -2
- package/src/components/DeleteEntityDialog.tsx +2 -4
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +74 -41
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +130 -79
- package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +121 -104
- package/src/components/EntityCollectionTable/PropertyTableCell.tsx +132 -103
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +20 -42
- package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +90 -49
- package/src/components/EntityCollectionView/Board.tsx +324 -0
- package/src/components/EntityCollectionView/BoardColumn.tsx +158 -0
- package/src/components/EntityCollectionView/BoardColumnTitle.tsx +45 -0
- package/src/components/EntityCollectionView/BoardSortableList.tsx +172 -0
- package/src/components/EntityCollectionView/EntityBoardCard.tsx +212 -0
- package/src/components/EntityCollectionView/EntityCard.tsx +231 -0
- package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +713 -0
- package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +244 -0
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +490 -203
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +31 -19
- package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +84 -15
- package/src/components/EntityCollectionView/FiltersDialog.tsx +249 -0
- package/src/components/EntityCollectionView/ViewModeToggle.tsx +199 -0
- package/src/components/EntityCollectionView/board_types.ts +113 -0
- package/src/components/EntityCollectionView/useBoardDataController.tsx +490 -0
- package/src/components/ErrorTooltip.tsx +2 -1
- package/src/components/HomePage/DefaultHomePage.tsx +47 -10
- package/src/components/HomePage/HomePageDnD.tsx +56 -41
- package/src/components/HomePage/NavigationCard.tsx +20 -18
- package/src/components/HomePage/NavigationGroup.tsx +17 -16
- package/src/components/HomePage/RenameGroupDialog.tsx +0 -2
- package/src/components/HomePage/SmallNavigationCard.tsx +10 -9
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -10
- package/src/components/ReferenceWidget.tsx +2 -4
- package/src/components/SelectableTable/SelectableTable.tsx +75 -67
- package/src/components/SelectableTable/filters/BooleanFilterField.tsx +7 -6
- package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +39 -40
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +38 -38
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +49 -58
- package/src/components/UnsavedChangesDialog.tsx +0 -2
- package/src/components/UserDisplay.tsx +4 -4
- package/src/components/VirtualTable/VirtualTable.tsx +170 -19
- package/src/components/VirtualTable/VirtualTableCell.tsx +18 -2
- package/src/components/VirtualTable/VirtualTableHeader.tsx +20 -11
- package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +158 -42
- package/src/components/VirtualTable/VirtualTableProps.tsx +14 -1
- package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
- package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +3 -0
- package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +17 -4
- package/src/components/VirtualTable/types.tsx +2 -0
- package/src/components/common/useColumnsIds.tsx +95 -3
- package/src/components/index.tsx +4 -0
- package/src/contexts/BreacrumbsContext.tsx +15 -8
- package/src/contexts/index.ts +10 -0
- package/src/core/DefaultAppBar.tsx +39 -26
- package/src/core/DefaultDrawer.tsx +42 -56
- package/src/core/DrawerNavigationGroup.tsx +118 -0
- package/src/core/DrawerNavigationItem.tsx +4 -3
- package/src/core/EntityEditView.tsx +41 -43
- package/src/core/SideDialogs.tsx +4 -2
- package/src/core/index.tsx +1 -0
- package/src/form/PropertyFieldBinding.tsx +58 -43
- package/src/form/components/StorageItemPreview.tsx +2 -1
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +0 -1
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +17 -16
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +0 -1
- package/src/form/field_bindings/MapFieldBinding.tsx +69 -67
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +21 -17
- package/src/form/field_bindings/TextFieldBinding.tsx +71 -35
- package/src/form/validation.ts +245 -160
- package/src/hooks/useBreadcrumbsController.tsx +18 -0
- package/src/hooks/useBuildNavigationController.tsx +42 -19
- package/src/hooks/useCollapsedGroups.ts +12 -4
- package/src/internal/useBuildDataSource.ts +69 -34
- package/src/internal/useBuildSideDialogsController.tsx +11 -8
- package/src/internal/useBuildSideEntityController.tsx +2 -4
- package/src/internal/useRestoreScroll.tsx +26 -14
- package/src/preview/PropertyPreview.tsx +40 -32
- package/src/preview/PropertyPreviewProps.tsx +6 -0
- package/src/preview/components/DatePreview.tsx +72 -4
- package/src/preview/components/EmptyValue.tsx +1 -1
- package/src/preview/components/ImagePreview.tsx +37 -21
- package/src/preview/components/StorageThumbnail.tsx +16 -12
- package/src/preview/components/UrlComponentPreview.tsx +28 -25
- package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +9 -7
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +11 -9
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +26 -24
- package/src/preview/property_previews/SkeletonPropertyComponent.tsx +61 -56
- package/src/routes/CustomCMSRoute.tsx +1 -0
- package/src/routes/FireCMSRoute.tsx +26 -13
- package/src/types/collections.ts +57 -3
- package/src/types/datasource.ts +54 -56
- package/src/types/plugins.tsx +51 -1
- package/src/types/properties.ts +347 -27
- package/src/util/__tests__/conditions.test.ts +506 -0
- package/src/util/__tests__/objects.test.ts +196 -0
- package/src/util/callbacks.ts +6 -3
- package/src/util/collections.ts +51 -6
- package/src/util/conditions.ts +339 -0
- package/src/util/entities.ts +28 -29
- package/src/util/entity_cache.ts +2 -1
- package/src/util/index.ts +2 -1
- package/src/util/objects.ts +31 -13
- package/src/util/{references.ts → previews.ts} +14 -0
- package/src/util/property_utils.tsx +36 -10
- package/src/util/resolutions.ts +57 -55
- /package/dist/util/{references.d.ts → previews.d.ts} +0 -0
|
@@ -14,13 +14,15 @@ import {
|
|
|
14
14
|
PartialEntityCollection,
|
|
15
15
|
PropertyOrBuilder,
|
|
16
16
|
ResolvedProperty,
|
|
17
|
-
SaveEntityProps
|
|
17
|
+
SaveEntityProps,
|
|
18
|
+
ViewMode
|
|
18
19
|
} from "../../types";
|
|
19
20
|
import {
|
|
20
21
|
EntityCollectionRowActions,
|
|
21
22
|
EntityCollectionTable,
|
|
22
23
|
useDataSourceTableController
|
|
23
24
|
} from "../EntityCollectionTable";
|
|
25
|
+
import { CollectionTableToolbar } from "../EntityCollectionTable/internal/CollectionTableToolbar";
|
|
24
26
|
|
|
25
27
|
import {
|
|
26
28
|
canCreateEntity,
|
|
@@ -45,8 +47,12 @@ import {
|
|
|
45
47
|
useNavigationController,
|
|
46
48
|
useSideEntityController
|
|
47
49
|
} from "../../hooks";
|
|
50
|
+
import { useBreadcrumbsController } from "../../hooks/useBreadcrumbsController";
|
|
48
51
|
import { useUserConfigurationPersistence } from "../../hooks/useUserConfigurationPersistence";
|
|
49
52
|
import { EntityCollectionViewActions } from "./EntityCollectionViewActions";
|
|
53
|
+
import { EntityCollectionCardView } from "./EntityCollectionCardView";
|
|
54
|
+
import { EntityCollectionBoardView } from "./EntityCollectionBoardView";
|
|
55
|
+
import { ViewModeToggle, KanbanPropertyOption } from "./ViewModeToggle";
|
|
50
56
|
import {
|
|
51
57
|
AddIcon,
|
|
52
58
|
Button,
|
|
@@ -142,18 +148,19 @@ export type EntityCollectionViewProps<M extends Record<string, any>> = {
|
|
|
142
148
|
*/
|
|
143
149
|
export const EntityCollectionView = React.memo(
|
|
144
150
|
function EntityCollectionView<M extends Record<string, any>>({
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
fullPath: fullPathProp,
|
|
152
|
+
fullIdPath,
|
|
153
|
+
parentCollectionIds,
|
|
154
|
+
isSubCollection,
|
|
155
|
+
className,
|
|
156
|
+
updateUrl,
|
|
157
|
+
...collectionProp
|
|
158
|
+
}: EntityCollectionViewProps<M>
|
|
153
159
|
) {
|
|
154
160
|
|
|
155
161
|
const context = useFireCMSContext();
|
|
156
162
|
const navigation = useNavigationController();
|
|
163
|
+
const breadcrumbs = useBreadcrumbsController();
|
|
157
164
|
const fullPath = fullPathProp ?? collectionProp.path;
|
|
158
165
|
const dataSource = useDataSource(collectionProp);
|
|
159
166
|
const sideEntityController = useSideEntityController();
|
|
@@ -184,8 +191,19 @@ export const EntityCollectionView = React.memo(
|
|
|
184
191
|
|
|
185
192
|
const [lastDeleteTimestamp, setLastDeleteTimestamp] = React.useState<number>(0);
|
|
186
193
|
|
|
187
|
-
//
|
|
188
|
-
const [
|
|
194
|
+
// Track recently deleted entities for optimistic Kanban count updates
|
|
195
|
+
const [deletedEntities, setDeletedEntities] = React.useState<Entity<M>[]>([]);
|
|
196
|
+
|
|
197
|
+
// number of entities in the collection (undefined = loading)
|
|
198
|
+
const [docsCount, setDocsCount] = useState<number | undefined>(undefined);
|
|
199
|
+
|
|
200
|
+
// Optimistic state for column order to prevent UI flickering during persistence
|
|
201
|
+
const [localPropertiesOrder, setLocalPropertiesOrder] = useState<string[] | undefined>(collection.propertiesOrder);
|
|
202
|
+
|
|
203
|
+
// Sync local state with collection's propertiesOrder when it changes from external sources
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
setLocalPropertiesOrder(collection.propertiesOrder);
|
|
206
|
+
}, [collection.propertiesOrder]);
|
|
189
207
|
|
|
190
208
|
const unselectNavigatedEntity = useCallback(() => {
|
|
191
209
|
const currentSelection = highlightedEntity;
|
|
@@ -208,6 +226,83 @@ export const EntityCollectionView = React.memo(
|
|
|
208
226
|
|
|
209
227
|
const [popOverOpen, setPopOverOpen] = useState(false);
|
|
210
228
|
|
|
229
|
+
// View mode priority: URL > saved user config > collection.defaultViewMode
|
|
230
|
+
const defaultViewMode = collection.defaultViewMode ?? "table";
|
|
231
|
+
|
|
232
|
+
// Parse view from URL
|
|
233
|
+
const getViewFromUrl = useCallback((): ViewMode | null => {
|
|
234
|
+
const params = new URLSearchParams(window.location.search);
|
|
235
|
+
const urlView = params.get("__view");
|
|
236
|
+
if (urlView && ["table", "kanban", "cards"].includes(urlView)) {
|
|
237
|
+
return urlView as ViewMode;
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}, []);
|
|
241
|
+
|
|
242
|
+
// Get saved view from local persistence
|
|
243
|
+
const getSavedView = useCallback((): ViewMode | null => {
|
|
244
|
+
const saved = userConfigPersistence?.getCollectionConfig<M>(fullPath)?.defaultViewMode;
|
|
245
|
+
return (saved as ViewMode) ?? null;
|
|
246
|
+
}, [userConfigPersistence, fullPath]);
|
|
247
|
+
|
|
248
|
+
const [viewMode, setViewModeState] = useState<ViewMode>(() => {
|
|
249
|
+
// Priority: URL > saved config > collection default
|
|
250
|
+
const urlView = getViewFromUrl();
|
|
251
|
+
if (urlView) return urlView;
|
|
252
|
+
const savedView = getSavedView();
|
|
253
|
+
if (savedView) return savedView;
|
|
254
|
+
return defaultViewMode;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Sync URL with current view on init (if view came from saved config)
|
|
258
|
+
useEffect(() => {
|
|
259
|
+
const urlView = getViewFromUrl();
|
|
260
|
+
if (!urlView && viewMode !== "table") {
|
|
261
|
+
// View came from saved config but URL doesn't have it - update URL without push
|
|
262
|
+
const url = new URL(window.location.href);
|
|
263
|
+
url.searchParams.set("__view", viewMode);
|
|
264
|
+
window.history.replaceState({}, "", url.toString());
|
|
265
|
+
}
|
|
266
|
+
}, []); // Only on mount
|
|
267
|
+
|
|
268
|
+
// Update URL when view mode changes (user action)
|
|
269
|
+
const setViewMode = useCallback((newMode: ViewMode) => {
|
|
270
|
+
setViewModeState(newMode);
|
|
271
|
+
|
|
272
|
+
// Update URL with __view param
|
|
273
|
+
const url = new URL(window.location.href);
|
|
274
|
+
if (newMode === "table") {
|
|
275
|
+
url.searchParams.delete("__view");
|
|
276
|
+
} else {
|
|
277
|
+
url.searchParams.set("__view", newMode);
|
|
278
|
+
}
|
|
279
|
+
window.history.pushState({}, "", url.toString());
|
|
280
|
+
}, []);
|
|
281
|
+
|
|
282
|
+
// Listen for browser back/forward
|
|
283
|
+
useEffect(() => {
|
|
284
|
+
const handlePopState = () => {
|
|
285
|
+
const urlView = getViewFromUrl();
|
|
286
|
+
if (urlView) {
|
|
287
|
+
// URL has explicit view - use it
|
|
288
|
+
setViewModeState(urlView);
|
|
289
|
+
} else {
|
|
290
|
+
// No URL param - fallback to saved config or collection default
|
|
291
|
+
const savedView = getSavedView();
|
|
292
|
+
setViewModeState(savedView ?? defaultViewMode);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
window.addEventListener("popstate", handlePopState);
|
|
297
|
+
return () => window.removeEventListener("popstate", handlePopState);
|
|
298
|
+
}, [getViewFromUrl, getSavedView, defaultViewMode]);
|
|
299
|
+
|
|
300
|
+
// Card view size state - controls the grid column count
|
|
301
|
+
const [cardSize, setCardSize] = useState<CollectionSize>(collection.defaultSize ?? "m");
|
|
302
|
+
|
|
303
|
+
// Table view size state - controls row height
|
|
304
|
+
const [tableSize, setTableSize] = useState<CollectionSize>(collection.defaultSize ?? "m");
|
|
305
|
+
|
|
211
306
|
const selectionController = useSelectionController<M>();
|
|
212
307
|
const usedSelectionController = collection.selectionController ?? selectionController;
|
|
213
308
|
const {
|
|
@@ -284,6 +379,7 @@ export const EntityCollectionView = React.memo(
|
|
|
284
379
|
path: fullPath
|
|
285
380
|
});
|
|
286
381
|
setSelectedEntities((selectedEntities) => selectedEntities.filter((e) => e.id !== entity.id));
|
|
382
|
+
setDeletedEntities(prev => [...prev, entity]);
|
|
287
383
|
setLastDeleteTimestamp(Date.now());
|
|
288
384
|
};
|
|
289
385
|
|
|
@@ -293,6 +389,7 @@ export const EntityCollectionView = React.memo(
|
|
|
293
389
|
});
|
|
294
390
|
setSelectedEntities([]);
|
|
295
391
|
setDeleteEntityClicked(undefined);
|
|
392
|
+
setDeletedEntities(prev => [...prev, ...entities]);
|
|
296
393
|
setLastDeleteTimestamp(Date.now());
|
|
297
394
|
};
|
|
298
395
|
|
|
@@ -317,9 +414,9 @@ export const EntityCollectionView = React.memo(
|
|
|
317
414
|
}, [userConfigPersistence]);
|
|
318
415
|
|
|
319
416
|
const onColumnResize = useCallback(({
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
417
|
+
width,
|
|
418
|
+
key
|
|
419
|
+
}: OnColumnResizeParams) => {
|
|
323
420
|
|
|
324
421
|
const collection = collectionRef.current;
|
|
325
422
|
// Only for property columns
|
|
@@ -328,29 +425,39 @@ export const EntityCollectionView = React.memo(
|
|
|
328
425
|
onCollectionModifiedForUser(fullPath, localCollection);
|
|
329
426
|
}, [onCollectionModifiedForUser, fullPath]);
|
|
330
427
|
|
|
331
|
-
const
|
|
428
|
+
const onTableSizeChanged = useCallback((size: CollectionSize) => {
|
|
429
|
+
setTableSize(size);
|
|
332
430
|
if (userConfigPersistence)
|
|
333
431
|
onCollectionModifiedForUser(fullPath, { defaultSize: size })
|
|
334
432
|
}, [onCollectionModifiedForUser, fullPath, userConfigPersistence]);
|
|
335
433
|
|
|
434
|
+
// View mode change: update URL + save to local persistence
|
|
435
|
+
const onViewModeChange = useCallback((mode: ViewMode) => {
|
|
436
|
+
setViewMode(mode);
|
|
437
|
+
// Save to local persistence for next visit
|
|
438
|
+
if (userConfigPersistence) {
|
|
439
|
+
onCollectionModifiedForUser(fullPath, { defaultViewMode: mode } as PartialEntityCollection<M>);
|
|
440
|
+
}
|
|
441
|
+
}, [setViewMode, userConfigPersistence, onCollectionModifiedForUser, fullPath]);
|
|
442
|
+
|
|
336
443
|
const createEnabled = canCreateEntity(collection, authController, fullPath, null);
|
|
337
444
|
|
|
338
445
|
const uniqueFieldValidator: UniqueFieldValidator = useCallback(
|
|
339
446
|
({
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
447
|
+
name,
|
|
448
|
+
value,
|
|
449
|
+
property,
|
|
450
|
+
entityId
|
|
451
|
+
}) => dataSource.checkUniqueField(fullPath, name, value, entityId, collection),
|
|
345
452
|
[fullPath]);
|
|
346
453
|
|
|
347
454
|
const onValueChange: OnCellValueChange<any, any> = ({
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
455
|
+
value,
|
|
456
|
+
propertyKey,
|
|
457
|
+
onValueUpdated,
|
|
458
|
+
setError,
|
|
459
|
+
data: entity,
|
|
460
|
+
}) => {
|
|
354
461
|
|
|
355
462
|
const updatedValues = setIn({ ...entity.values }, propertyKey, value);
|
|
356
463
|
|
|
@@ -394,10 +501,84 @@ export const EntityCollectionView = React.memo(
|
|
|
394
501
|
authController,
|
|
395
502
|
}), [collection, fullPath]);
|
|
396
503
|
|
|
504
|
+
// Check if Kanban view is possible (collection has at least one string enum property)
|
|
505
|
+
const hasEnumProperty = useMemo(() => {
|
|
506
|
+
const properties = resolvedCollection.properties;
|
|
507
|
+
return Object.values(properties).some((prop: any) =>
|
|
508
|
+
prop && prop.dataType === "string" && prop.enumValues
|
|
509
|
+
);
|
|
510
|
+
}, [resolvedCollection.properties]);
|
|
511
|
+
|
|
512
|
+
// Compute the effective enabled views:
|
|
513
|
+
// - Start from collection.enabledViews (defaults to all three)
|
|
514
|
+
// - Filter out kanban if no enum properties exist
|
|
515
|
+
const enabledViews: ViewMode[] = useMemo(() => {
|
|
516
|
+
const configured = collection.enabledViews ?? ["table", "cards", "kanban"];
|
|
517
|
+
if (!hasEnumProperty) {
|
|
518
|
+
return configured.filter(v => v !== "kanban");
|
|
519
|
+
}
|
|
520
|
+
return configured;
|
|
521
|
+
}, [collection.enabledViews, hasEnumProperty]);
|
|
522
|
+
|
|
523
|
+
// Compute available enum properties for kanban column selection
|
|
524
|
+
const kanbanPropertyOptions: KanbanPropertyOption[] = useMemo(() => {
|
|
525
|
+
const options: KanbanPropertyOption[] = [];
|
|
526
|
+
const properties = resolvedCollection.properties;
|
|
527
|
+
|
|
528
|
+
for (const [key, property] of Object.entries(properties)) {
|
|
529
|
+
const prop = property as any;
|
|
530
|
+
if (prop && prop.dataType === "string" && prop.enumValues) {
|
|
531
|
+
options.push({
|
|
532
|
+
key,
|
|
533
|
+
label: prop.name || key
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return options;
|
|
539
|
+
}, [resolvedCollection.properties]);
|
|
540
|
+
|
|
541
|
+
// Get saved kanban property from user config
|
|
542
|
+
const getSavedKanbanProperty = useCallback((): string | undefined => {
|
|
543
|
+
const saved = userConfigPersistence?.getCollectionConfig<M>(fullPath);
|
|
544
|
+
return (saved as any)?.kanbanColumnProperty;
|
|
545
|
+
}, [userConfigPersistence, fullPath]);
|
|
546
|
+
|
|
547
|
+
// Selected kanban property state - priority: saved config > collection default > first available
|
|
548
|
+
const [selectedKanbanProperty, setSelectedKanbanProperty] = useState<string>(() => {
|
|
549
|
+
const saved = getSavedKanbanProperty();
|
|
550
|
+
if (saved && kanbanPropertyOptions.some(o => o.key === saved)) return saved;
|
|
551
|
+
if (collection.kanban?.columnProperty) return collection.kanban.columnProperty;
|
|
552
|
+
return kanbanPropertyOptions[0]?.key ?? "";
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
// Update selected property if options change and current selection is no longer valid
|
|
556
|
+
useEffect(() => {
|
|
557
|
+
if (kanbanPropertyOptions.length > 0 && !kanbanPropertyOptions.some(o => o.key === selectedKanbanProperty)) {
|
|
558
|
+
const saved = getSavedKanbanProperty();
|
|
559
|
+
if (saved && kanbanPropertyOptions.some(o => o.key === saved)) {
|
|
560
|
+
setSelectedKanbanProperty(saved);
|
|
561
|
+
} else if (collection.kanban?.columnProperty && kanbanPropertyOptions.some(o => o.key === collection.kanban?.columnProperty)) {
|
|
562
|
+
setSelectedKanbanProperty(collection.kanban.columnProperty);
|
|
563
|
+
} else {
|
|
564
|
+
setSelectedKanbanProperty(kanbanPropertyOptions[0]?.key ?? "");
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}, [kanbanPropertyOptions, selectedKanbanProperty, getSavedKanbanProperty, collection.kanban?.columnProperty]);
|
|
568
|
+
|
|
569
|
+
// Handle kanban property change
|
|
570
|
+
const onKanbanPropertyChange = useCallback((property: string) => {
|
|
571
|
+
setSelectedKanbanProperty(property);
|
|
572
|
+
// Save to local persistence
|
|
573
|
+
if (userConfigPersistence) {
|
|
574
|
+
onCollectionModifiedForUser(fullPath, { kanbanColumnProperty: property } as any);
|
|
575
|
+
}
|
|
576
|
+
}, [userConfigPersistence, onCollectionModifiedForUser, fullPath]);
|
|
577
|
+
|
|
397
578
|
const getPropertyFor = useCallback(({
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
579
|
+
propertyKey,
|
|
580
|
+
entity
|
|
581
|
+
}: GetPropertyForProps<M>) => {
|
|
401
582
|
let propertyOrBuilder: PropertyOrBuilder<any, M> | undefined = getPropertyInPath<M>(collection.properties, propertyKey);
|
|
402
583
|
|
|
403
584
|
// we might not find the property in the collection if combining property builders and map spread
|
|
@@ -417,7 +598,18 @@ export const EntityCollectionView = React.memo(
|
|
|
417
598
|
});
|
|
418
599
|
}, [collection.properties, customizationController.propertyConfigs, resolvedCollection.properties]);
|
|
419
600
|
|
|
420
|
-
|
|
601
|
+
// Use a collection with local propertiesOrder for optimistic UI updates
|
|
602
|
+
const collectionWithLocalOrder = useMemo(() => {
|
|
603
|
+
if (localPropertiesOrder && localPropertiesOrder !== resolvedCollection.propertiesOrder) {
|
|
604
|
+
return {
|
|
605
|
+
...resolvedCollection,
|
|
606
|
+
propertiesOrder: localPropertiesOrder
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
return resolvedCollection;
|
|
610
|
+
}, [resolvedCollection, localPropertiesOrder]);
|
|
611
|
+
|
|
612
|
+
const displayedColumnIds = useColumnIds(collectionWithLocalOrder, true);
|
|
421
613
|
|
|
422
614
|
const additionalFields = useMemo(() => {
|
|
423
615
|
const subcollectionColumns: AdditionalFieldDelegate<M, any>[] = collection.subcollections?.map((subcollection) => {
|
|
@@ -427,23 +619,22 @@ export const EntityCollectionView = React.memo(
|
|
|
427
619
|
width: 200,
|
|
428
620
|
dependencies: [],
|
|
429
621
|
Builder: ({ entity }) => (
|
|
430
|
-
<Button
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
}}>
|
|
622
|
+
<Button
|
|
623
|
+
className={"max-w-full truncate justify-start"}
|
|
624
|
+
startIcon={<KeyboardTabIcon size={"small"} />}
|
|
625
|
+
onClick={(event: any) => {
|
|
626
|
+
event.stopPropagation();
|
|
627
|
+
navigateToEntity({
|
|
628
|
+
openEntityMode,
|
|
629
|
+
collection,
|
|
630
|
+
entityId: entity.id,
|
|
631
|
+
selectedTab: subcollection.id ?? subcollection.path,
|
|
632
|
+
path: fullPath,
|
|
633
|
+
fullIdPath,
|
|
634
|
+
navigation,
|
|
635
|
+
sideEntityController
|
|
636
|
+
})
|
|
637
|
+
}}>
|
|
447
638
|
{subcollection.name}
|
|
448
639
|
</Button>
|
|
449
640
|
)
|
|
@@ -465,7 +656,7 @@ export const EntityCollectionView = React.memo(
|
|
|
465
656
|
<ReferencePreview
|
|
466
657
|
key={reference.path + "/" + reference.id}
|
|
467
658
|
reference={reference}
|
|
468
|
-
size={"small"}/>
|
|
659
|
+
size={"small"} />
|
|
469
660
|
);
|
|
470
661
|
})}
|
|
471
662
|
</div>
|
|
@@ -488,9 +679,9 @@ export const EntityCollectionView = React.memo(
|
|
|
488
679
|
const largeLayout = useLargeLayout();
|
|
489
680
|
|
|
490
681
|
const getActionsForEntity = ({
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
682
|
+
entity,
|
|
683
|
+
customEntityActions
|
|
684
|
+
}: {
|
|
494
685
|
entity?: Entity<M>,
|
|
495
686
|
customEntityActions?: EntityAction[]
|
|
496
687
|
}): EntityAction[] => {
|
|
@@ -514,11 +705,11 @@ export const EntityCollectionView = React.memo(
|
|
|
514
705
|
};
|
|
515
706
|
|
|
516
707
|
const tableRowActionsBuilder = useCallback(({
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
708
|
+
entity,
|
|
709
|
+
size,
|
|
710
|
+
width,
|
|
711
|
+
frozen
|
|
712
|
+
}: {
|
|
522
713
|
entity: Entity<any>,
|
|
523
714
|
size: CollectionSize,
|
|
524
715
|
width: number,
|
|
@@ -558,45 +749,28 @@ export const EntityCollectionView = React.memo(
|
|
|
558
749
|
|
|
559
750
|
}, [updateLastDeleteTimestamp, usedSelectionController]);
|
|
560
751
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
variant={"subtitle1"}
|
|
568
|
-
className={`leading-none truncate max-w-[160px] lg:max-w-[240px] ${collection.description ? "cursor-pointer" : "cursor-auto"}`}
|
|
569
|
-
onClick={collection.description
|
|
570
|
-
? (e) => {
|
|
571
|
-
setPopOverOpen(true);
|
|
572
|
-
e.stopPropagation();
|
|
573
|
-
}
|
|
574
|
-
: undefined}>
|
|
575
|
-
{`${collection.name}`}
|
|
576
|
-
</Typography>
|
|
577
|
-
|
|
578
|
-
<EntitiesCount
|
|
579
|
-
fullPath={fullPath}
|
|
580
|
-
collection={collection}
|
|
581
|
-
filter={tableController.filterValues}
|
|
582
|
-
sortBy={tableController.sortBy}
|
|
583
|
-
onCountChange={setDocsCount}
|
|
584
|
-
/>
|
|
585
|
-
|
|
586
|
-
</div>}
|
|
587
|
-
>
|
|
752
|
+
// Update breadcrumb count when count changes (only if loaded)
|
|
753
|
+
useEffect(() => {
|
|
754
|
+
if (docsCount !== undefined) {
|
|
755
|
+
breadcrumbs.updateCount(fullPath, docsCount);
|
|
756
|
+
}
|
|
757
|
+
}, [docsCount, fullPath, breadcrumbs.updateCount]);
|
|
588
758
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
759
|
+
// EntitiesCount fetches count and updates breadcrumb - no visual rendering needed here
|
|
760
|
+
const countFetcher = <EntitiesCount
|
|
761
|
+
fullPath={fullPath}
|
|
762
|
+
collection={collection}
|
|
763
|
+
filter={tableController.filterValues}
|
|
764
|
+
sortBy={tableController.sortBy}
|
|
765
|
+
onCountChange={setDocsCount}
|
|
766
|
+
/>;
|
|
592
767
|
|
|
593
|
-
</Popover>;
|
|
594
768
|
|
|
595
769
|
const buildAdditionalHeaderWidget = useCallback(({
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
770
|
+
property,
|
|
771
|
+
propertyKey,
|
|
772
|
+
onHover
|
|
773
|
+
}: {
|
|
600
774
|
property: ResolvedProperty,
|
|
601
775
|
propertyKey: string,
|
|
602
776
|
onHover: boolean
|
|
@@ -616,7 +790,7 @@ export const EntityCollectionView = React.memo(
|
|
|
616
790
|
fullPath={fullPath}
|
|
617
791
|
collection={collection}
|
|
618
792
|
tableController={tableController}
|
|
619
|
-
parentCollectionIds={parentCollectionIds ?? []}/>;
|
|
793
|
+
parentCollectionIds={parentCollectionIds ?? []} />;
|
|
620
794
|
})}
|
|
621
795
|
</>;
|
|
622
796
|
}, [customizationController.plugins, fullPath, parentCollectionIds]);
|
|
@@ -625,9 +799,9 @@ export const EntityCollectionView = React.memo(
|
|
|
625
799
|
? function () {
|
|
626
800
|
if (typeof AddColumnComponent === "function")
|
|
627
801
|
return <AddColumnComponent fullPath={fullPath}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
802
|
+
parentCollectionIds={parentCollectionIds ?? []}
|
|
803
|
+
collection={collection}
|
|
804
|
+
tableController={tableController} />;
|
|
631
805
|
return null;
|
|
632
806
|
}
|
|
633
807
|
: undefined;
|
|
@@ -643,32 +817,37 @@ export const EntityCollectionView = React.memo(
|
|
|
643
817
|
parentCollectionIds
|
|
644
818
|
});
|
|
645
819
|
|
|
820
|
+
// Popover open state managed at parent level to prevent closing when view changes
|
|
821
|
+
const [viewModePopoverOpen, setViewModePopoverOpen] = useState(false);
|
|
822
|
+
|
|
823
|
+
// Create ViewModeToggle once to prevent remounting when view changes
|
|
824
|
+
const viewModeToggleElement = (
|
|
825
|
+
<ViewModeToggle
|
|
826
|
+
viewMode={viewMode}
|
|
827
|
+
onViewModeChange={onViewModeChange}
|
|
828
|
+
enabledViews={enabledViews}
|
|
829
|
+
size={viewMode === "table" ? tableSize : viewMode === "cards" ? cardSize : undefined}
|
|
830
|
+
onSizeChanged={viewMode === "table" ? onTableSizeChanged : viewMode === "cards" ? setCardSize : undefined}
|
|
831
|
+
open={viewModePopoverOpen}
|
|
832
|
+
onOpenChange={setViewModePopoverOpen}
|
|
833
|
+
kanbanPropertyOptions={kanbanPropertyOptions}
|
|
834
|
+
selectedKanbanProperty={selectedKanbanProperty}
|
|
835
|
+
onKanbanPropertyChange={onKanbanPropertyChange}
|
|
836
|
+
/>
|
|
837
|
+
);
|
|
838
|
+
|
|
646
839
|
return (
|
|
647
|
-
<div className={cls("overflow-hidden h-full w-full rounded-md", className)}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
onEntityClick={onEntityClick}
|
|
657
|
-
onColumnResize={onColumnResize}
|
|
658
|
-
onValueChange={onValueChange}
|
|
659
|
-
tableRowActionsBuilder={tableRowActionsBuilder}
|
|
660
|
-
uniqueFieldValidator={uniqueFieldValidator}
|
|
661
|
-
title={title}
|
|
662
|
-
selectionController={usedSelectionController}
|
|
663
|
-
highlightedEntities={highlightedEntity ? [highlightedEntity] : []}
|
|
664
|
-
defaultSize={collection.defaultSize}
|
|
665
|
-
properties={resolvedCollection.properties}
|
|
666
|
-
getPropertyFor={getPropertyFor}
|
|
667
|
-
onTextSearchClick={textSearchInitialised ? undefined : onTextSearchClick}
|
|
668
|
-
onScroll={tableController.onScroll}
|
|
669
|
-
initialScroll={tableController.initialScroll}
|
|
840
|
+
<div className={cls("overflow-hidden h-full w-full rounded-md flex flex-col", className)}
|
|
841
|
+
ref={containerRef}>
|
|
842
|
+
|
|
843
|
+
{/* Unified toolbar - rendered once, outside view conditionals */}
|
|
844
|
+
{countFetcher}
|
|
845
|
+
<CollectionTableToolbar
|
|
846
|
+
loading={tableController.dataLoading}
|
|
847
|
+
onTextSearch={textSearchEnabled && textSearchInitialised ? tableController.setSearchString : undefined}
|
|
848
|
+
onTextSearchClick={textSearchEnabled && !textSearchInitialised ? onTextSearchClick : undefined}
|
|
670
849
|
textSearchLoading={textSearchLoading}
|
|
671
|
-
|
|
850
|
+
viewModeToggle={viewModeToggleElement}
|
|
672
851
|
actionsStart={<EntityCollectionViewStartActions
|
|
673
852
|
parentCollectionIds={parentCollectionIds ?? []}
|
|
674
853
|
collection={collection}
|
|
@@ -676,7 +855,8 @@ export const EntityCollectionView = React.memo(
|
|
|
676
855
|
path={fullPath}
|
|
677
856
|
relativePath={collection.path}
|
|
678
857
|
selectionController={usedSelectionController}
|
|
679
|
-
collectionEntitiesCount={docsCount}
|
|
858
|
+
collectionEntitiesCount={docsCount}
|
|
859
|
+
resolvedProperties={resolvedCollection.properties} />}
|
|
680
860
|
actions={<EntityCollectionViewActions
|
|
681
861
|
parentCollectionIds={parentCollectionIds ?? []}
|
|
682
862
|
collection={collection}
|
|
@@ -689,33 +869,143 @@ export const EntityCollectionView = React.memo(
|
|
|
689
869
|
selectionEnabled={selectionEnabled}
|
|
690
870
|
collectionEntitiesCount={docsCount}
|
|
691
871
|
/>}
|
|
692
|
-
emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
|
|
693
|
-
? <div className="flex flex-col items-center justify-center">
|
|
694
|
-
<Typography variant={"subtitle2"}>So empty...</Typography>
|
|
695
|
-
<Button
|
|
696
|
-
color={"primary"}
|
|
697
|
-
variant={"outlined"}
|
|
698
|
-
onClick={onNewClick}
|
|
699
|
-
className="mt-4"
|
|
700
|
-
>
|
|
701
|
-
<AddIcon/>
|
|
702
|
-
Create your first entry
|
|
703
|
-
</Button>
|
|
704
|
-
</div>
|
|
705
|
-
: <Typography variant={"label"}>No results with the applied filter/sort</Typography>
|
|
706
|
-
}
|
|
707
|
-
hoverRow={hoverRow}
|
|
708
|
-
inlineEditing={checkInlineEditing()}
|
|
709
|
-
AdditionalHeaderWidget={buildAdditionalHeaderWidget}
|
|
710
|
-
AddColumnComponent={addColumnComponentInternal}
|
|
711
|
-
getIdColumnWidth={getIdColumnWidth}
|
|
712
|
-
additionalIDHeaderWidget={<EntityIdHeaderWidget
|
|
713
|
-
path={fullPath}
|
|
714
|
-
fullIdPath={fullIdPath ?? fullPath}
|
|
715
|
-
collection={collection}/>}
|
|
716
|
-
openEntityMode={openEntityMode}
|
|
717
872
|
/>
|
|
718
873
|
|
|
874
|
+
{/* View content - only the view-specific content changes */}
|
|
875
|
+
{viewMode === "kanban" && enabledViews.includes("kanban") ? (
|
|
876
|
+
<EntityCollectionBoardView
|
|
877
|
+
key={`kanban-view-${fullPath}-${selectedKanbanProperty}`}
|
|
878
|
+
collection={collection}
|
|
879
|
+
tableController={tableController}
|
|
880
|
+
fullPath={fullPath}
|
|
881
|
+
parentCollectionIds={parentCollectionIds}
|
|
882
|
+
columnProperty={selectedKanbanProperty}
|
|
883
|
+
onEntityClick={onEntityClick}
|
|
884
|
+
selectionController={usedSelectionController}
|
|
885
|
+
selectionEnabled={selectionEnabled}
|
|
886
|
+
highlightedEntities={highlightedEntity ? [highlightedEntity] : []}
|
|
887
|
+
deletedEntities={deletedEntities}
|
|
888
|
+
emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
|
|
889
|
+
? <div className="flex flex-col items-center justify-center">
|
|
890
|
+
<Typography variant={"subtitle2"}>So empty...</Typography>
|
|
891
|
+
<Button
|
|
892
|
+
onClick={onNewClick}
|
|
893
|
+
className="mt-4"
|
|
894
|
+
>
|
|
895
|
+
<AddIcon />
|
|
896
|
+
Create your first entry
|
|
897
|
+
</Button>
|
|
898
|
+
</div>
|
|
899
|
+
: <Typography variant={"label"}>No results with the applied filter/sort</Typography>
|
|
900
|
+
}
|
|
901
|
+
/>
|
|
902
|
+
) : viewMode === "cards" ? (
|
|
903
|
+
<EntityCollectionCardView
|
|
904
|
+
key={`cards-view-${fullPath}`}
|
|
905
|
+
collection={collection}
|
|
906
|
+
tableController={tableController}
|
|
907
|
+
onEntityClick={onEntityClick}
|
|
908
|
+
selectionController={usedSelectionController}
|
|
909
|
+
selectionEnabled={selectionEnabled}
|
|
910
|
+
highlightedEntities={highlightedEntity ? [highlightedEntity] : []}
|
|
911
|
+
onScroll={tableController.onScroll}
|
|
912
|
+
initialScroll={tableController.initialScroll}
|
|
913
|
+
size={cardSize}
|
|
914
|
+
emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
|
|
915
|
+
? <div className="flex flex-col items-center justify-center">
|
|
916
|
+
<Typography variant={"subtitle2"}>So empty...</Typography>
|
|
917
|
+
<Button
|
|
918
|
+
onClick={onNewClick}
|
|
919
|
+
className="mt-4"
|
|
920
|
+
>
|
|
921
|
+
<AddIcon />
|
|
922
|
+
Create your first entry
|
|
923
|
+
</Button>
|
|
924
|
+
</div>
|
|
925
|
+
: <Typography variant={"label"}>No results with the applied filter/sort</Typography>
|
|
926
|
+
}
|
|
927
|
+
/>
|
|
928
|
+
) : (
|
|
929
|
+
<EntityCollectionTable
|
|
930
|
+
key={`collection_table_${fullPath}`}
|
|
931
|
+
hideToolbar={true}
|
|
932
|
+
additionalFields={additionalFields}
|
|
933
|
+
tableController={tableController}
|
|
934
|
+
enablePopupIcon={true}
|
|
935
|
+
displayedColumnIds={displayedColumnIds}
|
|
936
|
+
onSizeChanged={onTableSizeChanged}
|
|
937
|
+
onEntityClick={onEntityClick}
|
|
938
|
+
onColumnResize={onColumnResize}
|
|
939
|
+
onValueChange={onValueChange}
|
|
940
|
+
tableRowActionsBuilder={tableRowActionsBuilder}
|
|
941
|
+
uniqueFieldValidator={uniqueFieldValidator}
|
|
942
|
+
selectionController={usedSelectionController}
|
|
943
|
+
highlightedEntities={highlightedEntity ? [highlightedEntity] : []}
|
|
944
|
+
defaultSize={tableSize}
|
|
945
|
+
properties={resolvedCollection.properties}
|
|
946
|
+
getPropertyFor={getPropertyFor}
|
|
947
|
+
onTextSearchClick={textSearchInitialised ? undefined : onTextSearchClick}
|
|
948
|
+
onScroll={tableController.onScroll}
|
|
949
|
+
initialScroll={tableController.initialScroll}
|
|
950
|
+
textSearchLoading={textSearchLoading}
|
|
951
|
+
textSearchEnabled={textSearchEnabled}
|
|
952
|
+
emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
|
|
953
|
+
? <div className="flex flex-col items-center justify-center">
|
|
954
|
+
<Typography variant={"subtitle2"}>So empty...</Typography>
|
|
955
|
+
<Button
|
|
956
|
+
onClick={onNewClick}
|
|
957
|
+
className="mt-4"
|
|
958
|
+
>
|
|
959
|
+
<AddIcon />
|
|
960
|
+
Create your first entry
|
|
961
|
+
</Button>
|
|
962
|
+
</div>
|
|
963
|
+
: <Typography variant={"label"}>No results with the applied filter/sort</Typography>
|
|
964
|
+
}
|
|
965
|
+
hoverRow={hoverRow}
|
|
966
|
+
inlineEditing={checkInlineEditing()}
|
|
967
|
+
AdditionalHeaderWidget={buildAdditionalHeaderWidget}
|
|
968
|
+
AddColumnComponent={addColumnComponentInternal}
|
|
969
|
+
getIdColumnWidth={getIdColumnWidth}
|
|
970
|
+
additionalIDHeaderWidget={<EntityIdHeaderWidget
|
|
971
|
+
path={fullPath}
|
|
972
|
+
fullIdPath={fullIdPath ?? fullPath}
|
|
973
|
+
collection={collection} />}
|
|
974
|
+
openEntityMode={openEntityMode}
|
|
975
|
+
onColumnsOrderChange={(newColumns) => {
|
|
976
|
+
// Extract property keys from the new column order
|
|
977
|
+
// Filter to only include actual property columns (not frozen columns, not additional fields, etc.)
|
|
978
|
+
// Deduplicate to clean up any previously duplicated keys
|
|
979
|
+
const seenKeys = new Set<string>();
|
|
980
|
+
const newPropertiesOrder = newColumns
|
|
981
|
+
.filter(col => !col.frozen && getPropertyInPath(collection.properties, col.key))
|
|
982
|
+
.map(col => col.key)
|
|
983
|
+
.filter(key => {
|
|
984
|
+
if (seenKeys.has(key)) return false;
|
|
985
|
+
seenKeys.add(key);
|
|
986
|
+
return true;
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
// Optimistically update local state to prevent UI flickering
|
|
990
|
+
setLocalPropertiesOrder(newPropertiesOrder);
|
|
991
|
+
|
|
992
|
+
// Call each plugin's onColumnsReorder callback
|
|
993
|
+
if (customizationController?.plugins) {
|
|
994
|
+
customizationController.plugins
|
|
995
|
+
.filter(plugin => plugin.collectionView?.onColumnsReorder)
|
|
996
|
+
.forEach(plugin => {
|
|
997
|
+
plugin.collectionView!.onColumnsReorder!({
|
|
998
|
+
fullPath,
|
|
999
|
+
parentCollectionIds: parentCollectionIds ?? [],
|
|
1000
|
+
collection,
|
|
1001
|
+
newPropertiesOrder
|
|
1002
|
+
});
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
}}
|
|
1006
|
+
/>
|
|
1007
|
+
)}
|
|
1008
|
+
|
|
719
1009
|
{popupCell && <PopupFormField
|
|
720
1010
|
key={`popup_form_${popupCell?.propertyKey}_${popupCell?.entityId}`}
|
|
721
1011
|
open={Boolean(popupCell)}
|
|
@@ -728,7 +1018,7 @@ export const EntityCollectionView = React.memo(
|
|
|
728
1018
|
customFieldValidator={uniqueFieldValidator}
|
|
729
1019
|
path={resolvedFullPath}
|
|
730
1020
|
onCellValueChange={onValueChange}
|
|
731
|
-
container={containerRef.current}/>}
|
|
1021
|
+
container={containerRef.current} />}
|
|
732
1022
|
|
|
733
1023
|
{deleteEntityClicked &&
|
|
734
1024
|
<DeleteEntityDialog
|
|
@@ -739,7 +1029,7 @@ export const EntityCollectionView = React.memo(
|
|
|
739
1029
|
open={Boolean(deleteEntityClicked)}
|
|
740
1030
|
onEntityDelete={internalOnEntityDelete}
|
|
741
1031
|
onMultipleEntitiesDelete={internalOnMultipleEntitiesDelete}
|
|
742
|
-
onClose={() => setDeleteEntityClicked(undefined)}/>}
|
|
1032
|
+
onClose={() => setDeleteEntityClicked(undefined)} />}
|
|
743
1033
|
|
|
744
1034
|
</div>
|
|
745
1035
|
);
|
|
@@ -769,12 +1059,12 @@ export const EntityCollectionView = React.memo(
|
|
|
769
1059
|
}) as React.FunctionComponent<EntityCollectionViewProps<any>>
|
|
770
1060
|
|
|
771
1061
|
function EntitiesCount({
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
1062
|
+
fullPath,
|
|
1063
|
+
collection,
|
|
1064
|
+
filter,
|
|
1065
|
+
sortBy,
|
|
1066
|
+
onCountChange
|
|
1067
|
+
}: {
|
|
778
1068
|
fullPath: string,
|
|
779
1069
|
collection: EntityCollection,
|
|
780
1070
|
filter?: FilterValues<any>,
|
|
@@ -803,9 +1093,9 @@ function EntitiesCount({
|
|
|
803
1093
|
}, [fullPath, dataSource.countEntities, resolvedPath, collection, filter, sortByProperty, currentSort]);
|
|
804
1094
|
|
|
805
1095
|
useEffect(() => {
|
|
806
|
-
if (onCountChange) {
|
|
1096
|
+
if (onCountChange && count !== undefined) {
|
|
807
1097
|
setError(undefined);
|
|
808
|
-
onCountChange(count
|
|
1098
|
+
onCountChange(count);
|
|
809
1099
|
}
|
|
810
1100
|
}, [onCountChange, count]);
|
|
811
1101
|
|
|
@@ -813,14 +1103,11 @@ function EntitiesCount({
|
|
|
813
1103
|
return null;
|
|
814
1104
|
}
|
|
815
1105
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
variant={"caption"}
|
|
819
|
-
color={"secondary"}>
|
|
820
|
-
{count !== undefined ? `${count} entities` : <Skeleton className={"w-full max-w-[80px] mt-1"}/>}
|
|
821
|
-
</Typography>;
|
|
1106
|
+
// Count is now displayed in the breadcrumb bar, this component only fetches and reports
|
|
1107
|
+
return null;
|
|
822
1108
|
}
|
|
823
1109
|
|
|
1110
|
+
|
|
824
1111
|
function buildPropertyWidthOverwrite(key: string, width: number): PartialEntityCollection {
|
|
825
1112
|
if (key.includes(".")) {
|
|
826
1113
|
const [parentKey, ...childKey] = key.split(".");
|
|
@@ -830,10 +1117,10 @@ function buildPropertyWidthOverwrite(key: string, width: number): PartialEntityC
|
|
|
830
1117
|
}
|
|
831
1118
|
|
|
832
1119
|
function EntityIdHeaderWidget({
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
1120
|
+
collection,
|
|
1121
|
+
path,
|
|
1122
|
+
fullIdPath
|
|
1123
|
+
}: {
|
|
837
1124
|
collection: EntityCollection,
|
|
838
1125
|
path: string,
|
|
839
1126
|
fullIdPath: string
|
|
@@ -857,29 +1144,29 @@ function EntityIdHeaderWidget({
|
|
|
857
1144
|
alignOffset={-117}
|
|
858
1145
|
trigger={
|
|
859
1146
|
<IconButton size={"small"}>
|
|
860
|
-
<SearchIcon size={"small"}/>
|
|
1147
|
+
<SearchIcon size={"small"} />
|
|
861
1148
|
</IconButton>
|
|
862
1149
|
}>
|
|
863
1150
|
<div
|
|
864
1151
|
className={cls("my-2 rounded-lg bg-surface-50 dark:bg-surface-950 text-surface-900 dark:text-white")}>
|
|
865
1152
|
<form noValidate={true}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1153
|
+
onSubmit={(e) => {
|
|
1154
|
+
e.preventDefault();
|
|
1155
|
+
if (!searchString) return;
|
|
1156
|
+
setOpenPopup(false);
|
|
1157
|
+
const entityId = searchString.trim();
|
|
1158
|
+
setRecentIds(addRecentId(collection.id, entityId));
|
|
1159
|
+
navigateToEntity({
|
|
1160
|
+
openEntityMode,
|
|
1161
|
+
collection,
|
|
1162
|
+
entityId,
|
|
1163
|
+
path,
|
|
1164
|
+
fullIdPath,
|
|
1165
|
+
sideEntityController,
|
|
1166
|
+
navigation
|
|
1167
|
+
})
|
|
1168
|
+
}}
|
|
1169
|
+
className={"w-96 max-w-full"}>
|
|
883
1170
|
|
|
884
1171
|
<div className="flex p-2 w-full gap-2">
|
|
885
1172
|
<input
|
|
@@ -890,32 +1177,32 @@ function EntityIdHeaderWidget({
|
|
|
890
1177
|
setSearchString(e.target.value);
|
|
891
1178
|
}}
|
|
892
1179
|
value={searchString}
|
|
893
|
-
className={"rounded-lg bg-white dark:bg-surface-800 flex-grow bg-transparent outline-none p-2 " + focusedDisabled}/>
|
|
1180
|
+
className={"rounded-lg bg-white dark:bg-surface-800 flex-grow bg-transparent outline-none p-2 " + focusedDisabled} />
|
|
894
1181
|
<Button variant={"text"}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
><KeyboardTabIcon/></Button>
|
|
1182
|
+
disabled={!(searchString.trim())}
|
|
1183
|
+
type={"submit"}
|
|
1184
|
+
><KeyboardTabIcon /></Button>
|
|
898
1185
|
</div>
|
|
899
1186
|
</form>
|
|
900
1187
|
{recentIds && recentIds.length > 0 && <div className="flex flex-col gap-2 p-2">
|
|
901
1188
|
{recentIds.map(id => (
|
|
902
1189
|
<ReferencePreview reference={new EntityReference(id, path)}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1190
|
+
key={id}
|
|
1191
|
+
hover={true}
|
|
1192
|
+
onClick={() => {
|
|
1193
|
+
setOpenPopup(false);
|
|
1194
|
+
navigateToEntity({
|
|
1195
|
+
openEntityMode,
|
|
1196
|
+
collection,
|
|
1197
|
+
entityId: id,
|
|
1198
|
+
path,
|
|
1199
|
+
fullIdPath,
|
|
1200
|
+
sideEntityController,
|
|
1201
|
+
navigation
|
|
1202
|
+
})
|
|
1203
|
+
}}
|
|
1204
|
+
includeEntityLink={false}
|
|
1205
|
+
size={"small"} />
|
|
919
1206
|
))}
|
|
920
1207
|
</div>}
|
|
921
1208
|
</div>
|