@jmruthers/pace-core 0.5.107 → 0.5.108

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 (120) hide show
  1. package/dist/{DataTable-H2WIR2DN.js → DataTable-WFCHVWTY.js} +2 -2
  2. package/dist/{PublicLoadingSpinner-48ewSMKK.d.ts → PublicLoadingSpinner-DgDWTFqn.d.ts} +4 -2
  3. package/dist/{chunk-EWKCROSF.js → chunk-B3QX32P5.js} +47 -8
  4. package/dist/chunk-B3QX32P5.js.map +1 -0
  5. package/dist/{chunk-5JJCXTVE.js → chunk-IMZGJ2X7.js} +105 -83
  6. package/dist/{chunk-5JJCXTVE.js.map → chunk-IMZGJ2X7.js.map} +1 -1
  7. package/dist/components.d.ts +1 -1
  8. package/dist/components.js +2 -2
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.js +2 -2
  11. package/dist/utils.js +1 -1
  12. package/docs/api/classes/ColumnFactory.md +1 -1
  13. package/docs/api/classes/ErrorBoundary.md +1 -1
  14. package/docs/api/classes/InvalidScopeError.md +1 -1
  15. package/docs/api/classes/MissingUserContextError.md +1 -1
  16. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  17. package/docs/api/classes/PermissionDeniedError.md +1 -1
  18. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  19. package/docs/api/classes/RBACAuditManager.md +1 -1
  20. package/docs/api/classes/RBACCache.md +1 -1
  21. package/docs/api/classes/RBACEngine.md +1 -1
  22. package/docs/api/classes/RBACError.md +1 -1
  23. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  24. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  25. package/docs/api/classes/StorageUtils.md +1 -1
  26. package/docs/api/enums/FileCategory.md +1 -1
  27. package/docs/api/interfaces/AggregateConfig.md +1 -1
  28. package/docs/api/interfaces/ButtonProps.md +1 -1
  29. package/docs/api/interfaces/CardProps.md +1 -1
  30. package/docs/api/interfaces/ColorPalette.md +1 -1
  31. package/docs/api/interfaces/ColorShade.md +1 -1
  32. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  33. package/docs/api/interfaces/DataRecord.md +1 -1
  34. package/docs/api/interfaces/DataTableAction.md +1 -1
  35. package/docs/api/interfaces/DataTableColumn.md +1 -1
  36. package/docs/api/interfaces/DataTableProps.md +1 -1
  37. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  38. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  39. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  40. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  41. package/docs/api/interfaces/FileMetadata.md +1 -1
  42. package/docs/api/interfaces/FileReference.md +1 -1
  43. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  44. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  45. package/docs/api/interfaces/FileUploadProps.md +1 -1
  46. package/docs/api/interfaces/FooterProps.md +1 -1
  47. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  48. package/docs/api/interfaces/InputProps.md +1 -1
  49. package/docs/api/interfaces/LabelProps.md +1 -1
  50. package/docs/api/interfaces/LoginFormProps.md +1 -1
  51. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  52. package/docs/api/interfaces/NavigationContextType.md +1 -1
  53. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  54. package/docs/api/interfaces/NavigationItem.md +1 -1
  55. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  56. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  57. package/docs/api/interfaces/Organisation.md +1 -1
  58. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  59. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  60. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  61. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  62. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  63. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  64. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  65. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  66. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  67. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  68. package/docs/api/interfaces/PaletteData.md +1 -1
  69. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  70. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  71. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  72. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  73. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  74. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  75. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  76. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  77. package/docs/api/interfaces/RBACConfig.md +1 -1
  78. package/docs/api/interfaces/RBACLogger.md +1 -1
  79. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  80. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  81. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  82. package/docs/api/interfaces/RouteConfig.md +1 -1
  83. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  84. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  85. package/docs/api/interfaces/StorageConfig.md +1 -1
  86. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  87. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  88. package/docs/api/interfaces/StorageListOptions.md +1 -1
  89. package/docs/api/interfaces/StorageListResult.md +1 -1
  90. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  91. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  92. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  93. package/docs/api/interfaces/StyleImport.md +1 -1
  94. package/docs/api/interfaces/SwitchProps.md +1 -1
  95. package/docs/api/interfaces/ToastActionElement.md +1 -1
  96. package/docs/api/interfaces/ToastProps.md +1 -1
  97. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  98. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  99. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  100. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  101. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  102. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  103. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  104. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  105. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  106. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  107. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  108. package/docs/api/interfaces/UserEventAccess.md +1 -1
  109. package/docs/api/interfaces/UserMenuProps.md +1 -1
  110. package/docs/api/interfaces/UserProfile.md +1 -1
  111. package/docs/api/modules.md +7 -5
  112. package/package.json +1 -1
  113. package/src/components/DataTable/components/ColumnFilter.tsx +2 -1
  114. package/src/components/DataTable/components/EditableRow.tsx +7 -2
  115. package/src/components/DataTable/components/FilterRow.tsx +22 -11
  116. package/src/components/DataTable/components/PaginationControls.tsx +1 -1
  117. package/src/components/DataTable/components/UnifiedTableBody.tsx +39 -10
  118. package/src/components/PaceAppLayout/PaceAppLayout.tsx +79 -10
  119. package/dist/chunk-EWKCROSF.js.map +0 -1
  120. /package/dist/{DataTable-H2WIR2DN.js.map → DataTable-WFCHVWTY.js.map} +0 -0
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / RouteConfig
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / RouteConfig
2
2
 
