@taskforcehq/taskforce 0.3.169 → 0.3.300
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/README.md +52 -21
- package/dist/Taskforce.module.css +3394 -5981
- package/dist/TaskforceCore.CategoryFallback.test.js +5 -2
- package/dist/TaskforceCore.filter-persistence.test.js +402 -0
- package/dist/TaskforceCore.js +388 -71
- package/dist/TaskforceCore.routing.test.js +311 -0
- package/dist/TaskforceCore.test.js +3344 -615
- package/dist/TaskforceWidget.js +3 -2
- package/dist/__tests__/localizationCatalogs.test.js +48 -31
- package/dist/__tests__/workflowSimulation.test.js +2 -2
- package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.d.ts +60 -0
- package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.js +324 -0
- package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.test.js +278 -0
- package/dist/annotatedAttachments/payload.d.ts +2 -0
- package/dist/annotatedAttachments/payload.js +54 -0
- package/dist/annotatedAttachments/payload.test.js +67 -0
- package/dist/annotatedAttachments/service.d.ts +49 -0
- package/dist/annotatedAttachments/service.js +121 -0
- package/dist/annotatedAttachments/types.d.ts +86 -0
- package/dist/cli.js +74 -25
- package/dist/compat/deploymentCompat.d.ts +12 -0
- package/dist/compat/deploymentCompat.js +43 -0
- package/dist/compat/workspaceSyncCompat.d.ts +6 -0
- package/dist/compat/workspaceSyncCompat.js +79 -0
- package/dist/compat/workspaceSyncCompat.test.js +87 -0
- package/dist/components/features/AgentsModule.d.ts +5 -0
- package/dist/components/features/AgentsModule.js +131 -0
- package/dist/components/features/AgentsModule.test.js +102 -0
- package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +24 -0
- package/dist/components/features/AnnotatedAttachmentWorkspace.js +1942 -0
- package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +2831 -0
- package/dist/components/features/DocumentGenerateModal.js +8 -16
- package/dist/components/features/DocumentIndex.d.ts +3 -4
- package/dist/components/features/DocumentIndex.js +22 -10
- package/dist/components/features/DocumentViewer.d.ts +6 -2
- package/dist/components/features/DocumentViewer.js +1000 -18
- package/dist/components/features/DocumentWorkspace.d.ts +40 -2
- package/dist/components/features/DocumentWorkspace.js +96 -16
- package/dist/components/features/DocumentWorkspace.test.js +1202 -0
- package/dist/components/features/InitiativesModule.d.ts +22 -0
- package/dist/components/features/InitiativesModule.js +33 -0
- package/dist/components/features/TaskSettings.d.ts +41 -32
- package/dist/components/features/TaskSettings.js +798 -313
- package/dist/components/features/TaskSettings.test.js +1221 -47
- package/dist/components/features/WorkflowsModule.d.ts +27 -0
- package/dist/components/features/WorkflowsModule.js +21 -0
- package/dist/components/features/documentWorkspaceFilters.d.ts +10 -0
- package/dist/components/features/documentWorkspaceFilters.js +11 -0
- package/dist/components/features/settings/CategoryManager.d.ts +2 -0
- package/dist/components/features/settings/CategoryManager.js +15 -8
- package/dist/components/features/settings/PriorityManager.d.ts +18 -0
- package/dist/components/features/settings/PriorityManager.js +51 -0
- package/dist/components/features/settings/TaxonomyEditor.d.ts +2 -1
- package/dist/components/features/settings/TaxonomyEditor.js +41 -18
- package/dist/components/features/settings/TaxonomyLibraryManager.d.ts +26 -0
- package/dist/components/features/settings/TaxonomyLibraryManager.js +301 -0
- package/dist/components/features/settings/TaxonomyLibraryManager.test.js +141 -0
- package/dist/components/features/settings/TaxonomyManager.d.ts +35 -12
- package/dist/components/features/settings/TaxonomyManager.js +59 -27
- package/dist/components/features/settings/TypeManager.d.ts +11 -0
- package/dist/components/features/settings/TypeManager.js +48 -11
- package/dist/components/features/settings/{WorkflowEditorPrototype.d.ts → WorkflowEditor.d.ts} +2 -2
- package/dist/components/features/settings/{WorkflowEditorPrototype.js → WorkflowEditor.js} +3 -3
- package/dist/components/task/TaskActionHeader.d.ts +12 -2
- package/dist/components/task/TaskActionHeader.js +64 -8
- package/dist/components/task/TaskActionHeader.test.js +73 -0
- package/dist/components/task/TaskCard.d.ts +14 -16
- package/dist/components/task/TaskCard.js +144 -174
- package/dist/components/task/TaskCard.test.js +416 -64
- package/dist/components/task/TaskContextUpload.d.ts +6 -5
- package/dist/components/task/TaskContextUpload.js +199 -64
- package/dist/components/task/TaskContextUpload.test.js +331 -14
- package/dist/components/task/TaskForm.d.ts +23 -29
- package/dist/components/task/TaskForm.js +348 -169
- package/dist/components/task/TaskForm.test.js +419 -23
- package/dist/components/task/TaskKanban.d.ts +20 -21
- package/dist/components/task/TaskKanban.js +176 -316
- package/dist/components/task/TaskKanban.planning-drop.test.js +93 -0
- package/dist/components/task/TaskList.d.ts +22 -11
- package/dist/components/task/TaskList.js +98 -12
- package/dist/components/task/TaskList.test.js +206 -7
- package/dist/components/task/TaskReferenceBadge.d.ts +12 -0
- package/dist/components/task/TaskReferenceBadge.js +5 -0
- package/dist/components/task/TaskStatusActions.d.ts +12 -0
- package/dist/components/task/TaskStatusActions.js +18 -0
- package/dist/components/task/TaskStatusActions.test.js +45 -0
- package/dist/components/ui/ImageReferenceBadge.d.ts +12 -0
- package/dist/components/ui/ImageReferenceBadge.js +5 -0
- package/dist/components/ui/LevelSelector.d.ts +2 -1
- package/dist/components/ui/LevelSelector.js +2 -2
- package/dist/components/ui/Markdown.d.ts +1 -2
- package/dist/components/ui/Markdown.js +2 -13
- package/dist/components/ui/Modal.d.ts +3 -2
- package/dist/components/ui/Modal.js +4 -3
- package/dist/components/ui/MultiSelectFilter.d.ts +3 -1
- package/dist/components/ui/MultiSelectFilter.js +2 -2
- package/dist/components/ui/ReferenceBadgeButton.d.ts +13 -0
- package/dist/components/ui/ReferenceBadgeButton.js +6 -0
- package/dist/components/ui/TaxonomyDropdown.d.ts +7 -1
- package/dist/components/ui/TaxonomyDropdown.js +58 -23
- package/dist/components/ui/TopNoticeLayer.d.ts +7 -0
- package/dist/components/ui/TopNoticeLayer.js +14 -0
- package/dist/components/ui/TopNoticeLayer.test.js +25 -0
- package/dist/components/views/AppShellHeader.d.ts +13 -0
- package/dist/components/views/AppShellHeader.js +19 -0
- package/dist/components/views/AppShellHeader.test.js +21 -0
- package/dist/components/views/PlanComparisonPage.d.ts +73 -0
- package/dist/components/views/PlanComparisonPage.js +196 -0
- package/dist/components/views/PlanComparisonPage.test.js +251 -0
- package/dist/components/views/PlansPage.d.ts +14 -0
- package/dist/components/views/PlansPage.js +355 -0
- package/dist/components/views/StandaloneLayout.d.ts +2 -2
- package/dist/components/views/StandaloneLayout.js +2212 -1194
- package/dist/components/views/WidgetView.d.ts +2 -2
- package/dist/components/views/WidgetView.js +149 -43
- package/dist/components/views/chrome/WorkspaceChromeSections.d.ts +57 -0
- package/dist/components/views/chrome/WorkspaceChromeSections.js +38 -0
- package/dist/components/views/chrome/WorkspaceChromeSections.test.js +54 -0
- package/dist/components/views/chrome/workspaceChrome.d.ts +31 -0
- package/dist/components/views/chrome/workspaceChrome.js +108 -0
- package/dist/components/views/chrome/workspaceChrome.test.js +52 -0
- package/dist/components/views/panels/DocumentFilterToolbar.d.ts +10 -0
- package/dist/components/views/panels/DocumentFilterToolbar.js +7 -0
- package/dist/components/views/panels/DocumentFilterToolbar.test.js +19 -0
- package/dist/components/views/panels/FilterToolbar.d.ts +66 -0
- package/dist/components/views/panels/FilterToolbar.js +21 -0
- package/dist/components/views/panels/FilterToolbar.test.d.ts +1 -0
- package/dist/components/views/panels/FilterToolbar.test.js +45 -0
- package/dist/components/views/panels/FilterToolbarControls.d.ts +46 -0
- package/dist/components/views/panels/FilterToolbarControls.js +61 -0
- package/dist/components/views/panels/PlanningDrawer.d.ts +115 -0
- package/dist/components/views/panels/PlanningDrawer.js +220 -0
- package/dist/components/views/panels/ScheduleSidebar.d.ts +52 -0
- package/dist/components/views/panels/ScheduleSidebar.js +127 -0
- package/dist/components/views/panels/scheduleUtils.d.ts +14 -0
- package/dist/components/views/panels/scheduleUtils.js +79 -0
- package/dist/components/views/teamManagementAccess.d.ts +6 -0
- package/dist/components/views/teamManagementAccess.js +18 -0
- package/dist/components/views/teamManagementAccess.test.d.ts +1 -0
- package/dist/components/views/teamManagementAccess.test.js +44 -0
- package/dist/config/clientDeployment.d.ts +2 -0
- package/dist/config/clientDeployment.js +13 -0
- package/dist/config/cloudEnvironment.d.ts +15 -0
- package/dist/config/cloudEnvironment.js +60 -0
- package/dist/config/deployment.d.ts +20 -0
- package/dist/config/deployment.js +67 -0
- package/dist/config/deployment.test.d.ts +1 -0
- package/dist/config/deployment.test.js +88 -0
- package/dist/config/envSchema.js +36 -8
- package/dist/config/envSchema.test.js +28 -0
- package/dist/core/AiProfileService.d.ts +66 -0
- package/dist/core/AiProfileService.js +320 -0
- package/dist/core/AiProfileService.test.d.ts +1 -0
- package/dist/core/AiProfileService.test.js +164 -0
- package/dist/core/AiProfiles.test.d.ts +1 -0
- package/dist/core/AiProfiles.test.js +449 -0
- package/dist/core/AttachmentLinkService.d.ts +15 -0
- package/dist/core/AttachmentLinkService.js +157 -0
- package/dist/core/AuthTokenService.d.ts +44 -0
- package/dist/core/AuthTokenService.js +207 -0
- package/dist/core/CreatedBy.test.js +134 -6
- package/dist/core/DeletedTaskLifecycleService.d.ts +33 -0
- package/dist/core/DeletedTaskLifecycleService.js +158 -0
- package/dist/core/DeletedTaskTrash.test.d.ts +1 -0
- package/dist/core/DeletedTaskTrash.test.js +104 -0
- package/dist/core/EntitlementsPolicy.test.js +78 -5
- package/dist/core/GlobalSettingsService.d.ts +16 -0
- package/dist/core/GlobalSettingsService.js +370 -0
- package/dist/core/JsonBackupRuntime.test.d.ts +1 -0
- package/dist/core/JsonBackupRuntime.test.js +155 -0
- package/dist/core/McpTokenService.d.ts +118 -0
- package/dist/core/McpTokenService.js +921 -0
- package/dist/core/PlanEntitlementService.d.ts +201 -0
- package/dist/core/PlanEntitlementService.js +1442 -0
- package/dist/core/PlanVersionPolicy.test.d.ts +1 -0
- package/dist/core/PlanVersionPolicy.test.js +178 -0
- package/dist/core/PlanningEntities.test.d.ts +1 -0
- package/dist/core/PlanningEntities.test.js +95 -0
- package/dist/core/ScheduleFields.test.js +15 -19
- package/dist/core/SignupMonetizationSettings.test.d.ts +1 -0
- package/dist/core/SignupMonetizationSettings.test.js +159 -0
- package/dist/core/Stats.test.d.ts +1 -0
- package/dist/core/Stats.test.js +36 -0
- package/dist/core/SyncReconciliationService.d.ts +93 -0
- package/dist/core/SyncReconciliationService.js +406 -0
- package/dist/core/SystemAdmin.test.js +578 -13
- package/dist/core/TaskActivityService.d.ts +21 -0
- package/dist/core/TaskActivityService.js +130 -0
- package/dist/core/TaskAttachmentsCanonical.test.d.ts +1 -0
- package/dist/core/TaskAttachmentsCanonical.test.js +306 -0
- package/dist/core/TaskAuditTimeline.test.d.ts +1 -0
- package/dist/core/TaskAuditTimeline.test.js +132 -0
- package/dist/core/TaskTaxonomyValidation.test.js +338 -15
- package/dist/core/Taskforce.d.ts +930 -183
- package/dist/core/Taskforce.ids.test.js +252 -0
- package/dist/core/Taskforce.js +3848 -2532
- package/dist/core/Taskforce.mcpAuth.test.d.ts +1 -0
- package/dist/core/Taskforce.mcpAuth.test.js +343 -0
- package/dist/core/Taskforce.realtimeMutation.test.js +57 -2
- package/dist/core/TaxonomySettingsService.d.ts +18 -0
- package/dist/core/TaxonomySettingsService.js +162 -0
- package/dist/core/WorkspaceDelete.test.js +32 -13
- package/dist/core/WorkspaceLifecycleService.d.ts +11 -0
- package/dist/core/WorkspaceLifecycleService.js +88 -0
- package/dist/core/WorkspacePermissions.test.js +95 -0
- package/dist/core/WorkspacePlanMode.test.js +114 -1
- package/dist/core/WorkspacePreferenceService.d.ts +41 -0
- package/dist/core/WorkspacePreferenceService.js +246 -0
- package/dist/core/WorkspaceSyncSettings.test.d.ts +1 -0
- package/dist/core/WorkspaceSyncSettings.test.js +65 -0
- package/dist/core/__tests__/TaskAttachmentsCanonical.test.d.ts +1 -0
- package/dist/core/__tests__/TaskAttachmentsCanonical.test.js +212 -0
- package/dist/core/shared.d.ts +36 -0
- package/dist/core/shared.js +128 -0
- package/dist/core/types.d.ts +614 -0
- package/dist/core/types.js +27 -0
- package/dist/documentReviews/DocumentReviewCommentStore.d.ts +56 -0
- package/dist/documentReviews/DocumentReviewCommentStore.js +230 -0
- package/dist/documentReviews/DocumentReviewSessionStore.d.ts +52 -0
- package/dist/documentReviews/DocumentReviewSessionStore.js +193 -0
- package/dist/documentReviews/DocumentReviewSessionStore.test.d.ts +1 -0
- package/dist/documentReviews/DocumentReviewSessionStore.test.js +127 -0
- package/dist/documentReviews/service.d.ts +68 -0
- package/dist/documentReviews/service.js +210 -0
- package/dist/documentReviews/types.d.ts +58 -0
- package/dist/documentReviews/types.js +1 -0
- package/dist/hooks/auth/useAuthGuardPolicy.d.ts +2 -0
- package/dist/hooks/auth/useAuthGuardPolicy.js +1 -0
- package/dist/hooks/tasks/useCurrentTaskRelations.d.ts +15 -0
- package/dist/hooks/tasks/useCurrentTaskRelations.js +37 -0
- package/dist/hooks/tasks/useTaskActions.d.ts +3 -0
- package/dist/hooks/tasks/useTaskActions.js +2 -0
- package/dist/hooks/tasks/useTaskEditorNavigation.d.ts +65 -0
- package/dist/hooks/tasks/useTaskEditorNavigation.js +235 -0
- package/dist/hooks/tasks/useTaskFilters.d.ts +2 -0
- package/dist/hooks/tasks/useTaskFilters.js +2 -0
- package/dist/hooks/tasks/useTaskFormActions.d.ts +58 -0
- package/dist/hooks/tasks/useTaskFormActions.js +365 -0
- package/dist/hooks/tasks/useTaskRelationships.d.ts +16 -0
- package/dist/hooks/tasks/useTaskRelationships.js +64 -0
- package/dist/hooks/ui/useResourceExport.d.ts +19 -0
- package/dist/hooks/ui/useResourceExport.js +84 -0
- package/dist/hooks/ui/useSettingsModel.d.ts +203 -0
- package/dist/hooks/ui/useSettingsModel.js +125 -0
- package/dist/hooks/ui/useSettingsPersistence.d.ts +36 -0
- package/dist/hooks/ui/useSettingsPersistence.js +254 -0
- package/dist/hooks/ui/useSettingsTaxonomyManagement.d.ts +119 -0
- package/dist/hooks/ui/useSettingsTaxonomyManagement.js +536 -0
- package/dist/hooks/ui/useUiNotice.d.ts +1 -0
- package/dist/hooks/ui/useUiNotice.js +1 -0
- package/dist/hooks/ui/useWorkflowTemplates.d.ts +37 -0
- package/dist/hooks/ui/useWorkflowTemplates.js +111 -0
- package/dist/hooks/ui/useZenMode.d.ts +5 -0
- package/dist/hooks/ui/useZenMode.js +32 -0
- package/dist/hooks/useFilterSort.d.ts +60 -0
- package/dist/hooks/useFilterSort.js +362 -0
- package/dist/hooks/useFilterSort.test.d.ts +1 -0
- package/dist/hooks/useFilterSort.test.js +333 -0
- package/dist/hooks/useRealtimeSync.d.ts +1 -0
- package/dist/hooks/useRealtimeSync.js +24 -9
- package/dist/hooks/useRealtimeSync.test.js +28 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.d.ts +1 -0
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +34 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +65 -103
- package/dist/hooks/useSyncOrchestrator.js +1538 -670
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +700 -174
- package/dist/hooks/useTaskData.d.ts +35 -0
- package/dist/hooks/useTaskData.js +239 -0
- package/dist/hooks/useTaskData.test.d.ts +1 -0
- package/dist/hooks/useTaskData.test.js +119 -0
- package/dist/hooks/useTaskMutations.d.ts +46 -0
- package/dist/hooks/useTaskMutations.js +472 -0
- package/dist/hooks/useTaskMutations.test.d.ts +1 -0
- package/dist/hooks/useTaskMutations.test.js +80 -0
- package/dist/hooks/useTaskforce.contract.test.d.ts +1 -0
- package/dist/hooks/useTaskforce.contract.test.js +7 -0
- package/dist/hooks/useTaskforce.d.ts +260 -295
- package/dist/hooks/useTaskforce.js +1954 -2623
- package/dist/hooks/useTaskforce.priority-filters.test.js +6 -11
- package/dist/hooks/useTaskforce.sync-behavior.test.d.ts +1 -0
- package/dist/hooks/useTaskforce.sync-behavior.test.js +1120 -0
- package/dist/hooks/useUiNotice.d.ts +16 -0
- package/dist/hooks/useUiNotice.js +49 -0
- package/dist/hooks/useWorkspaceSyncController.d.ts +23 -46
- package/dist/hooks/useWorkspaceSyncController.js +280 -461
- package/dist/hooks/useWorkspaceSyncController.test.js +106 -199
- package/dist/hooks/workspace/useTaskforceApiRouting.d.ts +8 -0
- package/dist/hooks/workspace/useTaskforceApiRouting.js +91 -0
- package/dist/hooks/workspace/useTaskforceApiRouting.test.d.ts +1 -0
- package/dist/hooks/workspace/useTaskforceApiRouting.test.js +60 -0
- package/dist/hooks/workspace/useWorkspaceSyncController.d.ts +1 -0
- package/dist/hooks/workspace/useWorkspaceSyncController.js +1 -0
- package/dist/hooks/workspace/workspaceLocalState.d.ts +30 -0
- package/dist/hooks/workspace/workspaceLocalState.js +101 -0
- package/dist/localization/locales/en-US.d.ts +1 -8
- package/dist/localization/locales/en-US.js +4 -11
- package/dist/localization/locales/es-419.js +2 -9
- package/dist/localization/locales/pt-BR.js +2 -9
- package/dist/localization/localizationCatalogs.test.d.ts +1 -0
- package/dist/localization/localizationCatalogs.test.js +37 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +9 -15
- package/dist/mcp/clientIntegrations.d.ts +33 -0
- package/dist/mcp/clientIntegrations.js +199 -0
- package/dist/mcp/clientIntegrations.test.d.ts +1 -0
- package/dist/mcp/clientIntegrations.test.js +110 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +2 -0
- package/dist/mcp/optionResolution.test.js +6 -2
- package/dist/mcp/runtime.d.ts +120 -0
- package/dist/mcp/runtime.js +5668 -0
- package/dist/mcp/runtime.test.d.ts +1 -0
- package/dist/mcp/runtime.test.js +3293 -0
- package/dist/mcp/server.js +2 -1793
- package/dist/mcp/structuredCommentValidation.test.d.ts +1 -0
- package/dist/{__tests__ → mcp}/structuredCommentValidation.test.js +1 -1
- package/dist/mcp/structuredDocValidation.js +1 -2
- package/dist/mcp/structuredDocValidation.test.d.ts +1 -0
- package/dist/{__tests__ → mcp}/structuredDocValidation.test.js +9 -4
- package/dist/mcp/toolRegistry.d.ts +570 -0
- package/dist/mcp/toolRegistry.js +90 -0
- package/dist/mcp/toolRegistry.test.d.ts +1 -0
- package/dist/mcp/toolRegistry.test.js +30 -0
- package/dist/migrations/billingSchemaParity.test.js +111 -0
- package/dist/migrations/taskSchemaMigrations.d.ts +5 -0
- package/dist/migrations/taskSchemaMigrations.js +681 -39
- package/dist/migrations/taskSchemaMigrations.test.d.ts +1 -0
- package/dist/migrations/taskSchemaMigrations.test.js +210 -0
- package/dist/plugins/vite.js +13 -7
- package/dist/public/apple-touch-icon.png +0 -0
- package/dist/public/archive/phoenix/apple-touch-icon.png +0 -0
- package/dist/public/archive/phoenix/favicon.ico +0 -0
- package/dist/public/archive/phoenix/site.webmanifest +1 -0
- package/dist/public/favicon-96x96.png +0 -0
- package/dist/public/favicon.ico +0 -0
- package/dist/public/favicon.svg +17 -0
- package/dist/public/site.webmanifest +21 -1
- package/dist/public/web-app-manifest-192x192.png +0 -0
- package/dist/public/web-app-manifest-512x512.png +0 -0
- package/dist/releaseVersion.test.d.ts +1 -0
- package/dist/{__tests__/releaseVersion.test.js → releaseVersion.test.js} +1 -1
- package/dist/resources/templates/WORKFLOW_V2_POLICY.md +1 -1
- package/dist/resources/templates/workflow-sources/workflowDocs.mjs +62 -52
- package/dist/resources/templates/workflows/collaborate.yaml +11 -5
- package/dist/resources/templates/workflows/do.yaml +8 -2
- package/dist/resources/templates/workflows/evaluate.yaml +13 -7
- package/dist/resources/templates/workflows/execute.yaml +40 -23
- package/dist/resources/templates/workflows/plan.yaml +15 -8
- package/dist/resources/templates/workflows/review.yaml +14 -8
- package/dist/runtime/appGateDefinitions.d.ts +32 -0
- package/dist/runtime/appGateDefinitions.js +32 -0
- package/dist/runtime/appGates.d.ts +29 -0
- package/dist/runtime/appGates.js +53 -0
- package/dist/runtime/appGates.test.d.ts +1 -0
- package/dist/runtime/appGates.test.js +42 -0
- package/dist/runtime/migrationFlags.d.ts +1 -0
- package/dist/runtime/migrationFlags.js +1 -0
- package/dist/server/annotatedAttachmentsRoutes.test.d.ts +1 -0
- package/dist/server/annotatedAttachmentsRoutes.test.js +473 -0
- package/dist/server/auth.js +14 -2
- package/dist/server/auth.test.js +21 -1
- package/dist/server/cors.js +11 -5
- package/dist/server/cors.test.js +6 -0
- package/dist/server/documentReviewRoutes.test.d.ts +1 -0
- package/dist/server/documentReviewRoutes.test.js +157 -0
- package/dist/server/index.cookieProxy.test.js +46 -1
- package/dist/server/index.d.ts +20 -0
- package/dist/server/index.js +168 -41
- package/dist/server/index.rateLimit.test.js +6 -0
- package/dist/server/index.test.js +133 -2
- package/dist/server/mockStripe.d.ts +72 -0
- package/dist/server/mockStripe.js +357 -0
- package/dist/server/realtimeSyncWs.js +4 -0
- package/dist/server/routes/admin.d.ts +114 -0
- package/dist/server/routes/admin.js +2226 -0
- package/dist/server/routes/annotatedAttachments.d.ts +24 -0
- package/dist/server/routes/annotatedAttachments.js +235 -0
- package/dist/server/routes/auth.d.ts +61 -0
- package/dist/server/routes/auth.js +1296 -0
- package/dist/server/{routes.billing.d.ts → routes/billing.d.ts} +3 -2
- package/dist/server/routes/billing.js +2001 -0
- package/dist/server/routes/billing.test.d.ts +1 -0
- package/dist/server/routes/billing.test.js +1631 -0
- package/dist/server/routes/documentReviews.d.ts +11 -0
- package/dist/server/routes/documentReviews.js +191 -0
- package/dist/server/routes/documents.d.ts +118 -0
- package/dist/server/routes/documents.js +1113 -0
- package/dist/server/routes/resources.d.ts +25 -0
- package/dist/server/routes/resources.js +225 -0
- package/dist/server/routes/shared.d.ts +35 -0
- package/dist/server/routes/shared.js +305 -0
- package/dist/server/routes/shared.test.d.ts +1 -0
- package/dist/server/routes/shared.test.js +32 -0
- package/dist/server/{routes.sync.d.ts → routes/sync.d.ts} +5 -5
- package/dist/server/routes/sync.integration.test.d.ts +1 -0
- package/dist/server/routes/sync.integration.test.js +2003 -0
- package/dist/server/routes/sync.js +3143 -0
- package/dist/server/routes/tasks.d.ts +72 -0
- package/dist/server/routes/tasks.js +1252 -0
- package/dist/server/routes/workspaces.d.ts +32 -0
- package/dist/server/routes/workspaces.js +307 -0
- package/dist/server/routes.d.ts +8 -9
- package/dist/server/routes.js +968 -4092
- package/dist/server/routes.test.js +9043 -2415
- package/dist/server/runtimeMode.d.ts +1 -1
- package/dist/server/runtimeMode.js +3 -18
- package/dist/server/runtimeMode.test.js +3 -3
- package/dist/server/securityHeaders.d.ts +1 -1
- package/dist/server/securityHeaders.js +65 -6
- package/dist/server/securityHeaders.test.js +40 -2
- package/dist/services/templateEngine.test.d.ts +1 -0
- package/dist/{__tests__ → services}/templateEngine.test.js +1 -1
- package/dist/services/templates.test.d.ts +1 -0
- package/dist/{__tests__ → services}/templates.test.js +1 -1
- package/dist/services/workflowDocumentFixtures.test.d.ts +1 -0
- package/dist/{__tests__ → services}/workflowDocumentFixtures.test.js +4 -4
- package/dist/services/workflowExportMatrix.test.d.ts +1 -0
- package/dist/{__tests__ → services}/workflowExportMatrix.test.js +1 -1
- package/dist/services/workflowExportSnapshots.test.d.ts +1 -0
- package/dist/services/workflowGeneration.test.d.ts +1 -0
- package/dist/services/workflowSimulation.test.d.ts +1 -0
- package/dist/services/workflowSimulation.test.js +113 -0
- package/dist/services/workflowV2Policy.test.d.ts +1 -0
- package/dist/{__tests__ → services}/workflowV2Policy.test.js +2 -2
- package/dist/shared/aiProfileSurfaceType.d.ts +9 -0
- package/dist/shared/aiProfileSurfaceType.js +49 -0
- package/dist/shared/customTaxonomyLibrary.d.ts +11 -0
- package/dist/shared/customTaxonomyLibrary.js +276 -0
- package/dist/shared/customTaxonomyLibrary.test.d.ts +1 -0
- package/dist/shared/customTaxonomyLibrary.test.js +20 -0
- package/dist/shared/taxonomyCatalog.d.ts +19 -0
- package/dist/shared/taxonomyCatalog.js +36 -0
- package/dist/shared/taxonomyDefaults.d.ts +29 -0
- package/dist/shared/taxonomyDefaults.js +26 -0
- package/dist/shared/taxonomyLibrary.d.ts +15 -0
- package/dist/shared/taxonomyLibrary.js +98 -0
- package/dist/shared/taxonomyLibrary.test.d.ts +1 -0
- package/dist/shared/taxonomyLibrary.test.js +26 -0
- package/dist/shared/taxonomyState.d.ts +36 -0
- package/dist/shared/taxonomyState.js +73 -0
- package/dist/shared/taxonomyState.test.d.ts +1 -0
- package/dist/shared/taxonomyState.test.js +71 -0
- package/dist/shared/workflowDefaults.d.ts +9 -0
- package/dist/shared/workflowDefaults.js +11 -0
- package/dist/storage/documentIntegrity.d.ts +2 -0
- package/dist/storage/documentIntegrity.js +34 -17
- package/dist/storage/documentPurge.d.ts +2 -1
- package/dist/storage/documentPurge.js +35 -8
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/objectStorage.d.ts +1 -1
- package/dist/storage/objectStorageClient.d.ts +6 -4
- package/dist/storage/objectStorageClient.js +77 -29
- package/dist/storage/objectStorageClient.test.d.ts +1 -0
- package/dist/storage/objectStorageClient.test.js +57 -0
- package/dist/storage/objectStorageConfig.js +3 -1
- package/dist/storage/objectStorageConfig.test.d.ts +1 -0
- package/dist/storage/objectStorageConfig.test.js +38 -0
- package/dist/storage/postgresAdapter.d.ts +5 -0
- package/dist/storage/postgresAdapter.js +2 -2
- package/dist/storage/postgresAdapter.test.d.ts +1 -0
- package/dist/storage/postgresAdapter.test.js +29 -0
- package/dist/storage/taskAssetStore.d.ts +7 -4
- package/dist/storage/taskAssetStore.js +65 -60
- package/dist/storage/workspaceAssetStore.d.ts +122 -0
- package/dist/storage/workspaceAssetStore.js +496 -0
- package/dist/storage/workspaceAssetStore.test.d.ts +1 -0
- package/dist/storage/workspaceAssetStore.test.js +501 -0
- package/dist/sync/cloudSyncApi.d.ts +33 -2
- package/dist/sync/cloudSyncApi.js +37 -15
- package/dist/sync/cloudSyncApi.test.js +60 -3
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/index.js +3 -0
- package/dist/sync/syncService.d.ts +25 -0
- package/dist/sync/syncService.js +184 -16
- package/dist/sync/syncService.test.js +289 -25
- package/dist/sync/workspaceRepair.d.ts +114 -0
- package/dist/sync/workspaceRepair.js +566 -0
- package/dist/sync/workspaceSyncModel.d.ts +230 -2
- package/dist/sync/workspaceSyncModel.js +456 -17
- package/dist/sync/workspaceSyncModel.test.js +642 -0
- package/dist/sync/workspaceSyncState.d.ts +12 -0
- package/dist/sync/workspaceSyncState.js +12 -0
- package/dist/sync/workspaceSyncSurface.d.ts +33 -0
- package/dist/sync/workspaceSyncSurface.js +74 -0
- package/dist/types.d.ts +143 -18
- package/dist/types.js +9 -24
- package/dist/ui/assets/AgentsModule-TVmCgizS.js +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-CAyhnPeR.js +3 -0
- package/dist/ui/assets/DocumentWorkspace-BOCw5e4D.js +250 -0
- package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +1 -0
- package/dist/ui/assets/InitiativesModule-CN0r02Oi.js +1 -0
- package/dist/ui/assets/PlansPage-BlVC_lRq.css +1 -0
- package/dist/ui/assets/PlansPage-FcQSNLZd.js +1 -0
- package/dist/ui/assets/TaskSettings-BwB9NtmJ.css +1 -0
- package/dist/ui/assets/TaskSettings-Cw5zYD1L.js +8 -0
- package/dist/ui/assets/WorkflowsModule-CY9R2zPB.js +5 -0
- package/dist/ui/assets/index-Bzqy5QGm.js +6 -0
- package/dist/ui/assets/index-DxRiI3tY.css +1 -0
- package/dist/ui/assets/vendor-dnd-DRzYolkg.js +5 -0
- package/dist/ui/assets/{vendor-icons-ZFG3SGwo.js → vendor-icons-QZyhEwgT.js} +1 -1
- package/dist/ui/assets/{vendor-markdown-CKYkQHfj.js → vendor-markdown-BUxTU7dS.js} +1 -1
- package/dist/ui/assets/vendor-react-CKJs5o3c.js +61 -0
- package/dist/ui/assets/vendor-router-BbWMxlnO.js +3 -0
- package/dist/ui/branding/logos/logo.png +0 -0
- package/dist/ui/branding/logos/logo_dark.svg +27 -0
- package/dist/ui/branding/logos/logo_favicon.svg +35 -0
- package/dist/ui/branding/logos/logo_light.svg +27 -0
- package/dist/ui/favicon/apple-touch-icon.png +0 -0
- package/dist/ui/favicon/archive/phoenix/apple-touch-icon.png +0 -0
- package/dist/ui/favicon/archive/phoenix/favicon.ico +0 -0
- package/dist/ui/favicon/archive/phoenix/site.webmanifest +1 -0
- package/dist/ui/favicon/favicon-96x96.png +0 -0
- package/dist/ui/favicon/favicon.ico +0 -0
- package/dist/ui/favicon/favicon.svg +17 -0
- package/dist/ui/favicon/site.webmanifest +21 -1
- package/dist/ui/favicon/web-app-manifest-192x192.png +0 -0
- package/dist/ui/favicon/web-app-manifest-512x512.png +0 -0
- package/dist/ui/index.html +10 -7
- package/dist/ui/product/screen_01.png +0 -0
- package/dist/ui/product/screen_02.png +0 -0
- package/dist/ui/product/screen_03.png +0 -0
- package/dist/utils/annotatedAttachments.d.ts +20 -0
- package/dist/utils/annotatedAttachments.js +74 -0
- package/dist/utils/annotatedAttachments.test.d.ts +1 -0
- package/dist/utils/annotatedAttachments.test.js +108 -0
- package/dist/utils/assignees.d.ts +26 -0
- package/dist/utils/assignees.js +132 -0
- package/dist/utils/canonicalAssetKind.d.ts +16 -0
- package/dist/utils/canonicalAssetKind.js +39 -0
- package/dist/utils/canonicalAssetKind.test.d.ts +1 -0
- package/dist/utils/canonicalAssetKind.test.js +24 -0
- package/dist/utils/coalescedRefresh.d.ts +5 -0
- package/dist/utils/coalescedRefresh.js +16 -0
- package/dist/utils/coalescedRefresh.test.d.ts +1 -0
- package/dist/utils/coalescedRefresh.test.js +30 -0
- package/dist/utils/constants.d.ts +16 -3
- package/dist/utils/constants.icons.test.js +2 -1
- package/dist/utils/constants.js +70 -13
- package/dist/utils/contextAssetEvents.d.ts +7 -0
- package/dist/utils/contextAssetEvents.js +6 -0
- package/dist/utils/contextFiles.d.ts +14 -3
- package/dist/utils/contextFiles.js +40 -8
- package/dist/utils/documentManagerApi.d.ts +6 -0
- package/dist/utils/documentManagerApi.js +19 -0
- package/dist/utils/documentNames.d.ts +24 -0
- package/dist/utils/documentNames.js +91 -0
- package/dist/utils/documentReferences.d.ts +9 -0
- package/dist/utils/documentReferences.js +11 -0
- package/dist/utils/entityReferences.d.ts +9 -0
- package/dist/utils/entityReferences.js +32 -0
- package/dist/utils/imageReferences.d.ts +9 -0
- package/dist/utils/imageReferences.js +11 -0
- package/dist/utils/planParser.d.ts +2 -2
- package/dist/utils/planParser.js +11 -11
- package/dist/utils/planParser.test.d.ts +1 -0
- package/dist/{__tests__ → utils}/planParser.test.js +22 -22
- package/dist/utils/planningReferences.d.ts +17 -0
- package/dist/utils/planningReferences.js +30 -0
- package/dist/utils/planningReferences.test.d.ts +1 -0
- package/dist/utils/planningReferences.test.js +17 -0
- package/dist/utils/taskActivity.d.ts +9 -0
- package/dist/utils/taskActivity.js +147 -0
- package/dist/utils/taskActivity.test.d.ts +1 -0
- package/dist/utils/taskActivity.test.js +34 -0
- package/dist/utils/taskDraftDefaults.d.ts +21 -0
- package/dist/utils/taskDraftDefaults.js +40 -0
- package/dist/utils/taskDraftDefaults.test.d.ts +1 -0
- package/dist/utils/taskDraftDefaults.test.js +34 -0
- package/dist/utils/taskNormalization.d.ts +25 -0
- package/dist/utils/taskNormalization.js +161 -0
- package/dist/utils/taskNormalization.test.d.ts +1 -0
- package/dist/utils/taskNormalization.test.js +158 -0
- package/dist/utils/taskReferences.d.ts +5 -0
- package/dist/utils/taskReferences.js +11 -0
- package/dist/utils/taskforceApiClient.d.ts +2 -0
- package/dist/utils/taskforceApiClient.js +101 -3
- package/dist/utils/taskforceApiClient.test.d.ts +1 -0
- package/dist/utils/taskforceApiClient.test.js +44 -0
- package/dist/utils/taxonomyFilterOptions.d.ts +2 -0
- package/dist/utils/taxonomyFilterOptions.js +17 -0
- package/dist/utils/taxonomyFormDefaults.d.ts +3 -0
- package/dist/utils/taxonomyFormDefaults.js +28 -0
- package/dist/utils/taxonomyFormDefaults.test.d.ts +1 -0
- package/dist/utils/taxonomyFormDefaults.test.js +63 -0
- package/dist/utils/taxonomyLifecycle.d.ts +3 -0
- package/dist/utils/taxonomyLifecycle.js +17 -0
- package/dist/utils/taxonomySorting.d.ts +7 -0
- package/dist/utils/taxonomySorting.js +74 -0
- package/dist/utils/theme.d.ts +15 -0
- package/dist/utils/theme.js +23 -0
- package/dist/utils/uiNotice.d.ts +7 -0
- package/dist/utils/uiNotice.js +6 -0
- package/dist/utils/uiNotice.test.d.ts +1 -0
- package/dist/utils/uiNotice.test.js +18 -0
- package/dist/utils/workspaceIdentity.d.ts +14 -0
- package/dist/utils/workspaceIdentity.js +21 -0
- package/dist/utils/workspaceIdentity.test.js +30 -1
- package/dist/utils/workspaceSyncPresentation.d.ts +20 -0
- package/dist/utils/workspaceSyncPresentation.js +21 -0
- package/dist/utils/workspaceSyncPresentation.test.d.ts +1 -0
- package/dist/utils/workspaceSyncPresentation.test.js +36 -0
- package/package.json +28 -23
- package/scripts/bootstrap-playwright-auth.mjs +155 -0
- package/scripts/check-compat-boundaries.mjs +41 -0
- package/scripts/check-env.ts +9 -2
- package/scripts/debug-login.mjs +32 -0
- package/scripts/migrate-planning-model.ts +233 -0
- package/scripts/migrate-task-data-assets.mjs +408 -0
- package/scripts/playwright-auth.sh +11 -0
- package/scripts/run-dev-proxy.sh +2 -0
- package/scripts/update-homebrew-formula.sh +20 -3
- package/scripts/with-esbuild.sh +11 -0
- package/src/resources/templates/WORKFLOW_V2_POLICY.md +1 -1
- package/src/resources/templates/workflow-sources/workflowDocs.mjs +62 -52
- package/src/resources/templates/workflows/collaborate.yaml +11 -5
- package/src/resources/templates/workflows/do.yaml +8 -2
- package/src/resources/templates/workflows/evaluate.yaml +13 -7
- package/src/resources/templates/workflows/execute.yaml +40 -23
- package/src/resources/templates/workflows/plan.yaml +15 -8
- package/src/resources/templates/workflows/review.yaml +14 -8
- package/dist/TaskforceScreenshot.d.ts +0 -11
- package/dist/TaskforceScreenshot.js +0 -113
- package/dist/compat/priorityCompat.d.ts +0 -2
- package/dist/compat/priorityCompat.js +0 -24
- package/dist/components/features/settings/TaxonomyManager 2.d.ts +0 -52
- package/dist/components/features/settings/TaxonomyManager 2.js +0 -75
- package/dist/components/task/TaskKanban 2.d.ts +0 -76
- package/dist/components/task/TaskKanban 2.js +0 -1004
- package/dist/components/task/TaskScreenshotSection.d.ts +0 -15
- package/dist/components/task/TaskScreenshotSection.js +0 -22
- package/dist/components/views/StandaloneLayout 2.d.ts +0 -8
- package/dist/components/views/StandaloneLayout 2.js +0 -1660
- package/dist/core/InitiativeTemplates.test.js +0 -50
- package/dist/core/ParentLifecycle.test.js +0 -98
- package/dist/migrations/clientMigrations.d.ts +0 -9
- package/dist/migrations/clientMigrations.js +0 -74
- package/dist/migrations/flags.d.ts +0 -6
- package/dist/migrations/flags.js +0 -8
- package/dist/migrations/taskMigrations.d.ts +0 -71
- package/dist/migrations/taskMigrations.js +0 -236
- package/dist/migrations/taskMigrations.test.js +0 -34
- package/dist/server/routes.billing.js +0 -991
- package/dist/server/routes.billing.test.js +0 -387
- package/dist/server/routes.sync.integration.test.js +0 -470
- package/dist/server/routes.sync.js +0 -1141
- package/dist/soak/syncSoakHarness.d.ts +0 -6
- package/dist/soak/syncSoakHarness.js +0 -10
- package/dist/ui/assets/index-0W5vyCpn.css +0 -1
- package/dist/ui/assets/index-MCZuNNfn.js +0 -23
- package/dist/ui/assets/logo_white_01-0Vx-iIIv.png +0 -0
- package/dist/ui/assets/vendor-dnd-BI1K77C1.js +0 -5
- package/dist/ui/assets/vendor-react-DdIDsh7f.js +0 -9
- package/dist/ui/assets/vendor-router-BtiNU7Dl.js +0 -3
- package/dist/utils/taskHierarchy.d.ts +0 -4
- package/dist/utils/taskHierarchy.js +0 -69
- package/dist/utils/taskHierarchy.test.js +0 -38
- package/dist/utils/taskParenting.d.ts +0 -3
- package/dist/utils/taskParenting.js +0 -24
- package/dist/utils/taskParenting.test.js +0 -21
- package/dist/utils/taskRelations.d.ts +0 -6
- package/dist/utils/taskRelations.js +0 -24
- package/dist/utils/taskRelations.test.js +0 -34
- package/scripts/check-compat-boundaries.js +0 -48
- /package/dist/{__tests__/planParser.test.d.ts → TaskforceCore.filter-persistence.test.d.ts} +0 -0
- /package/dist/{__tests__/releaseVersion.test.d.ts → TaskforceCore.routing.test.d.ts} +0 -0
- /package/dist/{__tests__/structuredCommentValidation.test.d.ts → annotatedAttachments/AnnotatedAttachmentSessionStore.test.d.ts} +0 -0
- /package/dist/{__tests__/structuredDocValidation.test.d.ts → annotatedAttachments/payload.test.d.ts} +0 -0
- /package/dist/{__tests__/templateEngine.test.d.ts → annotatedAttachments/types.js} +0 -0
- /package/dist/{__tests__/templates.test.d.ts → compat/workspaceSyncCompat.test.d.ts} +0 -0
- /package/dist/{__tests__/workflowDocumentFixtures.test.d.ts → components/features/AgentsModule.test.d.ts} +0 -0
- /package/dist/{__tests__/workflowExportMatrix.test.d.ts → components/features/AnnotatedAttachmentWorkspace.test.d.ts} +0 -0
- /package/dist/{__tests__/workflowExportSnapshots.test.d.ts → components/features/DocumentWorkspace.test.d.ts} +0 -0
- /package/dist/{__tests__/workflowGeneration.test.d.ts → components/features/settings/TaxonomyLibraryManager.test.d.ts} +0 -0
- /package/dist/{__tests__/workflowV2Policy.test.d.ts → components/task/TaskActionHeader.test.d.ts} +0 -0
- /package/dist/{core/InitiativeTemplates.test.d.ts → components/task/TaskKanban.planning-drop.test.d.ts} +0 -0
- /package/dist/{core/ParentLifecycle.test.d.ts → components/task/TaskStatusActions.test.d.ts} +0 -0
- /package/dist/{migrations/taskMigrations.test.d.ts → components/ui/TopNoticeLayer.test.d.ts} +0 -0
- /package/dist/{server/routes.billing.test.d.ts → components/views/AppShellHeader.test.d.ts} +0 -0
- /package/dist/{server/routes.sync.integration.test.d.ts → components/views/PlanComparisonPage.test.d.ts} +0 -0
- /package/dist/{utils/taskHierarchy.test.d.ts → components/views/chrome/WorkspaceChromeSections.test.d.ts} +0 -0
- /package/dist/{utils/taskParenting.test.d.ts → components/views/chrome/workspaceChrome.test.d.ts} +0 -0
- /package/dist/{utils/taskRelations.test.d.ts → components/views/panels/DocumentFilterToolbar.test.d.ts} +0 -0
- /package/dist/public/{android-chrome-192x192.png → archive/phoenix/android-chrome-192x192.png} +0 -0
- /package/dist/public/{android-chrome-512x512.png → archive/phoenix/android-chrome-512x512.png} +0 -0
- /package/dist/public/{favicon-16x16.png → archive/phoenix/favicon-16x16.png} +0 -0
- /package/dist/public/{favicon-32x32.png → archive/phoenix/favicon-32x32.png} +0 -0
- /package/dist/public/{favicon.png → archive/phoenix/favicon.png} +0 -0
- /package/dist/{__tests__ → services}/workflowExportSnapshots.test.js +0 -0
- /package/dist/{__tests__ → services}/workflowGeneration.test.js +0 -0
- /package/dist/ui/favicon/{android-chrome-192x192.png → archive/phoenix/android-chrome-192x192.png} +0 -0
- /package/dist/ui/favicon/{android-chrome-512x512.png → archive/phoenix/android-chrome-512x512.png} +0 -0
- /package/dist/ui/favicon/{favicon-16x16.png → archive/phoenix/favicon-16x16.png} +0 -0
- /package/dist/ui/favicon/{favicon-32x32.png → archive/phoenix/favicon-32x32.png} +0 -0
- /package/dist/ui/favicon/{favicon.png → archive/phoenix/favicon.png} +0 -0
|
@@ -1,117 +1,197 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
2
|
-
import React, { useMemo, useCallback, useState, useEffect, useRef } from 'react';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React, { useMemo, useCallback, useState, useEffect, useRef, useLayoutEffect, Suspense } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
import { useLocation, useNavigate } from 'react-router-dom';
|
|
5
|
-
import { Settings, Plus,
|
|
6
|
-
import { STATUS_OPTIONS } from '../../utils/constants';
|
|
7
|
-
import { MultiSelectFilter } from '../ui/MultiSelectFilter';
|
|
5
|
+
import { Settings, Plus, Filter, Layers, User, Maximize2, Minimize2, Check, ChevronLeft, ArrowUp, ArrowDown, ArrowUpDown, AlertTriangle, Save, Loader2, Folder, FileText, LogOut, NotebookTabs, ClipboardCheck, Cloud, CloudOff, RefreshCw, Users, Copy, ChevronsUp, ChevronsDown, EyeOff, Zap, HelpCircle, Bot, Image, Workflow } from 'lucide-react';
|
|
6
|
+
import { STATUS_OPTIONS, getStatusDefinition } from '../../utils/constants';
|
|
8
7
|
import styles from '../../Taskforce.module.css';
|
|
8
|
+
import shellStyles from './WorkspaceShell.module.css';
|
|
9
|
+
import accountStyles from './StandaloneAccount.module.css';
|
|
10
|
+
import authStyles from './AuthShell.module.css';
|
|
11
|
+
import folderBrowserStyles from './FolderBrowser.module.css';
|
|
12
|
+
import managementStyles from './StandaloneManagement.module.css';
|
|
13
|
+
import syncStatusStyles from './SyncStatusModal.module.css';
|
|
9
14
|
import modalStyles from '../ui/Modal.module.css';
|
|
10
15
|
import { Modal } from '../ui/Modal';
|
|
11
16
|
import { TaskForm } from '../task/TaskForm';
|
|
12
|
-
import { TaskSettings } from '../features/TaskSettings';
|
|
13
|
-
import { DocumentWorkspace } from '../features/DocumentWorkspace';
|
|
14
17
|
import { TaskKanban } from '../task/TaskKanban';
|
|
15
18
|
import { TaskActionHeader } from '../task/TaskActionHeader';
|
|
19
|
+
import { TopNoticeLayer } from '../ui/TopNoticeLayer';
|
|
16
20
|
import { TypeMeta } from '../../utils/constants';
|
|
17
|
-
import {
|
|
21
|
+
import { resolveDraftDefaultsForGrouping } from '../../utils/taskDraftDefaults';
|
|
18
22
|
import { formatDate } from '../../utils/formatting';
|
|
23
|
+
import { resolveHeaderWorkspaceSyncPresentation } from '../../utils/workspaceSyncPresentation';
|
|
24
|
+
import { getTaskReferenceLabel, parseTaskReference } from '../../utils/taskReferences';
|
|
25
|
+
import { formatInitiativeReference, formatWorkstreamReference, parseWorkstreamReference, resolveInitiativeReference } from '../../utils/planningReferences';
|
|
26
|
+
import { DEFAULT_TYPE_VALUE } from '../../shared/taxonomyDefaults.js';
|
|
27
|
+
import { getImageReferenceLabel } from '../../utils/imageReferences';
|
|
28
|
+
import { buildCustomTaxonomySortOptions } from '../../utils/taxonomySorting.js';
|
|
19
29
|
import { getLocale, t } from '../../localization';
|
|
20
|
-
import
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
import { resolveAnnotatedAttachmentDetail } from '../../utils/annotatedAttachments';
|
|
31
|
+
import { AppShellHeader } from './AppShellHeader';
|
|
32
|
+
import { getTaskforceCloudEnvironmentConfig, parseTaskforceCloudEnvironment, resolveTaskforceCloudEnvironmentFromBaseUrl } from '../../config/cloudEnvironment';
|
|
33
|
+
import { FilterToolbar } from './panels/FilterToolbar';
|
|
34
|
+
import { DocumentFilterToolbar } from './panels/DocumentFilterToolbar';
|
|
35
|
+
import { PlanningDrawer, getPlanningDrawerWidthPx, } from './panels/PlanningDrawer';
|
|
36
|
+
import { getWorkspaceChromeDefinition, getWorkspaceModuleDefinitions, resolveWorkspaceModule, } from './chrome/workspaceChrome';
|
|
37
|
+
import { EmptyWorkspaceFilterBar, WorkspaceHeaderActions, WorkspaceHeaderDivider, WorkspaceHeaderSearch, WorkspaceHeaderSelect, WorkspacePrimaryAction, } from './chrome/WorkspaceChromeSections';
|
|
38
|
+
import { ScheduleSidebar, SCHEDULE_SIDEBAR_WIDTH_PX } from './panels/ScheduleSidebar';
|
|
39
|
+
import { DOCUMENT_ATTACHMENT_FILTER_OPTIONS, DOCUMENT_TYPE_FILTER_OPTIONS, } from '../features/documentWorkspaceFilters';
|
|
40
|
+
import { canManageTeamWorkspace as computeCanManageTeamWorkspace, canOpenTeamManagement as computeCanOpenTeamManagement, resolveTeamPlanModeFromEntitlements } from './teamManagementAccess';
|
|
41
|
+
import { toDateOnlyLocal, parseDateOnlyLocal, startOfWeek, getDayOffsetFromWeekStart, addDays, dateOnlyToIsoWeekKey, SCHEDULE_DAY_KEYS, SCHEDULE_DAY_LABELS, } from './panels/scheduleUtils';
|
|
42
|
+
import { AGENTS_WORKSPACE_FEATURE_KEY, ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY, DOCUMENT_WORKSPACE_FEATURE_KEY, INITIATIVES_WORKSPACE_FEATURE_KEY, WORKFLOWS_WORKSPACE_FEATURE_KEY, resolveFeatureAccess, } from '../../runtime/appGates';
|
|
43
|
+
// Heavy feature modules — lazy-loaded to reduce the initial app chunk size.
|
|
44
|
+
const TaskSettings = React.lazy(() => import('../features/TaskSettings').then(m => ({ default: m.TaskSettings })));
|
|
45
|
+
const AnnotatedAttachmentWorkspaceShell = React.lazy(() => import('../features/AnnotatedAttachmentWorkspace').then(m => ({ default: m.AnnotatedAttachmentWorkspaceShell })));
|
|
46
|
+
const DocumentWorkspaceShell = React.lazy(() => import('../features/DocumentWorkspace').then(m => ({ default: m.DocumentWorkspaceShell })));
|
|
47
|
+
const WorkflowsModule = React.lazy(() => import('../features/WorkflowsModule').then(m => ({ default: m.WorkflowsModule })));
|
|
48
|
+
const AgentsModule = React.lazy(() => import('../features/AgentsModule').then(m => ({ default: m.AgentsModule })));
|
|
49
|
+
const InitiativesModule = React.lazy(() => import('../features/InitiativesModule').then(m => ({ default: m.InitiativesModule })));
|
|
50
|
+
const PlansPage = React.lazy(() => import('./PlansPage').then(m => ({ default: m.PlansPage })));
|
|
51
|
+
function getSyncEventStableKey(event, index = 0) {
|
|
52
|
+
const explicitId = String(event.id ?? '').trim();
|
|
53
|
+
if (explicitId)
|
|
54
|
+
return `id:${explicitId}`;
|
|
55
|
+
return [
|
|
56
|
+
String(event.occurredAt || '').trim() || 'unknown-time',
|
|
57
|
+
String(event.eventType || '').trim() || 'sync',
|
|
58
|
+
String(event.status || '').trim() || 'unknown-status',
|
|
59
|
+
String(event.statusCode ?? ''),
|
|
60
|
+
String(event.changeCount ?? ''),
|
|
61
|
+
String(event.requestMs ?? ''),
|
|
62
|
+
String(event.errorMessage || '').trim(),
|
|
63
|
+
JSON.stringify(event.details || null),
|
|
64
|
+
String(index)
|
|
65
|
+
].join('|');
|
|
41
66
|
}
|
|
42
|
-
function
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
function mergeRecentSyncEvents(previous, incoming, limit = 15) {
|
|
68
|
+
const merged = new Map();
|
|
69
|
+
for (const [index, event] of previous.entries()) {
|
|
70
|
+
merged.set(getSyncEventStableKey(event, index), event);
|
|
71
|
+
}
|
|
72
|
+
for (const [index, event] of incoming.entries()) {
|
|
73
|
+
merged.set(getSyncEventStableKey(event, index), event);
|
|
74
|
+
}
|
|
75
|
+
return Array.from(merged.values())
|
|
76
|
+
.sort((left, right) => String(right.occurredAt || '').localeCompare(String(left.occurredAt || '')))
|
|
77
|
+
.slice(0, Math.max(1, limit));
|
|
50
78
|
}
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return
|
|
79
|
+
function areSyncEventListsEqual(left, right) {
|
|
80
|
+
if (left.length !== right.length)
|
|
81
|
+
return false;
|
|
82
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
83
|
+
if (getSyncEventStableKey(left[index], index) !== getSyncEventStableKey(right[index], index)) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
60
88
|
}
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
return sundayFirst[dayKey];
|
|
89
|
+
function getActiveReferenceMismatchCount(events) {
|
|
90
|
+
let count = 0;
|
|
91
|
+
for (const event of events) {
|
|
92
|
+
const message = String(event.errorMessage || '').toLowerCase();
|
|
93
|
+
if (message.includes('reference number mismatch')) {
|
|
94
|
+
count += 1;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (String(event.status || '').toLowerCase() === 'success') {
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
73
100
|
}
|
|
74
|
-
|
|
75
|
-
mon: 0,
|
|
76
|
-
tue: 1,
|
|
77
|
-
wed: 2,
|
|
78
|
-
thu: 3,
|
|
79
|
-
fri: 4,
|
|
80
|
-
sat: 5,
|
|
81
|
-
sun: 6
|
|
82
|
-
};
|
|
83
|
-
return mondayFirst[dayKey];
|
|
101
|
+
return count;
|
|
84
102
|
}
|
|
85
|
-
function
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
103
|
+
function getActiveReferenceMismatchEvents(events) {
|
|
104
|
+
const mismatches = [];
|
|
105
|
+
for (const event of events) {
|
|
106
|
+
const message = String(event.errorMessage || '').toLowerCase();
|
|
107
|
+
if (message.includes('reference number mismatch')) {
|
|
108
|
+
mismatches.push(event);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (String(event.status || '').toLowerCase() === 'success') {
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return mismatches;
|
|
89
116
|
}
|
|
90
|
-
function
|
|
91
|
-
const
|
|
92
|
-
if (
|
|
93
|
-
return
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
117
|
+
function resolveConnectedEnvironmentLabel(explicitEnvironment, candidates) {
|
|
118
|
+
const parsedExplicitEnvironment = parseTaskforceCloudEnvironment(explicitEnvironment);
|
|
119
|
+
if (parsedExplicitEnvironment) {
|
|
120
|
+
return getTaskforceCloudEnvironmentConfig(parsedExplicitEnvironment).label.toUpperCase();
|
|
121
|
+
}
|
|
122
|
+
for (const candidate of candidates) {
|
|
123
|
+
const source = String(candidate || '').trim();
|
|
124
|
+
if (!source)
|
|
125
|
+
continue;
|
|
126
|
+
const parsedEnvironment = resolveTaskforceCloudEnvironmentFromBaseUrl(source);
|
|
127
|
+
if (parsedEnvironment) {
|
|
128
|
+
return getTaskforceCloudEnvironmentConfig(parsedEnvironment).label.toUpperCase();
|
|
129
|
+
}
|
|
130
|
+
let hostname = source.toLowerCase();
|
|
131
|
+
try {
|
|
132
|
+
hostname = new URL(source.includes('://') ? source : `https://${source}`).hostname.toLowerCase();
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
hostname = source.toLowerCase();
|
|
136
|
+
}
|
|
137
|
+
if (hostname.includes('localhost') || hostname.includes('127.0.0.1') || hostname.includes('::1')) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
return hostname.toUpperCase();
|
|
141
|
+
}
|
|
142
|
+
return 'UNKNOWN';
|
|
143
|
+
}
|
|
144
|
+
function isDebugModeEnabled() {
|
|
145
|
+
const runtimeOverride = globalThis.__DEBUG_MODE__;
|
|
146
|
+
if (typeof runtimeOverride === 'boolean')
|
|
147
|
+
return runtimeOverride;
|
|
148
|
+
return typeof __DEBUG_MODE__ !== 'undefined' && __DEBUG_MODE__;
|
|
100
149
|
}
|
|
101
150
|
export function StandaloneLayout(props) {
|
|
151
|
+
const WORKSPACE_TOOL_RAIL_WIDTH = 56;
|
|
152
|
+
const WORKSPACE_CONTENT_GUTTER = 6;
|
|
102
153
|
const navigate = useNavigate();
|
|
103
154
|
const location = useLocation();
|
|
155
|
+
const routeQuery = useMemo(() => new URLSearchParams(location.search), [location.search]);
|
|
156
|
+
const isPlansScreen = String(routeQuery.get('screen') || '').trim().toLowerCase() === 'plans';
|
|
157
|
+
const openPlansScreen = useCallback((extraParams) => {
|
|
158
|
+
const params = new URLSearchParams(location.search);
|
|
159
|
+
params.set('screen', 'plans');
|
|
160
|
+
for (const [key, value] of Object.entries(extraParams || {})) {
|
|
161
|
+
if (value === null || value === undefined || String(value).trim() === '') {
|
|
162
|
+
params.delete(key);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
params.set(key, String(value));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
navigate(`/?${params.toString()}${location.hash || ''}`);
|
|
169
|
+
}, [location.hash, location.search, navigate]);
|
|
170
|
+
const closePlansScreen = useCallback(() => {
|
|
171
|
+
const params = new URLSearchParams(location.search);
|
|
172
|
+
params.delete('screen');
|
|
173
|
+
params.delete('gate');
|
|
174
|
+
params.delete('checkout');
|
|
175
|
+
params.delete('planId');
|
|
176
|
+
params.delete('planVersionId');
|
|
177
|
+
params.delete('interval');
|
|
178
|
+
const nextQuery = params.toString();
|
|
179
|
+
navigate(`${location.pathname === '/' ? '/' : location.pathname}${nextQuery ? `?${nextQuery}` : ''}${location.hash || ''}`);
|
|
180
|
+
}, [location.hash, location.pathname, location.search, navigate]);
|
|
181
|
+
const handlePlansBack = useCallback(() => {
|
|
182
|
+
closePlansScreen();
|
|
183
|
+
}, [closePlansScreen]);
|
|
104
184
|
const {
|
|
105
185
|
// State
|
|
106
|
-
activeTab, setActiveTab, activeCategories, groupBy, setGroupBy, emptyColumnMode, setEmptyColumnMode, zenMode, setZenMode, filterStatus, setFilterStatus,
|
|
186
|
+
activeTab, setActiveTab, activeCategories, groupBy, setGroupBy, activeWorkspaceModule, setActiveWorkspaceModule, emptyColumnMode, setEmptyColumnMode, zenMode, setZenMode, filterStatus, setFilterStatus,
|
|
107
187
|
// Data & Actions
|
|
108
188
|
tasks, handleEdit, handleSubmit, resetForm, handleUpdateTask,
|
|
109
189
|
// Form State (destructure all needed for TaskForm)
|
|
110
|
-
editingTaskId, loading, error, title, setTitle, description, setDescription, category, setCategory, type, setType, priority, setPriority, complexity, setComplexity, approach, setApproach, assignee, setAssignee, scheduledDate, setScheduledDate, dueDate, setDueDate,
|
|
190
|
+
editingTaskId, loading, error, title, setTitle, description, setDescription, checklistItems, setChecklistItems, category, setCategory, type, setType, priority, setPriority, complexity, setComplexity, setStatus, approach, setApproach, assignee, setAssignee, scheduledDate, setScheduledDate, dueDate, setDueDate, workstreamInput, setWorkstreamInput, manualComplexityEnabled, checklistDropdownEnabled, showTaskCardStatusLabel, formTaxonomies, setFormTaxonomies, comments, newCommentText, setNewCommentText, attachments, setAttachments, setAttachmentsDirty, descriptionFocused, setDescriptionFocused, showMarkdownHelp, setShowMarkdownHelp, showChecklist, setShowChecklist, showComments, setShowComments,
|
|
111
191
|
// Form Actions
|
|
112
|
-
handleAddComment,
|
|
192
|
+
handleAddComment, handleSetWorkstreamForCurrentTask, handleOpenTaskById,
|
|
113
193
|
// Other Props for Form
|
|
114
|
-
taxonomies, activeTypes, priorities,
|
|
194
|
+
taxonomies, activeTypes, priorities, taxonomyDisplayLabels, approaches, copiedId, handleCopyId, handleToggleInProgress, handleToggleComplete, handleToggleReview, handleToggleCancel, handleArchiveTask, handleDelete, handleUnarchive, handleRestoreDeletedTask, handlePermanentlyDeleteDeletedTask, handleEmptyDeletedTasks, fetchTasks, searchQuery, setSearchQuery, filterCategories, setFilterCategories, filterTypes, setFilterTypes, filterPriorities, setFilterPriorities, filterAssignees, setFilterAssignees, assigneeOptions, taskScope, setTaskScope,
|
|
115
195
|
// filterStatus, setFilterStatus, // This was duplicated, removed one instance
|
|
116
196
|
sortBy, setSortBy, clearFilters, // setSearchQuery, // This was duplicated, removed one instance
|
|
117
197
|
// filterCategories, setFilterCategories, // This was duplicated, removed one instance
|
|
@@ -122,21 +202,19 @@ export function StandaloneLayout(props) {
|
|
|
122
202
|
// clearFilters, // This was duplicated, removed one instance
|
|
123
203
|
// activeTypes, // This was duplicated, removed one instance
|
|
124
204
|
// Settings State & Props
|
|
125
|
-
settingsModel, currentTheme, setCurrentTheme, pathSaved, saveSettings, keyShortcut, setKeyShortcut, globalWeekStartsOn, locale, jsonBackupEnabled, setJsonBackupEnabled,
|
|
205
|
+
settingsModel, configLoaded, currentTheme, setCurrentTheme, pathSaved, saveSettings, keyShortcut, setKeyShortcut, globalWeekStartsOn, locale, jsonBackupEnabled, setJsonBackupEnabled, mcpHostRoot, setMcpHostRoot, settingsSection, setSettingsSection, runtimeMode, workspaceSwitchingEnabled, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, authWorkspaceId, authUserEmail, authUserDisplayName, authSessionResolved, realtimeSyncEnabled, realtimeSyncFlagSource, workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceSyncStatus, workspaceSyncSummary, workspaceSyncRecommendedAction, workspaceSyncBusy, workspaceSyncPendingChanges, saveWorkspaceCloudSyncSettings, pushNotice, userGlobalSyncStatus, workspaceLastSuccessfulSyncAt, workspaceLastPullAt, workspaceLastPushAt, workspaceLastErrorAt, userGlobalSyncError, workspaceLastErrorMessage, retryWorkspaceCloudSync, resetWorkspaceSyncCursorAndPull, getWorkspaceSyncDiagnostics, currentWorkspaceId, currentWorkspaceRole, availableWorkspaces, switchWorkspace, updateCurrentUserProfile, logout, projectRoot, projectName, mcpScriptPath, serverHostRoot,
|
|
126
206
|
// Browser Props
|
|
127
|
-
showFolderBrowser, setShowFolderBrowser, folders, files, currentBrowsePath, fetchFolders, browserTarget, setBrowserTarget,
|
|
207
|
+
showFolderBrowser, setShowFolderBrowser, folders, files, currentBrowsePath, fetchFolders, browserTarget, setBrowserTarget, handleSelectPath, handleAddPath, handleRemovePath,
|
|
128
208
|
// Settings Managers
|
|
129
|
-
handleUpdateCategory, handleRemoveCategory, handleSaveCategory, handleUpdateCategoryIcon, handleUpdateCategoryColor, handleSaveType, handleRemoveType, handleUpdateTaxonomies, handleUpdatePriorities,
|
|
209
|
+
handleUpdateCategory, handleRemoveCategory, handleSaveCategory, handleUpdateCategoryIcon, handleUpdateCategoryColor, handleSaveType, handleRemoveType, handleUpdateTaxonomies, handleUpdatePriorities, pathValidation,
|
|
130
210
|
// Exports
|
|
131
|
-
exportEnvironment, setExportEnvironment, exportWorkflowsPath, setExportWorkflowsPath,
|
|
211
|
+
exportEnvironment, setExportEnvironment, exportWorkflowsPath, setExportWorkflowsPath, exportResult, exportingResource, availableWorkflows, onExportWorkflows, fetchWorkflows, availableEnvironments, initiativeTemplates, fetchInitiativeTemplates, createInitiativeFromTemplate,
|
|
132
212
|
// Misc
|
|
133
|
-
|
|
213
|
+
uiNotice, clearNotice, taskReturnTrail, clearReturnToParentTask, returnToPreviousTask, tasksScrollRef, setTasksScrollPos,
|
|
134
214
|
// Refs
|
|
135
215
|
commentsEndRef,
|
|
136
216
|
// Modals - Unsaved Changes
|
|
137
|
-
unsavedModalOpen, setUnsavedModalOpen, pendingNavigation, handleNavigation, currentTask,
|
|
138
|
-
// Modals - Cancellation
|
|
139
|
-
cancellationPromptTask, cancellationOpenDescendantIds, cancellationMode, setCancellationMode, confirmCancellation, cancelCancellationPrompt, scheduleWarningPrompt, confirmScheduleWarning, cancelScheduleWarning } = props;
|
|
217
|
+
autoSaveState, unsavedModalOpen, setUnsavedModalOpen, pendingNavigation, handleNavigation, currentTask, currentTaskWorkstream, currentTaskInitiative, scheduleWarningPrompt, confirmScheduleWarning, cancelScheduleWarning } = props;
|
|
140
218
|
const resolvedLocale = locale || getLocale();
|
|
141
219
|
useEffect(() => {
|
|
142
220
|
const normalizedProject = String(projectName || '').trim();
|
|
@@ -171,11 +249,6 @@ export function StandaloneLayout(props) {
|
|
|
171
249
|
const [scheduleShowBacklog, setScheduleShowBacklog] = useState(true);
|
|
172
250
|
const [scheduleOnlyExpired, setScheduleOnlyExpired] = useState(false);
|
|
173
251
|
const [scheduleSidebarOpen, setScheduleSidebarOpen] = useState(true);
|
|
174
|
-
const [hierarchySidebarOpen, setHierarchySidebarOpen] = useState(true);
|
|
175
|
-
const [hierarchyRootTaskId, setHierarchyRootTaskId] = useState('');
|
|
176
|
-
const [hierarchyShowUnlinkedColumn, setHierarchyShowUnlinkedColumn] = useState(true);
|
|
177
|
-
const [hierarchyChildOrderMode, setHierarchyChildOrderMode] = useState('manual');
|
|
178
|
-
const [hierarchyRootSearch, setHierarchyRootSearch] = useState('');
|
|
179
252
|
const [scheduleSelectedDate, setScheduleSelectedDate] = useState(toDateOnlyLocal(new Date()));
|
|
180
253
|
const [scheduleCalendarMonth, setScheduleCalendarMonth] = useState(() => {
|
|
181
254
|
const now = new Date();
|
|
@@ -183,10 +256,35 @@ export function StandaloneLayout(props) {
|
|
|
183
256
|
});
|
|
184
257
|
const [scheduleScrollLeft, setScheduleScrollLeft] = useState(0);
|
|
185
258
|
const [showFilters, setShowFilters] = useState(false);
|
|
259
|
+
const [taskModalFullscreen, setTaskModalFullscreen] = useState(false);
|
|
260
|
+
const [planningDrawerOpen, setPlanningDrawerOpen] = useState(false);
|
|
261
|
+
const [planningTreeCollapsed, setPlanningTreeCollapsed] = useState(false);
|
|
262
|
+
const [planningPrimaryCollapsed, setPlanningPrimaryCollapsed] = useState(false);
|
|
263
|
+
const [planningSecondaryCollapsed, setPlanningSecondaryCollapsed] = useState(false);
|
|
264
|
+
const [selectedPlanningInitiativeId, setSelectedPlanningInitiativeId] = useState('');
|
|
265
|
+
const [selectedPlanningWorkstreamId, setSelectedPlanningWorkstreamId] = useState('');
|
|
266
|
+
const [expandedPlanningInitiativeIds, setExpandedPlanningInitiativeIds] = useState(new Set());
|
|
267
|
+
const [planningDrawerDetail, setPlanningDrawerDetail] = useState(null);
|
|
268
|
+
const [planningNestedWorkstreamDetailId, setPlanningNestedWorkstreamDetailId] = useState(null);
|
|
269
|
+
const [planningEditor, setPlanningEditor] = useState(null);
|
|
270
|
+
const [planningSecondaryEditor, setPlanningSecondaryEditor] = useState(null);
|
|
271
|
+
const [planningDraftTitle, setPlanningDraftTitle] = useState('');
|
|
272
|
+
const [planningDraftDescription, setPlanningDraftDescription] = useState('');
|
|
273
|
+
const [planningDraftOwner, setPlanningDraftOwner] = useState('');
|
|
274
|
+
const [planningDraftInitiativeId, setPlanningDraftInitiativeId] = useState('');
|
|
275
|
+
const [documentTypeFilters, setDocumentTypeFilters] = useState(() => DOCUMENT_TYPE_FILTER_OPTIONS.map((option) => option.value));
|
|
276
|
+
const [documentAttachmentFilters, setDocumentAttachmentFilters] = useState(() => DOCUMENT_ATTACHMENT_FILTER_OPTIONS.map((option) => option.value));
|
|
186
277
|
const [requestedDocPath, setRequestedDocPath] = useState(null);
|
|
278
|
+
const [requestedDocAssetId, setRequestedDocAssetId] = useState(null);
|
|
279
|
+
const [requestedAnnotatedTarget, setRequestedAnnotatedTarget] = useState(null);
|
|
280
|
+
const [requestedAnnotatedSessionId, setRequestedAnnotatedSessionId] = useState(null);
|
|
281
|
+
const [requestedAnnotatedOpenVersion, setRequestedAnnotatedOpenVersion] = useState(0);
|
|
282
|
+
const [canonicalAnnotatedImageReferenceLabels, setCanonicalAnnotatedImageReferenceLabels] = useState({});
|
|
187
283
|
const [layoutStateReady, setLayoutStateReady] = useState(false);
|
|
188
|
-
const lastNonDocsGroupByRef = useRef('category');
|
|
189
284
|
const loadedLayoutStateWorkspaceRef = useRef('');
|
|
285
|
+
const requestedAnnotatedTargetRef = useRef(null);
|
|
286
|
+
const requestedAnnotatedSessionIdRef = useRef(null);
|
|
287
|
+
const annotatedImageReferenceFetchesRef = useRef(new Set());
|
|
190
288
|
const workspaceUiStateHeaders = useMemo(() => {
|
|
191
289
|
const headers = {};
|
|
192
290
|
const workspaceIdHeader = String(currentWorkspaceId || '').trim();
|
|
@@ -196,23 +294,93 @@ export function StandaloneLayout(props) {
|
|
|
196
294
|
return headers;
|
|
197
295
|
}, [runtimeMode, currentWorkspaceId]);
|
|
198
296
|
const layoutStateWorkspaceKey = useMemo(() => `${runtimeMode}:${String(currentWorkspaceId || 'default').trim() || 'default'}`, [runtimeMode, currentWorkspaceId]);
|
|
297
|
+
const annotatedLayoutCacheKey = useMemo(() => `taskforce:annotate-layout:${layoutStateWorkspaceKey}`, [layoutStateWorkspaceKey]);
|
|
298
|
+
const annotatedWorkspaceReady = useMemo(() => (runtimeMode === 'cloud' ? authSessionResolved : configLoaded), [authSessionResolved, configLoaded, runtimeMode]);
|
|
299
|
+
useEffect(() => {
|
|
300
|
+
requestedAnnotatedTargetRef.current = requestedAnnotatedTarget;
|
|
301
|
+
}, [requestedAnnotatedTarget]);
|
|
302
|
+
useEffect(() => {
|
|
303
|
+
requestedAnnotatedSessionIdRef.current = requestedAnnotatedSessionId;
|
|
304
|
+
}, [requestedAnnotatedSessionId]);
|
|
305
|
+
const workspaceFeatureAccess = useMemo(() => ({
|
|
306
|
+
[DOCUMENT_WORKSPACE_FEATURE_KEY]: resolveFeatureAccess({
|
|
307
|
+
featureKey: DOCUMENT_WORKSPACE_FEATURE_KEY,
|
|
308
|
+
runtimeMode,
|
|
309
|
+
}),
|
|
310
|
+
[ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY]: resolveFeatureAccess({
|
|
311
|
+
featureKey: ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY,
|
|
312
|
+
runtimeMode,
|
|
313
|
+
}),
|
|
314
|
+
[WORKFLOWS_WORKSPACE_FEATURE_KEY]: resolveFeatureAccess({
|
|
315
|
+
featureKey: WORKFLOWS_WORKSPACE_FEATURE_KEY,
|
|
316
|
+
runtimeMode,
|
|
317
|
+
}),
|
|
318
|
+
[AGENTS_WORKSPACE_FEATURE_KEY]: resolveFeatureAccess({
|
|
319
|
+
featureKey: AGENTS_WORKSPACE_FEATURE_KEY,
|
|
320
|
+
runtimeMode,
|
|
321
|
+
}),
|
|
322
|
+
[INITIATIVES_WORKSPACE_FEATURE_KEY]: resolveFeatureAccess({
|
|
323
|
+
featureKey: INITIATIVES_WORKSPACE_FEATURE_KEY,
|
|
324
|
+
runtimeMode,
|
|
325
|
+
}),
|
|
326
|
+
}), [runtimeMode]);
|
|
327
|
+
const workspaceModules = useMemo(() => getWorkspaceModuleDefinitions({ featureAccess: workspaceFeatureAccess }), [workspaceFeatureAccess]);
|
|
328
|
+
const resolvedWorkspaceModule = useMemo(() => resolveWorkspaceModule(activeWorkspaceModule, workspaceModules), [activeWorkspaceModule, workspaceModules]);
|
|
329
|
+
const documentsWorkspaceEnabled = workspaceFeatureAccess[DOCUMENT_WORKSPACE_FEATURE_KEY]?.allowed ?? false;
|
|
330
|
+
const annotatedAttachmentsWorkspaceEnabled = workspaceFeatureAccess[ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY]?.allowed ?? false;
|
|
331
|
+
const documentsWorkspaceUnavailableMessage = 'Documents is not available in this build yet.';
|
|
332
|
+
const annotatedAttachmentsWorkspaceUnavailableMessage = 'Annotate is not available in this build yet.';
|
|
199
333
|
useEffect(() => {
|
|
200
334
|
const handleOpenMarkdownDocument = (event) => {
|
|
201
335
|
const customEvent = event;
|
|
202
336
|
const fsPath = customEvent.detail?.fsPath;
|
|
203
|
-
|
|
337
|
+
const assetId = String(customEvent.detail?.assetId || '').trim() || null;
|
|
338
|
+
if (!fsPath && !assetId)
|
|
204
339
|
return;
|
|
205
340
|
customEvent.preventDefault();
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
341
|
+
if (!documentsWorkspaceEnabled) {
|
|
342
|
+
pushNotice(documentsWorkspaceUnavailableMessage, 'error');
|
|
343
|
+
return;
|
|
209
344
|
}
|
|
345
|
+
setRequestedDocPath(fsPath || null);
|
|
346
|
+
setRequestedDocAssetId(assetId);
|
|
347
|
+
setActiveWorkspaceModule('docs');
|
|
210
348
|
};
|
|
211
349
|
window.addEventListener('taskforce:open-markdown-document', handleOpenMarkdownDocument);
|
|
212
350
|
return () => {
|
|
213
351
|
window.removeEventListener('taskforce:open-markdown-document', handleOpenMarkdownDocument);
|
|
214
352
|
};
|
|
215
|
-
}, [
|
|
353
|
+
}, [documentsWorkspaceEnabled, documentsWorkspaceUnavailableMessage, pushNotice, setActiveWorkspaceModule]);
|
|
354
|
+
useEffect(() => {
|
|
355
|
+
if (resolvedWorkspaceModule !== activeWorkspaceModule) {
|
|
356
|
+
setActiveWorkspaceModule(resolvedWorkspaceModule);
|
|
357
|
+
}
|
|
358
|
+
}, [activeWorkspaceModule, resolvedWorkspaceModule, setActiveWorkspaceModule]);
|
|
359
|
+
useEffect(() => {
|
|
360
|
+
if (resolvedWorkspaceModule === 'docs' && documentsWorkspaceEnabled)
|
|
361
|
+
return;
|
|
362
|
+
setRequestedDocPath(null);
|
|
363
|
+
setRequestedDocAssetId(null);
|
|
364
|
+
}, [documentsWorkspaceEnabled, resolvedWorkspaceModule]);
|
|
365
|
+
useEffect(() => {
|
|
366
|
+
if (typeof window === 'undefined')
|
|
367
|
+
return;
|
|
368
|
+
try {
|
|
369
|
+
const cachedRaw = window.sessionStorage.getItem(annotatedLayoutCacheKey);
|
|
370
|
+
if (!cachedRaw)
|
|
371
|
+
return;
|
|
372
|
+
const cached = JSON.parse(cachedRaw);
|
|
373
|
+
if (cached?.annotatedTarget && typeof cached.annotatedTarget === 'object') {
|
|
374
|
+
setRequestedAnnotatedTarget(cached.annotatedTarget);
|
|
375
|
+
}
|
|
376
|
+
if (typeof cached?.annotatedSessionId === 'string') {
|
|
377
|
+
setRequestedAnnotatedSessionId(cached.annotatedSessionId.trim() || null);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
// ignore cache read failure
|
|
382
|
+
}
|
|
383
|
+
}, [annotatedLayoutCacheKey]);
|
|
216
384
|
useEffect(() => {
|
|
217
385
|
if (loadedLayoutStateWorkspaceRef.current === layoutStateWorkspaceKey)
|
|
218
386
|
return;
|
|
@@ -242,15 +410,6 @@ export function StandaloneLayout(props) {
|
|
|
242
410
|
setScheduleOnlyExpired(state.scheduleOnlyExpired);
|
|
243
411
|
if (typeof state.scheduleSidebarOpen === 'boolean')
|
|
244
412
|
setScheduleSidebarOpen(state.scheduleSidebarOpen);
|
|
245
|
-
if (typeof state.hierarchySidebarOpen === 'boolean')
|
|
246
|
-
setHierarchySidebarOpen(state.hierarchySidebarOpen);
|
|
247
|
-
if (typeof state.hierarchyRootTaskId === 'string')
|
|
248
|
-
setHierarchyRootTaskId(state.hierarchyRootTaskId);
|
|
249
|
-
if (typeof state.hierarchyShowUnlinkedColumn === 'boolean')
|
|
250
|
-
setHierarchyShowUnlinkedColumn(state.hierarchyShowUnlinkedColumn);
|
|
251
|
-
if (state.hierarchyChildOrderMode === 'manual' || state.hierarchyChildOrderMode === 'auto') {
|
|
252
|
-
setHierarchyChildOrderMode(state.hierarchyChildOrderMode);
|
|
253
|
-
}
|
|
254
413
|
if (typeof state.scheduleSelectedDate === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(state.scheduleSelectedDate)) {
|
|
255
414
|
setScheduleSelectedDate(state.scheduleSelectedDate);
|
|
256
415
|
}
|
|
@@ -262,6 +421,47 @@ export function StandaloneLayout(props) {
|
|
|
262
421
|
}
|
|
263
422
|
if (typeof state.showFilters === 'boolean')
|
|
264
423
|
setShowFilters(state.showFilters);
|
|
424
|
+
if (Array.isArray(state.documentTypeFilters)) {
|
|
425
|
+
setDocumentTypeFilters(state.documentTypeFilters);
|
|
426
|
+
}
|
|
427
|
+
if (Array.isArray(state.documentAttachmentFilters)) {
|
|
428
|
+
setDocumentAttachmentFilters(state.documentAttachmentFilters);
|
|
429
|
+
}
|
|
430
|
+
if (typeof state.planningDrawerOpen === 'boolean') {
|
|
431
|
+
setPlanningDrawerOpen(state.planningDrawerOpen);
|
|
432
|
+
}
|
|
433
|
+
if (Array.isArray(state.planningExpandedInitiativeIds)) {
|
|
434
|
+
setExpandedPlanningInitiativeIds(new Set(state.planningExpandedInitiativeIds
|
|
435
|
+
.map((value) => String(value || '').trim())
|
|
436
|
+
.filter(Boolean)));
|
|
437
|
+
}
|
|
438
|
+
if (state.planningDrawerDetail
|
|
439
|
+
&& typeof state.planningDrawerDetail === 'object'
|
|
440
|
+
&& (state.planningDrawerDetail.type === 'initiative' || state.planningDrawerDetail.type === 'workstream')
|
|
441
|
+
&& typeof state.planningDrawerDetail.id === 'string') {
|
|
442
|
+
const detailId = state.planningDrawerDetail.id.trim();
|
|
443
|
+
setPlanningDrawerDetail(detailId ? { type: state.planningDrawerDetail.type, id: detailId } : null);
|
|
444
|
+
}
|
|
445
|
+
else if (state.planningDrawerDetail === null) {
|
|
446
|
+
setPlanningDrawerDetail(null);
|
|
447
|
+
}
|
|
448
|
+
if (typeof state.planningNestedWorkstreamDetailId === 'string') {
|
|
449
|
+
setPlanningNestedWorkstreamDetailId(state.planningNestedWorkstreamDetailId.trim() || null);
|
|
450
|
+
}
|
|
451
|
+
else if (state.planningNestedWorkstreamDetailId === null) {
|
|
452
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
453
|
+
}
|
|
454
|
+
const annotatedTarget = state.annotatedTarget;
|
|
455
|
+
if (annotatedTarget && typeof annotatedTarget === 'object') {
|
|
456
|
+
if (!requestedAnnotatedTargetRef.current) {
|
|
457
|
+
setRequestedAnnotatedTarget(annotatedTarget);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (typeof state.annotatedSessionId === 'string') {
|
|
461
|
+
if (!requestedAnnotatedSessionIdRef.current) {
|
|
462
|
+
setRequestedAnnotatedSessionId(state.annotatedSessionId.trim() || null);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
265
465
|
}
|
|
266
466
|
catch {
|
|
267
467
|
// ignore read failure
|
|
@@ -292,14 +492,18 @@ export function StandaloneLayout(props) {
|
|
|
292
492
|
scheduleShowBacklog,
|
|
293
493
|
scheduleOnlyExpired,
|
|
294
494
|
scheduleSidebarOpen,
|
|
295
|
-
hierarchySidebarOpen,
|
|
296
|
-
hierarchyRootTaskId,
|
|
297
|
-
hierarchyShowUnlinkedColumn,
|
|
298
|
-
hierarchyChildOrderMode,
|
|
299
495
|
scheduleSelectedDate,
|
|
300
496
|
scheduleCalendarMonth,
|
|
301
497
|
scheduleScrollLeft,
|
|
302
|
-
showFilters
|
|
498
|
+
showFilters,
|
|
499
|
+
documentTypeFilters,
|
|
500
|
+
documentAttachmentFilters,
|
|
501
|
+
planningDrawerOpen,
|
|
502
|
+
planningExpandedInitiativeIds: Array.from(expandedPlanningInitiativeIds),
|
|
503
|
+
planningDrawerDetail,
|
|
504
|
+
planningNestedWorkstreamDetailId,
|
|
505
|
+
annotatedTarget: requestedAnnotatedTarget,
|
|
506
|
+
annotatedSessionId: requestedAnnotatedSessionId
|
|
303
507
|
}
|
|
304
508
|
})
|
|
305
509
|
});
|
|
@@ -315,137 +519,469 @@ export function StandaloneLayout(props) {
|
|
|
315
519
|
scheduleShowBacklog,
|
|
316
520
|
scheduleOnlyExpired,
|
|
317
521
|
scheduleSidebarOpen,
|
|
318
|
-
hierarchySidebarOpen,
|
|
319
|
-
hierarchyRootTaskId,
|
|
320
|
-
hierarchyShowUnlinkedColumn,
|
|
321
|
-
hierarchyChildOrderMode,
|
|
322
522
|
scheduleSelectedDate,
|
|
323
523
|
scheduleCalendarMonth,
|
|
324
524
|
scheduleScrollLeft,
|
|
325
525
|
showFilters,
|
|
526
|
+
documentTypeFilters,
|
|
527
|
+
documentAttachmentFilters,
|
|
528
|
+
planningDrawerOpen,
|
|
529
|
+
expandedPlanningInitiativeIds,
|
|
530
|
+
planningDrawerDetail,
|
|
531
|
+
planningNestedWorkstreamDetailId,
|
|
532
|
+
requestedAnnotatedTarget,
|
|
533
|
+
requestedAnnotatedSessionId,
|
|
326
534
|
workspaceUiStateHeaders
|
|
327
535
|
]);
|
|
328
536
|
useEffect(() => {
|
|
329
|
-
if (
|
|
330
|
-
|
|
537
|
+
if (typeof window === 'undefined')
|
|
538
|
+
return;
|
|
539
|
+
try {
|
|
540
|
+
window.sessionStorage.setItem(annotatedLayoutCacheKey, JSON.stringify({
|
|
541
|
+
annotatedTarget: requestedAnnotatedTarget,
|
|
542
|
+
annotatedSessionId: requestedAnnotatedSessionId,
|
|
543
|
+
}));
|
|
331
544
|
}
|
|
332
|
-
|
|
545
|
+
catch {
|
|
546
|
+
// ignore cache write failure
|
|
547
|
+
}
|
|
548
|
+
}, [annotatedLayoutCacheKey, requestedAnnotatedSessionId, requestedAnnotatedTarget]);
|
|
333
549
|
useEffect(() => {
|
|
334
550
|
if (groupBy === 'schedule') {
|
|
335
551
|
setScheduleSidebarOpen(true);
|
|
336
552
|
}
|
|
337
|
-
if (groupBy === 'hierarchy') {
|
|
338
|
-
setHierarchySidebarOpen(true);
|
|
339
|
-
}
|
|
340
553
|
}, [groupBy]);
|
|
341
554
|
const { showArchive, setShowArchive, fetchArchive, filteredArchive } = props;
|
|
555
|
+
const archivedScopeTasks = useMemo(() => filteredArchive.map((task) => ({ ...task, isArchived: true })), [filteredArchive]);
|
|
556
|
+
const archivedScopeAllTasks = useMemo(() => props.archivedTasks.map((task) => ({ ...task, isArchived: true })), [props.archivedTasks]);
|
|
557
|
+
const annotatedAttachmentTasks = useMemo(() => [...props.searchAgnosticTasks, ...archivedScopeAllTasks], [archivedScopeAllTasks, props.searchAgnosticTasks]);
|
|
558
|
+
const resolveAnnotatedTaskReferenceLabel = useCallback((taskId) => {
|
|
559
|
+
const normalizedTaskId = String(taskId || '').trim();
|
|
560
|
+
if (!normalizedTaskId)
|
|
561
|
+
return '';
|
|
562
|
+
const matchedTask = annotatedAttachmentTasks.find((task) => task.id === normalizedTaskId);
|
|
563
|
+
return getTaskReferenceLabel(matchedTask) || '';
|
|
564
|
+
}, [annotatedAttachmentTasks]);
|
|
565
|
+
const resolveAnnotatedImageReferenceLabel = useCallback((assetId) => {
|
|
566
|
+
const normalizedAssetId = String(assetId || '').trim();
|
|
567
|
+
if (!normalizedAssetId)
|
|
568
|
+
return '';
|
|
569
|
+
const canonicalReferenceLabel = String(canonicalAnnotatedImageReferenceLabels[normalizedAssetId] || '').trim();
|
|
570
|
+
if (canonicalReferenceLabel)
|
|
571
|
+
return canonicalReferenceLabel;
|
|
572
|
+
for (const task of annotatedAttachmentTasks) {
|
|
573
|
+
for (const attachment of task.attachments || []) {
|
|
574
|
+
if (!attachment || typeof attachment !== 'object')
|
|
575
|
+
continue;
|
|
576
|
+
if (String(attachment.assetId || '').trim() !== normalizedAssetId)
|
|
577
|
+
continue;
|
|
578
|
+
const referenceLabel = getImageReferenceLabel({
|
|
579
|
+
referenceNumber: typeof attachment.referenceNumber === 'number' ? attachment.referenceNumber : null,
|
|
580
|
+
referenceLabel: String(attachment.referenceLabel || '').trim() || null,
|
|
581
|
+
});
|
|
582
|
+
if (referenceLabel)
|
|
583
|
+
return referenceLabel;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return '';
|
|
587
|
+
}, [annotatedAttachmentTasks, canonicalAnnotatedImageReferenceLabels]);
|
|
588
|
+
useEffect(() => {
|
|
589
|
+
const handleOpenAnnotatedAttachment = (event) => {
|
|
590
|
+
const customEvent = event;
|
|
591
|
+
const detail = resolveAnnotatedAttachmentDetail(customEvent.detail, annotatedAttachmentTasks);
|
|
592
|
+
if (!detail)
|
|
593
|
+
return;
|
|
594
|
+
customEvent.preventDefault();
|
|
595
|
+
if (!annotatedAttachmentsWorkspaceEnabled) {
|
|
596
|
+
pushNotice(annotatedAttachmentsWorkspaceUnavailableMessage, 'error');
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
setRequestedAnnotatedTarget(detail);
|
|
600
|
+
setRequestedAnnotatedSessionId(null);
|
|
601
|
+
setRequestedAnnotatedOpenVersion((current) => current + 1);
|
|
602
|
+
setActiveWorkspaceModule('annotate');
|
|
603
|
+
};
|
|
604
|
+
window.addEventListener('taskforce:open-annotated-attachment', handleOpenAnnotatedAttachment);
|
|
605
|
+
return () => {
|
|
606
|
+
window.removeEventListener('taskforce:open-annotated-attachment', handleOpenAnnotatedAttachment);
|
|
607
|
+
};
|
|
608
|
+
}, [
|
|
609
|
+
annotatedAttachmentTasks,
|
|
610
|
+
annotatedAttachmentsWorkspaceEnabled,
|
|
611
|
+
annotatedAttachmentsWorkspaceUnavailableMessage,
|
|
612
|
+
pushNotice,
|
|
613
|
+
setActiveWorkspaceModule
|
|
614
|
+
]);
|
|
615
|
+
useEffect(() => {
|
|
616
|
+
if (!requestedAnnotatedTarget)
|
|
617
|
+
return;
|
|
618
|
+
const resolvedTarget = resolveAnnotatedAttachmentDetail(requestedAnnotatedTarget, annotatedAttachmentTasks);
|
|
619
|
+
if (!resolvedTarget)
|
|
620
|
+
return;
|
|
621
|
+
if (resolvedTarget.taskReferenceLabel === requestedAnnotatedTarget.taskReferenceLabel
|
|
622
|
+
&& resolvedTarget.imageReferenceLabel === requestedAnnotatedTarget.imageReferenceLabel)
|
|
623
|
+
return;
|
|
624
|
+
setRequestedAnnotatedTarget(resolvedTarget);
|
|
625
|
+
}, [annotatedAttachmentTasks, requestedAnnotatedTarget]);
|
|
626
|
+
useEffect(() => {
|
|
627
|
+
const activeTarget = requestedAnnotatedTargetRef.current;
|
|
628
|
+
const normalizedAssetId = String(activeTarget?.assetId || '').trim();
|
|
629
|
+
if (!normalizedAssetId)
|
|
630
|
+
return;
|
|
631
|
+
const canonicalReferenceLabel = String(canonicalAnnotatedImageReferenceLabels[normalizedAssetId] || '').trim();
|
|
632
|
+
if (!canonicalReferenceLabel)
|
|
633
|
+
return;
|
|
634
|
+
if (canonicalReferenceLabel === String(activeTarget?.imageReferenceLabel || '').trim())
|
|
635
|
+
return;
|
|
636
|
+
setRequestedAnnotatedTarget((current) => {
|
|
637
|
+
if (!current || String(current.assetId || '').trim() !== normalizedAssetId)
|
|
638
|
+
return current;
|
|
639
|
+
return {
|
|
640
|
+
...current,
|
|
641
|
+
imageReferenceLabel: canonicalReferenceLabel,
|
|
642
|
+
};
|
|
643
|
+
});
|
|
644
|
+
}, [canonicalAnnotatedImageReferenceLabels, requestedAnnotatedTarget]);
|
|
645
|
+
useEffect(() => {
|
|
646
|
+
if (!annotatedWorkspaceReady)
|
|
647
|
+
return;
|
|
648
|
+
const activeTarget = requestedAnnotatedTarget;
|
|
649
|
+
const normalizedAssetId = String(activeTarget?.assetId || '').trim();
|
|
650
|
+
if (!normalizedAssetId)
|
|
651
|
+
return;
|
|
652
|
+
if (annotatedImageReferenceFetchesRef.current.has(normalizedAssetId))
|
|
653
|
+
return;
|
|
654
|
+
if (String(canonicalAnnotatedImageReferenceLabels[normalizedAssetId] || '').trim())
|
|
655
|
+
return;
|
|
656
|
+
let cancelled = false;
|
|
657
|
+
annotatedImageReferenceFetchesRef.current.add(normalizedAssetId);
|
|
658
|
+
const workspaceIdQuery = String(currentWorkspaceId || 'default').trim() || 'default';
|
|
659
|
+
const query = new URLSearchParams({ workspaceId: workspaceIdQuery });
|
|
660
|
+
const loadCanonicalImageReferenceLabel = async () => {
|
|
661
|
+
try {
|
|
662
|
+
const response = await fetch(`/api/taskforce/annotated-attachments/images/${encodeURIComponent(normalizedAssetId)}?${query.toString()}`, {
|
|
663
|
+
method: 'GET',
|
|
664
|
+
credentials: 'include',
|
|
665
|
+
headers: workspaceUiStateHeaders,
|
|
666
|
+
});
|
|
667
|
+
if (!response.ok)
|
|
668
|
+
return;
|
|
669
|
+
const data = await response.json().catch(() => ({}));
|
|
670
|
+
const canonicalReferenceLabel = String(data?.target?.imageReferenceLabel || '').trim();
|
|
671
|
+
if (!canonicalReferenceLabel || cancelled)
|
|
672
|
+
return;
|
|
673
|
+
setCanonicalAnnotatedImageReferenceLabels((current) => {
|
|
674
|
+
if (current[normalizedAssetId] === canonicalReferenceLabel)
|
|
675
|
+
return current;
|
|
676
|
+
return {
|
|
677
|
+
...current,
|
|
678
|
+
[normalizedAssetId]: canonicalReferenceLabel,
|
|
679
|
+
};
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
catch {
|
|
683
|
+
// Ignore canonical image reference refresh failures and keep the current hint.
|
|
684
|
+
}
|
|
685
|
+
finally {
|
|
686
|
+
annotatedImageReferenceFetchesRef.current.delete(normalizedAssetId);
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
void loadCanonicalImageReferenceLabel();
|
|
690
|
+
return () => {
|
|
691
|
+
cancelled = true;
|
|
692
|
+
};
|
|
693
|
+
}, [annotatedWorkspaceReady, canonicalAnnotatedImageReferenceLabels, currentWorkspaceId, requestedAnnotatedTarget, workspaceUiStateHeaders]);
|
|
694
|
+
const deletedScopeAllTasks = useMemo(() => props.deletedTasks.map((entry) => ({
|
|
695
|
+
...entry.taskSnapshot,
|
|
696
|
+
isDeleted: true,
|
|
697
|
+
deletedRecordId: entry.id
|
|
698
|
+
})), [props.deletedTasks]);
|
|
699
|
+
const deletedTaskRecordByTaskId = useMemo(() => {
|
|
700
|
+
const map = new Map();
|
|
701
|
+
props.deletedTasks.forEach((entry) => {
|
|
702
|
+
map.set(entry.taskSnapshot.id, entry);
|
|
703
|
+
});
|
|
704
|
+
return map;
|
|
705
|
+
}, [props.deletedTasks]);
|
|
706
|
+
const filteredDeletedTasks = useMemo(() => {
|
|
707
|
+
const deletedTasks = deletedScopeAllTasks;
|
|
708
|
+
const searchNeedle = searchQuery.trim().toLowerCase();
|
|
709
|
+
const allCategoriesSelected = activeCategories.every((taskCategory) => filterCategories.includes(taskCategory.value));
|
|
710
|
+
const allTypesSelected = activeTypes.every((taskType) => filterTypes.includes(taskType.value));
|
|
711
|
+
const selectedPriorities = new Set(filterPriorities.map((value) => String(value)));
|
|
712
|
+
const allPrioritiesSelected = priorities.every((priorityOption) => selectedPriorities.has(String(priorityOption.value)));
|
|
713
|
+
const selectedStatuses = new Set(filterStatus.map((value) => String(value)));
|
|
714
|
+
const allStatusesSelected = STATUS_OPTIONS.every((statusOption) => selectedStatuses.has(String(statusOption.value)));
|
|
715
|
+
return deletedTasks.filter((task) => {
|
|
716
|
+
const referenceLabel = (getTaskReferenceLabel(task) || task.id).toLowerCase();
|
|
717
|
+
const matchesSearch = !searchNeedle
|
|
718
|
+
|| task.title.toLowerCase().includes(searchNeedle)
|
|
719
|
+
|| String(task.description || '').toLowerCase().includes(searchNeedle)
|
|
720
|
+
|| task.id.toLowerCase().includes(searchNeedle)
|
|
721
|
+
|| referenceLabel.includes(searchNeedle);
|
|
722
|
+
const matchesCategory = allCategoriesSelected || filterCategories.includes(task.category);
|
|
723
|
+
const matchesType = allTypesSelected || filterTypes.includes(task.type || DEFAULT_TYPE_VALUE);
|
|
724
|
+
const matchesPriority = allPrioritiesSelected || selectedPriorities.has(String(task.priority));
|
|
725
|
+
const matchesStatus = allStatusesSelected || selectedStatuses.has(String(task.status));
|
|
726
|
+
const matchesAssignee = filterAssignees.length === 0 || filterAssignees.includes((task.assignee || 'unassigned'));
|
|
727
|
+
return matchesSearch && matchesCategory && matchesType && matchesPriority && matchesStatus && matchesAssignee;
|
|
728
|
+
});
|
|
729
|
+
}, [
|
|
730
|
+
activeCategories,
|
|
731
|
+
activeTypes,
|
|
732
|
+
deletedScopeAllTasks,
|
|
733
|
+
filterAssignees,
|
|
734
|
+
filterCategories,
|
|
735
|
+
filterPriorities,
|
|
736
|
+
filterStatus,
|
|
737
|
+
filterTypes,
|
|
738
|
+
priorities,
|
|
739
|
+
searchQuery
|
|
740
|
+
]);
|
|
741
|
+
const taskFilterRecords = useMemo(() => [...props.tasks, ...archivedScopeAllTasks, ...deletedScopeAllTasks], [archivedScopeAllTasks, deletedScopeAllTasks, props.tasks]);
|
|
742
|
+
const categoryFilterOptions = useMemo(() => {
|
|
743
|
+
const visibleCategories = activeCategories
|
|
744
|
+
.filter((category) => !category.disabled)
|
|
745
|
+
.map((category) => ({ value: category.value, label: category.label }));
|
|
746
|
+
const legacyCategoriesInUse = activeCategories
|
|
747
|
+
.filter((category) => category.disabled && taskFilterRecords.some((task) => task.category === category.value))
|
|
748
|
+
.map((category) => ({ value: category.value, label: `${category.label} (Legacy)` }));
|
|
749
|
+
return [...visibleCategories, ...legacyCategoriesInUse];
|
|
750
|
+
}, [activeCategories, taskFilterRecords]);
|
|
751
|
+
const typeFilterOptions = useMemo(() => {
|
|
752
|
+
const activeTypeOptions = activeTypes
|
|
753
|
+
.filter((type) => type.status !== 'retired')
|
|
754
|
+
.map((type) => ({ value: type.value, label: type.label }));
|
|
755
|
+
const retiredTypesInUse = activeTypes
|
|
756
|
+
.filter((type) => type.status === 'retired' && taskFilterRecords.some((task) => (task.type || DEFAULT_TYPE_VALUE) === type.value))
|
|
757
|
+
.map((type) => ({ value: type.value, label: `${type.label} (Retired)` }));
|
|
758
|
+
return [...activeTypeOptions, ...retiredTypesInUse];
|
|
759
|
+
}, [activeTypes, taskFilterRecords]);
|
|
342
760
|
const kanbanTasks = useMemo(() => {
|
|
761
|
+
if (taskScope === 'archived')
|
|
762
|
+
return archivedScopeTasks;
|
|
763
|
+
if (taskScope === 'deleted')
|
|
764
|
+
return filteredDeletedTasks;
|
|
343
765
|
if (!showArchive)
|
|
344
766
|
return props.filteredTasks;
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
767
|
+
return [...props.filteredTasks, ...archivedScopeTasks];
|
|
768
|
+
}, [archivedScopeTasks, filteredDeletedTasks, props.filteredTasks, showArchive, taskScope]);
|
|
769
|
+
const scheduleBaseTasks = useMemo(() => taskScope === 'open'
|
|
770
|
+
? props.tasks.filter(task => !task.isArchived)
|
|
771
|
+
: kanbanTasks, [kanbanTasks, props.tasks, taskScope]);
|
|
772
|
+
const scheduleFilteredTaskIds = useMemo(() => new Set(kanbanTasks.map(task => task.id)), [kanbanTasks]);
|
|
350
773
|
const kanbanAllTasks = useMemo(() => {
|
|
774
|
+
if (taskScope === 'archived')
|
|
775
|
+
return archivedScopeAllTasks;
|
|
776
|
+
if (taskScope === 'deleted')
|
|
777
|
+
return deletedScopeAllTasks;
|
|
351
778
|
if (!showArchive)
|
|
352
779
|
return props.searchAgnosticTasks;
|
|
353
|
-
return [...props.searchAgnosticTasks, ...
|
|
354
|
-
}, [props.searchAgnosticTasks,
|
|
780
|
+
return [...props.searchAgnosticTasks, ...archivedScopeAllTasks];
|
|
781
|
+
}, [archivedScopeAllTasks, deletedScopeAllTasks, props.searchAgnosticTasks, showArchive, taskScope]);
|
|
355
782
|
const hierarchyTasks = useMemo(() => {
|
|
356
783
|
const byId = new Map();
|
|
357
|
-
for (const task of
|
|
784
|
+
for (const task of archivedScopeAllTasks)
|
|
785
|
+
byId.set(task.id, task);
|
|
786
|
+
for (const task of deletedScopeAllTasks)
|
|
358
787
|
byId.set(task.id, task);
|
|
359
788
|
for (const task of props.tasks)
|
|
360
789
|
byId.set(task.id, task);
|
|
361
790
|
return Array.from(byId.values());
|
|
362
|
-
}, [
|
|
363
|
-
const hierarchyParentIdSet = useMemo(() => {
|
|
364
|
-
const set = new Set();
|
|
365
|
-
for (const task of hierarchyTasks) {
|
|
366
|
-
if (task.parentTaskId)
|
|
367
|
-
set.add(task.parentTaskId);
|
|
368
|
-
}
|
|
369
|
-
return set;
|
|
370
|
-
}, [hierarchyTasks]);
|
|
791
|
+
}, [archivedScopeAllTasks, deletedScopeAllTasks, props.tasks]);
|
|
371
792
|
const hierarchyTaskById = useMemo(() => {
|
|
372
793
|
const map = new Map();
|
|
373
794
|
hierarchyTasks.forEach((task) => map.set(task.id, task));
|
|
374
795
|
return map;
|
|
375
796
|
}, [hierarchyTasks]);
|
|
376
|
-
const
|
|
797
|
+
const assigneeLabelByValue = useMemo(() => {
|
|
377
798
|
const map = new Map();
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
return;
|
|
381
|
-
const list = map.get(task.parentTaskId) || [];
|
|
382
|
-
list.push(task);
|
|
383
|
-
map.set(task.parentTaskId, list);
|
|
799
|
+
assigneeOptions.forEach((option) => {
|
|
800
|
+
map.set(String(option.value), option.label);
|
|
384
801
|
});
|
|
385
802
|
return map;
|
|
386
|
-
}, [
|
|
387
|
-
const
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
return 5;
|
|
407
|
-
return null;
|
|
803
|
+
}, [assigneeOptions]);
|
|
804
|
+
const planningStructure = useMemo(() => {
|
|
805
|
+
const planningInitiatives = taskScope === 'deleted'
|
|
806
|
+
? []
|
|
807
|
+
: props.initiatives.filter((initiative) => taskScope === 'archived'
|
|
808
|
+
? Boolean(initiative.isArchived)
|
|
809
|
+
: !initiative.isArchived);
|
|
810
|
+
const planningWorkstreams = taskScope === 'deleted'
|
|
811
|
+
? []
|
|
812
|
+
: props.workstreams.filter((workstream) => taskScope === 'archived'
|
|
813
|
+
? Boolean(workstream.isArchived)
|
|
814
|
+
: !workstream.isArchived);
|
|
815
|
+
const summarizeTasks = (scopeTasks) => {
|
|
816
|
+
const taskCount = scopeTasks.length;
|
|
817
|
+
const completedTaskCount = scopeTasks.filter((task) => task.isArchived || task.status === 'done' || task.status === 'cancelled').length;
|
|
818
|
+
return {
|
|
819
|
+
progressPercent: taskCount > 0 ? Math.round((completedTaskCount / taskCount) * 100) : 0,
|
|
820
|
+
taskCount,
|
|
821
|
+
completedTaskCount,
|
|
822
|
+
};
|
|
408
823
|
};
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
824
|
+
const workstreamTaskIds = new Map();
|
|
825
|
+
const workstreamInitiativeIds = new Map();
|
|
826
|
+
const initiativeTaskIds = new Map();
|
|
827
|
+
const workstreamById = new Map();
|
|
828
|
+
const initiativeById = new Map();
|
|
829
|
+
const buildWorkstreamSummary = (workstream) => {
|
|
830
|
+
const scopedTasks = hierarchyTasks.filter((task) => (task.workstreamId || null) === workstream.id);
|
|
831
|
+
const taskPreviews = scopedTasks.map((task) => ({
|
|
832
|
+
id: task.id,
|
|
833
|
+
referenceNumber: task.referenceNumber ?? null,
|
|
834
|
+
title: task.title,
|
|
835
|
+
status: task.status || null,
|
|
836
|
+
}));
|
|
837
|
+
const summary = {
|
|
838
|
+
id: workstream.id,
|
|
839
|
+
referenceNumber: workstream.referenceNumber ?? null,
|
|
840
|
+
title: workstream.title,
|
|
841
|
+
description: String(workstream.description || '').trim() || undefined,
|
|
842
|
+
ownerLabel: workstream.ownerId ? assigneeLabelByValue.get(String(workstream.ownerId)) || String(workstream.ownerId) : null,
|
|
843
|
+
...summarizeTasks(scopedTasks),
|
|
844
|
+
initiativeId: workstream.initiativeId || null,
|
|
845
|
+
commentCount: Array.isArray(workstream.comments) ? workstream.comments.length : 0,
|
|
846
|
+
attachmentCount: Array.isArray(workstream.attachments) ? workstream.attachments.length : 0,
|
|
847
|
+
isArchived: Boolean(workstream.isArchived),
|
|
848
|
+
tasks: taskPreviews,
|
|
849
|
+
};
|
|
850
|
+
workstreamTaskIds.set(workstream.id, scopedTasks.map((task) => task.id));
|
|
851
|
+
if (workstream.initiativeId)
|
|
852
|
+
workstreamInitiativeIds.set(workstream.id, workstream.initiativeId);
|
|
853
|
+
workstreamById.set(summary.id, summary);
|
|
854
|
+
return summary;
|
|
855
|
+
};
|
|
856
|
+
const initiatives = planningInitiatives.map((initiative) => {
|
|
857
|
+
const workstreams = planningWorkstreams
|
|
858
|
+
.filter((workstream) => (workstream.initiativeId || null) === initiative.id)
|
|
859
|
+
.map(buildWorkstreamSummary);
|
|
860
|
+
const initiativeScopedTasks = workstreams
|
|
861
|
+
.flatMap((workstream) => workstreamTaskIds.get(workstream.id) || [])
|
|
862
|
+
.map((taskId) => hierarchyTaskById.get(taskId))
|
|
863
|
+
.filter((task) => Boolean(task));
|
|
864
|
+
const initiativeSummary = {
|
|
865
|
+
id: initiative.id,
|
|
866
|
+
referenceNumber: initiative.referenceNumber ?? null,
|
|
867
|
+
title: initiative.title,
|
|
868
|
+
description: String(initiative.description || '').trim() || undefined,
|
|
869
|
+
ownerLabel: initiative.ownerId ? assigneeLabelByValue.get(String(initiative.ownerId)) || String(initiative.ownerId) : null,
|
|
870
|
+
...summarizeTasks(initiativeScopedTasks),
|
|
871
|
+
workstreamCount: workstreams.length,
|
|
872
|
+
workstreams,
|
|
873
|
+
commentCount: Array.isArray(initiative.comments) ? initiative.comments.length : 0,
|
|
874
|
+
attachmentCount: Array.isArray(initiative.attachments) ? initiative.attachments.length : 0,
|
|
875
|
+
isArchived: Boolean(initiative.isArchived),
|
|
876
|
+
};
|
|
877
|
+
initiativeTaskIds.set(initiative.id, initiativeScopedTasks.map((task) => task.id));
|
|
878
|
+
initiativeById.set(initiativeSummary.id, initiativeSummary);
|
|
879
|
+
return initiativeSummary;
|
|
880
|
+
});
|
|
881
|
+
const standaloneWorkstreams = planningWorkstreams
|
|
882
|
+
.filter((workstream) => !workstream.initiativeId)
|
|
883
|
+
.map(buildWorkstreamSummary);
|
|
884
|
+
return {
|
|
885
|
+
initiatives,
|
|
886
|
+
standaloneWorkstreams,
|
|
887
|
+
workstreamTaskIds,
|
|
888
|
+
workstreamInitiativeIds,
|
|
889
|
+
initiativeTaskIds,
|
|
890
|
+
workstreamById,
|
|
891
|
+
initiativeById,
|
|
892
|
+
};
|
|
893
|
+
}, [assigneeLabelByValue, hierarchyTaskById, hierarchyTasks, props.initiatives, props.workstreams, taskScope]);
|
|
894
|
+
const initiativeFilterOptions = useMemo(() => planningStructure.initiatives.map((initiative) => ({ value: initiative.id, label: initiative.title })), [planningStructure.initiatives]);
|
|
895
|
+
const workstreamFilterOptions = useMemo(() => [
|
|
896
|
+
...planningStructure.initiatives.flatMap((initiative) => initiative.workstreams.map((workstream) => ({ value: workstream.id, label: workstream.title }))),
|
|
897
|
+
...planningStructure.standaloneWorkstreams.map((workstream) => ({ value: workstream.id, label: workstream.title })),
|
|
898
|
+
], [planningStructure.initiatives, planningStructure.standaloneWorkstreams]);
|
|
899
|
+
const selectedPlanningTaskIds = useMemo(() => {
|
|
900
|
+
if (selectedPlanningWorkstreamId) {
|
|
901
|
+
return new Set(planningStructure.workstreamTaskIds.get(selectedPlanningWorkstreamId) || []);
|
|
902
|
+
}
|
|
903
|
+
if (selectedPlanningInitiativeId) {
|
|
904
|
+
return new Set(planningStructure.initiativeTaskIds.get(selectedPlanningInitiativeId) || []);
|
|
905
|
+
}
|
|
906
|
+
return null;
|
|
907
|
+
}, [planningStructure.initiativeTaskIds, planningStructure.workstreamTaskIds, selectedPlanningInitiativeId, selectedPlanningWorkstreamId]);
|
|
908
|
+
const planningDrawerItem = useMemo(() => {
|
|
909
|
+
if (!planningDrawerDetail)
|
|
421
910
|
return null;
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
if (
|
|
432
|
-
|
|
911
|
+
if (planningDrawerDetail.type === 'initiative') {
|
|
912
|
+
const item = planningStructure.initiativeById.get(planningDrawerDetail.id);
|
|
913
|
+
return item ? { type: 'initiative', item } : null;
|
|
914
|
+
}
|
|
915
|
+
const item = planningStructure.workstreamById.get(planningDrawerDetail.id)
|
|
916
|
+
|| planningStructure.standaloneWorkstreams.find((workstream) => workstream.id === planningDrawerDetail.id);
|
|
917
|
+
return item ? { type: 'workstream', item } : null;
|
|
918
|
+
}, [planningDrawerDetail, planningStructure.initiativeById, planningStructure.standaloneWorkstreams, planningStructure.workstreamById]);
|
|
919
|
+
const planningNestedWorkstreamItem = useMemo(() => {
|
|
920
|
+
if (!planningNestedWorkstreamDetailId)
|
|
921
|
+
return null;
|
|
922
|
+
const item = planningStructure.workstreamById.get(planningNestedWorkstreamDetailId)
|
|
923
|
+
|| planningStructure.standaloneWorkstreams.find((workstream) => workstream.id === planningNestedWorkstreamDetailId);
|
|
924
|
+
return item ? { type: 'workstream', item } : null;
|
|
925
|
+
}, [planningNestedWorkstreamDetailId, planningStructure.standaloneWorkstreams, planningStructure.workstreamById]);
|
|
926
|
+
const planningSecondaryPane = useMemo(() => {
|
|
927
|
+
if (planningSecondaryEditor) {
|
|
928
|
+
return { kind: 'editor', editor: planningSecondaryEditor };
|
|
929
|
+
}
|
|
930
|
+
if (planningNestedWorkstreamItem) {
|
|
931
|
+
return { kind: 'detail', detail: planningNestedWorkstreamItem };
|
|
932
|
+
}
|
|
933
|
+
return null;
|
|
934
|
+
}, [planningNestedWorkstreamItem, planningSecondaryEditor]);
|
|
935
|
+
const planningDrawerWidthPx = getPlanningDrawerWidthPx(planningTreeCollapsed, Boolean(planningDrawerItem || planningEditor), planningPrimaryCollapsed, Boolean(planningSecondaryPane), planningSecondaryCollapsed);
|
|
936
|
+
const planningDraftInitiativeSummary = useMemo(() => {
|
|
937
|
+
if (!planningDraftInitiativeId.trim())
|
|
938
|
+
return null;
|
|
939
|
+
const exactInitiative = planningStructure.initiativeById.get(planningDraftInitiativeId);
|
|
940
|
+
if (exactInitiative)
|
|
941
|
+
return exactInitiative;
|
|
942
|
+
return resolveInitiativeReference(planningStructure.initiatives, planningDraftInitiativeId);
|
|
943
|
+
}, [planningDraftInitiativeId, planningStructure.initiativeById, planningStructure.initiatives]);
|
|
944
|
+
useEffect(() => {
|
|
945
|
+
if (!planningDrawerOpen)
|
|
946
|
+
return;
|
|
947
|
+
setPlanningTreeCollapsed(false);
|
|
948
|
+
setPlanningPrimaryCollapsed(false);
|
|
949
|
+
setPlanningSecondaryCollapsed(false);
|
|
950
|
+
}, [planningDrawerOpen]);
|
|
951
|
+
useEffect(() => {
|
|
952
|
+
if (selectedPlanningInitiativeId && !planningStructure.initiativeById.has(selectedPlanningInitiativeId)) {
|
|
953
|
+
setSelectedPlanningInitiativeId('');
|
|
433
954
|
}
|
|
434
|
-
|
|
435
|
-
|
|
955
|
+
if (selectedPlanningWorkstreamId && !planningStructure.workstreamById.has(selectedPlanningWorkstreamId)) {
|
|
956
|
+
setSelectedPlanningWorkstreamId('');
|
|
436
957
|
}
|
|
437
|
-
|
|
438
|
-
|
|
958
|
+
if (planningDrawerDetail?.type === 'initiative' && !planningStructure.initiativeById.has(planningDrawerDetail.id)) {
|
|
959
|
+
setPlanningDrawerDetail(null);
|
|
439
960
|
}
|
|
440
|
-
if (
|
|
441
|
-
|
|
961
|
+
if (planningDrawerDetail?.type === 'workstream' && !planningStructure.workstreamById.has(planningDrawerDetail.id)) {
|
|
962
|
+
setPlanningDrawerDetail(null);
|
|
442
963
|
}
|
|
443
|
-
if (
|
|
444
|
-
|
|
445
|
-
|| (hierarchyShowUnlinkedColumn && (parentTaskFilterMode === 'all' || parentTaskFilterMode === 'unlinked') && isUnlinkedTask(task)));
|
|
964
|
+
if (planningNestedWorkstreamDetailId && !planningStructure.workstreamById.has(planningNestedWorkstreamDetailId)) {
|
|
965
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
446
966
|
}
|
|
447
|
-
|
|
448
|
-
|
|
967
|
+
if (planningSecondaryEditor?.mode === 'edit'
|
|
968
|
+
&& !planningStructure.workstreamById.has(planningSecondaryEditor.targetId)) {
|
|
969
|
+
setPlanningSecondaryEditor(null);
|
|
970
|
+
}
|
|
971
|
+
}, [
|
|
972
|
+
planningDrawerDetail,
|
|
973
|
+
planningNestedWorkstreamDetailId,
|
|
974
|
+
planningSecondaryEditor,
|
|
975
|
+
planningStructure.initiativeById,
|
|
976
|
+
planningStructure.workstreamById,
|
|
977
|
+
selectedPlanningInitiativeId,
|
|
978
|
+
selectedPlanningWorkstreamId,
|
|
979
|
+
]);
|
|
980
|
+
const hierarchyVisibleTasks = useMemo(() => {
|
|
981
|
+
if (groupBy === 'schedule')
|
|
982
|
+
return scheduleBaseTasks;
|
|
983
|
+
return kanbanTasks;
|
|
984
|
+
}, [groupBy, kanbanTasks, scheduleBaseTasks]);
|
|
449
985
|
const todayDateOnly = useMemo(() => {
|
|
450
986
|
const now = new Date();
|
|
451
987
|
const y = now.getFullYear();
|
|
@@ -465,6 +1001,11 @@ export function StandaloneLayout(props) {
|
|
|
465
1001
|
return expiredScheduled || overdueDue;
|
|
466
1002
|
});
|
|
467
1003
|
}, [groupBy, scheduleOnlyExpired, hierarchyVisibleTasks, todayDateOnly]);
|
|
1004
|
+
const scopedBoardVisibleTasks = useMemo(() => {
|
|
1005
|
+
if (!selectedPlanningTaskIds || selectedPlanningTaskIds.size === 0)
|
|
1006
|
+
return boardVisibleTasks;
|
|
1007
|
+
return boardVisibleTasks.filter((task) => selectedPlanningTaskIds.has(task.id));
|
|
1008
|
+
}, [boardVisibleTasks, selectedPlanningTaskIds]);
|
|
468
1009
|
const expiredRecoveryCandidates = useMemo(() => {
|
|
469
1010
|
if (groupBy !== 'schedule')
|
|
470
1011
|
return [];
|
|
@@ -507,108 +1048,78 @@ export function StandaloneLayout(props) {
|
|
|
507
1048
|
return !!task.scheduledDate && task.scheduledDate < todayDateOnly;
|
|
508
1049
|
});
|
|
509
1050
|
}, [groupBy, hierarchyVisibleTasks, todayDateOnly]);
|
|
510
|
-
const expiredLeafCandidates =
|
|
1051
|
+
const expiredLeafCandidates = expiredRecoveryCandidates;
|
|
511
1052
|
const [scheduleBulkBusy, setScheduleBulkBusy] = useState(false);
|
|
512
|
-
const [scheduleParentDropBusy, setScheduleParentDropBusy] = useState(false);
|
|
513
|
-
const [scheduleParentDropPrompt, setScheduleParentDropPrompt] = useState(null);
|
|
514
|
-
const hierarchyDepthTasks = useMemo(() => hierarchyVisibleTasks.filter(task => !!task.parentTaskId || hierarchyParentIdSet.has(task.id)), [hierarchyVisibleTasks, hierarchyParentIdSet]);
|
|
515
|
-
const hierarchyGlobalDepthMap = useMemo(() => buildHierarchyDepthMap(hierarchyTasks), [hierarchyTasks]);
|
|
516
|
-
const maxHierarchyDepth = useMemo(() => hierarchyDepthTasks.reduce((maxDepth, task) => {
|
|
517
|
-
const depth = hierarchyGlobalDepthMap.get(task.id) ?? 0;
|
|
518
|
-
return depth > maxDepth ? depth : maxDepth;
|
|
519
|
-
}, 0), [hierarchyDepthTasks, hierarchyGlobalDepthMap]);
|
|
520
|
-
const tryReturnToParent = () => {
|
|
521
|
-
if (activeTab === 'add'
|
|
522
|
-
&& editingTaskId
|
|
523
|
-
&& returnToParentTaskId
|
|
524
|
-
&& returnFromChildTaskId === editingTaskId) {
|
|
525
|
-
const parentTask = tasks.find(t => t.id === returnToParentTaskId);
|
|
526
|
-
clearReturnToParentTask();
|
|
527
|
-
if (parentTask) {
|
|
528
|
-
handleEdit(parentTask);
|
|
529
|
-
return true;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
return false;
|
|
533
|
-
};
|
|
534
1053
|
const handleCloseModal = () => {
|
|
535
1054
|
handleNavigation(() => {
|
|
536
|
-
if (
|
|
1055
|
+
if (returnToPreviousTask())
|
|
537
1056
|
return;
|
|
1057
|
+
if (activeTab === 'add' && taskReturnTrail.length > 0) {
|
|
1058
|
+
clearReturnToParentTask();
|
|
1059
|
+
}
|
|
1060
|
+
setTaskModalFullscreen(false);
|
|
538
1061
|
if (activeTab === 'add')
|
|
539
1062
|
resetForm();
|
|
540
1063
|
setActiveTab('tasks');
|
|
541
1064
|
});
|
|
542
1065
|
};
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
if (success) {
|
|
547
|
-
setLocalCancellationReason('');
|
|
1066
|
+
useEffect(() => {
|
|
1067
|
+
if (activeTab !== 'add') {
|
|
1068
|
+
setTaskModalFullscreen(false);
|
|
548
1069
|
}
|
|
549
|
-
};
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
setLocalCancellationReason('');
|
|
553
|
-
};
|
|
554
|
-
const handleDeleteFromCancellation = async () => {
|
|
555
|
-
if (!cancellationPromptTask)
|
|
1070
|
+
}, [activeTab]);
|
|
1071
|
+
const handleSetStatus = useCallback((task, status) => {
|
|
1072
|
+
if (task.status === status)
|
|
556
1073
|
return;
|
|
557
|
-
|
|
558
|
-
|
|
1074
|
+
if (status === 'in-progress') {
|
|
1075
|
+
void handleToggleInProgress(task);
|
|
559
1076
|
return;
|
|
560
|
-
const deleted = await handleDelete(cancellationPromptTask.id, false, { skipConfirm: true });
|
|
561
|
-
if (deleted) {
|
|
562
|
-
handleCancelCancellation();
|
|
563
1077
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
// Handle different grouping fields
|
|
568
|
-
if (groupField === 'category') {
|
|
569
|
-
const cat = activeCategories.find(c => String(c.value) === String(value) || c.label === String(value));
|
|
570
|
-
if (cat)
|
|
571
|
-
setCategory(cat.label);
|
|
572
|
-
}
|
|
573
|
-
else if (groupField === 'type') {
|
|
574
|
-
setType(String(value));
|
|
575
|
-
}
|
|
576
|
-
else if (groupField === 'priority') {
|
|
577
|
-
setPriority(Number(value));
|
|
578
|
-
}
|
|
579
|
-
else if (groupField === 'approach') {
|
|
580
|
-
const app = approaches.find(a => String(a.value) === String(value) || a.label === String(value));
|
|
581
|
-
if (app) {
|
|
582
|
-
setApproach(app.value);
|
|
583
|
-
setFormTaxonomies(prev => ({ ...prev, approach: app.value }));
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
else if (groupField === 'status') {
|
|
587
|
-
// When adding to a status column, we just open the form.
|
|
588
|
-
// But we can preset the status flags if we want, or just let users set them.
|
|
589
|
-
// Currently, the TaskForm doesn't expose a simple 'status' dropdown to preset.
|
|
590
|
-
// So for now, we'll just open the form.
|
|
591
|
-
// Ideally, we would preset the flags (inProgress, readyForReview, etc.)
|
|
592
|
-
// implementing basic presetting:
|
|
593
|
-
if (value === 'inProgress') {
|
|
594
|
-
handleToggleInProgress({}); // Hacky: assumes handleToggle works on new task? No.
|
|
595
|
-
// Better: we can't easily preset status flags in the current form setup without modifying resetForm/TaskForm props deeply.
|
|
596
|
-
// For now, simple open.
|
|
597
|
-
// Actually, let's try to pass initial state if possible, but useTaskforce API is limited.
|
|
598
|
-
// We'll leave it as default (Task) for now, or users can toggle after adding.
|
|
599
|
-
}
|
|
1078
|
+
if (status === 'review') {
|
|
1079
|
+
void handleToggleReview(task);
|
|
1080
|
+
return;
|
|
600
1081
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
setComplexity(direct);
|
|
605
|
-
}
|
|
1082
|
+
if (status === 'done') {
|
|
1083
|
+
void handleToggleComplete(task);
|
|
1084
|
+
return;
|
|
606
1085
|
}
|
|
607
|
-
|
|
608
|
-
|
|
1086
|
+
if (status === 'cancelled') {
|
|
1087
|
+
void handleToggleCancel(task);
|
|
1088
|
+
return;
|
|
609
1089
|
}
|
|
1090
|
+
void handleUpdateTask(task.id, {
|
|
1091
|
+
status,
|
|
1092
|
+
completedAt: null
|
|
1093
|
+
});
|
|
1094
|
+
}, [handleToggleInProgress, handleToggleReview, handleToggleComplete, handleToggleCancel, handleUpdateTask]);
|
|
1095
|
+
const handleAddTaskToColumn = useCallback((groupField, value) => {
|
|
1096
|
+
resetForm();
|
|
1097
|
+
const defaults = resolveDraftDefaultsForGrouping(groupField, value, {
|
|
1098
|
+
categories: activeCategories,
|
|
1099
|
+
approaches
|
|
1100
|
+
});
|
|
1101
|
+
if (defaults.category)
|
|
1102
|
+
setCategory(defaults.category);
|
|
1103
|
+
if (defaults.type)
|
|
1104
|
+
setType(defaults.type);
|
|
1105
|
+
if (typeof defaults.priority === 'number')
|
|
1106
|
+
setPriority(defaults.priority);
|
|
1107
|
+
if (typeof defaults.complexity === 'number')
|
|
1108
|
+
setComplexity(defaults.complexity);
|
|
1109
|
+
if (defaults.approach)
|
|
1110
|
+
setApproach(defaults.approach);
|
|
1111
|
+
const taxonomyApproach = defaults.taxonomyApproach;
|
|
1112
|
+
if (typeof taxonomyApproach === 'string' && taxonomyApproach.length > 0) {
|
|
1113
|
+
setFormTaxonomies(prev => ({ ...prev, approach: taxonomyApproach }));
|
|
1114
|
+
}
|
|
1115
|
+
if (defaults.assignee)
|
|
1116
|
+
setAssignee(defaults.assignee);
|
|
1117
|
+
if (defaults.status)
|
|
1118
|
+
setStatus(defaults.status);
|
|
1119
|
+
if (defaults.scheduledDate)
|
|
1120
|
+
setScheduledDate(defaults.scheduledDate);
|
|
610
1121
|
setActiveTab('add');
|
|
611
|
-
}, [resetForm, activeCategories, approaches, setCategory, setType, setPriority, setComplexity, setApproach, setFormTaxonomies, setActiveTab]);
|
|
1122
|
+
}, [resetForm, activeCategories, approaches, setCategory, setType, setPriority, setComplexity, setStatus, setApproach, setAssignee, setScheduledDate, setFormTaxonomies, setActiveTab]);
|
|
612
1123
|
// Ensure 'done' status is shown by default as requested
|
|
613
1124
|
React.useEffect(() => {
|
|
614
1125
|
if (filterStatus && !filterStatus.includes('done')) {
|
|
@@ -619,14 +1130,21 @@ export function StandaloneLayout(props) {
|
|
|
619
1130
|
const [showHelpModal, setShowHelpModal] = useState(false);
|
|
620
1131
|
const [showAccountMenu, setShowAccountMenu] = useState(false);
|
|
621
1132
|
const [showAccountHub, setShowAccountHub] = useState(false);
|
|
1133
|
+
const [showEditProfileModal, setShowEditProfileModal] = useState(false);
|
|
622
1134
|
const [showSyncStatusModal, setShowSyncStatusModal] = useState(false);
|
|
623
|
-
const [
|
|
1135
|
+
const [workspaceSyncToggleBusy, setWorkspaceSyncToggleBusy] = useState(false);
|
|
624
1136
|
const [workspaceSyncError, setWorkspaceSyncError] = useState(null);
|
|
625
|
-
const [
|
|
626
|
-
const [
|
|
627
|
-
const [
|
|
1137
|
+
const [workspaceSyncRepairBusy, setWorkspaceSyncRepairBusy] = useState(false);
|
|
1138
|
+
const [workspaceSyncRepairQueued, setWorkspaceSyncRepairQueued] = useState(false);
|
|
1139
|
+
const [workspaceSyncCopied, setWorkspaceSyncCopied] = useState(false);
|
|
1140
|
+
const [syncRecentEvents, setSyncRecentEvents] = useState([]);
|
|
1141
|
+
const [syncRecentEventsLoading, setSyncRecentEventsLoading] = useState(false);
|
|
1142
|
+
const [syncRecentEventsError, setSyncRecentEventsError] = useState(null);
|
|
1143
|
+
const syncEventsListRef = useRef(null);
|
|
1144
|
+
const pendingSyncEventsScrollRestoreRef = useRef(null);
|
|
628
1145
|
const [workspaceActionBusy, setWorkspaceActionBusy] = useState(false);
|
|
629
1146
|
const [workspaceActionError, setWorkspaceActionError] = useState('');
|
|
1147
|
+
const [showCreateWorkspaceConfirm, setShowCreateWorkspaceConfirm] = useState(false);
|
|
630
1148
|
const [showTeamManagementModal, setShowTeamManagementModal] = useState(false);
|
|
631
1149
|
const [teamManagementTab, setTeamManagementTab] = useState('members');
|
|
632
1150
|
const [teamPlanMode, setTeamPlanMode] = useState('unknown');
|
|
@@ -651,9 +1169,12 @@ export function StandaloneLayout(props) {
|
|
|
651
1169
|
const [billingActionError, setBillingActionError] = useState(null);
|
|
652
1170
|
const [billingNotice, setBillingNotice] = useState(null);
|
|
653
1171
|
const [billingIntervalChoice, setBillingIntervalChoice] = useState('month');
|
|
1172
|
+
const [profileDisplayNameDraft, setProfileDisplayNameDraft] = useState('');
|
|
1173
|
+
const [profileSaveBusy, setProfileSaveBusy] = useState(false);
|
|
1174
|
+
const [profileSaveError, setProfileSaveError] = useState(null);
|
|
1175
|
+
const [profileSaveNotice, setProfileSaveNotice] = useState(null);
|
|
654
1176
|
const TEAM_AUDIT_PAGE_SIZE = 25;
|
|
655
1177
|
const accountMenuRef = useRef(null);
|
|
656
|
-
const billingSuccessPollRef = useRef(null);
|
|
657
1178
|
// showSearch state removed - search always visible in actions area
|
|
658
1179
|
useEffect(() => {
|
|
659
1180
|
if (!showAccountMenu)
|
|
@@ -666,45 +1187,119 @@ export function StandaloneLayout(props) {
|
|
|
666
1187
|
document.addEventListener('mousedown', handlePointerDown);
|
|
667
1188
|
return () => document.removeEventListener('mousedown', handlePointerDown);
|
|
668
1189
|
}, [showAccountMenu]);
|
|
669
|
-
useEffect(() => () => {
|
|
670
|
-
if (billingSuccessPollRef.current) {
|
|
671
|
-
window.clearInterval(billingSuccessPollRef.current);
|
|
672
|
-
billingSuccessPollRef.current = null;
|
|
673
|
-
}
|
|
674
|
-
}, []);
|
|
675
1190
|
const canManageCloudWorkspaces = runtimeMode === 'cloud' && workspaceSwitchingEnabled && isAuthenticated;
|
|
1191
|
+
const canAccessCloudTeamManagement = isAuthenticated && (runtimeMode === 'cloud' || cloudAuthConfigured);
|
|
676
1192
|
const canManageWorkspaceSync = runtimeMode === 'local' && isAuthenticated;
|
|
677
|
-
const
|
|
1193
|
+
const handleWorkspaceSyncToggle = useCallback(async (enabled) => {
|
|
1194
|
+
if (!canManageWorkspaceSync)
|
|
1195
|
+
return;
|
|
1196
|
+
setWorkspaceSyncError(null);
|
|
1197
|
+
setWorkspaceSyncToggleBusy(true);
|
|
1198
|
+
const result = await saveWorkspaceCloudSyncSettings({ enabled });
|
|
1199
|
+
if (!result.success) {
|
|
1200
|
+
setWorkspaceSyncError(result.error || (enabled ? 'Failed to enable sync.' : 'Failed to disable sync.'));
|
|
1201
|
+
}
|
|
1202
|
+
setWorkspaceSyncToggleBusy(false);
|
|
1203
|
+
}, [
|
|
1204
|
+
canManageWorkspaceSync,
|
|
1205
|
+
saveWorkspaceCloudSyncSettings
|
|
1206
|
+
]);
|
|
1207
|
+
const syncControlBusy = workspaceSyncToggleBusy;
|
|
1208
|
+
const currentWorkspaceLabel = useMemo(() => {
|
|
678
1209
|
const current = availableWorkspaces.find((workspace) => workspace.id === currentWorkspaceId);
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
if (
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
1210
|
+
const namedWorkspace = String(current?.name || '').trim();
|
|
1211
|
+
if (namedWorkspace)
|
|
1212
|
+
return namedWorkspace;
|
|
1213
|
+
if (runtimeMode === 'local') {
|
|
1214
|
+
const localProjectName = String(projectName || '').trim();
|
|
1215
|
+
if (localProjectName)
|
|
1216
|
+
return localProjectName;
|
|
1217
|
+
}
|
|
1218
|
+
const fallbackId = String(currentWorkspaceId || '').trim();
|
|
1219
|
+
return fallbackId || 'Workspace';
|
|
1220
|
+
}, [availableWorkspaces, currentWorkspaceId, projectName, runtimeMode]);
|
|
1221
|
+
const canManageTeamWorkspace = computeCanManageTeamWorkspace(canAccessCloudTeamManagement, currentWorkspaceRole);
|
|
1222
|
+
const canOpenTeamManagement = computeCanOpenTeamManagement(canAccessCloudTeamManagement, currentWorkspaceRole, teamPlanMode);
|
|
1223
|
+
const authDisplayName = String(authUserDisplayName || '').trim();
|
|
1224
|
+
const authEmailLabel = String(authUserEmail || '').trim();
|
|
1225
|
+
const authIdentityLabel = authDisplayName || authEmailLabel;
|
|
690
1226
|
const hasAuthIdentity = authIdentityLabel.length > 0;
|
|
1227
|
+
const hasAuthEmail = authEmailLabel.length > 0;
|
|
1228
|
+
const accountBadgeLabel = (authIdentityLabel || authEmailLabel || 'U').trim();
|
|
1229
|
+
const accountBadgeInitial = accountBadgeLabel.charAt(0).toUpperCase() || 'U';
|
|
1230
|
+
const subscriptionLabel = String(billingStatus?.planName || billingStatus?.planId || '').trim();
|
|
1231
|
+
const hasSubscription = subscriptionLabel.length > 0;
|
|
1232
|
+
const teamManagementWorkspaceId = String(authWorkspaceId || currentWorkspaceId || '').trim();
|
|
1233
|
+
const runtimeLabel = runtimeMode === 'cloud' ? 'CLOUD' : 'LOCAL';
|
|
1234
|
+
const apiBaseUrl = props.config?.apiBaseUrl || '';
|
|
1235
|
+
const billingApiBaseUrl = useMemo(() => {
|
|
1236
|
+
const preferredBase = runtimeMode === 'local'
|
|
1237
|
+
? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
|
|
1238
|
+
: (props.config?.apiBaseUrl || props.config?.cloudAuthBaseUrl || '');
|
|
1239
|
+
return String(preferredBase || '').trim().replace(/\/+$/, '');
|
|
1240
|
+
}, [props.config?.apiBaseUrl, props.config?.cloudAuthBaseUrl, runtimeMode]);
|
|
1241
|
+
const buildBillingApiUrl = useCallback((path) => {
|
|
1242
|
+
const normalizedPath = String(path || '').trim();
|
|
1243
|
+
if (!billingApiBaseUrl)
|
|
1244
|
+
return normalizedPath;
|
|
1245
|
+
return `${billingApiBaseUrl}${normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`}`;
|
|
1246
|
+
}, [billingApiBaseUrl]);
|
|
1247
|
+
const connectedEnvironmentLabel = useMemo(() => resolveConnectedEnvironmentLabel(props.config?.cloudEnvironment, [
|
|
1248
|
+
String(props.config?.cloudBaseUrl || ''),
|
|
1249
|
+
String(props.config?.cloudAuthBaseUrl || ''),
|
|
1250
|
+
String(props.config?.apiBaseUrl || ''),
|
|
1251
|
+
String(props.config?.baseUrl || '')
|
|
1252
|
+
]), [
|
|
1253
|
+
props.config?.cloudEnvironment,
|
|
1254
|
+
props.config?.cloudBaseUrl,
|
|
1255
|
+
props.config?.cloudAuthBaseUrl,
|
|
1256
|
+
props.config?.apiBaseUrl,
|
|
1257
|
+
props.config?.baseUrl
|
|
1258
|
+
]);
|
|
691
1259
|
const isAuthBootstrapPending = cloudAuthConfigured && !authSessionResolved;
|
|
1260
|
+
useEffect(() => {
|
|
1261
|
+
if (!showEditProfileModal)
|
|
1262
|
+
return;
|
|
1263
|
+
setProfileDisplayNameDraft(authDisplayName);
|
|
1264
|
+
setProfileSaveError(null);
|
|
1265
|
+
setProfileSaveNotice(null);
|
|
1266
|
+
}, [authDisplayName, showEditProfileModal]);
|
|
1267
|
+
const handleSaveProfile = useCallback(async () => {
|
|
1268
|
+
const trimmedDisplayName = profileDisplayNameDraft.trim();
|
|
1269
|
+
if (!trimmedDisplayName) {
|
|
1270
|
+
setProfileSaveError('Display name is required.');
|
|
1271
|
+
setProfileSaveNotice(null);
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
setProfileSaveBusy(true);
|
|
1275
|
+
setProfileSaveError(null);
|
|
1276
|
+
setProfileSaveNotice(null);
|
|
1277
|
+
const result = await updateCurrentUserProfile({ displayName: trimmedDisplayName });
|
|
1278
|
+
if (!result.success) {
|
|
1279
|
+
setProfileSaveError(result.error || 'Failed to update profile.');
|
|
1280
|
+
setProfileSaveBusy(false);
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
setProfileSaveNotice('Profile updated.');
|
|
1284
|
+
setProfileSaveBusy(false);
|
|
1285
|
+
setShowEditProfileModal(false);
|
|
1286
|
+
}, [profileDisplayNameDraft, updateCurrentUserProfile]);
|
|
692
1287
|
const teamAdminHeaders = useCallback(() => {
|
|
693
1288
|
const headers = {};
|
|
694
|
-
const workspaceId = String(
|
|
1289
|
+
const workspaceId = String(teamManagementWorkspaceId || '').trim();
|
|
695
1290
|
if (workspaceId)
|
|
696
1291
|
headers['x-taskforce-workspace-id'] = workspaceId;
|
|
697
1292
|
return headers;
|
|
698
|
-
}, [
|
|
1293
|
+
}, [teamManagementWorkspaceId]);
|
|
699
1294
|
const loadBillingStatus = useCallback(async () => {
|
|
700
|
-
if (
|
|
1295
|
+
if (!isAuthenticated) {
|
|
701
1296
|
setBillingStatus(null);
|
|
702
1297
|
return null;
|
|
703
1298
|
}
|
|
704
1299
|
setBillingLoading(true);
|
|
705
1300
|
setBillingError(null);
|
|
706
1301
|
try {
|
|
707
|
-
const res = await fetch('/api/taskforce/billing/status', {
|
|
1302
|
+
const res = await fetch(buildBillingApiUrl('/api/taskforce/billing/status'), {
|
|
708
1303
|
method: 'GET',
|
|
709
1304
|
credentials: 'include'
|
|
710
1305
|
});
|
|
@@ -725,46 +1320,19 @@ export function StandaloneLayout(props) {
|
|
|
725
1320
|
finally {
|
|
726
1321
|
setBillingLoading(false);
|
|
727
1322
|
}
|
|
728
|
-
}, [
|
|
1323
|
+
}, [buildBillingApiUrl, isAuthenticated]);
|
|
729
1324
|
const handleStartCheckout = useCallback(async () => {
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
method: 'POST',
|
|
736
|
-
credentials: 'include',
|
|
737
|
-
headers: { 'Content-Type': 'application/json' },
|
|
738
|
-
body: JSON.stringify({
|
|
739
|
-
tier: 'team',
|
|
740
|
-
interval: billingIntervalChoice
|
|
741
|
-
})
|
|
742
|
-
});
|
|
743
|
-
const payload = await res.json().catch(() => ({}));
|
|
744
|
-
if (!res.ok) {
|
|
745
|
-
setBillingActionError(String(payload?.error || 'Failed to create checkout session.'));
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
const nextUrl = String(payload?.url || '').trim();
|
|
749
|
-
if (!nextUrl) {
|
|
750
|
-
setBillingActionError('Checkout session did not return a redirect URL.');
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
753
|
-
window.location.assign(nextUrl);
|
|
754
|
-
}
|
|
755
|
-
catch {
|
|
756
|
-
setBillingActionError('Failed to start checkout.');
|
|
757
|
-
}
|
|
758
|
-
finally {
|
|
759
|
-
setBillingActionBusy(false);
|
|
760
|
-
}
|
|
761
|
-
}, [billingIntervalChoice]);
|
|
1325
|
+
const params = new URLSearchParams();
|
|
1326
|
+
params.set('screen', 'plans');
|
|
1327
|
+
params.set('interval', billingIntervalChoice);
|
|
1328
|
+
navigate(`/?${params.toString()}`);
|
|
1329
|
+
}, [billingIntervalChoice, navigate]);
|
|
762
1330
|
const handleOpenBillingPortal = useCallback(async () => {
|
|
763
1331
|
setBillingActionError(null);
|
|
764
1332
|
setBillingNotice(null);
|
|
765
1333
|
setBillingActionBusy(true);
|
|
766
1334
|
try {
|
|
767
|
-
const res = await fetch('/api/taskforce/billing/portal-session', {
|
|
1335
|
+
const res = await fetch(buildBillingApiUrl('/api/taskforce/billing/portal-session'), {
|
|
768
1336
|
method: 'POST',
|
|
769
1337
|
credentials: 'include'
|
|
770
1338
|
});
|
|
@@ -786,18 +1354,23 @@ export function StandaloneLayout(props) {
|
|
|
786
1354
|
finally {
|
|
787
1355
|
setBillingActionBusy(false);
|
|
788
1356
|
}
|
|
789
|
-
}, []);
|
|
1357
|
+
}, [buildBillingApiUrl]);
|
|
790
1358
|
const handleSwitchSubscriptionInterval = useCallback(async () => {
|
|
791
1359
|
setBillingActionError(null);
|
|
792
1360
|
setBillingNotice(null);
|
|
793
1361
|
setBillingActionBusy(true);
|
|
794
1362
|
try {
|
|
795
|
-
const
|
|
1363
|
+
const currentPlanVersionId = String(billingStatus?.planVersionId || '').trim();
|
|
1364
|
+
if (!currentPlanVersionId) {
|
|
1365
|
+
setBillingActionError('No active plan version is linked to this account.');
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
const res = await fetch(buildBillingApiUrl('/api/taskforce/billing/subscription'), {
|
|
796
1369
|
method: 'POST',
|
|
797
1370
|
credentials: 'include',
|
|
798
1371
|
headers: { 'Content-Type': 'application/json' },
|
|
799
1372
|
body: JSON.stringify({
|
|
800
|
-
|
|
1373
|
+
planVersionId: currentPlanVersionId,
|
|
801
1374
|
interval: billingIntervalChoice
|
|
802
1375
|
})
|
|
803
1376
|
});
|
|
@@ -815,11 +1388,11 @@ export function StandaloneLayout(props) {
|
|
|
815
1388
|
finally {
|
|
816
1389
|
setBillingActionBusy(false);
|
|
817
1390
|
}
|
|
818
|
-
}, [billingIntervalChoice, loadBillingStatus]);
|
|
1391
|
+
}, [billingIntervalChoice, billingStatus?.planVersionId, buildBillingApiUrl, loadBillingStatus]);
|
|
819
1392
|
const loadTeamPlanMode = useCallback(async () => {
|
|
820
|
-
if (!
|
|
1393
|
+
if (!canManageTeamWorkspace) {
|
|
821
1394
|
setTeamPlanMode('unknown');
|
|
822
|
-
return;
|
|
1395
|
+
return 'unknown';
|
|
823
1396
|
}
|
|
824
1397
|
try {
|
|
825
1398
|
const res = await fetch('/api/taskforce/account/entitlements', {
|
|
@@ -830,80 +1403,38 @@ export function StandaloneLayout(props) {
|
|
|
830
1403
|
const payload = await res.json().catch(() => ({}));
|
|
831
1404
|
if (!res.ok) {
|
|
832
1405
|
setTeamPlanMode('unknown');
|
|
833
|
-
return;
|
|
1406
|
+
return 'unknown';
|
|
834
1407
|
}
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
const stateAllows = state === 'active' || state === 'trialing' || state === 'grace';
|
|
839
|
-
setTeamPlanMode(planId === 'team' && teamAllowed && stateAllows ? 'team' : 'personal');
|
|
1408
|
+
const nextMode = resolveTeamPlanModeFromEntitlements(payload);
|
|
1409
|
+
setTeamPlanMode(nextMode);
|
|
1410
|
+
return nextMode;
|
|
840
1411
|
}
|
|
841
1412
|
catch {
|
|
842
1413
|
setTeamPlanMode('unknown');
|
|
1414
|
+
return 'unknown';
|
|
843
1415
|
}
|
|
844
|
-
}, [
|
|
1416
|
+
}, [canManageTeamWorkspace, teamAdminHeaders]);
|
|
845
1417
|
useEffect(() => {
|
|
846
1418
|
if (!showAccountMenu && !showTeamManagementModal && !showAccountHub)
|
|
847
1419
|
return;
|
|
848
1420
|
void loadTeamPlanMode();
|
|
849
1421
|
}, [showAccountHub, showAccountMenu, showTeamManagementModal, loadTeamPlanMode]);
|
|
850
1422
|
useEffect(() => {
|
|
851
|
-
if (!
|
|
852
|
-
return;
|
|
853
|
-
void loadBillingStatus();
|
|
854
|
-
}, [loadBillingStatus, showAccountHub]);
|
|
855
|
-
useEffect(() => {
|
|
856
|
-
if (!isAuthenticated)
|
|
1423
|
+
if (!isAuthenticated || !authSessionResolved)
|
|
857
1424
|
return;
|
|
858
|
-
if (
|
|
859
|
-
|
|
860
|
-
setShowAccountHub(true);
|
|
861
|
-
setShowAccountMenu(false);
|
|
862
|
-
setBillingActionError(null);
|
|
863
|
-
setBillingError(null);
|
|
864
|
-
if (location.pathname === '/billing/cancel') {
|
|
865
|
-
setBillingNotice('Checkout canceled. No billing changes were applied.');
|
|
866
|
-
navigate('/', { replace: true });
|
|
1425
|
+
if (!canManageTeamWorkspace) {
|
|
1426
|
+
setTeamPlanMode('unknown');
|
|
867
1427
|
return;
|
|
868
1428
|
}
|
|
869
|
-
|
|
1429
|
+
void loadTeamPlanMode();
|
|
1430
|
+
}, [authSessionResolved, canManageTeamWorkspace, isAuthenticated, loadTeamPlanMode]);
|
|
1431
|
+
useEffect(() => {
|
|
1432
|
+
if (!showAccountHub && !showAccountMenu)
|
|
1433
|
+
return;
|
|
870
1434
|
void loadBillingStatus();
|
|
871
|
-
|
|
872
|
-
const maxAttempts = 30;
|
|
873
|
-
if (billingSuccessPollRef.current) {
|
|
874
|
-
window.clearInterval(billingSuccessPollRef.current);
|
|
875
|
-
billingSuccessPollRef.current = null;
|
|
876
|
-
}
|
|
877
|
-
billingSuccessPollRef.current = window.setInterval(() => {
|
|
878
|
-
attempts += 1;
|
|
879
|
-
void loadBillingStatus().then((payload) => {
|
|
880
|
-
const entitlementState = String(payload?.entitlementState || '').trim().toLowerCase();
|
|
881
|
-
if (entitlementState && entitlementState !== 'free') {
|
|
882
|
-
setBillingNotice('Subscription is active.');
|
|
883
|
-
if (billingSuccessPollRef.current) {
|
|
884
|
-
window.clearInterval(billingSuccessPollRef.current);
|
|
885
|
-
billingSuccessPollRef.current = null;
|
|
886
|
-
}
|
|
887
|
-
navigate('/', { replace: true });
|
|
888
|
-
return;
|
|
889
|
-
}
|
|
890
|
-
if (attempts >= maxAttempts && billingSuccessPollRef.current) {
|
|
891
|
-
window.clearInterval(billingSuccessPollRef.current);
|
|
892
|
-
billingSuccessPollRef.current = null;
|
|
893
|
-
setBillingNotice('Checkout finished, but entitlement update is still pending. Refresh Billing Status in a few seconds.');
|
|
894
|
-
navigate('/', { replace: true });
|
|
895
|
-
}
|
|
896
|
-
});
|
|
897
|
-
}, 1500);
|
|
898
|
-
return () => {
|
|
899
|
-
if (billingSuccessPollRef.current) {
|
|
900
|
-
window.clearInterval(billingSuccessPollRef.current);
|
|
901
|
-
billingSuccessPollRef.current = null;
|
|
902
|
-
}
|
|
903
|
-
};
|
|
904
|
-
}, [isAuthenticated, loadBillingStatus, location.pathname, navigate]);
|
|
1435
|
+
}, [loadBillingStatus, showAccountHub, showAccountMenu]);
|
|
905
1436
|
const loadTeamUsers = useCallback(async () => {
|
|
906
|
-
if (!
|
|
1437
|
+
if (!canManageTeamWorkspace)
|
|
907
1438
|
return;
|
|
908
1439
|
setTeamUsersLoading(true);
|
|
909
1440
|
setTeamMgmtError(null);
|
|
@@ -945,16 +1476,16 @@ export function StandaloneLayout(props) {
|
|
|
945
1476
|
finally {
|
|
946
1477
|
setTeamUsersLoading(false);
|
|
947
1478
|
}
|
|
948
|
-
}, [
|
|
1479
|
+
}, [canManageTeamWorkspace, teamAdminHeaders]);
|
|
949
1480
|
const loadTeamAuditPage = useCallback(async (page) => {
|
|
950
|
-
if (!
|
|
1481
|
+
if (!canManageTeamWorkspace)
|
|
951
1482
|
return;
|
|
952
1483
|
const safePage = Math.max(0, Math.floor(page));
|
|
953
1484
|
setTeamAuditLoading(true);
|
|
954
1485
|
setTeamMgmtError(null);
|
|
955
1486
|
try {
|
|
956
1487
|
const offset = safePage * TEAM_AUDIT_PAGE_SIZE;
|
|
957
|
-
const res = await fetch(`/api/taskforce/admin/audit-logs?limit=${TEAM_AUDIT_PAGE_SIZE}&offset=${offset}`, {
|
|
1488
|
+
const res = await fetch(`/api/taskforce/admin/workspace-audit-logs?limit=${TEAM_AUDIT_PAGE_SIZE}&offset=${offset}`, {
|
|
958
1489
|
method: 'GET',
|
|
959
1490
|
credentials: 'include',
|
|
960
1491
|
headers: teamAdminHeaders()
|
|
@@ -983,7 +1514,7 @@ export function StandaloneLayout(props) {
|
|
|
983
1514
|
finally {
|
|
984
1515
|
setTeamAuditLoading(false);
|
|
985
1516
|
}
|
|
986
|
-
}, [
|
|
1517
|
+
}, [canManageTeamWorkspace, teamAdminHeaders]);
|
|
987
1518
|
const reloadTeamManagementData = useCallback(async () => {
|
|
988
1519
|
await Promise.all([loadTeamUsers(), loadTeamAuditPage(teamAuditPage)]);
|
|
989
1520
|
}, [loadTeamUsers, loadTeamAuditPage, teamAuditPage]);
|
|
@@ -995,10 +1526,14 @@ export function StandaloneLayout(props) {
|
|
|
995
1526
|
setShowSyncStatusModal(false);
|
|
996
1527
|
navigate(`/login?mode=${mode}&next=${encodeURIComponent(target)}`);
|
|
997
1528
|
}, [location.hash, location.pathname, location.search, navigate]);
|
|
998
|
-
const openWorkspaceSetup = useCallback(() => {
|
|
1529
|
+
const openWorkspaceSetup = useCallback((options) => {
|
|
999
1530
|
setShowAccountMenu(false);
|
|
1000
|
-
|
|
1001
|
-
|
|
1531
|
+
setShowCreateWorkspaceConfirm(false);
|
|
1532
|
+
const params = new URLSearchParams({ step: 'workspace' });
|
|
1533
|
+
if (options?.intent === 'create-workspace') {
|
|
1534
|
+
params.set('intent', 'create-workspace');
|
|
1535
|
+
}
|
|
1536
|
+
navigate(`/setup?${params.toString()}`, { replace: true });
|
|
1002
1537
|
}, [navigate]);
|
|
1003
1538
|
const handleSwitchWorkspace = useCallback(async (workspaceId) => {
|
|
1004
1539
|
if (!workspaceId || workspaceActionBusy)
|
|
@@ -1017,53 +1552,24 @@ export function StandaloneLayout(props) {
|
|
|
1017
1552
|
}
|
|
1018
1553
|
await fetchTasks(true);
|
|
1019
1554
|
setShowAccountMenu(false);
|
|
1020
|
-
setWorkspacePanelOpen(false);
|
|
1021
1555
|
}
|
|
1022
1556
|
finally {
|
|
1023
1557
|
setWorkspaceActionBusy(false);
|
|
1024
1558
|
}
|
|
1025
1559
|
}, [switchWorkspace, workspaceActionBusy, fetchTasks, openWorkspaceSetup]);
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1560
|
+
const openTeamManagementModal = useCallback(async () => {
|
|
1561
|
+
setShowAccountMenu(false);
|
|
1562
|
+
setShowTeamManagementModal(true);
|
|
1563
|
+
setTeamManagementTab('members');
|
|
1564
|
+
const resolvedMode = await loadTeamPlanMode();
|
|
1565
|
+
if (resolvedMode !== 'team')
|
|
1032
1566
|
return;
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
if (result.code === 'WORKSPACE_NOT_FOUND' || result.code === 'WORKSPACE_ID_REQUIRED') {
|
|
1040
|
-
openWorkspaceSetup();
|
|
1041
|
-
return;
|
|
1042
|
-
}
|
|
1043
|
-
setWorkspaceActionError(result.error || 'Failed to create workspace.');
|
|
1044
|
-
return;
|
|
1045
|
-
}
|
|
1046
|
-
await fetchTasks(true);
|
|
1047
|
-
setWorkspaceFormName('');
|
|
1048
|
-
setWorkspaceFormDescription('');
|
|
1049
|
-
setWorkspacePanelOpen(false);
|
|
1050
|
-
setShowAccountMenu(false);
|
|
1051
|
-
}
|
|
1052
|
-
finally {
|
|
1053
|
-
setWorkspaceActionBusy(false);
|
|
1054
|
-
}
|
|
1055
|
-
}, [workspaceActionBusy, workspaceFormName, workspaceFormDescription, createWorkspace, fetchTasks, openWorkspaceSetup]);
|
|
1056
|
-
const openTeamManagementModal = useCallback(async () => {
|
|
1057
|
-
setShowAccountMenu(false);
|
|
1058
|
-
setShowTeamManagementModal(true);
|
|
1059
|
-
setTeamManagementTab('members');
|
|
1060
|
-
await loadTeamPlanMode();
|
|
1061
|
-
await loadTeamUsers();
|
|
1062
|
-
await loadTeamAuditPage(0);
|
|
1063
|
-
}, [loadTeamAuditPage, loadTeamPlanMode, loadTeamUsers]);
|
|
1064
|
-
const executeTeamUserAction = useCallback(async (userId, fn) => {
|
|
1065
|
-
setTeamMgmtError(null);
|
|
1066
|
-
setTeamActionBusyUserId(userId);
|
|
1567
|
+
await loadTeamUsers();
|
|
1568
|
+
await loadTeamAuditPage(0);
|
|
1569
|
+
}, [loadTeamAuditPage, loadTeamPlanMode, loadTeamUsers]);
|
|
1570
|
+
const executeTeamUserAction = useCallback(async (userId, fn) => {
|
|
1571
|
+
setTeamMgmtError(null);
|
|
1572
|
+
setTeamActionBusyUserId(userId);
|
|
1067
1573
|
try {
|
|
1068
1574
|
const res = await fn();
|
|
1069
1575
|
const payload = await res.json().catch(() => ({}));
|
|
@@ -1096,7 +1602,7 @@ export function StandaloneLayout(props) {
|
|
|
1096
1602
|
},
|
|
1097
1603
|
credentials: 'include',
|
|
1098
1604
|
body: JSON.stringify({
|
|
1099
|
-
workspaceId:
|
|
1605
|
+
workspaceId: teamManagementWorkspaceId,
|
|
1100
1606
|
email,
|
|
1101
1607
|
role: teamInviteRole,
|
|
1102
1608
|
permissionMode: teamInviteRole === 'member' ? teamInvitePermissionMode : 'read-write'
|
|
@@ -1138,27 +1644,64 @@ export function StandaloneLayout(props) {
|
|
|
1138
1644
|
const hasTypeFilter = filterTypes.length !== activeTypes.length;
|
|
1139
1645
|
const hasPriorityFilter = filterPriorities.length !== priorities.length;
|
|
1140
1646
|
const hasStatusFilter = filterStatus.length !== STATUS_OPTIONS.length;
|
|
1141
|
-
const hasAssigneeFilter =
|
|
1142
|
-
const
|
|
1143
|
-
|
|
1144
|
-
|
|
1647
|
+
const hasAssigneeFilter = filterAssignees.length !== assigneeOptions.length;
|
|
1648
|
+
const hasInitiativeScopeFilter = selectedPlanningInitiativeId.length > 0;
|
|
1649
|
+
const hasWorkstreamScopeFilter = selectedPlanningWorkstreamId.length > 0;
|
|
1650
|
+
return hasCategoryFilter
|
|
1651
|
+
|| hasTypeFilter
|
|
1652
|
+
|| hasPriorityFilter
|
|
1653
|
+
|| hasStatusFilter
|
|
1654
|
+
|| hasAssigneeFilter
|
|
1655
|
+
|| hasInitiativeScopeFilter
|
|
1656
|
+
|| hasWorkstreamScopeFilter;
|
|
1657
|
+
}, [
|
|
1658
|
+
activeCategories.length,
|
|
1659
|
+
activeTypes.length,
|
|
1660
|
+
assigneeOptions.length,
|
|
1661
|
+
filterAssignees.length,
|
|
1662
|
+
filterCategories.length,
|
|
1663
|
+
filterPriorities.length,
|
|
1664
|
+
filterStatus.length,
|
|
1665
|
+
filterTypes.length,
|
|
1666
|
+
priorities.length,
|
|
1667
|
+
selectedPlanningInitiativeId,
|
|
1668
|
+
selectedPlanningWorkstreamId,
|
|
1669
|
+
]);
|
|
1670
|
+
const hasDocumentDropdownFilters = useMemo(() => (documentTypeFilters.length !== DOCUMENT_TYPE_FILTER_OPTIONS.length
|
|
1671
|
+
|| documentAttachmentFilters.length !== DOCUMENT_ATTACHMENT_FILTER_OPTIONS.length), [documentAttachmentFilters.length, documentTypeFilters.length]);
|
|
1672
|
+
const workspaceChromeMode = resolvedWorkspaceModule;
|
|
1673
|
+
const workspaceChromeDefinition = useMemo(() => getWorkspaceChromeDefinition(workspaceChromeMode), [workspaceChromeMode]);
|
|
1674
|
+
const hasActiveWorkspaceFilterControls = useMemo(() => {
|
|
1675
|
+
switch (workspaceChromeDefinition.filterBar.kind) {
|
|
1676
|
+
case 'task-filters':
|
|
1677
|
+
return hasDropdownFilters;
|
|
1678
|
+
case 'document-filters':
|
|
1679
|
+
return hasDocumentDropdownFilters;
|
|
1680
|
+
default:
|
|
1681
|
+
return false;
|
|
1682
|
+
}
|
|
1683
|
+
}, [
|
|
1684
|
+
hasDocumentDropdownFilters,
|
|
1685
|
+
hasDropdownFilters,
|
|
1686
|
+
workspaceChromeDefinition.filterBar.kind,
|
|
1687
|
+
]);
|
|
1145
1688
|
// Check if any filters are set (search + dropdown filters)
|
|
1146
1689
|
const isFiltered = hasSearchFilter || hasDropdownFilters;
|
|
1147
|
-
const totalActiveCount =
|
|
1148
|
-
|
|
1690
|
+
const totalActiveCount = taskScope === 'archived'
|
|
1691
|
+
? archivedScopeAllTasks.length
|
|
1692
|
+
: taskScope === 'deleted'
|
|
1693
|
+
? deletedScopeAllTasks.length
|
|
1694
|
+
: tasks.length;
|
|
1695
|
+
const visibleActiveCount = taskScope === 'archived'
|
|
1696
|
+
? archivedScopeTasks.length
|
|
1697
|
+
: taskScope === 'deleted'
|
|
1698
|
+
? filteredDeletedTasks.length
|
|
1699
|
+
: props.filteredTasks.length;
|
|
1149
1700
|
const activeCountLabel = isFiltered ? `${visibleActiveCount}/${totalActiveCount}` : `${totalActiveCount}`;
|
|
1150
1701
|
const activeCountTitle = isFiltered
|
|
1151
|
-
?
|
|
1152
|
-
:
|
|
1153
|
-
const shouldShowSyncStatus = runtimeMode === 'local';
|
|
1154
|
-
const formattedLastSyncTime = useMemo(() => {
|
|
1155
|
-
if (!userGlobalLastSyncedAt)
|
|
1156
|
-
return 'Never';
|
|
1157
|
-
const parsed = new Date(userGlobalLastSyncedAt);
|
|
1158
|
-
if (Number.isNaN(parsed.getTime()))
|
|
1159
|
-
return 'Never';
|
|
1160
|
-
return parsed.toLocaleString();
|
|
1161
|
-
}, [userGlobalLastSyncedAt]);
|
|
1702
|
+
? `${taskScope.charAt(0).toUpperCase() + taskScope.slice(1)} tasks matching current filters`
|
|
1703
|
+
: `${taskScope.charAt(0).toUpperCase() + taskScope.slice(1)} tasks`;
|
|
1704
|
+
const shouldShowSyncStatus = runtimeMode === 'local' && isAuthenticated;
|
|
1162
1705
|
const formatSyncTimestamp = useCallback((value) => {
|
|
1163
1706
|
if (!value)
|
|
1164
1707
|
return 'Never';
|
|
@@ -1167,259 +1710,379 @@ export function StandaloneLayout(props) {
|
|
|
1167
1710
|
return 'Never';
|
|
1168
1711
|
return parsed.toLocaleString();
|
|
1169
1712
|
}, []);
|
|
1170
|
-
const
|
|
1171
|
-
const
|
|
1172
|
-
const
|
|
1173
|
-
const
|
|
1174
|
-
const
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1713
|
+
const formattedLastSyncTime = useMemo(() => formatSyncTimestamp(workspaceLastSuccessfulSyncAt), [formatSyncTimestamp, workspaceLastSuccessfulSyncAt]);
|
|
1714
|
+
const formattedLastPullTime = useMemo(() => formatSyncTimestamp(workspaceLastPullAt), [formatSyncTimestamp, workspaceLastPullAt]);
|
|
1715
|
+
const formattedLastPushTime = useMemo(() => formatSyncTimestamp(workspaceLastPushAt), [formatSyncTimestamp, workspaceLastPushAt]);
|
|
1716
|
+
const rawSyncLastError = workspaceSyncError || workspaceLastErrorMessage || userGlobalSyncError || 'None';
|
|
1717
|
+
const headerSyncPresentation = useMemo(() => resolveHeaderWorkspaceSyncPresentation({
|
|
1718
|
+
runtimeMode,
|
|
1719
|
+
isAuthenticated,
|
|
1720
|
+
workspaceSyncStatus,
|
|
1721
|
+
workspaceCloudSyncEnabled,
|
|
1722
|
+
workspaceSyncSummary,
|
|
1723
|
+
workspaceSyncRecommendedAction,
|
|
1724
|
+
workspaceSyncError: rawSyncLastError
|
|
1725
|
+
}), [
|
|
1726
|
+
runtimeMode,
|
|
1727
|
+
isAuthenticated,
|
|
1728
|
+
workspaceSyncStatus,
|
|
1729
|
+
workspaceCloudSyncEnabled,
|
|
1730
|
+
workspaceSyncSummary,
|
|
1731
|
+
workspaceSyncRecommendedAction,
|
|
1732
|
+
rawSyncLastError
|
|
1733
|
+
]);
|
|
1734
|
+
const syncLastError = headerSyncPresentation.lastError;
|
|
1735
|
+
const [headerSyncStatus, setHeaderSyncStatus] = useState(headerSyncPresentation.status);
|
|
1736
|
+
const syncStageLabel = useMemo(() => {
|
|
1737
|
+
if (workspaceSyncRepairBusy) {
|
|
1738
|
+
return 'Repairing';
|
|
1739
|
+
}
|
|
1740
|
+
if (workspaceSyncBusy && workspaceSyncPhase === 'error') {
|
|
1741
|
+
return 'Recovering';
|
|
1742
|
+
}
|
|
1743
|
+
switch (workspaceSyncPhase) {
|
|
1744
|
+
case 'provision-local':
|
|
1745
|
+
return 'Initial cloud upload';
|
|
1746
|
+
case 'attach-cloud':
|
|
1747
|
+
return 'Initial cloud pull';
|
|
1748
|
+
case 'active':
|
|
1749
|
+
return 'Active';
|
|
1750
|
+
case 'error':
|
|
1751
|
+
return 'Error';
|
|
1752
|
+
default:
|
|
1753
|
+
return 'Idle';
|
|
1754
|
+
}
|
|
1755
|
+
}, [workspaceSyncPhase, workspaceSyncRepairBusy]);
|
|
1756
|
+
const workspaceSyncDiagnostics = getWorkspaceSyncDiagnostics();
|
|
1757
|
+
const syncDiagnosticsSummary = useMemo(() => ([
|
|
1758
|
+
{
|
|
1759
|
+
label: 'AI profile snapshot',
|
|
1760
|
+
value: `${workspaceSyncDiagnostics.aiProfileSnapshotCount} local profile${workspaceSyncDiagnostics.aiProfileSnapshotCount === 1 ? '' : 's'}`
|
|
1761
|
+
},
|
|
1762
|
+
{
|
|
1763
|
+
label: 'AI profile raw response',
|
|
1764
|
+
value: `${workspaceSyncDiagnostics.aiProfileSnapshotRawCount} from route`
|
|
1765
|
+
},
|
|
1766
|
+
{
|
|
1767
|
+
label: 'Last pushed AI profiles',
|
|
1768
|
+
value: `${workspaceSyncDiagnostics.lastPushedAiProfileCount} tracked`
|
|
1769
|
+
},
|
|
1770
|
+
{
|
|
1771
|
+
label: 'AI watermark map',
|
|
1772
|
+
value: `${workspaceSyncDiagnostics.lastPushedAiProfileWatermarkCount} tracked`
|
|
1773
|
+
},
|
|
1774
|
+
{
|
|
1775
|
+
label: 'Document snapshot',
|
|
1776
|
+
value: `${workspaceSyncDiagnostics.documentSnapshotCount} local doc${workspaceSyncDiagnostics.documentSnapshotCount === 1 ? '' : 's'}`
|
|
1777
|
+
},
|
|
1778
|
+
{
|
|
1779
|
+
label: 'Asset snapshot',
|
|
1780
|
+
value: `${workspaceSyncDiagnostics.assetSnapshotCount} local asset${workspaceSyncDiagnostics.assetSnapshotCount === 1 ? '' : 's'}`
|
|
1781
|
+
},
|
|
1782
|
+
{
|
|
1783
|
+
label: 'Queued full AI sync',
|
|
1784
|
+
value: workspaceSyncDiagnostics.forceFullAiProfilePushQueued ? 'Yes' : 'No'
|
|
1785
|
+
},
|
|
1786
|
+
{
|
|
1787
|
+
label: 'AI snapshot last fetch',
|
|
1788
|
+
value: workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt
|
|
1789
|
+
? new Date(workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt).toLocaleString()
|
|
1790
|
+
: 'Never'
|
|
1791
|
+
},
|
|
1792
|
+
{
|
|
1793
|
+
label: 'AI snapshot fetch error',
|
|
1794
|
+
value: workspaceSyncDiagnostics.aiProfileSnapshotLastFetchError || 'None'
|
|
1795
|
+
},
|
|
1796
|
+
{
|
|
1797
|
+
label: 'AI snapshot skip reason',
|
|
1798
|
+
value: workspaceSyncDiagnostics.aiProfileSnapshotLastSkipReason || 'None'
|
|
1799
|
+
}
|
|
1800
|
+
]), [workspaceSyncDiagnostics]);
|
|
1801
|
+
const shouldDelayHeaderSyncing = headerSyncPresentation.actionable
|
|
1802
|
+
&& workspaceSyncStatus === 'syncing'
|
|
1803
|
+
&& workspaceSyncPhase === 'active'
|
|
1804
|
+
&& workspaceSyncPendingChanges === 0;
|
|
1188
1805
|
useEffect(() => {
|
|
1189
|
-
if (
|
|
1190
|
-
|
|
1191
|
-
syncStateTimerRef.current = null;
|
|
1192
|
-
}
|
|
1193
|
-
const applyState = (next) => {
|
|
1194
|
-
setDisplaySyncState(next);
|
|
1195
|
-
syncStateSinceRef.current = Date.now();
|
|
1196
|
-
};
|
|
1197
|
-
if (rawSyncState === displaySyncState)
|
|
1198
|
-
return;
|
|
1199
|
-
// Delay "syncing" to avoid flicker on very fast sync cycles.
|
|
1200
|
-
if (rawSyncState === 'syncing' || rawSyncState === 'reconnecting') {
|
|
1201
|
-
syncStateTimerRef.current = window.setTimeout(() => {
|
|
1202
|
-
applyState(rawSyncState);
|
|
1203
|
-
}, SYNCING_DISPLAY_DELAY_MS);
|
|
1806
|
+
if (!shouldDelayHeaderSyncing) {
|
|
1807
|
+
setHeaderSyncStatus(headerSyncPresentation.status);
|
|
1204
1808
|
return;
|
|
1205
1809
|
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1810
|
+
const timeoutId = window.setTimeout(() => {
|
|
1811
|
+
setHeaderSyncStatus(headerSyncPresentation.status);
|
|
1812
|
+
}, 1200);
|
|
1813
|
+
return () => window.clearTimeout(timeoutId);
|
|
1814
|
+
}, [headerSyncPresentation.status, shouldDelayHeaderSyncing]);
|
|
1815
|
+
const formatSyncEventLine = useCallback((event) => {
|
|
1816
|
+
const occurredAt = typeof event.occurredAt === 'string' && event.occurredAt.trim().length > 0
|
|
1817
|
+
? event.occurredAt
|
|
1818
|
+
: 'unknown-time';
|
|
1819
|
+
const eventType = typeof event.eventType === 'string' && event.eventType.trim().length > 0
|
|
1820
|
+
? event.eventType.trim()
|
|
1821
|
+
: 'sync';
|
|
1822
|
+
const status = event.status === 'error' ? 'failed' : 'succeeded';
|
|
1823
|
+
const changeCountValue = Number(event.changeCount);
|
|
1824
|
+
const changeCount = Number.isFinite(changeCountValue)
|
|
1825
|
+
? ` (${Math.max(0, Math.floor(changeCountValue))} change${Math.floor(changeCountValue) === 1 ? '' : 's'})`
|
|
1826
|
+
: '';
|
|
1827
|
+
const requestMsValue = Number(event.requestMs);
|
|
1828
|
+
const requestMs = Number.isFinite(requestMsValue)
|
|
1829
|
+
? ` in ${Math.max(0, Math.floor(requestMsValue))}ms`
|
|
1830
|
+
: '';
|
|
1831
|
+
const statusCodeValue = Number(event.statusCode);
|
|
1832
|
+
const statusCode = Number.isFinite(statusCodeValue)
|
|
1833
|
+
? ` [${Math.floor(statusCodeValue)}]`
|
|
1834
|
+
: '';
|
|
1835
|
+
const errorMessage = typeof event.errorMessage === 'string' && event.errorMessage.trim().length > 0
|
|
1836
|
+
? `: ${event.errorMessage.trim()}`
|
|
1837
|
+
: '';
|
|
1838
|
+
return `${occurredAt} ${eventType} ${status}${changeCount}${requestMs}${statusCode}${errorMessage}`;
|
|
1839
|
+
}, []);
|
|
1840
|
+
const syncEventRows = useMemo(() => {
|
|
1841
|
+
if (syncRecentEventsLoading && syncRecentEvents.length === 0) {
|
|
1842
|
+
return [{
|
|
1843
|
+
key: 'loading',
|
|
1844
|
+
text: 'Loading recent sync events...',
|
|
1845
|
+
tone: 'muted'
|
|
1846
|
+
}];
|
|
1847
|
+
}
|
|
1848
|
+
if (syncRecentEventsError) {
|
|
1849
|
+
return [{
|
|
1850
|
+
key: 'error',
|
|
1851
|
+
text: syncRecentEventsError,
|
|
1852
|
+
tone: 'error'
|
|
1853
|
+
}];
|
|
1854
|
+
}
|
|
1855
|
+
if (syncRecentEvents.length === 0) {
|
|
1856
|
+
return [{
|
|
1857
|
+
key: 'empty',
|
|
1858
|
+
text: 'No recent sync events recorded.',
|
|
1859
|
+
tone: 'muted'
|
|
1860
|
+
}];
|
|
1861
|
+
}
|
|
1862
|
+
return syncRecentEvents.map((event, index) => ({
|
|
1863
|
+
key: getSyncEventStableKey(event, index),
|
|
1864
|
+
text: formatSyncEventLine(event),
|
|
1865
|
+
tone: event.status === 'error' ? 'error' : 'default'
|
|
1866
|
+
}));
|
|
1867
|
+
}, [formatSyncEventLine, syncRecentEvents, syncRecentEventsError, syncRecentEventsLoading]);
|
|
1868
|
+
const referenceMismatchCount = useMemo(() => getActiveReferenceMismatchCount(syncRecentEvents), [syncRecentEvents]);
|
|
1869
|
+
const activeReferenceMismatchEvents = useMemo(() => getActiveReferenceMismatchEvents(syncRecentEvents), [syncRecentEvents]);
|
|
1870
|
+
const activeReferenceMismatchSummaries = useMemo(() => (activeReferenceMismatchEvents.map((event, index) => {
|
|
1871
|
+
const details = event.details && typeof event.details === 'object' ? event.details : null;
|
|
1872
|
+
const pathValue = String(details?.path || '').trim();
|
|
1873
|
+
const existingRefValue = Number(details?.existingReferenceNumber);
|
|
1874
|
+
const incomingRefValue = Number(details?.incomingReferenceNumber);
|
|
1875
|
+
const pathLabel = pathValue || `Mismatch ${index + 1}`;
|
|
1876
|
+
const refsLabel = (Number.isFinite(existingRefValue) || Number.isFinite(incomingRefValue))
|
|
1877
|
+
? `Existing ${Number.isFinite(existingRefValue) ? existingRefValue : '?'} vs incoming ${Number.isFinite(incomingRefValue) ? incomingRefValue : '?'}`
|
|
1878
|
+
: null;
|
|
1879
|
+
return {
|
|
1880
|
+
key: getSyncEventStableKey(event, index),
|
|
1881
|
+
pathLabel,
|
|
1882
|
+
refsLabel,
|
|
1883
|
+
};
|
|
1884
|
+
})), [activeReferenceMismatchEvents]);
|
|
1885
|
+
useLayoutEffect(() => {
|
|
1886
|
+
const pendingRestore = pendingSyncEventsScrollRestoreRef.current;
|
|
1887
|
+
const listEl = syncEventsListRef.current;
|
|
1888
|
+
if (!pendingRestore || !listEl)
|
|
1213
1889
|
return;
|
|
1890
|
+
const delta = listEl.scrollHeight - pendingRestore.scrollHeight;
|
|
1891
|
+
listEl.scrollTop = pendingRestore.scrollTop + Math.max(0, delta);
|
|
1892
|
+
pendingSyncEventsScrollRestoreRef.current = null;
|
|
1893
|
+
}, [syncRecentEvents]);
|
|
1894
|
+
const loadRecentSyncEvents = useCallback(async () => {
|
|
1895
|
+
const res = await fetch(`/api/taskforce/sync/events?workspace_id=${encodeURIComponent(currentWorkspaceId)}&limit=15`, {
|
|
1896
|
+
method: 'GET',
|
|
1897
|
+
credentials: 'include'
|
|
1898
|
+
});
|
|
1899
|
+
if (!res.ok) {
|
|
1900
|
+
throw new Error(`Failed to load sync events (${res.status})`);
|
|
1214
1901
|
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1902
|
+
const data = await res.json().catch(() => ({}));
|
|
1903
|
+
return Array.isArray(data?.events)
|
|
1904
|
+
? data.events.filter((event) => event && typeof event === 'object')
|
|
1905
|
+
: [];
|
|
1906
|
+
}, [currentWorkspaceId]);
|
|
1907
|
+
useEffect(() => {
|
|
1908
|
+
if (!showSyncStatusModal)
|
|
1221
1909
|
return;
|
|
1910
|
+
let cancelled = false;
|
|
1911
|
+
const isInitialLoad = syncRecentEvents.length === 0;
|
|
1912
|
+
if (isInitialLoad) {
|
|
1913
|
+
setSyncRecentEventsLoading(true);
|
|
1222
1914
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1915
|
+
void loadRecentSyncEvents()
|
|
1916
|
+
.then((events) => {
|
|
1917
|
+
if (cancelled)
|
|
1918
|
+
return;
|
|
1919
|
+
const listEl = syncEventsListRef.current;
|
|
1920
|
+
pendingSyncEventsScrollRestoreRef.current = listEl && listEl.scrollTop > 8
|
|
1921
|
+
? {
|
|
1922
|
+
scrollTop: listEl.scrollTop,
|
|
1923
|
+
scrollHeight: listEl.scrollHeight
|
|
1924
|
+
}
|
|
1925
|
+
: null;
|
|
1926
|
+
setSyncRecentEvents((current) => {
|
|
1927
|
+
const next = current.length === 0 ? events : mergeRecentSyncEvents(current, events, 15);
|
|
1928
|
+
return areSyncEventListsEqual(current, next) ? current : next;
|
|
1929
|
+
});
|
|
1930
|
+
setSyncRecentEventsError(null);
|
|
1931
|
+
})
|
|
1932
|
+
.catch((error) => {
|
|
1933
|
+
if (cancelled)
|
|
1934
|
+
return;
|
|
1935
|
+
const message = error instanceof Error && error.message.trim().length > 0
|
|
1936
|
+
? error.message.trim()
|
|
1937
|
+
: 'Unable to load recent sync events.';
|
|
1938
|
+
setSyncRecentEventsError(message);
|
|
1939
|
+
if (isInitialLoad) {
|
|
1940
|
+
setSyncRecentEvents([]);
|
|
1230
1941
|
}
|
|
1942
|
+
})
|
|
1943
|
+
.finally(() => {
|
|
1944
|
+
if (cancelled)
|
|
1945
|
+
return;
|
|
1946
|
+
if (isInitialLoad) {
|
|
1947
|
+
setSyncRecentEventsLoading(false);
|
|
1948
|
+
}
|
|
1949
|
+
});
|
|
1950
|
+
return () => {
|
|
1951
|
+
cancelled = true;
|
|
1231
1952
|
};
|
|
1232
|
-
}, [
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
switch (
|
|
1243
|
-
case '
|
|
1244
|
-
return {
|
|
1245
|
-
label: 'Syncing',
|
|
1246
|
-
color: '#f59e0b',
|
|
1247
|
-
border: 'rgba(245, 158, 11, 0.45)',
|
|
1248
|
-
background: 'rgba(245, 158, 11, 0.16)'
|
|
1249
|
-
};
|
|
1250
|
-
case 'reconnecting':
|
|
1251
|
-
return {
|
|
1252
|
-
label: 'Reconnecting',
|
|
1253
|
-
color: '#f59e0b',
|
|
1254
|
-
border: 'rgba(245, 158, 11, 0.45)',
|
|
1255
|
-
background: 'rgba(245, 158, 11, 0.16)'
|
|
1256
|
-
};
|
|
1257
|
-
case 'degraded-fallback':
|
|
1953
|
+
}, [
|
|
1954
|
+
showSyncStatusModal,
|
|
1955
|
+
loadRecentSyncEvents,
|
|
1956
|
+
syncRecentEvents.length,
|
|
1957
|
+
workspaceLastPushAt,
|
|
1958
|
+
workspaceLastPullAt,
|
|
1959
|
+
workspaceLastErrorAt,
|
|
1960
|
+
workspaceSyncStatus
|
|
1961
|
+
]);
|
|
1962
|
+
const syncStatusMeta = useMemo(() => {
|
|
1963
|
+
switch (headerSyncStatus) {
|
|
1964
|
+
case 'off':
|
|
1258
1965
|
return {
|
|
1259
|
-
label: '
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1966
|
+
label: 'Off',
|
|
1967
|
+
icon: 'off',
|
|
1968
|
+
color: '#94a3b8',
|
|
1969
|
+
border: 'rgba(148, 163, 184, 0.35)',
|
|
1970
|
+
background: 'rgba(148, 163, 184, 0.12)'
|
|
1263
1971
|
};
|
|
1264
|
-
case '
|
|
1972
|
+
case 'syncing':
|
|
1265
1973
|
return {
|
|
1266
|
-
label: '
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1974
|
+
label: 'Syncing',
|
|
1975
|
+
icon: 'cloud',
|
|
1976
|
+
color: '#60a5fa',
|
|
1977
|
+
border: 'rgba(96, 165, 250, 0.35)',
|
|
1978
|
+
background: 'rgba(59, 130, 246, 0.12)'
|
|
1270
1979
|
};
|
|
1271
|
-
case '
|
|
1980
|
+
case 'attention':
|
|
1272
1981
|
return {
|
|
1273
|
-
label: '
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1982
|
+
label: 'Needs Attention',
|
|
1983
|
+
icon: 'cloud',
|
|
1984
|
+
color: '#fda4af',
|
|
1985
|
+
border: 'rgba(253, 164, 175, 0.4)',
|
|
1986
|
+
background: 'rgba(239, 68, 68, 0.12)'
|
|
1277
1987
|
};
|
|
1278
|
-
case 'idle':
|
|
1279
1988
|
default:
|
|
1280
1989
|
return {
|
|
1281
|
-
label: '
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1990
|
+
label: 'Healthy',
|
|
1991
|
+
icon: 'cloud',
|
|
1992
|
+
color: '#86efac',
|
|
1993
|
+
border: 'rgba(134, 239, 172, 0.4)',
|
|
1994
|
+
background: 'rgba(34, 197, 94, 0.12)'
|
|
1285
1995
|
};
|
|
1286
1996
|
}
|
|
1287
|
-
}, [
|
|
1288
|
-
const
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
}
|
|
1997
|
+
}, [headerSyncStatus]);
|
|
1998
|
+
const handleCopySyncDetails = useCallback(async () => {
|
|
1999
|
+
const details = [
|
|
2000
|
+
'Taskforce Sync Manager',
|
|
2001
|
+
`Workspace ID: ${currentWorkspaceId}`,
|
|
2002
|
+
`Status: ${syncStatusMeta.label}`,
|
|
2003
|
+
`Summary: ${headerSyncPresentation.summary}`,
|
|
2004
|
+
`Last successful sync: ${formattedLastSyncTime}`,
|
|
2005
|
+
`Last pull from cloud: ${formattedLastPullTime}`,
|
|
2006
|
+
`Last push to cloud: ${formattedLastPushTime}`,
|
|
2007
|
+
`Last error at: ${workspaceSyncDiagnostics.lastErrorAt ? new Date(workspaceSyncDiagnostics.lastErrorAt).toLocaleString() : 'Never'}`,
|
|
2008
|
+
`Pending local changes: ${workspaceSyncPendingChanges}`,
|
|
2009
|
+
`Last error: ${syncLastError}`,
|
|
2010
|
+
`Sync stage: ${syncStageLabel}`,
|
|
2011
|
+
`AI profile snapshot: ${workspaceSyncDiagnostics.aiProfileSnapshotCount}`,
|
|
2012
|
+
`AI profile raw response: ${workspaceSyncDiagnostics.aiProfileSnapshotRawCount}`,
|
|
2013
|
+
`Last pushed AI profiles tracked: ${workspaceSyncDiagnostics.lastPushedAiProfileCount}`,
|
|
2014
|
+
`AI profile watermarks tracked: ${workspaceSyncDiagnostics.lastPushedAiProfileWatermarkCount}`,
|
|
2015
|
+
`AI snapshot last fetch: ${workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt ? new Date(workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt).toLocaleString() : 'Never'}`,
|
|
2016
|
+
`AI snapshot fetch error: ${workspaceSyncDiagnostics.aiProfileSnapshotLastFetchError || 'None'}`,
|
|
2017
|
+
`AI snapshot skip reason: ${workspaceSyncDiagnostics.aiProfileSnapshotLastSkipReason || 'None'}`,
|
|
2018
|
+
`Document snapshot: ${workspaceSyncDiagnostics.documentSnapshotCount}`,
|
|
2019
|
+
`Asset snapshot: ${workspaceSyncDiagnostics.assetSnapshotCount}`,
|
|
2020
|
+
`Reference mismatches detected: ${referenceMismatchCount}`,
|
|
2021
|
+
`Queued full AI sync: ${workspaceSyncDiagnostics.forceFullAiProfilePushQueued ? 'Yes' : 'No'}`
|
|
2022
|
+
];
|
|
2023
|
+
try {
|
|
2024
|
+
const events = syncRecentEvents.length > 0 ? syncRecentEvents : await loadRecentSyncEvents();
|
|
2025
|
+
const recentEventLines = events.map((event) => formatSyncEventLine(event));
|
|
2026
|
+
const copyText = [
|
|
2027
|
+
...details,
|
|
2028
|
+
'',
|
|
2029
|
+
'Recent sync events',
|
|
2030
|
+
...(recentEventLines.length > 0 ? recentEventLines : ['No recent sync events recorded.'])
|
|
2031
|
+
].join('\n');
|
|
2032
|
+
await navigator.clipboard.writeText(copyText);
|
|
2033
|
+
setWorkspaceSyncCopied(true);
|
|
2034
|
+
window.setTimeout(() => {
|
|
2035
|
+
setWorkspaceSyncCopied(false);
|
|
2036
|
+
}, 1800);
|
|
2037
|
+
pushNotice('Sync details copied to clipboard.', 'success');
|
|
1306
2038
|
}
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
color: '#fb923c',
|
|
1311
|
-
border: 'rgba(251, 146, 60, 0.45)',
|
|
1312
|
-
background: 'rgba(251, 146, 60, 0.16)',
|
|
1313
|
-
icon: 'off'
|
|
1314
|
-
};
|
|
2039
|
+
catch {
|
|
2040
|
+
setWorkspaceSyncCopied(false);
|
|
2041
|
+
pushNotice('Failed to copy sync details.', 'error');
|
|
1315
2042
|
}
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
2043
|
+
}, [
|
|
2044
|
+
currentWorkspaceId,
|
|
2045
|
+
syncStatusMeta.label,
|
|
2046
|
+
headerSyncPresentation.summary,
|
|
2047
|
+
formattedLastSyncTime,
|
|
2048
|
+
formattedLastPullTime,
|
|
2049
|
+
formattedLastPushTime,
|
|
2050
|
+
workspaceSyncDiagnostics,
|
|
2051
|
+
workspaceSyncPendingChanges,
|
|
2052
|
+
syncLastError,
|
|
2053
|
+
syncStageLabel,
|
|
2054
|
+
referenceMismatchCount,
|
|
2055
|
+
pushNotice,
|
|
2056
|
+
syncRecentEvents,
|
|
2057
|
+
loadRecentSyncEvents,
|
|
2058
|
+
formatSyncEventLine
|
|
2059
|
+
]);
|
|
2060
|
+
const handleRepairSync = useCallback(async () => {
|
|
2061
|
+
setWorkspaceSyncRepairQueued(false);
|
|
2062
|
+
setWorkspaceSyncRepairBusy(true);
|
|
2063
|
+
try {
|
|
2064
|
+
await resetWorkspaceSyncCursorAndPull();
|
|
1324
2065
|
}
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
label: 'Disconnected',
|
|
1328
|
-
color: '#94a3b8',
|
|
1329
|
-
border: 'rgba(148, 163, 184, 0.45)',
|
|
1330
|
-
background: 'rgba(148, 163, 184, 0.12)',
|
|
1331
|
-
icon: 'off'
|
|
1332
|
-
};
|
|
2066
|
+
finally {
|
|
2067
|
+
setWorkspaceSyncRepairBusy(false);
|
|
1333
2068
|
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
}, [displaySyncState, workspaceCloudSyncEnabled]);
|
|
1342
|
-
const parentFilterLabel = hierarchyRootTaskId
|
|
1343
|
-
? 'Subtree'
|
|
1344
|
-
: parentTaskFilterMode === 'parents'
|
|
1345
|
-
? t('standalone.linksParents')
|
|
1346
|
-
: parentTaskFilterMode === 'linked'
|
|
1347
|
-
? t('standalone.linksLinked')
|
|
1348
|
-
: parentTaskFilterMode === 'unlinked'
|
|
1349
|
-
? t('standalone.linksUnlinked')
|
|
1350
|
-
: t('standalone.linksAll');
|
|
1351
|
-
const selectedHierarchyRootTask = hierarchyRootTaskId ? hierarchyTaskById.get(hierarchyRootTaskId) : undefined;
|
|
1352
|
-
const hierarchySubtreeStats = useMemo(() => {
|
|
1353
|
-
if (!hierarchyDescendantIdSet || !selectedHierarchyRootTask)
|
|
1354
|
-
return null;
|
|
1355
|
-
const selectedTasks = hierarchyTasks.filter((task) => hierarchyDescendantIdSet.has(task.id));
|
|
1356
|
-
let active = 0;
|
|
1357
|
-
let terminal = 0;
|
|
1358
|
-
let overdue = 0;
|
|
1359
|
-
selectedTasks.forEach((task) => {
|
|
1360
|
-
const isTerminal = task.status === 'done' || task.status === 'cancelled' || Boolean(task.isArchived);
|
|
1361
|
-
if (isTerminal) {
|
|
1362
|
-
terminal += 1;
|
|
1363
|
-
}
|
|
1364
|
-
else {
|
|
1365
|
-
active += 1;
|
|
1366
|
-
if (task.dueDate && task.dueDate < todayDateOnly)
|
|
1367
|
-
overdue += 1;
|
|
1368
|
-
}
|
|
1369
|
-
});
|
|
1370
|
-
let depth = 0;
|
|
1371
|
-
const visited = new Set();
|
|
1372
|
-
const stack = [{ id: selectedHierarchyRootTask.id, depth: 0 }];
|
|
1373
|
-
while (stack.length > 0) {
|
|
1374
|
-
const current = stack.pop();
|
|
1375
|
-
if (visited.has(current.id))
|
|
1376
|
-
continue;
|
|
1377
|
-
visited.add(current.id);
|
|
1378
|
-
if (current.depth > depth)
|
|
1379
|
-
depth = current.depth;
|
|
1380
|
-
const children = hierarchyChildrenByParent.get(current.id) || [];
|
|
1381
|
-
children.forEach((child) => {
|
|
1382
|
-
if (!visited.has(child.id) && hierarchyDescendantIdSet.has(child.id)) {
|
|
1383
|
-
stack.push({ id: child.id, depth: current.depth + 1 });
|
|
1384
|
-
}
|
|
1385
|
-
});
|
|
2069
|
+
}, [resetWorkspaceSyncCursorAndPull]);
|
|
2070
|
+
const handleQueueOrRunRepairSync = useCallback(() => {
|
|
2071
|
+
if (workspaceSyncRepairBusy)
|
|
2072
|
+
return;
|
|
2073
|
+
if (workspaceSyncBusy) {
|
|
2074
|
+
setWorkspaceSyncRepairQueued(true);
|
|
2075
|
+
return;
|
|
1386
2076
|
}
|
|
1387
|
-
|
|
1388
|
-
}, [
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
hierarchyDepthTasks.forEach((task) => {
|
|
1392
|
-
const level = (hierarchyGlobalDepthMap.get(task.id) ?? 0) + 1;
|
|
1393
|
-
byLevel.set(level, (byLevel.get(level) || 0) + 1);
|
|
1394
|
-
});
|
|
1395
|
-
return Array.from(byLevel.entries())
|
|
1396
|
-
.sort((a, b) => a[0] - b[0])
|
|
1397
|
-
.map(([level, count]) => ({ level, count }));
|
|
1398
|
-
}, [hierarchyDepthTasks, hierarchyGlobalDepthMap]);
|
|
1399
|
-
const hierarchyVisibleUnlinkedCount = useMemo(() => hierarchyVisibleTasks.filter((task) => !task.parentTaskId && !hierarchyParentIdSet.has(task.id)).length, [hierarchyVisibleTasks, hierarchyParentIdSet]);
|
|
1400
|
-
const hierarchyVisibleRootCount = useMemo(() => hierarchyVisibleTasks.filter((task) => !task.parentTaskId && hierarchyParentIdSet.has(task.id)).length, [hierarchyVisibleTasks, hierarchyParentIdSet]);
|
|
1401
|
-
const hierarchySubtreeTaskList = useMemo(() => {
|
|
1402
|
-
if (!hierarchyRootTaskId || !hierarchyDescendantIdSet)
|
|
1403
|
-
return [];
|
|
1404
|
-
return hierarchyTasks
|
|
1405
|
-
.filter((task) => hierarchyDescendantIdSet.has(task.id))
|
|
1406
|
-
.sort((a, b) => {
|
|
1407
|
-
const levelA = hierarchyGlobalDepthMap.get(a.id) ?? Number.MAX_SAFE_INTEGER;
|
|
1408
|
-
const levelB = hierarchyGlobalDepthMap.get(b.id) ?? Number.MAX_SAFE_INTEGER;
|
|
1409
|
-
if (levelA !== levelB)
|
|
1410
|
-
return levelA - levelB;
|
|
1411
|
-
return a.title.localeCompare(b.title);
|
|
1412
|
-
});
|
|
1413
|
-
}, [hierarchyRootTaskId, hierarchyDescendantIdSet, hierarchyTasks, hierarchyGlobalDepthMap]);
|
|
1414
|
-
const applyHierarchySearchSelection = useCallback(() => {
|
|
1415
|
-
const bestMatch = hierarchySearchMatches[0];
|
|
1416
|
-
if (!bestMatch)
|
|
2077
|
+
void handleRepairSync();
|
|
2078
|
+
}, [handleRepairSync, workspaceSyncBusy, workspaceSyncRepairBusy]);
|
|
2079
|
+
useEffect(() => {
|
|
2080
|
+
if (!workspaceSyncRepairQueued)
|
|
1417
2081
|
return;
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
}, [hierarchySearchMatches, parentTaskFilterMode, setParentTaskFilterMode]);
|
|
2082
|
+
if (workspaceSyncBusy || workspaceSyncRepairBusy)
|
|
2083
|
+
return;
|
|
2084
|
+
void handleRepairSync();
|
|
2085
|
+
}, [handleRepairSync, workspaceSyncBusy, workspaceSyncRepairBusy, workspaceSyncRepairQueued]);
|
|
1423
2086
|
const selectedDateObj = useMemo(() => parseDateOnlyLocal(scheduleSelectedDate) || new Date(), [scheduleSelectedDate]);
|
|
1424
2087
|
const scheduleWeekStart = useMemo(() => startOfWeek(selectedDateObj, globalWeekStartsOn), [selectedDateObj, globalWeekStartsOn]);
|
|
1425
2088
|
const scheduleDates = useMemo(() => {
|
|
@@ -1490,99 +2153,7 @@ export function StandaloneLayout(props) {
|
|
|
1490
2153
|
setScheduleBulkBusy(false);
|
|
1491
2154
|
}
|
|
1492
2155
|
}, [scheduleBulkBusy, expiredRecoveryCandidates, handleUpdateTask, fetchTasks]);
|
|
1493
|
-
const handleScheduleParentDrop = useCallback((payload) => {
|
|
1494
|
-
if (payload.targetDate < todayDateOnly) {
|
|
1495
|
-
const byId = new Map(tasks.map((task) => [task.id, task]));
|
|
1496
|
-
const hasNonTerminalLeaf = payload.toScheduleLeafTaskIds.some((taskId) => {
|
|
1497
|
-
const task = byId.get(taskId);
|
|
1498
|
-
if (!task)
|
|
1499
|
-
return true;
|
|
1500
|
-
return task.status !== 'done' && task.status !== 'cancelled';
|
|
1501
|
-
});
|
|
1502
|
-
if (hasNonTerminalLeaf) {
|
|
1503
|
-
window.alert(t('schedule.completedOnlyPastSchedule'));
|
|
1504
|
-
return;
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
if (payload.toScheduleLeafTaskIds.length === 0) {
|
|
1508
|
-
window.alert(t('schedule.noUnscheduledLeafTasks', { parentTitle: payload.parentTitle }));
|
|
1509
|
-
return;
|
|
1510
|
-
}
|
|
1511
|
-
setScheduleParentDropPrompt(payload);
|
|
1512
|
-
}, [tasks, todayDateOnly]);
|
|
1513
|
-
const confirmScheduleParentDrop = useCallback(async () => {
|
|
1514
|
-
if (!scheduleParentDropPrompt || scheduleParentDropBusy)
|
|
1515
|
-
return;
|
|
1516
|
-
setScheduleParentDropBusy(true);
|
|
1517
|
-
try {
|
|
1518
|
-
const { targetDate, toScheduleLeafTaskIds } = scheduleParentDropPrompt;
|
|
1519
|
-
const dayTasks = tasks
|
|
1520
|
-
.filter((task) => task.scheduledDate === targetDate && !toScheduleLeafTaskIds.includes(task.id))
|
|
1521
|
-
.sort((a, b) => {
|
|
1522
|
-
const orderA = typeof a.orderInDay === 'number' ? a.orderInDay : Number.MAX_SAFE_INTEGER;
|
|
1523
|
-
const orderB = typeof b.orderInDay === 'number' ? b.orderInDay : Number.MAX_SAFE_INTEGER;
|
|
1524
|
-
if (orderA !== orderB)
|
|
1525
|
-
return orderA - orderB;
|
|
1526
|
-
return String(a.createdAt || '').localeCompare(String(b.createdAt || ''));
|
|
1527
|
-
});
|
|
1528
|
-
let nextOrder = dayTasks.length;
|
|
1529
|
-
for (const taskId of toScheduleLeafTaskIds) {
|
|
1530
|
-
await handleUpdateTask(taskId, {
|
|
1531
|
-
scheduledDate: targetDate,
|
|
1532
|
-
scheduledWeekKey: dateOnlyToIsoWeekKey(targetDate),
|
|
1533
|
-
orderInDay: nextOrder++
|
|
1534
|
-
});
|
|
1535
|
-
}
|
|
1536
|
-
setScheduleParentDropPrompt(null);
|
|
1537
|
-
await fetchTasks(true);
|
|
1538
|
-
}
|
|
1539
|
-
finally {
|
|
1540
|
-
setScheduleParentDropBusy(false);
|
|
1541
|
-
}
|
|
1542
|
-
}, [scheduleParentDropPrompt, scheduleParentDropBusy, tasks, handleUpdateTask, fetchTasks]);
|
|
1543
|
-
const cancelScheduleParentDrop = useCallback(() => {
|
|
1544
|
-
if (scheduleParentDropBusy)
|
|
1545
|
-
return;
|
|
1546
|
-
setScheduleParentDropPrompt(null);
|
|
1547
|
-
}, [scheduleParentDropBusy]);
|
|
1548
|
-
const scheduleMonthDate = useMemo(() => {
|
|
1549
|
-
const [yearRaw, monthRaw] = scheduleCalendarMonth.split('-');
|
|
1550
|
-
const year = Number(yearRaw);
|
|
1551
|
-
const month = Number(monthRaw);
|
|
1552
|
-
if (!Number.isInteger(year) || !Number.isInteger(month) || month < 1 || month > 12) {
|
|
1553
|
-
return new Date(selectedDateObj.getFullYear(), selectedDateObj.getMonth(), 1);
|
|
1554
|
-
}
|
|
1555
|
-
return new Date(year, month - 1, 1);
|
|
1556
|
-
}, [scheduleCalendarMonth, selectedDateObj]);
|
|
1557
|
-
const scheduleCalendarMonthLabel = useMemo(() => formatDate(scheduleMonthDate, { month: 'long', year: 'numeric' }, resolvedLocale), [scheduleMonthDate, resolvedLocale]);
|
|
1558
2156
|
const scheduleWeekLabel = useMemo(() => formatDate(scheduleWeekStart, { month: 'short', day: 'numeric', year: 'numeric' }, resolvedLocale), [scheduleWeekStart, resolvedLocale]);
|
|
1559
|
-
const calendarCells = useMemo(() => {
|
|
1560
|
-
const start = startOfWeek(scheduleMonthDate, globalWeekStartsOn);
|
|
1561
|
-
return Array.from({ length: 42 }, (_, i) => addDays(start, i));
|
|
1562
|
-
}, [scheduleMonthDate, globalWeekStartsOn]);
|
|
1563
|
-
const calendarTaskSignals = useMemo(() => {
|
|
1564
|
-
const byDate = new Map();
|
|
1565
|
-
for (const task of scheduleBaseTasks) {
|
|
1566
|
-
const dateOnly = task.scheduledDate || '';
|
|
1567
|
-
if (!dateOnly)
|
|
1568
|
-
continue;
|
|
1569
|
-
const isTerminal = task.status === 'done' || task.status === 'cancelled';
|
|
1570
|
-
const isExpired = !isTerminal && dateOnly < todayDateOnly;
|
|
1571
|
-
const existing = byDate.get(dateOnly);
|
|
1572
|
-
if (existing) {
|
|
1573
|
-
existing.count += 1;
|
|
1574
|
-
if (isExpired)
|
|
1575
|
-
existing.hasExpired = true;
|
|
1576
|
-
}
|
|
1577
|
-
else {
|
|
1578
|
-
byDate.set(dateOnly, { count: 1, hasExpired: isExpired });
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
return byDate;
|
|
1582
|
-
}, [scheduleBaseTasks, todayDateOnly]);
|
|
1583
|
-
const calendarWeekdayLabels = useMemo(() => (globalWeekStartsOn === 'sunday'
|
|
1584
|
-
? ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
|
1585
|
-
: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']), [globalWeekStartsOn]);
|
|
1586
2157
|
// Calculate columns based on grouping
|
|
1587
2158
|
const scheduleColumns = useMemo(() => {
|
|
1588
2159
|
const orderedKeys = globalWeekStartsOn === 'sunday'
|
|
@@ -1620,8 +2191,8 @@ export function StandaloneLayout(props) {
|
|
|
1620
2191
|
case 'type':
|
|
1621
2192
|
return (activeTypes || []).map(t => ({
|
|
1622
2193
|
...t,
|
|
1623
|
-
icon: TypeMeta[t.value]?.icon,
|
|
1624
|
-
color: TypeMeta[t.value]?.color
|
|
2194
|
+
icon: t.icon || TypeMeta[t.value]?.icon,
|
|
2195
|
+
color: t.color || TypeMeta[t.value]?.color
|
|
1625
2196
|
}));
|
|
1626
2197
|
case 'priority':
|
|
1627
2198
|
return priorities;
|
|
@@ -1636,159 +2207,718 @@ export function StandaloneLayout(props) {
|
|
|
1636
2207
|
case 'approach':
|
|
1637
2208
|
return approaches;
|
|
1638
2209
|
case 'assignee':
|
|
1639
|
-
return
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
2210
|
+
return assigneeOptions.map((option) => ({
|
|
2211
|
+
value: option.value,
|
|
2212
|
+
label: option.label,
|
|
2213
|
+
icon: option.icon === 'HelpCircle' ? 'Circle' : option.icon,
|
|
2214
|
+
color: option.color,
|
|
2215
|
+
}));
|
|
1644
2216
|
case 'status':
|
|
1645
2217
|
return [
|
|
1646
|
-
|
|
1647
|
-
{
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
2218
|
+
...STATUS_OPTIONS.filter((option) => option.value !== 'done' && option.value !== 'cancelled'),
|
|
2219
|
+
{
|
|
2220
|
+
value: 'completed',
|
|
2221
|
+
label: 'Completed',
|
|
2222
|
+
icon: getStatusDefinition('done').icon,
|
|
2223
|
+
color: getStatusDefinition('done').color
|
|
2224
|
+
}
|
|
1651
2225
|
];
|
|
1652
|
-
case '
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
2226
|
+
case 'schedule':
|
|
2227
|
+
return scheduleColumns;
|
|
2228
|
+
default:
|
|
2229
|
+
return activeCategories;
|
|
2230
|
+
}
|
|
2231
|
+
}, [groupBy, activeCategories, activeTypes, priorities, approaches, assigneeOptions, scheduleColumns]);
|
|
2232
|
+
// === SECTION: AccountMenuControl ===
|
|
2233
|
+
// Not extracted: ~25 distinct props from local state would be needed.
|
|
2234
|
+
// References: isAuthenticated, authIdentityLabel, billingStatus, runtimeLabel,
|
|
2235
|
+
// connectedEnvironmentLabel, canManageCloudWorkspaces, workspaceActionBusy,
|
|
2236
|
+
// setWorkspacePanelOpen, canOpenTeamManagement, openTeamManagementModal, and more.
|
|
2237
|
+
const accountMenuControl = (_jsxs("div", { className: accountStyles.accountMenuWrap, ref: accountMenuRef, children: [_jsx("button", { className: `tf-control-icon ${accountStyles.avatarBtn}`, onClick: () => setShowAccountMenu(prev => !prev), title: isAuthenticated ? 'Account' : 'Account (Not signed in)', "aria-label": isAuthenticated ? 'Account' : 'Account (Not signed in)', "aria-haspopup": "menu", "aria-expanded": showAccountMenu, children: _jsx("span", { className: accountStyles.avatarBadge, "aria-hidden": "true", children: isAuthenticated ? accountBadgeInitial : '?' }) }), showAccountMenu && (_jsxs("div", { className: accountStyles.accountMenu, role: "menu", "aria-label": "Account menu", children: [_jsxs("div", { className: accountStyles.accountMenuSection, children: [_jsx("div", { className: accountStyles.accountMenuSectionLabel, children: "Account" }), _jsx("div", { className: accountStyles.accountMenuHint, children: isAuthBootstrapPending
|
|
2238
|
+
? 'Checking sign-in status...'
|
|
2239
|
+
: isAuthenticated
|
|
2240
|
+
? (_jsx(_Fragment, { children: hasAuthIdentity
|
|
2241
|
+
? (_jsx(_Fragment, { children: _jsxs("span", { className: accountStyles.accountIdentityBlock, children: [_jsx("span", { className: accountStyles.accountIdentityEmail, children: authIdentityLabel }), hasAuthEmail && authDisplayName && (_jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Email" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: authEmailLabel })] })), hasSubscription && (_jsxs("button", { "aria-label": `Subscription ${subscriptionLabel}`, className: `${accountStyles.accountIdentityMetaLine} ${accountStyles.accountIdentityMetaAction}`, onClick: () => {
|
|
2242
|
+
setShowAccountMenu(false);
|
|
2243
|
+
openPlansScreen();
|
|
2244
|
+
}, type: "button", children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Subscription" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: subscriptionLabel })] })), _jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Runtime" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: runtimeLabel })] }), _jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Environment" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: connectedEnvironmentLabel })] })] }) }))
|
|
2245
|
+
: 'Signed in' }))
|
|
2246
|
+
: 'Not signed in' })] }), canManageCloudWorkspaces && (_jsxs("div", { className: accountStyles.accountMenuSection, children: [_jsx("div", { className: accountStyles.accountMenuSectionLabel, children: "Workspaces (Owned + Invited)" }), availableWorkspaces.length === 0 && (_jsx("div", { className: accountStyles.accountMenuHint, children: "No workspaces found for this account yet." })), availableWorkspaces.map((workspace) => (_jsxs("button", { className: `${accountStyles.accountMenuItem} ${workspace.id === currentWorkspaceId ? accountStyles.accountMenuItemActive : ''}`, onClick: () => handleSwitchWorkspace(workspace.id), role: "menuitem", disabled: workspaceActionBusy || workspace.id === currentWorkspaceId, title: workspace.description || workspace.name, children: [_jsx(Folder, { size: 15 }), _jsxs("span", { style: { display: 'flex', flexDirection: 'column', gap: '2px' }, children: [_jsx("span", { children: workspace.name }), _jsx("span", { className: accountStyles.accountMenuMeta, children: workspace.role })] })] }, workspace.id))), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
|
|
2247
|
+
setWorkspaceActionError('');
|
|
2248
|
+
setShowCreateWorkspaceConfirm(true);
|
|
2249
|
+
setShowAccountMenu(false);
|
|
2250
|
+
}, role: "menuitem", disabled: workspaceActionBusy, children: [_jsx(Plus, { size: 15 }), "Create Workspace"] }), workspaceActionError && (_jsx("div", { className: accountStyles.accountMenuError, children: workspaceActionError }))] })), canOpenTeamManagement && (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => { void openTeamManagementModal(); }, role: "menuitem", children: [_jsx(Users, { size: 15 }), "Team Management"] })), isAuthenticated && (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
|
|
2251
|
+
setShowAccountMenu(false);
|
|
2252
|
+
setShowEditProfileModal(true);
|
|
2253
|
+
}, role: "menuitem", children: [_jsx(User, { size: 15 }), "Edit Profile"] })), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
|
|
2254
|
+
setShowAccountMenu(false);
|
|
2255
|
+
openPlansScreen();
|
|
2256
|
+
}, role: "menuitem", children: [_jsx(Layers, { size: 15 }), "Plans"] }), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
|
|
2257
|
+
setShowAccountMenu(false);
|
|
2258
|
+
setShowAccountHub(true);
|
|
2259
|
+
}, role: "menuitem", children: [_jsx(User, { size: 15 }), "Account Hub"] }), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
|
|
2260
|
+
setShowAccountMenu(false);
|
|
2261
|
+
setShowHelpModal(true);
|
|
2262
|
+
}, role: "menuitem", children: [_jsx(HelpCircle, { size: 15 }), "Help & Tutorial"] }), runtimeMode === 'local' && !isAuthenticated && !isAuthBootstrapPending && (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
|
|
2263
|
+
openSharedLoginRoute('login');
|
|
2264
|
+
}, role: "menuitem", children: [_jsx(User, { size: 15 }), "Sign In / Register"] })), isAuthenticated && (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: async () => {
|
|
2265
|
+
setShowAccountMenu(false);
|
|
2266
|
+
await logout();
|
|
2267
|
+
}, role: "menuitem", children: [_jsx(LogOut, { size: 15 }), "Sign Out"] }))] }))] }));
|
|
2268
|
+
const cycleEmptyColumnVisibility = useCallback(() => {
|
|
2269
|
+
setEmptyColumnMode((prev) => {
|
|
2270
|
+
if (prev === 'show')
|
|
2271
|
+
return 'collapse';
|
|
2272
|
+
if (prev === 'collapse')
|
|
2273
|
+
return 'hide';
|
|
2274
|
+
return 'show';
|
|
2275
|
+
});
|
|
2276
|
+
}, [setEmptyColumnMode]);
|
|
2277
|
+
const openAddTaskModal = useCallback(() => {
|
|
2278
|
+
resetForm();
|
|
2279
|
+
setActiveWorkspaceModule('tasks');
|
|
2280
|
+
setActiveTab('add');
|
|
2281
|
+
}, [resetForm, setActiveTab, setActiveWorkspaceModule]);
|
|
2282
|
+
const openAddTaskModalForWorkstream = useCallback((workstreamId) => {
|
|
2283
|
+
const workstream = props.workstreams.find((item) => item.id === workstreamId);
|
|
2284
|
+
resetForm();
|
|
2285
|
+
setWorkstreamInput(formatWorkstreamReference(workstream) || workstreamId);
|
|
2286
|
+
setActiveWorkspaceModule('tasks');
|
|
2287
|
+
setActiveTab('add');
|
|
2288
|
+
}, [props.workstreams, resetForm, setActiveTab, setActiveWorkspaceModule, setWorkstreamInput]);
|
|
2289
|
+
const openPlanningEditor = useCallback((editor) => {
|
|
2290
|
+
setPlanningEditor(editor);
|
|
2291
|
+
setPlanningPrimaryCollapsed(false);
|
|
2292
|
+
setPlanningSecondaryEditor(null);
|
|
2293
|
+
setPlanningSecondaryCollapsed(false);
|
|
2294
|
+
setPlanningDraftTitle('');
|
|
2295
|
+
setPlanningDraftDescription('');
|
|
2296
|
+
setPlanningDraftOwner('');
|
|
2297
|
+
const selectedInitiative = editor.entityType === 'workstream' && selectedPlanningInitiativeId
|
|
2298
|
+
? planningStructure.initiativeById.get(selectedPlanningInitiativeId) || null
|
|
2299
|
+
: null;
|
|
2300
|
+
setPlanningDraftInitiativeId(selectedInitiative ? formatInitiativeReference(selectedInitiative) : '');
|
|
2301
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2302
|
+
}, [planningStructure.initiativeById, selectedPlanningInitiativeId]);
|
|
2303
|
+
const openPlanningSecondaryEditor = useCallback((editor, initiativeId) => {
|
|
2304
|
+
setPlanningSecondaryEditor(editor);
|
|
2305
|
+
setPlanningSecondaryCollapsed(false);
|
|
2306
|
+
setPlanningDraftTitle('');
|
|
2307
|
+
setPlanningDraftDescription('');
|
|
2308
|
+
setPlanningDraftOwner('');
|
|
2309
|
+
const selectedInitiative = editor.entityType === 'workstream'
|
|
2310
|
+
? ((initiativeId ? planningStructure.initiativeById.get(initiativeId) : null)
|
|
2311
|
+
|| (selectedPlanningInitiativeId ? planningStructure.initiativeById.get(selectedPlanningInitiativeId) || null : null))
|
|
2312
|
+
: null;
|
|
2313
|
+
setPlanningDraftInitiativeId(selectedInitiative ? formatInitiativeReference(selectedInitiative) : '');
|
|
2314
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2315
|
+
}, [planningStructure.initiativeById, selectedPlanningInitiativeId]);
|
|
2316
|
+
const closePlanningEditor = useCallback(() => {
|
|
2317
|
+
setPlanningEditor(null);
|
|
2318
|
+
setPlanningDraftTitle('');
|
|
2319
|
+
setPlanningDraftDescription('');
|
|
2320
|
+
setPlanningDraftOwner('');
|
|
2321
|
+
setPlanningDraftInitiativeId('');
|
|
2322
|
+
}, []);
|
|
2323
|
+
const closePlanningSecondaryEditor = useCallback(() => {
|
|
2324
|
+
setPlanningSecondaryEditor(null);
|
|
2325
|
+
setPlanningDraftTitle('');
|
|
2326
|
+
setPlanningDraftDescription('');
|
|
2327
|
+
setPlanningDraftOwner('');
|
|
2328
|
+
setPlanningDraftInitiativeId('');
|
|
2329
|
+
}, []);
|
|
2330
|
+
const submitPlanningEditor = useCallback(async () => {
|
|
2331
|
+
const activeEditor = planningSecondaryEditor || planningEditor;
|
|
2332
|
+
if (!activeEditor)
|
|
2333
|
+
return;
|
|
2334
|
+
const isSecondaryEditor = Boolean(planningSecondaryEditor);
|
|
2335
|
+
const ownerId = planningDraftOwner.trim() || null;
|
|
2336
|
+
try {
|
|
2337
|
+
if (activeEditor.entityType === 'initiative') {
|
|
2338
|
+
if (activeEditor.mode === 'edit') {
|
|
2339
|
+
await props.updateInitiative(activeEditor.targetId, {
|
|
2340
|
+
title: planningDraftTitle.trim() || 'Untitled Initiative',
|
|
2341
|
+
description: planningDraftDescription.trim() || null,
|
|
2342
|
+
ownerId,
|
|
2343
|
+
});
|
|
2344
|
+
setPlanningDrawerDetail({ type: 'initiative', id: activeEditor.targetId });
|
|
2345
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2346
|
+
pushNotice('Initiative updated', 'success');
|
|
1660
2347
|
}
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
color: level === 1 ? '#3b82f6' : '#64748b'
|
|
1667
|
-
}));
|
|
1668
|
-
if (parentTaskFilterMode === 'all' && hierarchyShowUnlinkedColumn) {
|
|
1669
|
-
hierarchyColumns.push({
|
|
1670
|
-
value: 'standalone',
|
|
1671
|
-
label: 'Unlinked',
|
|
1672
|
-
icon: 'Circle',
|
|
1673
|
-
color: '#64748b'
|
|
2348
|
+
else {
|
|
2349
|
+
const created = await props.createInitiative({
|
|
2350
|
+
title: planningDraftTitle.trim() || 'Untitled Initiative',
|
|
2351
|
+
description: planningDraftDescription.trim() || null,
|
|
2352
|
+
ownerId,
|
|
1674
2353
|
});
|
|
2354
|
+
setPlanningDrawerDetail({ type: 'initiative', id: created.id });
|
|
2355
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2356
|
+
pushNotice('Initiative created', 'success');
|
|
1675
2357
|
}
|
|
1676
|
-
return hierarchyColumns;
|
|
1677
2358
|
}
|
|
1678
|
-
|
|
1679
|
-
|
|
2359
|
+
else {
|
|
2360
|
+
const nextInitiativeRaw = planningDraftInitiativeId.trim();
|
|
2361
|
+
const resolvedInitiative = nextInitiativeRaw.length === 0
|
|
2362
|
+
? null
|
|
2363
|
+
: resolveInitiativeReference(planningStructure.initiatives, nextInitiativeRaw);
|
|
2364
|
+
if (nextInitiativeRaw.length > 0 && !resolvedInitiative) {
|
|
2365
|
+
pushNotice('Initiative not found by that reference.', 'error');
|
|
2366
|
+
return;
|
|
2367
|
+
}
|
|
2368
|
+
const patch = {
|
|
2369
|
+
title: planningDraftTitle.trim() || 'Untitled Workstream',
|
|
2370
|
+
description: planningDraftDescription.trim() || null,
|
|
2371
|
+
ownerId,
|
|
2372
|
+
initiativeId: resolvedInitiative?.id || null,
|
|
2373
|
+
};
|
|
2374
|
+
if (activeEditor.mode === 'edit') {
|
|
2375
|
+
await props.updateWorkstream(activeEditor.targetId, patch);
|
|
2376
|
+
if (isSecondaryEditor) {
|
|
2377
|
+
setPlanningNestedWorkstreamDetailId(activeEditor.targetId);
|
|
2378
|
+
}
|
|
2379
|
+
else {
|
|
2380
|
+
setPlanningDrawerDetail({ type: 'workstream', id: activeEditor.targetId });
|
|
2381
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2382
|
+
}
|
|
2383
|
+
pushNotice('Workstream updated', 'success');
|
|
2384
|
+
}
|
|
2385
|
+
else {
|
|
2386
|
+
const created = await props.createWorkstream(patch);
|
|
2387
|
+
if (isSecondaryEditor) {
|
|
2388
|
+
setPlanningNestedWorkstreamDetailId(created.id);
|
|
2389
|
+
}
|
|
2390
|
+
else {
|
|
2391
|
+
setPlanningDrawerDetail({ type: 'workstream', id: created.id });
|
|
2392
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2393
|
+
}
|
|
2394
|
+
pushNotice('Workstream created', 'success');
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
if (isSecondaryEditor) {
|
|
2398
|
+
closePlanningSecondaryEditor();
|
|
2399
|
+
}
|
|
2400
|
+
else {
|
|
2401
|
+
closePlanningEditor();
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
catch (error) {
|
|
2405
|
+
pushNotice(error instanceof Error ? error.message : 'Failed to save planning item.', 'error');
|
|
2406
|
+
}
|
|
2407
|
+
}, [
|
|
2408
|
+
closePlanningEditor,
|
|
2409
|
+
closePlanningSecondaryEditor,
|
|
2410
|
+
planningDraftDescription,
|
|
2411
|
+
planningDraftInitiativeId,
|
|
2412
|
+
planningDraftOwner,
|
|
2413
|
+
planningDraftTitle,
|
|
2414
|
+
planningEditor,
|
|
2415
|
+
planningSecondaryEditor,
|
|
2416
|
+
planningStructure.initiatives,
|
|
2417
|
+
props,
|
|
2418
|
+
pushNotice,
|
|
2419
|
+
]);
|
|
2420
|
+
const togglePlanningInitiative = useCallback((initiativeId) => {
|
|
2421
|
+
setExpandedPlanningInitiativeIds((current) => {
|
|
2422
|
+
const next = new Set(current);
|
|
2423
|
+
if (next.has(initiativeId)) {
|
|
2424
|
+
next.delete(initiativeId);
|
|
2425
|
+
}
|
|
2426
|
+
else {
|
|
2427
|
+
next.add(initiativeId);
|
|
2428
|
+
}
|
|
2429
|
+
return next;
|
|
2430
|
+
});
|
|
2431
|
+
}, []);
|
|
2432
|
+
const selectPlanningInitiative = useCallback((initiativeId) => {
|
|
2433
|
+
setSelectedPlanningInitiativeId((current) => current === initiativeId ? '' : initiativeId);
|
|
2434
|
+
setSelectedPlanningWorkstreamId('');
|
|
2435
|
+
}, []);
|
|
2436
|
+
const selectPlanningWorkstream = useCallback((workstreamId, initiativeId) => {
|
|
2437
|
+
setSelectedPlanningWorkstreamId((current) => {
|
|
2438
|
+
const nextWorkstreamId = current === workstreamId ? '' : workstreamId;
|
|
2439
|
+
setSelectedPlanningInitiativeId(nextWorkstreamId ? (initiativeId || '') : '');
|
|
2440
|
+
return nextWorkstreamId;
|
|
2441
|
+
});
|
|
2442
|
+
}, []);
|
|
2443
|
+
const openInitiativeDetails = useCallback((initiativeId) => {
|
|
2444
|
+
setPlanningEditor(null);
|
|
2445
|
+
setPlanningSecondaryEditor(null);
|
|
2446
|
+
setPlanningPrimaryCollapsed(false);
|
|
2447
|
+
setPlanningSecondaryCollapsed(false);
|
|
2448
|
+
setPlanningDrawerDetail(initiativeId ? { type: 'initiative', id: initiativeId } : null);
|
|
2449
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2450
|
+
}, []);
|
|
2451
|
+
const openWorkstreamDetails = useCallback((workstreamId) => {
|
|
2452
|
+
setPlanningEditor(null);
|
|
2453
|
+
setPlanningSecondaryEditor(null);
|
|
2454
|
+
setPlanningPrimaryCollapsed(false);
|
|
2455
|
+
setPlanningSecondaryCollapsed(false);
|
|
2456
|
+
setPlanningDrawerDetail(workstreamId ? { type: 'workstream', id: workstreamId } : null);
|
|
2457
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2458
|
+
}, []);
|
|
2459
|
+
const openNestedWorkstreamDetails = useCallback((workstreamId) => {
|
|
2460
|
+
setPlanningSecondaryEditor(null);
|
|
2461
|
+
setPlanningSecondaryCollapsed(false);
|
|
2462
|
+
setPlanningNestedWorkstreamDetailId(workstreamId);
|
|
2463
|
+
}, []);
|
|
2464
|
+
const ownerValueByLabel = useMemo(() => {
|
|
2465
|
+
const map = new Map();
|
|
2466
|
+
assigneeOptions.forEach((option) => {
|
|
2467
|
+
map.set(option.label, String(option.value));
|
|
2468
|
+
});
|
|
2469
|
+
return map;
|
|
2470
|
+
}, [assigneeOptions]);
|
|
2471
|
+
const openPlanningCreateEditor = useCallback((type, initiativeId) => {
|
|
2472
|
+
if (type === 'workstream' && initiativeId) {
|
|
2473
|
+
openPlanningSecondaryEditor({ mode: 'create', entityType: 'workstream' }, initiativeId);
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
openPlanningEditor({ mode: 'create', entityType: type });
|
|
2477
|
+
if (type === 'workstream') {
|
|
2478
|
+
const initiative = (initiativeId ? planningStructure.initiativeById.get(initiativeId) : null)
|
|
2479
|
+
|| (selectedPlanningInitiativeId ? planningStructure.initiativeById.get(selectedPlanningInitiativeId) : null)
|
|
2480
|
+
|| null;
|
|
2481
|
+
setPlanningDraftInitiativeId(initiative ? formatInitiativeReference(initiative) : '');
|
|
2482
|
+
}
|
|
2483
|
+
}, [openPlanningEditor, openPlanningSecondaryEditor, planningStructure.initiativeById, selectedPlanningInitiativeId]);
|
|
2484
|
+
const openPlanningEditDialog = useCallback((type, id) => {
|
|
2485
|
+
if (type === 'initiative') {
|
|
2486
|
+
const item = planningStructure.initiativeById.get(id);
|
|
2487
|
+
if (!item)
|
|
2488
|
+
return;
|
|
2489
|
+
setPlanningEditor({ mode: 'edit', entityType: 'initiative', targetId: id });
|
|
2490
|
+
setPlanningDraftTitle(item.title);
|
|
2491
|
+
setPlanningDraftDescription(item.description || '');
|
|
2492
|
+
setPlanningDraftOwner(item.ownerLabel ? (ownerValueByLabel.get(item.ownerLabel) || '') : '');
|
|
2493
|
+
setPlanningDraftInitiativeId('');
|
|
2494
|
+
}
|
|
2495
|
+
else {
|
|
2496
|
+
const item = planningStructure.workstreamById.get(id)
|
|
2497
|
+
|| planningStructure.standaloneWorkstreams.find((workstream) => workstream.id === id);
|
|
2498
|
+
if (!item)
|
|
2499
|
+
return;
|
|
2500
|
+
setPlanningEditor({ mode: 'edit', entityType: 'workstream', targetId: id });
|
|
2501
|
+
setPlanningDraftTitle(item.title);
|
|
2502
|
+
setPlanningDraftDescription(item.description || '');
|
|
2503
|
+
setPlanningDraftOwner(item.ownerLabel ? (ownerValueByLabel.get(item.ownerLabel) || '') : '');
|
|
2504
|
+
const initiative = item.initiativeId ? planningStructure.initiativeById.get(item.initiativeId) || null : null;
|
|
2505
|
+
setPlanningDraftInitiativeId(initiative ? formatInitiativeReference(initiative) : '');
|
|
2506
|
+
}
|
|
2507
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2508
|
+
}, [ownerValueByLabel, planningStructure.initiativeById, planningStructure.standaloneWorkstreams, planningStructure.workstreamById]);
|
|
2509
|
+
const handleAssignInitiativeToWorkstream = useCallback(async (workstreamId, initiativeReferenceOverride) => {
|
|
2510
|
+
const workstream = props.workstreams.find((item) => item.id === workstreamId);
|
|
2511
|
+
if (!workstream) {
|
|
2512
|
+
pushNotice('Workstream not found.', 'error');
|
|
2513
|
+
return;
|
|
2514
|
+
}
|
|
2515
|
+
const nextInitiativeRaw = typeof initiativeReferenceOverride === 'string'
|
|
2516
|
+
? initiativeReferenceOverride.trim()
|
|
2517
|
+
: initiativeReferenceOverride === null
|
|
2518
|
+
? ''
|
|
2519
|
+
: planningDraftInitiativeId.trim();
|
|
2520
|
+
const resolvedInitiative = nextInitiativeRaw.length === 0
|
|
2521
|
+
? null
|
|
2522
|
+
: resolveInitiativeReference(planningStructure.initiatives, nextInitiativeRaw);
|
|
2523
|
+
if (nextInitiativeRaw.length > 0 && !resolvedInitiative) {
|
|
2524
|
+
pushNotice('Initiative not found by that reference.', 'error');
|
|
2525
|
+
return;
|
|
2526
|
+
}
|
|
2527
|
+
const normalizedParentId = resolvedInitiative?.id || null;
|
|
2528
|
+
if ((workstream.initiativeId || null) === normalizedParentId) {
|
|
2529
|
+
pushNotice(normalizedParentId ? 'Workstream already belongs to that initiative.' : 'Workstream is already standalone.', 'info');
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
try {
|
|
2533
|
+
await props.updateWorkstream(workstreamId, { initiativeId: normalizedParentId });
|
|
2534
|
+
setPlanningDraftInitiativeId(resolvedInitiative ? formatInitiativeReference(resolvedInitiative) : '');
|
|
2535
|
+
pushNotice(normalizedParentId ? 'Initiative set' : 'Initiative removed', 'success');
|
|
2536
|
+
}
|
|
2537
|
+
catch {
|
|
2538
|
+
pushNotice('Failed to set initiative.', 'error');
|
|
2539
|
+
}
|
|
2540
|
+
}, [planningDraftInitiativeId, planningStructure.initiatives, props, pushNotice]);
|
|
2541
|
+
const handleAssignTaskToWorkstream = useCallback(async (taskId, workstreamId) => {
|
|
2542
|
+
const task = hierarchyTaskById.get(taskId);
|
|
2543
|
+
if (!task) {
|
|
2544
|
+
pushNotice('Task not found.', 'error');
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
const normalizedWorkstreamId = workstreamId || null;
|
|
2548
|
+
if (planningStructure.initiativeById.has(taskId) || planningStructure.workstreamById.has(taskId)) {
|
|
2549
|
+
pushNotice('Only execution tasks can be moved into workstreams.', 'error');
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2552
|
+
if (normalizedWorkstreamId && !planningStructure.workstreamById.has(normalizedWorkstreamId)) {
|
|
2553
|
+
pushNotice('Workstream not found.', 'error');
|
|
2554
|
+
return;
|
|
2555
|
+
}
|
|
2556
|
+
if ((task.workstreamId || null) === normalizedWorkstreamId) {
|
|
2557
|
+
pushNotice(normalizedWorkstreamId ? 'Task already belongs to that workstream.' : 'Task is already standalone.', 'info');
|
|
2558
|
+
return;
|
|
2559
|
+
}
|
|
2560
|
+
try {
|
|
2561
|
+
const res = await fetch(`/api/taskforce/task/${taskId}`, {
|
|
2562
|
+
method: 'PATCH',
|
|
2563
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2564
|
+
body: JSON.stringify({ workstreamId: normalizedWorkstreamId })
|
|
2565
|
+
});
|
|
2566
|
+
if (!res.ok) {
|
|
2567
|
+
const data = await res.json().catch(() => ({}));
|
|
2568
|
+
pushNotice(data?.error || 'Failed to set workstream.', 'error');
|
|
2569
|
+
return;
|
|
2570
|
+
}
|
|
2571
|
+
await res.json().catch(() => null);
|
|
2572
|
+
await fetchTasks(true);
|
|
2573
|
+
pushNotice(normalizedWorkstreamId ? 'Workstream set' : 'Workstream removed', 'success');
|
|
2574
|
+
}
|
|
2575
|
+
catch {
|
|
2576
|
+
pushNotice('Failed to set workstream.', 'error');
|
|
2577
|
+
}
|
|
2578
|
+
}, [fetchTasks, hierarchyTaskById, planningStructure.initiativeById, planningStructure.workstreamById, pushNotice]);
|
|
2579
|
+
const handleAttachTaskToWorkstreamByReference = useCallback(async (workstreamId, taskReference) => {
|
|
2580
|
+
const needle = taskReference.trim();
|
|
2581
|
+
if (!needle) {
|
|
2582
|
+
pushNotice('Enter a task reference.', 'error');
|
|
2583
|
+
return;
|
|
2584
|
+
}
|
|
2585
|
+
const parsedReference = parseTaskReference(needle);
|
|
2586
|
+
const matchedTask = hierarchyTasks.find((task) => (task.id === needle
|
|
2587
|
+
|| getTaskReferenceLabel(task) === needle
|
|
2588
|
+
|| (parsedReference !== null && task.referenceNumber === parsedReference)));
|
|
2589
|
+
if (!matchedTask) {
|
|
2590
|
+
pushNotice('Task not found by that reference.', 'error');
|
|
2591
|
+
return;
|
|
2592
|
+
}
|
|
2593
|
+
await handleAssignTaskToWorkstream(matchedTask.id, workstreamId);
|
|
2594
|
+
}, [handleAssignTaskToWorkstream, hierarchyTasks, pushNotice]);
|
|
2595
|
+
const handleAttachWorkstreamToInitiativeByReference = useCallback(async (initiativeId, workstreamReference) => {
|
|
2596
|
+
const needle = workstreamReference.trim();
|
|
2597
|
+
if (!needle) {
|
|
2598
|
+
pushNotice('Enter a workstream reference.', 'error');
|
|
2599
|
+
return;
|
|
2600
|
+
}
|
|
2601
|
+
const initiative = planningStructure.initiativeById.get(initiativeId);
|
|
2602
|
+
if (!initiative) {
|
|
2603
|
+
pushNotice('Initiative not found.', 'error');
|
|
2604
|
+
return;
|
|
2605
|
+
}
|
|
2606
|
+
const parsedReference = parseWorkstreamReference(needle);
|
|
2607
|
+
const matchedWorkstream = props.workstreams.find((workstream) => (workstream.id === needle
|
|
2608
|
+
|| formatWorkstreamReference(workstream) === needle
|
|
2609
|
+
|| (parsedReference !== null && workstream.referenceNumber === parsedReference)));
|
|
2610
|
+
if (!matchedWorkstream) {
|
|
2611
|
+
pushNotice('Workstream not found by that reference.', 'error');
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2614
|
+
await handleAssignInitiativeToWorkstream(matchedWorkstream.id, formatInitiativeReference(initiative));
|
|
2615
|
+
}, [handleAssignInitiativeToWorkstream, planningStructure.initiativeById, props.workstreams, pushNotice]);
|
|
2616
|
+
const handleInitiativeFilterChange = useCallback((initiativeId) => {
|
|
2617
|
+
setSelectedPlanningInitiativeId(initiativeId);
|
|
2618
|
+
setSelectedPlanningWorkstreamId('');
|
|
2619
|
+
}, []);
|
|
2620
|
+
const handleWorkstreamFilterChange = useCallback((workstreamId) => {
|
|
2621
|
+
setSelectedPlanningWorkstreamId(workstreamId);
|
|
2622
|
+
setSelectedPlanningInitiativeId(workstreamId ? planningStructure.workstreamInitiativeIds.get(workstreamId) || '' : '');
|
|
2623
|
+
}, [planningStructure.workstreamInitiativeIds]);
|
|
2624
|
+
const handleModuleChange = useCallback((nextModuleId) => {
|
|
2625
|
+
const nextModule = resolveWorkspaceModule(nextModuleId, workspaceModules);
|
|
2626
|
+
if (nextModule !== nextModuleId) {
|
|
2627
|
+
const unavailableLabel = workspaceModules.find((module) => module.id === nextModuleId)?.label || 'That module';
|
|
2628
|
+
pushNotice(`${unavailableLabel} is not available in this build yet.`, 'error');
|
|
2629
|
+
return;
|
|
2630
|
+
}
|
|
2631
|
+
setActiveWorkspaceModule(nextModule);
|
|
2632
|
+
}, [
|
|
2633
|
+
pushNotice,
|
|
2634
|
+
setActiveWorkspaceModule,
|
|
2635
|
+
workspaceModules,
|
|
2636
|
+
]);
|
|
2637
|
+
const handlePlanningToolToggle = useCallback(() => {
|
|
2638
|
+
if (resolvedWorkspaceModule === 'tasks' && planningDrawerOpen) {
|
|
2639
|
+
setPlanningDrawerOpen(false);
|
|
2640
|
+
setPlanningDrawerDetail(null);
|
|
2641
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2642
|
+
setPlanningEditor(null);
|
|
2643
|
+
return;
|
|
2644
|
+
}
|
|
2645
|
+
if (resolvedWorkspaceModule !== 'tasks') {
|
|
2646
|
+
setActiveWorkspaceModule('tasks');
|
|
2647
|
+
setPlanningDrawerOpen(true);
|
|
2648
|
+
return;
|
|
2649
|
+
}
|
|
2650
|
+
setPlanningDrawerOpen((current) => !current);
|
|
2651
|
+
}, [planningDrawerOpen, resolvedWorkspaceModule, setActiveWorkspaceModule]);
|
|
2652
|
+
const handleBackToTaskFromAnnotatedAttachment = useCallback((taskId) => {
|
|
2653
|
+
setActiveWorkspaceModule('tasks');
|
|
2654
|
+
handleOpenTaskById(taskId);
|
|
2655
|
+
}, [handleOpenTaskById, setActiveWorkspaceModule]);
|
|
2656
|
+
const handleAnnotatedAttachmentOpenTarget = useCallback((target, options) => {
|
|
2657
|
+
setRequestedAnnotatedTarget(target);
|
|
2658
|
+
setRequestedAnnotatedSessionId(options?.sessionId ?? null);
|
|
2659
|
+
setRequestedAnnotatedOpenVersion((current) => current + 1);
|
|
2660
|
+
setActiveWorkspaceModule('annotate');
|
|
2661
|
+
}, [setActiveWorkspaceModule]);
|
|
2662
|
+
const handleAnnotatedAttachmentContextChange = useCallback(({ target, sessionId }) => {
|
|
2663
|
+
setRequestedAnnotatedTarget((current) => {
|
|
2664
|
+
if (!current && !target)
|
|
2665
|
+
return current;
|
|
2666
|
+
if (current
|
|
2667
|
+
&& target
|
|
2668
|
+
&& current.taskId === target.taskId
|
|
2669
|
+
&& current.taskReferenceLabel === target.taskReferenceLabel
|
|
2670
|
+
&& current.assetId === target.assetId
|
|
2671
|
+
&& current.imageReferenceLabel === target.imageReferenceLabel
|
|
2672
|
+
&& current.path === target.path
|
|
2673
|
+
&& current.displayName === target.displayName) {
|
|
2674
|
+
return current;
|
|
2675
|
+
}
|
|
2676
|
+
return target;
|
|
2677
|
+
});
|
|
2678
|
+
setRequestedAnnotatedSessionId((current) => (current === sessionId ? current : sessionId));
|
|
2679
|
+
}, []);
|
|
2680
|
+
const sortOptions = useMemo(() => [
|
|
2681
|
+
{ value: 'created', label: t('standalone.sortCreated') },
|
|
2682
|
+
{ value: 'updated', label: t('standalone.sortUpdated') },
|
|
2683
|
+
{ value: 'priority', label: t('standalone.sortPriority') },
|
|
2684
|
+
{ value: 'complexity', label: t('standalone.sortComplexity') },
|
|
2685
|
+
...buildCustomTaxonomySortOptions(taxonomies, [...props.searchAgnosticTasks, ...archivedScopeAllTasks])
|
|
2686
|
+
], [archivedScopeAllTasks, props.searchAgnosticTasks, taxonomies]);
|
|
2687
|
+
const groupingOptions = useMemo(() => [
|
|
2688
|
+
{ value: 'category', label: t('standalone.groupCategory') },
|
|
2689
|
+
{ value: 'type', label: t('standalone.groupType') },
|
|
2690
|
+
{ value: 'priority', label: t('standalone.groupPriority') },
|
|
2691
|
+
{ value: 'complexity', label: t('standalone.groupComplexity') },
|
|
2692
|
+
{ value: 'assignee', label: t('standalone.groupAssignee') },
|
|
2693
|
+
{ value: 'status', label: t('standalone.groupStatus') },
|
|
2694
|
+
{ value: 'schedule', label: t('standalone.groupSchedule') },
|
|
2695
|
+
], []);
|
|
2696
|
+
const workspaceToolModules = useMemo(() => workspaceModules.filter((module) => module.enabled), [workspaceModules]);
|
|
2697
|
+
const taskDisplayActions = useMemo(() => {
|
|
2698
|
+
const actions = [
|
|
2699
|
+
{
|
|
2700
|
+
key: 'empty-columns',
|
|
2701
|
+
icon: emptyColumnMode === 'show'
|
|
2702
|
+
? _jsx(Maximize2, { size: 16 })
|
|
2703
|
+
: emptyColumnMode === 'collapse'
|
|
2704
|
+
? _jsx(Minimize2, { size: 16 })
|
|
2705
|
+
: _jsx(EyeOff, { size: 16 }),
|
|
2706
|
+
title: emptyColumnMode === 'show'
|
|
2707
|
+
? t('standalone.showEmptyColumns')
|
|
2708
|
+
: emptyColumnMode === 'collapse'
|
|
2709
|
+
? t('standalone.compressEmptyColumns')
|
|
2710
|
+
: t('standalone.hideEmptyColumns'),
|
|
2711
|
+
onClick: cycleEmptyColumnVisibility,
|
|
2712
|
+
active: emptyColumnMode !== 'show',
|
|
2713
|
+
},
|
|
2714
|
+
{
|
|
2715
|
+
key: 'compressed-cards',
|
|
2716
|
+
icon: compressedCards ? _jsx(ChevronsDown, { size: 16 }) : _jsx(ChevronsUp, { size: 16 }),
|
|
2717
|
+
title: compressedCards ? t('standalone.expandCards') : t('standalone.compressCards'),
|
|
2718
|
+
onClick: () => setCompressedCards(!compressedCards),
|
|
2719
|
+
active: compressedCards,
|
|
2720
|
+
},
|
|
2721
|
+
];
|
|
2722
|
+
return actions;
|
|
2723
|
+
}, [
|
|
2724
|
+
compressedCards,
|
|
2725
|
+
cycleEmptyColumnVisibility,
|
|
2726
|
+
emptyColumnMode,
|
|
2727
|
+
]);
|
|
2728
|
+
const universalFilterActions = useMemo(() => [{
|
|
2729
|
+
key: 'filter-toggle',
|
|
2730
|
+
icon: _jsx(Filter, { size: 16 }),
|
|
2731
|
+
title: t('standalone.toggleFilters'),
|
|
2732
|
+
onClick: () => setShowFilters(!showFilters),
|
|
2733
|
+
active: showFilters,
|
|
2734
|
+
className: hasActiveWorkspaceFilterControls ? styles.filterGlow : '',
|
|
2735
|
+
}], [hasActiveWorkspaceFilterControls, setShowFilters, showFilters]);
|
|
2736
|
+
const workspaceFilterBar = useMemo(() => {
|
|
2737
|
+
switch (workspaceChromeDefinition.filterBar.kind) {
|
|
2738
|
+
case 'task-filters':
|
|
2739
|
+
return (_jsx(FilterToolbar, { categoryFilterOptions: categoryFilterOptions, typeFilterOptions: typeFilterOptions, priorities: priorities, taxonomyDisplayLabels: taxonomyDisplayLabels, filterCategories: filterCategories, setFilterCategories: setFilterCategories, filterTypes: filterTypes, setFilterTypes: setFilterTypes, filterPriorities: filterPriorities, setFilterPriorities: setFilterPriorities, filterStatus: filterStatus, setFilterStatus: setFilterStatus, filterAssignees: filterAssignees, setFilterAssignees: setFilterAssignees, assigneeOptions: assigneeOptions, initiativeFilterOptions: initiativeFilterOptions, selectedInitiativeId: selectedPlanningInitiativeId, setSelectedInitiativeId: handleInitiativeFilterChange, workstreamFilterOptions: workstreamFilterOptions, selectedWorkstreamId: selectedPlanningWorkstreamId, setSelectedWorkstreamId: handleWorkstreamFilterChange, taskScope: taskScope, setTaskScope: setTaskScope, showArchive: showArchive, setShowArchive: setShowArchive, fetchArchive: fetchArchive, onDeleteAllDeleted: () => {
|
|
2740
|
+
const deletedCount = deletedScopeAllTasks.length;
|
|
2741
|
+
if (deletedCount === 0)
|
|
2742
|
+
return;
|
|
2743
|
+
const confirmed = window.confirm(`Permanently delete ${deletedCount} deleted task${deletedCount === 1 ? '' : 's'}? This cannot be undone.`);
|
|
2744
|
+
if (!confirmed)
|
|
2745
|
+
return;
|
|
2746
|
+
void handleEmptyDeletedTasks();
|
|
2747
|
+
}, clearFilters: () => {
|
|
2748
|
+
clearFilters();
|
|
2749
|
+
setSelectedPlanningInitiativeId('');
|
|
2750
|
+
setSelectedPlanningWorkstreamId('');
|
|
2751
|
+
} }));
|
|
2752
|
+
case 'document-filters':
|
|
2753
|
+
return (_jsx(DocumentFilterToolbar, { typeFilters: documentTypeFilters, setTypeFilters: setDocumentTypeFilters, attachmentFilters: documentAttachmentFilters, setAttachmentFilters: setDocumentAttachmentFilters, clearFilters: () => {
|
|
2754
|
+
setDocumentTypeFilters(DOCUMENT_TYPE_FILTER_OPTIONS.map((option) => option.value));
|
|
2755
|
+
setDocumentAttachmentFilters(DOCUMENT_ATTACHMENT_FILTER_OPTIONS.map((option) => option.value));
|
|
2756
|
+
} }));
|
|
2757
|
+
case 'empty':
|
|
1680
2758
|
default:
|
|
1681
|
-
return
|
|
2759
|
+
return _jsx(EmptyWorkspaceFilterBar, {});
|
|
1682
2760
|
}
|
|
1683
|
-
}, [
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
2761
|
+
}, [
|
|
2762
|
+
assigneeOptions,
|
|
2763
|
+
categoryFilterOptions,
|
|
2764
|
+
clearFilters,
|
|
2765
|
+
deletedScopeAllTasks.length,
|
|
2766
|
+
documentAttachmentFilters,
|
|
2767
|
+
documentTypeFilters,
|
|
2768
|
+
filterAssignees,
|
|
2769
|
+
filterCategories,
|
|
2770
|
+
filterPriorities,
|
|
2771
|
+
filterStatus,
|
|
2772
|
+
filterTypes,
|
|
2773
|
+
handleInitiativeFilterChange,
|
|
2774
|
+
handleWorkstreamFilterChange,
|
|
2775
|
+
initiativeFilterOptions,
|
|
2776
|
+
priorities,
|
|
2777
|
+
selectedPlanningInitiativeId,
|
|
2778
|
+
selectedPlanningWorkstreamId,
|
|
2779
|
+
setDocumentAttachmentFilters,
|
|
2780
|
+
setDocumentTypeFilters,
|
|
2781
|
+
setFilterAssignees,
|
|
2782
|
+
setFilterCategories,
|
|
2783
|
+
setFilterPriorities,
|
|
2784
|
+
setFilterStatus,
|
|
2785
|
+
setFilterTypes,
|
|
2786
|
+
setSelectedPlanningInitiativeId,
|
|
2787
|
+
setSelectedPlanningWorkstreamId,
|
|
2788
|
+
setShowArchive,
|
|
2789
|
+
setTaskScope,
|
|
2790
|
+
showArchive,
|
|
2791
|
+
taskScope,
|
|
2792
|
+
taxonomyDisplayLabels,
|
|
2793
|
+
typeFilterOptions,
|
|
2794
|
+
workstreamFilterOptions,
|
|
2795
|
+
workspaceChromeDefinition.filterBar.kind,
|
|
2796
|
+
fetchArchive,
|
|
2797
|
+
handleEmptyDeletedTasks,
|
|
2798
|
+
]);
|
|
2799
|
+
const universalDisplayActions = useMemo(() => {
|
|
2800
|
+
const actions = [{
|
|
2801
|
+
key: 'zen-toggle',
|
|
2802
|
+
icon: _jsx(Zap, { size: 16, fill: zenMode ? 'currentColor' : 'none' }),
|
|
2803
|
+
title: zenMode ? t('standalone.exitZenMode') : t('standalone.enterZenMode'),
|
|
2804
|
+
onClick: () => setZenMode(),
|
|
2805
|
+
active: zenMode,
|
|
2806
|
+
}];
|
|
2807
|
+
return actions;
|
|
2808
|
+
}, [
|
|
2809
|
+
zenMode,
|
|
2810
|
+
setZenMode,
|
|
2811
|
+
]);
|
|
2812
|
+
const headerSectionRegistry = useMemo(() => ({
|
|
2813
|
+
search: (_jsx(WorkspaceHeaderSearch, { value: searchQuery, placeholder: t('standalone.searchPlaceholder'), active: hasSearchFilter, onChange: setSearchQuery, onClear: () => setSearchQuery(''), clearTitle: t('standalone.clearSearchTitle') })),
|
|
2814
|
+
sort: (_jsx(WorkspaceHeaderSelect, { label: _jsxs(_Fragment, { children: [_jsx(ArrowUpDown, { size: 12 }), " ", t('standalone.sortLabel')] }), value: sortBy, options: sortOptions, onChange: (value) => setSortBy(value), trailingAction: (_jsx("button", { className: `tf-control-icon ${styles.sortDirectionBtn}`, onClick: () => props.toggleSortOrder(), title: props.sortOrder === 'desc' ? t('standalone.sortDirectionDescTitle') : t('standalone.sortDirectionAscTitle'), children: props.sortOrder === 'desc' ? _jsx(ArrowDown, { size: 14 }) : _jsx(ArrowUp, { size: 14 }) })) })),
|
|
2815
|
+
'divider-primary': _jsx(WorkspaceHeaderDivider, {}),
|
|
2816
|
+
grouping: (_jsx(WorkspaceHeaderSelect, { label: _jsxs(_Fragment, { children: [_jsx(Layers, { size: 12 }), " ", t('standalone.groupLabel')] }), value: groupBy, options: groupingOptions, onChange: (value) => setGroupBy(value) })),
|
|
2817
|
+
'divider-secondary': _jsx(WorkspaceHeaderDivider, {}),
|
|
2818
|
+
'task-display-actions': (_jsx(WorkspaceHeaderActions, { actions: taskDisplayActions })),
|
|
2819
|
+
}), [
|
|
2820
|
+
groupBy,
|
|
2821
|
+
groupingOptions,
|
|
2822
|
+
handleModuleChange,
|
|
2823
|
+
hasSearchFilter,
|
|
2824
|
+
props,
|
|
2825
|
+
searchQuery,
|
|
2826
|
+
setGroupBy,
|
|
2827
|
+
setSearchQuery,
|
|
2828
|
+
setSortBy,
|
|
2829
|
+
sortBy,
|
|
2830
|
+
sortOptions,
|
|
2831
|
+
taskDisplayActions,
|
|
2832
|
+
workspaceChromeDefinition.moduleId,
|
|
2833
|
+
]);
|
|
2834
|
+
const primaryActionRegistry = useMemo(() => ({
|
|
2835
|
+
'add-task': (_jsx(WorkspacePrimaryAction, { icon: _jsx(Plus, { size: 16 }), label: t('standalone.addTask'), onClick: openAddTaskModal })),
|
|
2836
|
+
'add-document': null,
|
|
2837
|
+
}), [openAddTaskModal]);
|
|
2838
|
+
const workspaceToolRail = useMemo(() => {
|
|
2839
|
+
const moduleIcons = {
|
|
2840
|
+
tasks: _jsx(ClipboardCheck, { size: 18 }),
|
|
2841
|
+
docs: _jsx(FileText, { size: 18 }),
|
|
2842
|
+
annotate: _jsx(Image, { size: 18 }),
|
|
2843
|
+
workflows: _jsx(Workflow, { size: 18 }),
|
|
2844
|
+
agents: _jsx(Bot, { size: 18 }),
|
|
2845
|
+
};
|
|
2846
|
+
return (_jsxs("aside", { className: shellStyles.workspaceToolRail, "aria-label": "Workspace tools", children: [_jsxs("div", { className: shellStyles.workspaceToolRailMain, children: [_jsx("button", { type: "button", className: `${shellStyles.workspaceToolButton} ${(resolvedWorkspaceModule === 'tasks' && planningDrawerOpen) ? shellStyles.workspaceToolButtonActive : ''}`.trim(), onClick: handlePlanningToolToggle, title: resolvedWorkspaceModule === 'tasks' && planningDrawerOpen ? 'Collapse planning trays' : 'Planning', "aria-label": resolvedWorkspaceModule === 'tasks' && planningDrawerOpen ? 'Collapse planning trays' : 'Planning', children: resolvedWorkspaceModule === 'tasks' && planningDrawerOpen ? _jsx(ChevronLeft, { size: 18 }) : _jsx(NotebookTabs, { size: 18 }) }), _jsx("div", { className: shellStyles.workspaceToolRailDivider }), workspaceToolModules.map((module) => (_jsx("button", { type: "button", className: `${shellStyles.workspaceToolButton} ${resolvedWorkspaceModule === module.id ? shellStyles.workspaceToolButtonActive : ''}`.trim(), onClick: () => handleModuleChange(module.id), title: module.label, "aria-label": module.label, children: moduleIcons[module.id] }, module.id)))] }), _jsxs("div", { className: shellStyles.workspaceToolRailBottom, children: [_jsx("div", { className: shellStyles.workspaceToolRailDivider }), _jsx("button", { type: "button", className: `${shellStyles.workspaceToolButton} ${activeTab === 'settings' ? shellStyles.workspaceToolButtonActive : ''}`.trim(), onClick: () => {
|
|
2847
|
+
setSettingsSection('general');
|
|
2848
|
+
setActiveTab('settings');
|
|
2849
|
+
}, title: "Workspace Settings", "aria-label": "Workspace Settings", children: _jsx(Settings, { size: 18 }) })] })] }));
|
|
2850
|
+
}, [activeTab, handleModuleChange, handlePlanningToolToggle, planningDrawerOpen, resolvedWorkspaceModule, setSettingsSection, setActiveTab, workspaceToolModules]);
|
|
2851
|
+
const planningDrawerPortalRef = useRef(null);
|
|
2852
|
+
const planningDrawerNode = (_jsx(PlanningDrawer, { open: planningDrawerOpen, leftOffset: WORKSPACE_TOOL_RAIL_WIDTH, initiatives: planningStructure.initiatives, standaloneWorkstreams: planningStructure.standaloneWorkstreams, activeInitiativeId: selectedPlanningInitiativeId, activeWorkstreamId: selectedPlanningWorkstreamId, expandedInitiativeIds: expandedPlanningInitiativeIds, detail: planningDrawerItem, editor: planningEditor, secondaryPane: planningSecondaryPane, assigneeOptions: assigneeOptions.map((option) => ({ value: String(option.value), label: option.label })), draftInitiativeSummary: planningDraftInitiativeSummary, draftTitle: planningDraftTitle, draftDescription: planningDraftDescription, draftOwner: planningDraftOwner, draftInitiativeId: planningDraftInitiativeId, onChangeDraftTitle: setPlanningDraftTitle, onChangeDraftDescription: setPlanningDraftDescription, onChangeDraftOwner: setPlanningDraftOwner, onChangeDraftInitiativeId: setPlanningDraftInitiativeId, onCollapseTreePane: () => setPlanningTreeCollapsed(true), onExpandTreePane: () => setPlanningTreeCollapsed(false), onCollapsePrimaryPane: () => setPlanningPrimaryCollapsed(true), onExpandPrimaryPane: () => setPlanningPrimaryCollapsed(false), onCollapseSecondaryPane: () => setPlanningSecondaryCollapsed(true), onExpandSecondaryPane: () => setPlanningSecondaryCollapsed(false), onBackFromSecondary: () => {
|
|
2853
|
+
setPlanningSecondaryEditor(null);
|
|
2854
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2855
|
+
}, treeCollapsed: planningTreeCollapsed, primaryCollapsed: planningPrimaryCollapsed, secondaryCollapsed: planningSecondaryCollapsed, onCancelEditor: closePlanningEditor, onSubmitEditor: submitPlanningEditor, onCreateInitiative: () => openPlanningCreateEditor('initiative'), onCreateWorkstream: () => openPlanningCreateEditor('workstream'), onCreateTaskInWorkstream: openAddTaskModalForWorkstream, onCreateWorkstreamInInitiative: (initiativeId) => openPlanningCreateEditor('workstream', initiativeId), onAssignInitiativeToWorkstream: handleAssignInitiativeToWorkstream, onOpenTaskById: handleOpenTaskById, onAttachTaskToWorkstreamByReference: handleAttachTaskToWorkstreamByReference, onAttachWorkstreamToInitiativeByReference: handleAttachWorkstreamToInitiativeByReference, onToggleInitiative: togglePlanningInitiative, onSelectInitiative: selectPlanningInitiative, onSelectWorkstream: selectPlanningWorkstream, onOpenInitiativeDetails: openInitiativeDetails, onOpenWorkstreamDetails: openWorkstreamDetails, onOpenNestedWorkstreamDetails: openNestedWorkstreamDetails, onEditInitiative: (initiativeId) => openPlanningEditDialog('initiative', initiativeId), onEditWorkstream: (workstreamId) => openPlanningEditDialog('workstream', workstreamId), onArchiveInitiative: (initiativeId) => {
|
|
2856
|
+
void props.archiveInitiative(initiativeId).then(() => {
|
|
2857
|
+
pushNotice('Initiative archived', 'success');
|
|
2858
|
+
}).catch((error) => {
|
|
2859
|
+
pushNotice(error instanceof Error ? error.message : 'Failed to archive initiative.', 'error');
|
|
2860
|
+
});
|
|
2861
|
+
}, onUnarchiveInitiative: (initiativeId) => {
|
|
2862
|
+
void props.unarchiveInitiative(initiativeId).then(() => {
|
|
2863
|
+
pushNotice('Initiative unarchived', 'success');
|
|
2864
|
+
}).catch((error) => {
|
|
2865
|
+
pushNotice(error instanceof Error ? error.message : 'Failed to unarchive initiative.', 'error');
|
|
2866
|
+
});
|
|
2867
|
+
}, onArchiveWorkstream: (workstreamId) => {
|
|
2868
|
+
void props.archiveWorkstream(workstreamId).then(() => {
|
|
2869
|
+
pushNotice('Workstream archived', 'success');
|
|
2870
|
+
}).catch((error) => {
|
|
2871
|
+
pushNotice(error instanceof Error ? error.message : 'Failed to archive workstream.', 'error');
|
|
2872
|
+
});
|
|
2873
|
+
}, onUnarchiveWorkstream: (workstreamId) => {
|
|
2874
|
+
void props.unarchiveWorkstream(workstreamId).then(() => {
|
|
2875
|
+
pushNotice('Workstream unarchived', 'success');
|
|
2876
|
+
}).catch((error) => {
|
|
2877
|
+
pushNotice(error instanceof Error ? error.message : 'Failed to unarchive workstream.', 'error');
|
|
2878
|
+
});
|
|
2879
|
+
} }));
|
|
2880
|
+
const effectiveHeaderSectionIds = workspaceChromeDefinition.headerSections;
|
|
2881
|
+
const workspaceHeaderSections = effectiveHeaderSectionIds
|
|
2882
|
+
.map((sectionId) => {
|
|
2883
|
+
const section = headerSectionRegistry[sectionId];
|
|
2884
|
+
return section ? _jsx(React.Fragment, { children: section }, sectionId) : null;
|
|
2885
|
+
})
|
|
2886
|
+
.filter(Boolean);
|
|
2887
|
+
const workspacePrimaryAction = workspaceChromeDefinition.primaryAction
|
|
2888
|
+
? primaryActionRegistry[workspaceChromeDefinition.primaryAction]
|
|
2889
|
+
: null;
|
|
2890
|
+
return (_jsxs("div", { className: `${shellStyles.standaloneWrapper} ${zenMode ? styles.zenModeEnabled : ''}`, "data-theme": currentTheme, children: [_jsx(AppShellHeader, { projectName: projectName, currentWorkspaceId: currentWorkspaceId, runtimeMode: runtimeMode, theme: currentTheme, meta: isPlansScreen ? null : (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.taskCountBadge, title: activeCountTitle, children: activeCountLabel }), shouldShowSyncStatus && (_jsx("button", { className: "tf-control-icon", onClick: () => setShowSyncStatusModal(true), title: `Sync manager: ${syncStatusMeta.label} | Last success: ${formattedLastSyncTime}`, style: {
|
|
2891
|
+
marginLeft: '6px',
|
|
2892
|
+
height: '24px',
|
|
2893
|
+
width: '24px',
|
|
2894
|
+
padding: 0,
|
|
2895
|
+
borderRadius: '999px',
|
|
2896
|
+
border: `1px solid ${syncStatusMeta.border}`,
|
|
2897
|
+
background: syncStatusMeta.background,
|
|
2898
|
+
color: syncStatusMeta.color,
|
|
2899
|
+
display: 'inline-flex',
|
|
2900
|
+
alignItems: 'center',
|
|
2901
|
+
justifyContent: 'center'
|
|
2902
|
+
}, children: syncStatusMeta.icon === 'off' ? (_jsx(CloudOff, { size: 16 })) : (_jsx(Cloud, { size: 16 })) }))] })), actions: (_jsxs(_Fragment, { children: [isPlansScreen && (_jsx("button", { className: "tf-control-icon", onClick: handlePlansBack, title: "Back", children: "Back" })), !isPlansScreen && (_jsxs(_Fragment, { children: [workspaceHeaderSections, workspacePrimaryAction] })), !isPlansScreen && _jsx(WorkspaceHeaderActions, { actions: universalDisplayActions }), !isPlansScreen && _jsx(WorkspaceHeaderActions, { actions: universalFilterActions }), accountMenuControl] })) }), _jsx(TopNoticeLayer, { notice: uiNotice, onDismiss: clearNotice }), authBlocked && (_jsx("div", { className: authStyles.authBlockedBanner, children: "Authentication required for this environment. Use the Account menu to sign in." })), !isPlansScreen && showFilters && workspaceFilterBar, isPlansScreen ? (_jsx("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} ${styles.appScrollbar} tf-scrollbar`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflowY: 'auto', overflowX: 'hidden', scrollbarGutter: 'stable' }, children: _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(PlansPage, { ...props, currentTheme: currentTheme, projectName: projectName, currentWorkspaceId: currentWorkspaceId, apiBaseUrl: billingApiBaseUrl, connectedEnvironmentSource: props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '', embedded: true, shellOwnsScroll: true }) }) })) : resolvedWorkspaceModule === 'docs' && documentsWorkspaceEnabled ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(DocumentWorkspaceShell, { tasks: tasks, runtimeMode: runtimeMode, apiBaseUrl: props.config?.apiBaseUrl || '', cloudAuthBaseUrl: props.config?.cloudAuthBaseUrl || '', typeFilters: documentTypeFilters, attachmentFilters: documentAttachmentFilters, onTypeFiltersChange: setDocumentTypeFilters, onAttachmentFiltersChange: setDocumentAttachmentFilters, requestedDocPath: requestedDocPath, requestedDocAssetId: requestedDocAssetId, onRequestedDocHandled: () => {
|
|
2903
|
+
setRequestedDocPath(null);
|
|
2904
|
+
setRequestedDocAssetId(null);
|
|
2905
|
+
}, enableTaskGeneration: true }) })] })) : resolvedWorkspaceModule === 'annotate' && annotatedAttachmentsWorkspaceEnabled ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(AnnotatedAttachmentWorkspaceShell, { runtimeMode: runtimeMode, apiBaseUrl: props.config?.apiBaseUrl || '', cloudAuthBaseUrl: props.config?.cloudAuthBaseUrl || '', workspaceId: currentWorkspaceId, sessionLoadReady: annotatedWorkspaceReady, requestedTarget: requestedAnnotatedTarget, requestedSessionId: requestedAnnotatedSessionId, requestedOpenVersion: requestedAnnotatedOpenVersion, resolveTaskReferenceLabel: resolveAnnotatedTaskReferenceLabel, resolveImageReferenceLabel: resolveAnnotatedImageReferenceLabel, onOpenTarget: handleAnnotatedAttachmentOpenTarget, onContextChange: handleAnnotatedAttachmentContextChange, onBackToTask: handleBackToTaskFromAnnotatedAttachment }) })] })) : resolvedWorkspaceModule === 'workflows' ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(WorkflowsModule, { availableWorkflows: availableWorkflows, availableEnvironments: availableEnvironments, exportEnvironment: exportEnvironment, exportWorkflowsPath: props.exportWorkflowsPath, exportingResource: exportingResource, exportResult: exportResult, onExportEnvironmentChange: props.setExportEnvironment, onExportWorkflows: onExportWorkflows, onRefreshWorkflows: fetchWorkflows, onFetchWorkflowTemplate: props.fetchWorkflowTemplate, onFetchWorkflowOverrideNames: props.fetchWorkflowOverrideNames, onSaveWorkflowTemplateDraft: props.saveWorkflowTemplateDraft, onResetWorkflowTemplateDraft: props.resetWorkflowTemplateDraft }) })] })) : resolvedWorkspaceModule === 'agents' ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(AgentsModule, { workspaceId: currentWorkspaceId }) })] })) : (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} `, style: {
|
|
1769
2906
|
position: 'relative',
|
|
1770
2907
|
opacity: props.loadingTasks ? 0.7 : 1,
|
|
1771
|
-
transition: 'opacity 0.2s ease, padding
|
|
2908
|
+
transition: 'opacity 0.2s ease, padding 220ms cubic-bezier(0.4, 0, 0.2, 1)',
|
|
1772
2909
|
display: 'flex',
|
|
1773
2910
|
flex: 1,
|
|
1774
2911
|
minHeight: 0,
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
backgroundSize: '60%',
|
|
1786
|
-
opacity: 0.2,
|
|
1787
|
-
pointerEvents: 'none',
|
|
1788
|
-
zIndex: 0
|
|
1789
|
-
} }), boardVisibleTasks.length === 0 && groupBy !== 'schedule' ? (_jsxs("div", { className: styles.emptyState, children: [_jsx(Layers, { size: 48 }), _jsx("p", { children: groupBy === 'hierarchy' && hierarchyRootTaskId ? 'No tasks match this subtree with current filters.' : t('standalone.noTasksMatchFilters') }), _jsx("p", { style: { fontSize: '12px', color: 'var(--text-secondary)' }, children: groupBy === 'hierarchy' && hierarchyRootTaskId
|
|
1790
|
-
? `Focused on ${selectedHierarchyRootTask?.title || hierarchyRootTaskId}. Try clearing subtree focus or broadening filters.`
|
|
1791
|
-
: t('standalone.showingWithLinksHint', { parentFilterLabel }) })] })) : (_jsx(TaskKanban, { tasks: boardVisibleTasks, allTasks: kanbanAllTasks, hierarchyTasks: hierarchyTasks, columns: kanbanColumns, groupBy: groupBy, scheduleDates: scheduleDates, searchQuery: searchQuery, filterCategories: filterCategories, filterTypes: filterTypes, filterPriorities: filterPriorities, filterStatus: filterStatus, filterAssignees: filterAssignees, assigneeFilterEnabled: assigneeFilterEnabled, scheduleFilteredTaskIds: Array.from(scheduleFilteredTaskIds), copiedId: copiedId, availableSpecialists: availableSpecialists, taxonomies: taxonomies, priorities: priorities, approaches: approaches, onUpdateTask: handleUpdateTask, onTaskClick: (task) => handleEdit(task), onOpenTaskById: handleOpenTaskById, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onArchiveTask: handleArchiveTask, onUnarchive: handleUnarchive, onDelete: handleDelete, onUnlinkChildTask: handleUnlinkChildTask, onClearParentTask: handleClearParentTask, onAddTaskToColumn: handleAddTaskToColumn, onScheduleParentDrop: handleScheduleParentDrop, onScheduleDaySelected: (dateOnly) => {
|
|
2912
|
+
paddingLeft: planningDrawerOpen
|
|
2913
|
+
? `${WORKSPACE_TOOL_RAIL_WIDTH + planningDrawerWidthPx + WORKSPACE_CONTENT_GUTTER}px`
|
|
2914
|
+
: `${WORKSPACE_TOOL_RAIL_WIDTH + WORKSPACE_CONTENT_GUTTER}px`,
|
|
2915
|
+
paddingRight: (groupBy === 'schedule' && scheduleSidebarOpen) ? `${SCHEDULE_SIDEBAR_WIDTH_PX}px` : 0
|
|
2916
|
+
}, children: [workspaceToolRail, _jsx("div", { ref: planningDrawerPortalRef, style: {
|
|
2917
|
+
position: 'absolute',
|
|
2918
|
+
inset: 0,
|
|
2919
|
+
zIndex: 35,
|
|
2920
|
+
pointerEvents: 'none',
|
|
2921
|
+
} }), _jsxs("div", { style: { position: 'relative', width: '100%', minWidth: 0, flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [props.loadingTasks && boardVisibleTasks.length > 0 && (_jsx("div", { className: styles.boardRefreshIndicator, "aria-live": "polite", "aria-label": "Refreshing tasks", children: _jsx(Loader2, { size: 14, className: styles.spinner }) })), _jsx(TaskKanban, { tasks: scopedBoardVisibleTasks, allTasks: [], columns: kanbanColumns, groupBy: groupBy, scheduleDates: scheduleDates, searchQuery: searchQuery, filterCategories: filterCategories, filterTypes: filterTypes, filterPriorities: filterPriorities, filterStatus: filterStatus, filterAssignees: filterAssignees, assigneeOptions: assigneeOptions, scheduleFilteredTaskIds: Array.from(scheduleFilteredTaskIds), copiedId: copiedId, taxonomies: taxonomies, types: activeTypes, priorities: priorities, onUpdateTask: handleUpdateTask, onTaskClick: (task) => handleEdit(task), onOpenTaskById: handleOpenTaskById, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onSetStatus: handleSetStatus, onArchiveTask: handleArchiveTask, onAddTaskToColumn: handleAddTaskToColumn, showTaskCardStatusLabel: showTaskCardStatusLabel, onScheduleDaySelected: (dateOnly) => {
|
|
1792
2922
|
setScheduleSelectedDate(dateOnly);
|
|
1793
2923
|
const parsed = parseDateOnlyLocal(dateOnly);
|
|
1794
2924
|
if (parsed) {
|
|
@@ -1798,291 +2928,154 @@ export function StandaloneLayout(props) {
|
|
|
1798
2928
|
if (groupBy !== 'schedule')
|
|
1799
2929
|
return;
|
|
1800
2930
|
setScheduleScrollLeft(scrollLeft);
|
|
1801
|
-
}, emptyColumnMode: emptyColumnMode, categories: activeCategories, compressed: compressedCards,
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
2931
|
+
}, emptyColumnMode: emptyColumnMode, categories: activeCategories, compressed: compressedCards, readOnlyMode: taskScope === 'deleted' ? 'deleted' : taskScope === 'archived' ? 'archived' : null, recentlyChangedTaskIds: props.recentlyChangedTaskIds, sortBy: sortBy, sortOrder: props.sortOrder, planningDropTargets: planningDrawerPortalRef.current
|
|
2932
|
+
? createPortal(planningDrawerNode, planningDrawerPortalRef.current)
|
|
2933
|
+
: null, onAssignTaskToWorkstream: handleAssignTaskToWorkstream, onAssignWorkstreamToInitiative: handleAssignInitiativeToWorkstream, workstreams: props.workstreams, initiatives: props.initiatives, onUnarchive: (taskId) => {
|
|
2934
|
+
if (taskScope === 'deleted') {
|
|
2935
|
+
const deletedRecord = deletedTaskRecordByTaskId.get(taskId);
|
|
2936
|
+
if (!deletedRecord)
|
|
2937
|
+
return;
|
|
2938
|
+
void handleRestoreDeletedTask(deletedRecord.id);
|
|
2939
|
+
return;
|
|
2940
|
+
}
|
|
2941
|
+
void handleUnarchive(taskId);
|
|
2942
|
+
}, onDelete: (taskId) => {
|
|
2943
|
+
if (taskScope === 'deleted') {
|
|
2944
|
+
const deletedRecord = deletedTaskRecordByTaskId.get(taskId);
|
|
2945
|
+
if (!deletedRecord)
|
|
2946
|
+
return;
|
|
2947
|
+
const confirmed = window.confirm('Permanently delete this task? This cannot be undone.');
|
|
2948
|
+
if (!confirmed)
|
|
2949
|
+
return;
|
|
2950
|
+
void handlePermanentlyDeleteDeletedTask(deletedRecord.id);
|
|
2951
|
+
return;
|
|
2952
|
+
}
|
|
2953
|
+
void handleDelete(taskId);
|
|
2954
|
+
} }, `${groupBy}-${kanbanColumns.map(c => String(c.value)).join('|')}-${scheduleDates.mon}`), groupBy === 'schedule' && !scheduleSidebarOpen && (_jsx("button", { type: "button", className: "tf-control-icon", onClick: () => {
|
|
2955
|
+
setPlanningDrawerDetail(null);
|
|
2956
|
+
setScheduleSidebarOpen(true);
|
|
2957
|
+
}, title: "Show Schedule Sidebar", style: {
|
|
1809
2958
|
position: 'absolute',
|
|
1810
2959
|
top: '10px',
|
|
1811
2960
|
right: '10px',
|
|
1812
2961
|
zIndex: 20,
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
gap: '12px',
|
|
1828
|
-
zIndex: 40,
|
|
1829
|
-
overflowY: 'auto',
|
|
1830
|
-
transform: hierarchySidebarOpen ? 'translateX(0)' : 'translateX(108%)',
|
|
1831
|
-
pointerEvents: hierarchySidebarOpen ? 'auto' : 'none',
|
|
1832
|
-
transition: 'transform 220ms cubic-bezier(0.4, 0, 0.2, 1)',
|
|
1833
|
-
willChange: 'transform'
|
|
1834
|
-
}, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px', fontWeight: 600 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px' }, children: [_jsx(Layers, { size: 16 }), _jsx("span", { children: "Hierarchy Controls" })] }), _jsx("button", { type: "button", className: styles.headerActionBtn, onClick: () => setHierarchySidebarOpen(false), title: "Hide Hierarchy Sidebar", children: _jsx(PanelRightClose, { size: 16 }) })] }), _jsxs("div", { style: {
|
|
1835
|
-
border: '1px solid var(--border-color)',
|
|
1836
|
-
borderRadius: '10px',
|
|
1837
|
-
padding: '10px',
|
|
1838
|
-
display: 'flex',
|
|
1839
|
-
flexDirection: 'column',
|
|
1840
|
-
gap: '8px'
|
|
1841
|
-
}, children: [_jsx("div", { style: { fontSize: '12px', fontWeight: 600 }, children: "Focus Subtree" }), _jsx("input", { type: "text", className: styles.input, placeholder: "Search parent tasks...", value: hierarchyRootSearch, onChange: (e) => setHierarchyRootSearch(e.target.value), onKeyDown: (e) => {
|
|
1842
|
-
if (e.key === 'Enter') {
|
|
1843
|
-
e.preventDefault();
|
|
1844
|
-
applyHierarchySearchSelection();
|
|
1845
|
-
}
|
|
1846
|
-
} }), _jsx("div", { style: {
|
|
1847
|
-
border: '1px solid var(--border-color)',
|
|
1848
|
-
borderRadius: '8px',
|
|
1849
|
-
padding: '8px',
|
|
1850
|
-
display: 'flex',
|
|
1851
|
-
flexDirection: 'column',
|
|
1852
|
-
gap: '6px',
|
|
1853
|
-
minHeight: '190px'
|
|
1854
|
-
}, children: hierarchyRootSearch.trim().length > 0 ? (_jsxs("div", { className: styles.appScrollbar, style: {
|
|
1855
|
-
maxHeight: '180px',
|
|
1856
|
-
minHeight: '180px',
|
|
1857
|
-
overflowY: 'auto',
|
|
1858
|
-
border: '1px solid var(--border-color)',
|
|
1859
|
-
borderRadius: '8px',
|
|
1860
|
-
padding: '6px',
|
|
1861
|
-
display: 'flex',
|
|
1862
|
-
flexDirection: 'column',
|
|
1863
|
-
gap: '6px'
|
|
1864
|
-
}, children: [hierarchySearchMatches.slice(0, 24).map((task) => (_jsxs("button", { type: "button", className: styles.headerActionBtn, onClick: () => {
|
|
1865
|
-
setHierarchyRootTaskId(task.id);
|
|
1866
|
-
setHierarchyRootSearch('');
|
|
1867
|
-
if (parentTaskFilterMode === 'unlinked') {
|
|
1868
|
-
setParentTaskFilterMode('all');
|
|
1869
|
-
}
|
|
1870
|
-
}, style: { justifyContent: 'space-between', border: '1px solid var(--border-color)' }, title: task.title, children: [_jsx("span", { style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: task.title }), _jsx("span", { style: { opacity: 0.75 }, children: task.id.split('-').slice(-1)[0] })] }, task.id))), hierarchySearchMatches.length === 0 && (_jsxs("div", { style: { fontSize: '12px', color: 'var(--text-secondary)', padding: '4px 2px' }, children: ["No matches for \"", hierarchyRootSearch, "\"."] }))] })) : hierarchyRootTaskId ? (_jsxs(_Fragment, { children: [_jsx("div", { style: { fontSize: '13px', fontWeight: 600, color: 'var(--text-primary)' }, title: selectedHierarchyRootTask?.title || hierarchyRootTaskId, children: selectedHierarchyRootTask?.title || hierarchyRootTaskId }), hierarchySubtreeStats && (_jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px', fontSize: '12px', color: 'var(--text-secondary)' }, children: [_jsxs("div", { style: { border: '1px solid var(--border-color)', borderRadius: '8px', padding: '6px' }, children: [_jsx("strong", { style: { color: 'var(--text-primary)' }, children: hierarchySubtreeStats.total }), " total"] }), _jsxs("div", { style: { border: '1px solid var(--border-color)', borderRadius: '8px', padding: '6px' }, children: [_jsx("strong", { style: { color: 'var(--text-primary)' }, children: hierarchySubtreeStats.active }), " active"] }), _jsxs("div", { style: { border: '1px solid var(--border-color)', borderRadius: '8px', padding: '6px' }, children: [_jsx("strong", { style: { color: 'var(--text-primary)' }, children: hierarchySubtreeStats.terminal }), " done/cancelled"] }), _jsxs("div", { style: { border: '1px solid var(--border-color)', borderRadius: '8px', padding: '6px' }, children: [_jsx("strong", { style: { color: hierarchySubtreeStats.overdue > 0 ? 'var(--color-error, #ef4444)' : 'var(--text-primary)' }, children: hierarchySubtreeStats.overdue }), " overdue"] }), _jsxs("div", { style: { gridColumn: '1 / -1', border: '1px solid var(--border-color)', borderRadius: '8px', padding: '6px' }, children: [_jsx("strong", { style: { color: 'var(--text-primary)' }, children: hierarchySubtreeStats.depth }), " levels deep"] })] })), _jsx("button", { className: styles.headerActionBtn, onClick: () => {
|
|
1871
|
-
setHierarchyRootTaskId('');
|
|
1872
|
-
setHierarchyRootSearch('');
|
|
1873
|
-
}, style: { justifyContent: 'center', border: '1px solid var(--border-color)', background: 'rgba(59,130,246,0.12)' }, children: "Clear subtree focus" })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px', fontSize: '12px', color: 'var(--text-secondary)' }, children: [_jsxs("div", { style: { border: '1px solid var(--border-color)', borderRadius: '8px', padding: '6px' }, children: [_jsx("strong", { style: { color: 'var(--text-primary)' }, children: hierarchyVisibleTasks.length }), " visible"] }), _jsxs("div", { style: { border: '1px solid var(--border-color)', borderRadius: '8px', padding: '6px' }, children: [_jsx("strong", { style: { color: 'var(--text-primary)' }, children: hierarchyVisibleRootCount }), " root parents"] }), _jsxs("div", { style: { border: '1px solid var(--border-color)', borderRadius: '8px', padding: '6px' }, children: [_jsx("strong", { style: { color: 'var(--text-primary)' }, children: hierarchyVisibleUnlinkedCount }), " unlinked"] }), _jsxs("div", { style: { border: '1px solid var(--border-color)', borderRadius: '8px', padding: '6px' }, children: [_jsx("strong", { style: { color: 'var(--text-primary)' }, children: hierarchyDepthTasks.length > 0 ? maxHierarchyDepth + 1 : 0 }), " levels"] })] }), _jsx("div", { style: { fontSize: '12px', color: 'var(--text-secondary)' }, children: hierarchyVisibleLevelCounts.length > 0
|
|
1874
|
-
? hierarchyVisibleLevelCounts.map((entry) => `L${entry.level}: ${entry.count}`).join(' • ')
|
|
1875
|
-
: 'No linked hierarchy tasks in current filters.' })] })) })] }), _jsxs("div", { style: {
|
|
1876
|
-
border: '1px solid var(--border-color)',
|
|
1877
|
-
borderRadius: '10px',
|
|
1878
|
-
padding: '10px',
|
|
1879
|
-
display: 'flex',
|
|
1880
|
-
flexDirection: 'column',
|
|
1881
|
-
gap: '8px'
|
|
1882
|
-
}, children: [_jsx("div", { style: { fontSize: '12px', fontWeight: 600 }, children: "Display" }), _jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: '8px', fontSize: '13px' }, children: [_jsx("input", { type: "checkbox", checked: hierarchyShowUnlinkedColumn, onChange: (e) => {
|
|
1883
|
-
const next = e.target.checked;
|
|
1884
|
-
setHierarchyShowUnlinkedColumn(next);
|
|
1885
|
-
if (!next && parentTaskFilterMode === 'unlinked') {
|
|
1886
|
-
setParentTaskFilterMode('all');
|
|
1887
|
-
}
|
|
1888
|
-
} }), "Show unlinked column"] }), _jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: '8px', fontSize: '13px' }, children: [_jsx("input", { type: "checkbox", checked: hierarchyChildOrderMode === 'manual', onChange: (e) => setHierarchyChildOrderMode(e.target.checked ? 'manual' : 'auto') }), "Sort by parent task order"] }), _jsx("div", { style: { fontSize: '11px', color: 'var(--text-tertiary)' }, children: "Keeps siblings in the order defined on the parent task." })] })] })), groupBy === 'schedule' && (_jsxs("aside", { className: styles.appScrollbar, style: {
|
|
1889
|
-
position: 'absolute',
|
|
1890
|
-
top: 0,
|
|
1891
|
-
right: 0,
|
|
1892
|
-
bottom: 0,
|
|
1893
|
-
width: '320px',
|
|
1894
|
-
minWidth: '320px',
|
|
1895
|
-
borderLeft: '1px solid var(--border-color)',
|
|
1896
|
-
background: 'var(--bg-secondary)',
|
|
1897
|
-
padding: '12px',
|
|
1898
|
-
display: 'flex',
|
|
1899
|
-
flexDirection: 'column',
|
|
1900
|
-
gap: '12px',
|
|
1901
|
-
zIndex: 40,
|
|
1902
|
-
overflowY: 'auto',
|
|
1903
|
-
transform: scheduleSidebarOpen ? 'translateX(0)' : 'translateX(108%)',
|
|
1904
|
-
pointerEvents: scheduleSidebarOpen ? 'auto' : 'none',
|
|
1905
|
-
transition: 'transform 220ms cubic-bezier(0.4, 0, 0.2, 1)',
|
|
1906
|
-
willChange: 'transform'
|
|
1907
|
-
}, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px', fontWeight: 600 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px' }, children: [_jsx(CalendarDays, { size: 16 }), _jsx("span", { children: "Schedule Controls" })] }), _jsx("button", { type: "button", className: styles.headerActionBtn, onClick: () => setScheduleSidebarOpen(false), title: "Hide Schedule Sidebar", children: _jsx(PanelRightClose, { size: 16 }) })] }), _jsxs("div", { style: { fontSize: '12px', color: 'var(--text-secondary)' }, children: ["Week of ", scheduleWeekLabel] }), _jsxs("div", { style: {
|
|
1908
|
-
border: '1px solid var(--border-color)',
|
|
1909
|
-
borderRadius: '10px',
|
|
1910
|
-
padding: '10px'
|
|
1911
|
-
}, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }, children: [_jsx("button", { className: styles.headerActionBtn, onClick: () => {
|
|
1912
|
-
const prev = new Date(scheduleMonthDate);
|
|
1913
|
-
prev.setMonth(scheduleMonthDate.getMonth() - 1);
|
|
1914
|
-
setScheduleCalendarMonth(`${prev.getFullYear()}-${String(prev.getMonth() + 1).padStart(2, '0')}`);
|
|
1915
|
-
}, title: "Previous month", children: _jsx(ChevronLeft, { size: 14 }) }), _jsx("div", { style: { fontSize: '12px', fontWeight: 600 }, children: scheduleCalendarMonthLabel }), _jsx("button", { className: styles.headerActionBtn, onClick: () => {
|
|
1916
|
-
const next = new Date(scheduleMonthDate);
|
|
1917
|
-
next.setMonth(scheduleMonthDate.getMonth() + 1);
|
|
1918
|
-
setScheduleCalendarMonth(`${next.getFullYear()}-${String(next.getMonth() + 1).padStart(2, '0')}`);
|
|
1919
|
-
}, title: "Next month", children: _jsx(ChevronRight, { size: 14 }) })] }), _jsx("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(7, minmax(0, 1fr))', gap: '4px', marginBottom: '4px' }, children: calendarWeekdayLabels.map((label) => (_jsx("div", { style: { fontSize: '11px', color: 'var(--text-secondary)', textAlign: 'center' }, children: label }, label))) }), _jsx("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(7, minmax(0, 1fr))', gap: '4px' }, children: calendarCells.map((date) => {
|
|
1920
|
-
const dateOnly = toDateOnlyLocal(date);
|
|
1921
|
-
const isOutsideMonth = date.getMonth() !== scheduleMonthDate.getMonth();
|
|
1922
|
-
const weekStartForCell = startOfWeek(date, globalWeekStartsOn);
|
|
1923
|
-
const isSelectedWeek = toDateOnlyLocal(weekStartForCell) === toDateOnlyLocal(scheduleWeekStart);
|
|
1924
|
-
const isSelectedDate = dateOnly === scheduleSelectedDate;
|
|
1925
|
-
const isToday = dateOnly === todayDateOnly;
|
|
1926
|
-
const isPast = dateOnly < todayDateOnly;
|
|
1927
|
-
const signal = calendarTaskSignals.get(dateOnly);
|
|
1928
|
-
const hasScheduledTasks = !!signal;
|
|
1929
|
-
const hasExpiredScheduledTasks = !!signal?.hasExpired;
|
|
1930
|
-
return (_jsxs("button", { onClick: () => {
|
|
1931
|
-
setScheduleSelectedDate(dateOnly);
|
|
1932
|
-
setScheduleCalendarMonth(`${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`);
|
|
1933
|
-
}, style: {
|
|
1934
|
-
border: isSelectedDate
|
|
1935
|
-
? '1px solid #2563eb'
|
|
1936
|
-
: isToday
|
|
1937
|
-
? '1px solid rgba(245, 158, 11, 0.95)'
|
|
1938
|
-
: '1px solid transparent',
|
|
1939
|
-
borderRadius: '8px',
|
|
1940
|
-
padding: '6px 0',
|
|
1941
|
-
fontSize: '12px',
|
|
1942
|
-
position: 'relative',
|
|
1943
|
-
background: isSelectedDate
|
|
1944
|
-
? 'rgba(59,130,246,0.22)'
|
|
1945
|
-
: isToday
|
|
1946
|
-
? 'rgba(245,158,11,0.16)'
|
|
1947
|
-
: isSelectedWeek
|
|
1948
|
-
? 'rgba(59,130,246,0.18)'
|
|
1949
|
-
: 'transparent',
|
|
1950
|
-
color: isOutsideMonth ? 'var(--text-tertiary)' : 'var(--text-primary)',
|
|
1951
|
-
cursor: 'pointer',
|
|
1952
|
-
fontWeight: isSelectedDate || isToday ? 700 : 500,
|
|
1953
|
-
opacity: isPast ? 0.42 : 1
|
|
1954
|
-
}, title: `${isToday ? `${dateOnly} (Today)` : dateOnly}`
|
|
1955
|
-
+ (hasScheduledTasks ? ` • ${signal?.count} scheduled` : '')
|
|
1956
|
-
+ (hasExpiredScheduledTasks ? ' • includes expired' : ''), children: [date.getDate(), hasScheduledTasks && (_jsx("span", { style: {
|
|
1957
|
-
position: 'absolute',
|
|
1958
|
-
left: '50%',
|
|
1959
|
-
bottom: '3px',
|
|
1960
|
-
transform: 'translateX(-50%)',
|
|
1961
|
-
width: '5px',
|
|
1962
|
-
height: '5px',
|
|
1963
|
-
borderRadius: '999px',
|
|
1964
|
-
background: hasExpiredScheduledTasks ? '#ef4444' : '#3b82f6',
|
|
1965
|
-
opacity: isOutsideMonth ? 0.6 : 0.95
|
|
1966
|
-
} }))] }, dateOnly));
|
|
1967
|
-
}) }), _jsx("div", { style: { display: 'flex', justifyContent: 'space-between', marginTop: '10px' }, children: _jsx("button", { className: styles.headerActionBtn, onClick: () => {
|
|
1968
|
-
const now = new Date();
|
|
1969
|
-
setScheduleSelectedDate(toDateOnlyLocal(now));
|
|
1970
|
-
setScheduleCalendarMonth(`${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`);
|
|
1971
|
-
}, title: "Jump to current week", children: "Today" }) })] }), _jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: '8px', fontSize: '13px' }, children: [_jsx("input", { type: "checkbox", checked: scheduleShowWeekends, onChange: (e) => setScheduleShowWeekends(e.target.checked) }), "Show weekends"] }), _jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: '8px', fontSize: '13px' }, children: [_jsx("input", { type: "checkbox", checked: scheduleShowBacklog, onChange: (e) => setScheduleShowBacklog(e.target.checked) }), "Show backlog"] }), _jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: '8px', fontSize: '13px' }, children: [_jsx("input", { type: "checkbox", checked: scheduleOnlyExpired, onChange: (e) => setScheduleOnlyExpired(e.target.checked) }), "Only expired/overdue"] }), expiredScheduledCount > 0 && (_jsxs("div", { style: {
|
|
1972
|
-
border: '1px solid var(--border-color)',
|
|
1973
|
-
borderRadius: '10px',
|
|
1974
|
-
padding: '10px',
|
|
1975
|
-
display: 'flex',
|
|
1976
|
-
flexDirection: 'column',
|
|
1977
|
-
gap: '8px'
|
|
1978
|
-
}, children: [_jsx("div", { style: { fontSize: '12px', fontWeight: 600 }, children: "Expired Tasks" }), _jsxs("div", { style: { fontSize: '12px', color: 'var(--text-secondary)', display: 'flex', flexDirection: 'column', gap: '2px' }, children: [_jsxs("span", { children: [expiredScheduledCount, " expired"] }), _jsxs("span", { children: [overdueDueCount, " overdue"] })] }), _jsx("button", { className: styles.headerActionBtn, onClick: handleMoveExpiredToSelectedWeek, disabled: scheduleBulkBusy || expiredLeafCandidates.length === 0, title: "Move expired leaf tasks to the selected week while keeping weekday alignment", style: { justifyContent: 'flex-start', border: '1px solid var(--border-color)' }, children: scheduleBulkBusy ? 'Working...' : 'Schedule to selected week' }), _jsx("button", { className: styles.headerActionBtn, onClick: handleMoveExpiredToBacklog, disabled: scheduleBulkBusy || expiredRecoveryCandidates.length === 0, title: "Unschedule expired/overdue tasks back to backlog", style: { justifyContent: 'flex-start', border: '1px solid var(--border-color)' }, children: scheduleBulkBusy ? 'Working...' : 'Unschedule to backlog' })] }))] }))] })), _jsxs(Modal, { isOpen: activeTab === 'add', onClose: handleCloseModal, title: editingTaskId ? 'Edit Task' : 'New Task', size: "md", theme: currentTheme, draggable: true, closeOnOverlayClick: false, children: [_jsx(TaskActionHeader, { editingTaskId: editingTaskId, loading: loading, title: title, currentTask: currentTask, handleSubmit: handleSubmit, resetForm: resetForm, handleCopyId: handleCopyId, copiedId: copiedId, tasks: tasks, handleToggleInProgress: handleToggleInProgress, handleToggleReview: handleToggleReview, handleToggleComplete: handleToggleComplete, handleToggleCancel: handleToggleCancel, handleArchiveTask: handleArchiveTask }), _jsx(TaskForm, { editingTaskId: editingTaskId, error: error, title: title, description: description, category: category, type: type, priority: priority, complexity: complexity, manualComplexityEnabled: manualComplexityEnabled, approach: approach, assignee: assignee, scheduledDate: scheduledDate, dueDate: dueDate, parentTaskIdInput: parentTaskIdInput, addChildTaskIdInput: addChildTaskIdInput, formTaxonomies: formTaxonomies, onTaxonomyChange: (id, val) => setFormTaxonomies(prev => ({ ...prev, [id]: val })), onParentTaskIdInputChange: setParentTaskIdInput, onAddChildTaskIdInputChange: setAddChildTaskIdInput, taxonomies: taxonomies, selectedSpecialists: selectedSpecialists, comments: comments, newCommentText: newCommentText, contextFiles: screenshots, contextLinkBrowsePath: contextLinkBrowsePath, descriptionFocused: descriptionFocused, showMarkdownHelp: showMarkdownHelp, showSpecialists: showSpecialists, showChecklist: showChecklist, checklistEnabled: checklistDropdownEnabled, showTaskRelationships: showTaskRelationships, showChildTasks: showChildTasks, showComments: showComments, categories: activeCategories, types: activeTypes, priorities: priorities, approaches: approaches, availableSpecialists: availableSpecialists, copiedId: copiedId, onTitleChange: setTitle, onDescriptionChange: setDescription, onCategoryChange: setCategory, onTypeChange: setType, onPriorityChange: setPriority, onComplexityChange: setComplexity, onApproachChange: (val) => { setApproach(val); setFormTaxonomies(prev => ({ ...prev, approach: val })); }, onAssigneeChange: setAssignee, onScheduledDateChange: setScheduledDate, onDueDateChange: setDueDate, onSelectedSpecialistsChange: setSelectedSpecialists, onNewCommentTextChange: setNewCommentText, onDescriptionFocusedChange: setDescriptionFocused, onShowMarkdownHelpChange: setShowMarkdownHelp, onShowSpecialistsChange: setShowSpecialists, onShowChecklistChange: setShowChecklist, onShowTaskRelationshipsChange: setShowTaskRelationships, onShowChildTasksChange: setShowChildTasks, onShowCommentsChange: setShowComments, onOpenSettings: (section) => {
|
|
2962
|
+
}, children: _jsx(ChevronLeft, { size: 16 }) }))] }), groupBy === 'schedule' && (_jsx(ScheduleSidebar, { open: scheduleSidebarOpen, onClose: () => setScheduleSidebarOpen(false), scheduleSelectedDate: scheduleSelectedDate, setScheduleSelectedDate: setScheduleSelectedDate, scheduleCalendarMonth: scheduleCalendarMonth, setScheduleCalendarMonth: setScheduleCalendarMonth, scheduleShowWeekends: scheduleShowWeekends, setScheduleShowWeekends: setScheduleShowWeekends, scheduleShowBacklog: scheduleShowBacklog, setScheduleShowBacklog: setScheduleShowBacklog, scheduleOnlyExpired: scheduleOnlyExpired, setScheduleOnlyExpired: setScheduleOnlyExpired, expiredScheduledCount: expiredScheduledCount, overdueDueCount: overdueDueCount, expiredLeafCandidates: expiredLeafCandidates, expiredRecoveryCandidates: expiredRecoveryCandidates, onMoveExpiredToSelectedWeek: handleMoveExpiredToSelectedWeek, onMoveExpiredToBacklog: handleMoveExpiredToBacklog, scheduleBulkBusy: scheduleBulkBusy, globalWeekStartsOn: globalWeekStartsOn, resolvedLocale: resolvedLocale, todayDateOnly: todayDateOnly, scheduleBaseTasks: scheduleBaseTasks, scheduleWeekStart: scheduleWeekStart, scheduleWeekLabel: scheduleWeekLabel }))] })), _jsxs(Modal, { isOpen: activeTab === 'add', onClose: handleCloseModal, title: editingTaskId ? (currentTask?.isDeleted ? 'Deleted Task' : 'Edit Task') : 'New Task', size: taskModalFullscreen ? 'full' : 'xl', theme: currentTheme, headerActions: (_jsx("button", { className: "tf-control-icon", onClick: () => setTaskModalFullscreen((value) => !value), title: taskModalFullscreen ? 'Exit full screen' : 'Enter full screen', "aria-label": taskModalFullscreen ? 'Exit full screen' : 'Enter full screen', children: taskModalFullscreen ? _jsx(Minimize2, { size: 18 }) : _jsx(Maximize2, { size: 18 }) })), draggable: !taskModalFullscreen, closeOnOverlayClick: false, children: [_jsx(TaskActionHeader, { editingTaskId: editingTaskId, loading: loading, autoSaveState: autoSaveState, title: title, currentTask: currentTask, currentTaskWorkstream: currentTaskWorkstream, currentTaskInitiative: currentTaskInitiative, workstreamInput: workstreamInput, onWorkstreamInputChange: setWorkstreamInput, onSetWorkstreamForCurrentTask: handleSetWorkstreamForCurrentTask, handleSubmit: handleSubmit, resetForm: resetForm, handleCopyId: handleCopyId, copiedId: copiedId, tasks: tasks, handleToggleInProgress: handleToggleInProgress, handleToggleReview: handleToggleReview, handleToggleComplete: handleToggleComplete, handleToggleCancel: handleToggleCancel, handleSetStatus: handleSetStatus, handleArchiveTask: handleArchiveTask, handleRestoreDeletedTask: currentTask?.deletedRecordId
|
|
2963
|
+
? (deletedRecordId) => {
|
|
2964
|
+
void handleRestoreDeletedTask(deletedRecordId);
|
|
2965
|
+
}
|
|
2966
|
+
: undefined, handlePermanentlyDeleteDeletedTask: currentTask?.deletedRecordId
|
|
2967
|
+
? (deletedRecordId) => {
|
|
2968
|
+
const confirmed = window.confirm('Permanently delete this task? This cannot be undone.');
|
|
2969
|
+
if (!confirmed)
|
|
2970
|
+
return;
|
|
2971
|
+
void handlePermanentlyDeleteDeletedTask(deletedRecordId);
|
|
2972
|
+
resetForm();
|
|
2973
|
+
setActiveTab('tasks');
|
|
2974
|
+
}
|
|
2975
|
+
: undefined }), _jsx(TaskForm, { editingTaskId: editingTaskId, error: error, title: title, description: description, checklistItems: checklistItems, category: category, type: type, priority: priority, complexity: complexity, manualComplexityEnabled: manualComplexityEnabled, approach: approach, assignee: assignee, scheduledDate: scheduledDate, dueDate: dueDate, workstreamInput: workstreamInput, formTaxonomies: formTaxonomies, onTaxonomyChange: (id, val) => setFormTaxonomies(prev => ({ ...prev, [id]: val })), taxonomies: taxonomies, comments: comments, newCommentText: newCommentText, contextFiles: attachments, currentWorkspaceId: currentWorkspaceId, apiBaseUrl: apiBaseUrl, descriptionFocused: descriptionFocused, showMarkdownHelp: showMarkdownHelp, showChecklist: showChecklist, checklistEnabled: checklistDropdownEnabled, showComments: showComments, categories: activeCategories, types: activeTypes, priorities: priorities, taxonomyDisplayLabels: taxonomyDisplayLabels, assigneeOptions: assigneeOptions, workstreams: props.workstreams, copiedId: copiedId, onTitleChange: setTitle, onDescriptionChange: setDescription, onChecklistItemsChange: setChecklistItems, onCategoryChange: setCategory, onTypeChange: setType, onPriorityChange: setPriority, onComplexityChange: setComplexity, onApproachChange: (val) => { setApproach(val); setFormTaxonomies(prev => ({ ...prev, approach: val })); }, onAssigneeChange: setAssignee, onScheduledDateChange: setScheduledDate, onDueDateChange: setDueDate, onWorkstreamInputChange: setWorkstreamInput, onNewCommentTextChange: setNewCommentText, onDescriptionFocusedChange: setDescriptionFocused, onShowMarkdownHelpChange: setShowMarkdownHelp, onShowChecklistChange: setShowChecklist, onShowCommentsChange: setShowComments, onOpenSettings: (section) => {
|
|
1979
2976
|
setSettingsSection(section);
|
|
1980
2977
|
setActiveTab('settings');
|
|
1981
|
-
}, onSubmit: handleSubmit, commentsEndRef: commentsEndRef, onAddComment: () => handleAddComment(newCommentText),
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
2978
|
+
}, onSubmit: handleSubmit, commentsEndRef: commentsEndRef, onAddComment: () => handleAddComment(newCommentText), onOpenTaskById: handleOpenTaskById, onAddContextFile: (file) => {
|
|
2979
|
+
let nextAttachments = attachments;
|
|
2980
|
+
setAttachments((prev) => {
|
|
2981
|
+
nextAttachments = [...prev, file];
|
|
2982
|
+
return nextAttachments;
|
|
2983
|
+
});
|
|
2984
|
+
setAttachmentsDirty(true);
|
|
2985
|
+
if (editingTaskId) {
|
|
2986
|
+
void handleUpdateTask(editingTaskId, { attachments: nextAttachments });
|
|
2987
|
+
}
|
|
1987
2988
|
}, onRemoveContextFile: async (index) => {
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
2989
|
+
let nextAttachments = attachments;
|
|
2990
|
+
setAttachments((prev) => {
|
|
2991
|
+
nextAttachments = prev.filter((_, i) => i !== index);
|
|
2992
|
+
return nextAttachments;
|
|
2993
|
+
});
|
|
2994
|
+
setAttachmentsDirty(true);
|
|
2995
|
+
if (editingTaskId) {
|
|
2996
|
+
void handleUpdateTask(editingTaskId, { attachments: nextAttachments });
|
|
1996
2997
|
}
|
|
1997
2998
|
}, onUpdateContextCaption: (index, caption) => {
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2999
|
+
let nextAttachments = attachments;
|
|
3000
|
+
setAttachments((prev) => {
|
|
3001
|
+
nextAttachments = prev.map((s, i) => {
|
|
3002
|
+
if (i !== index)
|
|
3003
|
+
return s;
|
|
3004
|
+
if (typeof s === 'string')
|
|
3005
|
+
return { path: s, caption, timestamp: new Date().toISOString() };
|
|
3006
|
+
return { ...s, caption };
|
|
3007
|
+
});
|
|
3008
|
+
return nextAttachments;
|
|
3009
|
+
});
|
|
3010
|
+
setAttachmentsDirty(true);
|
|
3011
|
+
if (editingTaskId) {
|
|
3012
|
+
void handleUpdateTask(editingTaskId, { attachments: nextAttachments });
|
|
3013
|
+
}
|
|
3014
|
+
}, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onSetStatus: handleSetStatus, onArchiveTask: handleArchiveTask, onUnarchive: (taskId) => {
|
|
3015
|
+
if (currentTask?.isDeleted) {
|
|
3016
|
+
const deletedRecord = deletedTaskRecordByTaskId.get(taskId);
|
|
3017
|
+
if (!deletedRecord)
|
|
3018
|
+
return;
|
|
3019
|
+
void handleRestoreDeletedTask(deletedRecord.id);
|
|
3020
|
+
return;
|
|
3021
|
+
}
|
|
3022
|
+
void handleUnarchive(taskId);
|
|
3023
|
+
}, currentTask: currentTask })] }), _jsx(Modal, { isOpen: activeTab === 'settings', onClose: handleCloseModal, title: "Settings", size: "xl", theme: currentTheme, draggable: true, isSettings: true, closeOnOverlayClick: false, children: _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '2rem' }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(TaskSettings, { settingsModel: settingsModel, onSectionChange: setSettingsSection }) }) }), _jsx(Modal, { isOpen: !!scheduleWarningPrompt, onClose: cancelScheduleWarning, title: "Date Warning", size: "sm", theme: currentTheme, draggable: false, closeOnOverlayClick: false, children: _jsxs("div", { className: `${styles.form} tf-inline-stack-sm`, style: { padding: '16px', gap: '12px' }, children: [_jsx("p", { className: "tf-text-body", children: "Due date is before scheduled date." }), scheduleWarningPrompt && (_jsxs("p", { className: "tf-text-helper", children: ["Due: ", scheduleWarningPrompt.dueDate, " \u00B7 Scheduled: ", scheduleWarningPrompt.scheduledDate] })), _jsxs("div", { className: styles.formActions, children: [_jsx("button", { className: styles.cancelBtn, onClick: cancelScheduleWarning, children: "Go Back" }), _jsx("button", { className: styles.submitBtn, onClick: confirmScheduleWarning, children: "Save Anyway" })] })] }) }), _jsx(Modal, { isOpen: showHelpModal, onClose: () => setShowHelpModal(false), title: "Quick Start", size: "md", theme: currentTheme, draggable: true, children: _jsxs("div", { className: `${modalStyles.form} ${managementStyles.modalBody}`, children: [_jsx("p", { className: managementStyles.mutedText, children: "Use this quick guide to get productive in Taskforce dashboard mode." }), _jsxs("div", { children: [_jsx("strong", { children: "1. Capture work" }), _jsxs("p", { className: managementStyles.sectionText, children: ["Click ", _jsx("code", { children: "Add Task" }), ", write a short title, and save."] })] }), _jsxs("div", { children: [_jsx("strong", { children: "2. Plan the board" }), _jsxs("p", { className: managementStyles.sectionText, children: ["Use ", _jsx("code", { children: "Sort" }), ", ", _jsx("code", { children: "Group" }), ", and ", _jsx("code", { children: "Filters" }), " in the header."] })] }), _jsxs("div", { children: [_jsx("strong", { children: "3. Execute and stage" }), _jsxs("p", { className: managementStyles.sectionText, children: ["Move tasks through ", _jsx("code", { children: "Task" }), ", ", _jsx("code", { children: "In Progress" }), ", ", _jsx("code", { children: "Review" }), ", and ", _jsx("code", { children: "Done" }), "."] })] }), _jsxs("div", { children: [_jsx("strong", { children: "4. Archive finished work" }), _jsx("p", { className: managementStyles.sectionText, children: "Archive completed/cancelled tasks to keep active board signal high." })] }), _jsxs("div", { className: `${modalStyles.formActions} ${managementStyles.modalActionsEnd}`, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => {
|
|
2006
3024
|
setShowHelpModal(false);
|
|
2007
3025
|
setActiveTab('settings');
|
|
2008
3026
|
setSettingsSection('general');
|
|
2009
|
-
}, children: "Open Settings" }), _jsx("button", { className: styles.submitBtn, onClick: () => setShowHelpModal(false), children: "Close Tutorial" })] })] }) }), _jsx(Modal, { isOpen:
|
|
3027
|
+
}, children: "Open Settings" }), _jsx("button", { className: styles.submitBtn, onClick: () => setShowHelpModal(false), children: "Close Tutorial" })] })] }) }), _jsx(Modal, { isOpen: showEditProfileModal, onClose: () => setShowEditProfileModal(false), title: "Edit Profile", size: "sm", theme: currentTheme, draggable: true, footer: (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: styles.secondaryButton, onClick: () => setShowEditProfileModal(false), disabled: profileSaveBusy, children: "Cancel" }), _jsx("button", { type: "button", className: styles.primaryUpdateBtn, onClick: () => { void handleSaveProfile(); }, disabled: profileSaveBusy || !profileDisplayNameDraft.trim(), children: profileSaveBusy ? 'Saving...' : 'Save Profile' })] })), children: _jsxs("div", { className: `${modalStyles.form} ${managementStyles.modalBody}`, children: [_jsx("p", { className: managementStyles.sectionText, children: "Update the name shown around your account and shared workspace surfaces." }), _jsxs("label", { className: managementStyles.inlineFieldLabel, children: ["Display name", _jsx("input", { className: styles.input, type: "text", value: profileDisplayNameDraft, onChange: (event) => setProfileDisplayNameDraft(event.target.value), placeholder: "Display name", disabled: profileSaveBusy })] }), _jsxs("label", { className: managementStyles.inlineFieldLabel, children: ["Email", _jsx("input", { className: styles.input, type: "email", value: authEmailLabel, readOnly: true, disabled: true })] }), profileSaveError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: profileSaveError })), profileSaveNotice && !profileSaveError && (_jsx("p", { className: managementStyles.errorText, children: profileSaveNotice }))] }) }), _jsx(Modal, { isOpen: showAccountHub, onClose: () => setShowAccountHub(false), title: "Account Hub", size: "md", theme: currentTheme, draggable: true, children: _jsxs("div", { className: `${modalStyles.form} ${managementStyles.modalBody}`, children: [_jsx("p", { className: managementStyles.mutedText, children: "Manage sign in, profile, and workspace controls from one place." }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Authentication" }), _jsx("p", { className: managementStyles.sectionText, children: runtimeMode === 'cloud'
|
|
2010
3028
|
? (authRequiredForApi
|
|
2011
3029
|
? (isAuthenticated ? 'Signed in. API access is enabled.' : 'Cloud mode requires sign in before app usage.')
|
|
2012
3030
|
: 'Cloud runtime with optional authentication.')
|
|
2013
|
-
: 'Local runtime allows guest usage without sign in.' }), _jsx("p", {
|
|
3031
|
+
: 'Local runtime allows guest usage without sign in.' }), _jsx("p", { className: managementStyles.sectionTextSpaced, children: isAuthenticated
|
|
2014
3032
|
? (hasAuthIdentity
|
|
2015
|
-
? _jsxs(_Fragment, { children: ["Signed in as ", _jsx("span", { className:
|
|
3033
|
+
? _jsxs(_Fragment, { children: ["Signed in as ", _jsx("span", { className: accountStyles.accountIdentityEmail, children: authIdentityLabel })] })
|
|
2016
3034
|
: 'Signed in')
|
|
2017
|
-
: 'Signed in as: not signed in' }), runtimeMode === 'cloud' && authRequiredForApi && !isAuthenticated && (_jsx("p", {
|
|
3035
|
+
: 'Signed in as: not signed in' }), runtimeMode === 'cloud' && authRequiredForApi && !isAuthenticated && (_jsx("p", { className: managementStyles.sectionTextTop, children: "Use the `/login` screen to sign in." }))] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Profile & Account" }), _jsx("p", { className: managementStyles.sectionText, children: "Profile editing and account preferences will live here." })] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Billing" }), !isAuthenticated ? (_jsx("p", { className: managementStyles.sectionText, children: "Sign in to manage subscription billing." })) : runtimeMode !== 'cloud' ? (_jsx("p", { className: managementStyles.sectionText, children: "Billing remains cloud-backed in local runtime and uses your connected cloud account." })) : (_jsxs(_Fragment, { children: [billingLoading && (_jsx("p", { className: managementStyles.sectionTextSpaced, children: "Loading billing status..." })), billingError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: billingError })), billingActionError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: billingActionError })), billingNotice && (_jsx("p", { className: managementStyles.errorText, children: billingNotice })), _jsxs("p", { className: managementStyles.sectionTextSpaced, children: ["Plan: ", _jsx("code", { children: String(billingStatus?.planId || 'n/a') }), ' · ', "Entitlement: ", _jsx("code", { children: String(billingStatus?.entitlementState || 'n/a') })] }), _jsxs("p", { className: managementStyles.sectionText, children: ["Stripe status: ", _jsx("code", { children: String(billingStatus?.stripeStatus || 'n/a') }), billingStatus?.effectiveUntil ? ` · Effective until ${new Date(billingStatus.effectiveUntil).toLocaleString()}` : ''] }), _jsx("div", { className: managementStyles.wrapRow, children: _jsxs("label", { className: `${accountStyles.accountMenuMeta} ${managementStyles.inlineFieldLabel}`, children: ["Interval", _jsxs("select", { className: styles.input, value: billingIntervalChoice, onChange: (event) => setBillingIntervalChoice(event.target.value === 'year' ? 'year' : 'month'), disabled: billingActionBusy, children: [_jsx("option", { value: "month", children: "Monthly" }), _jsx("option", { value: "year", children: "Yearly" })] })] }) }), _jsxs("div", { className: managementStyles.actionRowEnd, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => { void loadBillingStatus(); }, disabled: billingActionBusy || billingLoading, children: "Refresh Billing" }), _jsx("button", { className: styles.cancelBtn, onClick: () => { void handleSwitchSubscriptionInterval(); }, disabled: billingActionBusy || !billingStatus?.stripeSubscriptionId, children: "Update Interval" }), _jsx("button", { className: styles.cancelBtn, onClick: () => { void handleOpenBillingPortal(); }, disabled: billingActionBusy || !billingStatus?.stripeCustomerId, children: "Manage Billing" }), _jsx("button", { className: styles.submitBtn, onClick: () => { void handleStartCheckout(); }, disabled: billingActionBusy, children: billingActionBusy ? 'Working...' : 'Start Checkout' })] })] }))] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Workspace Audit Log" }), _jsx("p", { className: managementStyles.sectionText, children: "Audit entries are workspace-scoped for the active workspace, not global account history." }), _jsxs("p", { className: managementStyles.sectionText, children: ["Active workspace: ", _jsx("code", { children: currentWorkspaceId })] }), _jsx("div", { className: managementStyles.actionRowEnd, children: _jsx("button", { className: styles.cancelBtn, onClick: () => {
|
|
2018
3036
|
setShowAccountHub(false);
|
|
2019
3037
|
void openTeamManagementModal();
|
|
2020
3038
|
setTeamManagementTab('audit');
|
|
2021
|
-
}, disabled: !canOpenTeamManagement, children: "Open Workspace Audit" }) })] }), _jsxs("div", { className:
|
|
3039
|
+
}, disabled: !canOpenTeamManagement, children: "Open Workspace Audit" }) })] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Workspace Cloud Sync" }), _jsx("p", { className: managementStyles.sectionText, children: canManageWorkspaceSync
|
|
2022
3040
|
? (workspaceCloudSyncEnabled
|
|
2023
|
-
? `Enabled
|
|
3041
|
+
? `Enabled · ${syncStatusMeta.label}`
|
|
2024
3042
|
: 'Disabled')
|
|
2025
|
-
: 'Sign in to cloud account to enable workspace sync.' }), workspaceSyncError && (_jsx("p", { className:
|
|
2026
|
-
if (!canManageWorkspaceSync)
|
|
2027
|
-
return;
|
|
2028
|
-
setWorkspaceSyncError(null);
|
|
2029
|
-
if (!workspaceCloudSyncEnabled) {
|
|
2030
|
-
setWorkspaceSyncBusy(true);
|
|
2031
|
-
const result = await saveWorkspaceCloudSyncSettings({
|
|
2032
|
-
enabled: true,
|
|
2033
|
-
// I6: start false; the orchestrator marks it true after first successful pull.
|
|
2034
|
-
onboardingCompleted: false
|
|
2035
|
-
});
|
|
2036
|
-
if (!result.success)
|
|
2037
|
-
setWorkspaceSyncError(result.error || 'Failed to enable sync.');
|
|
2038
|
-
setWorkspaceSyncBusy(false);
|
|
2039
|
-
return;
|
|
2040
|
-
}
|
|
2041
|
-
setWorkspaceSyncBusy(true);
|
|
2042
|
-
const result = await saveWorkspaceCloudSyncSettings({
|
|
2043
|
-
enabled: false,
|
|
2044
|
-
sourceOfTruth: workspaceSyncSourceOfTruth,
|
|
2045
|
-
onboardingCompleted: workspaceSyncOnboardingCompleted
|
|
2046
|
-
});
|
|
2047
|
-
if (!result.success)
|
|
2048
|
-
setWorkspaceSyncError(result.error || 'Failed to disable sync.');
|
|
2049
|
-
setWorkspaceSyncBusy(false);
|
|
2050
|
-
}, children: workspaceCloudSyncEnabled ? 'Disable Sync' : 'Enable Sync' }) })] }), _jsxs("div", { className: modalStyles.formActions, style: { display: 'flex', justifyContent: 'flex-end', gap: '10px' }, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => {
|
|
3043
|
+
: 'Sign in to cloud account to enable workspace sync.' }), workspaceSyncError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: workspaceSyncError })), _jsx("div", { className: managementStyles.actionRowEnd, children: _jsx("button", { className: styles.cancelBtn, disabled: !canManageWorkspaceSync || syncControlBusy, onClick: () => { void handleWorkspaceSyncToggle(!workspaceCloudSyncEnabled); }, children: workspaceCloudSyncEnabled ? 'Disable Sync' : 'Enable Sync' }) })] }), _jsxs("div", { className: `${modalStyles.formActions} ${managementStyles.modalActionsEnd}`, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => {
|
|
2051
3044
|
setShowAccountHub(false);
|
|
2052
3045
|
setShowHelpModal(true);
|
|
2053
|
-
}, children: "Help & Tutorial" }), _jsx("button", { className: styles.submitBtn, onClick: () => setShowAccountHub(false), children: "Close" })] })] }) }), _jsx(Modal, { isOpen: showTeamManagementModal, onClose: () => setShowTeamManagementModal(false), title: "Team Management", size: "md", theme: currentTheme, draggable: true, children: _jsxs("div", { className: modalStyles.form
|
|
3046
|
+
}, children: "Help & Tutorial" }), _jsx("button", { className: styles.submitBtn, onClick: () => setShowAccountHub(false), children: "Close" })] })] }) }), _jsx(Modal, { isOpen: showTeamManagementModal, onClose: () => setShowTeamManagementModal(false), title: "Team Management", size: "md", theme: currentTheme, draggable: true, children: _jsxs("div", { className: `${modalStyles.form} ${managementStyles.modalBody}`, children: [teamPlanMode === 'personal' && (_jsx("p", { className: authStyles.loginError, children: "Team management is only available in TEAM accounts." })), teamMgmtError && (_jsx("p", { className: authStyles.loginError, children: teamMgmtError })), _jsxs("div", { className: `${styles.settingsTabs} tf-scrollbar tf-scrollbar--track-transparent tf-scrollbar--compact`, children: [_jsx("button", { className: `${styles.settingsTabBtn} ${teamManagementTab === 'members' ? styles.settingsTabBtnActive : ''}`, onClick: () => {
|
|
2054
3047
|
setTeamManagementTab('members');
|
|
2055
3048
|
void loadTeamUsers();
|
|
2056
3049
|
}, children: "Members" }), _jsx("button", { className: `${styles.settingsTabBtn} ${teamManagementTab === 'invites' ? styles.settingsTabBtnActive : ''}`, onClick: () => setTeamManagementTab('invites'), children: "Invites" }), _jsx("button", { className: `${styles.settingsTabBtn} ${teamManagementTab === 'audit' ? styles.settingsTabBtnActive : ''}`, onClick: () => {
|
|
2057
3050
|
setTeamManagementTab('audit');
|
|
2058
3051
|
void loadTeamAuditPage(teamAuditPage);
|
|
2059
|
-
}, children: "Audit Log" })] }), teamManagementTab === 'members' && (_jsxs("div", { className:
|
|
3052
|
+
}, children: "Audit Log" })] }), teamManagementTab === 'members' && (_jsxs("div", { className: accountStyles.accountHubCard, children: [teamUsersLoading && _jsx("p", { className: managementStyles.mutedText, children: "Loading members..." }), !teamUsersLoading && teamUsers.length === 0 && (_jsx("p", { className: managementStyles.mutedText, children: "No users found." })), !teamUsersLoading && teamUsers.map((member) => (_jsxs("div", { className: managementStyles.memberRow, children: [_jsx("div", { className: managementStyles.memberTitle, children: member.displayName || member.email }), _jsxs("div", { className: accountStyles.accountMenuMeta, children: [member.email, " \u00B7 ", member.status, member.disabled ? ' · deactivated' : ''] }), _jsxs("div", { className: managementStyles.memberActions, children: [_jsxs("select", { className: `${styles.input} ${managementStyles.selectRole}`, value: member.role, onChange: (event) => {
|
|
2060
3053
|
void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}/role`, {
|
|
2061
3054
|
method: 'PATCH',
|
|
2062
3055
|
headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
|
|
2063
3056
|
credentials: 'include',
|
|
2064
3057
|
body: JSON.stringify({
|
|
2065
|
-
workspaceId:
|
|
3058
|
+
workspaceId: teamManagementWorkspaceId,
|
|
2066
3059
|
role: event.target.value
|
|
2067
3060
|
})
|
|
2068
3061
|
}));
|
|
2069
|
-
}, disabled: teamActionBusyUserId === member.userId,
|
|
3062
|
+
}, disabled: teamActionBusyUserId === member.userId, children: [_jsx("option", { value: "owner", children: "Owner" }), _jsx("option", { value: "admin", children: "Admin" }), _jsx("option", { value: "member", children: "Member" })] }), member.role === 'member' && (_jsxs("select", { className: `${styles.input} ${managementStyles.selectPermission}`, value: member.permissionMode, onChange: (event) => {
|
|
2070
3063
|
void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}/permission-mode`, {
|
|
2071
3064
|
method: 'PATCH',
|
|
2072
3065
|
headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
|
|
2073
3066
|
credentials: 'include',
|
|
2074
3067
|
body: JSON.stringify({
|
|
2075
|
-
workspaceId:
|
|
3068
|
+
workspaceId: teamManagementWorkspaceId,
|
|
2076
3069
|
mode: event.target.value
|
|
2077
3070
|
})
|
|
2078
3071
|
}));
|
|
2079
|
-
}, disabled: teamActionBusyUserId === member.userId,
|
|
3072
|
+
}, disabled: teamActionBusyUserId === member.userId, children: [_jsx("option", { value: "read-write", children: "Read / Write" }), _jsx("option", { value: "read-only", children: "Read Only" })] })), _jsx("button", { className: styles.cancelBtn, disabled: teamActionBusyUserId === member.userId, onClick: () => {
|
|
2080
3073
|
void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}/disable`, {
|
|
2081
3074
|
method: 'PATCH',
|
|
2082
3075
|
headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
|
|
2083
3076
|
credentials: 'include',
|
|
2084
3077
|
body: JSON.stringify({
|
|
2085
|
-
workspaceId:
|
|
3078
|
+
workspaceId: teamManagementWorkspaceId,
|
|
2086
3079
|
disabled: !member.disabled
|
|
2087
3080
|
})
|
|
2088
3081
|
}));
|
|
@@ -2091,27 +3084,52 @@ export function StandaloneLayout(props) {
|
|
|
2091
3084
|
method: 'POST',
|
|
2092
3085
|
headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
|
|
2093
3086
|
credentials: 'include',
|
|
2094
|
-
body: JSON.stringify({ workspaceId:
|
|
3087
|
+
body: JSON.stringify({ workspaceId: teamManagementWorkspaceId })
|
|
2095
3088
|
}));
|
|
2096
3089
|
}, children: "Revoke Invite" })), _jsx("button", { className: styles.cancelBtn, disabled: teamActionBusyUserId === member.userId, onClick: () => {
|
|
2097
|
-
void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}?workspaceId=${encodeURIComponent(
|
|
3090
|
+
void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}?workspaceId=${encodeURIComponent(teamManagementWorkspaceId)}`, {
|
|
2098
3091
|
method: 'DELETE',
|
|
2099
3092
|
headers: teamAdminHeaders(),
|
|
2100
3093
|
credentials: 'include'
|
|
2101
3094
|
}));
|
|
2102
|
-
}, children: "Remove" })] })] }, member.userId)))] })), teamManagementTab === 'invites' && (_jsxs("div", { className:
|
|
3095
|
+
}, children: "Remove" })] })] }, member.userId)))] })), teamManagementTab === 'invites' && (_jsxs("div", { className: accountStyles.accountHubCard, children: [teamInviteFeedback && (_jsx("p", { className: managementStyles.sectionText, children: teamInviteFeedback })), _jsxs("div", { className: styles.pathInputGroup, children: [_jsx("label", { className: `${styles.label} ${managementStyles.labelFixed}`, children: "Email" }), _jsx("input", { className: styles.input, value: teamInviteEmail, onChange: (event) => setTeamInviteEmail(event.target.value), placeholder: "name@company.com" })] }), _jsxs("div", { className: styles.pathInputGroup, children: [_jsx("label", { className: `${styles.label} ${managementStyles.labelFixed}`, children: "Role" }), _jsxs("select", { className: `${styles.input} ${managementStyles.selectRole}`, value: teamInviteRole, onChange: (event) => setTeamInviteRole(event.target.value === 'admin' ? 'admin' : 'member'), children: [_jsx("option", { value: "member", children: "Member" }), _jsx("option", { value: "admin", children: "Admin" })] }), teamInviteRole === 'member' && (_jsxs("select", { className: `${styles.input} ${managementStyles.selectPermission}`, value: teamInvitePermissionMode, onChange: (event) => setTeamInvitePermissionMode(event.target.value === 'read-only' ? 'read-only' : 'read-write'), children: [_jsx("option", { value: "read-write", children: "Read / Write" }), _jsx("option", { value: "read-only", children: "Read Only" })] })), _jsx("button", { className: styles.submitBtn, disabled: teamInviteBusy || !teamInviteEmail.trim() || teamPlanMode !== 'team', onClick: () => { void submitTeamInvite(); }, children: teamInviteBusy ? 'Sending...' : 'Send Invite' })] }), _jsxs("div", { className: managementStyles.inviteRow, children: [_jsx("div", { className: managementStyles.listHeading, children: "Pending Invites" }), pendingInvites.length === 0 && (_jsx("p", { className: managementStyles.mutedText, children: "No pending invites." })), pendingInvites.map((invite) => (_jsxs("div", { className: managementStyles.memberRow, children: [_jsx("div", { className: managementStyles.memberTitle, children: invite.displayName || invite.email }), _jsxs("div", { className: accountStyles.accountMenuMeta, children: [invite.email, " \u00B7 ", invite.role] })] }, invite.userId)))] })] })), teamManagementTab === 'audit' && (_jsxs("div", { className: accountStyles.accountHubCard, children: [teamAuditLoading && _jsx("p", { className: managementStyles.mutedText, children: "Loading audit entries..." }), !teamAuditLoading && teamAuditEvents.length === 0 && (_jsx("p", { className: managementStyles.mutedText, children: "No audit events for this workspace." })), !teamAuditLoading && (_jsx("div", { className: managementStyles.auditList, children: teamAuditEvents.map((event) => (_jsxs("div", { className: managementStyles.memberRow, children: [_jsx("div", { className: accountStyles.accountMenuMeta, children: new Date(event.createdAt).toLocaleString() }), _jsx("div", { className: managementStyles.memberTitle, children: event.action }), _jsxs("div", { className: accountStyles.accountMenuMeta, children: [event.actorUserId, " (", event.actorRole, ")"] })] }, event.id))) })), _jsxs("div", { className: managementStyles.auditPager, children: [_jsx("button", { className: styles.cancelBtn, disabled: teamAuditLoading || teamAuditPage === 0, onClick: () => { void loadTeamAuditPage(teamAuditPage - 1); }, children: "Previous" }), _jsxs("span", { className: accountStyles.accountMenuMeta, children: ["Page ", teamAuditPage + 1, " of ", teamAuditPages] }), _jsx("button", { className: styles.cancelBtn, disabled: teamAuditLoading || !teamAuditHasMore || teamAuditPage + 1 >= teamAuditPages, onClick: () => { void loadTeamAuditPage(teamAuditPage + 1); }, children: "Next" })] })] })), _jsx("div", { className: `${modalStyles.formActions} ${managementStyles.modalActionsEnd}`, children: _jsx("button", { className: styles.cancelBtn, onClick: () => setShowTeamManagementModal(false), children: "Close" }) })] }) }), _jsx(Modal, { isOpen: showCreateWorkspaceConfirm, onClose: () => setShowCreateWorkspaceConfirm(false), title: "Create Workspace", size: "sm", theme: currentTheme, draggable: true, footer: (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: styles.secondaryButton, onClick: () => setShowCreateWorkspaceConfirm(false), children: "Cancel" }), _jsx("button", { type: "button", className: styles.primaryUpdateBtn, onClick: () => openWorkspaceSetup({ intent: 'create-workspace' }), children: "Continue to Setup" })] })), children: _jsx("div", { className: `${modalStyles.form} ${managementStyles.modalBody}`, children: _jsx("p", { className: managementStyles.sectionText, children: "Start a new workspace with the guided setup flow. You will choose the workspace name, description, and starter taxonomy library before anything is created." }) }) }), _jsx(Modal, { isOpen: showSyncStatusModal, onClose: () => {
|
|
2103
3096
|
setShowSyncStatusModal(false);
|
|
2104
|
-
}, title: "
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
3097
|
+
}, title: "Sync Manager", size: "md", theme: currentTheme, draggable: true, children: _jsxs("div", { className: `${modalStyles.form} ${syncStatusStyles.syncStatusModal}`, style: { gap: '10px' }, children: [_jsxs("div", { className: syncStatusStyles.syncStatusTop, children: [_jsx("div", { className: syncStatusStyles.syncStatusHeaderInfo, children: _jsxs("div", { className: syncStatusStyles.syncStatusMetaHeader, children: [_jsx("div", { className: syncStatusStyles.syncStatusBadge, style: {
|
|
3098
|
+
borderColor: syncStatusMeta.border,
|
|
3099
|
+
background: syncStatusMeta.background,
|
|
3100
|
+
color: syncStatusMeta.color
|
|
3101
|
+
}, children: syncStatusMeta.label }), _jsxs("span", { className: syncStatusStyles.syncStatusLabel, children: ["Workspace: ", _jsx("code", { children: currentWorkspaceLabel })] })] }) }), _jsx("label", { className: syncStatusStyles.syncToggle, children: _jsxs("span", { className: [
|
|
3102
|
+
syncStatusStyles.syncToggleControl,
|
|
3103
|
+
workspaceCloudSyncEnabled ? syncStatusStyles.syncToggleControlEnabled : syncStatusStyles.syncToggleControlDisabled,
|
|
3104
|
+
syncControlBusy ? syncStatusStyles.syncToggleControlBusy : '',
|
|
3105
|
+
!canManageWorkspaceSync ? syncStatusStyles.syncToggleControlBlocked : ''
|
|
3106
|
+
].filter(Boolean).join(' '), children: [_jsx("input", { className: syncStatusStyles.syncToggleInput, type: "checkbox", role: "switch", "aria-label": "Enable Sync", checked: workspaceCloudSyncEnabled, disabled: !canManageWorkspaceSync || syncControlBusy, onChange: (event) => {
|
|
3107
|
+
void handleWorkspaceSyncToggle(event.target.checked);
|
|
3108
|
+
} }), _jsx("span", { className: `${syncStatusStyles.syncToggleState} ${syncStatusStyles.syncToggleStateOn}`, children: "On" }), _jsx("span", { className: `${syncStatusStyles.syncToggleState} ${syncStatusStyles.syncToggleStateOff}`, children: "Off" }), _jsx("span", { className: syncStatusStyles.syncToggleThumb })] }) })] }), _jsxs("div", { className: syncStatusStyles.syncStatusSummaryCompact, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Health" }), _jsx("span", { className: syncStatusStyles.syncStatusSummaryValue, children: workspaceSyncSummary })] }), workspaceSyncRepairBusy && (_jsxs("div", { className: syncStatusStyles.syncStatusRepairBanner, children: [_jsxs("div", { className: syncStatusStyles.syncStatusRepairHeader, children: [_jsxs("div", { className: syncStatusStyles.syncStatusRepairBadge, children: [_jsx(Loader2, { size: 13, className: styles.spinner }), _jsx("span", { children: "Repair In Progress" })] }), _jsx("span", { className: syncStatusStyles.syncStatusRepairMeta, children: "Advanced recovery mode" })] }), _jsx("span", { className: syncStatusStyles.syncStatusRepairText, children: referenceMismatchCount > 0
|
|
3109
|
+
? `Taskforce is clearing the saved sync cursor and reconciling workspace state from the cloud again, including ${referenceMismatchCount} detected reference mismatch${referenceMismatchCount === 1 ? '' : 'es'}. The panel may stay busy for a while during large repairs.`
|
|
3110
|
+
: 'Taskforce is clearing the saved sync cursor and reconciling workspace state from the cloud again. The panel may stay busy for a while during large repairs.' })] })), _jsx("div", { className: syncStatusStyles.syncStatusSectionHeader, children: _jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Current Session" }) }), _jsxs("div", { className: syncStatusStyles.syncStatusCompactGrid, children: [_jsxs("div", { className: syncStatusStyles.syncStatusCompactItem, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Stage" }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: syncStageLabel })] }), _jsxs("div", { className: syncStatusStyles.syncStatusCompactItem, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Local Changes" }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: workspaceSyncPendingChanges })] }), _jsxs("div", { className: syncStatusStyles.syncStatusCompactItem, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Last Successful" }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: formattedLastSyncTime })] })] }), _jsx("div", { className: syncStatusStyles.syncStatusSectionHeader, children: _jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Activity" }) }), _jsxs("div", { className: syncStatusStyles.syncStatusCompactGrid, children: [_jsxs("div", { className: syncStatusStyles.syncStatusCompactItem, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Last Pull" }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: formattedLastPullTime })] }), _jsxs("div", { className: syncStatusStyles.syncStatusCompactItem, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Last Push" }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: formattedLastPushTime })] })] }), syncLastError !== 'None' && (_jsxs("div", { className: syncStatusStyles.syncStatusPanel, style: { background: 'rgba(239, 68, 68, 0.05)', borderColor: 'rgba(239, 68, 68, 0.2)' }, children: [_jsxs("div", { className: syncStatusStyles.syncStatusSectionHeader, style: { marginTop: 0 }, children: [_jsx(AlertTriangle, { size: 12, color: "#fda4af" }), _jsxs("span", { className: syncStatusStyles.syncStatusLabel, style: { color: '#fda4af' }, children: ["Last Error (", workspaceSyncDiagnostics.lastErrorAt ? new Date(workspaceSyncDiagnostics.lastErrorAt).toLocaleTimeString() : 'Recent', ")"] })] }), _jsx("span", { className: `${syncStatusStyles.syncStatusValue} ${syncStatusStyles.syncStatusValueError}`, children: syncLastError })] })), referenceMismatchCount > 0 && (_jsxs("div", { className: syncStatusStyles.syncStatusPanel, style: { background: 'rgba(250, 204, 21, 0.08)', borderColor: 'rgba(250, 204, 21, 0.22)' }, children: [_jsxs("div", { className: syncStatusStyles.syncStatusPanelHeader, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, style: { color: '#fde68a' }, children: "Identifier Integrity" }), _jsxs("span", { className: syncStatusStyles.syncStatusPanelMeta, children: [referenceMismatchCount, " mismatch", referenceMismatchCount === 1 ? '' : 'es', " detected"] })] }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: "Document or image reference numbers disagreed during normal sync. Standard sync now preserves existing identifiers; use Repair to reconcile mismatched references deliberately." }), activeReferenceMismatchSummaries.length > 0 && (_jsx("div", { style: { marginTop: '10px', display: 'flex', flexDirection: 'column', gap: '8px' }, children: activeReferenceMismatchSummaries.map((item) => (_jsxs("div", { style: {
|
|
3111
|
+
display: 'flex',
|
|
3112
|
+
flexDirection: 'column',
|
|
3113
|
+
gap: '2px',
|
|
3114
|
+
padding: '8px 10px',
|
|
3115
|
+
borderRadius: '8px',
|
|
3116
|
+
background: 'rgba(15, 23, 42, 0.18)',
|
|
3117
|
+
border: '1px solid rgba(250, 204, 21, 0.16)'
|
|
3118
|
+
}, children: [_jsx("span", { className: syncStatusStyles.syncStatusValue, style: { fontSize: '12px', wordBreak: 'break-all' }, children: item.pathLabel }), item.refsLabel ? (_jsx("span", { className: syncStatusStyles.syncStatusPanelMeta, children: item.refsLabel })) : null] }, item.key))) }))] })), _jsxs("div", { className: syncStatusStyles.syncStatusPanel, children: [_jsxs("div", { className: syncStatusStyles.syncStatusPanelHeader, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Local Sync Diagnostics" }), _jsx("span", { className: syncStatusStyles.syncStatusPanelMeta, style: { fontSize: '10px' }, children: "AI profiles & metadata" })] }), _jsx("div", { className: syncStatusStyles.syncStatusCompactGrid, style: { gridTemplateColumns: 'repeat(2, 1fr)' }, children: syncDiagnosticsSummary.map((item) => (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: '8px' }, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, style: { fontSize: '9px', textTransform: 'capitalize' }, children: item.label }), _jsx("span", { className: syncStatusStyles.syncStatusValue, style: { fontSize: '11px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }, children: item.value })] }, item.label))) })] }), _jsxs("div", { className: syncStatusStyles.syncStatusPanel, children: [_jsx("div", { className: syncStatusStyles.syncStatusPanelHeader, children: _jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Recent Events" }) }), _jsx("div", { ref: syncEventsListRef, className: syncStatusStyles.syncStatusEventsList, style: { height: '120px' }, children: syncEventRows.map((row) => (_jsx("div", { className: [
|
|
3119
|
+
syncStatusStyles.syncStatusEventItem,
|
|
3120
|
+
row.tone === 'error' ? syncStatusStyles.syncStatusEventItemError : '',
|
|
3121
|
+
row.tone === 'muted' ? syncStatusStyles.syncStatusEventItemMuted : ''
|
|
3122
|
+
].filter(Boolean).join(' '), style: { padding: '6px 8px', borderRadius: '6px' }, children: _jsx("span", { className: syncStatusStyles.syncStatusEventLine, style: { fontSize: '11px' }, children: row.text }) }, row.key))) })] }), _jsxs("div", { className: `${modalStyles.formActions} ${syncStatusStyles.syncStatusActions}`, children: [_jsxs("div", { className: syncStatusStyles.syncStatusSecondaryActions, children: [_jsx("button", { className: styles.cancelBtn, onClick: handleQueueOrRunRepairSync, disabled: workspaceSyncRepairBusy, title: workspaceSyncRepairBusy
|
|
3123
|
+
? 'Repair is running now.'
|
|
3124
|
+
: workspaceSyncRepairQueued
|
|
3125
|
+
? 'Repair is queued and will start when the current sync finishes.'
|
|
3126
|
+
: workspaceSyncBusy
|
|
3127
|
+
? 'Queue a repair to start automatically when the current sync finishes.'
|
|
3128
|
+
: 'Advanced recovery: clear the saved pull cursor and re-fetch cloud state from the beginning.', children: workspaceSyncRepairBusy ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { size: 14, className: styles.spinner }), _jsx("span", { style: { marginLeft: '6px' }, children: "Repairing..." })] })) : workspaceSyncRepairQueued ? ('Repair Queued') : ('Repair') }), _jsxs("button", { className: styles.cancelBtn, onClick: () => { void handleCopySyncDetails(); }, title: "Copy the current sync manager report for support or AI troubleshooting.", children: [_jsx(Copy, { size: 14 }), _jsx("span", { style: { marginLeft: '6px' }, children: workspaceSyncCopied ? 'Copied' : 'Copy Report' })] })] }), _jsxs("div", { className: syncStatusStyles.syncStatusPrimaryActions, children: [_jsx("div", { className: syncStatusStyles.syncStatusPrimaryActionSlot, children: runtimeMode === 'local' && !isAuthenticated ? (_jsx("button", { className: styles.submitBtn, onClick: () => {
|
|
3129
|
+
openSharedLoginRoute('login');
|
|
3130
|
+
}, children: "Sign In / Register" })) : (_jsx("div", { className: syncStatusStyles.syncStatusActionPlaceholder, "aria-hidden": "true" })) }), _jsx("div", { className: syncStatusStyles.syncStatusPrimaryActionSlot, children: _jsx("button", { className: styles.submitBtn, onClick: () => void retryWorkspaceCloudSync(), disabled: workspaceSyncBusy, children: workspaceSyncBusy ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { size: 14, className: styles.spinner }), "Syncing..."] })) : (_jsxs(_Fragment, { children: [_jsx(RefreshCw, { size: 14 }), "Sync"] })) }) })] })] })] }) }), unsavedModalOpen && createPortal(_jsx("div", { className: `${modalStyles.overlay} ${modalStyles.unsavedOverlay}`, style: { zIndex: 2000 }, children: _jsxs("div", { className: `tf-surface-modal tf-modal-shell ${modalStyles.modal} ${modalStyles.unsavedModal}`, "data-theme": currentTheme, children: [_jsx("div", { className: `tf-modal-header ${modalStyles.unsavedHeader}`, children: _jsxs("div", { className: `tf-modal-title ${modalStyles.unsavedTitle}`, children: [_jsx(AlertTriangle, { size: 20 }), "Unsaved Changes"] }) }), _jsx("div", { className: `${modalStyles.form} ${modalStyles.unsavedContent}`, children: _jsx("p", { className: modalStyles.unsavedText, children: "You have unsaved changes. Would you like to save them?" }) }), _jsxs("div", { className: `${modalStyles.formActions} ${modalStyles.unsavedActions}`, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => setUnsavedModalOpen(false), title: "Close dialog and continue editing", children: "Keep Editing" }), _jsx("button", { className: styles.destructiveBtn, onClick: handleDiscard, title: "Discard unsaved changes and leave", children: "Discard" }), _jsxs("button", { className: styles.submitBtn, onClick: handleSaveAndNavigate, disabled: loading, title: "Save changes and leave", children: [loading ? _jsx(Loader2, { size: 16, className: styles.spinner }) : _jsx(Save, { size: 16 }), "Save"] })] })] }) }), document.body), showFolderBrowser && createPortal(_jsx("div", { className: folderBrowserStyles.overlay, children: _jsxs("div", { className: folderBrowserStyles.browser, children: [_jsxs("div", { className: folderBrowserStyles.header, children: [_jsxs("div", { className: folderBrowserStyles.pathInfo, children: [_jsx(Folder, { size: 14 }), _jsx("span", { children: currentBrowsePath || 'Project Root' })] }), _jsxs("div", { className: folderBrowserStyles.actions, children: [currentBrowsePath && (_jsx("button", { className: styles.helpLink, onClick: () => {
|
|
2113
3131
|
const parts = currentBrowsePath.split('/').filter(Boolean);
|
|
2114
3132
|
parts.pop();
|
|
2115
3133
|
fetchFolders(parts.length ? parts.join('/') + '/' : '');
|
|
2116
|
-
}, children: "Back" })), _jsx("button", { className: styles.helpLink, onClick: () => setShowFolderBrowser(false), children: "Close" })] })] }), _jsxs("div", { className:
|
|
3134
|
+
}, children: "Back" })), _jsx("button", { className: styles.helpLink, onClick: () => setShowFolderBrowser(false), children: "Close" })] })] }), _jsxs("div", { className: folderBrowserStyles.list, children: [browserTarget && typeof browserTarget === 'object' && (_jsxs("div", { className: `${folderBrowserStyles.item} ${folderBrowserStyles.itemFile} ${folderBrowserStyles.itemCurrent}`, onClick: () => handleSelectPath(currentBrowsePath), children: [_jsx(Check, { size: 14 }), " Select Current: ./", currentBrowsePath || '(root)'] })), folders.map(folder => (_jsxs("div", { className: folderBrowserStyles.item, onClick: () => fetchFolders(currentBrowsePath + folder + '/'), children: [_jsx(Folder, { size: 14 }), " ", folder, "/"] }, folder))), files.map(file => (_jsxs("div", { className: `${folderBrowserStyles.item} ${folderBrowserStyles.itemFile}`, onClick: () => handleSelectPath(currentBrowsePath + file), children: [_jsx(FileText, { size: 14 }), " ", file] }, file))), folders.length === 0 && files.length === 0 && (_jsx("div", { className: `${folderBrowserStyles.item} ${folderBrowserStyles.empty}`, children: "No items found" }))] })] }) }), document.body)] }));
|
|
2117
3135
|
}
|