@jmruthers/pace-core 0.5.184 → 0.5.186
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README.md +60 -1
- package/core-usage-manifest.json +312 -0
- package/dist/{DataTable-QAB34V6K.js → DataTable-IX2NBUTP.js} +6 -6
- package/dist/{DataTable-Bz8ffqyA.d.ts → DataTable-Z9NLVJh0.d.ts} +1 -1
- package/dist/{index-Bl--n7-T.d.ts → PublicPageProvider-DIzEzwKl.d.ts} +23 -10
- package/dist/{UnifiedAuthProvider-7F6T4B6K.js → UnifiedAuthProvider-A4BCQRJY.js} +4 -2
- package/dist/{UnifiedAuthProvider-F86d7dSi.d.ts → UnifiedAuthProvider-BG0AL5eE.d.ts} +2 -1
- package/dist/{api-ROMBCNKU.js → api-BMFCXVQX.js} +2 -2
- package/dist/{chunk-RA3JUFMW.js → chunk-445GEP27.js} +154 -4
- package/dist/{chunk-RA3JUFMW.js.map → chunk-445GEP27.js.map} +1 -1
- package/dist/{chunk-W22JP75J.js → chunk-DAGICKHT.js} +9 -7
- package/dist/chunk-DAGICKHT.js.map +1 -0
- package/dist/{chunk-FUEYYMX5.js → chunk-FXFJRTKI.js} +24 -3
- package/dist/chunk-FXFJRTKI.js.map +1 -0
- package/dist/{chunk-CSOFYHAG.js → chunk-GRIQLQ52.js} +374 -60
- package/dist/chunk-GRIQLQ52.js.map +1 -0
- package/dist/{chunk-NQPMQGS2.js → chunk-HDCUMOOI.js} +497 -399
- package/dist/chunk-HDCUMOOI.js.map +1 -0
- package/dist/chunk-HESYZWZW.js +388 -0
- package/dist/chunk-HESYZWZW.js.map +1 -0
- package/dist/{chunk-QUVSNGIP.js → chunk-HGPQUCBC.js} +34 -9
- package/dist/{chunk-QUVSNGIP.js.map → chunk-HGPQUCBC.js.map} +1 -1
- package/dist/{chunk-PWAHJW4G.js → chunk-OALXJH4Y.js} +86 -33
- package/dist/chunk-OALXJH4Y.js.map +1 -0
- package/dist/{chunk-MI7HBHN3.js → chunk-TC7D3CR3.js} +89 -9
- package/dist/chunk-TC7D3CR3.js.map +1 -0
- package/dist/chunk-THRPYOFK.js +215 -0
- package/dist/chunk-THRPYOFK.js.map +1 -0
- package/dist/{chunk-M7W4CP3M.js → chunk-U6WNSFX5.js} +2 -1
- package/dist/chunk-U6WNSFX5.js.map +1 -0
- package/dist/{chunk-UHNYIBXL.js → chunk-UQWSHFVX.js} +1 -1
- package/dist/chunk-UQWSHFVX.js.map +1 -0
- package/dist/{chunk-QCDXODCA.js → chunk-XAUHJD3L.js} +2 -2
- package/dist/components.d.ts +182 -6
- package/dist/components.js +157 -11
- package/dist/components.js.map +1 -1
- package/dist/{database.generated-CBmg2950.d.ts → database.generated-DI89OQeI.d.ts} +63 -9
- package/dist/eslint-rules/pace-core-compliance.cjs +406 -0
- package/dist/{file-reference-D06mEEWW.d.ts → file-reference-PRTSLxKx.d.ts} +10 -1
- package/dist/hooks.d.ts +52 -15
- package/dist/hooks.js +12 -22
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +12 -12
- package/dist/index.js +82 -18
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +3 -1
- package/dist/rbac/index.d.ts +206 -15
- package/dist/rbac/index.js +28 -6
- package/dist/timezone-_pgH8qrY.d.ts +530 -0
- package/dist/{types-_x1f4QBF.d.ts → types-DUyCRSTj.d.ts} +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-JJczomYq.d.ts → usePublicRouteParams-D71QLlg4.d.ts} +114 -3
- package/dist/utils.d.ts +110 -152
- package/dist/utils.js +128 -138
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +60 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/Logger.md +178 -0
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +2 -2
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +2 -2
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +5 -5
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +54 -0
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +18 -2
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +30 -0
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/DatabaseComplianceResult.md +85 -0
- package/docs/api/interfaces/DatabaseIssue.md +41 -0
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +6 -6
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +48 -8
- package/docs/api/interfaces/FileUploadProps.md +46 -13
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +9 -9
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +62 -0
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +36 -23
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +6 -6
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/QuickFix.md +52 -0
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +4 -4
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +7 -7
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +5 -5
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +55 -0
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.md +41 -0
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseFormDialogOptions.md +62 -0
- package/docs/api/interfaces/UseFormDialogReturn.md +117 -0
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +746 -50
- package/docs/api-reference/components.md +26 -12
- package/docs/api-reference/hooks.md +111 -0
- package/docs/api-reference/rpc-functions.md +1 -1
- package/docs/api-reference/utilities.md +184 -0
- package/docs/getting-started/installation-guide.md +75 -16
- package/docs/getting-started/quick-start.md +61 -11
- package/docs/implementation-guides/authentication.md +88 -12
- package/docs/implementation-guides/file-reference-system.md +26 -3
- package/docs/implementation-guides/file-upload-storage.md +30 -1
- package/docs/rbac/README.md +1 -0
- package/docs/rbac/compliance/compliance-guide.md +544 -0
- package/docs/rbac/getting-started.md +158 -33
- package/docs/standards/pace-core-compliance.md +432 -0
- package/eslint-config-pace-core.cjs +93 -0
- package/package.json +15 -3
- package/scripts/analyze-bundle.js +232 -0
- package/scripts/build-css.js +56 -0
- package/scripts/build-docs-incremental.js +1015 -0
- package/scripts/check-pace-core-compliance.cjs +2353 -0
- package/scripts/check-pace-core-compliance.js +512 -0
- package/scripts/generate-docs.js +157 -0
- package/scripts/setup-build-cache.js +73 -0
- package/scripts/utils/command-runner.js +131 -0
- package/scripts/utils/env.js +33 -0
- package/scripts/utils/index.js +10 -0
- package/scripts/utils/logger.js +88 -0
- package/scripts/utils/path-helpers.js +37 -0
- package/scripts/validate-formats.js +133 -0
- package/scripts/validate-master.js +155 -0
- package/scripts/validate-pre-publish.js +140 -0
- package/scripts/validate-theme.js +142 -0
- package/src/components/Calendar/Calendar.tsx +8 -1
- package/src/components/Card/Card.tsx +47 -8
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +314 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +126 -0
- package/src/components/DatePickerWithTimezone/README.md +135 -0
- package/src/components/DatePickerWithTimezone/index.ts +10 -0
- package/src/components/DateTimeField/DateTimeField.test.tsx +358 -0
- package/src/components/DateTimeField/DateTimeField.tsx +232 -0
- package/src/components/DateTimeField/README.md +148 -0
- package/src/components/DateTimeField/index.ts +10 -0
- package/src/components/FileUpload/FileUpload.test.tsx +2 -0
- package/src/components/FileUpload/FileUpload.tsx +10 -1
- package/src/components/Header/Header.test.tsx +47 -18
- package/src/components/Header/Header.tsx +22 -7
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +29 -20
- package/src/components/PaceAppLayout/README.md +9 -0
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +37 -8
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +146 -5
- package/src/components/index.ts +8 -0
- package/src/eslint-rules/pace-core-compliance.cjs +406 -0
- package/src/eslint-rules/pace-core-compliance.js +640 -0
- package/src/hooks/__tests__/useFormDialog.test.ts +478 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useFileReference.test.ts +2 -0
- package/src/hooks/useFormDialog.ts +147 -0
- package/src/hooks/usePreventTabReload.ts +106 -0
- package/src/hooks/useSecureDataAccess.ts +2 -2
- package/src/index.ts +27 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +6 -5
- package/src/providers/services/UnifiedAuthProvider.tsx +24 -3
- package/src/rbac/__tests__/rbac-role-isolation.test.ts +456 -0
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +3 -0
- package/src/rbac/compliance/database-validator.ts +165 -0
- package/src/rbac/compliance/index.ts +38 -0
- package/src/rbac/compliance/quick-fix-suggestions.ts +209 -0
- package/src/rbac/compliance/runtime-compliance.ts +77 -0
- package/src/rbac/compliance/setup-validator.ts +131 -0
- package/src/rbac/components/PagePermissionGuard.tsx +8 -64
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +35 -21
- package/src/rbac/docs/event-based-apps.md +285 -0
- package/src/rbac/errors.ts +11 -0
- package/src/rbac/hooks/useRoleManagement.ts +292 -12
- package/src/rbac/index.ts +30 -0
- package/src/services/OrganisationService.ts +4 -0
- package/src/styles/core.css +5 -5
- package/src/types/database.generated.ts +63 -9
- package/src/types/file-reference.ts +9 -0
- package/src/utils/__tests__/timezone.test.ts +345 -0
- package/src/utils/file-reference/__tests__/file-reference.test.ts +60 -4
- package/src/utils/file-reference/index.ts +13 -2
- package/src/utils/formatting/formatDateTimeTimezone.test.ts +167 -0
- package/src/utils/formatting/formatting.ts +179 -0
- package/src/utils/index.ts +27 -1
- package/src/utils/location/index.ts +16 -0
- package/src/utils/location/location.test.ts +286 -0
- package/src/utils/location/location.ts +175 -0
- package/src/utils/security/secureDataAccess.ts +1 -1
- package/src/utils/storage/helpers.ts +68 -0
- package/src/utils/timezone/index.ts +17 -0
- package/src/utils/timezone/timezone.test.ts +349 -0
- package/src/utils/timezone/timezone.ts +281 -0
- package/dist/chunk-CSOFYHAG.js.map +0 -1
- package/dist/chunk-FUEYYMX5.js.map +0 -1
- package/dist/chunk-HKIT6O7W.js +0 -198
- package/dist/chunk-HKIT6O7W.js.map +0 -1
- package/dist/chunk-KUEN3HFB.js +0 -94
- package/dist/chunk-KUEN3HFB.js.map +0 -1
- package/dist/chunk-M7W4CP3M.js.map +0 -1
- package/dist/chunk-MI7HBHN3.js.map +0 -1
- package/dist/chunk-NQPMQGS2.js.map +0 -1
- package/dist/chunk-PWAHJW4G.js.map +0 -1
- package/dist/chunk-UHNYIBXL.js.map +0 -1
- package/dist/chunk-W22JP75J.js.map +0 -1
- package/dist/formatting-5wETwiGF.d.ts +0 -162
- /package/dist/{DataTable-QAB34V6K.js.map → DataTable-IX2NBUTP.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-7F6T4B6K.js.map → UnifiedAuthProvider-A4BCQRJY.js.map} +0 -0
- /package/dist/{api-ROMBCNKU.js.map → api-BMFCXVQX.js.map} +0 -0
- /package/dist/{chunk-QCDXODCA.js.map → chunk-XAUHJD3L.js.map} +0 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file DateTimeField Component
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Components/DateTimeField
|
|
5
|
+
* @since 0.1.0
|
|
6
|
+
*
|
|
7
|
+
* Form input component for datetime values with timezone support.
|
|
8
|
+
* Handles UTC ↔ timezone conversion automatically.
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Automatic UTC ↔ timezone conversion
|
|
12
|
+
* - Prevents unwanted conversions during user editing
|
|
13
|
+
* - Shows timezone information when not UTC
|
|
14
|
+
* - Supports both ISO string and Date object values
|
|
15
|
+
* - Uses native datetime-local input type
|
|
16
|
+
* - Accessible form field with proper labels
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* import { DateTimeField } from '@jmruthers/pace-core/components';
|
|
21
|
+
* import { useState } from 'react';
|
|
22
|
+
*
|
|
23
|
+
* function EventForm() {
|
|
24
|
+
* const [startTime, setStartTime] = useState<string>();
|
|
25
|
+
*
|
|
26
|
+
* return (
|
|
27
|
+
* <DateTimeField
|
|
28
|
+
* label="Start Time"
|
|
29
|
+
* value={startTime}
|
|
30
|
+
* onChange={setStartTime}
|
|
31
|
+
* timezone="America/New_York"
|
|
32
|
+
* required
|
|
33
|
+
* />
|
|
34
|
+
* );
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @accessibility
|
|
39
|
+
* - Proper label association with htmlFor
|
|
40
|
+
* - Required field indicators
|
|
41
|
+
* - Screen reader friendly
|
|
42
|
+
* - Keyboard navigation support
|
|
43
|
+
* - Focus management
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
import * as React from 'react';
|
|
47
|
+
import { format, parse } from 'date-fns';
|
|
48
|
+
import { Label } from '../Label';
|
|
49
|
+
import { Input } from '../Input';
|
|
50
|
+
import { cn } from '../../utils/core/cn';
|
|
51
|
+
import { toZonedTime, fromZonedTime, getUserTimeZone } from '../../utils/timezone';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Props for the DateTimeField component
|
|
55
|
+
*/
|
|
56
|
+
export interface DateTimeFieldProps {
|
|
57
|
+
/**
|
|
58
|
+
* Field label
|
|
59
|
+
*/
|
|
60
|
+
label: string;
|
|
61
|
+
/**
|
|
62
|
+
* UTC date value (ISO string, Date object, or undefined)
|
|
63
|
+
*/
|
|
64
|
+
value: string | Date | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Change handler that receives UTC value (ISO string or Date object)
|
|
67
|
+
*/
|
|
68
|
+
onChange: (value: string | Date | undefined) => void;
|
|
69
|
+
/**
|
|
70
|
+
* Target timezone for display (default: 'UTC')
|
|
71
|
+
*/
|
|
72
|
+
timezone?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Whether the field is required
|
|
75
|
+
*/
|
|
76
|
+
required?: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Additional CSS classes
|
|
79
|
+
*/
|
|
80
|
+
className?: string;
|
|
81
|
+
/**
|
|
82
|
+
* If true, onChange returns Date object instead of ISO string
|
|
83
|
+
*/
|
|
84
|
+
returnAsDate?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Input id (auto-generated if not provided)
|
|
87
|
+
*/
|
|
88
|
+
id?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Helper text to display below the label
|
|
91
|
+
*/
|
|
92
|
+
helperText?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Error message to display
|
|
95
|
+
*/
|
|
96
|
+
error?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* DateTimeField component
|
|
101
|
+
* Form input for datetime values with automatic timezone conversion
|
|
102
|
+
*
|
|
103
|
+
* @param props - DateTimeField configuration
|
|
104
|
+
* @returns JSX.Element - The rendered datetime field
|
|
105
|
+
*/
|
|
106
|
+
export function DateTimeField({
|
|
107
|
+
label,
|
|
108
|
+
value,
|
|
109
|
+
onChange,
|
|
110
|
+
timezone = 'UTC',
|
|
111
|
+
required = false,
|
|
112
|
+
className,
|
|
113
|
+
returnAsDate = false,
|
|
114
|
+
id,
|
|
115
|
+
helperText,
|
|
116
|
+
error
|
|
117
|
+
}: DateTimeFieldProps) {
|
|
118
|
+
const [isEditing, setIsEditing] = React.useState(false);
|
|
119
|
+
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
120
|
+
const fieldId = id || `datetime-field-${React.useId()}`;
|
|
121
|
+
|
|
122
|
+
// Convert UTC value to timezone for display
|
|
123
|
+
const getDisplayValue = React.useCallback((): string => {
|
|
124
|
+
if (!value) {
|
|
125
|
+
return '';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
let dateObj: Date;
|
|
130
|
+
if (typeof value === 'string') {
|
|
131
|
+
dateObj = new Date(value);
|
|
132
|
+
} else {
|
|
133
|
+
dateObj = value;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!dateObj || isNaN(dateObj.getTime())) {
|
|
137
|
+
return '';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Convert UTC to timezone
|
|
141
|
+
const zonedDate = toZonedTime(dateObj, timezone);
|
|
142
|
+
|
|
143
|
+
// Format for datetime-local input (YYYY-MM-DDTHH:mm)
|
|
144
|
+
return format(zonedDate, "yyyy-MM-dd'T'HH:mm");
|
|
145
|
+
} catch {
|
|
146
|
+
return '';
|
|
147
|
+
}
|
|
148
|
+
}, [value, timezone]);
|
|
149
|
+
|
|
150
|
+
const displayValue = isEditing ? undefined : getDisplayValue();
|
|
151
|
+
|
|
152
|
+
// Handle input change
|
|
153
|
+
const handleChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
154
|
+
setIsEditing(true);
|
|
155
|
+
const inputValue = e.target.value;
|
|
156
|
+
|
|
157
|
+
if (!inputValue) {
|
|
158
|
+
onChange(undefined);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
// Parse the datetime-local value (in timezone)
|
|
164
|
+
const localDate = parse(inputValue, "yyyy-MM-dd'T'HH:mm", new Date());
|
|
165
|
+
|
|
166
|
+
if (isNaN(localDate.getTime())) {
|
|
167
|
+
onChange(undefined);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Convert from timezone to UTC
|
|
172
|
+
const utcDate = fromZonedTime(localDate, timezone);
|
|
173
|
+
|
|
174
|
+
// Return as ISO string or Date object
|
|
175
|
+
if (returnAsDate) {
|
|
176
|
+
onChange(utcDate);
|
|
177
|
+
} else {
|
|
178
|
+
onChange(utcDate.toISOString());
|
|
179
|
+
}
|
|
180
|
+
} catch {
|
|
181
|
+
onChange(undefined);
|
|
182
|
+
}
|
|
183
|
+
}, [timezone, returnAsDate, onChange]);
|
|
184
|
+
|
|
185
|
+
// Handle blur to stop editing mode
|
|
186
|
+
const handleBlur = React.useCallback(() => {
|
|
187
|
+
setIsEditing(false);
|
|
188
|
+
}, []);
|
|
189
|
+
|
|
190
|
+
// Get timezone display text
|
|
191
|
+
const getTimezoneDisplay = React.useCallback((): string => {
|
|
192
|
+
if (timezone === 'UTC') {
|
|
193
|
+
return '';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const userTz = getUserTimeZone();
|
|
197
|
+
if (timezone === userTz) {
|
|
198
|
+
return 'Local';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return timezone;
|
|
202
|
+
}, [timezone]);
|
|
203
|
+
|
|
204
|
+
const timezoneDisplay = getTimezoneDisplay();
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<div className={cn('space-y-2', className)}>
|
|
208
|
+
<Label htmlFor={fieldId} required={required} helperText={helperText} error={error}>
|
|
209
|
+
{label}
|
|
210
|
+
</Label>
|
|
211
|
+
<div className="relative">
|
|
212
|
+
<Input
|
|
213
|
+
ref={inputRef}
|
|
214
|
+
id={fieldId}
|
|
215
|
+
type="datetime-local"
|
|
216
|
+
value={displayValue}
|
|
217
|
+
onChange={handleChange}
|
|
218
|
+
onBlur={handleBlur}
|
|
219
|
+
required={required}
|
|
220
|
+
error={!!error}
|
|
221
|
+
className="w-full"
|
|
222
|
+
/>
|
|
223
|
+
{timezoneDisplay && (
|
|
224
|
+
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-sm text-muted-foreground pointer-events-none">
|
|
225
|
+
{timezoneDisplay}
|
|
226
|
+
</span>
|
|
227
|
+
)}
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# DateTimeField Component
|
|
2
|
+
|
|
3
|
+
Form input component for datetime values with automatic UTC ↔ timezone conversion.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `DateTimeField` component provides a datetime input field that automatically handles conversion between UTC (for storage) and a specified timezone (for display). It prevents unwanted conversions during user editing and displays timezone information when not using UTC.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Automatic UTC ↔ timezone conversion
|
|
12
|
+
- Prevents unwanted conversions during user editing
|
|
13
|
+
- Shows timezone information when not UTC
|
|
14
|
+
- Supports both ISO string and Date object values
|
|
15
|
+
- Uses native `datetime-local` input type
|
|
16
|
+
- Accessible form field with proper labels
|
|
17
|
+
- Error state support
|
|
18
|
+
- Helper text support
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Basic Example
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { DateTimeField } from '@jmruthers/pace-core/components';
|
|
26
|
+
import { useState } from 'react';
|
|
27
|
+
|
|
28
|
+
function EventForm() {
|
|
29
|
+
const [startTime, setStartTime] = useState<string>();
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<DateTimeField
|
|
33
|
+
label="Start Time"
|
|
34
|
+
value={startTime}
|
|
35
|
+
onChange={setStartTime}
|
|
36
|
+
timezone="America/New_York"
|
|
37
|
+
required
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### With Date Object
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { DateTimeField } from '@jmruthers/pace-core/components';
|
|
47
|
+
import { useState } from 'react';
|
|
48
|
+
|
|
49
|
+
function EventForm() {
|
|
50
|
+
const [startTime, setStartTime] = useState<Date>();
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<DateTimeField
|
|
54
|
+
label="Start Time"
|
|
55
|
+
value={startTime}
|
|
56
|
+
onChange={setStartTime}
|
|
57
|
+
timezone="America/New_York"
|
|
58
|
+
returnAsDate
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### With Error State
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
<DateTimeField
|
|
68
|
+
label="Start Time"
|
|
69
|
+
value={startTime}
|
|
70
|
+
onChange={setStartTime}
|
|
71
|
+
timezone="America/New_York"
|
|
72
|
+
error="Please select a valid start time"
|
|
73
|
+
required
|
|
74
|
+
/>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### With Helper Text
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
<DateTimeField
|
|
81
|
+
label="Start Time"
|
|
82
|
+
value={startTime}
|
|
83
|
+
onChange={setStartTime}
|
|
84
|
+
timezone="America/New_York"
|
|
85
|
+
helperText="Select the event start time in your local timezone"
|
|
86
|
+
/>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## API
|
|
90
|
+
|
|
91
|
+
### Props
|
|
92
|
+
|
|
93
|
+
| Prop | Type | Default | Description |
|
|
94
|
+
|------|------|---------|-------------|
|
|
95
|
+
| `label` | `string` | **required** | Field label |
|
|
96
|
+
| `value` | `string \| Date \| undefined` | - | UTC date value (ISO string, Date object, or undefined) |
|
|
97
|
+
| `onChange` | `(value: string \| Date \| undefined) => void` | **required** | Change handler that receives UTC value |
|
|
98
|
+
| `timezone` | `string` | `'UTC'` | Target timezone for display (IANA timezone string) |
|
|
99
|
+
| `required` | `boolean` | `false` | Whether the field is required |
|
|
100
|
+
| `className` | `string` | - | Additional CSS classes |
|
|
101
|
+
| `returnAsDate` | `boolean` | `false` | If true, onChange returns Date object instead of ISO string |
|
|
102
|
+
| `id` | `string` | - | Input id (auto-generated if not provided) |
|
|
103
|
+
| `helperText` | `string` | - | Helper text to display below the label |
|
|
104
|
+
| `error` | `string` | - | Error message to display |
|
|
105
|
+
|
|
106
|
+
## Behavior
|
|
107
|
+
|
|
108
|
+
### Timezone Conversion
|
|
109
|
+
|
|
110
|
+
The component automatically converts between UTC (for storage) and the specified timezone (for display):
|
|
111
|
+
|
|
112
|
+
- **Display**: UTC values are converted to the specified timezone for display in the input
|
|
113
|
+
- **Input**: User-entered values (in the timezone) are converted back to UTC before calling `onChange`
|
|
114
|
+
|
|
115
|
+
### Editing Prevention
|
|
116
|
+
|
|
117
|
+
The component tracks editing state to prevent unwanted conversions while the user is typing. Conversions only occur when:
|
|
118
|
+
- The component receives a new `value` prop (controlled component)
|
|
119
|
+
- The user finishes editing (blur event)
|
|
120
|
+
|
|
121
|
+
### Timezone Display
|
|
122
|
+
|
|
123
|
+
- When `timezone` is `'UTC'`: No timezone indicator is shown
|
|
124
|
+
- When `timezone` matches the user's browser timezone: Shows "Local"
|
|
125
|
+
- When `timezone` is different: Shows the timezone name (e.g., "America/New_York")
|
|
126
|
+
|
|
127
|
+
## Accessibility
|
|
128
|
+
|
|
129
|
+
- Proper label association with `htmlFor`
|
|
130
|
+
- Required field indicators for screen readers
|
|
131
|
+
- Error messages with `role="alert"`
|
|
132
|
+
- Keyboard navigation support
|
|
133
|
+
- Focus management
|
|
134
|
+
|
|
135
|
+
## Edge Cases
|
|
136
|
+
|
|
137
|
+
- **Invalid dates**: Returns empty string in input, calls `onChange(undefined)`
|
|
138
|
+
- **Null/undefined values**: Displays empty input
|
|
139
|
+
- **Invalid timezone**: Falls back to UTC behavior
|
|
140
|
+
|
|
141
|
+
## Dependencies
|
|
142
|
+
|
|
143
|
+
- `date-fns` - Date formatting and parsing
|
|
144
|
+
- `date-fns-tz` - Timezone conversion
|
|
145
|
+
- `@jmruthers/pace-core/components/Label` - Label component
|
|
146
|
+
- `@jmruthers/pace-core/components/Input` - Input component
|
|
147
|
+
- `@jmruthers/pace-core/utils/timezone` - Timezone utilities
|
|
148
|
+
|
|
@@ -22,6 +22,9 @@ export interface FileUploadProps {
|
|
|
22
22
|
organisation_id: string;
|
|
23
23
|
app_id?: string; // Optional - will be resolved from app name if not provided
|
|
24
24
|
category: FileCategory;
|
|
25
|
+
folder: string; // Folder name in storage bucket (e.g., 'profile_photos', 'documents')
|
|
26
|
+
pageContext: string; // The page context where the file upload occurs (e.g., 'configuration', 'forms', 'applications')
|
|
27
|
+
event_id?: string; // Optional event ID for event-scoped permission checks (required for event-based apps)
|
|
25
28
|
accept?: string;
|
|
26
29
|
maxSize?: number;
|
|
27
30
|
multiple?: boolean;
|
|
@@ -50,6 +53,9 @@ export function FileUpload({
|
|
|
50
53
|
organisation_id,
|
|
51
54
|
app_id,
|
|
52
55
|
category,
|
|
56
|
+
folder,
|
|
57
|
+
pageContext,
|
|
58
|
+
event_id,
|
|
53
59
|
accept = '*/*',
|
|
54
60
|
maxSize = 10 * 1024 * 1024, // 10MB default
|
|
55
61
|
multiple = false,
|
|
@@ -284,6 +290,9 @@ export function FileUpload({
|
|
|
284
290
|
organisation_id,
|
|
285
291
|
app_id: resolvedAppId ? assertAppId(resolvedAppId) : assertAppId(''),
|
|
286
292
|
category,
|
|
293
|
+
folder,
|
|
294
|
+
pageContext,
|
|
295
|
+
event_id,
|
|
287
296
|
is_public: isPublic
|
|
288
297
|
}, file);
|
|
289
298
|
|
|
@@ -381,7 +390,7 @@ export function FileUpload({
|
|
|
381
390
|
onUploadError?.(errorMessage, file);
|
|
382
391
|
}
|
|
383
392
|
}
|
|
384
|
-
}, [uploadFile, table_name, record_id, organisation_id, resolvedAppId, category, isPublic, maxSize, onUploadSuccess, onUploadError, onProgress, validateFile, generatePreview, showPreview, appIdError]);
|
|
393
|
+
}, [uploadFile, table_name, record_id, organisation_id, resolvedAppId, category, folder, isPublic, maxSize, onUploadSuccess, onUploadError, onProgress, validateFile, generatePreview, showPreview, appIdError]);
|
|
385
394
|
|
|
386
395
|
const handleDragOver = useCallback((e: React.DragEvent) => {
|
|
387
396
|
e.preventDefault();
|
|
@@ -60,6 +60,14 @@ vi.mock('../EventSelector', () => ({
|
|
|
60
60
|
),
|
|
61
61
|
}));
|
|
62
62
|
|
|
63
|
+
vi.mock('../OrganisationSelector', () => ({
|
|
64
|
+
OrganisationSelector: ({ placeholder, className, 'data-testid': testId }: any) => (
|
|
65
|
+
<div data-testid={testId || 'org-selector'} className={className}>
|
|
66
|
+
<button>{placeholder}</button>
|
|
67
|
+
</div>
|
|
68
|
+
),
|
|
69
|
+
}));
|
|
70
|
+
|
|
63
71
|
// Test data
|
|
64
72
|
const mockUser: User = {
|
|
65
73
|
id: '123',
|
|
@@ -312,16 +320,8 @@ describe('Header Component', () => {
|
|
|
312
320
|
expect(screen.getByRole('button', { name: 'Select event' })).toBeInTheDocument();
|
|
313
321
|
});
|
|
314
322
|
|
|
315
|
-
it('
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const placeholder = container.querySelector('del.invisible');
|
|
319
|
-
expect(placeholder).toBeInTheDocument();
|
|
320
|
-
expect(placeholder).toHaveTextContent('Event Selector N/A');
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
it('preserves grid layout when event selector is hidden', () => {
|
|
324
|
-
const { container } = renderWithProviders(
|
|
323
|
+
it('preserves layout when event selector is hidden', () => {
|
|
324
|
+
renderWithProviders(
|
|
325
325
|
<Header
|
|
326
326
|
showEventSelector={false}
|
|
327
327
|
user={mockUser}
|
|
@@ -333,15 +333,47 @@ describe('Header Component', () => {
|
|
|
333
333
|
expect(nav).toBeInTheDocument();
|
|
334
334
|
expect(nav).toBeVisible();
|
|
335
335
|
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
-
expect(placeholder).toBeInTheDocument();
|
|
336
|
+
// Event selector should not be rendered
|
|
337
|
+
expect(screen.queryByTestId('event-selector')).not.toBeInTheDocument();
|
|
339
338
|
|
|
340
339
|
// User menu should still be present and positioned correctly
|
|
341
340
|
expect(screen.getByTestId('user-menu')).toBeInTheDocument();
|
|
342
341
|
});
|
|
343
342
|
});
|
|
344
343
|
|
|
344
|
+
// Organisation Selector tests
|
|
345
|
+
describe('Organisation Selector', () => {
|
|
346
|
+
it('does not render organisation selector by default', () => {
|
|
347
|
+
renderWithProviders(<Header />);
|
|
348
|
+
|
|
349
|
+
expect(screen.queryByTestId('org-selector')).not.toBeInTheDocument();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('renders organisation selector when showOrgSelector is true', () => {
|
|
353
|
+
renderWithProviders(<Header showOrgSelector={true} />);
|
|
354
|
+
|
|
355
|
+
expect(screen.getByTestId('org-selector')).toBeInTheDocument();
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('does not render organisation selector when showOrgSelector is false', () => {
|
|
359
|
+
renderWithProviders(<Header showOrgSelector={false} />);
|
|
360
|
+
|
|
361
|
+
expect(screen.queryByTestId('org-selector')).not.toBeInTheDocument();
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('can render both organisation and event selectors together', () => {
|
|
365
|
+
renderWithProviders(
|
|
366
|
+
<Header
|
|
367
|
+
showOrgSelector={true}
|
|
368
|
+
showEventSelector={true}
|
|
369
|
+
/>
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
expect(screen.getByTestId('org-selector')).toBeInTheDocument();
|
|
373
|
+
expect(screen.getByTestId('event-selector')).toBeInTheDocument();
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
345
377
|
// Custom actions tests
|
|
346
378
|
describe('Custom Actions', () => {
|
|
347
379
|
it('renders custom actions when provided', () => {
|
|
@@ -560,7 +592,7 @@ describe('Header Component', () => {
|
|
|
560
592
|
});
|
|
561
593
|
|
|
562
594
|
it('renders minimal configuration', () => {
|
|
563
|
-
|
|
595
|
+
renderWithProviders(
|
|
564
596
|
<Header
|
|
565
597
|
showEventSelector={false}
|
|
566
598
|
showUserMenu={false}
|
|
@@ -573,10 +605,7 @@ describe('Header Component', () => {
|
|
|
573
605
|
expect(screen.queryByTestId('navigation-menu')).not.toBeInTheDocument();
|
|
574
606
|
expect(screen.queryByTestId('event-selector')).not.toBeInTheDocument();
|
|
575
607
|
expect(screen.queryByTestId('user-menu')).not.toBeInTheDocument();
|
|
576
|
-
|
|
577
|
-
// Should have placeholder for event selector
|
|
578
|
-
const placeholder = container.querySelector('del.invisible');
|
|
579
|
-
expect(placeholder).toBeInTheDocument();
|
|
608
|
+
expect(screen.queryByTestId('org-selector')).not.toBeInTheDocument();
|
|
580
609
|
});
|
|
581
610
|
});
|
|
582
611
|
|
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
* @since 0.1.0
|
|
6
6
|
*
|
|
7
7
|
* A comprehensive header component for application layouts with navigation,
|
|
8
|
-
* user menu, event selector, and customizable branding.
|
|
8
|
+
* user menu, organisation selector, event selector, and customizable branding.
|
|
9
9
|
*
|
|
10
10
|
* Features:
|
|
11
11
|
* - Customizable logo (URL or component)
|
|
12
12
|
* - Clickable logo that routes to dashboard (configurable)
|
|
13
13
|
* - Navigation menu integration
|
|
14
14
|
* - User menu with authentication
|
|
15
|
+
* - Organisation selector for multi-organisation applications
|
|
15
16
|
* - Event selector for multi-tenant applications
|
|
16
17
|
* - Custom actions support
|
|
17
18
|
* - Responsive design
|
|
@@ -82,6 +83,7 @@
|
|
|
82
83
|
* - Tailwind CSS - Styling
|
|
83
84
|
* - NavigationMenu component
|
|
84
85
|
* - UserMenu component
|
|
86
|
+
* - OrganisationSelector component
|
|
85
87
|
* - EventSelector component
|
|
86
88
|
*/
|
|
87
89
|
|
|
@@ -90,6 +92,7 @@ import { Link } from 'react-router-dom';
|
|
|
90
92
|
import { User } from '@supabase/supabase-js';
|
|
91
93
|
import { cn } from '../../utils/core/cn';
|
|
92
94
|
import { EventSelector } from '../EventSelector';
|
|
95
|
+
import { OrganisationSelector } from '../OrganisationSelector';
|
|
93
96
|
import { UserMenu } from '../UserMenu';
|
|
94
97
|
import { NavigationMenu } from '../NavigationMenu';
|
|
95
98
|
import type { NavigationItem } from '../NavigationMenu';
|
|
@@ -121,6 +124,8 @@ export interface HeaderProps {
|
|
|
121
124
|
className?: string;
|
|
122
125
|
/** Show/hide event selector */
|
|
123
126
|
showEventSelector?: boolean;
|
|
127
|
+
/** Show/hide organisation selector */
|
|
128
|
+
showOrgSelector?: boolean;
|
|
124
129
|
/** Show/hide user menu */
|
|
125
130
|
showUserMenu?: boolean;
|
|
126
131
|
/** Current path for navigation highlighting */
|
|
@@ -244,6 +249,7 @@ export function Header({
|
|
|
244
249
|
userMenu,
|
|
245
250
|
className,
|
|
246
251
|
showEventSelector = true,
|
|
252
|
+
showOrgSelector = false,
|
|
247
253
|
showUserMenu = true,
|
|
248
254
|
currentPath,
|
|
249
255
|
onNavigate,
|
|
@@ -254,7 +260,7 @@ export function Header({
|
|
|
254
260
|
"w-full border-b border-main-200 h-16 shadow-sm bg-main-100 ",
|
|
255
261
|
className
|
|
256
262
|
)} role="banner">
|
|
257
|
-
<nav className="px-4 w-[min(var(--app-width),100%)] mx-auto
|
|
263
|
+
<nav className="px-4 w-[min(var(--app-width),100%)] mx-auto flex items-center gap-4 h-full">
|
|
258
264
|
{/* Logo */}
|
|
259
265
|
{logo ? (
|
|
260
266
|
logoHref ? (
|
|
@@ -311,18 +317,26 @@ export function Header({
|
|
|
311
317
|
)}
|
|
312
318
|
|
|
313
319
|
|
|
314
|
-
{/* Right side: Event Selector, Actions, and User Menu */}
|
|
320
|
+
{/* Right side: Organisation Selector, Event Selector, Actions, and User Menu */}
|
|
321
|
+
<div className="flex items-center gap-4 ml-auto">
|
|
322
|
+
{/* Organisation Selector */}
|
|
323
|
+
{showOrgSelector ? (
|
|
324
|
+
<OrganisationSelector
|
|
325
|
+
placeholder="Select organisation"
|
|
326
|
+
className="w-64"
|
|
327
|
+
data-testid="org-selector"
|
|
328
|
+
compact={true}
|
|
329
|
+
/>
|
|
330
|
+
) : null}
|
|
315
331
|
|
|
316
332
|
{/* Event Selector */}
|
|
317
333
|
{showEventSelector ? (
|
|
318
334
|
<EventSelector
|
|
319
335
|
placeholder="Select event"
|
|
320
|
-
className="
|
|
336
|
+
className="w-96"
|
|
321
337
|
data-testid="event-selector"
|
|
322
338
|
/>
|
|
323
|
-
) :
|
|
324
|
-
<del className="justify-self-end invisible">Event Selector N/A</del>
|
|
325
|
-
)}
|
|
339
|
+
) : null}
|
|
326
340
|
|
|
327
341
|
{/* Custom Actions */}
|
|
328
342
|
{actions}
|
|
@@ -340,6 +354,7 @@ export function Header({
|
|
|
340
354
|
/>
|
|
341
355
|
)
|
|
342
356
|
)}
|
|
357
|
+
</div>
|
|
343
358
|
|
|
344
359
|
</nav>
|
|
345
360
|
</header>
|