@firecms/core 3.1.0-canary.1df3b2c → 3.1.0-canary.75005e4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/dist/components/EntityCollectionTable/internal/popup_field/useDraggable.d.ts +2 -2
  2. package/dist/components/EntityCollectionView/CollectionDataErrorBanner.d.ts +4 -0
  3. package/dist/components/EntityCollectionView/ViewModeToggle.d.ts +5 -10
  4. package/dist/components/ErrorBoundary.d.ts +4 -2
  5. package/dist/components/HomePage/DefaultHomePage.d.ts +0 -1
  6. package/dist/components/LanguageToggle.d.ts +1 -0
  7. package/dist/components/UnsavedChangesDialog.d.ts +1 -0
  8. package/dist/components/VirtualTable/VirtualTableHeader.d.ts +1 -1
  9. package/dist/components/index.d.ts +1 -0
  10. package/dist/core/DrawerNavigationGroup.d.ts +2 -2
  11. package/dist/editor/components/SlashCommandMenu.d.ts +6 -0
  12. package/dist/editor/components/editor-bubble-item.d.ts +8 -0
  13. package/dist/editor/components/editor-bubble.d.ts +8 -0
  14. package/dist/editor/components/index.d.ts +14 -0
  15. package/dist/editor/editor.d.ts +30 -0
  16. package/dist/editor/extensions/HighlightDecorationExtension.d.ts +24 -0
  17. package/dist/editor/extensions/Image/index.d.ts +6 -0
  18. package/dist/editor/extensions/Image.d.ts +6 -0
  19. package/dist/editor/extensions/TextLoadingDecorationExtension.d.ts +16 -0
  20. package/dist/editor/extensions/clipboard.d.ts +7 -0
  21. package/dist/editor/extensions/custom-keymap.d.ts +1 -0
  22. package/dist/editor/extensions/drag-and-drop.d.ts +9 -0
  23. package/dist/editor/hooks/useProseMirror.d.ts +14 -0
  24. package/dist/editor/hooks/useProseMirrorContext.d.ts +9 -0
  25. package/dist/editor/index.d.ts +2 -0
  26. package/dist/editor/markdown.d.ts +5 -0
  27. package/dist/editor/nodeViews/ImageComponent.d.ts +3 -0
  28. package/dist/editor/nodeViews/ReactNodeView.d.ts +29 -0
  29. package/dist/editor/nodeViews/TaskItemComponent.d.ts +3 -0
  30. package/dist/editor/nodeViews/index.d.ts +6 -0
  31. package/dist/editor/plugins/index.d.ts +2 -0
  32. package/dist/editor/plugins/inputrules.d.ts +6 -0
  33. package/dist/editor/plugins/placeholderPlugin.d.ts +3 -0
  34. package/dist/editor/plugins/slashCommandPlugin.d.ts +11 -0
  35. package/dist/editor/schema.d.ts +2 -0
  36. package/dist/editor/selectors/ai-selector.d.ts +0 -0
  37. package/dist/editor/selectors/color-selector.d.ts +10 -0
  38. package/dist/editor/selectors/link-selector.d.ts +8 -0
  39. package/dist/editor/selectors/node-selector.d.ts +15 -0
  40. package/dist/editor/selectors/text-buttons.d.ts +1 -0
  41. package/dist/editor/types.d.ts +5 -0
  42. package/dist/editor/useProseMirror.d.ts +16 -0
  43. package/dist/editor/utils/prosemirror-utils.d.ts +6 -0
  44. package/dist/editor/utils/remove_classes.d.ts +1 -0
  45. package/dist/editor/utils/useDebouncedCallback.d.ts +1 -0
  46. package/dist/form/components/ErrorFocus.d.ts +1 -1
  47. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
  48. package/dist/hooks/index.d.ts +1 -0
  49. package/dist/hooks/useBuildNavigationController.d.ts +0 -1
  50. package/dist/hooks/useCollapsedGroups.d.ts +3 -3
  51. package/dist/hooks/useTranslation.d.ts +17 -0
  52. package/dist/i18n/FireCMSi18nProvider.d.ts +33 -0
  53. package/dist/index.d.ts +4 -0
  54. package/dist/index.es.js +11441 -2215
  55. package/dist/index.es.js.map +1 -1
  56. package/dist/index.umd.js +11423 -2216
  57. package/dist/index.umd.js.map +1 -1
  58. package/dist/internal/useRestoreScroll.d.ts +1 -1
  59. package/dist/locales/de.d.ts +2 -0
  60. package/dist/locales/en.d.ts +10 -0
  61. package/dist/locales/es.d.ts +10 -0
  62. package/dist/locales/fr.d.ts +2 -0
  63. package/dist/locales/hi.d.ts +2 -0
  64. package/dist/locales/it.d.ts +2 -0
  65. package/dist/locales/pt.d.ts +7 -0
  66. package/dist/types/analytics.d.ts +1 -1
  67. package/dist/types/collections.d.ts +8 -0
  68. package/dist/types/customization_controller.d.ts +2 -1
  69. package/dist/types/firecms.d.ts +2 -1
  70. package/dist/types/index.d.ts +1 -0
  71. package/dist/types/navigation.d.ts +2 -2
  72. package/dist/types/plugins.d.ts +23 -0
  73. package/dist/types/translations.d.ts +646 -0
  74. package/dist/util/entities.d.ts +1 -1
  75. package/dist/util/resolutions.d.ts +2 -2
  76. package/package.json +47 -13
  77. package/src/app/Scaffold.tsx +7 -5
  78. package/src/components/AIIcon.tsx +3 -1
  79. package/src/components/ArrayContainer.tsx +6 -4
  80. package/src/components/ClearFilterSortButton.tsx +6 -3
  81. package/src/components/ConfirmationDialog.tsx +4 -2
  82. package/src/components/DeleteEntityDialog.tsx +10 -7
  83. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +6 -3
  84. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +3 -1
  85. package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +1 -1
  86. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +3 -2
  87. package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +11 -11
  88. package/src/components/EntityCollectionView/BoardSortableList.tsx +3 -1
  89. package/src/components/EntityCollectionView/CollectionDataErrorBanner.tsx +43 -0
  90. package/src/components/EntityCollectionView/EntityBoardCard.tsx +1 -1
  91. package/src/components/EntityCollectionView/EntityCard.tsx +4 -0
  92. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +39 -46
  93. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +17 -25
  94. package/src/components/EntityCollectionView/EntityCollectionView.tsx +73 -31
  95. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +4 -3
  96. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +4 -2
  97. package/src/components/EntityCollectionView/FiltersDialog.tsx +8 -5
  98. package/src/components/EntityCollectionView/ViewModeToggle.tsx +37 -37
  99. package/src/components/EntityView.tsx +3 -2
  100. package/src/components/ErrorBoundary.tsx +27 -15
  101. package/src/components/HomePage/DefaultHomePage.tsx +19 -13
  102. package/src/components/HomePage/HomePageDnD.tsx +3 -1
  103. package/src/components/HomePage/NavigationGroup.tsx +3 -1
  104. package/src/components/HomePage/RenameGroupDialog.tsx +15 -13
  105. package/src/components/LanguageToggle.tsx +66 -0
  106. package/src/components/NotFoundPage.tsx +5 -3
  107. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +9 -7
  108. package/src/components/ReferenceWidget.tsx +3 -2
  109. package/src/components/SearchIconsView.tsx +3 -1
  110. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +11 -0
  111. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +15 -2
  112. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +11 -0
  113. package/src/components/UnsavedChangesDialog.tsx +6 -4
  114. package/src/components/VirtualTable/VirtualTable.performance.test.tsx +1 -0
  115. package/src/components/VirtualTable/VirtualTable.tsx +116 -113
  116. package/src/components/VirtualTable/VirtualTableHeader.tsx +54 -52
  117. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +1 -1
  118. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +3 -3
  119. package/src/components/common/default_entity_actions.tsx +4 -0
  120. package/src/components/common/useDataSourceTableController.tsx +12 -4
  121. package/src/components/index.tsx +1 -0
  122. package/src/core/DefaultAppBar.tsx +15 -11
  123. package/src/core/DefaultDrawer.tsx +8 -2
  124. package/src/core/DrawerNavigationGroup.tsx +5 -3
  125. package/src/core/EntityEditView.tsx +4 -3
  126. package/src/core/EntityEditViewFormActions.tsx +24 -17
  127. package/src/core/EntitySidePanel.tsx +32 -29
  128. package/src/core/FireCMS.tsx +33 -6
  129. package/src/core/field_configs.tsx +14 -9
  130. package/src/editor/components/SlashCommandMenu.tsx +348 -0
  131. package/src/editor/components/editor-bubble-item.tsx +32 -0
  132. package/src/editor/components/editor-bubble.tsx +118 -0
  133. package/src/editor/components/index.ts +12 -0
  134. package/src/editor/editor.tsx +307 -0
  135. package/src/editor/extensions/HighlightDecorationExtension.ts +114 -0
  136. package/src/editor/extensions/Image/index.ts +133 -0
  137. package/src/editor/extensions/Image.ts +144 -0
  138. package/src/editor/extensions/TextLoadingDecorationExtension.tsx +107 -0
  139. package/src/editor/extensions/clipboard.ts +72 -0
  140. package/src/editor/extensions/custom-keymap.ts +24 -0
  141. package/src/editor/extensions/drag-and-drop.tsx +472 -0
  142. package/src/editor/hooks/useProseMirror.ts +115 -0
  143. package/src/editor/hooks/useProseMirrorContext.ts +15 -0
  144. package/src/editor/index.ts +2 -0
  145. package/src/editor/markdown.ts +110 -0
  146. package/src/editor/nodeViews/ImageComponent.tsx +20 -0
  147. package/src/editor/nodeViews/ReactNodeView.tsx +89 -0
  148. package/src/editor/nodeViews/TaskItemComponent.tsx +29 -0
  149. package/src/editor/nodeViews/index.ts +35 -0
  150. package/src/editor/plugins/index.ts +55 -0
  151. package/src/editor/plugins/inputrules.ts +82 -0
  152. package/src/editor/plugins/placeholderPlugin.ts +55 -0
  153. package/src/editor/plugins/slashCommandPlugin.ts +49 -0
  154. package/src/editor/schema.ts +228 -0
  155. package/src/editor/selectors/ai-selector.tsx +111 -0
  156. package/src/editor/selectors/color-selector.tsx +200 -0
  157. package/src/editor/selectors/link-selector.tsx +118 -0
  158. package/src/editor/selectors/node-selector.tsx +157 -0
  159. package/src/editor/selectors/text-buttons.tsx +86 -0
  160. package/src/editor/types.ts +6 -0
  161. package/src/editor/useProseMirror.ts +126 -0
  162. package/src/editor/utils/prosemirror-utils.ts +78 -0
  163. package/src/editor/utils/remove_classes.ts +17 -0
  164. package/src/editor/utils/useDebouncedCallback.ts +25 -0
  165. package/src/form/EntityForm.tsx +76 -63
  166. package/src/form/EntityFormActions.tsx +19 -12
  167. package/src/form/PropertyFieldBinding.tsx +6 -5
  168. package/src/form/components/ErrorFocus.tsx +3 -3
  169. package/src/form/components/LocalChangesMenu.tsx +13 -13
  170. package/src/form/components/StorageItemPreview.tsx +3 -2
  171. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +4 -4
  172. package/src/form/field_bindings/BlockFieldBinding.tsx +5 -2
  173. package/src/form/field_bindings/KeyValueFieldBinding.tsx +23 -18
  174. package/src/form/field_bindings/MapFieldBinding.tsx +4 -3
  175. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +4 -4
  176. package/src/form/field_bindings/RepeatFieldBinding.tsx +3 -1
  177. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +87 -85
  178. package/src/hooks/index.tsx +1 -0
  179. package/src/hooks/useBuildNavigationController.tsx +49 -22
  180. package/src/hooks/useCollapsedGroups.ts +7 -6
  181. package/src/hooks/useTranslation.ts +31 -0
  182. package/src/hooks/useValidateAuthenticator.tsx +1 -1
  183. package/src/i18n/FireCMSi18nProvider.tsx +160 -0
  184. package/src/index.ts +4 -0
  185. package/src/internal/useBuildDataSource.ts +1 -2
  186. package/src/internal/useBuildSideEntityController.tsx +22 -20
  187. package/src/locales/de.ts +691 -0
  188. package/src/locales/en.ts +703 -0
  189. package/src/locales/es.ts +703 -0
  190. package/src/locales/fr.ts +691 -0
  191. package/src/locales/hi.ts +691 -0
  192. package/src/locales/it.ts +691 -0
  193. package/src/locales/pt.ts +700 -0
  194. package/src/preview/PropertyPreview.tsx +1 -0
  195. package/src/preview/components/UrlComponentPreview.tsx +4 -2
  196. package/src/preview/components/UserPreview.tsx +3 -1
  197. package/src/types/analytics.ts +10 -0
  198. package/src/types/collections.ts +9 -0
  199. package/src/types/customization_controller.tsx +2 -1
  200. package/src/types/firecms.tsx +2 -1
  201. package/src/types/index.ts +1 -0
  202. package/src/types/navigation.ts +2 -2
  203. package/src/types/plugins.tsx +26 -0
  204. package/src/types/translations.ts +725 -0
  205. package/src/util/entities.ts +1 -1
  206. package/src/util/join_collections.ts +10 -8
  207. package/src/util/previews.ts +2 -2
  208. package/src/util/property_utils.tsx +1 -1
  209. package/src/util/resolutions.ts +5 -3