3
3
  # Interface: RouteConfig
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / SecureDataContextType
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / SecureDataContextType
2
2
 
3
3
  # Interface: SecureDataContextType
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / SecureDataProviderProps
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / SecureDataProviderProps
2
2
 
3
3
  # Interface: SecureDataProviderProps
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / StorageConfig
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / StorageConfig
2
2
 
3
3
  # Interface: StorageConfig
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / StorageFileInfo
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / StorageFileInfo
2
2
 
3
3
  # Interface: StorageFileInfo
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / StorageFileMetadata
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / StorageFileMetadata
2
2
 
3
3
  # Interface: StorageFileMetadata
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / StorageListOptions
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / StorageListOptions
2
2
 
3
3
  # Interface: StorageListOptions
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / StorageListResult
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / StorageListResult
2
2
 
3
3
  # Interface: StorageListResult
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / StorageUploadOptions
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / StorageUploadOptions
2
2
 
3
3
  # Interface: StorageUploadOptions
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / StorageUploadResult
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / StorageUploadResult
2
2
 
3
3
  # Interface: StorageUploadResult
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / StorageUrlOptions
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / StorageUrlOptions
2
2
 
3
3
  # Interface: StorageUrlOptions
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / StyleImport
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / StyleImport
2
2
 
3
3
  # Interface: StyleImport
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / SwitchProps
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / SwitchProps
2
2
 
3
3
  # Interface: SwitchProps
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / ToastActionElement
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / ToastActionElement
2
2
 
3
3
  # Interface: ToastActionElement
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / ToastProps
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / ToastProps
2
2
 
3
3
  # Interface: ToastProps
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / UnifiedAuthContextType
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / UnifiedAuthContextType
2
2
 
3
3
  # Interface: UnifiedAuthContextType
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / UnifiedAuthProviderProps
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / UnifiedAuthProviderProps
2
2
 
3
3
  # Interface: UnifiedAuthProviderProps
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / UseInactivityTrackerOptions
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / UseInactivityTrackerOptions
2
2
 
3
3
  # Interface: UseInactivityTrackerOptions
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / UseInactivityTrackerReturn
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / UseInactivityTrackerReturn
2
2
 
3
3
  # Interface: UseInactivityTrackerReturn
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / UsePublicEventOptions
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / UsePublicEventOptions
2
2
 
3
3
  # Interface: UsePublicEventOptions
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / UsePublicEventReturn
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / UsePublicEventReturn
2
2
 
3
3
  # Interface: UsePublicEventReturn
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / UsePublicFileDisplayOptions
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / UsePublicFileDisplayOptions
2
2
 
3
3
  # Interface: UsePublicFileDisplayOptions
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / UsePublicFileDisplayReturn
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / UsePublicFileDisplayReturn
2
2
 
