@firecms/core 3.1.0-canary.9e89e98 → 3.1.0-canary.dc8ac43

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 (216) 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/ErrorBoundary.d.ts +4 -2
  4. package/dist/components/HomePage/DefaultHomePage.d.ts +0 -1
  5. package/dist/components/LanguageToggle.d.ts +1 -0
  6. package/dist/components/UnsavedChangesDialog.d.ts +1 -0
  7. package/dist/components/VirtualTable/VirtualTableHeader.d.ts +1 -1
  8. package/dist/components/index.d.ts +1 -0
  9. package/dist/core/DrawerNavigationGroup.d.ts +2 -2
  10. package/dist/editor/components/SlashCommandMenu.d.ts +6 -0
  11. package/dist/editor/components/editor-bubble-item.d.ts +8 -0
  12. package/dist/editor/components/editor-bubble.d.ts +8 -0
  13. package/dist/editor/components/image-bubble.d.ts +5 -0
  14. package/dist/editor/components/index.d.ts +16 -0
  15. package/dist/editor/components/table-bubble.d.ts +5 -0
  16. package/dist/editor/editor.d.ts +30 -0
  17. package/dist/editor/extensions/HighlightDecorationExtension.d.ts +24 -0
  18. package/dist/editor/extensions/Image/index.d.ts +6 -0
  19. package/dist/editor/extensions/Image.d.ts +6 -0
  20. package/dist/editor/extensions/TextLoadingDecorationExtension.d.ts +16 -0
  21. package/dist/editor/extensions/clipboard.d.ts +7 -0
  22. package/dist/editor/extensions/custom-keymap.d.ts +1 -0
  23. package/dist/editor/extensions/drag-and-drop.d.ts +9 -0
  24. package/dist/editor/hooks/useProseMirror.d.ts +13 -0
  25. package/dist/editor/hooks/useProseMirrorContext.d.ts +9 -0
  26. package/dist/editor/index.d.ts +2 -0
  27. package/dist/editor/markdown.d.ts +5 -0
  28. package/dist/editor/nodeViews/ImageComponent.d.ts +3 -0
  29. package/dist/editor/nodeViews/ReactNodeView.d.ts +29 -0
  30. package/dist/editor/nodeViews/TaskItemComponent.d.ts +3 -0
  31. package/dist/editor/nodeViews/index.d.ts +6 -0
  32. package/dist/editor/plugins/index.d.ts +2 -0
  33. package/dist/editor/plugins/inputrules.d.ts +6 -0
  34. package/dist/editor/plugins/placeholderPlugin.d.ts +3 -0
  35. package/dist/editor/plugins/slashCommandPlugin.d.ts +12 -0
  36. package/dist/editor/schema.d.ts +2 -0
  37. package/dist/editor/selectors/ai-selector.d.ts +0 -0
  38. package/dist/editor/selectors/color-selector.d.ts +10 -0
  39. package/dist/editor/selectors/link-selector.d.ts +8 -0
  40. package/dist/editor/selectors/node-selector.d.ts +15 -0
  41. package/dist/editor/selectors/text-buttons.d.ts +1 -0
  42. package/dist/editor/types.d.ts +5 -0
  43. package/dist/editor/useProseMirror.d.ts +16 -0
  44. package/dist/editor/utils/prosemirror-utils.d.ts +6 -0
  45. package/dist/editor/utils/remove_classes.d.ts +1 -0
  46. package/dist/editor/utils/useDebouncedCallback.d.ts +1 -0
  47. package/dist/form/components/ErrorFocus.d.ts +1 -1
  48. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
  49. package/dist/hooks/index.d.ts +1 -0
  50. package/dist/hooks/useBuildNavigationController.d.ts +0 -1
  51. package/dist/hooks/useCollapsedGroups.d.ts +3 -3
  52. package/dist/hooks/useTranslation.d.ts +17 -0
  53. package/dist/i18n/FireCMSi18nProvider.d.ts +33 -0
  54. package/dist/index.d.ts +4 -0
  55. package/dist/index.es.js +13009 -2312
  56. package/dist/index.es.js.map +1 -1
  57. package/dist/index.umd.js +12997 -2320
  58. package/dist/index.umd.js.map +1 -1
  59. package/dist/internal/useRestoreScroll.d.ts +1 -1
  60. package/dist/locales/de.d.ts +2 -0
  61. package/dist/locales/en.d.ts +10 -0
  62. package/dist/locales/es.d.ts +10 -0
  63. package/dist/locales/fr.d.ts +2 -0
  64. package/dist/locales/hi.d.ts +2 -0
  65. package/dist/locales/it.d.ts +2 -0
  66. package/dist/locales/pt.d.ts +7 -0
  67. package/dist/types/analytics.d.ts +1 -1
  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/storage.d.ts +1 -0
  74. package/dist/types/translations.d.ts +646 -0
  75. package/dist/util/entities.d.ts +1 -1
  76. package/dist/util/resolutions.d.ts +2 -2
  77. package/dist/util/useStorageUploadController.d.ts +10 -1
  78. package/package.json +49 -13
  79. package/src/app/Scaffold.tsx +7 -5
  80. package/src/components/AIIcon.tsx +3 -1
  81. package/src/components/ArrayContainer.tsx +6 -4
  82. package/src/components/ClearFilterSortButton.tsx +6 -3
  83. package/src/components/ConfirmationDialog.tsx +4 -2
  84. package/src/components/DeleteEntityDialog.tsx +10 -7
  85. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +6 -3
  86. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +3 -1
  87. package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +1 -1
  88. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +3 -2
  89. package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +11 -11
  90. package/src/components/EntityCollectionView/BoardSortableList.tsx +3 -1
  91. package/src/components/EntityCollectionView/CollectionDataErrorBanner.tsx +43 -0
  92. package/src/components/EntityCollectionView/EntityBoardCard.tsx +1 -1
  93. package/src/components/EntityCollectionView/EntityCard.tsx +4 -0
  94. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +39 -46
  95. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +17 -25
  96. package/src/components/EntityCollectionView/EntityCollectionView.tsx +54 -17
  97. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +4 -3
  98. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +4 -2
  99. package/src/components/EntityCollectionView/FiltersDialog.tsx +8 -5
  100. package/src/components/EntityCollectionView/ViewModeToggle.tsx +11 -8
  101. package/src/components/EntityView.tsx +3 -2
  102. package/src/components/ErrorBoundary.tsx +27 -15
  103. package/src/components/HomePage/DefaultHomePage.tsx +19 -13
  104. package/src/components/HomePage/HomePageDnD.tsx +3 -1
  105. package/src/components/HomePage/NavigationGroup.tsx +3 -1
  106. package/src/components/HomePage/RenameGroupDialog.tsx +15 -13
  107. package/src/components/LanguageToggle.tsx +66 -0
  108. package/src/components/NotFoundPage.tsx +5 -3
  109. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +9 -7
  110. package/src/components/ReferenceWidget.tsx +3 -2
  111. package/src/components/SearchIconsView.tsx +3 -1
  112. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +11 -0
  113. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +15 -2
  114. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +11 -0
  115. package/src/components/UnsavedChangesDialog.tsx +6 -4
  116. package/src/components/VirtualTable/VirtualTable.performance.test.tsx +1 -0
  117. package/src/components/VirtualTable/VirtualTable.tsx +116 -113
  118. package/src/components/VirtualTable/VirtualTableHeader.tsx +54 -52
  119. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +1 -1
  120. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +3 -3
  121. package/src/components/common/default_entity_actions.tsx +4 -0
  122. package/src/components/common/useDataSourceTableController.tsx +12 -4
  123. package/src/components/index.tsx +1 -0
  124. package/src/core/DefaultAppBar.tsx +15 -11
  125. package/src/core/DefaultDrawer.tsx +8 -2
  126. package/src/core/DrawerNavigationGroup.tsx +5 -3
  127. package/src/core/EntityEditView.tsx +4 -3
  128. package/src/core/EntityEditViewFormActions.tsx +24 -17
  129. package/src/core/EntitySidePanel.tsx +32 -29
  130. package/src/core/FireCMS.tsx +33 -6
  131. package/src/core/field_configs.tsx +14 -9
  132. package/src/editor/components/SlashCommandMenu.tsx +516 -0
  133. package/src/editor/components/editor-bubble-item.tsx +32 -0
  134. package/src/editor/components/editor-bubble.tsx +118 -0
  135. package/src/editor/components/image-bubble.tsx +156 -0
  136. package/src/editor/components/index.ts +14 -0
  137. package/src/editor/components/table-bubble.tsx +165 -0
  138. package/src/editor/editor.tsx +455 -0
  139. package/src/editor/extensions/HighlightDecorationExtension.ts +114 -0
  140. package/src/editor/extensions/Image/index.ts +133 -0
  141. package/src/editor/extensions/Image.ts +159 -0
  142. package/src/editor/extensions/TextLoadingDecorationExtension.tsx +107 -0
  143. package/src/editor/extensions/clipboard.ts +72 -0
  144. package/src/editor/extensions/custom-keymap.ts +24 -0
  145. package/src/editor/extensions/drag-and-drop.tsx +480 -0
  146. package/src/editor/hooks/useProseMirror.ts +124 -0
  147. package/src/editor/hooks/useProseMirrorContext.ts +15 -0
  148. package/src/editor/index.ts +2 -0
  149. package/src/editor/markdown.ts +172 -0
  150. package/src/editor/nodeViews/ImageComponent.tsx +20 -0
  151. package/src/editor/nodeViews/ReactNodeView.tsx +89 -0
  152. package/src/editor/nodeViews/TaskItemComponent.tsx +29 -0
  153. package/src/editor/nodeViews/index.ts +35 -0
  154. package/src/editor/plugins/index.ts +58 -0
  155. package/src/editor/plugins/inputrules.ts +82 -0
  156. package/src/editor/plugins/placeholderPlugin.ts +55 -0
  157. package/src/editor/plugins/slashCommandPlugin.ts +61 -0
  158. package/src/editor/schema.ts +240 -0
  159. package/src/editor/selectors/ai-selector.tsx +111 -0
  160. package/src/editor/selectors/color-selector.tsx +200 -0
  161. package/src/editor/selectors/link-selector.tsx +118 -0
  162. package/src/editor/selectors/node-selector.tsx +157 -0
  163. package/src/editor/selectors/text-buttons.tsx +86 -0
  164. package/src/editor/types.ts +6 -0
  165. package/src/editor/useProseMirror.ts +126 -0
  166. package/src/editor/utils/prosemirror-utils.ts +108 -0
  167. package/src/editor/utils/remove_classes.ts +17 -0
  168. package/src/editor/utils/useDebouncedCallback.ts +25 -0
  169. package/src/form/EntityForm.tsx +85 -63
  170. package/src/form/EntityFormActions.tsx +19 -12
  171. package/src/form/PropertyFieldBinding.tsx +6 -5
  172. package/src/form/components/ErrorFocus.tsx +3 -3
  173. package/src/form/components/LocalChangesMenu.tsx +13 -13
  174. package/src/form/components/StorageItemPreview.tsx +3 -2
  175. package/src/form/components/StorageUploadProgress.tsx +18 -3
  176. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +4 -4
  177. package/src/form/field_bindings/BlockFieldBinding.tsx +5 -2
  178. package/src/form/field_bindings/KeyValueFieldBinding.tsx +23 -18
  179. package/src/form/field_bindings/MapFieldBinding.tsx +4 -3
  180. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +34 -20
  181. package/src/form/field_bindings/RepeatFieldBinding.tsx +3 -1
  182. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +87 -86
  183. package/src/hooks/index.tsx +1 -0
  184. package/src/hooks/useBuildNavigationController.tsx +49 -22
  185. package/src/hooks/useCollapsedGroups.ts +7 -6
  186. package/src/hooks/useTranslation.ts +31 -0
  187. package/src/hooks/useValidateAuthenticator.tsx +1 -1
  188. package/src/i18n/FireCMSi18nProvider.tsx +160 -0
  189. package/src/index.ts +4 -0
  190. package/src/internal/useBuildDataSource.ts +1 -2
  191. package/src/internal/useBuildSideEntityController.tsx +22 -20
  192. package/src/locales/de.ts +691 -0
  193. package/src/locales/en.ts +703 -0
  194. package/src/locales/es.ts +703 -0
  195. package/src/locales/fr.ts +691 -0
  196. package/src/locales/hi.ts +691 -0
  197. package/src/locales/it.ts +691 -0
  198. package/src/locales/pt.ts +700 -0
  199. package/src/preview/PropertyPreview.tsx +1 -0
  200. package/src/preview/components/UrlComponentPreview.tsx +4 -2
  201. package/src/preview/components/UserPreview.tsx +3 -1
  202. package/src/types/analytics.ts +10 -0
  203. package/src/types/customization_controller.tsx +2 -1
  204. package/src/types/firecms.tsx +2 -1
  205. package/src/types/index.ts +1 -0
  206. package/src/types/navigation.ts +2 -2
  207. package/src/types/plugins.tsx +26 -0
  208. package/src/types/properties.ts +1 -0
  209. package/src/types/storage.ts +2 -1
  210. package/src/types/translations.ts +725 -0
  211. package/src/util/entities.ts +1 -1
  212. package/src/util/join_collections.ts +10 -8
  213. package/src/util/previews.ts +2 -2
  214. package/src/util/property_utils.tsx +1 -1
  215. package/src/util/resolutions.ts +5 -3
  216. package/src/util/useStorageUploadController.tsx +23 -29