@@ -20,9 +20,6 @@ import {
20
20
  DialogActions,
21
21
  DialogContent,
22
22
  getColorSchemeForSeed,
23
- IconButton,
24
- RefreshIcon,
25
- Tooltip,
26
23
  Typography
27
24
  } from "@firecms/ui";
28
25
  import { getPropertyInPath, resolveCollection, resolveEnumValues } from "../../util";
@@ -34,9 +31,12 @@ import {
34
31
  useFireCMSContext,
35
32
  useSideEntityController
36
33
  } from "../../hooks";
34
+ import { useAnalyticsController } from "../../hooks/useAnalyticsController";
37
35
  import { SaveEntityProps } from "../../types/datasource";
38
36
  import { setIn } from "@firecms/formex";
39
37
  import { useBoardDataController } from "./useBoardDataController";
38
+ import { CollectionDataErrorBanner } from "./CollectionDataErrorBanner";
39
+ import { useTranslation } from "../../hooks/useTranslation";
40
40
 
41
41
  export type EntityCollectionBoardViewProps<M extends Record<string, any> = any> = {
42
42
  collection: EntityCollection<M>;
@@ -74,7 +74,9 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
74
74
  const context = useFireCMSContext();
75
75
  const dataSource = useDataSource(collection);
76
76
  const sideEntityController = useSideEntityController();
77
+ const analyticsController = useAnalyticsController();
77
78
  const plugins = customizationController.plugins ?? [];
79
+ const { t } = useTranslation();
78
80
 
79
81
  // State for backfill dialog
80
82
  const [showBackfillDialog, setShowBackfillDialog] = useState(false);
@@ -222,6 +224,10 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
222
224
  }, [plugins]);