3
3
  # Interface: UsePublicFileDisplayReturn
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / UsePublicRouteParamsReturn
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / UsePublicRouteParamsReturn
2
2
 
3
3
  # Interface: UsePublicRouteParamsReturn
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / UseResolvedScopeOptions
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / UseResolvedScopeOptions
2
2
 
3
3
  # Interface: UseResolvedScopeOptions
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / UseResolvedScopeReturn
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / UseResolvedScopeReturn
2
2
 
3
3
  # Interface: UseResolvedScopeReturn
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / UserEventAccess
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / UserEventAccess
2
2
 
3
3
  # Interface: UserEventAccess
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / UserMenuProps
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / UserMenuProps
2
2
 
3
3
  # Interface: UserMenuProps
4
4
 
@@ -1,4 +1,4 @@
1
- [@jmruthers/pace-core - v0.5.107](../README.md) / [Exports](../modules.md) / UserProfile
1
+ [@jmruthers/pace-core - v0.5.108](../README.md) / [Exports](../modules.md) / UserProfile
2
2
 
3
3
  # Interface: UserProfile
4
4
 
@@ -1,6 +1,6 @@
1
- [@jmruthers/pace-core - v0.5.107](README.md) / Exports
1
+ [@jmruthers/pace-core - v0.5.108](README.md) / Exports
2
2
 
3
- # @jmruthers/pace-core - v0.5.107
3
+ # @jmruthers/pace-core - v0.5.108
4
4
 
5
5
  **`File`**
6
6
 
