@taskforcehq/taskforce 0.3.170 → 0.3.301
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 +71 -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 +2227 -1188
- 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 +68 -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 +47 -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 +243 -0
- package/dist/hooks/useTaskData.test.d.ts +1 -0
- package/dist/hooks/useTaskData.test.js +180 -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 +3299 -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-Bdq3T1Ts.js +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-jKUVo-PJ.js +3 -0
- package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-DuQa-uIC.js +250 -0
- package/dist/ui/assets/InitiativesModule-C8Pi2VTk.js +1 -0
- package/dist/ui/assets/PlansPage-B6BDECSi.js +1 -0
- package/dist/ui/assets/PlansPage-BlVC_lRq.css +1 -0
- package/dist/ui/assets/TaskSettings-BwB9NtmJ.css +1 -0
- package/dist/ui/assets/TaskSettings-DUJgRTPU.js +8 -0
- package/dist/ui/assets/WorkflowsModule-b6TLuHwJ.js +5 -0
- package/dist/ui/assets/index-BiiF1cLQ.css +1 -0
- package/dist/ui/assets/index-xBWLPOEb.js +6 -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 +30 -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/deploy-prod.sh +41 -0
- package/scripts/deploy-staging.sh +23 -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 +2 -2
- 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,490 @@ 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) => {
|
|
895
|
+
const referenceLabel = formatInitiativeReference(initiative);
|
|
896
|
+
return {
|
|
897
|
+
value: initiative.id,
|
|
898
|
+
label: referenceLabel ? `${referenceLabel} - ${initiative.title}` : initiative.title,
|
|
899
|
+
selectedLabel: referenceLabel || initiative.title,
|
|
900
|
+
};
|
|
901
|
+
}), [planningStructure.initiatives]);
|
|
902
|
+
const workstreamFilterOptions = useMemo(() => [
|
|
903
|
+
...planningStructure.initiatives.flatMap((initiative) => initiative.workstreams.map((workstream) => {
|
|
904
|
+
const referenceLabel = formatWorkstreamReference(workstream);
|
|
905
|
+
return {
|
|
906
|
+
value: workstream.id,
|
|
907
|
+
label: referenceLabel ? `${referenceLabel} - ${workstream.title}` : workstream.title,
|
|
908
|
+
selectedLabel: referenceLabel || workstream.title,
|
|
909
|
+
};
|
|
910
|
+
})),
|
|
911
|
+
...planningStructure.standaloneWorkstreams.map((workstream) => {
|
|
912
|
+
const referenceLabel = formatWorkstreamReference(workstream);
|
|
913
|
+
return {
|
|
914
|
+
value: workstream.id,
|
|
915
|
+
label: referenceLabel ? `${referenceLabel} - ${workstream.title}` : workstream.title,
|
|
916
|
+
selectedLabel: referenceLabel || workstream.title,
|
|
917
|
+
};
|
|
918
|
+
}),
|
|
919
|
+
], [planningStructure.initiatives, planningStructure.standaloneWorkstreams]);
|
|
920
|
+
const selectedPlanningTaskIds = useMemo(() => {
|
|
921
|
+
if (selectedPlanningWorkstreamId) {
|
|
922
|
+
return new Set(planningStructure.workstreamTaskIds.get(selectedPlanningWorkstreamId) || []);
|
|
923
|
+
}
|
|
924
|
+
if (selectedPlanningInitiativeId) {
|
|
925
|
+
return new Set(planningStructure.initiativeTaskIds.get(selectedPlanningInitiativeId) || []);
|
|
926
|
+
}
|
|
927
|
+
return null;
|
|
928
|
+
}, [planningStructure.initiativeTaskIds, planningStructure.workstreamTaskIds, selectedPlanningInitiativeId, selectedPlanningWorkstreamId]);
|
|
929
|
+
const planningDrawerItem = useMemo(() => {
|
|
930
|
+
if (!planningDrawerDetail)
|
|
421
931
|
return null;
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
if (
|
|
432
|
-
|
|
932
|
+
if (planningDrawerDetail.type === 'initiative') {
|
|
933
|
+
const item = planningStructure.initiativeById.get(planningDrawerDetail.id);
|
|
934
|
+
return item ? { type: 'initiative', item } : null;
|
|
935
|
+
}
|
|
936
|
+
const item = planningStructure.workstreamById.get(planningDrawerDetail.id)
|
|
937
|
+
|| planningStructure.standaloneWorkstreams.find((workstream) => workstream.id === planningDrawerDetail.id);
|
|
938
|
+
return item ? { type: 'workstream', item } : null;
|
|
939
|
+
}, [planningDrawerDetail, planningStructure.initiativeById, planningStructure.standaloneWorkstreams, planningStructure.workstreamById]);
|
|
940
|
+
const planningNestedWorkstreamItem = useMemo(() => {
|
|
941
|
+
if (!planningNestedWorkstreamDetailId)
|
|
942
|
+
return null;
|
|
943
|
+
const item = planningStructure.workstreamById.get(planningNestedWorkstreamDetailId)
|
|
944
|
+
|| planningStructure.standaloneWorkstreams.find((workstream) => workstream.id === planningNestedWorkstreamDetailId);
|
|
945
|
+
return item ? { type: 'workstream', item } : null;
|
|
946
|
+
}, [planningNestedWorkstreamDetailId, planningStructure.standaloneWorkstreams, planningStructure.workstreamById]);
|
|
947
|
+
const planningSecondaryPane = useMemo(() => {
|
|
948
|
+
if (planningSecondaryEditor) {
|
|
949
|
+
return { kind: 'editor', editor: planningSecondaryEditor };
|
|
950
|
+
}
|
|
951
|
+
if (planningNestedWorkstreamItem) {
|
|
952
|
+
return { kind: 'detail', detail: planningNestedWorkstreamItem };
|
|
953
|
+
}
|
|
954
|
+
return null;
|
|
955
|
+
}, [planningNestedWorkstreamItem, planningSecondaryEditor]);
|
|
956
|
+
const planningDrawerWidthPx = getPlanningDrawerWidthPx(planningTreeCollapsed, Boolean(planningDrawerItem || planningEditor), planningPrimaryCollapsed, Boolean(planningSecondaryPane), planningSecondaryCollapsed);
|
|
957
|
+
const planningDraftInitiativeSummary = useMemo(() => {
|
|
958
|
+
if (!planningDraftInitiativeId.trim())
|
|
959
|
+
return null;
|
|
960
|
+
const exactInitiative = planningStructure.initiativeById.get(planningDraftInitiativeId);
|
|
961
|
+
if (exactInitiative)
|
|
962
|
+
return exactInitiative;
|
|
963
|
+
return resolveInitiativeReference(planningStructure.initiatives, planningDraftInitiativeId);
|
|
964
|
+
}, [planningDraftInitiativeId, planningStructure.initiativeById, planningStructure.initiatives]);
|
|
965
|
+
useEffect(() => {
|
|
966
|
+
if (!planningDrawerOpen)
|
|
967
|
+
return;
|
|
968
|
+
setPlanningTreeCollapsed(false);
|
|
969
|
+
setPlanningPrimaryCollapsed(false);
|
|
970
|
+
setPlanningSecondaryCollapsed(false);
|
|
971
|
+
}, [planningDrawerOpen]);
|
|
972
|
+
useEffect(() => {
|
|
973
|
+
if (selectedPlanningInitiativeId && !planningStructure.initiativeById.has(selectedPlanningInitiativeId)) {
|
|
974
|
+
setSelectedPlanningInitiativeId('');
|
|
975
|
+
}
|
|
976
|
+
if (selectedPlanningWorkstreamId && !planningStructure.workstreamById.has(selectedPlanningWorkstreamId)) {
|
|
977
|
+
setSelectedPlanningWorkstreamId('');
|
|
433
978
|
}
|
|
434
|
-
|
|
435
|
-
|
|
979
|
+
if (planningDrawerDetail?.type === 'initiative' && !planningStructure.initiativeById.has(planningDrawerDetail.id)) {
|
|
980
|
+
setPlanningDrawerDetail(null);
|
|
436
981
|
}
|
|
437
|
-
|
|
438
|
-
|
|
982
|
+
if (planningDrawerDetail?.type === 'workstream' && !planningStructure.workstreamById.has(planningDrawerDetail.id)) {
|
|
983
|
+
setPlanningDrawerDetail(null);
|
|
439
984
|
}
|
|
440
|
-
if (
|
|
441
|
-
|
|
985
|
+
if (planningNestedWorkstreamDetailId && !planningStructure.workstreamById.has(planningNestedWorkstreamDetailId)) {
|
|
986
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
442
987
|
}
|
|
443
|
-
if (
|
|
444
|
-
|
|
445
|
-
|
|
988
|
+
if (planningSecondaryEditor?.mode === 'edit'
|
|
989
|
+
&& !planningStructure.workstreamById.has(planningSecondaryEditor.targetId)) {
|
|
990
|
+
setPlanningSecondaryEditor(null);
|
|
446
991
|
}
|
|
447
|
-
|
|
448
|
-
|
|
992
|
+
}, [
|
|
993
|
+
planningDrawerDetail,
|
|
994
|
+
planningNestedWorkstreamDetailId,
|
|
995
|
+
planningSecondaryEditor,
|
|
996
|
+
planningStructure.initiativeById,
|
|
997
|
+
planningStructure.workstreamById,
|
|
998
|
+
selectedPlanningInitiativeId,
|
|
999
|
+
selectedPlanningWorkstreamId,
|
|
1000
|
+
]);
|
|
1001
|
+
const hierarchyVisibleTasks = useMemo(() => {
|
|
1002
|
+
if (groupBy === 'schedule')
|
|
1003
|
+
return scheduleBaseTasks;
|
|
1004
|
+
return kanbanTasks;
|
|
1005
|
+
}, [groupBy, kanbanTasks, scheduleBaseTasks]);
|
|
449
1006
|
const todayDateOnly = useMemo(() => {
|
|
450
1007
|
const now = new Date();
|
|
451
1008
|
const y = now.getFullYear();
|
|
@@ -465,6 +1022,11 @@ export function StandaloneLayout(props) {
|
|
|
465
1022
|
return expiredScheduled || overdueDue;
|
|
466
1023
|
});
|
|
467
1024
|
}, [groupBy, scheduleOnlyExpired, hierarchyVisibleTasks, todayDateOnly]);
|
|
1025
|
+
const scopedBoardVisibleTasks = useMemo(() => {
|
|
1026
|
+
if (!selectedPlanningTaskIds || selectedPlanningTaskIds.size === 0)
|
|
1027
|
+
return boardVisibleTasks;
|
|
1028
|
+
return boardVisibleTasks.filter((task) => selectedPlanningTaskIds.has(task.id));
|
|
1029
|
+
}, [boardVisibleTasks, selectedPlanningTaskIds]);
|
|
468
1030
|
const expiredRecoveryCandidates = useMemo(() => {
|
|
469
1031
|
if (groupBy !== 'schedule')
|
|
470
1032
|
return [];
|
|
@@ -507,108 +1069,78 @@ export function StandaloneLayout(props) {
|
|
|
507
1069
|
return !!task.scheduledDate && task.scheduledDate < todayDateOnly;
|
|
508
1070
|
});
|
|
509
1071
|
}, [groupBy, hierarchyVisibleTasks, todayDateOnly]);
|
|
510
|
-
const expiredLeafCandidates =
|
|
1072
|
+
const expiredLeafCandidates = expiredRecoveryCandidates;
|
|
511
1073
|
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
1074
|
const handleCloseModal = () => {
|
|
535
1075
|
handleNavigation(() => {
|
|
536
|
-
if (
|
|
1076
|
+
if (returnToPreviousTask())
|
|
537
1077
|
return;
|
|
1078
|
+
if (activeTab === 'add' && taskReturnTrail.length > 0) {
|
|
1079
|
+
clearReturnToParentTask();
|
|
1080
|
+
}
|
|
1081
|
+
setTaskModalFullscreen(false);
|
|
538
1082
|
if (activeTab === 'add')
|
|
539
1083
|
resetForm();
|
|
540
1084
|
setActiveTab('tasks');
|
|
541
1085
|
});
|
|
542
1086
|
};
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
if (success) {
|
|
547
|
-
setLocalCancellationReason('');
|
|
1087
|
+
useEffect(() => {
|
|
1088
|
+
if (activeTab !== 'add') {
|
|
1089
|
+
setTaskModalFullscreen(false);
|
|
548
1090
|
}
|
|
549
|
-
};
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
setLocalCancellationReason('');
|
|
553
|
-
};
|
|
554
|
-
const handleDeleteFromCancellation = async () => {
|
|
555
|
-
if (!cancellationPromptTask)
|
|
1091
|
+
}, [activeTab]);
|
|
1092
|
+
const handleSetStatus = useCallback((task, status) => {
|
|
1093
|
+
if (task.status === status)
|
|
556
1094
|
return;
|
|
557
|
-
|
|
558
|
-
|
|
1095
|
+
if (status === 'in-progress') {
|
|
1096
|
+
void handleToggleInProgress(task);
|
|
559
1097
|
return;
|
|
560
|
-
const deleted = await handleDelete(cancellationPromptTask.id, false, { skipConfirm: true });
|
|
561
|
-
if (deleted) {
|
|
562
|
-
handleCancelCancellation();
|
|
563
|
-
}
|
|
564
|
-
};
|
|
565
|
-
const handleAddTaskToColumn = useCallback((groupField, value) => {
|
|
566
|
-
resetForm();
|
|
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
1098
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
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
|
-
}
|
|
1099
|
+
if (status === 'review') {
|
|
1100
|
+
void handleToggleReview(task);
|
|
1101
|
+
return;
|
|
600
1102
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
setComplexity(direct);
|
|
605
|
-
}
|
|
1103
|
+
if (status === 'done') {
|
|
1104
|
+
void handleToggleComplete(task);
|
|
1105
|
+
return;
|
|
606
1106
|
}
|
|
607
|
-
|
|
608
|
-
|
|
1107
|
+
if (status === 'cancelled') {
|
|
1108
|
+
void handleToggleCancel(task);
|
|
1109
|
+
return;
|
|
609
1110
|
}
|
|
1111
|
+
void handleUpdateTask(task.id, {
|
|
1112
|
+
status,
|
|
1113
|
+
completedAt: null
|
|
1114
|
+
});
|
|
1115
|
+
}, [handleToggleInProgress, handleToggleReview, handleToggleComplete, handleToggleCancel, handleUpdateTask]);
|
|
1116
|
+
const handleAddTaskToColumn = useCallback((groupField, value) => {
|
|
1117
|
+
resetForm();
|
|
1118
|
+
const defaults = resolveDraftDefaultsForGrouping(groupField, value, {
|
|
1119
|
+
categories: activeCategories,
|
|
1120
|
+
approaches
|
|
1121
|
+
});
|
|
1122
|
+
if (defaults.category)
|
|
1123
|
+
setCategory(defaults.category);
|
|
1124
|
+
if (defaults.type)
|
|
1125
|
+
setType(defaults.type);
|
|
1126
|
+
if (typeof defaults.priority === 'number')
|
|
1127
|
+
setPriority(defaults.priority);
|
|
1128
|
+
if (typeof defaults.complexity === 'number')
|
|
1129
|
+
setComplexity(defaults.complexity);
|
|
1130
|
+
if (defaults.approach)
|
|
1131
|
+
setApproach(defaults.approach);
|
|
1132
|
+
const taxonomyApproach = defaults.taxonomyApproach;
|
|
1133
|
+
if (typeof taxonomyApproach === 'string' && taxonomyApproach.length > 0) {
|
|
1134
|
+
setFormTaxonomies(prev => ({ ...prev, approach: taxonomyApproach }));
|
|
1135
|
+
}
|
|
1136
|
+
if (defaults.assignee)
|
|
1137
|
+
setAssignee(defaults.assignee);
|
|
1138
|
+
if (defaults.status)
|
|
1139
|
+
setStatus(defaults.status);
|
|
1140
|
+
if (defaults.scheduledDate)
|
|
1141
|
+
setScheduledDate(defaults.scheduledDate);
|
|
610
1142
|
setActiveTab('add');
|
|
611
|
-
}, [resetForm, activeCategories, approaches, setCategory, setType, setPriority, setComplexity, setApproach, setFormTaxonomies, setActiveTab]);
|
|
1143
|
+
}, [resetForm, activeCategories, approaches, setCategory, setType, setPriority, setComplexity, setStatus, setApproach, setAssignee, setScheduledDate, setFormTaxonomies, setActiveTab]);
|
|
612
1144
|
// Ensure 'done' status is shown by default as requested
|
|
613
1145
|
React.useEffect(() => {
|
|
614
1146
|
if (filterStatus && !filterStatus.includes('done')) {
|
|
@@ -619,14 +1151,21 @@ export function StandaloneLayout(props) {
|
|
|
619
1151
|
const [showHelpModal, setShowHelpModal] = useState(false);
|
|
620
1152
|
const [showAccountMenu, setShowAccountMenu] = useState(false);
|
|
621
1153
|
const [showAccountHub, setShowAccountHub] = useState(false);
|
|
1154
|
+
const [showEditProfileModal, setShowEditProfileModal] = useState(false);
|
|
622
1155
|
const [showSyncStatusModal, setShowSyncStatusModal] = useState(false);
|
|
623
|
-
const [
|
|
1156
|
+
const [workspaceSyncToggleBusy, setWorkspaceSyncToggleBusy] = useState(false);
|
|
624
1157
|
const [workspaceSyncError, setWorkspaceSyncError] = useState(null);
|
|
625
|
-
const [
|
|
626
|
-
const [
|
|
627
|
-
const [
|
|
1158
|
+
const [workspaceSyncRepairBusy, setWorkspaceSyncRepairBusy] = useState(false);
|
|
1159
|
+
const [workspaceSyncRepairQueued, setWorkspaceSyncRepairQueued] = useState(false);
|
|
1160
|
+
const [workspaceSyncCopied, setWorkspaceSyncCopied] = useState(false);
|
|
1161
|
+
const [syncRecentEvents, setSyncRecentEvents] = useState([]);
|
|
1162
|
+
const [syncRecentEventsLoading, setSyncRecentEventsLoading] = useState(false);
|
|
1163
|
+
const [syncRecentEventsError, setSyncRecentEventsError] = useState(null);
|
|
1164
|
+
const syncEventsListRef = useRef(null);
|
|
1165
|
+
const pendingSyncEventsScrollRestoreRef = useRef(null);
|
|
628
1166
|
const [workspaceActionBusy, setWorkspaceActionBusy] = useState(false);
|
|
629
1167
|
const [workspaceActionError, setWorkspaceActionError] = useState('');
|
|
1168
|
+
const [showCreateWorkspaceConfirm, setShowCreateWorkspaceConfirm] = useState(false);
|
|
630
1169
|
const [showTeamManagementModal, setShowTeamManagementModal] = useState(false);
|
|
631
1170
|
const [teamManagementTab, setTeamManagementTab] = useState('members');
|
|
632
1171
|
const [teamPlanMode, setTeamPlanMode] = useState('unknown');
|
|
@@ -651,9 +1190,12 @@ export function StandaloneLayout(props) {
|
|
|
651
1190
|
const [billingActionError, setBillingActionError] = useState(null);
|
|
652
1191
|
const [billingNotice, setBillingNotice] = useState(null);
|
|
653
1192
|
const [billingIntervalChoice, setBillingIntervalChoice] = useState('month');
|
|
1193
|
+
const [profileDisplayNameDraft, setProfileDisplayNameDraft] = useState('');
|
|
1194
|
+
const [profileSaveBusy, setProfileSaveBusy] = useState(false);
|
|
1195
|
+
const [profileSaveError, setProfileSaveError] = useState(null);
|
|
1196
|
+
const [profileSaveNotice, setProfileSaveNotice] = useState(null);
|
|
654
1197
|
const TEAM_AUDIT_PAGE_SIZE = 25;
|
|
655
1198
|
const accountMenuRef = useRef(null);
|
|
656
|
-
const billingSuccessPollRef = useRef(null);
|
|
657
1199
|
// showSearch state removed - search always visible in actions area
|
|
658
1200
|
useEffect(() => {
|
|
659
1201
|
if (!showAccountMenu)
|
|
@@ -666,45 +1208,119 @@ export function StandaloneLayout(props) {
|
|
|
666
1208
|
document.addEventListener('mousedown', handlePointerDown);
|
|
667
1209
|
return () => document.removeEventListener('mousedown', handlePointerDown);
|
|
668
1210
|
}, [showAccountMenu]);
|
|
669
|
-
useEffect(() => () => {
|
|
670
|
-
if (billingSuccessPollRef.current) {
|
|
671
|
-
window.clearInterval(billingSuccessPollRef.current);
|
|
672
|
-
billingSuccessPollRef.current = null;
|
|
673
|
-
}
|
|
674
|
-
}, []);
|
|
675
1211
|
const canManageCloudWorkspaces = runtimeMode === 'cloud' && workspaceSwitchingEnabled && isAuthenticated;
|
|
1212
|
+
const canAccessCloudTeamManagement = isAuthenticated && (runtimeMode === 'cloud' || cloudAuthConfigured);
|
|
676
1213
|
const canManageWorkspaceSync = runtimeMode === 'local' && isAuthenticated;
|
|
677
|
-
const
|
|
1214
|
+
const handleWorkspaceSyncToggle = useCallback(async (enabled) => {
|
|
1215
|
+
if (!canManageWorkspaceSync)
|
|
1216
|
+
return;
|
|
1217
|
+
setWorkspaceSyncError(null);
|
|
1218
|
+
setWorkspaceSyncToggleBusy(true);
|
|
1219
|
+
const result = await saveWorkspaceCloudSyncSettings({ enabled });
|
|
1220
|
+
if (!result.success) {
|
|
1221
|
+
setWorkspaceSyncError(result.error || (enabled ? 'Failed to enable sync.' : 'Failed to disable sync.'));
|
|
1222
|
+
}
|
|
1223
|
+
setWorkspaceSyncToggleBusy(false);
|
|
1224
|
+
}, [
|
|
1225
|
+
canManageWorkspaceSync,
|
|
1226
|
+
saveWorkspaceCloudSyncSettings
|
|
1227
|
+
]);
|
|
1228
|
+
const syncControlBusy = workspaceSyncToggleBusy;
|
|
1229
|
+
const currentWorkspaceLabel = useMemo(() => {
|
|
678
1230
|
const current = availableWorkspaces.find((workspace) => workspace.id === currentWorkspaceId);
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
if (
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
1231
|
+
const namedWorkspace = String(current?.name || '').trim();
|
|
1232
|
+
if (namedWorkspace)
|
|
1233
|
+
return namedWorkspace;
|
|
1234
|
+
if (runtimeMode === 'local') {
|
|
1235
|
+
const localProjectName = String(projectName || '').trim();
|
|
1236
|
+
if (localProjectName)
|
|
1237
|
+
return localProjectName;
|
|
1238
|
+
}
|
|
1239
|
+
const fallbackId = String(currentWorkspaceId || '').trim();
|
|
1240
|
+
return fallbackId || 'Workspace';
|
|
1241
|
+
}, [availableWorkspaces, currentWorkspaceId, projectName, runtimeMode]);
|
|
1242
|
+
const canManageTeamWorkspace = computeCanManageTeamWorkspace(canAccessCloudTeamManagement, currentWorkspaceRole);
|
|
1243
|
+
const canOpenTeamManagement = computeCanOpenTeamManagement(canAccessCloudTeamManagement, currentWorkspaceRole, teamPlanMode);
|
|
1244
|
+
const authDisplayName = String(authUserDisplayName || '').trim();
|
|
1245
|
+
const authEmailLabel = String(authUserEmail || '').trim();
|
|
1246
|
+
const authIdentityLabel = authDisplayName || authEmailLabel;
|
|
690
1247
|
const hasAuthIdentity = authIdentityLabel.length > 0;
|
|
1248
|
+
const hasAuthEmail = authEmailLabel.length > 0;
|
|
1249
|
+
const accountBadgeLabel = (authIdentityLabel || authEmailLabel || 'U').trim();
|
|
1250
|
+
const accountBadgeInitial = accountBadgeLabel.charAt(0).toUpperCase() || 'U';
|
|
1251
|
+
const subscriptionLabel = String(billingStatus?.planName || billingStatus?.planId || '').trim();
|
|
1252
|
+
const hasSubscription = subscriptionLabel.length > 0;
|
|
1253
|
+
const teamManagementWorkspaceId = String(authWorkspaceId || currentWorkspaceId || '').trim();
|
|
1254
|
+
const runtimeLabel = runtimeMode === 'cloud' ? 'CLOUD' : 'LOCAL';
|
|
1255
|
+
const apiBaseUrl = props.config?.apiBaseUrl || '';
|
|
1256
|
+
const billingApiBaseUrl = useMemo(() => {
|
|
1257
|
+
const preferredBase = runtimeMode === 'local'
|
|
1258
|
+
? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
|
|
1259
|
+
: (props.config?.apiBaseUrl || props.config?.cloudAuthBaseUrl || '');
|
|
1260
|
+
return String(preferredBase || '').trim().replace(/\/+$/, '');
|
|
1261
|
+
}, [props.config?.apiBaseUrl, props.config?.cloudAuthBaseUrl, runtimeMode]);
|
|
1262
|
+
const buildBillingApiUrl = useCallback((path) => {
|
|
1263
|
+
const normalizedPath = String(path || '').trim();
|
|
1264
|
+
if (!billingApiBaseUrl)
|
|
1265
|
+
return normalizedPath;
|
|
1266
|
+
return `${billingApiBaseUrl}${normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`}`;
|
|
1267
|
+
}, [billingApiBaseUrl]);
|
|
1268
|
+
const connectedEnvironmentLabel = useMemo(() => resolveConnectedEnvironmentLabel(props.config?.cloudEnvironment, [
|
|
1269
|
+
String(props.config?.cloudBaseUrl || ''),
|
|
1270
|
+
String(props.config?.cloudAuthBaseUrl || ''),
|
|
1271
|
+
String(props.config?.apiBaseUrl || ''),
|
|
1272
|
+
String(props.config?.baseUrl || '')
|
|
1273
|
+
]), [
|
|
1274
|
+
props.config?.cloudEnvironment,
|
|
1275
|
+
props.config?.cloudBaseUrl,
|
|
1276
|
+
props.config?.cloudAuthBaseUrl,
|
|
1277
|
+
props.config?.apiBaseUrl,
|
|
1278
|
+
props.config?.baseUrl
|
|
1279
|
+
]);
|
|
691
1280
|
const isAuthBootstrapPending = cloudAuthConfigured && !authSessionResolved;
|
|
1281
|
+
useEffect(() => {
|
|
1282
|
+
if (!showEditProfileModal)
|
|
1283
|
+
return;
|
|
1284
|
+
setProfileDisplayNameDraft(authDisplayName);
|
|
1285
|
+
setProfileSaveError(null);
|
|
1286
|
+
setProfileSaveNotice(null);
|
|
1287
|
+
}, [authDisplayName, showEditProfileModal]);
|
|
1288
|
+
const handleSaveProfile = useCallback(async () => {
|
|
1289
|
+
const trimmedDisplayName = profileDisplayNameDraft.trim();
|
|
1290
|
+
if (!trimmedDisplayName) {
|
|
1291
|
+
setProfileSaveError('Display name is required.');
|
|
1292
|
+
setProfileSaveNotice(null);
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
setProfileSaveBusy(true);
|
|
1296
|
+
setProfileSaveError(null);
|
|
1297
|
+
setProfileSaveNotice(null);
|
|
1298
|
+
const result = await updateCurrentUserProfile({ displayName: trimmedDisplayName });
|
|
1299
|
+
if (!result.success) {
|
|
1300
|
+
setProfileSaveError(result.error || 'Failed to update profile.');
|
|
1301
|
+
setProfileSaveBusy(false);
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
setProfileSaveNotice('Profile updated.');
|
|
1305
|
+
setProfileSaveBusy(false);
|
|
1306
|
+
setShowEditProfileModal(false);
|
|
1307
|
+
}, [profileDisplayNameDraft, updateCurrentUserProfile]);
|
|
692
1308
|
const teamAdminHeaders = useCallback(() => {
|
|
693
1309
|
const headers = {};
|
|
694
|
-
const workspaceId = String(
|
|
1310
|
+
const workspaceId = String(teamManagementWorkspaceId || '').trim();
|
|
695
1311
|
if (workspaceId)
|
|
696
1312
|
headers['x-taskforce-workspace-id'] = workspaceId;
|
|
697
1313
|
return headers;
|
|
698
|
-
}, [
|
|
1314
|
+
}, [teamManagementWorkspaceId]);
|
|
699
1315
|
const loadBillingStatus = useCallback(async () => {
|
|
700
|
-
if (
|
|
1316
|
+
if (!isAuthenticated) {
|
|
701
1317
|
setBillingStatus(null);
|
|
702
1318
|
return null;
|
|
703
1319
|
}
|
|
704
1320
|
setBillingLoading(true);
|
|
705
1321
|
setBillingError(null);
|
|
706
1322
|
try {
|
|
707
|
-
const res = await fetch('/api/taskforce/billing/status', {
|
|
1323
|
+
const res = await fetch(buildBillingApiUrl('/api/taskforce/billing/status'), {
|
|
708
1324
|
method: 'GET',
|
|
709
1325
|
credentials: 'include'
|
|
710
1326
|
});
|
|
@@ -725,46 +1341,19 @@ export function StandaloneLayout(props) {
|
|
|
725
1341
|
finally {
|
|
726
1342
|
setBillingLoading(false);
|
|
727
1343
|
}
|
|
728
|
-
}, [
|
|
1344
|
+
}, [buildBillingApiUrl, isAuthenticated]);
|
|
729
1345
|
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]);
|
|
1346
|
+
const params = new URLSearchParams();
|
|
1347
|
+
params.set('screen', 'plans');
|
|
1348
|
+
params.set('interval', billingIntervalChoice);
|
|
1349
|
+
navigate(`/?${params.toString()}`);
|
|
1350
|
+
}, [billingIntervalChoice, navigate]);
|
|
762
1351
|
const handleOpenBillingPortal = useCallback(async () => {
|
|
763
1352
|
setBillingActionError(null);
|
|
764
1353
|
setBillingNotice(null);
|
|
765
1354
|
setBillingActionBusy(true);
|
|
766
1355
|
try {
|
|
767
|
-
const res = await fetch('/api/taskforce/billing/portal-session', {
|
|
1356
|
+
const res = await fetch(buildBillingApiUrl('/api/taskforce/billing/portal-session'), {
|
|
768
1357
|
method: 'POST',
|
|
769
1358
|
credentials: 'include'
|
|
770
1359
|
});
|
|
@@ -786,18 +1375,23 @@ export function StandaloneLayout(props) {
|
|
|
786
1375
|
finally {
|
|
787
1376
|
setBillingActionBusy(false);
|
|
788
1377
|
}
|
|
789
|
-
}, []);
|
|
1378
|
+
}, [buildBillingApiUrl]);
|
|
790
1379
|
const handleSwitchSubscriptionInterval = useCallback(async () => {
|
|
791
1380
|
setBillingActionError(null);
|
|
792
1381
|
setBillingNotice(null);
|
|
793
1382
|
setBillingActionBusy(true);
|
|
794
1383
|
try {
|
|
795
|
-
const
|
|
1384
|
+
const currentPlanVersionId = String(billingStatus?.planVersionId || '').trim();
|
|
1385
|
+
if (!currentPlanVersionId) {
|
|
1386
|
+
setBillingActionError('No active plan version is linked to this account.');
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
const res = await fetch(buildBillingApiUrl('/api/taskforce/billing/subscription'), {
|
|
796
1390
|
method: 'POST',
|
|
797
1391
|
credentials: 'include',
|
|
798
1392
|
headers: { 'Content-Type': 'application/json' },
|
|
799
1393
|
body: JSON.stringify({
|
|
800
|
-
|
|
1394
|
+
planVersionId: currentPlanVersionId,
|
|
801
1395
|
interval: billingIntervalChoice
|
|
802
1396
|
})
|
|
803
1397
|
});
|
|
@@ -815,11 +1409,11 @@ export function StandaloneLayout(props) {
|
|
|
815
1409
|
finally {
|
|
816
1410
|
setBillingActionBusy(false);
|
|
817
1411
|
}
|
|
818
|
-
}, [billingIntervalChoice, loadBillingStatus]);
|
|
1412
|
+
}, [billingIntervalChoice, billingStatus?.planVersionId, buildBillingApiUrl, loadBillingStatus]);
|
|
819
1413
|
const loadTeamPlanMode = useCallback(async () => {
|
|
820
|
-
if (!
|
|
1414
|
+
if (!canManageTeamWorkspace) {
|
|
821
1415
|
setTeamPlanMode('unknown');
|
|
822
|
-
return;
|
|
1416
|
+
return 'unknown';
|
|
823
1417
|
}
|
|
824
1418
|
try {
|
|
825
1419
|
const res = await fetch('/api/taskforce/account/entitlements', {
|
|
@@ -830,80 +1424,38 @@ export function StandaloneLayout(props) {
|
|
|
830
1424
|
const payload = await res.json().catch(() => ({}));
|
|
831
1425
|
if (!res.ok) {
|
|
832
1426
|
setTeamPlanMode('unknown');
|
|
833
|
-
return;
|
|
1427
|
+
return 'unknown';
|
|
834
1428
|
}
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
const stateAllows = state === 'active' || state === 'trialing' || state === 'grace';
|
|
839
|
-
setTeamPlanMode(planId === 'team' && teamAllowed && stateAllows ? 'team' : 'personal');
|
|
1429
|
+
const nextMode = resolveTeamPlanModeFromEntitlements(payload);
|
|
1430
|
+
setTeamPlanMode(nextMode);
|
|
1431
|
+
return nextMode;
|
|
840
1432
|
}
|
|
841
1433
|
catch {
|
|
842
1434
|
setTeamPlanMode('unknown');
|
|
1435
|
+
return 'unknown';
|
|
843
1436
|
}
|
|
844
|
-
}, [
|
|
1437
|
+
}, [canManageTeamWorkspace, teamAdminHeaders]);
|
|
845
1438
|
useEffect(() => {
|
|
846
1439
|
if (!showAccountMenu && !showTeamManagementModal && !showAccountHub)
|
|
847
1440
|
return;
|
|
848
1441
|
void loadTeamPlanMode();
|
|
849
1442
|
}, [showAccountHub, showAccountMenu, showTeamManagementModal, loadTeamPlanMode]);
|
|
850
1443
|
useEffect(() => {
|
|
851
|
-
if (!
|
|
852
|
-
return;
|
|
853
|
-
void loadBillingStatus();
|
|
854
|
-
}, [loadBillingStatus, showAccountHub]);
|
|
855
|
-
useEffect(() => {
|
|
856
|
-
if (!isAuthenticated)
|
|
857
|
-
return;
|
|
858
|
-
if (location.pathname !== '/billing/success' && location.pathname !== '/billing/cancel')
|
|
1444
|
+
if (!isAuthenticated || !authSessionResolved)
|
|
859
1445
|
return;
|
|
860
|
-
|
|
861
|
-
|
|
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 });
|
|
1446
|
+
if (!canManageTeamWorkspace) {
|
|
1447
|
+
setTeamPlanMode('unknown');
|
|
867
1448
|
return;
|
|
868
1449
|
}
|
|
869
|
-
|
|
1450
|
+
void loadTeamPlanMode();
|
|
1451
|
+
}, [authSessionResolved, canManageTeamWorkspace, isAuthenticated, loadTeamPlanMode]);
|
|
1452
|
+
useEffect(() => {
|
|
1453
|
+
if (!showAccountHub && !showAccountMenu)
|
|
1454
|
+
return;
|
|
870
1455
|
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]);
|
|
1456
|
+
}, [loadBillingStatus, showAccountHub, showAccountMenu]);
|
|
905
1457
|
const loadTeamUsers = useCallback(async () => {
|
|
906
|
-
if (!
|
|
1458
|
+
if (!canManageTeamWorkspace)
|
|
907
1459
|
return;
|
|
908
1460
|
setTeamUsersLoading(true);
|
|
909
1461
|
setTeamMgmtError(null);
|
|
@@ -945,16 +1497,16 @@ export function StandaloneLayout(props) {
|
|
|
945
1497
|
finally {
|
|
946
1498
|
setTeamUsersLoading(false);
|
|
947
1499
|
}
|
|
948
|
-
}, [
|
|
1500
|
+
}, [canManageTeamWorkspace, teamAdminHeaders]);
|
|
949
1501
|
const loadTeamAuditPage = useCallback(async (page) => {
|
|
950
|
-
if (!
|
|
1502
|
+
if (!canManageTeamWorkspace)
|
|
951
1503
|
return;
|
|
952
1504
|
const safePage = Math.max(0, Math.floor(page));
|
|
953
1505
|
setTeamAuditLoading(true);
|
|
954
1506
|
setTeamMgmtError(null);
|
|
955
1507
|
try {
|
|
956
1508
|
const offset = safePage * TEAM_AUDIT_PAGE_SIZE;
|
|
957
|
-
const res = await fetch(`/api/taskforce/admin/audit-logs?limit=${TEAM_AUDIT_PAGE_SIZE}&offset=${offset}`, {
|
|
1509
|
+
const res = await fetch(`/api/taskforce/admin/workspace-audit-logs?limit=${TEAM_AUDIT_PAGE_SIZE}&offset=${offset}`, {
|
|
958
1510
|
method: 'GET',
|
|
959
1511
|
credentials: 'include',
|
|
960
1512
|
headers: teamAdminHeaders()
|
|
@@ -983,7 +1535,7 @@ export function StandaloneLayout(props) {
|
|
|
983
1535
|
finally {
|
|
984
1536
|
setTeamAuditLoading(false);
|
|
985
1537
|
}
|
|
986
|
-
}, [
|
|
1538
|
+
}, [canManageTeamWorkspace, teamAdminHeaders]);
|
|
987
1539
|
const reloadTeamManagementData = useCallback(async () => {
|
|
988
1540
|
await Promise.all([loadTeamUsers(), loadTeamAuditPage(teamAuditPage)]);
|
|
989
1541
|
}, [loadTeamUsers, loadTeamAuditPage, teamAuditPage]);
|
|
@@ -995,10 +1547,14 @@ export function StandaloneLayout(props) {
|
|
|
995
1547
|
setShowSyncStatusModal(false);
|
|
996
1548
|
navigate(`/login?mode=${mode}&next=${encodeURIComponent(target)}`);
|
|
997
1549
|
}, [location.hash, location.pathname, location.search, navigate]);
|
|
998
|
-
const openWorkspaceSetup = useCallback(() => {
|
|
1550
|
+
const openWorkspaceSetup = useCallback((options) => {
|
|
999
1551
|
setShowAccountMenu(false);
|
|
1000
|
-
|
|
1001
|
-
|
|
1552
|
+
setShowCreateWorkspaceConfirm(false);
|
|
1553
|
+
const params = new URLSearchParams({ step: 'workspace' });
|
|
1554
|
+
if (options?.intent === 'create-workspace') {
|
|
1555
|
+
params.set('intent', 'create-workspace');
|
|
1556
|
+
}
|
|
1557
|
+
navigate(`/setup?${params.toString()}`, { replace: true });
|
|
1002
1558
|
}, [navigate]);
|
|
1003
1559
|
const handleSwitchWorkspace = useCallback(async (workspaceId) => {
|
|
1004
1560
|
if (!workspaceId || workspaceActionBusy)
|
|
@@ -1017,47 +1573,18 @@ export function StandaloneLayout(props) {
|
|
|
1017
1573
|
}
|
|
1018
1574
|
await fetchTasks(true);
|
|
1019
1575
|
setShowAccountMenu(false);
|
|
1020
|
-
setWorkspacePanelOpen(false);
|
|
1021
1576
|
}
|
|
1022
1577
|
finally {
|
|
1023
1578
|
setWorkspaceActionBusy(false);
|
|
1024
1579
|
}
|
|
1025
1580
|
}, [switchWorkspace, workspaceActionBusy, fetchTasks, openWorkspaceSetup]);
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1581
|
+
const openTeamManagementModal = useCallback(async () => {
|
|
1582
|
+
setShowAccountMenu(false);
|
|
1583
|
+
setShowTeamManagementModal(true);
|
|
1584
|
+
setTeamManagementTab('members');
|
|
1585
|
+
const resolvedMode = await loadTeamPlanMode();
|
|
1586
|
+
if (resolvedMode !== 'team')
|
|
1032
1587
|
return;
|
|
1033
|
-
}
|
|
1034
|
-
setWorkspaceActionError('');
|
|
1035
|
-
setWorkspaceActionBusy(true);
|
|
1036
|
-
try {
|
|
1037
|
-
const result = await createWorkspace(name, workspaceFormDescription.trim() || undefined);
|
|
1038
|
-
if (!result.success) {
|
|
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
1588
|
await loadTeamUsers();
|
|
1062
1589
|
await loadTeamAuditPage(0);
|
|
1063
1590
|
}, [loadTeamAuditPage, loadTeamPlanMode, loadTeamUsers]);
|
|
@@ -1096,7 +1623,7 @@ export function StandaloneLayout(props) {
|
|
|
1096
1623
|
},
|
|
1097
1624
|
credentials: 'include',
|
|
1098
1625
|
body: JSON.stringify({
|
|
1099
|
-
workspaceId:
|
|
1626
|
+
workspaceId: teamManagementWorkspaceId,
|
|
1100
1627
|
email,
|
|
1101
1628
|
role: teamInviteRole,
|
|
1102
1629
|
permissionMode: teamInviteRole === 'member' ? teamInvitePermissionMode : 'read-write'
|
|
@@ -1138,27 +1665,64 @@ export function StandaloneLayout(props) {
|
|
|
1138
1665
|
const hasTypeFilter = filterTypes.length !== activeTypes.length;
|
|
1139
1666
|
const hasPriorityFilter = filterPriorities.length !== priorities.length;
|
|
1140
1667
|
const hasStatusFilter = filterStatus.length !== STATUS_OPTIONS.length;
|
|
1141
|
-
const hasAssigneeFilter =
|
|
1142
|
-
const
|
|
1143
|
-
|
|
1144
|
-
|
|
1668
|
+
const hasAssigneeFilter = filterAssignees.length !== assigneeOptions.length;
|
|
1669
|
+
const hasInitiativeScopeFilter = selectedPlanningInitiativeId.length > 0;
|
|
1670
|
+
const hasWorkstreamScopeFilter = selectedPlanningWorkstreamId.length > 0;
|
|
1671
|
+
return hasCategoryFilter
|
|
1672
|
+
|| hasTypeFilter
|
|
1673
|
+
|| hasPriorityFilter
|
|
1674
|
+
|| hasStatusFilter
|
|
1675
|
+
|| hasAssigneeFilter
|
|
1676
|
+
|| hasInitiativeScopeFilter
|
|
1677
|
+
|| hasWorkstreamScopeFilter;
|
|
1678
|
+
}, [
|
|
1679
|
+
activeCategories.length,
|
|
1680
|
+
activeTypes.length,
|
|
1681
|
+
assigneeOptions.length,
|
|
1682
|
+
filterAssignees.length,
|
|
1683
|
+
filterCategories.length,
|
|
1684
|
+
filterPriorities.length,
|
|
1685
|
+
filterStatus.length,
|
|
1686
|
+
filterTypes.length,
|
|
1687
|
+
priorities.length,
|
|
1688
|
+
selectedPlanningInitiativeId,
|
|
1689
|
+
selectedPlanningWorkstreamId,
|
|
1690
|
+
]);
|
|
1691
|
+
const hasDocumentDropdownFilters = useMemo(() => (documentTypeFilters.length !== DOCUMENT_TYPE_FILTER_OPTIONS.length
|
|
1692
|
+
|| documentAttachmentFilters.length !== DOCUMENT_ATTACHMENT_FILTER_OPTIONS.length), [documentAttachmentFilters.length, documentTypeFilters.length]);
|
|
1693
|
+
const workspaceChromeMode = resolvedWorkspaceModule;
|
|
1694
|
+
const workspaceChromeDefinition = useMemo(() => getWorkspaceChromeDefinition(workspaceChromeMode), [workspaceChromeMode]);
|
|
1695
|
+
const hasActiveWorkspaceFilterControls = useMemo(() => {
|
|
1696
|
+
switch (workspaceChromeDefinition.filterBar.kind) {
|
|
1697
|
+
case 'task-filters':
|
|
1698
|
+
return hasDropdownFilters;
|
|
1699
|
+
case 'document-filters':
|
|
1700
|
+
return hasDocumentDropdownFilters;
|
|
1701
|
+
default:
|
|
1702
|
+
return false;
|
|
1703
|
+
}
|
|
1704
|
+
}, [
|
|
1705
|
+
hasDocumentDropdownFilters,
|
|
1706
|
+
hasDropdownFilters,
|
|
1707
|
+
workspaceChromeDefinition.filterBar.kind,
|
|
1708
|
+
]);
|
|
1145
1709
|
// Check if any filters are set (search + dropdown filters)
|
|
1146
1710
|
const isFiltered = hasSearchFilter || hasDropdownFilters;
|
|
1147
|
-
const totalActiveCount =
|
|
1148
|
-
|
|
1711
|
+
const totalActiveCount = taskScope === 'archived'
|
|
1712
|
+
? archivedScopeAllTasks.length
|
|
1713
|
+
: taskScope === 'deleted'
|
|
1714
|
+
? deletedScopeAllTasks.length
|
|
1715
|
+
: tasks.length;
|
|
1716
|
+
const visibleActiveCount = taskScope === 'archived'
|
|
1717
|
+
? archivedScopeTasks.length
|
|
1718
|
+
: taskScope === 'deleted'
|
|
1719
|
+
? filteredDeletedTasks.length
|
|
1720
|
+
: props.filteredTasks.length;
|
|
1149
1721
|
const activeCountLabel = isFiltered ? `${visibleActiveCount}/${totalActiveCount}` : `${totalActiveCount}`;
|
|
1150
1722
|
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]);
|
|
1723
|
+
? `${taskScope.charAt(0).toUpperCase() + taskScope.slice(1)} tasks matching current filters`
|
|
1724
|
+
: `${taskScope.charAt(0).toUpperCase() + taskScope.slice(1)} tasks`;
|
|
1725
|
+
const shouldShowSyncStatus = runtimeMode === 'local' && isAuthenticated;
|
|
1162
1726
|
const formatSyncTimestamp = useCallback((value) => {
|
|
1163
1727
|
if (!value)
|
|
1164
1728
|
return 'Never';
|
|
@@ -1167,259 +1731,379 @@ export function StandaloneLayout(props) {
|
|
|
1167
1731
|
return 'Never';
|
|
1168
1732
|
return parsed.toLocaleString();
|
|
1169
1733
|
}, []);
|
|
1170
|
-
const
|
|
1171
|
-
const
|
|
1172
|
-
const
|
|
1173
|
-
const
|
|
1174
|
-
const
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1734
|
+
const formattedLastSyncTime = useMemo(() => formatSyncTimestamp(workspaceLastSuccessfulSyncAt), [formatSyncTimestamp, workspaceLastSuccessfulSyncAt]);
|
|
1735
|
+
const formattedLastPullTime = useMemo(() => formatSyncTimestamp(workspaceLastPullAt), [formatSyncTimestamp, workspaceLastPullAt]);
|
|
1736
|
+
const formattedLastPushTime = useMemo(() => formatSyncTimestamp(workspaceLastPushAt), [formatSyncTimestamp, workspaceLastPushAt]);
|
|
1737
|
+
const rawSyncLastError = workspaceSyncError || workspaceLastErrorMessage || userGlobalSyncError || 'None';
|
|
1738
|
+
const headerSyncPresentation = useMemo(() => resolveHeaderWorkspaceSyncPresentation({
|
|
1739
|
+
runtimeMode,
|
|
1740
|
+
isAuthenticated,
|
|
1741
|
+
workspaceSyncStatus,
|
|
1742
|
+
workspaceCloudSyncEnabled,
|
|
1743
|
+
workspaceSyncSummary,
|
|
1744
|
+
workspaceSyncRecommendedAction,
|
|
1745
|
+
workspaceSyncError: rawSyncLastError
|
|
1746
|
+
}), [
|
|
1747
|
+
runtimeMode,
|
|
1748
|
+
isAuthenticated,
|
|
1749
|
+
workspaceSyncStatus,
|
|
1750
|
+
workspaceCloudSyncEnabled,
|
|
1751
|
+
workspaceSyncSummary,
|
|
1752
|
+
workspaceSyncRecommendedAction,
|
|
1753
|
+
rawSyncLastError
|
|
1754
|
+
]);
|
|
1755
|
+
const syncLastError = headerSyncPresentation.lastError;
|
|
1756
|
+
const [headerSyncStatus, setHeaderSyncStatus] = useState(headerSyncPresentation.status);
|
|
1757
|
+
const syncStageLabel = useMemo(() => {
|
|
1758
|
+
if (workspaceSyncRepairBusy) {
|
|
1759
|
+
return 'Repairing';
|
|
1760
|
+
}
|
|
1761
|
+
if (workspaceSyncBusy && workspaceSyncPhase === 'error') {
|
|
1762
|
+
return 'Recovering';
|
|
1763
|
+
}
|
|
1764
|
+
switch (workspaceSyncPhase) {
|
|
1765
|
+
case 'provision-local':
|
|
1766
|
+
return 'Initial cloud upload';
|
|
1767
|
+
case 'attach-cloud':
|
|
1768
|
+
return 'Initial cloud pull';
|
|
1769
|
+
case 'active':
|
|
1770
|
+
return 'Active';
|
|
1771
|
+
case 'error':
|
|
1772
|
+
return 'Error';
|
|
1773
|
+
default:
|
|
1774
|
+
return 'Idle';
|
|
1775
|
+
}
|
|
1776
|
+
}, [workspaceSyncPhase, workspaceSyncRepairBusy]);
|
|
1777
|
+
const workspaceSyncDiagnostics = getWorkspaceSyncDiagnostics();
|
|
1778
|
+
const syncDiagnosticsSummary = useMemo(() => ([
|
|
1779
|
+
{
|
|
1780
|
+
label: 'AI profile snapshot',
|
|
1781
|
+
value: `${workspaceSyncDiagnostics.aiProfileSnapshotCount} local profile${workspaceSyncDiagnostics.aiProfileSnapshotCount === 1 ? '' : 's'}`
|
|
1782
|
+
},
|
|
1783
|
+
{
|
|
1784
|
+
label: 'AI profile raw response',
|
|
1785
|
+
value: `${workspaceSyncDiagnostics.aiProfileSnapshotRawCount} from route`
|
|
1786
|
+
},
|
|
1787
|
+
{
|
|
1788
|
+
label: 'Last pushed AI profiles',
|
|
1789
|
+
value: `${workspaceSyncDiagnostics.lastPushedAiProfileCount} tracked`
|
|
1790
|
+
},
|
|
1791
|
+
{
|
|
1792
|
+
label: 'AI watermark map',
|
|
1793
|
+
value: `${workspaceSyncDiagnostics.lastPushedAiProfileWatermarkCount} tracked`
|
|
1794
|
+
},
|
|
1795
|
+
{
|
|
1796
|
+
label: 'Document snapshot',
|
|
1797
|
+
value: `${workspaceSyncDiagnostics.documentSnapshotCount} local doc${workspaceSyncDiagnostics.documentSnapshotCount === 1 ? '' : 's'}`
|
|
1798
|
+
},
|
|
1799
|
+
{
|
|
1800
|
+
label: 'Asset snapshot',
|
|
1801
|
+
value: `${workspaceSyncDiagnostics.assetSnapshotCount} local asset${workspaceSyncDiagnostics.assetSnapshotCount === 1 ? '' : 's'}`
|
|
1802
|
+
},
|
|
1803
|
+
{
|
|
1804
|
+
label: 'Queued full AI sync',
|
|
1805
|
+
value: workspaceSyncDiagnostics.forceFullAiProfilePushQueued ? 'Yes' : 'No'
|
|
1806
|
+
},
|
|
1807
|
+
{
|
|
1808
|
+
label: 'AI snapshot last fetch',
|
|
1809
|
+
value: workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt
|
|
1810
|
+
? new Date(workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt).toLocaleString()
|
|
1811
|
+
: 'Never'
|
|
1812
|
+
},
|
|
1813
|
+
{
|
|
1814
|
+
label: 'AI snapshot fetch error',
|
|
1815
|
+
value: workspaceSyncDiagnostics.aiProfileSnapshotLastFetchError || 'None'
|
|
1816
|
+
},
|
|
1817
|
+
{
|
|
1818
|
+
label: 'AI snapshot skip reason',
|
|
1819
|
+
value: workspaceSyncDiagnostics.aiProfileSnapshotLastSkipReason || 'None'
|
|
1820
|
+
}
|
|
1821
|
+
]), [workspaceSyncDiagnostics]);
|
|
1822
|
+
const shouldDelayHeaderSyncing = headerSyncPresentation.actionable
|
|
1823
|
+
&& workspaceSyncStatus === 'syncing'
|
|
1824
|
+
&& workspaceSyncPhase === 'active'
|
|
1825
|
+
&& workspaceSyncPendingChanges === 0;
|
|
1188
1826
|
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);
|
|
1827
|
+
if (!shouldDelayHeaderSyncing) {
|
|
1828
|
+
setHeaderSyncStatus(headerSyncPresentation.status);
|
|
1204
1829
|
return;
|
|
1205
1830
|
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1831
|
+
const timeoutId = window.setTimeout(() => {
|
|
1832
|
+
setHeaderSyncStatus(headerSyncPresentation.status);
|
|
1833
|
+
}, 1200);
|
|
1834
|
+
return () => window.clearTimeout(timeoutId);
|
|
1835
|
+
}, [headerSyncPresentation.status, shouldDelayHeaderSyncing]);
|
|
1836
|
+
const formatSyncEventLine = useCallback((event) => {
|
|
1837
|
+
const occurredAt = typeof event.occurredAt === 'string' && event.occurredAt.trim().length > 0
|
|
1838
|
+
? event.occurredAt
|
|
1839
|
+
: 'unknown-time';
|
|
1840
|
+
const eventType = typeof event.eventType === 'string' && event.eventType.trim().length > 0
|
|
1841
|
+
? event.eventType.trim()
|
|
1842
|
+
: 'sync';
|
|
1843
|
+
const status = event.status === 'error' ? 'failed' : 'succeeded';
|
|
1844
|
+
const changeCountValue = Number(event.changeCount);
|
|
1845
|
+
const changeCount = Number.isFinite(changeCountValue)
|
|
1846
|
+
? ` (${Math.max(0, Math.floor(changeCountValue))} change${Math.floor(changeCountValue) === 1 ? '' : 's'})`
|
|
1847
|
+
: '';
|
|
1848
|
+
const requestMsValue = Number(event.requestMs);
|
|
1849
|
+
const requestMs = Number.isFinite(requestMsValue)
|
|
1850
|
+
? ` in ${Math.max(0, Math.floor(requestMsValue))}ms`
|
|
1851
|
+
: '';
|
|
1852
|
+
const statusCodeValue = Number(event.statusCode);
|
|
1853
|
+
const statusCode = Number.isFinite(statusCodeValue)
|
|
1854
|
+
? ` [${Math.floor(statusCodeValue)}]`
|
|
1855
|
+
: '';
|
|
1856
|
+
const errorMessage = typeof event.errorMessage === 'string' && event.errorMessage.trim().length > 0
|
|
1857
|
+
? `: ${event.errorMessage.trim()}`
|
|
1858
|
+
: '';
|
|
1859
|
+
return `${occurredAt} ${eventType} ${status}${changeCount}${requestMs}${statusCode}${errorMessage}`;
|
|
1860
|
+
}, []);
|
|
1861
|
+
const syncEventRows = useMemo(() => {
|
|
1862
|
+
if (syncRecentEventsLoading && syncRecentEvents.length === 0) {
|
|
1863
|
+
return [{
|
|
1864
|
+
key: 'loading',
|
|
1865
|
+
text: 'Loading recent sync events...',
|
|
1866
|
+
tone: 'muted'
|
|
1867
|
+
}];
|
|
1868
|
+
}
|
|
1869
|
+
if (syncRecentEventsError) {
|
|
1870
|
+
return [{
|
|
1871
|
+
key: 'error',
|
|
1872
|
+
text: syncRecentEventsError,
|
|
1873
|
+
tone: 'error'
|
|
1874
|
+
}];
|
|
1875
|
+
}
|
|
1876
|
+
if (syncRecentEvents.length === 0) {
|
|
1877
|
+
return [{
|
|
1878
|
+
key: 'empty',
|
|
1879
|
+
text: 'No recent sync events recorded.',
|
|
1880
|
+
tone: 'muted'
|
|
1881
|
+
}];
|
|
1882
|
+
}
|
|
1883
|
+
return syncRecentEvents.map((event, index) => ({
|
|
1884
|
+
key: getSyncEventStableKey(event, index),
|
|
1885
|
+
text: formatSyncEventLine(event),
|
|
1886
|
+
tone: event.status === 'error' ? 'error' : 'default'
|
|
1887
|
+
}));
|
|
1888
|
+
}, [formatSyncEventLine, syncRecentEvents, syncRecentEventsError, syncRecentEventsLoading]);
|
|
1889
|
+
const referenceMismatchCount = useMemo(() => getActiveReferenceMismatchCount(syncRecentEvents), [syncRecentEvents]);
|
|
1890
|
+
const activeReferenceMismatchEvents = useMemo(() => getActiveReferenceMismatchEvents(syncRecentEvents), [syncRecentEvents]);
|
|
1891
|
+
const activeReferenceMismatchSummaries = useMemo(() => (activeReferenceMismatchEvents.map((event, index) => {
|
|
1892
|
+
const details = event.details && typeof event.details === 'object' ? event.details : null;
|
|
1893
|
+
const pathValue = String(details?.path || '').trim();
|
|
1894
|
+
const existingRefValue = Number(details?.existingReferenceNumber);
|
|
1895
|
+
const incomingRefValue = Number(details?.incomingReferenceNumber);
|
|
1896
|
+
const pathLabel = pathValue || `Mismatch ${index + 1}`;
|
|
1897
|
+
const refsLabel = (Number.isFinite(existingRefValue) || Number.isFinite(incomingRefValue))
|
|
1898
|
+
? `Existing ${Number.isFinite(existingRefValue) ? existingRefValue : '?'} vs incoming ${Number.isFinite(incomingRefValue) ? incomingRefValue : '?'}`
|
|
1899
|
+
: null;
|
|
1900
|
+
return {
|
|
1901
|
+
key: getSyncEventStableKey(event, index),
|
|
1902
|
+
pathLabel,
|
|
1903
|
+
refsLabel,
|
|
1904
|
+
};
|
|
1905
|
+
})), [activeReferenceMismatchEvents]);
|
|
1906
|
+
useLayoutEffect(() => {
|
|
1907
|
+
const pendingRestore = pendingSyncEventsScrollRestoreRef.current;
|
|
1908
|
+
const listEl = syncEventsListRef.current;
|
|
1909
|
+
if (!pendingRestore || !listEl)
|
|
1213
1910
|
return;
|
|
1911
|
+
const delta = listEl.scrollHeight - pendingRestore.scrollHeight;
|
|
1912
|
+
listEl.scrollTop = pendingRestore.scrollTop + Math.max(0, delta);
|
|
1913
|
+
pendingSyncEventsScrollRestoreRef.current = null;
|
|
1914
|
+
}, [syncRecentEvents]);
|
|
1915
|
+
const loadRecentSyncEvents = useCallback(async () => {
|
|
1916
|
+
const res = await fetch(`/api/taskforce/sync/events?workspace_id=${encodeURIComponent(currentWorkspaceId)}&limit=15`, {
|
|
1917
|
+
method: 'GET',
|
|
1918
|
+
credentials: 'include'
|
|
1919
|
+
});
|
|
1920
|
+
if (!res.ok) {
|
|
1921
|
+
throw new Error(`Failed to load sync events (${res.status})`);
|
|
1214
1922
|
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1923
|
+
const data = await res.json().catch(() => ({}));
|
|
1924
|
+
return Array.isArray(data?.events)
|
|
1925
|
+
? data.events.filter((event) => event && typeof event === 'object')
|
|
1926
|
+
: [];
|
|
1927
|
+
}, [currentWorkspaceId]);
|
|
1928
|
+
useEffect(() => {
|
|
1929
|
+
if (!showSyncStatusModal)
|
|
1221
1930
|
return;
|
|
1931
|
+
let cancelled = false;
|
|
1932
|
+
const isInitialLoad = syncRecentEvents.length === 0;
|
|
1933
|
+
if (isInitialLoad) {
|
|
1934
|
+
setSyncRecentEventsLoading(true);
|
|
1222
1935
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1936
|
+
void loadRecentSyncEvents()
|
|
1937
|
+
.then((events) => {
|
|
1938
|
+
if (cancelled)
|
|
1939
|
+
return;
|
|
1940
|
+
const listEl = syncEventsListRef.current;
|
|
1941
|
+
pendingSyncEventsScrollRestoreRef.current = listEl && listEl.scrollTop > 8
|
|
1942
|
+
? {
|
|
1943
|
+
scrollTop: listEl.scrollTop,
|
|
1944
|
+
scrollHeight: listEl.scrollHeight
|
|
1945
|
+
}
|
|
1946
|
+
: null;
|
|
1947
|
+
setSyncRecentEvents((current) => {
|
|
1948
|
+
const next = current.length === 0 ? events : mergeRecentSyncEvents(current, events, 15);
|
|
1949
|
+
return areSyncEventListsEqual(current, next) ? current : next;
|
|
1950
|
+
});
|
|
1951
|
+
setSyncRecentEventsError(null);
|
|
1952
|
+
})
|
|
1953
|
+
.catch((error) => {
|
|
1954
|
+
if (cancelled)
|
|
1955
|
+
return;
|
|
1956
|
+
const message = error instanceof Error && error.message.trim().length > 0
|
|
1957
|
+
? error.message.trim()
|
|
1958
|
+
: 'Unable to load recent sync events.';
|
|
1959
|
+
setSyncRecentEventsError(message);
|
|
1960
|
+
if (isInitialLoad) {
|
|
1961
|
+
setSyncRecentEvents([]);
|
|
1230
1962
|
}
|
|
1963
|
+
})
|
|
1964
|
+
.finally(() => {
|
|
1965
|
+
if (cancelled)
|
|
1966
|
+
return;
|
|
1967
|
+
if (isInitialLoad) {
|
|
1968
|
+
setSyncRecentEventsLoading(false);
|
|
1969
|
+
}
|
|
1970
|
+
});
|
|
1971
|
+
return () => {
|
|
1972
|
+
cancelled = true;
|
|
1231
1973
|
};
|
|
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':
|
|
1974
|
+
}, [
|
|
1975
|
+
showSyncStatusModal,
|
|
1976
|
+
loadRecentSyncEvents,
|
|
1977
|
+
syncRecentEvents.length,
|
|
1978
|
+
workspaceLastPushAt,
|
|
1979
|
+
workspaceLastPullAt,
|
|
1980
|
+
workspaceLastErrorAt,
|
|
1981
|
+
workspaceSyncStatus
|
|
1982
|
+
]);
|
|
1983
|
+
const syncStatusMeta = useMemo(() => {
|
|
1984
|
+
switch (headerSyncStatus) {
|
|
1985
|
+
case 'off':
|
|
1258
1986
|
return {
|
|
1259
|
-
label: '
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1987
|
+
label: 'Off',
|
|
1988
|
+
icon: 'off',
|
|
1989
|
+
color: '#94a3b8',
|
|
1990
|
+
border: 'rgba(148, 163, 184, 0.35)',
|
|
1991
|
+
background: 'rgba(148, 163, 184, 0.12)'
|
|
1263
1992
|
};
|
|
1264
|
-
case '
|
|
1993
|
+
case 'syncing':
|
|
1265
1994
|
return {
|
|
1266
|
-
label: '
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1995
|
+
label: 'Syncing',
|
|
1996
|
+
icon: 'cloud',
|
|
1997
|
+
color: '#60a5fa',
|
|
1998
|
+
border: 'rgba(96, 165, 250, 0.35)',
|
|
1999
|
+
background: 'rgba(59, 130, 246, 0.12)'
|
|
1270
2000
|
};
|
|
1271
|
-
case '
|
|
2001
|
+
case 'attention':
|
|
1272
2002
|
return {
|
|
1273
|
-
label: '
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
2003
|
+
label: 'Needs Attention',
|
|
2004
|
+
icon: 'cloud',
|
|
2005
|
+
color: '#fda4af',
|
|
2006
|
+
border: 'rgba(253, 164, 175, 0.4)',
|
|
2007
|
+
background: 'rgba(239, 68, 68, 0.12)'
|
|
1277
2008
|
};
|
|
1278
|
-
case 'idle':
|
|
1279
2009
|
default:
|
|
1280
2010
|
return {
|
|
1281
|
-
label: '
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
2011
|
+
label: 'Healthy',
|
|
2012
|
+
icon: 'cloud',
|
|
2013
|
+
color: '#86efac',
|
|
2014
|
+
border: 'rgba(134, 239, 172, 0.4)',
|
|
2015
|
+
background: 'rgba(34, 197, 94, 0.12)'
|
|
1285
2016
|
};
|
|
1286
2017
|
}
|
|
1287
|
-
}, [
|
|
1288
|
-
const
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
}
|
|
2018
|
+
}, [headerSyncStatus]);
|
|
2019
|
+
const handleCopySyncDetails = useCallback(async () => {
|
|
2020
|
+
const details = [
|
|
2021
|
+
'Taskforce Sync Manager',
|
|
2022
|
+
`Workspace ID: ${currentWorkspaceId}`,
|
|
2023
|
+
`Status: ${syncStatusMeta.label}`,
|
|
2024
|
+
`Summary: ${headerSyncPresentation.summary}`,
|
|
2025
|
+
`Last successful sync: ${formattedLastSyncTime}`,
|
|
2026
|
+
`Last pull from cloud: ${formattedLastPullTime}`,
|
|
2027
|
+
`Last push to cloud: ${formattedLastPushTime}`,
|
|
2028
|
+
`Last error at: ${workspaceSyncDiagnostics.lastErrorAt ? new Date(workspaceSyncDiagnostics.lastErrorAt).toLocaleString() : 'Never'}`,
|
|
2029
|
+
`Pending local changes: ${workspaceSyncPendingChanges}`,
|
|
2030
|
+
`Last error: ${syncLastError}`,
|
|
2031
|
+
`Sync stage: ${syncStageLabel}`,
|
|
2032
|
+
`AI profile snapshot: ${workspaceSyncDiagnostics.aiProfileSnapshotCount}`,
|
|
2033
|
+
`AI profile raw response: ${workspaceSyncDiagnostics.aiProfileSnapshotRawCount}`,
|
|
2034
|
+
`Last pushed AI profiles tracked: ${workspaceSyncDiagnostics.lastPushedAiProfileCount}`,
|
|
2035
|
+
`AI profile watermarks tracked: ${workspaceSyncDiagnostics.lastPushedAiProfileWatermarkCount}`,
|
|
2036
|
+
`AI snapshot last fetch: ${workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt ? new Date(workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt).toLocaleString() : 'Never'}`,
|
|
2037
|
+
`AI snapshot fetch error: ${workspaceSyncDiagnostics.aiProfileSnapshotLastFetchError || 'None'}`,
|
|
2038
|
+
`AI snapshot skip reason: ${workspaceSyncDiagnostics.aiProfileSnapshotLastSkipReason || 'None'}`,
|
|
2039
|
+
`Document snapshot: ${workspaceSyncDiagnostics.documentSnapshotCount}`,
|
|
2040
|
+
`Asset snapshot: ${workspaceSyncDiagnostics.assetSnapshotCount}`,
|
|
2041
|
+
`Reference mismatches detected: ${referenceMismatchCount}`,
|
|
2042
|
+
`Queued full AI sync: ${workspaceSyncDiagnostics.forceFullAiProfilePushQueued ? 'Yes' : 'No'}`
|
|
2043
|
+
];
|
|
2044
|
+
try {
|
|
2045
|
+
const events = syncRecentEvents.length > 0 ? syncRecentEvents : await loadRecentSyncEvents();
|
|
2046
|
+
const recentEventLines = events.map((event) => formatSyncEventLine(event));
|
|
2047
|
+
const copyText = [
|
|
2048
|
+
...details,
|
|
2049
|
+
'',
|
|
2050
|
+
'Recent sync events',
|
|
2051
|
+
...(recentEventLines.length > 0 ? recentEventLines : ['No recent sync events recorded.'])
|
|
2052
|
+
].join('\n');
|
|
2053
|
+
await navigator.clipboard.writeText(copyText);
|
|
2054
|
+
setWorkspaceSyncCopied(true);
|
|
2055
|
+
window.setTimeout(() => {
|
|
2056
|
+
setWorkspaceSyncCopied(false);
|
|
2057
|
+
}, 1800);
|
|
2058
|
+
pushNotice('Sync details copied to clipboard.', 'success');
|
|
1306
2059
|
}
|
|
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
|
-
};
|
|
2060
|
+
catch {
|
|
2061
|
+
setWorkspaceSyncCopied(false);
|
|
2062
|
+
pushNotice('Failed to copy sync details.', 'error');
|
|
1315
2063
|
}
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
2064
|
+
}, [
|
|
2065
|
+
currentWorkspaceId,
|
|
2066
|
+
syncStatusMeta.label,
|
|
2067
|
+
headerSyncPresentation.summary,
|
|
2068
|
+
formattedLastSyncTime,
|
|
2069
|
+
formattedLastPullTime,
|
|
2070
|
+
formattedLastPushTime,
|
|
2071
|
+
workspaceSyncDiagnostics,
|
|
2072
|
+
workspaceSyncPendingChanges,
|
|
2073
|
+
syncLastError,
|
|
2074
|
+
syncStageLabel,
|
|
2075
|
+
referenceMismatchCount,
|
|
2076
|
+
pushNotice,
|
|
2077
|
+
syncRecentEvents,
|
|
2078
|
+
loadRecentSyncEvents,
|
|
2079
|
+
formatSyncEventLine
|
|
2080
|
+
]);
|
|
2081
|
+
const handleRepairSync = useCallback(async () => {
|
|
2082
|
+
setWorkspaceSyncRepairQueued(false);
|
|
2083
|
+
setWorkspaceSyncRepairBusy(true);
|
|
2084
|
+
try {
|
|
2085
|
+
await resetWorkspaceSyncCursorAndPull();
|
|
1324
2086
|
}
|
|
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
|
-
};
|
|
2087
|
+
finally {
|
|
2088
|
+
setWorkspaceSyncRepairBusy(false);
|
|
1333
2089
|
}
|
|
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
|
-
});
|
|
2090
|
+
}, [resetWorkspaceSyncCursorAndPull]);
|
|
2091
|
+
const handleQueueOrRunRepairSync = useCallback(() => {
|
|
2092
|
+
if (workspaceSyncRepairBusy)
|
|
2093
|
+
return;
|
|
2094
|
+
if (workspaceSyncBusy) {
|
|
2095
|
+
setWorkspaceSyncRepairQueued(true);
|
|
2096
|
+
return;
|
|
1386
2097
|
}
|
|
1387
|
-
|
|
1388
|
-
}, [
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
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)
|
|
2098
|
+
void handleRepairSync();
|
|
2099
|
+
}, [handleRepairSync, workspaceSyncBusy, workspaceSyncRepairBusy]);
|
|
2100
|
+
useEffect(() => {
|
|
2101
|
+
if (!workspaceSyncRepairQueued)
|
|
2102
|
+
return;
|
|
2103
|
+
if (workspaceSyncBusy || workspaceSyncRepairBusy)
|
|
1417
2104
|
return;
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
if (parentTaskFilterMode === 'unlinked')
|
|
1421
|
-
setParentTaskFilterMode('all');
|
|
1422
|
-
}, [hierarchySearchMatches, parentTaskFilterMode, setParentTaskFilterMode]);
|
|
2105
|
+
void handleRepairSync();
|
|
2106
|
+
}, [handleRepairSync, workspaceSyncBusy, workspaceSyncRepairBusy, workspaceSyncRepairQueued]);
|
|
1423
2107
|
const selectedDateObj = useMemo(() => parseDateOnlyLocal(scheduleSelectedDate) || new Date(), [scheduleSelectedDate]);
|
|
1424
2108
|
const scheduleWeekStart = useMemo(() => startOfWeek(selectedDateObj, globalWeekStartsOn), [selectedDateObj, globalWeekStartsOn]);
|
|
1425
2109
|
const scheduleDates = useMemo(() => {
|
|
@@ -1490,99 +2174,7 @@ export function StandaloneLayout(props) {
|
|
|
1490
2174
|
setScheduleBulkBusy(false);
|
|
1491
2175
|
}
|
|
1492
2176
|
}, [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
2177
|
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
2178
|
// Calculate columns based on grouping
|
|
1587
2179
|
const scheduleColumns = useMemo(() => {
|
|
1588
2180
|
const orderedKeys = globalWeekStartsOn === 'sunday'
|
|
@@ -1620,8 +2212,8 @@ export function StandaloneLayout(props) {
|
|
|
1620
2212
|
case 'type':
|
|
1621
2213
|
return (activeTypes || []).map(t => ({
|
|
1622
2214
|
...t,
|
|
1623
|
-
icon: TypeMeta[t.value]?.icon,
|
|
1624
|
-
color: TypeMeta[t.value]?.color
|
|
2215
|
+
icon: t.icon || TypeMeta[t.value]?.icon,
|
|
2216
|
+
color: t.color || TypeMeta[t.value]?.color
|
|
1625
2217
|
}));
|
|
1626
2218
|
case 'priority':
|
|
1627
2219
|
return priorities;
|
|
@@ -1636,159 +2228,718 @@ export function StandaloneLayout(props) {
|
|
|
1636
2228
|
case 'approach':
|
|
1637
2229
|
return approaches;
|
|
1638
2230
|
case 'assignee':
|
|
1639
|
-
return
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
2231
|
+
return assigneeOptions.map((option) => ({
|
|
2232
|
+
value: option.value,
|
|
2233
|
+
label: option.label,
|
|
2234
|
+
icon: option.icon === 'HelpCircle' ? 'Circle' : option.icon,
|
|
2235
|
+
color: option.color,
|
|
2236
|
+
}));
|
|
1644
2237
|
case 'status':
|
|
1645
2238
|
return [
|
|
1646
|
-
|
|
1647
|
-
{
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
2239
|
+
...STATUS_OPTIONS.filter((option) => option.value !== 'done' && option.value !== 'cancelled'),
|
|
2240
|
+
{
|
|
2241
|
+
value: 'completed',
|
|
2242
|
+
label: 'Completed',
|
|
2243
|
+
icon: getStatusDefinition('done').icon,
|
|
2244
|
+
color: getStatusDefinition('done').color
|
|
2245
|
+
}
|
|
1651
2246
|
];
|
|
1652
|
-
case '
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
2247
|
+
case 'schedule':
|
|
2248
|
+
return scheduleColumns;
|
|
2249
|
+
default:
|
|
2250
|
+
return activeCategories;
|
|
2251
|
+
}
|
|
2252
|
+
}, [groupBy, activeCategories, activeTypes, priorities, approaches, assigneeOptions, scheduleColumns]);
|
|
2253
|
+
// === SECTION: AccountMenuControl ===
|
|
2254
|
+
// Not extracted: ~25 distinct props from local state would be needed.
|
|
2255
|
+
// References: isAuthenticated, authIdentityLabel, billingStatus, runtimeLabel,
|
|
2256
|
+
// connectedEnvironmentLabel, canManageCloudWorkspaces, workspaceActionBusy,
|
|
2257
|
+
// setWorkspacePanelOpen, canOpenTeamManagement, openTeamManagementModal, and more.
|
|
2258
|
+
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
|
|
2259
|
+
? 'Checking sign-in status...'
|
|
2260
|
+
: isAuthenticated
|
|
2261
|
+
? (_jsx(_Fragment, { children: hasAuthIdentity
|
|
2262
|
+
? (_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: () => {
|
|
2263
|
+
setShowAccountMenu(false);
|
|
2264
|
+
openPlansScreen();
|
|
2265
|
+
}, 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 })] })] }) }))
|
|
2266
|
+
: 'Signed in' }))
|
|
2267
|
+
: '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: () => {
|
|
2268
|
+
setWorkspaceActionError('');
|
|
2269
|
+
setShowCreateWorkspaceConfirm(true);
|
|
2270
|
+
setShowAccountMenu(false);
|
|
2271
|
+
}, 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: () => {
|
|
2272
|
+
setShowAccountMenu(false);
|
|
2273
|
+
setShowEditProfileModal(true);
|
|
2274
|
+
}, role: "menuitem", children: [_jsx(User, { size: 15 }), "Edit Profile"] })), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
|
|
2275
|
+
setShowAccountMenu(false);
|
|
2276
|
+
openPlansScreen();
|
|
2277
|
+
}, role: "menuitem", children: [_jsx(Layers, { size: 15 }), "Plans"] }), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
|
|
2278
|
+
setShowAccountMenu(false);
|
|
2279
|
+
setShowAccountHub(true);
|
|
2280
|
+
}, role: "menuitem", children: [_jsx(User, { size: 15 }), "Account Hub"] }), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
|
|
2281
|
+
setShowAccountMenu(false);
|
|
2282
|
+
setShowHelpModal(true);
|
|
2283
|
+
}, role: "menuitem", children: [_jsx(HelpCircle, { size: 15 }), "Help & Tutorial"] }), runtimeMode === 'local' && !isAuthenticated && !isAuthBootstrapPending && (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
|
|
2284
|
+
openSharedLoginRoute('login');
|
|
2285
|
+
}, role: "menuitem", children: [_jsx(User, { size: 15 }), "Sign In / Register"] })), isAuthenticated && (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: async () => {
|
|
2286
|
+
setShowAccountMenu(false);
|
|
2287
|
+
await logout();
|
|
2288
|
+
}, role: "menuitem", children: [_jsx(LogOut, { size: 15 }), "Sign Out"] }))] }))] }));
|
|
2289
|
+
const cycleEmptyColumnVisibility = useCallback(() => {
|
|
2290
|
+
setEmptyColumnMode((prev) => {
|
|
2291
|
+
if (prev === 'show')
|
|
2292
|
+
return 'collapse';
|
|
2293
|
+
if (prev === 'collapse')
|
|
2294
|
+
return 'hide';
|
|
2295
|
+
return 'show';
|
|
2296
|
+
});
|
|
2297
|
+
}, [setEmptyColumnMode]);
|
|
2298
|
+
const openAddTaskModal = useCallback(() => {
|
|
2299
|
+
resetForm();
|
|
2300
|
+
setActiveWorkspaceModule('tasks');
|
|
2301
|
+
setActiveTab('add');
|
|
2302
|
+
}, [resetForm, setActiveTab, setActiveWorkspaceModule]);
|
|
2303
|
+
const openAddTaskModalForWorkstream = useCallback((workstreamId) => {
|
|
2304
|
+
const workstream = props.workstreams.find((item) => item.id === workstreamId);
|
|
2305
|
+
resetForm();
|
|
2306
|
+
setWorkstreamInput(formatWorkstreamReference(workstream) || workstreamId);
|
|
2307
|
+
setActiveWorkspaceModule('tasks');
|
|
2308
|
+
setActiveTab('add');
|
|
2309
|
+
}, [props.workstreams, resetForm, setActiveTab, setActiveWorkspaceModule, setWorkstreamInput]);
|
|
2310
|
+
const openPlanningEditor = useCallback((editor) => {
|
|
2311
|
+
setPlanningEditor(editor);
|
|
2312
|
+
setPlanningPrimaryCollapsed(false);
|
|
2313
|
+
setPlanningSecondaryEditor(null);
|
|
2314
|
+
setPlanningSecondaryCollapsed(false);
|
|
2315
|
+
setPlanningDraftTitle('');
|
|
2316
|
+
setPlanningDraftDescription('');
|
|
2317
|
+
setPlanningDraftOwner('');
|
|
2318
|
+
const selectedInitiative = editor.entityType === 'workstream' && selectedPlanningInitiativeId
|
|
2319
|
+
? planningStructure.initiativeById.get(selectedPlanningInitiativeId) || null
|
|
2320
|
+
: null;
|
|
2321
|
+
setPlanningDraftInitiativeId(selectedInitiative ? formatInitiativeReference(selectedInitiative) : '');
|
|
2322
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2323
|
+
}, [planningStructure.initiativeById, selectedPlanningInitiativeId]);
|
|
2324
|
+
const openPlanningSecondaryEditor = useCallback((editor, initiativeId) => {
|
|
2325
|
+
setPlanningSecondaryEditor(editor);
|
|
2326
|
+
setPlanningSecondaryCollapsed(false);
|
|
2327
|
+
setPlanningDraftTitle('');
|
|
2328
|
+
setPlanningDraftDescription('');
|
|
2329
|
+
setPlanningDraftOwner('');
|
|
2330
|
+
const selectedInitiative = editor.entityType === 'workstream'
|
|
2331
|
+
? ((initiativeId ? planningStructure.initiativeById.get(initiativeId) : null)
|
|
2332
|
+
|| (selectedPlanningInitiativeId ? planningStructure.initiativeById.get(selectedPlanningInitiativeId) || null : null))
|
|
2333
|
+
: null;
|
|
2334
|
+
setPlanningDraftInitiativeId(selectedInitiative ? formatInitiativeReference(selectedInitiative) : '');
|
|
2335
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2336
|
+
}, [planningStructure.initiativeById, selectedPlanningInitiativeId]);
|
|
2337
|
+
const closePlanningEditor = useCallback(() => {
|
|
2338
|
+
setPlanningEditor(null);
|
|
2339
|
+
setPlanningDraftTitle('');
|
|
2340
|
+
setPlanningDraftDescription('');
|
|
2341
|
+
setPlanningDraftOwner('');
|
|
2342
|
+
setPlanningDraftInitiativeId('');
|
|
2343
|
+
}, []);
|
|
2344
|
+
const closePlanningSecondaryEditor = useCallback(() => {
|
|
2345
|
+
setPlanningSecondaryEditor(null);
|
|
2346
|
+
setPlanningDraftTitle('');
|
|
2347
|
+
setPlanningDraftDescription('');
|
|
2348
|
+
setPlanningDraftOwner('');
|
|
2349
|
+
setPlanningDraftInitiativeId('');
|
|
2350
|
+
}, []);
|
|
2351
|
+
const submitPlanningEditor = useCallback(async () => {
|
|
2352
|
+
const activeEditor = planningSecondaryEditor || planningEditor;
|
|
2353
|
+
if (!activeEditor)
|
|
2354
|
+
return;
|
|
2355
|
+
const isSecondaryEditor = Boolean(planningSecondaryEditor);
|
|
2356
|
+
const ownerId = planningDraftOwner.trim() || null;
|
|
2357
|
+
try {
|
|
2358
|
+
if (activeEditor.entityType === 'initiative') {
|
|
2359
|
+
if (activeEditor.mode === 'edit') {
|
|
2360
|
+
await props.updateInitiative(activeEditor.targetId, {
|
|
2361
|
+
title: planningDraftTitle.trim() || 'Untitled Initiative',
|
|
2362
|
+
description: planningDraftDescription.trim() || null,
|
|
2363
|
+
ownerId,
|
|
2364
|
+
});
|
|
2365
|
+
setPlanningDrawerDetail({ type: 'initiative', id: activeEditor.targetId });
|
|
2366
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2367
|
+
pushNotice('Initiative updated', 'success');
|
|
1660
2368
|
}
|
|
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'
|
|
2369
|
+
else {
|
|
2370
|
+
const created = await props.createInitiative({
|
|
2371
|
+
title: planningDraftTitle.trim() || 'Untitled Initiative',
|
|
2372
|
+
description: planningDraftDescription.trim() || null,
|
|
2373
|
+
ownerId,
|
|
1674
2374
|
});
|
|
2375
|
+
setPlanningDrawerDetail({ type: 'initiative', id: created.id });
|
|
2376
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2377
|
+
pushNotice('Initiative created', 'success');
|
|
1675
2378
|
}
|
|
1676
|
-
return hierarchyColumns;
|
|
1677
2379
|
}
|
|
1678
|
-
|
|
1679
|
-
|
|
2380
|
+
else {
|
|
2381
|
+
const nextInitiativeRaw = planningDraftInitiativeId.trim();
|
|
2382
|
+
const resolvedInitiative = nextInitiativeRaw.length === 0
|
|
2383
|
+
? null
|
|
2384
|
+
: resolveInitiativeReference(planningStructure.initiatives, nextInitiativeRaw);
|
|
2385
|
+
if (nextInitiativeRaw.length > 0 && !resolvedInitiative) {
|
|
2386
|
+
pushNotice('Initiative not found by that reference.', 'error');
|
|
2387
|
+
return;
|
|
2388
|
+
}
|
|
2389
|
+
const patch = {
|
|
2390
|
+
title: planningDraftTitle.trim() || 'Untitled Workstream',
|
|
2391
|
+
description: planningDraftDescription.trim() || null,
|
|
2392
|
+
ownerId,
|
|
2393
|
+
initiativeId: resolvedInitiative?.id || null,
|
|
2394
|
+
};
|
|
2395
|
+
if (activeEditor.mode === 'edit') {
|
|
2396
|
+
await props.updateWorkstream(activeEditor.targetId, patch);
|
|
2397
|
+
if (isSecondaryEditor) {
|
|
2398
|
+
setPlanningNestedWorkstreamDetailId(activeEditor.targetId);
|
|
2399
|
+
}
|
|
2400
|
+
else {
|
|
2401
|
+
setPlanningDrawerDetail({ type: 'workstream', id: activeEditor.targetId });
|
|
2402
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2403
|
+
}
|
|
2404
|
+
pushNotice('Workstream updated', 'success');
|
|
2405
|
+
}
|
|
2406
|
+
else {
|
|
2407
|
+
const created = await props.createWorkstream(patch);
|
|
2408
|
+
if (isSecondaryEditor) {
|
|
2409
|
+
setPlanningNestedWorkstreamDetailId(created.id);
|
|
2410
|
+
}
|
|
2411
|
+
else {
|
|
2412
|
+
setPlanningDrawerDetail({ type: 'workstream', id: created.id });
|
|
2413
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2414
|
+
}
|
|
2415
|
+
pushNotice('Workstream created', 'success');
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
if (isSecondaryEditor) {
|
|
2419
|
+
closePlanningSecondaryEditor();
|
|
2420
|
+
}
|
|
2421
|
+
else {
|
|
2422
|
+
closePlanningEditor();
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
catch (error) {
|
|
2426
|
+
pushNotice(error instanceof Error ? error.message : 'Failed to save planning item.', 'error');
|
|
2427
|
+
}
|
|
2428
|
+
}, [
|
|
2429
|
+
closePlanningEditor,
|
|
2430
|
+
closePlanningSecondaryEditor,
|
|
2431
|
+
planningDraftDescription,
|
|
2432
|
+
planningDraftInitiativeId,
|
|
2433
|
+
planningDraftOwner,
|
|
2434
|
+
planningDraftTitle,
|
|
2435
|
+
planningEditor,
|
|
2436
|
+
planningSecondaryEditor,
|
|
2437
|
+
planningStructure.initiatives,
|
|
2438
|
+
props,
|
|
2439
|
+
pushNotice,
|
|
2440
|
+
]);
|
|
2441
|
+
const togglePlanningInitiative = useCallback((initiativeId) => {
|
|
2442
|
+
setExpandedPlanningInitiativeIds((current) => {
|
|
2443
|
+
const next = new Set(current);
|
|
2444
|
+
if (next.has(initiativeId)) {
|
|
2445
|
+
next.delete(initiativeId);
|
|
2446
|
+
}
|
|
2447
|
+
else {
|
|
2448
|
+
next.add(initiativeId);
|
|
2449
|
+
}
|
|
2450
|
+
return next;
|
|
2451
|
+
});
|
|
2452
|
+
}, []);
|
|
2453
|
+
const selectPlanningInitiative = useCallback((initiativeId) => {
|
|
2454
|
+
setSelectedPlanningInitiativeId((current) => current === initiativeId ? '' : initiativeId);
|
|
2455
|
+
setSelectedPlanningWorkstreamId('');
|
|
2456
|
+
}, []);
|
|
2457
|
+
const selectPlanningWorkstream = useCallback((workstreamId, initiativeId) => {
|
|
2458
|
+
setSelectedPlanningWorkstreamId((current) => {
|
|
2459
|
+
const nextWorkstreamId = current === workstreamId ? '' : workstreamId;
|
|
2460
|
+
setSelectedPlanningInitiativeId(nextWorkstreamId ? (initiativeId || '') : '');
|
|
2461
|
+
return nextWorkstreamId;
|
|
2462
|
+
});
|
|
2463
|
+
}, []);
|
|
2464
|
+
const openInitiativeDetails = useCallback((initiativeId) => {
|
|
2465
|
+
setPlanningEditor(null);
|
|
2466
|
+
setPlanningSecondaryEditor(null);
|
|
2467
|
+
setPlanningPrimaryCollapsed(false);
|
|
2468
|
+
setPlanningSecondaryCollapsed(false);
|
|
2469
|
+
setPlanningDrawerDetail(initiativeId ? { type: 'initiative', id: initiativeId } : null);
|
|
2470
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2471
|
+
}, []);
|
|
2472
|
+
const openWorkstreamDetails = useCallback((workstreamId) => {
|
|
2473
|
+
setPlanningEditor(null);
|
|
2474
|
+
setPlanningSecondaryEditor(null);
|
|
2475
|
+
setPlanningPrimaryCollapsed(false);
|
|
2476
|
+
setPlanningSecondaryCollapsed(false);
|
|
2477
|
+
setPlanningDrawerDetail(workstreamId ? { type: 'workstream', id: workstreamId } : null);
|
|
2478
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2479
|
+
}, []);
|
|
2480
|
+
const openNestedWorkstreamDetails = useCallback((workstreamId) => {
|
|
2481
|
+
setPlanningSecondaryEditor(null);
|
|
2482
|
+
setPlanningSecondaryCollapsed(false);
|
|
2483
|
+
setPlanningNestedWorkstreamDetailId(workstreamId);
|
|
2484
|
+
}, []);
|
|
2485
|
+
const ownerValueByLabel = useMemo(() => {
|
|
2486
|
+
const map = new Map();
|
|
2487
|
+
assigneeOptions.forEach((option) => {
|
|
2488
|
+
map.set(option.label, String(option.value));
|
|
2489
|
+
});
|
|
2490
|
+
return map;
|
|
2491
|
+
}, [assigneeOptions]);
|
|
2492
|
+
const openPlanningCreateEditor = useCallback((type, initiativeId) => {
|
|
2493
|
+
if (type === 'workstream' && initiativeId) {
|
|
2494
|
+
openPlanningSecondaryEditor({ mode: 'create', entityType: 'workstream' }, initiativeId);
|
|
2495
|
+
return;
|
|
2496
|
+
}
|
|
2497
|
+
openPlanningEditor({ mode: 'create', entityType: type });
|
|
2498
|
+
if (type === 'workstream') {
|
|
2499
|
+
const initiative = (initiativeId ? planningStructure.initiativeById.get(initiativeId) : null)
|
|
2500
|
+
|| (selectedPlanningInitiativeId ? planningStructure.initiativeById.get(selectedPlanningInitiativeId) : null)
|
|
2501
|
+
|| null;
|
|
2502
|
+
setPlanningDraftInitiativeId(initiative ? formatInitiativeReference(initiative) : '');
|
|
2503
|
+
}
|
|
2504
|
+
}, [openPlanningEditor, openPlanningSecondaryEditor, planningStructure.initiativeById, selectedPlanningInitiativeId]);
|
|
2505
|
+
const openPlanningEditDialog = useCallback((type, id) => {
|
|
2506
|
+
if (type === 'initiative') {
|
|
2507
|
+
const item = planningStructure.initiativeById.get(id);
|
|
2508
|
+
if (!item)
|
|
2509
|
+
return;
|
|
2510
|
+
setPlanningEditor({ mode: 'edit', entityType: 'initiative', targetId: id });
|
|
2511
|
+
setPlanningDraftTitle(item.title);
|
|
2512
|
+
setPlanningDraftDescription(item.description || '');
|
|
2513
|
+
setPlanningDraftOwner(item.ownerLabel ? (ownerValueByLabel.get(item.ownerLabel) || '') : '');
|
|
2514
|
+
setPlanningDraftInitiativeId('');
|
|
2515
|
+
}
|
|
2516
|
+
else {
|
|
2517
|
+
const item = planningStructure.workstreamById.get(id)
|
|
2518
|
+
|| planningStructure.standaloneWorkstreams.find((workstream) => workstream.id === id);
|
|
2519
|
+
if (!item)
|
|
2520
|
+
return;
|
|
2521
|
+
setPlanningEditor({ mode: 'edit', entityType: 'workstream', targetId: id });
|
|
2522
|
+
setPlanningDraftTitle(item.title);
|
|
2523
|
+
setPlanningDraftDescription(item.description || '');
|
|
2524
|
+
setPlanningDraftOwner(item.ownerLabel ? (ownerValueByLabel.get(item.ownerLabel) || '') : '');
|
|
2525
|
+
const initiative = item.initiativeId ? planningStructure.initiativeById.get(item.initiativeId) || null : null;
|
|
2526
|
+
setPlanningDraftInitiativeId(initiative ? formatInitiativeReference(initiative) : '');
|
|
2527
|
+
}
|
|
2528
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2529
|
+
}, [ownerValueByLabel, planningStructure.initiativeById, planningStructure.standaloneWorkstreams, planningStructure.workstreamById]);
|
|
2530
|
+
const handleAssignInitiativeToWorkstream = useCallback(async (workstreamId, initiativeReferenceOverride) => {
|
|
2531
|
+
const workstream = props.workstreams.find((item) => item.id === workstreamId);
|
|
2532
|
+
if (!workstream) {
|
|
2533
|
+
pushNotice('Workstream not found.', 'error');
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
2536
|
+
const nextInitiativeRaw = typeof initiativeReferenceOverride === 'string'
|
|
2537
|
+
? initiativeReferenceOverride.trim()
|
|
2538
|
+
: initiativeReferenceOverride === null
|
|
2539
|
+
? ''
|
|
2540
|
+
: planningDraftInitiativeId.trim();
|
|
2541
|
+
const resolvedInitiative = nextInitiativeRaw.length === 0
|
|
2542
|
+
? null
|
|
2543
|
+
: resolveInitiativeReference(planningStructure.initiatives, nextInitiativeRaw);
|
|
2544
|
+
if (nextInitiativeRaw.length > 0 && !resolvedInitiative) {
|
|
2545
|
+
pushNotice('Initiative not found by that reference.', 'error');
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2548
|
+
const normalizedParentId = resolvedInitiative?.id || null;
|
|
2549
|
+
if ((workstream.initiativeId || null) === normalizedParentId) {
|
|
2550
|
+
pushNotice(normalizedParentId ? 'Workstream already belongs to that initiative.' : 'Workstream is already standalone.', 'info');
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
try {
|
|
2554
|
+
await props.updateWorkstream(workstreamId, { initiativeId: normalizedParentId });
|
|
2555
|
+
setPlanningDraftInitiativeId(resolvedInitiative ? formatInitiativeReference(resolvedInitiative) : '');
|
|
2556
|
+
pushNotice(normalizedParentId ? 'Initiative set' : 'Initiative removed', 'success');
|
|
2557
|
+
}
|
|
2558
|
+
catch {
|
|
2559
|
+
pushNotice('Failed to set initiative.', 'error');
|
|
2560
|
+
}
|
|
2561
|
+
}, [planningDraftInitiativeId, planningStructure.initiatives, props, pushNotice]);
|
|
2562
|
+
const handleAssignTaskToWorkstream = useCallback(async (taskId, workstreamId) => {
|
|
2563
|
+
const task = hierarchyTaskById.get(taskId);
|
|
2564
|
+
if (!task) {
|
|
2565
|
+
pushNotice('Task not found.', 'error');
|
|
2566
|
+
return;
|
|
2567
|
+
}
|
|
2568
|
+
const normalizedWorkstreamId = workstreamId || null;
|
|
2569
|
+
if (planningStructure.initiativeById.has(taskId) || planningStructure.workstreamById.has(taskId)) {
|
|
2570
|
+
pushNotice('Only execution tasks can be moved into workstreams.', 'error');
|
|
2571
|
+
return;
|
|
2572
|
+
}
|
|
2573
|
+
if (normalizedWorkstreamId && !planningStructure.workstreamById.has(normalizedWorkstreamId)) {
|
|
2574
|
+
pushNotice('Workstream not found.', 'error');
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
if ((task.workstreamId || null) === normalizedWorkstreamId) {
|
|
2578
|
+
pushNotice(normalizedWorkstreamId ? 'Task already belongs to that workstream.' : 'Task is already standalone.', 'info');
|
|
2579
|
+
return;
|
|
2580
|
+
}
|
|
2581
|
+
try {
|
|
2582
|
+
const res = await fetch(`/api/taskforce/task/${taskId}`, {
|
|
2583
|
+
method: 'PATCH',
|
|
2584
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2585
|
+
body: JSON.stringify({ workstreamId: normalizedWorkstreamId })
|
|
2586
|
+
});
|
|
2587
|
+
if (!res.ok) {
|
|
2588
|
+
const data = await res.json().catch(() => ({}));
|
|
2589
|
+
pushNotice(data?.error || 'Failed to set workstream.', 'error');
|
|
2590
|
+
return;
|
|
2591
|
+
}
|
|
2592
|
+
await res.json().catch(() => null);
|
|
2593
|
+
await fetchTasks(true);
|
|
2594
|
+
pushNotice(normalizedWorkstreamId ? 'Workstream set' : 'Workstream removed', 'success');
|
|
2595
|
+
}
|
|
2596
|
+
catch {
|
|
2597
|
+
pushNotice('Failed to set workstream.', 'error');
|
|
2598
|
+
}
|
|
2599
|
+
}, [fetchTasks, hierarchyTaskById, planningStructure.initiativeById, planningStructure.workstreamById, pushNotice]);
|
|
2600
|
+
const handleAttachTaskToWorkstreamByReference = useCallback(async (workstreamId, taskReference) => {
|
|
2601
|
+
const needle = taskReference.trim();
|
|
2602
|
+
if (!needle) {
|
|
2603
|
+
pushNotice('Enter a task reference.', 'error');
|
|
2604
|
+
return;
|
|
2605
|
+
}
|
|
2606
|
+
const parsedReference = parseTaskReference(needle);
|
|
2607
|
+
const matchedTask = hierarchyTasks.find((task) => (task.id === needle
|
|
2608
|
+
|| getTaskReferenceLabel(task) === needle
|
|
2609
|
+
|| (parsedReference !== null && task.referenceNumber === parsedReference)));
|
|
2610
|
+
if (!matchedTask) {
|
|
2611
|
+
pushNotice('Task not found by that reference.', 'error');
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2614
|
+
await handleAssignTaskToWorkstream(matchedTask.id, workstreamId);
|
|
2615
|
+
}, [handleAssignTaskToWorkstream, hierarchyTasks, pushNotice]);
|
|
2616
|
+
const handleAttachWorkstreamToInitiativeByReference = useCallback(async (initiativeId, workstreamReference) => {
|
|
2617
|
+
const needle = workstreamReference.trim();
|
|
2618
|
+
if (!needle) {
|
|
2619
|
+
pushNotice('Enter a workstream reference.', 'error');
|
|
2620
|
+
return;
|
|
2621
|
+
}
|
|
2622
|
+
const initiative = planningStructure.initiativeById.get(initiativeId);
|
|
2623
|
+
if (!initiative) {
|
|
2624
|
+
pushNotice('Initiative not found.', 'error');
|
|
2625
|
+
return;
|
|
2626
|
+
}
|
|
2627
|
+
const parsedReference = parseWorkstreamReference(needle);
|
|
2628
|
+
const matchedWorkstream = props.workstreams.find((workstream) => (workstream.id === needle
|
|
2629
|
+
|| formatWorkstreamReference(workstream) === needle
|
|
2630
|
+
|| (parsedReference !== null && workstream.referenceNumber === parsedReference)));
|
|
2631
|
+
if (!matchedWorkstream) {
|
|
2632
|
+
pushNotice('Workstream not found by that reference.', 'error');
|
|
2633
|
+
return;
|
|
2634
|
+
}
|
|
2635
|
+
await handleAssignInitiativeToWorkstream(matchedWorkstream.id, formatInitiativeReference(initiative));
|
|
2636
|
+
}, [handleAssignInitiativeToWorkstream, planningStructure.initiativeById, props.workstreams, pushNotice]);
|
|
2637
|
+
const handleInitiativeFilterChange = useCallback((initiativeId) => {
|
|
2638
|
+
setSelectedPlanningInitiativeId(initiativeId);
|
|
2639
|
+
setSelectedPlanningWorkstreamId('');
|
|
2640
|
+
}, []);
|
|
2641
|
+
const handleWorkstreamFilterChange = useCallback((workstreamId) => {
|
|
2642
|
+
setSelectedPlanningWorkstreamId(workstreamId);
|
|
2643
|
+
setSelectedPlanningInitiativeId(workstreamId ? planningStructure.workstreamInitiativeIds.get(workstreamId) || '' : '');
|
|
2644
|
+
}, [planningStructure.workstreamInitiativeIds]);
|
|
2645
|
+
const handleModuleChange = useCallback((nextModuleId) => {
|
|
2646
|
+
const nextModule = resolveWorkspaceModule(nextModuleId, workspaceModules);
|
|
2647
|
+
if (nextModule !== nextModuleId) {
|
|
2648
|
+
const unavailableLabel = workspaceModules.find((module) => module.id === nextModuleId)?.label || 'That module';
|
|
2649
|
+
pushNotice(`${unavailableLabel} is not available in this build yet.`, 'error');
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
setActiveWorkspaceModule(nextModule);
|
|
2653
|
+
}, [
|
|
2654
|
+
pushNotice,
|
|
2655
|
+
setActiveWorkspaceModule,
|
|
2656
|
+
workspaceModules,
|
|
2657
|
+
]);
|
|
2658
|
+
const handlePlanningToolToggle = useCallback(() => {
|
|
2659
|
+
if (resolvedWorkspaceModule === 'tasks' && planningDrawerOpen) {
|
|
2660
|
+
setPlanningDrawerOpen(false);
|
|
2661
|
+
setPlanningDrawerDetail(null);
|
|
2662
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2663
|
+
setPlanningEditor(null);
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
if (resolvedWorkspaceModule !== 'tasks') {
|
|
2667
|
+
setActiveWorkspaceModule('tasks');
|
|
2668
|
+
setPlanningDrawerOpen(true);
|
|
2669
|
+
return;
|
|
2670
|
+
}
|
|
2671
|
+
setPlanningDrawerOpen((current) => !current);
|
|
2672
|
+
}, [planningDrawerOpen, resolvedWorkspaceModule, setActiveWorkspaceModule]);
|
|
2673
|
+
const handleBackToTaskFromAnnotatedAttachment = useCallback((taskId) => {
|
|
2674
|
+
setActiveWorkspaceModule('tasks');
|
|
2675
|
+
handleOpenTaskById(taskId);
|
|
2676
|
+
}, [handleOpenTaskById, setActiveWorkspaceModule]);
|
|
2677
|
+
const handleAnnotatedAttachmentOpenTarget = useCallback((target, options) => {
|
|
2678
|
+
setRequestedAnnotatedTarget(target);
|
|
2679
|
+
setRequestedAnnotatedSessionId(options?.sessionId ?? null);
|
|
2680
|
+
setRequestedAnnotatedOpenVersion((current) => current + 1);
|
|
2681
|
+
setActiveWorkspaceModule('annotate');
|
|
2682
|
+
}, [setActiveWorkspaceModule]);
|
|
2683
|
+
const handleAnnotatedAttachmentContextChange = useCallback(({ target, sessionId }) => {
|
|
2684
|
+
setRequestedAnnotatedTarget((current) => {
|
|
2685
|
+
if (!current && !target)
|
|
2686
|
+
return current;
|
|
2687
|
+
if (current
|
|
2688
|
+
&& target
|
|
2689
|
+
&& current.taskId === target.taskId
|
|
2690
|
+
&& current.taskReferenceLabel === target.taskReferenceLabel
|
|
2691
|
+
&& current.assetId === target.assetId
|
|
2692
|
+
&& current.imageReferenceLabel === target.imageReferenceLabel
|
|
2693
|
+
&& current.path === target.path
|
|
2694
|
+
&& current.displayName === target.displayName) {
|
|
2695
|
+
return current;
|
|
2696
|
+
}
|
|
2697
|
+
return target;
|
|
2698
|
+
});
|
|
2699
|
+
setRequestedAnnotatedSessionId((current) => (current === sessionId ? current : sessionId));
|
|
2700
|
+
}, []);
|
|
2701
|
+
const sortOptions = useMemo(() => [
|
|
2702
|
+
{ value: 'created', label: t('standalone.sortCreated') },
|
|
2703
|
+
{ value: 'updated', label: t('standalone.sortUpdated') },
|
|
2704
|
+
{ value: 'priority', label: t('standalone.sortPriority') },
|
|
2705
|
+
{ value: 'complexity', label: t('standalone.sortComplexity') },
|
|
2706
|
+
...buildCustomTaxonomySortOptions(taxonomies, [...props.searchAgnosticTasks, ...archivedScopeAllTasks])
|
|
2707
|
+
], [archivedScopeAllTasks, props.searchAgnosticTasks, taxonomies]);
|
|
2708
|
+
const groupingOptions = useMemo(() => [
|
|
2709
|
+
{ value: 'category', label: t('standalone.groupCategory') },
|
|
2710
|
+
{ value: 'type', label: t('standalone.groupType') },
|
|
2711
|
+
{ value: 'priority', label: t('standalone.groupPriority') },
|
|
2712
|
+
{ value: 'complexity', label: t('standalone.groupComplexity') },
|
|
2713
|
+
{ value: 'assignee', label: t('standalone.groupAssignee') },
|
|
2714
|
+
{ value: 'status', label: t('standalone.groupStatus') },
|
|
2715
|
+
{ value: 'schedule', label: t('standalone.groupSchedule') },
|
|
2716
|
+
], []);
|
|
2717
|
+
const workspaceToolModules = useMemo(() => workspaceModules.filter((module) => module.enabled), [workspaceModules]);
|
|
2718
|
+
const taskDisplayActions = useMemo(() => {
|
|
2719
|
+
const actions = [
|
|
2720
|
+
{
|
|
2721
|
+
key: 'empty-columns',
|
|
2722
|
+
icon: emptyColumnMode === 'show'
|
|
2723
|
+
? _jsx(Maximize2, { size: 16 })
|
|
2724
|
+
: emptyColumnMode === 'collapse'
|
|
2725
|
+
? _jsx(Minimize2, { size: 16 })
|
|
2726
|
+
: _jsx(EyeOff, { size: 16 }),
|
|
2727
|
+
title: emptyColumnMode === 'show'
|
|
2728
|
+
? t('standalone.showEmptyColumns')
|
|
2729
|
+
: emptyColumnMode === 'collapse'
|
|
2730
|
+
? t('standalone.compressEmptyColumns')
|
|
2731
|
+
: t('standalone.hideEmptyColumns'),
|
|
2732
|
+
onClick: cycleEmptyColumnVisibility,
|
|
2733
|
+
active: emptyColumnMode !== 'show',
|
|
2734
|
+
},
|
|
2735
|
+
{
|
|
2736
|
+
key: 'compressed-cards',
|
|
2737
|
+
icon: compressedCards ? _jsx(ChevronsDown, { size: 16 }) : _jsx(ChevronsUp, { size: 16 }),
|
|
2738
|
+
title: compressedCards ? t('standalone.expandCards') : t('standalone.compressCards'),
|
|
2739
|
+
onClick: () => setCompressedCards(!compressedCards),
|
|
2740
|
+
active: compressedCards,
|
|
2741
|
+
},
|
|
2742
|
+
];
|
|
2743
|
+
return actions;
|
|
2744
|
+
}, [
|
|
2745
|
+
compressedCards,
|
|
2746
|
+
cycleEmptyColumnVisibility,
|
|
2747
|
+
emptyColumnMode,
|
|
2748
|
+
]);
|
|
2749
|
+
const universalFilterActions = useMemo(() => [{
|
|
2750
|
+
key: 'filter-toggle',
|
|
2751
|
+
icon: _jsx(Filter, { size: 16 }),
|
|
2752
|
+
title: t('standalone.toggleFilters'),
|
|
2753
|
+
onClick: () => setShowFilters(!showFilters),
|
|
2754
|
+
active: showFilters,
|
|
2755
|
+
className: hasActiveWorkspaceFilterControls ? styles.filterGlow : '',
|
|
2756
|
+
}], [hasActiveWorkspaceFilterControls, setShowFilters, showFilters]);
|
|
2757
|
+
const workspaceFilterBar = useMemo(() => {
|
|
2758
|
+
switch (workspaceChromeDefinition.filterBar.kind) {
|
|
2759
|
+
case 'task-filters':
|
|
2760
|
+
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: () => {
|
|
2761
|
+
const deletedCount = deletedScopeAllTasks.length;
|
|
2762
|
+
if (deletedCount === 0)
|
|
2763
|
+
return;
|
|
2764
|
+
const confirmed = window.confirm(`Permanently delete ${deletedCount} deleted task${deletedCount === 1 ? '' : 's'}? This cannot be undone.`);
|
|
2765
|
+
if (!confirmed)
|
|
2766
|
+
return;
|
|
2767
|
+
void handleEmptyDeletedTasks();
|
|
2768
|
+
}, clearFilters: () => {
|
|
2769
|
+
clearFilters();
|
|
2770
|
+
setSelectedPlanningInitiativeId('');
|
|
2771
|
+
setSelectedPlanningWorkstreamId('');
|
|
2772
|
+
} }));
|
|
2773
|
+
case 'document-filters':
|
|
2774
|
+
return (_jsx(DocumentFilterToolbar, { typeFilters: documentTypeFilters, setTypeFilters: setDocumentTypeFilters, attachmentFilters: documentAttachmentFilters, setAttachmentFilters: setDocumentAttachmentFilters, clearFilters: () => {
|
|
2775
|
+
setDocumentTypeFilters(DOCUMENT_TYPE_FILTER_OPTIONS.map((option) => option.value));
|
|
2776
|
+
setDocumentAttachmentFilters(DOCUMENT_ATTACHMENT_FILTER_OPTIONS.map((option) => option.value));
|
|
2777
|
+
} }));
|
|
2778
|
+
case 'empty':
|
|
1680
2779
|
default:
|
|
1681
|
-
return
|
|
2780
|
+
return _jsx(EmptyWorkspaceFilterBar, {});
|
|
1682
2781
|
}
|
|
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
|
-
|
|
2782
|
+
}, [
|
|
2783
|
+
assigneeOptions,
|
|
2784
|
+
categoryFilterOptions,
|
|
2785
|
+
clearFilters,
|
|
2786
|
+
deletedScopeAllTasks.length,
|
|
2787
|
+
documentAttachmentFilters,
|
|
2788
|
+
documentTypeFilters,
|
|
2789
|
+
filterAssignees,
|
|
2790
|
+
filterCategories,
|
|
2791
|
+
filterPriorities,
|
|
2792
|
+
filterStatus,
|
|
2793
|
+
filterTypes,
|
|
2794
|
+
handleInitiativeFilterChange,
|
|
2795
|
+
handleWorkstreamFilterChange,
|
|
2796
|
+
initiativeFilterOptions,
|
|
2797
|
+
priorities,
|
|
2798
|
+
selectedPlanningInitiativeId,
|
|
2799
|
+
selectedPlanningWorkstreamId,
|
|
2800
|
+
setDocumentAttachmentFilters,
|
|
2801
|
+
setDocumentTypeFilters,
|
|
2802
|
+
setFilterAssignees,
|
|
2803
|
+
setFilterCategories,
|
|
2804
|
+
setFilterPriorities,
|
|
2805
|
+
setFilterStatus,
|
|
2806
|
+
setFilterTypes,
|
|
2807
|
+
setSelectedPlanningInitiativeId,
|
|
2808
|
+
setSelectedPlanningWorkstreamId,
|
|
2809
|
+
setShowArchive,
|
|
2810
|
+
setTaskScope,
|
|
2811
|
+
showArchive,
|
|
2812
|
+
taskScope,
|
|
2813
|
+
taxonomyDisplayLabels,
|
|
2814
|
+
typeFilterOptions,
|
|
2815
|
+
workstreamFilterOptions,
|
|
2816
|
+
workspaceChromeDefinition.filterBar.kind,
|
|
2817
|
+
fetchArchive,
|
|
2818
|
+
handleEmptyDeletedTasks,
|
|
2819
|
+
]);
|
|
2820
|
+
const universalDisplayActions = useMemo(() => {
|
|
2821
|
+
const actions = [{
|
|
2822
|
+
key: 'zen-toggle',
|
|
2823
|
+
icon: _jsx(Zap, { size: 16, fill: zenMode ? 'currentColor' : 'none' }),
|
|
2824
|
+
title: zenMode ? t('standalone.exitZenMode') : t('standalone.enterZenMode'),
|
|
2825
|
+
onClick: () => setZenMode(),
|
|
2826
|
+
active: zenMode,
|
|
2827
|
+
}];
|
|
2828
|
+
return actions;
|
|
2829
|
+
}, [
|
|
2830
|
+
zenMode,
|
|
2831
|
+
setZenMode,
|
|
2832
|
+
]);
|
|
2833
|
+
const headerSectionRegistry = useMemo(() => ({
|
|
2834
|
+
search: (_jsx(WorkspaceHeaderSearch, { value: searchQuery, placeholder: t('standalone.searchPlaceholder'), active: hasSearchFilter, onChange: setSearchQuery, onClear: () => setSearchQuery(''), clearTitle: t('standalone.clearSearchTitle') })),
|
|
2835
|
+
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 }) })) })),
|
|
2836
|
+
'divider-primary': _jsx(WorkspaceHeaderDivider, {}),
|
|
2837
|
+
grouping: (_jsx(WorkspaceHeaderSelect, { label: _jsxs(_Fragment, { children: [_jsx(Layers, { size: 12 }), " ", t('standalone.groupLabel')] }), value: groupBy, options: groupingOptions, onChange: (value) => setGroupBy(value) })),
|
|
2838
|
+
'divider-secondary': _jsx(WorkspaceHeaderDivider, {}),
|
|
2839
|
+
'task-display-actions': (_jsx(WorkspaceHeaderActions, { actions: taskDisplayActions })),
|
|
2840
|
+
}), [
|
|
2841
|
+
groupBy,
|
|
2842
|
+
groupingOptions,
|
|
2843
|
+
handleModuleChange,
|
|
2844
|
+
hasSearchFilter,
|
|
2845
|
+
props,
|
|
2846
|
+
searchQuery,
|
|
2847
|
+
setGroupBy,
|
|
2848
|
+
setSearchQuery,
|
|
2849
|
+
setSortBy,
|
|
2850
|
+
sortBy,
|
|
2851
|
+
sortOptions,
|
|
2852
|
+
taskDisplayActions,
|
|
2853
|
+
workspaceChromeDefinition.moduleId,
|
|
2854
|
+
]);
|
|
2855
|
+
const primaryActionRegistry = useMemo(() => ({
|
|
2856
|
+
'add-task': (_jsx(WorkspacePrimaryAction, { icon: _jsx(Plus, { size: 16 }), label: t('standalone.addTask'), onClick: openAddTaskModal })),
|
|
2857
|
+
'add-document': null,
|
|
2858
|
+
}), [openAddTaskModal]);
|
|
2859
|
+
const workspaceToolRail = useMemo(() => {
|
|
2860
|
+
const moduleIcons = {
|
|
2861
|
+
tasks: _jsx(ClipboardCheck, { size: 18 }),
|
|
2862
|
+
docs: _jsx(FileText, { size: 18 }),
|
|
2863
|
+
annotate: _jsx(Image, { size: 18 }),
|
|
2864
|
+
workflows: _jsx(Workflow, { size: 18 }),
|
|
2865
|
+
agents: _jsx(Bot, { size: 18 }),
|
|
2866
|
+
};
|
|
2867
|
+
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: () => {
|
|
2868
|
+
setSettingsSection('general');
|
|
2869
|
+
setActiveTab('settings');
|
|
2870
|
+
}, title: "Workspace Settings", "aria-label": "Workspace Settings", children: _jsx(Settings, { size: 18 }) })] })] }));
|
|
2871
|
+
}, [activeTab, handleModuleChange, handlePlanningToolToggle, planningDrawerOpen, resolvedWorkspaceModule, setSettingsSection, setActiveTab, workspaceToolModules]);
|
|
2872
|
+
const planningDrawerPortalRef = useRef(null);
|
|
2873
|
+
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: () => {
|
|
2874
|
+
setPlanningSecondaryEditor(null);
|
|
2875
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
2876
|
+
}, 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) => {
|
|
2877
|
+
void props.archiveInitiative(initiativeId).then(() => {
|
|
2878
|
+
pushNotice('Initiative archived', 'success');
|
|
2879
|
+
}).catch((error) => {
|
|
2880
|
+
pushNotice(error instanceof Error ? error.message : 'Failed to archive initiative.', 'error');
|
|
2881
|
+
});
|
|
2882
|
+
}, onUnarchiveInitiative: (initiativeId) => {
|
|
2883
|
+
void props.unarchiveInitiative(initiativeId).then(() => {
|
|
2884
|
+
pushNotice('Initiative unarchived', 'success');
|
|
2885
|
+
}).catch((error) => {
|
|
2886
|
+
pushNotice(error instanceof Error ? error.message : 'Failed to unarchive initiative.', 'error');
|
|
2887
|
+
});
|
|
2888
|
+
}, onArchiveWorkstream: (workstreamId) => {
|
|
2889
|
+
void props.archiveWorkstream(workstreamId).then(() => {
|
|
2890
|
+
pushNotice('Workstream archived', 'success');
|
|
2891
|
+
}).catch((error) => {
|
|
2892
|
+
pushNotice(error instanceof Error ? error.message : 'Failed to archive workstream.', 'error');
|
|
2893
|
+
});
|
|
2894
|
+
}, onUnarchiveWorkstream: (workstreamId) => {
|
|
2895
|
+
void props.unarchiveWorkstream(workstreamId).then(() => {
|
|
2896
|
+
pushNotice('Workstream unarchived', 'success');
|
|
2897
|
+
}).catch((error) => {
|
|
2898
|
+
pushNotice(error instanceof Error ? error.message : 'Failed to unarchive workstream.', 'error');
|
|
2899
|
+
});
|
|
2900
|
+
} }));
|
|
2901
|
+
const effectiveHeaderSectionIds = workspaceChromeDefinition.headerSections;
|
|
2902
|
+
const workspaceHeaderSections = effectiveHeaderSectionIds
|
|
2903
|
+
.map((sectionId) => {
|
|
2904
|
+
const section = headerSectionRegistry[sectionId];
|
|
2905
|
+
return section ? _jsx(React.Fragment, { children: section }, sectionId) : null;
|
|
2906
|
+
})
|
|
2907
|
+
.filter(Boolean);
|
|
2908
|
+
const workspacePrimaryAction = workspaceChromeDefinition.primaryAction
|
|
2909
|
+
? primaryActionRegistry[workspaceChromeDefinition.primaryAction]
|
|
2910
|
+
: null;
|
|
2911
|
+
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: {
|
|
2912
|
+
marginLeft: '6px',
|
|
2913
|
+
height: '24px',
|
|
2914
|
+
width: '24px',
|
|
2915
|
+
padding: 0,
|
|
2916
|
+
borderRadius: '999px',
|
|
2917
|
+
border: `1px solid ${syncStatusMeta.border}`,
|
|
2918
|
+
background: syncStatusMeta.background,
|
|
2919
|
+
color: syncStatusMeta.color,
|
|
2920
|
+
display: 'inline-flex',
|
|
2921
|
+
alignItems: 'center',
|
|
2922
|
+
justifyContent: 'center'
|
|
2923
|
+
}, 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: () => {
|
|
2924
|
+
setRequestedDocPath(null);
|
|
2925
|
+
setRequestedDocAssetId(null);
|
|
2926
|
+
}, 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
2927
|
position: 'relative',
|
|
1770
2928
|
opacity: props.loadingTasks ? 0.7 : 1,
|
|
1771
|
-
transition: 'opacity 0.2s ease, padding
|
|
2929
|
+
transition: 'opacity 0.2s ease, padding 220ms cubic-bezier(0.4, 0, 0.2, 1)',
|
|
1772
2930
|
display: 'flex',
|
|
1773
2931
|
flex: 1,
|
|
1774
2932
|
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) => {
|
|
2933
|
+
paddingLeft: planningDrawerOpen
|
|
2934
|
+
? `${WORKSPACE_TOOL_RAIL_WIDTH + planningDrawerWidthPx + WORKSPACE_CONTENT_GUTTER}px`
|
|
2935
|
+
: `${WORKSPACE_TOOL_RAIL_WIDTH + WORKSPACE_CONTENT_GUTTER}px`,
|
|
2936
|
+
paddingRight: (groupBy === 'schedule' && scheduleSidebarOpen) ? `${SCHEDULE_SIDEBAR_WIDTH_PX}px` : 0
|
|
2937
|
+
}, children: [workspaceToolRail, _jsx("div", { ref: planningDrawerPortalRef, style: {
|
|
2938
|
+
position: 'absolute',
|
|
2939
|
+
inset: 0,
|
|
2940
|
+
zIndex: 35,
|
|
2941
|
+
pointerEvents: 'none',
|
|
2942
|
+
} }), _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
2943
|
setScheduleSelectedDate(dateOnly);
|
|
1793
2944
|
const parsed = parseDateOnlyLocal(dateOnly);
|
|
1794
2945
|
if (parsed) {
|
|
@@ -1798,291 +2949,154 @@ export function StandaloneLayout(props) {
|
|
|
1798
2949
|
if (groupBy !== 'schedule')
|
|
1799
2950
|
return;
|
|
1800
2951
|
setScheduleScrollLeft(scrollLeft);
|
|
1801
|
-
}, emptyColumnMode: emptyColumnMode, categories: activeCategories, compressed: compressedCards,
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
2952
|
+
}, 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
|
|
2953
|
+
? createPortal(planningDrawerNode, planningDrawerPortalRef.current)
|
|
2954
|
+
: null, onAssignTaskToWorkstream: handleAssignTaskToWorkstream, onAssignWorkstreamToInitiative: handleAssignInitiativeToWorkstream, workstreams: props.workstreams, initiatives: props.initiatives, onUnarchive: (taskId) => {
|
|
2955
|
+
if (taskScope === 'deleted') {
|
|
2956
|
+
const deletedRecord = deletedTaskRecordByTaskId.get(taskId);
|
|
2957
|
+
if (!deletedRecord)
|
|
2958
|
+
return;
|
|
2959
|
+
void handleRestoreDeletedTask(deletedRecord.id);
|
|
2960
|
+
return;
|
|
2961
|
+
}
|
|
2962
|
+
void handleUnarchive(taskId);
|
|
2963
|
+
}, onDelete: (taskId) => {
|
|
2964
|
+
if (taskScope === 'deleted') {
|
|
2965
|
+
const deletedRecord = deletedTaskRecordByTaskId.get(taskId);
|
|
2966
|
+
if (!deletedRecord)
|
|
2967
|
+
return;
|
|
2968
|
+
const confirmed = window.confirm('Permanently delete this task? This cannot be undone.');
|
|
2969
|
+
if (!confirmed)
|
|
2970
|
+
return;
|
|
2971
|
+
void handlePermanentlyDeleteDeletedTask(deletedRecord.id);
|
|
2972
|
+
return;
|
|
2973
|
+
}
|
|
2974
|
+
void handleDelete(taskId);
|
|
2975
|
+
} }, `${groupBy}-${kanbanColumns.map(c => String(c.value)).join('|')}-${scheduleDates.mon}`), groupBy === 'schedule' && !scheduleSidebarOpen && (_jsx("button", { type: "button", className: "tf-control-icon", onClick: () => {
|
|
2976
|
+
setPlanningDrawerDetail(null);
|
|
2977
|
+
setScheduleSidebarOpen(true);
|
|
2978
|
+
}, title: "Show Schedule Sidebar", style: {
|
|
1809
2979
|
position: 'absolute',
|
|
1810
2980
|
top: '10px',
|
|
1811
2981
|
right: '10px',
|
|
1812
2982
|
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) => {
|
|
2983
|
+
}, 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
|
|
2984
|
+
? (deletedRecordId) => {
|
|
2985
|
+
void handleRestoreDeletedTask(deletedRecordId);
|
|
2986
|
+
}
|
|
2987
|
+
: undefined, handlePermanentlyDeleteDeletedTask: currentTask?.deletedRecordId
|
|
2988
|
+
? (deletedRecordId) => {
|
|
2989
|
+
const confirmed = window.confirm('Permanently delete this task? This cannot be undone.');
|
|
2990
|
+
if (!confirmed)
|
|
2991
|
+
return;
|
|
2992
|
+
void handlePermanentlyDeleteDeletedTask(deletedRecordId);
|
|
2993
|
+
resetForm();
|
|
2994
|
+
setActiveTab('tasks');
|
|
2995
|
+
}
|
|
2996
|
+
: 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
2997
|
setSettingsSection(section);
|
|
1980
2998
|
setActiveTab('settings');
|
|
1981
|
-
}, onSubmit: handleSubmit, commentsEndRef: commentsEndRef, onAddComment: () => handleAddComment(newCommentText),
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
2999
|
+
}, onSubmit: handleSubmit, commentsEndRef: commentsEndRef, onAddComment: () => handleAddComment(newCommentText), onOpenTaskById: handleOpenTaskById, onAddContextFile: (file) => {
|
|
3000
|
+
let nextAttachments = attachments;
|
|
3001
|
+
setAttachments((prev) => {
|
|
3002
|
+
nextAttachments = [...prev, file];
|
|
3003
|
+
return nextAttachments;
|
|
3004
|
+
});
|
|
3005
|
+
setAttachmentsDirty(true);
|
|
3006
|
+
if (editingTaskId) {
|
|
3007
|
+
void handleUpdateTask(editingTaskId, { attachments: nextAttachments });
|
|
3008
|
+
}
|
|
1987
3009
|
}, onRemoveContextFile: async (index) => {
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
3010
|
+
let nextAttachments = attachments;
|
|
3011
|
+
setAttachments((prev) => {
|
|
3012
|
+
nextAttachments = prev.filter((_, i) => i !== index);
|
|
3013
|
+
return nextAttachments;
|
|
3014
|
+
});
|
|
3015
|
+
setAttachmentsDirty(true);
|
|
3016
|
+
if (editingTaskId) {
|
|
3017
|
+
void handleUpdateTask(editingTaskId, { attachments: nextAttachments });
|
|
1996
3018
|
}
|
|
1997
3019
|
}, onUpdateContextCaption: (index, caption) => {
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
3020
|
+
let nextAttachments = attachments;
|
|
3021
|
+
setAttachments((prev) => {
|
|
3022
|
+
nextAttachments = prev.map((s, i) => {
|
|
3023
|
+
if (i !== index)
|
|
3024
|
+
return s;
|
|
3025
|
+
if (typeof s === 'string')
|
|
3026
|
+
return { path: s, caption, timestamp: new Date().toISOString() };
|
|
3027
|
+
return { ...s, caption };
|
|
3028
|
+
});
|
|
3029
|
+
return nextAttachments;
|
|
3030
|
+
});
|
|
3031
|
+
setAttachmentsDirty(true);
|
|
3032
|
+
if (editingTaskId) {
|
|
3033
|
+
void handleUpdateTask(editingTaskId, { attachments: nextAttachments });
|
|
3034
|
+
}
|
|
3035
|
+
}, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onSetStatus: handleSetStatus, onArchiveTask: handleArchiveTask, onUnarchive: (taskId) => {
|
|
3036
|
+
if (currentTask?.isDeleted) {
|
|
3037
|
+
const deletedRecord = deletedTaskRecordByTaskId.get(taskId);
|
|
3038
|
+
if (!deletedRecord)
|
|
3039
|
+
return;
|
|
3040
|
+
void handleRestoreDeletedTask(deletedRecord.id);
|
|
3041
|
+
return;
|
|
3042
|
+
}
|
|
3043
|
+
void handleUnarchive(taskId);
|
|
3044
|
+
}, 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
3045
|
setShowHelpModal(false);
|
|
2007
3046
|
setActiveTab('settings');
|
|
2008
3047
|
setSettingsSection('general');
|
|
2009
|
-
}, children: "Open Settings" }), _jsx("button", { className: styles.submitBtn, onClick: () => setShowHelpModal(false), children: "Close Tutorial" })] })] }) }), _jsx(Modal, { isOpen:
|
|
3048
|
+
}, 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
3049
|
? (authRequiredForApi
|
|
2011
3050
|
? (isAuthenticated ? 'Signed in. API access is enabled.' : 'Cloud mode requires sign in before app usage.')
|
|
2012
3051
|
: 'Cloud runtime with optional authentication.')
|
|
2013
|
-
: 'Local runtime allows guest usage without sign in.' }), _jsx("p", {
|
|
3052
|
+
: 'Local runtime allows guest usage without sign in.' }), _jsx("p", { className: managementStyles.sectionTextSpaced, children: isAuthenticated
|
|
2014
3053
|
? (hasAuthIdentity
|
|
2015
|
-
? _jsxs(_Fragment, { children: ["Signed in as ", _jsx("span", { className:
|
|
3054
|
+
? _jsxs(_Fragment, { children: ["Signed in as ", _jsx("span", { className: accountStyles.accountIdentityEmail, children: authIdentityLabel })] })
|
|
2016
3055
|
: 'Signed in')
|
|
2017
|
-
: 'Signed in as: not signed in' }), runtimeMode === 'cloud' && authRequiredForApi && !isAuthenticated && (_jsx("p", {
|
|
3056
|
+
: '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
3057
|
setShowAccountHub(false);
|
|
2019
3058
|
void openTeamManagementModal();
|
|
2020
3059
|
setTeamManagementTab('audit');
|
|
2021
|
-
}, disabled: !canOpenTeamManagement, children: "Open Workspace Audit" }) })] }), _jsxs("div", { className:
|
|
3060
|
+
}, 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
3061
|
? (workspaceCloudSyncEnabled
|
|
2023
|
-
? `Enabled
|
|
3062
|
+
? `Enabled · ${syncStatusMeta.label}`
|
|
2024
3063
|
: '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: () => {
|
|
3064
|
+
: '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
3065
|
setShowAccountHub(false);
|
|
2052
3066
|
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
|
|
3067
|
+
}, 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
3068
|
setTeamManagementTab('members');
|
|
2055
3069
|
void loadTeamUsers();
|
|
2056
3070
|
}, 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
3071
|
setTeamManagementTab('audit');
|
|
2058
3072
|
void loadTeamAuditPage(teamAuditPage);
|
|
2059
|
-
}, children: "Audit Log" })] }), teamManagementTab === 'members' && (_jsxs("div", { className:
|
|
3073
|
+
}, 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
3074
|
void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}/role`, {
|
|
2061
3075
|
method: 'PATCH',
|
|
2062
3076
|
headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
|
|
2063
3077
|
credentials: 'include',
|
|
2064
3078
|
body: JSON.stringify({
|
|
2065
|
-
workspaceId:
|
|
3079
|
+
workspaceId: teamManagementWorkspaceId,
|
|
2066
3080
|
role: event.target.value
|
|
2067
3081
|
})
|
|
2068
3082
|
}));
|
|
2069
|
-
}, disabled: teamActionBusyUserId === member.userId,
|
|
3083
|
+
}, 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
3084
|
void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}/permission-mode`, {
|
|
2071
3085
|
method: 'PATCH',
|
|
2072
3086
|
headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
|
|
2073
3087
|
credentials: 'include',
|
|
2074
3088
|
body: JSON.stringify({
|
|
2075
|
-
workspaceId:
|
|
3089
|
+
workspaceId: teamManagementWorkspaceId,
|
|
2076
3090
|
mode: event.target.value
|
|
2077
3091
|
})
|
|
2078
3092
|
}));
|
|
2079
|
-
}, disabled: teamActionBusyUserId === member.userId,
|
|
3093
|
+
}, 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
3094
|
void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}/disable`, {
|
|
2081
3095
|
method: 'PATCH',
|
|
2082
3096
|
headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
|
|
2083
3097
|
credentials: 'include',
|
|
2084
3098
|
body: JSON.stringify({
|
|
2085
|
-
workspaceId:
|
|
3099
|
+
workspaceId: teamManagementWorkspaceId,
|
|
2086
3100
|
disabled: !member.disabled
|
|
2087
3101
|
})
|
|
2088
3102
|
}));
|
|
@@ -2091,27 +3105,52 @@ export function StandaloneLayout(props) {
|
|
|
2091
3105
|
method: 'POST',
|
|
2092
3106
|
headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
|
|
2093
3107
|
credentials: 'include',
|
|
2094
|
-
body: JSON.stringify({ workspaceId:
|
|
3108
|
+
body: JSON.stringify({ workspaceId: teamManagementWorkspaceId })
|
|
2095
3109
|
}));
|
|
2096
3110
|
}, 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(
|
|
3111
|
+
void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}?workspaceId=${encodeURIComponent(teamManagementWorkspaceId)}`, {
|
|
2098
3112
|
method: 'DELETE',
|
|
2099
3113
|
headers: teamAdminHeaders(),
|
|
2100
3114
|
credentials: 'include'
|
|
2101
3115
|
}));
|
|
2102
|
-
}, children: "Remove" })] })] }, member.userId)))] })), teamManagementTab === 'invites' && (_jsxs("div", { className:
|
|
3116
|
+
}, 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
3117
|
setShowSyncStatusModal(false);
|
|
2104
|
-
}, title: "
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
3118
|
+
}, 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: {
|
|
3119
|
+
borderColor: syncStatusMeta.border,
|
|
3120
|
+
background: syncStatusMeta.background,
|
|
3121
|
+
color: syncStatusMeta.color
|
|
3122
|
+
}, children: syncStatusMeta.label }), _jsxs("span", { className: syncStatusStyles.syncStatusLabel, children: ["Workspace: ", _jsx("code", { children: currentWorkspaceLabel })] })] }) }), _jsx("label", { className: syncStatusStyles.syncToggle, children: _jsxs("span", { className: [
|
|
3123
|
+
syncStatusStyles.syncToggleControl,
|
|
3124
|
+
workspaceCloudSyncEnabled ? syncStatusStyles.syncToggleControlEnabled : syncStatusStyles.syncToggleControlDisabled,
|
|
3125
|
+
syncControlBusy ? syncStatusStyles.syncToggleControlBusy : '',
|
|
3126
|
+
!canManageWorkspaceSync ? syncStatusStyles.syncToggleControlBlocked : ''
|
|
3127
|
+
].filter(Boolean).join(' '), children: [_jsx("input", { className: syncStatusStyles.syncToggleInput, type: "checkbox", role: "switch", "aria-label": "Enable Sync", checked: workspaceCloudSyncEnabled, disabled: !canManageWorkspaceSync || syncControlBusy, onChange: (event) => {
|
|
3128
|
+
void handleWorkspaceSyncToggle(event.target.checked);
|
|
3129
|
+
} }), _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
|
|
3130
|
+
? `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.`
|
|
3131
|
+
: '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: {
|
|
3132
|
+
display: 'flex',
|
|
3133
|
+
flexDirection: 'column',
|
|
3134
|
+
gap: '2px',
|
|
3135
|
+
padding: '8px 10px',
|
|
3136
|
+
borderRadius: '8px',
|
|
3137
|
+
background: 'rgba(15, 23, 42, 0.18)',
|
|
3138
|
+
border: '1px solid rgba(250, 204, 21, 0.16)'
|
|
3139
|
+
}, 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: [
|
|
3140
|
+
syncStatusStyles.syncStatusEventItem,
|
|
3141
|
+
row.tone === 'error' ? syncStatusStyles.syncStatusEventItemError : '',
|
|
3142
|
+
row.tone === 'muted' ? syncStatusStyles.syncStatusEventItemMuted : ''
|
|
3143
|
+
].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
|
|
3144
|
+
? 'Repair is running now.'
|
|
3145
|
+
: workspaceSyncRepairQueued
|
|
3146
|
+
? 'Repair is queued and will start when the current sync finishes.'
|
|
3147
|
+
: workspaceSyncBusy
|
|
3148
|
+
? 'Queue a repair to start automatically when the current sync finishes.'
|
|
3149
|
+
: '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: () => {
|
|
3150
|
+
openSharedLoginRoute('login');
|
|
3151
|
+
}, 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
3152
|
const parts = currentBrowsePath.split('/').filter(Boolean);
|
|
2114
3153
|
parts.pop();
|
|
2115
3154
|
fetchFolders(parts.length ? parts.join('/') + '/' : '');
|
|
2116
|
-
}, children: "Back" })), _jsx("button", { className: styles.helpLink, onClick: () => setShowFolderBrowser(false), children: "Close" })] })] }), _jsxs("div", { className:
|
|
3155
|
+
}, 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
3156
|
}
|