@jmruthers/pace-core 0.5.101 → 0.5.103

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 (152) hide show
  1. package/dist/{DataTable-DXELRJIX.js → DataTable-EEDFYMJP.js} +2 -2
  2. package/dist/{PublicLoadingSpinner-C2h8zg67.d.ts → PublicLoadingSpinner-48ewSMKK.d.ts} +22 -150
  3. package/dist/{chunk-2ZYHCFUO.js → chunk-5SGBVBRU.js} +2 -2
  4. package/dist/{chunk-EVVRUGQ2.js → chunk-62AVH7CM.js} +78 -55
  5. package/dist/{chunk-EVVRUGQ2.js.map → chunk-62AVH7CM.js.map} +1 -1
  6. package/dist/{chunk-A5DFMP3O.js → chunk-SZWCMVTQ.js} +135 -669
  7. package/dist/chunk-SZWCMVTQ.js.map +1 -0
  8. package/dist/{chunk-MKMKUCPF.js → chunk-X33A4WWI.js} +42 -141
  9. package/dist/chunk-X33A4WWI.js.map +1 -0
  10. package/dist/components.d.ts +1 -1
  11. package/dist/components.js +3 -15
  12. package/dist/components.js.map +1 -1
  13. package/dist/hooks.d.ts +1 -1
  14. package/dist/hooks.js +2 -9
  15. package/dist/hooks.js.map +1 -1
  16. package/dist/index.d.ts +3 -2
  17. package/dist/index.js +4 -22
  18. package/dist/index.js.map +1 -1
  19. package/dist/types.js +3 -3
  20. package/dist/{usePublicRouteParams-BwMR2uub.d.ts → usePublicRouteParams-BiXgKiYa.d.ts} +1 -117
  21. package/dist/utils.js +1 -1
  22. package/docs/api/classes/ColumnFactory.md +1 -1
  23. package/docs/api/classes/ErrorBoundary.md +1 -1
  24. package/docs/api/classes/InvalidScopeError.md +1 -1
  25. package/docs/api/classes/MissingUserContextError.md +1 -1
  26. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  27. package/docs/api/classes/PermissionDeniedError.md +1 -1
  28. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  29. package/docs/api/classes/RBACAuditManager.md +1 -1
  30. package/docs/api/classes/RBACCache.md +1 -1
  31. package/docs/api/classes/RBACEngine.md +1 -1
  32. package/docs/api/classes/RBACError.md +1 -1
  33. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  34. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  35. package/docs/api/classes/StorageUtils.md +2 -1
  36. package/docs/api/enums/FileCategory.md +1 -1
  37. package/docs/api/interfaces/AggregateConfig.md +1 -1
  38. package/docs/api/interfaces/ButtonProps.md +1 -1
  39. package/docs/api/interfaces/CardProps.md +1 -1
  40. package/docs/api/interfaces/ColorPalette.md +1 -1
  41. package/docs/api/interfaces/ColorShade.md +1 -1
  42. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  43. package/docs/api/interfaces/DataRecord.md +1 -1
  44. package/docs/api/interfaces/DataTableAction.md +1 -1
  45. package/docs/api/interfaces/DataTableColumn.md +1 -1
  46. package/docs/api/interfaces/DataTableProps.md +1 -1
  47. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  48. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  49. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  50. package/docs/api/interfaces/FileDisplayProps.md +77 -35
  51. package/docs/api/interfaces/FileMetadata.md +1 -1
  52. package/docs/api/interfaces/FileReference.md +1 -1
  53. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  54. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  55. package/docs/api/interfaces/FileUploadProps.md +1 -1
  56. package/docs/api/interfaces/FooterProps.md +1 -1
  57. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  58. package/docs/api/interfaces/InputProps.md +1 -1
  59. package/docs/api/interfaces/LabelProps.md +1 -1
  60. package/docs/api/interfaces/LoginFormProps.md +1 -1
  61. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  62. package/docs/api/interfaces/NavigationContextType.md +1 -1
  63. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  64. package/docs/api/interfaces/NavigationItem.md +1 -1
  65. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  66. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  67. package/docs/api/interfaces/Organisation.md +1 -1
  68. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  69. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  70. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  71. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  72. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  73. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  74. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  75. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  76. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  77. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  78. package/docs/api/interfaces/PaletteData.md +1 -1
  79. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  80. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  81. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  82. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  83. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  84. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  85. package/docs/api/interfaces/PublicPageHeaderProps.md +11 -24
  86. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  87. package/docs/api/interfaces/RBACConfig.md +1 -1
  88. package/docs/api/interfaces/RBACLogger.md +1 -1
  89. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  90. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  91. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  92. package/docs/api/interfaces/RouteConfig.md +1 -1
  93. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  94. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  95. package/docs/api/interfaces/StorageConfig.md +1 -1
  96. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  97. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  98. package/docs/api/interfaces/StorageListOptions.md +1 -1
  99. package/docs/api/interfaces/StorageListResult.md +1 -1
  100. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  101. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  102. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  103. package/docs/api/interfaces/StyleImport.md +1 -1
  104. package/docs/api/interfaces/SwitchProps.md +1 -1
  105. package/docs/api/interfaces/ToastActionElement.md +1 -1
  106. package/docs/api/interfaces/ToastProps.md +1 -1
  107. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  108. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  109. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  110. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  111. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  112. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  113. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  114. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  115. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  116. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  117. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  118. package/docs/api/interfaces/UserEventAccess.md +1 -1
  119. package/docs/api/interfaces/UserMenuProps.md +1 -1
  120. package/docs/api/interfaces/UserProfile.md +1 -1
  121. package/docs/api/modules.md +29 -244
  122. package/docs/implementation-guides/file-reference-system.md +84 -21
  123. package/package.json +1 -1
  124. package/src/components/DataTable/components/DataTableCore.tsx +23 -13
  125. package/src/components/DataTable/hooks/useTableColumns.ts +36 -6
  126. package/src/components/FileDisplay/FileDisplay.test.tsx +1 -1
  127. package/src/components/FileDisplay/FileDisplay.tsx +189 -300
  128. package/src/components/PublicLayout/PublicPageHeader.tsx +15 -10
  129. package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +25 -35
  130. package/src/components/PublicLayout/index.ts +2 -5
  131. package/src/components/Toast/Toast.tsx +1 -1
  132. package/src/components/index.ts +0 -2
  133. package/src/examples/PublicEventPage.tsx +17 -7
  134. package/src/examples/PublicPageApp.tsx +18 -8
  135. package/src/hooks/public/index.ts +2 -4
  136. package/src/hooks/useFileReference.ts +10 -1
  137. package/src/index.ts +0 -2
  138. package/src/utils/file-reference.ts +54 -9
  139. package/src/utils/storage/README.md +22 -20
  140. package/src/utils/storage/helpers.ts +12 -1
  141. package/dist/chunk-A5DFMP3O.js.map +0 -1
  142. package/dist/chunk-MKMKUCPF.js.map +0 -1
  143. package/docs/api/interfaces/EventLogoProps.md +0 -152
  144. package/docs/api/interfaces/UseEventLogoOptions.md +0 -74
  145. package/docs/api/interfaces/UseEventLogoReturn.md +0 -81
  146. package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
  147. package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -81
  148. package/src/components/PublicLayout/EventLogo.tsx +0 -474
  149. package/src/hooks/public/usePublicEventLogo.ts +0 -295
  150. package/src/hooks/useEventLogo.ts +0 -316
  151. /package/dist/{DataTable-DXELRJIX.js.map → DataTable-EEDFYMJP.js.map} +0 -0
  152. /package/dist/{chunk-2ZYHCFUO.js.map → chunk-5SGBVBRU.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
- lastUpdated: 2025-10-29T22:43:00+11:00
3
- version: 0.5.76
2
+ lastUpdated: 2025-11-01T14:58:00+11:00
3
+ version: 0.5.102
4
4
  reviewedBy: content-audit
5
5
  ---
6
6
 
@@ -30,7 +30,7 @@ CREATE TABLE file_references (
30
30
  table_name TEXT NOT NULL, -- Source table (e.g., 'pace_person')
31
31
  record_id TEXT NOT NULL, -- Source record ID
32
32
  file_path TEXT NOT NULL, -- Storage path
33
- file_metadata JSONB DEFAULT '{}', -- File metadata
33
+ file_metadata JSONB DEFAULT '{}', -- File metadata (includes category, fileName, fileType, etc.)
34
34
  organisation_id UUID NOT NULL, -- For RLS
35
35
  app_id UUID NOT NULL, -- Application context
36
36
  is_public BOOLEAN DEFAULT false, -- Public vs private
@@ -39,6 +39,8 @@ CREATE TABLE file_references (
39
39
  );
40
40
  ```
41
41
 
42
+ **Important:** The `category` is stored within the `file_metadata` JSONB field as `file_metadata->>'category'`, **not** as a direct column. Category filtering is performed via RPC functions (`data_file_reference_by_category_list`) that properly query the JSONB field. Direct queries filtering on a non-existent `category` column will fail.
43
+
42
44
  ### Storage Structure
43
45
 
44
46
  **Organisation-First Structure:**
@@ -222,7 +224,6 @@ import { FileUpload, FileCategory } from '@jmruthers/pace-core';
222
224
  import { FileDisplay, FileCategory } from '@jmruthers/pace-core';
223
225
 
224
226
  <FileDisplay
225
- supabase={supabase}
226
227
  table_name="pace_person"
227
228
  record_id={personId}
228
229
  organisation_id={orgId}
@@ -244,7 +245,6 @@ import { FileDisplay, FileCategory } from '@jmruthers/pace-core';
244
245
  ```tsx
245
246
  // Display just the first image without category filtering or metadata
246
247
  <FileDisplay
247
- supabase={supabase}
248
248
  table_name="event"
249
249
  record_id={eventId}
250
250
  organisation_id={orgId}
@@ -252,6 +252,59 @@ import { FileDisplay, FileCategory } from '@jmruthers/pace-core';
252
252
  />
253
253
  // When displayOnly is true, shows first file (prefers images) in simplified format
254
254
  // without wrappers, borders, or metadata. Perfect for clean logo/image displays.
255
+ // FileDisplay automatically detects context (public or authenticated) and uses the appropriate Supabase client.
256
+ ```
257
+
258
+ **With Fallback Display:**
259
+ ```tsx
260
+ // Show fallback UI when no file is available or image fails to load
261
+ <FileDisplay
262
+ table_name="pace_person"
263
+ record_id={personId}
264
+ organisation_id={orgId}
265
+ category={FileCategory.PROFILE_PHOTOS}
266
+ displayOnly={true}
267
+ showFallback={true}
268
+ fallbackSize="lg"
269
+ generateFallbackText={(fileName) => {
270
+ // Custom fallback text generator - extracts initials from file name
271
+ if (!fileName) return 'FL';
272
+ return fileName.split(/[\s\-_]+/)
273
+ .map(word => word.charAt(0).toUpperCase())
274
+ .join('')
275
+ .substring(0, 3);
276
+ }}
277
+ />
278
+ // When showFallback is true, displays a styled fallback box with initials or custom text
279
+ // when no file is found, an error occurs, or an image fails to load.
280
+ // FileDisplay automatically detects context (public or authenticated) and uses the appropriate Supabase client.
281
+ ```
282
+
283
+ **Event Logo Example:**
284
+
285
+ For displaying event logos, use `FileDisplay` directly with appropriate props:
286
+
287
+ ```tsx
288
+ import { FileDisplay, FileCategory } from '@jmruthers/pace-core';
289
+
290
+ <FileDisplay
291
+ table_name="event"
292
+ record_id={event.id}
293
+ organisation_id={event.organisation_id}
294
+ category={FileCategory.EVENT_LOGOS}
295
+ displayOnly={true}
296
+ showFallback={true}
297
+ fallbackSize="lg"
298
+ generateFallbackText={(fileName) => {
299
+ // Custom fallback generator from event name
300
+ if (!event.event_name) return 'EV';
301
+ return event.event_name
302
+ .split(/[\s\-_]+/)
303
+ .map(word => word.charAt(0).toUpperCase())
304
+ .join('')
305
+ .substring(0, 3);
306
+ }}
307
+ />
255
308
  ```
256
309
 
257
310
  ### React Hooks
@@ -346,25 +399,33 @@ fileReferences.forEach(fileRef => {
346
399
  });
347
400
  ```
348
401
 
349
- #### useEventLogo Hook
402
+ **Note:** Category filtering uses the RPC function `data_file_reference_by_category_list`, which filters on `file_metadata->>'category'` (the JSONB field), not a direct column. This hook automatically handles the filtering correctly.
350
403
 
351
- ```tsx
352
- import { useEventLogo } from '@jmruthers/pace-core';
404
+ #### Event Logo Display
353
405
 
354
- const { logoUrl, fallbackText, isLoading, error, refetch } = useEventLogo(
355
- supabase,
356
- eventId,
357
- eventName,
358
- organisationId,
359
- {
360
- validateImage: true,
361
- enableCache: true,
362
- cacheTtl: 30 * 60 * 1000 // 30 minutes
363
- }
364
- );
406
+ Event logos can be displayed using `FileDisplay` with category filtering:
365
407
 
366
- // Automatically handles both public and private event logos
367
- // Falls back to event initials if no logo is found
408
+ ```tsx
409
+ import { FileDisplay, FileCategory } from '@jmruthers/pace-core';
410
+
411
+ // Display event logo with fallback
412
+ <FileDisplay
413
+ table_name="event"
414
+ record_id={eventId}
415
+ organisation_id={organisationId}
416
+ category={FileCategory.EVENT_LOGOS}
417
+ displayOnly={true}
418
+ showFallback={true}
419
+ fallbackSize="lg"
420
+ generateFallbackText={(fileName) => {
421
+ if (!eventName) return 'EV';
422
+ return eventName
423
+ .split(/[\s\-_]+/)
424
+ .map(word => word.charAt(0).toUpperCase())
425
+ .join('')
426
+ .substring(0, 3);
427
+ }}
428
+ />
368
429
  ```
369
430
 
370
431
  #### useFileReferenceForRecord Hook
@@ -449,6 +510,8 @@ SELECT data_file_reference_get(
449
510
  );
450
511
 
451
512
  -- Get files by category
513
+ -- NOTE: Category filtering uses the file_metadata JSONB field (file_metadata->>'category'),
514
+ -- not a direct column. This RPC function properly filters on the JSONB field.
452
515
  SELECT data_file_reference_by_category_list(
453
516
  p_table_name := 'event',
454
517
  p_record_id := 'event-uuid',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jmruthers/pace-core",
3
- "version": "0.5.101",
3
+ "version": "0.5.103",
4
4
  "description": "Clean, modern React component library with Tailwind v4 styling and native utilities",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -1147,15 +1147,19 @@ function DataTableInternal<TData extends DataRecord>({
1147
1147
 
1148
1148
  {/* Table header */}
1149
1149
  <thead>
1150
- {table?.getHeaderGroups().map((headerGroup) => (
1151
- <tr key={headerGroup.id}>
1152
- {headerGroup.headers
1153
- .filter(header => {
1154
- return typeof header.column.getIsVisible === 'function'
1155
- ? header.column.getIsVisible()
1156
- : true;
1157
- })
1158
- .map((header) => {
1150
+ {table?.getHeaderGroups().map((headerGroup) => {
1151
+ // Filter visible headers once to determine first and last
1152
+ const visibleHeaders = headerGroup.headers.filter(header => {
1153
+ return typeof header.column.getIsVisible === 'function'
1154
+ ? header.column.getIsVisible()
1155
+ : true;
1156
+ });
1157
+
1158
+ return (
1159
+ <tr key={headerGroup.id}>
1160
+ {visibleHeaders.map((header, index) => {
1161
+ const isFirst = index === 0;
1162
+ const isLast = index === visibleHeaders.length - 1;
1159
1163
  const isSortable = header.column.getCanSort();
1160
1164
  const ariaSort = isSortable
1161
1165
  ? (header.column.getIsSorted() === 'asc'
@@ -1205,7 +1209,12 @@ function DataTableInternal<TData extends DataRecord>({
1205
1209
  return (
1206
1210
  <th
1207
1211
  key={header.id}
1208
- className={`px-3 py-2 ${isRightAligned ? 'text-right' : 'text-left'}`}
1212
+ className={cn(
1213
+ 'px-3 py-2 bg-main-200',
1214
+ isRightAligned ? 'text-right' : 'text-left',
1215
+ isFirst && 'rounded-l-md',
1216
+ isLast && 'rounded-r-md'
1217
+ )}
1209
1218
  scope="col"
1210
1219
  role="columnheader"
1211
1220
  {...(isSortable ? { 'aria-sort': ariaSort } : {})}
@@ -1215,7 +1224,7 @@ function DataTableInternal<TData extends DataRecord>({
1215
1224
  isSortable ? (
1216
1225
  <Button
1217
1226
  variant="ghost"
1218
- className={`h-auto p-0 font-medium hover:bg-transparent ${isRightAligned ? 'justify-end' : 'justify-start'}`}
1227
+ className={`h-auto p-0 font-bold hover:bg-transparent ${isRightAligned ? 'justify-end' : 'justify-start'}`}
1219
1228
  onClick={handleSortClick}
1220
1229
  {...headerKeyboardHandlers}
1221
1230
  aria-label={`Sort by ${typeof header.column.columnDef.header === 'string' ? header.column.columnDef.header : 'column'}`}
@@ -1241,8 +1250,9 @@ function DataTableInternal<TData extends DataRecord>({
1241
1250
  </th>
1242
1251
  );
1243
1252
  })}
1244
- </tr>
1245
- ))}
1253
+ </tr>
1254
+ );
1255
+ })}
1246
1256
  </thead>
1247
1257
 
1248
1258
  {/* Table body */}
@@ -50,12 +50,42 @@ export function useTableColumns<TData extends DataRecord>({
50
50
 
51
51
  const enhancedColumns = useMemo(() => {
52
52
  // Create enhanced base columns
53
- const baseColumns: ColumnDef<TData>[] = [...columns].map(column => ({
54
- ...column,
55
- enableSorting: features.sorting && (column.enableSorting !== false),
56
- enableColumnFilter: features.filtering && (column.enableColumnFilter !== false),
57
- enableGrouping: features.grouping && (column.enableGrouping !== false),
58
- }));
53
+ const baseColumns: ColumnDef<TData>[] = [...columns].map(column => {
54
+ const baseColumn = {
55
+ ...column,
56
+ enableSorting: features.sorting && (column.enableSorting !== false),
57
+ enableColumnFilter: features.filtering && (column.enableColumnFilter !== false),
58
+ enableGrouping: features.grouping && (column.enableGrouping !== false),
59
+ };
60
+
61
+ // Automatically set right alignment for numeric columns
62
+ // Check if column is numeric by:
63
+ // 1. meta.type === 'number'
64
+ // 2. fieldType === 'number'
65
+ // 3. filterType === 'number'
66
+ // Exclude dates (fieldType === 'date' or meta.type === 'date')
67
+ const dataTableColumn = column as DataTableColumn<TData>;
68
+ const isNumericColumn =
69
+ column.meta?.type === 'number' ||
70
+ dataTableColumn.fieldType === 'number' ||
71
+ dataTableColumn.filterType === 'number';
72
+
73
+ const isDateColumn =
74
+ column.meta?.type === 'date' ||
75
+ dataTableColumn.fieldType === 'date' ||
76
+ dataTableColumn.filterType === 'date';
77
+
78
+ // Set right alignment if numeric and not a date
79
+ if (isNumericColumn && !isDateColumn) {
80
+ baseColumn.meta = {
81
+ ...baseColumn.meta,
82
+ align: 'right',
83
+ type: 'number',
84
+ };
85
+ }
86
+
87
+ return baseColumn;
88
+ });
59
89
 
60
90
  // Create selection column if enabled
61
91
  const selectionColumn: ColumnDef<TData> | null = features.selection ? {
@@ -398,7 +398,7 @@ describe('[component] FileDisplay', () => {
398
398
  clearError: vi.fn(),
399
399
  });
400
400
 
401
- // This test verifies that displayOnly logic in FileDisplayBackwardsCompat prefers images
401
+ // This test verifies that displayOnly logic prefers images
402
402
  // The component should select the image file over the PDF
403
403
  renderWithProviders(<FileDisplay {...baseProps} displayOnly />);
404
404