223
225
 
224
226
  const handleColumnReorder = useCallback((newColumns: string[]) => {
227
+ analyticsController.onAnalyticsEvent?.("kanban_column_reorder", {
228
+ path: fullPath,
229
+ columnProperty
230
+ });
225
231
  setHasUserReordered(true);
226
232
  setLocalColumnsOrder(newColumns);
227
233
  plugins
@@ -235,7 +241,7 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
235
241
  newColumnsOrder: newColumns
236
242
  });
237
243
  });
238
- }, [plugins, fullPath, parentCollectionIds, collection, columnProperty]);
244
+ }, [plugins, fullPath, parentCollectionIds, collection, columnProperty, analyticsController]);
239
245
 
240
246
  // Collection-level count queries to detect missing order property
241
247
  // Just TWO counts: total and ordered (for the entire collection, not per column)
@@ -393,6 +399,13 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
393
399
  const entity = items.find(item => item.id === moveInfo?.itemId)?.entity;
394
400
  if (!entity) return;
395
401
 
402
+ analyticsController.onAnalyticsEvent?.("kanban_card_moved", {
403
+ path: fullPath,
404
+ entityId: entity.id,
405
+ sourceColumn: moveInfo?.sourceColumn,
406
+ targetColumn: moveInfo?.targetColumn
407
+ });
408
+
396
409
  const isColumnChange = moveInfo && moveInfo.sourceColumn !== moveInfo.targetColumn;