@@ -2355,7 +2355,7 @@ function App() {
2355
2355
 
2356
2356
  **`Example`**
2357
2357
 
2358
- Custom navigation items with permission filtering:
2358
+ Custom navigation items with permission filtering (works independently of route enforcement):
2359
2359
  ```tsx
2360
2360
  import { NavigationItem } from '@jmruthers/pace-core';
2361
2361
 
@@ -2373,13 +2373,15 @@ function App() {
2373
2373
  <PaceAppLayout
2374
2374
  appName="My Custom App"
2375
2375
  navItems={customNavItems}
2376
- enforcePermissions={true}
2376
+ // Navigation filtering works independently - no need for enforcePermissions
2377
2377
  filterNavigationByPermissions={true}
2378
2378
  routePermissions={{
2379
2379
  '/components': 'read',
2380
2380
  '/styles': 'read',
2381
2381
  '/meals': 'read'
2382
2382
  }}
2383
+ // Optionally enable route-level enforcement (separate from navigation filtering)
2384
+ // enforcePermissions={true}
2383
2385
  />
2384
2386
  }>
2385
2387
  <Route path="components" element={<ComponentsPage />} />
@@ -2433,7 +2435,7 @@ function AdminApp() {
2433
2435
 
2434
2436
  #### Defined in
2435
2437
 
2436
- [packages/core/src/components/PaceAppLayout/PaceAppLayout.tsx:322](https://github.com/jmruthers/pace-core/blob/main/packages/core/src/components/PaceAppLayout/PaceAppLayout.tsx#L322)
2438
+ [packages/core/src/components/PaceAppLayout/PaceAppLayout.tsx:324](https://github.com/jmruthers/pace-core/blob/main/packages/core/src/components/PaceAppLayout/PaceAppLayout.tsx#L324)
2437
2439
 
2438
2440
  ___
2439
2441
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jmruthers/pace-core",
3
- "version": "0.5.107",
3
+ "version": "0.5.108",
4
4
  "description": "Clean, modern React component library with Tailwind v4 styling and native utilities",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -62,13 +62,14 @@ export function ColumnFilter({
62
62
  );
63
63
 
64
64
  case 'number':
65
+ // Always hide spinner arrows for number filter inputs (cleaner UX)
65
66
  return (
66
67
  <Input
67
68
  type="number"
68
69
  value={columnFilterValue as string || ''}
69
70
  onChange={(e) => handleFilterChange(e.target.value ? Number(e.target.value) : undefined)}
70
71
  placeholder={placeholder || `Filter ${column.id}...`}
71
- className="h-8"
72
+ className="h-8 datatable-number-no-spinners"
72
73
  />
73
74
  );
74
75
 
@@ -44,7 +44,10 @@ function SelectEditField<TData extends DataRecord>({
44
44
  onChange: (value: CellValue) => void;
45
45
  className?: string;
46
46
  }) {
47
- const isSearchable = columnDef.selectSearchable !== false; // Default to true for better UX
47
+ // Determine if searchable - explicitly check for true to ensure visible search input appears
48
+ // When selectSearchable is true or undefined, show the visible search input box
49
+ // When selectSearchable is false, hide the search input (type-to-search still works via SelectContent internals)
50
+ const isSearchable = columnDef.selectSearchable !== false;
48
51
  const isCreatable = columnDef.creatable === true;
49
52
  const selectRef = React.useRef<HTMLFormElement>(null);
50
53
  const [searchTerm, setSearchTerm] = React.useState('');
@@ -137,7 +140,7 @@ function SelectEditField<TData extends DataRecord>({
137
140
  <SelectValue placeholder={placeholder || `Select ${columnDef.header || 'option'}...`} />
138
141
  </SelectTrigger>
139
142
  <SelectContent
140
- searchable={isSearchable}
143
+ searchable={Boolean(isSearchable)}
141
144
  searchPlaceholder={`Search ${columnDef.header || 'options'}...`}
142
145
  maxHeight={columnDef.selectMaxHeight}
143
146
  className={columnDef.selectContentClassName}
@@ -232,6 +235,8 @@ const renderEditField = <TData extends DataRecord>(
232
235
  }
233
236
 
234
237
  if (columnDef.fieldType === 'number') {
238
+ // Hide spinner arrows by default for number, currency, and percentage fields
239
+ // Only show spinners if explicitly set to false
235
240
  const hideSpinners = columnDef.hideNumberSpinners !== false; // Default to true
236
241
  return (
237
242
  <Input
@@ -12,7 +12,7 @@ export function FilterRow<TData>({ table, visibleColumns }: FilterRowProps<TData
12
12
  const { columnFilters } = getState();
13
13
 
14
14
  // Get unique values for select filters
15
- const getColumnOptions = (columnId: string) => {
15
+ const getColumnOptions = React.useCallback((columnId: string) => {
16
16
  const column = table.getColumn(columnId);
17
17
  if (!column) return [];
18
18
 
@@ -40,44 +40,55 @@ export function FilterRow<TData>({ table, visibleColumns }: FilterRowProps<TData
40
40
  return Array.from(uniqueValues)
41
41
  .sort()
42
42
  .map((value) => ({ value, label: value }));
43
- };
43
+ }, [table]);
44
44
 
45
45
  // Determine filter type based on column data
46
- const getFilterType = (columnId: string) => {
46
+ // IMPORTANT: Explicit filterType always takes priority - auto-detection only runs if filterType is not set
47
+ const getFilterType = React.useCallback((columnId: string) => {
47
48
  const column = table.getColumn(columnId);
48
49
  if (!column) return 'text';
49
50
 
50
51
  const columnDef = column.columnDef as any;
51
52
 
52
- // Check if column has explicit filter type configuration
53
- if (columnDef.filterType) {
54
- return columnDef.filterType;
53
+ // PRIORITY 1: Check if column has explicit filter type configuration
54
+ // This MUST be checked first and must respect any explicit value, including 'text'
55
+ // Use explicit !== undefined && !== null check to ensure 'text' is not treated as falsy
56
+ // This prevents auto-detection from overriding explicit filterType settings
57
+ const explicitFilterType = columnDef.filterType;
58
+ if (explicitFilterType !== undefined && explicitFilterType !== null && explicitFilterType !== '') {
59
+ // Explicit filterType set - return it immediately (no auto-detection)
60
+ // This ensures filterType: 'text' is always respected, even for columns with ≤10 unique values
61
+ return explicitFilterType as 'text' | 'select' | 'number' | 'date';
55
62
  }
56
63
 
57
- // Auto-detect select filter if filterSelectOptions is provided
64
+ // Only proceed with auto-detection if filterType was NOT explicitly set
65
+
66
+ // PRIORITY 2: Auto-detect select filter if filterSelectOptions is explicitly provided
58
67
  if (columnDef.filterSelectOptions && Array.isArray(columnDef.filterSelectOptions)) {
59
68
  return 'select';
60
69
  }
61
70
 
62
- // Check if it's a date column
71
+ // PRIORITY 3: Check if it's a date column (by column ID pattern)
63
72
  if (columnId.toLowerCase().includes('date') || columnId.toLowerCase().includes('time')) {
64
73
  return 'date';
65
74
  }
66
75
 
67
- // Check if it's a number column
76
+ // PRIORITY 4: Check if it's a number column (by data type)
68
77
  const firstValue = table.getRowModel().rows[0]?.getValue(columnId);
69
78
  if (typeof firstValue === 'number') {
70
79
  return 'number';
71
80
  }
72
81
 
73
- // Check if it has limited unique values (good for select)
82
+ // PRIORITY 5: Auto-detect select filter if limited unique values (≤10)
83
+ // Only runs if filterType was NOT explicitly set (checked above)
74
84
  const uniqueValues = getColumnOptions(columnId);
75
85
  if (uniqueValues.length <= 10 && uniqueValues.length > 1) {
76
86
  return 'select';
77
87
  }
78
88
 
89
+ // Default to text filter
79
90
  return 'text';
80
- };
91
+ }, [table, getColumnOptions]);
81
92
 
82
93
  return (
83
94
  <tr className="border-b bg-sec-50/50">
@@ -259,7 +259,7 @@ export function EnhancedPaginationControls<TData extends DataRecord>({
259
259
  max={pageCount}
260
260
  value={jumpToPage}
261
261
  onChange={(e) => setJumpToPage(e.target.value)}
262
- className="w-16 h-6 px-2 border rounded text-xs"
262
+ className="w-16 h-6 px-2 border rounded text-xs datatable-number-no-spinners"
263
263
  placeholder="1"
264
264
  />
265
265
  <Button type="submit" size="sm" variant="outline" className="h-6 px-2 text-xs">
@@ -134,7 +134,10 @@ function SelectEditField<TData extends DataRecord>({
134
134
  placeholder?: string;
135
135
  onChange: (value: CellValue) => void;
136
136
  }) {
137
- const isSearchable = columnDef.selectSearchable !== false; // Default to true for better UX
137
+ // Determine if searchable - explicitly check for true to ensure visible search input appears
138
+ // When selectSearchable is true or undefined, show the visible search input box
139
+ // When selectSearchable is false, hide the search input (type-to-search still works via SelectContent internals)
140
+ const isSearchable = columnDef.selectSearchable !== false;
138
141
  const isCreatable = columnDef.creatable === true;
139
142
  const selectRef = React.useRef<HTMLFormElement>(null);
140
143
  const [searchTerm, setSearchTerm] = React.useState('');
@@ -227,7 +230,7 @@ function SelectEditField<TData extends DataRecord>({
227
230
  <SelectValue placeholder={placeholder || `Select ${columnDef.header || 'option'}...`} />
228
231
  </SelectTrigger>
229
232
  <SelectContent
230
- searchable={isSearchable}
233
+ searchable={Boolean(isSearchable)}
231
234
  searchPlaceholder={`Search ${columnDef.header || 'options'}...`}
232
235
  maxHeight={columnDef.selectMaxHeight}
233
236
  className={columnDef.selectContentClassName}
@@ -312,8 +315,11 @@ const renderEditField = <TData extends DataRecord>(
312
315
  );
313
316
  }
314
317
 
315
- // Check for number type
318
+ // Check for number type (applies to number, currency, and percentage fields)
316
319
  if (columnDef.fieldType === 'number') {
320
+ // Hide spinner arrows by default for all number-related fields
321
+ // Currency and percentage columns use fieldType: 'number' with formatting in cell renderer
322
+ // Only show spinners if explicitly set to false
317
323
  const hideSpinners = columnDef.hideNumberSpinners !== false; // Default to true
318
324
  return (
319
325
  <Input
@@ -879,6 +885,26 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
879
885
  }
880
886
 
881
887
  // Render edit fields for data columns
888
+ // Determine the correct key to use for creationData
889
+ // Priority: editAccessorKey > accessorKey > column.id
890
+ const columnDef = header.column.columnDef as EditableColumnDef<TData>;
891
+ const dataKey = columnDef.editAccessorKey || columnDef.accessorKey || header.column.id;
892
+
893
+ // Always render a cell to maintain alignment - renderEditField always returns something
894
+ const editField = renderEditField(
895
+ header.column,
896
+ creationData[dataKey] ?? creationData[header.column.id] ?? '',
897
+ (value) => {
898
+ if (typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date)) {
899
+ onCreationDataChange({ ...creationData, ...(value as Record<string, CellValue>) });
900
+ } else {
901
+ // Use the determined dataKey for consistent data access
902
+ onCreationDataChange({ ...creationData, [dataKey]: value as CellValue });
903
+ }
904
+ },
905
+ creationData
906
+ );
907
+
882
908
  return (
883
909
  <td
884
910
  key={header.column.id}
@@ -887,13 +913,16 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
887
913
  className: "px-3 py-2"
888
914
  })}
889
915
  >
890
- {renderEditField(header.column, creationData[header.column.id], (value) => {
891
- if (typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date)) {
892
- onCreationDataChange({ ...creationData, ...(value as Record<string, CellValue>) });
893
- } else {
894
- onCreationDataChange({ ...creationData, [header.column.id]: value as CellValue });
895
- }
896
- }, creationData)}
916
+ {editField || (
917
+ // Fallback: render a text input if renderEditField somehow returns nothing
918
+ <Input
919
+ type="text"
920
+ value={String(creationData[dataKey] ?? creationData[header.column.id] ?? '')}
921
+ onChange={(e) => onCreationDataChange({ ...creationData, [dataKey]: e.target.value as CellValue })}
922
+ placeholder={`Enter ${columnDef.header || header.column.id}...`}
923
+ className="h-8"
924
+ />
925
+ )}
897
926
  </td>
898
927
  );
899
928
  })}
@@ -243,7 +243,7 @@ export interface PaceAppLayoutProps {
243
243
  *
244
244
  *
245
245
  * @example
246
- * Custom navigation items with permission filtering:
246
+ * Custom navigation items with permission filtering (works independently of route enforcement):
247
247
  * ```tsx
248
248
  * import { NavigationItem } from '@jmruthers/pace-core';
249
249
  *
@@ -261,13 +261,15 @@ export interface PaceAppLayoutProps {
261
261
  * <PaceAppLayout
262
262
  * appName="My Custom App"
263
263
  * navItems={customNavItems}
264
- * enforcePermissions={true}
264
+ * // Navigation filtering works independently - no need for enforcePermissions
265
265
  * filterNavigationByPermissions={true}
266
266
  * routePermissions={{
267
267
  * '/components': 'read',
268
268
  * '/styles': 'read',
269
269
  * '/meals': 'read'
270
270
  * }}
271
+ * // Optionally enable route-level enforcement (separate from navigation filtering)
272
+ * // enforcePermissions={true}
271
273
  * />
272
274
  * }>
273
275
  * <Route path="components" element={<ComponentsPage />} />
@@ -367,12 +369,24 @@ export function PaceAppLayout({
367
369
 
368
370
  // Check if user is super admin first - super admins can access everything
369
371
  // regardless of organisation context
370
- const { isSuperAdmin } = await import('../../rbac/api');
371
- const isSuper = await isSuperAdmin(user.id);
372
-
373
- if (isSuper) {
374
- // Super admin bypass - allow access regardless of organisation context
375
- return true;
372
+ // Gracefully handle RBAC not being initialized (e.g., in tests)
373
+ try {
374
+ const { isSuperAdmin } = await import('../../rbac/api');
375
+ const isSuper = await isSuperAdmin(user.id);
376
+
377
+ if (isSuper) {
378
+ // Super admin bypass - allow access regardless of organisation context
379
+ return true;
380
+ }
381
+ } catch (error) {
382
+ // If RBAC is not initialized (e.g., in tests), continue with normal permission check
383
+ // This prevents errors from breaking permission checks when RBAC isn't available
384
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'RBAC_NOT_INITIALIZED') {
385
+ // RBAC not available - proceed with normal permission check
386
+ } else {
387
+ // Re-throw unexpected errors
388
+ throw error;
389
+ }
376
390
  }
377
391
 
378
392
  // For non-super admins, ensure we have at least organisationId for RBAC
@@ -498,10 +512,12 @@ export function PaceAppLayout({
498
512
  }, [enforcePermissions, currentRoutePermission, currentPageId, strictMode, user?.id]);
499
513
 
500
514
  // Filter navigation items based on permissions
515
+ // This works independently of route enforcement - navigation filtering doesn't require enforcePermissions
501
516
  const [filteredMenuItems, setFilteredMenuItems] = useState<NavigationItem[]>(baseMenuItems);
502
517
 
503
518
  useEffect(() => {
504
- if (!filterNavigationByPermissions || !enforcePermissions) {
519
+ // Allow navigation filtering without route enforcement
520
+ if (!filterNavigationByPermissions) {
505
521
  setFilteredMenuItems(baseMenuItems);
506
522
  return;
507
523
  }
@@ -509,6 +525,58 @@ export function PaceAppLayout({
509
525
  let isMounted = true;
510
526
 
511
527
  const filterItems = async () => {
528
+ // Wait for organisation context to be ready before filtering
529
+ // This prevents blocking navigation while context is loading
530
+ if (!user?.id) {
531
+ // User not loaded yet - show all items until context is ready
532
+ if (isMounted) {
533
+ setFilteredMenuItems(baseMenuItems);
534
+ }
535
+ return;
536
+ }
537
+
538
+ // Check if organisation context is available
539
+ const scope = {
540
+ organisationId: user.user_metadata?.organisationId || user.app_metadata?.organisationId,
541
+ eventId: user.user_metadata?.eventId || user.app_metadata?.eventId,
542
+ appId: user.user_metadata?.appId || user.app_metadata?.appId,
543
+ };
544
+
545
+ // For super admins, show all items (they bypass permission checks)
546
+ // Gracefully handle RBAC not being initialized (e.g., in tests)
547
+ try {
548
+ const { isSuperAdmin } = await import('../../rbac/api');
549
+ const isSuper = await isSuperAdmin(user.id);
550
+
551
+ if (isSuper) {
552
+ // Super admins see all navigation items
553
+ if (isMounted) {
554
+ setFilteredMenuItems(baseMenuItems);
555
+ }
556
+ return;
557
+ }
558
+ } catch (error) {
559
+ // If RBAC is not initialized (e.g., in tests), continue with normal filtering
560
+ // This prevents errors from breaking navigation when RBAC isn't available
561
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'RBAC_NOT_INITIALIZED') {
562
+ // RBAC not available - proceed with normal filtering without super admin check
563
+ // In this case, we'll filter items normally based on permissions
564
+ } else {
565
+ // Re-throw unexpected errors
566
+ throw error;
567
+ }
568
+ }
569
+
570
+ // If no organisation context yet, show all items until context is ready
571
+ // This prevents navigation from being empty while context loads
572
+ if (!scope.organisationId) {
573
+ if (isMounted) {
574
+ setFilteredMenuItems(baseMenuItems);
575
+ }
576
+ return;
577
+ }
578
+
579
+ // Organisation context is ready - now filter items based on permissions
512
580
  const filtered = await Promise.all(
513
581
  baseMenuItems.map(async (item) => {
514
582
  if (!item.href) return { item, hasAccess: true };
@@ -520,6 +588,7 @@ export function PaceAppLayout({
520
588
  const hasAccess = await checkPermission(permission, pageId);
521
589
  return { item, hasAccess };
522
590
  } catch {
591
+ // On error, default to hiding the item (fail-safe)
523
592
  return { item, hasAccess: false };
524
593
  }
525
594
  })
@@ -539,7 +608,7 @@ export function PaceAppLayout({
539
608
  return () => {
540
609
  isMounted = false;
541
610
  };
542
- }, [baseMenuItems, filterNavigationByPermissions, enforcePermissions, pageIdMapping, routePermissions, defaultPermission]);
611
+ }, [baseMenuItems, filterNavigationByPermissions, pageIdMapping, routePermissions, defaultPermission, checkPermission, user?.id, user?.user_metadata, user?.app_metadata]);
543
612
 
544
613
  // NEW: Phase 2 - Enhanced Routing Features
545
614
  // Check route access for role-based routing