@@ -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
 
@@ -568,12 +576,16 @@ export const EntityCollectionView = React.memo(
568
576
 
569
577
  // Handle kanban property change
570
578
  const onKanbanPropertyChange = useCallback((property: string) => {
579
+ analyticsController.onAnalyticsEvent?.("kanban_property_changed", {
580
+ path: fullPath,
581
+ property
582
+ });
571
583
  setSelectedKanbanProperty(property);
572
584
  // Save to local persistence
573
585
  if (userConfigPersistence) {
574
586
  onCollectionModifiedForUser(fullPath, { kanbanColumnProperty: property } as any);
575
587
  }
576
- }, [userConfigPersistence, onCollectionModifiedForUser, fullPath]);
588
+ }, [userConfigPersistence, onCollectionModifiedForUser, fullPath, analyticsController]);
577
589
 
578
590
  const getPropertyFor = useCallback(({
579
591
  propertyKey,
@@ -686,11 +698,13 @@ export const EntityCollectionView = React.memo(
686
698
  customEntityActions?: EntityAction[]
687
699
  }): EntityAction[] => {
688
700
  const deleteEnabled = entity ? canDeleteEntity(collection, authController, fullPath, entity) : true;
689
- const actions: EntityAction[] = [editEntityAction];
701
+ const actions: EntityAction[] = [
702
+ { ...editEntityAction, name: t("edit") }
703
+ ];
690
704
  if (createEnabled)
691
- actions.push(copyEntityAction);
705
+ actions.push({ ...copyEntityAction, name: t("copy") });
692
706
  if (deleteEnabled)
693
- actions.push(deleteEntityAction);
707
+ actions.push({ ...deleteEntityAction, name: t("delete") });
694
708
  if (customEntityActions)
695
709
  return mergeEntityActions(actions, customEntityActions);
696
710
  return actions;
@@ -836,6 +850,24 @@ export const EntityCollectionView = React.memo(
836
850
  />
837
851
  );
838
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
+
839
871
  return (
840
872
  <div className={cls("overflow-hidden h-full w-full rounded-md flex flex-col", className)}
841
873
  ref={containerRef}>
@@ -872,6 +904,10 @@ export const EntityCollectionView = React.memo(
872
904
  />
873
905
 
874
906
  {/* View content - only the view-specific content changes */}
907
+ {tableController.dataLoadingError && pluginErrorView}
908
+ {tableController.dataLoadingError && !pluginErrorView && (
909
+ <CollectionDataErrorBanner error={tableController.dataLoadingError} />
910
+ )}
875
911
  {viewMode === "kanban" && enabledViews.includes("kanban") ? (
876
912
  <EntityCollectionBoardView
877
913
  key={`kanban-view-${fullPath}-${selectedKanbanProperty}`}
@@ -887,16 +923,16 @@ export const EntityCollectionView = React.memo(
887
923
  deletedEntities={deletedEntities}
888
924
  emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
889
925
  ? <div className="flex flex-col items-center justify-center">
890
- <Typography variant={"subtitle2"}>So empty...</Typography>
926
+ <Typography variant={"subtitle2"}>{t("so_empty")}</Typography>
891
927
  <Button
892
928
  onClick={onNewClick}
893
929
  className="mt-4"
894
930
  >
895
931
  <AddIcon />
896
- Create your first entry
932
+ {t("create_your_first_entry")}
897
933
  </Button>
898
934
  </div>
899
- : <Typography variant={"label"}>No results with the applied filter/sort</Typography>
935
+ : <Typography variant={"label"}>{t("no_results_filter_sort")}</Typography>
900
936
  }
901
937
  />
902
938
  ) : viewMode === "cards" ? (
@@ -913,16 +949,16 @@ export const EntityCollectionView = React.memo(
913
949
  size={cardSize}
914
950
  emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
915
951
  ? <div className="flex flex-col items-center justify-center">
916
- <Typography variant={"subtitle2"}>So empty...</Typography>
952
+ <Typography variant={"subtitle2"}>{t("so_empty")}</Typography>
917
953
  <Button
918
954
  onClick={onNewClick}
919
955
  className="mt-4"
920
956
  >
921
957
  <AddIcon />
922
- Create your first entry
958
+ {t("create_your_first_entry")}
923
959
  </Button>
924
960
  </div>
925
- : <Typography variant={"label"}>No results with the applied filter/sort</Typography>
961
+ : <Typography variant={"label"}>{t("no_results_filter_sort")}</Typography>
926
962
  }
927
963
  />
928
964
  ) : (
@@ -951,16 +987,16 @@ export const EntityCollectionView = React.memo(
951
987
  textSearchEnabled={textSearchEnabled}
952
988
  emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
953
989
  ? <div className="flex flex-col items-center justify-center">
954
- <Typography variant={"subtitle2"}>So empty...</Typography>
990
+ <Typography variant={"subtitle2"}>{t("so_empty")}</Typography>
955
991
  <Button
956
992
  onClick={onNewClick}
957
993
  className="mt-4"
958
994
  >
959
995
  <AddIcon />
960
- Create your first entry
996
+ {t("create_your_first_entry")}
961
997
  </Button>
962
998
  </div>
963
- : <Typography variant={"label"}>No results with the applied filter/sort</Typography>
999
+ : <Typography variant={"label"}>{t("no_results_filter_sort")}</Typography>
964
1000
  }
965
1001
  hoverRow={hoverRow}
966
1002
  inlineEditing={checkInlineEditing()}
@@ -1131,11 +1167,12 @@ function EntityIdHeaderWidget({
1131
1167
  const [searchString, setSearchString] = React.useState("");
1132
1168
  const [recentIds, setRecentIds] = React.useState<string[]>(getRecentIds(collection.id));
1133
1169
  const sideEntityController = useSideEntityController();
1170
+ const { t } = useTranslation();
1134
1171
 
1135
1172
  const openEntityMode = collection?.openEntityMode ?? DEFAULT_ENTITY_OPEN_MODE;
1136
1173
 
1137
1174
  return (
1138
- <Tooltip title={!openPopup ? "Find by ID" : undefined} asChild={false}>
1175
+ <Tooltip title={!openPopup ? t("find_by_id") : undefined} asChild={false}>
1139
1176
  <Popover
1140
1177
  open={openPopup}
1141
1178
  onOpenChange={setOpenPopup}
@@ -1171,7 +1208,7 @@ function EntityIdHeaderWidget({
1171
1208
  <div className="flex p-2 w-full gap-2">
1172
1209
  <input
1173
1210
  autoFocus={openPopup}
1174
- placeholder={"Find entity by ID"}
1211
+ placeholder={t("find_entity_by_id")}
1175
1212
  // size={"small"}
1176
1213
  onChange={(e) => {
1177
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
@@ -15,6 +15,7 @@ import {
15
15
  FilterListIcon,
16
16
  Typography
17
17
  } from "@firecms/ui";
18
+ import { useTranslation } from "../../hooks/useTranslation";
18
19
  import { StringNumberFilterField } from "../SelectableTable/filters/StringNumberFilterField";
19
20
  import { BooleanFilterField } from "../SelectableTable/filters/BooleanFilterField";
20
21
  import { DateTimeFilterField } from "../SelectableTable/filters/DateTimeFilterField";
@@ -43,6 +44,8 @@ export function FiltersDialog({
43
44
  setFilterValues,
44
45
  forceFilter
45
46
  }: FiltersDialogProps) {
47
+ const { t } = useTranslation();
48
+
46
49
  // Local state for filters being edited
47
50
  const [localFilters, setLocalFilters] = useState<FilterValues<any>>(filterValues ?? {});
48
51
 
@@ -173,7 +176,7 @@ export function FiltersDialog({
173
176
  containerClassName={isAnyFieldHidden ? "hidden" : undefined}
174
177
  >
175
178
  <DialogTitle className="flex items-center gap-2">
176
- <Typography variant="h6">Filters</Typography>
179
+ <Typography variant="h6">{t("filters")}</Typography>
177
180
  {activeFilterCount > 0 && (
178
181
  <span className="ml-2 px-2 py-0.5 text-xs rounded-full bg-primary text-white">
179
182
  {activeFilterCount}
@@ -184,7 +187,7 @@ export function FiltersDialog({
184
187
  <DialogContent >
185
188
  {filterableProperties.length === 0 ? (
186
189
  <Typography color="secondary" className="py-8 text-center">
187
- No filterable properties available
190
+ {t("no_filterable_properties")}
188
191
  </Typography>
189
192
  ) : (
190
193
  <table className="w-full border-collapse">
@@ -228,20 +231,20 @@ export function FiltersDialog({
228
231
  onClick={handleClearAll}
229
232
  disabled={activeFilterCount === 0}
230
233
  >
231
- Clear all
234
+ {t("clear")}
232
235
  </Button>
233
236
  <div className="flex-grow" />
234
237
  <Button
235
238
  variant="text"
236
239
  onClick={() => onOpenChange(false)}
237
240
  >
238
- Cancel
241
+ {t("cancel")}
239
242
  </Button>
240
243
  <Button
241
244
  variant="filled"
242
245
  onClick={handleApply}
243
246
  >
244
- Apply filters
247
+ {t("apply_filters")}
245
248
  </Button>
246
249
  </DialogActions>
247
250
  </Dialog>