397
410
 
398
411
  // If no orderProperty and not a column change, nothing to do
@@ -440,7 +453,7 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
440
453
  } catch (e) {
441
454
  console.error("Error saving entity:", e);
442
455
  }
443
- }, [collection, columnProperty, orderProperty, context, dataSource, calculateNewOrder, boardDataController]);
456
+ }, [collection, columnProperty, orderProperty, context, dataSource, calculateNewOrder, boardDataController, analyticsController, fullPath]);
444
457
 
445
458
  // Backfill order values for all entities
446
459
  const handleBackfill = useCallback(async () => {
@@ -449,6 +462,9 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
449
462
  console.log("No orderProperty, returning");
450
463
  return;
451
464
  }
465
+ analyticsController.onAnalyticsEvent?.("kanban_backfill_order", {
466
+ path: fullPath
467
+ });
452
468
  setBackfillLoading(true);
453
469
 
454
470
  try {
@@ -514,7 +530,7 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
514
530
  } finally {
515
531
  setBackfillLoading(false);
516
532
  }
517
- }, [orderProperty, fullPath, collection, dataSource, context, boardDataController]);
533
+ }, [orderProperty, fullPath, collection, dataSource, context, boardDataController, analyticsController]);
518
534
 
519
535
  const handleEntityClick = useCallback((entity: Entity<M>) => {
520
536
  onEntityClick?.(entity);
@@ -563,19 +579,16 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
563
579
 
564
580
  // Check for loading error
565
581
  const hasError = Boolean(dataLoadingError);
566
- const errorMessage = dataLoadingError?.message || "";
567
- const indexUrl = errorMessage.match(/https:\/\/console\.firebase\.google\.com[^\s]+/)?.[0];
568
582
 
569
583
  // Error: no enum properties available for Kanban columns
570
584
  if (!columnProperty || enumColumns.length === 0) {
571
585
  return (
572
586
  <div className="flex-1 flex flex-col items-center justify-center p-8 gap-4">
573
587
  <Typography variant="h6">
574
- Kanban view is not available
588
+ {t("kanban_view_not_available")}
575
589
  </Typography>
576
590
  <Typography variant="body2" color="secondary" className="text-center max-w-md">
577
- Kanban view requires a string property with enum values to group entities into columns.
578
- Please add an enum property to your collection schema to use this view.
591
+ {t("kanban_view_requires_enum")}
579
592
  </Typography>
580
593
  {KanbanSetupComponent && (
581
594
  <KanbanSetupComponent
@@ -596,7 +609,7 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
596
609
  return (
597
610
  <div className="flex-1 flex items-center justify-center p-8">
598
611
  <Typography variant="label" color="secondary">
599
- No enum values configured for property "{columnProperty}"
612
+ {t("no_enum_values_configured", { property: columnProperty })}
600
613
  </Typography>
601
614
  </div>
602
615
  );
@@ -606,33 +619,10 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
606
619
  <div className="flex-1 flex flex-col overflow-hidden">
607
620
  {/* Error banner - only show when no data loaded */}
608
621
  {hasError && allEntities.length === 0 && (
609
- <div
610
- className="flex items-center gap-4 px-4 py-3 bg-red-50 dark:bg-red-900/20 border-b border-red-200 dark:border-red-800">
611
- <Typography variant="body2" className="text-red-700 dark:text-red-300 flex-1">
612
- <strong>Error:</strong>{" "}
613
- {indexUrl
614
- ? "A Firestore index is required for this query."
615
- : errorMessage}
616
- </Typography>
617
- <Tooltip title="Refresh data">
618
- <IconButton
619
- size="small"
620
- onClick={() => boardDataController.refreshAll()}
621
- >
622
- <RefreshIcon size="small" />
623
- </IconButton>
624
- </Tooltip>
625
- {indexUrl && (
626
- <Button
627
- size="small"
628
- variant="outlined"
629
- color="error"
630
- onClick={() => window.open(indexUrl, "_blank")}
631
- >
632
- Create Index
633
- </Button>
634
- )}
635
- </div>
622
+ <CollectionDataErrorBanner
623
+ error={dataLoadingError}
624
+ onRetry={() => boardDataController.refreshAll()}
625
+ />
636
626
  )}
637
627
 
638
628
  {/* Backfill info bar - non-blocking */}
@@ -640,14 +630,14 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
640
630
  <div
641
631
  className="flex items-center justify-between gap-4 px-4 py-2 bg-amber-50 dark:bg-amber-900/20 border-b border-amber-200 dark:border-amber-800">
642
632
  <Typography variant="body2" color="secondary">
643
- Some items don't have order values. Initialize to enable drag-and-drop reordering.
633
+ {t("items_need_backfill")}
644
634
  </Typography>
645
635
  <Button
646
636
  size="small"
647
637
  variant="text"
648
638
  onClick={() => setShowBackfillDialog(true)}
649
639
  >
650
- Initialize Order
640
+ {t("initialize")}
651
641
  </Button>
652
642
  </div>
653
643
  )}
@@ -667,6 +657,10 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
667
657
  columnLoadingState={columnLoadingState}
668
658
  onLoadMoreColumn={(column) => boardDataController.loadMoreColumn(column)}
669
659
  onAddItemToColumn={(column) => {
660
+ analyticsController.onAnalyticsEvent?.("kanban_new_entity_in_column", {
661
+ path: fullPath,
662
+ column
663
+ });
670
664
  sideEntityController.open({
671
665
  path: fullPath,
672
666
  collection,
@@ -693,18 +687,17 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
693
687
  {/* Backfill dialog */}
694
688
  <Dialog open={showBackfillDialog} onOpenChange={setShowBackfillDialog}>
695
689
  <DialogContent>
696
- <Typography variant="h6" className="mb-4">Initialize Kanban Order</Typography>
690
+ <Typography variant="h6" className="mb-4">{t("initialize_kanban_order")}</Typography>
697
691
  <Typography variant="body2">
698
- This will assign sequential order values to all items that don't have one.
699
- Items will maintain their current order within each column.
692
+ {t("initialize_kanban_order_desc")}
700
693
  </Typography>
701
694
  </DialogContent>
702
695
  <DialogActions>
703
696
  <Button variant="text" onClick={() => setShowBackfillDialog(false)} disabled={backfillLoading}>
704
- Cancel
697
+ {t("cancel")}
705
698
  </Button>
706
699
  <Button onClick={handleBackfill} disabled={backfillLoading}>
707
- {backfillLoading ? <CircularProgress size="smallest" /> : "Initialize"}
700
+ {backfillLoading ? <CircularProgress size="smallest" /> : t("initialize")}
708
701
  </Button>
709
702
  </DialogActions>
710
703
  </Dialog>
@@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useRef } from "react";
2
2
  import { CollectionSize, Entity, EntityCollection, EntityTableController, SelectionController } from "../../types";
3
3
  import { EntityCard } from "./EntityCard";
4
4
  import { CircularProgress, cls, Typography } from "@firecms/ui";
5
- import { useAuthController, useCustomizationController } from "../../hooks";
5
+ import { useAuthController, useCustomizationController, useTranslation } from "../../hooks";
6
6
 
7
7
  export type EntityCollectionCardViewProps<M extends Record<string, any> = any> = {
8
8
  collection: EntityCollection<M>;
@@ -61,17 +61,18 @@ function getGridColumnsClass(size: CollectionSize): string {
61
61
  * Alternative to the EntityCollectionTable for visual browsing.
62
62
  */
63
63
  export function EntityCollectionCardView<M extends Record<string, any> = any>({
64
- collection,
65
- tableController,
66
- onEntityClick,
67
- selectionController,
68
- selectionEnabled = true,
69
- highlightedEntities,
70
- emptyComponent,
71
- onScroll,
72
- initialScroll,
73
- size = "m"
74
- }: EntityCollectionCardViewProps<M>) {
64
+ collection,
65
+ tableController,
66
+ onEntityClick,
67
+ selectionController,
68
+ selectionEnabled = true,
69
+ highlightedEntities,
70
+ emptyComponent,
71
+ onScroll,
72
+ initialScroll,
73
+ size = "m"
74
+ }: EntityCollectionCardViewProps<M>) {
75
+ const { t } = useTranslation();
75
76
  const authController = useAuthController();
76
77
  const customizationController = useCustomizationController();
77
78
 
@@ -178,23 +179,14 @@ export function EntityCollectionCardView<M extends Record<string, any> = any>({
178
179
  <div className="flex-1 flex items-center justify-center p-8">
179
180
  {emptyComponent ?? (
180
181
  <Typography variant="label" color="secondary">
181
- No entries found
182
+ {t("no_entries_found")}
182
183
  </Typography>
183
184
  )}
184
185
  </div>
185
186
  );
186
187
  }
187
188
 
188
- // Show error state
189
- if (dataLoadingError) {
190
- return (
191
- <div className="flex-1 flex items-center justify-center p-8">
192
- <Typography className="text-red-500">
193
- Error loading data: {dataLoadingError.message}
194
- </Typography>
195
- </div>
196
- );
197
- }
189
+
198
190
 
199
191
  const gridColumnsClass = getGridColumnsClass(size);
200
192
 
@@ -230,11 +222,11 @@ export function EntityCollectionCardView<M extends Record<string, any> = any>({
230
222
  className="flex items-center justify-center py-8"
231
223
  >
232
224
  {dataLoading && (
233
- <CircularProgress size="small"/>
225
+ <CircularProgress size="small" />
234
226
  )}
235
227
  {!dataLoading && noMoreToLoad && data.length > 0 && (
236
228
  <Typography variant="caption" color="secondary">
237
- All {data.length} entries loaded
229
+ {t("all_entries_loaded", { count: data.length.toString() })}
238
230
  </Typography>
239
231
  )}
240
232
  </div>
@@ -45,13 +45,15 @@ import {
45
45
  useFireCMSContext,
46
46
  useLargeLayout,
47
47
  useNavigationController,
48
- useSideEntityController
48
+ useSideEntityController,
49
+ useTranslation
49
50
  } from "../../hooks";
50
51
  import { useBreadcrumbsController } from "../../hooks/useBreadcrumbsController";
51
52
  import { useUserConfigurationPersistence } from "../../hooks/useUserConfigurationPersistence";
52
53
  import { EntityCollectionViewActions } from "./EntityCollectionViewActions";
53
54
  import { EntityCollectionCardView } from "./EntityCollectionCardView";
54
55
  import { EntityCollectionBoardView } from "./EntityCollectionBoardView";
56
+ import { CollectionDataErrorBanner } from "./CollectionDataErrorBanner";
55
57
  import { ViewModeToggle, KanbanPropertyOption } from "./ViewModeToggle";
56
58
  import {
57
59
  AddIcon,
@@ -159,6 +161,7 @@ export const EntityCollectionView = React.memo(
159
161
  ) {
160
162
 
161
163
  const context = useFireCMSContext();
164
+ const { t } = useTranslation();
162
165
  const navigation = useNavigationController();
163
166
  const breadcrumbs = useBreadcrumbsController();
164
167
  const fullPath = fullPathProp ?? collectionProp.path;
@@ -433,12 +436,17 @@ export const EntityCollectionView = React.memo(
433
436
 
434
437
  // View mode change: update URL + save to local persistence
435
438
  const onViewModeChange = useCallback((mode: ViewMode) => {
439
+ analyticsController.onAnalyticsEvent?.("view_mode_changed", {
440
+ path: fullPath,
441
+ from: viewMode,
442
+ to: mode
443
+ });
436
444
  setViewMode(mode);
437
445
  // Save to local persistence for next visit
438
446
  if (userConfigPersistence) {
439
447
  onCollectionModifiedForUser(fullPath, { defaultViewMode: mode } as PartialEntityCollection<M>);
440
448
  }
441
- }, [setViewMode, userConfigPersistence, onCollectionModifiedForUser, fullPath]);
449
+ }, [setViewMode, userConfigPersistence, onCollectionModifiedForUser, fullPath, analyticsController, viewMode]);
442
450
 
443
451
  const createEnabled = canCreateEntity(collection, authController, fullPath, null);
444
452
 
@@ -501,18 +509,24 @@ export const EntityCollectionView = React.memo(
501
509
  authController,
502
510
  }), [collection, fullPath]);
503
511
 
504
- // Check if Kanban view is available (needs kanban.columnProperty with enumValues)
505
- const kanbanEnabled = useMemo(() => {
506
- if (!collection.kanban?.columnProperty) return false;
507
- const property = getPropertyInPath(resolvedCollection.properties, collection.kanban.columnProperty);
508
- if (!property || !("dataType" in property) || property.dataType !== "string") return false;
509
- return Boolean(property.enumValues);
510
- }, [collection.kanban?.columnProperty, resolvedCollection.properties]);
512
+ // Check if Kanban view is possible (collection has at least one string enum property)
513
+ const hasEnumProperty = useMemo(() => {
514
+ const properties = resolvedCollection.properties;
515
+ return Object.values(properties).some((prop: any) =>
516
+ prop && prop.dataType === "string" && prop.enumValues
517
+ );
518
+ }, [resolvedCollection.properties]);
511
519
 
512
- // Check if a plugin can configure Kanban (has KanbanSetupComponent)
513
- const hasKanbanConfigPlugin = useMemo(() => {
514
- return customizationController.plugins?.some(plugin => plugin.collectionView?.KanbanSetupComponent) ?? false;
515
- }, [customizationController.plugins]);
520
+ // Compute the effective enabled views:
521
+ // - Start from collection.enabledViews (defaults to all three)
522
+ // - Filter out kanban if no enum properties exist
523
+ const enabledViews: ViewMode[] = useMemo(() => {
524
+ const configured = collection.enabledViews ?? ["table", "cards", "kanban"];
525
+ if (!hasEnumProperty) {
526
+ return configured.filter(v => v !== "kanban");
527
+ }
528
+ return configured;
529
+ }, [collection.enabledViews, hasEnumProperty]);
516
530
 
517
531
  // Compute available enum properties for kanban column selection
518
532
  const kanbanPropertyOptions: KanbanPropertyOption[] = useMemo(() => {
@@ -562,12 +576,16 @@ export const EntityCollectionView = React.memo(
562
576
 
563
577
  // Handle kanban property change
564
578
  const onKanbanPropertyChange = useCallback((property: string) => {
579
+ analyticsController.onAnalyticsEvent?.("kanban_property_changed", {
580
+ path: fullPath,
581
+ property
582
+ });
565
583
  setSelectedKanbanProperty(property);
566
584
  // Save to local persistence
567
585
  if (userConfigPersistence) {
568
586
  onCollectionModifiedForUser(fullPath, { kanbanColumnProperty: property } as any);
569
587
  }
570
- }, [userConfigPersistence, onCollectionModifiedForUser, fullPath]);
588
+ }, [userConfigPersistence, onCollectionModifiedForUser, fullPath, analyticsController]);
571
589
 
572
590
  const getPropertyFor = useCallback(({
573
591
  propertyKey,
@@ -680,11 +698,13 @@ export const EntityCollectionView = React.memo(
680
698
  customEntityActions?: EntityAction[]
681
699
  }): EntityAction[] => {
682
700
  const deleteEnabled = entity ? canDeleteEntity(collection, authController, fullPath, entity) : true;
683
- const actions: EntityAction[] = [editEntityAction];
701
+ const actions: EntityAction[] = [
702
+ { ...editEntityAction, name: t("edit") }
703
+ ];
684
704
  if (createEnabled)
685
- actions.push(copyEntityAction);
705
+ actions.push({ ...copyEntityAction, name: t("copy") });
686
706
  if (deleteEnabled)
687
- actions.push(deleteEntityAction);
707
+ actions.push({ ...deleteEntityAction, name: t("delete") });
688
708
  if (customEntityActions)
689
709
  return mergeEntityActions(actions, customEntityActions);
690
710
  return actions;
@@ -819,8 +839,7 @@ export const EntityCollectionView = React.memo(
819
839
  <ViewModeToggle
820
840
  viewMode={viewMode}
821
841
  onViewModeChange={onViewModeChange}
822
- kanbanEnabled={kanbanEnabled}
823
- hasKanbanConfigPlugin={hasKanbanConfigPlugin}
842
+ enabledViews={enabledViews}
824
843
  size={viewMode === "table" ? tableSize : viewMode === "cards" ? cardSize : undefined}
825
844
  onSizeChanged={viewMode === "table" ? onTableSizeChanged : viewMode === "cards" ? setCardSize : undefined}
826
845
  open={viewModePopoverOpen}
@@ -831,6 +850,24 @@ export const EntityCollectionView = React.memo(
831
850
  />
832
851
  );
833
852
 
853
+ // Compute plugin-provided error view for collection loading errors
854
+ const pluginErrorView = useMemo(() => {
855
+ const error = tableController.dataLoadingError;
856
+ if (!error || !customizationController.plugins) return null;
857
+ for (const plugin of customizationController.plugins) {
858
+ if (plugin.collectionView?.CollectionError) {
859
+ const CollectionError = plugin.collectionView.CollectionError;
860
+ return <CollectionError
861
+ path={fullPath}
862
+ collection={collection}
863
+ parentCollectionIds={parentCollectionIds}
864
+ error={error}
865
+ />;
866
+ }
867
+ }
868
+ return null;
869
+ }, [tableController.dataLoadingError, customizationController.plugins, fullPath, collection, parentCollectionIds]);
870
+
834
871
  return (
835
872
  <div className={cls("overflow-hidden h-full w-full rounded-md flex flex-col", className)}
836
873
  ref={containerRef}>
@@ -867,7 +904,11 @@ export const EntityCollectionView = React.memo(
867
904
  />
868
905
 
869
906
  {/* View content - only the view-specific content changes */}
870
- {viewMode === "kanban" && (kanbanEnabled || hasKanbanConfigPlugin) ? (
907
+ {tableController.dataLoadingError && pluginErrorView}
908
+ {tableController.dataLoadingError && !pluginErrorView && (
909
+ <CollectionDataErrorBanner error={tableController.dataLoadingError} />
910
+ )}
911
+ {viewMode === "kanban" && enabledViews.includes("kanban") ? (
871
912
  <EntityCollectionBoardView
872
913
  key={`kanban-view-${fullPath}-${selectedKanbanProperty}`}
873
914
  collection={collection}
@@ -882,16 +923,16 @@ export const EntityCollectionView = React.memo(
882
923
  deletedEntities={deletedEntities}
883
924
  emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
884
925
  ? <div className="flex flex-col items-center justify-center">
885
- <Typography variant={"subtitle2"}>So empty...</Typography>
926
+ <Typography variant={"subtitle2"}>{t("so_empty")}</Typography>
886
927
  <Button
887
928
  onClick={onNewClick}
888
929
  className="mt-4"
889
930
  >
890
931
  <AddIcon />
891
- Create your first entry
932
+ {t("create_your_first_entry")}
892
933
  </Button>
893
934
  </div>
894
- : <Typography variant={"label"}>No results with the applied filter/sort</Typography>
935
+ : <Typography variant={"label"}>{t("no_results_filter_sort")}</Typography>
895
936
  }
896
937
  />
897
938
  ) : viewMode === "cards" ? (
@@ -908,16 +949,16 @@ export const EntityCollectionView = React.memo(
908
949
  size={cardSize}
909
950
  emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
910
951
  ? <div className="flex flex-col items-center justify-center">
911
- <Typography variant={"subtitle2"}>So empty...</Typography>
952
+ <Typography variant={"subtitle2"}>{t("so_empty")}</Typography>
912
953
  <Button
913
954
  onClick={onNewClick}
914
955
  className="mt-4"
915
956
  >
916
957
  <AddIcon />
917
- Create your first entry
958
+ {t("create_your_first_entry")}
918
959
  </Button>
919
960
  </div>
920
- : <Typography variant={"label"}>No results with the applied filter/sort</Typography>
961
+ : <Typography variant={"label"}>{t("no_results_filter_sort")}</Typography>
921
962
  }
922
963
  />
923
964
  ) : (
@@ -946,16 +987,16 @@ export const EntityCollectionView = React.memo(
946
987
  textSearchEnabled={textSearchEnabled}
947
988
  emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
948
989
  ? <div className="flex flex-col items-center justify-center">
949
- <Typography variant={"subtitle2"}>So empty...</Typography>
990
+ <Typography variant={"subtitle2"}>{t("so_empty")}</Typography>
950
991
  <Button
951
992
  onClick={onNewClick}
952
993
  className="mt-4"
953
994
  >
954
995
  <AddIcon />
955
- Create your first entry
996
+ {t("create_your_first_entry")}
956
997
  </Button>
957
998
  </div>
958
- : <Typography variant={"label"}>No results with the applied filter/sort</Typography>
999
+ : <Typography variant={"label"}>{t("no_results_filter_sort")}</Typography>
959
1000
  }
960
1001
  hoverRow={hoverRow}
961
1002
  inlineEditing={checkInlineEditing()}
@@ -1126,11 +1167,12 @@ function EntityIdHeaderWidget({
1126
1167
  const [searchString, setSearchString] = React.useState("");
1127
1168
  const [recentIds, setRecentIds] = React.useState<string[]>(getRecentIds(collection.id));
1128
1169
  const sideEntityController = useSideEntityController();
1170
+ const { t } = useTranslation();
1129
1171
 
1130
1172
  const openEntityMode = collection?.openEntityMode ?? DEFAULT_ENTITY_OPEN_MODE;
1131
1173
 
1132
1174
  return (
1133
- <Tooltip title={!openPopup ? "Find by ID" : undefined} asChild={false}>
1175
+ <Tooltip title={!openPopup ? t("find_by_id") : undefined} asChild={false}>
1134
1176
  <Popover
1135
1177
  open={openPopup}
1136
1178
  onOpenChange={setOpenPopup}
@@ -1166,7 +1208,7 @@ function EntityIdHeaderWidget({
1166
1208
  <div className="flex p-2 w-full gap-2">
1167
1209
  <input
1168
1210
  autoFocus={openPopup}
1169
- placeholder={"Find entity by ID"}
1211
+ placeholder={t("find_entity_by_id")}
1170
1212
  // size={"small"}
1171
1213
  onChange={(e) => {
1172
1214
  setSearchString(e.target.value);
@@ -17,6 +17,7 @@ import {
17
17
  } from "@firecms/ui";
18
18
  import { toArray } from "../../util/arrays";
19
19
  import { ErrorBoundary } from "../ErrorBoundary";
20
+ import { useTranslation } from "../../hooks/useTranslation";
20
21
 
21
22
  export type EntityCollectionViewActionsProps<M extends Record<string, any>> = {
22
23
  collection: EntityCollection<M>;
@@ -45,9 +46,9 @@ export function EntityCollectionViewActions<M extends Record<string, any>>({
45
46
  }: EntityCollectionViewActionsProps<M>) {
46
47
 
47
48
  const context = useFireCMSContext();
48
-
49
49
  const customizationController = useCustomizationController();
50
50
  const plugins = customizationController.plugins ?? [];
51
+ const { t } = useTranslation();
51
52
 
52
53
  const authController = useAuthController();
53
54
 
@@ -63,7 +64,7 @@ export function EntityCollectionViewActions<M extends Record<string, any>>({
63
64
  startIcon={<AddIcon size={"small"} />}
64
65
  variant="filled"
65
66
  color="primary">
66
- Add {collection.singularName ?? collection.name}
67
+ {t("add")} {collection.singularName ?? collection.name}
67
68
  </Button>
68
69
  : <Button
69
70
  id={`add_entity_${path}`}
@@ -98,7 +99,7 @@ export function EntityCollectionViewActions<M extends Record<string, any>>({
98
99
  </IconButton>;
99
100
  multipleDeleteButton =
100
101
  <Tooltip
101
- title={multipleDeleteEnabled ? "Delete" : "You have selected at least one entity you cannot delete"}>
102
+ title={multipleDeleteEnabled ? t("delete_selected") : t("cannot_delete_selected")}>
102
103
  {button}
103
104
  </Tooltip>
104
105
  }
@@ -12,6 +12,7 @@ import { ErrorBoundary } from "../ErrorBoundary";
12
12
  import { ClearFilterSortButton } from "../ClearFilterSortButton";
13
13
  import { FiltersDialog } from "./FiltersDialog";
14
14
  import { Badge, Button, cls, FilterListIcon, IconButton, Tooltip } from "@firecms/ui";
15
+ import { useTranslation } from "../../hooks/useTranslation";
15
16
 
16
17
  export type EntityCollectionViewStartActionsProps<M extends Record<string, any>> = {
17
18
  collection: EntityCollection<M>;
@@ -42,6 +43,7 @@ export function EntityCollectionViewStartActions<M extends Record<string, any>>(
42
43
  const customizationController = useCustomizationController();
43
44
  const plugins = customizationController.plugins ?? [];
44
45
  const largeLayout = useLargeLayout();
46
+ const { t } = useTranslation();
45
47
 
46
48
  // Filters dialog state
47
49
  const [filtersDialogOpen, setFiltersDialogOpen] = useState(false);
@@ -66,7 +68,7 @@ export function EntityCollectionViewStartActions<M extends Record<string, any>>(
66
68
 
67
69
  // Filters button
68
70
  const filtersButton = resolvedProperties && tableController.setFilterValues && (
69
- <Tooltip title="Filters"
71
+ <Tooltip title={t("filters")}
70
72
  key={"filters_tooltip"}>
71
73
  <Badge
72
74
  color="primary"
@@ -80,7 +82,7 @@ export function EntityCollectionViewStartActions<M extends Record<string, any>>(
80
82
  startIcon={<FilterListIcon size="small" />}
81
83
  className={cls(activeFilterCount > 0 && "text-primary")}
82
84
  >
83
- Filters{activeFilterCount > 0 ? ` (${activeFilterCount})` : ""}
85
+ {t("filters")}{activeFilterCount > 0 ? ` (${activeFilterCount})` : ""}
84
86
  </Button>
85
87
  ) : (
86
88
  <IconButton