@taskforcehq/taskforce 0.3.327 → 0.3.328
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/dist/Taskforce.module.css +24 -53
- package/dist/components/activity/EntityActivityTimeline.js +15 -5
- package/dist/components/context/AttachmentPreviewHost.js +1 -1
- package/dist/components/context/ContextAttachmentManager.d.ts +3 -0
- package/dist/components/context/ContextAttachmentManager.js +116 -20
- package/dist/components/features/planning/PlanningEntitySummary.d.ts +3 -1
- package/dist/components/features/planning/PlanningEntitySummary.js +3 -2
- package/dist/components/features/planning/PlanningLinkPicker.d.ts +3 -1
- package/dist/components/features/planning/PlanningLinkPicker.js +5 -3
- package/dist/components/features/planning/PlanningModule.js +26 -9
- package/dist/components/features/planning/PlanningVisualIdentity.d.ts +14 -0
- package/dist/components/features/planning/PlanningVisualIdentity.js +106 -0
- package/dist/components/features/planning/planningWorkspaceModel.d.ts +11 -0
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +2 -2
- package/dist/components/task/TaskCard.js +37 -4
- package/dist/components/task/TaskContextUpload.d.ts +3 -0
- package/dist/components/task/TaskContextUpload.js +2 -2
- package/dist/components/task/TaskForm.d.ts +4 -0
- package/dist/components/task/TaskForm.js +32 -7
- package/dist/components/task/TaskImageReviews.js +16 -16
- package/dist/components/task/TaskStatusActions.js +11 -2
- package/dist/components/task/TaskWorkstreamPicker.js +2 -1
- package/dist/components/ui/DocumentTypeIcon.d.ts +7 -0
- package/dist/components/ui/DocumentTypeIcon.js +8 -0
- package/dist/components/ui/TaxonomyDropdown.d.ts +9 -1
- package/dist/components/ui/TaxonomyDropdown.js +30 -3
- package/dist/components/views/StandaloneLayout.js +62 -9
- package/dist/components/views/WidgetView.js +8 -5
- package/dist/components/views/panels/PlanningDrawer.d.ts +7 -1
- package/dist/components/views/panels/PlanningDrawer.js +48 -44
- package/dist/config/envSchema.js +1 -0
- package/dist/config/localServiceCloudTarget.d.ts +10 -0
- package/dist/config/localServiceCloudTarget.js +76 -0
- package/dist/core/AiProfileService.d.ts +1 -0
- package/dist/core/AiProfileService.js +2 -1
- package/dist/core/AttachmentLinkService.js +7 -4
- package/dist/core/DeletedTaskLifecycleService.d.ts +4 -0
- package/dist/core/DeletedTaskLifecycleService.js +21 -0
- package/dist/core/McpTokenService.d.ts +4 -0
- package/dist/core/McpTokenService.js +52 -0
- package/dist/core/TaskAttachmentLinksDurableMutationService.d.ts +5 -0
- package/dist/core/TaskAttachmentLinksDurableMutationService.js +20 -11
- package/dist/core/Taskforce.d.ts +63 -1
- package/dist/core/Taskforce.js +558 -73
- package/dist/core/types.d.ts +2 -1
- package/dist/documentReviews/DocumentReviewCommentStore.d.ts +11 -0
- package/dist/documentReviews/DocumentReviewCommentStore.js +35 -2
- package/dist/documentReviews/service.d.ts +4 -0
- package/dist/documentReviews/service.js +141 -46
- package/dist/hooks/tasks/useTaskFormActions.d.ts +3 -0
- package/dist/hooks/tasks/useTaskFormActions.js +33 -7
- package/dist/hooks/useTaskforce.d.ts +59 -50
- package/dist/hooks/useTaskforce.js +103 -1
- package/dist/mcp/canonicalAssetHelpers.d.ts +13 -0
- package/dist/mcp/canonicalAssetHelpers.js +39 -5
- package/dist/mcp/collaborationRegistrar.js +15 -13
- package/dist/mcp/documentAssetRegistrar.js +16 -9
- package/dist/mcp/localServiceProxy.js +4 -1
- package/dist/mcp/runtime.d.ts +16 -0
- package/dist/mcp/runtime.js +566 -171
- package/dist/mcp/taskAttachmentCommandAdapter.d.ts +2 -0
- package/dist/mcp/taskAttachmentCommandAdapter.js +14 -8
- package/dist/mcp/taskPlanningRegistrar.js +42 -34
- package/dist/mcp/toolRegistry.js +2 -2
- package/dist/migrations/taskSchemaMigrations.js +35 -0
- package/dist/runtime/appGateDefinitions.d.ts +2 -2
- package/dist/runtime/appGateDefinitions.js +2 -2
- package/dist/server/appAccess.d.ts +12 -0
- package/dist/server/appAccess.js +21 -0
- package/dist/server/authorizedWorkspaceAccess.d.ts +12 -0
- package/dist/server/authorizedWorkspaceAccess.js +40 -0
- package/dist/server/cloudCollectionRead.d.ts +25 -0
- package/dist/server/cloudCollectionRead.js +120 -0
- package/dist/server/cloudCollectionReadPool.d.ts +52 -0
- package/dist/server/cloudCollectionReadPool.js +140 -0
- package/dist/server/cloudCollectionReadWorker.d.ts +1 -0
- package/dist/server/cloudCollectionReadWorker.js +36 -0
- package/dist/server/index.d.ts +3 -11
- package/dist/server/index.js +53 -40
- package/dist/server/localServiceLease.d.ts +3 -0
- package/dist/server/localServiceLease.js +9 -1
- package/dist/server/routes/documents.d.ts +5 -2
- package/dist/server/routes/documents.js +160 -13
- package/dist/server/routes/durableTaskHttpRouters.d.ts +2 -0
- package/dist/server/routes/durableTaskHttpRouters.js +2 -2
- package/dist/server/routes/localService.js +2 -0
- package/dist/server/routes/primitives.d.ts +2 -0
- package/dist/server/routes/shared.d.ts +24 -0
- package/dist/server/routes/shared.js +114 -18
- package/dist/server/routes/sync.js +22 -0
- package/dist/server/routes/syncV3FeedRoutes.js +4 -2
- package/dist/server/routes/syncV3LocalCaptureRoutes.js +20 -2
- package/dist/server/routes/syncV3OperationRoutes.js +17 -2
- package/dist/server/routes/tasks.js +199 -89
- package/dist/server/routes.js +35 -7
- package/dist/service/localServiceCli.js +36 -25
- package/dist/service/localServiceClientLifecycle.js +67 -6
- package/dist/shared/planningIdentity.d.ts +20 -0
- package/dist/shared/planningIdentity.js +79 -0
- package/dist/storage/postgresAdapter.js +4 -0
- package/dist/storage/postgresRequestDiagnostics.d.ts +8 -0
- package/dist/storage/postgresRequestDiagnostics.js +24 -0
- package/dist/storage/workspaceAssetStore.d.ts +32 -0
- package/dist/storage/workspaceAssetStore.js +70 -0
- package/dist/sync/coordinator/localSyncExecutionPermitStore.d.ts +16 -0
- package/dist/sync/coordinator/localSyncExecutionPermitStore.js +70 -22
- package/dist/sync/coordinator/workspaceSyncTransportGateway.js +4 -2
- package/dist/sync/engine/workspaceSyncEngineRemoteServices.d.ts +5 -0
- package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.d.ts +1 -1
- package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.js +15 -0
- package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +26 -3
- package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.js +2 -0
- package/dist/sync/syncApplyHandlers.d.ts +3 -1
- package/dist/sync/syncApplyHandlers.js +29 -0
- package/dist/sync/syncService.js +9 -0
- package/dist/sync/taskSyncPayload.d.ts +1 -0
- package/dist/sync/taskSyncPayload.js +6 -0
- package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.d.ts +1 -0
- package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.js +21 -3
- package/dist/sync/v3/durableTaskHttpBulkMutationRouter.js +17 -4
- package/dist/sync/v3/localInitiativeOutboxService.js +4 -2
- package/dist/sync/v3/localOutboxDispatcher.js +5 -1
- package/dist/sync/v3/localTaskAttachmentLinksOutboxHandler.js +12 -2
- package/dist/sync/v3/localTaskAttachmentLinksOutboxService.d.ts +1 -0
- package/dist/sync/v3/localTaskAttachmentLinksOutboxService.js +15 -3
- package/dist/sync/v3/localWorkstreamOutboxService.js +4 -2
- package/dist/sync/v3/syncDurabilityStore.d.ts +8 -0
- package/dist/sync/v3/syncDurabilityStore.js +171 -33
- package/dist/sync/v3/taskAttachmentLinksMutationService.d.ts +1 -0
- package/dist/sync/v3/taskAttachmentLinksMutationService.js +18 -2
- package/dist/sync/v3/taskMetadataCoexistence.js +4 -0
- package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.js +1 -9
- package/dist/sync/v3/workspaceSyncV3CloudOperationHandler.js +1 -0
- package/dist/sync/v3/workspaceSyncV3CombinedFeedOrchestrator.js +7 -1
- package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +15 -3
- package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.d.ts +4 -0
- package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.js +29 -6
- package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.d.ts +1 -0
- package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.js +29 -7
- package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.js +62 -7
- package/dist/sync/v3/workspaceSyncV3OperationProtocol.d.ts +1 -0
- package/dist/sync/v3/workspaceSyncV3RepairSqlBatching.d.ts +2 -0
- package/dist/sync/v3/workspaceSyncV3RepairSqlBatching.js +10 -0
- package/dist/sync/v3/workspaceSyncV3TaskRootProjection.d.ts +1 -1
- package/dist/sync/v3/workspaceSyncV3TaskRootProjection.js +1 -1
- package/dist/sync/workspacePullFeed.d.ts +6 -1
- package/dist/sync/workspacePullFeed.js +172 -161
- package/dist/sync/workspaceSyncModel.d.ts +11 -0
- package/dist/sync/workspaceSyncModel.js +27 -2
- package/dist/types.d.ts +8 -0
- package/dist/ui/assets/{AiIdentityRosterCard-DB1BLO_j.js → AiIdentityRosterCard-MZs7lVED.js} +1 -1
- package/dist/ui/assets/{AiProfilesModule-DPxQeF9N.js → AiProfilesModule-C3i0LOxe.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-DHNYaNg6.js → AnnotatedAttachmentWorkspace-DVZk5Tln.js} +1 -1
- package/dist/ui/assets/AssetTraySortControl-Kf1pelSy.js +1 -0
- package/dist/ui/assets/ContextAttachmentManager-B8boOs6Q.js +3 -0
- package/dist/ui/assets/{DocumentWorkspace-BC7sy5TG.js → DocumentWorkspace-DXH4XUsB.js} +4 -4
- package/dist/ui/assets/EntityActivityTimeline-BtSTq0ON.js +1 -0
- package/dist/ui/assets/PlanningModule-BPVyEa-f.js +1 -0
- package/dist/ui/assets/PlanningModule-CoIR9hbV.css +1 -0
- package/dist/ui/assets/{PlansPage-BVH_NfgT.js → PlansPage-ChqgxALc.js} +1 -1
- package/dist/ui/assets/TaskContextUpload-CB3ol0sN.js +1 -0
- package/dist/ui/assets/TaskContextUpload-qmBeUw3u.css +1 -0
- package/dist/ui/assets/{TaskSettings-xVsxSa0g.js → TaskSettings-g4ECKNOz.js} +1 -1
- package/dist/ui/assets/{TaskforceAgentsModule-CkAxW19K.js → TaskforceAgentsModule-BazdUwxZ.js} +1 -1
- package/dist/ui/assets/WorkflowManagerModule-DWFspC-o.js +1 -0
- package/dist/ui/assets/index-BUplSsv_.js +7 -0
- package/dist/ui/assets/index-gz2EXuoK.css +1 -0
- package/dist/ui/assets/{vendor-icons-B8ZNsH1g.js → vendor-icons-BkFLXavV.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/constants.d.ts +2 -2
- package/dist/utils/constants.js +1 -1
- package/dist/utils/contextAttachmentUpload.d.ts +2 -0
- package/dist/utils/contextAttachmentUpload.js +7 -0
- package/dist/utils/contextFiles.d.ts +1 -1
- package/dist/utils/contextFiles.js +4 -4
- package/package.json +6 -1
- package/dist/ui/assets/AssetTraySortControl-DphbyHVc.js +0 -1
- package/dist/ui/assets/ContextAttachmentManager-CMxSzHqc.js +0 -3
- package/dist/ui/assets/EntityActivityTimeline-pJ2ZjZ-k.js +0 -1
- package/dist/ui/assets/PlanningModule-CeTqXMMz.css +0 -1
- package/dist/ui/assets/PlanningModule-t5GbvVR9.js +0 -1
- package/dist/ui/assets/TaskContextUpload-Ds4LnDzV.css +0 -1
- package/dist/ui/assets/TaskContextUpload-bl4fOOUz.js +0 -1
- package/dist/ui/assets/WorkflowManagerModule-BKqIGVnF.js +0 -1
- package/dist/ui/assets/index-BzJlVnNS.css +0 -1
- package/dist/ui/assets/index-Cx7vZJHL.js +0 -7
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { AlertTriangle, Archive, ArrowUpDown, ChevronLeft, Eye, EyeOff, Filter, Link2, Loader2, Pencil, Plus, RotateCcw, Search, Unlink, X, } from 'lucide-react';
|
|
4
4
|
import { TASK_ASSIGNEE_UNASSIGNED_COLOR, TASK_ASSIGNEE_UNASSIGNED_ICON, } from '../../../utils/assignees';
|
|
@@ -10,6 +10,7 @@ import { TaxonomyDropdown } from '../../ui/TaxonomyDropdown';
|
|
|
10
10
|
import { PlanningOwnerDropdown, normalizePlanningOwnerOptions, } from './PlanningOwnerDropdown';
|
|
11
11
|
import { buildPlanningNavigatorResults, PLANNING_NAVIGATOR_SORT_OPTIONS, } from './planningNavigator';
|
|
12
12
|
import { PlanningEntitySummary, PlanningReferenceBadge, } from './PlanningEntitySummary';
|
|
13
|
+
import { PlanningVisualIdentity } from './PlanningVisualIdentity';
|
|
13
14
|
import { PlanningTaskCard } from './PlanningTaskCard';
|
|
14
15
|
import { PlanningTaskArrangeControl } from './PlanningTaskArrangeControl';
|
|
15
16
|
import planningDropdownStyles from './PlanningDropdownControl.module.css';
|
|
@@ -29,6 +30,8 @@ function PlanningWorkspaceEditor({ entityType, mode, initialTitle = '', initialD
|
|
|
29
30
|
const [title, setTitle] = React.useState(initialTitle);
|
|
30
31
|
const [description, setDescription] = React.useState(initialDescription);
|
|
31
32
|
const [ownerId, setOwnerId] = React.useState('');
|
|
33
|
+
const [icon, setIcon] = React.useState(null);
|
|
34
|
+
const [color, setColor] = React.useState(null);
|
|
32
35
|
const [titleError, setTitleError] = React.useState(null);
|
|
33
36
|
const [saveError, setSaveError] = React.useState(null);
|
|
34
37
|
const [isSaving, setIsSaving] = React.useState(false);
|
|
@@ -57,6 +60,7 @@ function PlanningWorkspaceEditor({ entityType, mode, initialTitle = '', initialD
|
|
|
57
60
|
...(entityType === 'workstream' && mode === 'create'
|
|
58
61
|
? { initiativeId: fixedInitiativeId || null }
|
|
59
62
|
: {}),
|
|
63
|
+
...(mode === 'create' && (icon || color) ? { icon, color } : {}),
|
|
60
64
|
});
|
|
61
65
|
}
|
|
62
66
|
catch (error) {
|
|
@@ -72,7 +76,12 @@ function PlanningWorkspaceEditor({ entityType, mode, initialTitle = '', initialD
|
|
|
72
76
|
return;
|
|
73
77
|
event.preventDefault();
|
|
74
78
|
onCancel();
|
|
75
|
-
}, noValidate: true, children: [_jsxs("div", { className: styles.
|
|
79
|
+
}, noValidate: true, children: [mode === 'create' ? (_jsxs("div", { className: styles.workspaceEditorIdentity, children: [_jsx(PlanningVisualIdentity, { entityType: entityType, entityId: "create-preview", icon: icon, color: color || 'blue', size: "detail", onChange: (patch) => {
|
|
80
|
+
if (Object.prototype.hasOwnProperty.call(patch, 'icon'))
|
|
81
|
+
setIcon(patch.icon ?? null);
|
|
82
|
+
if (Object.prototype.hasOwnProperty.call(patch, 'color'))
|
|
83
|
+
setColor(patch.color ?? null);
|
|
84
|
+
} }), _jsx("span", { children: "Choose an icon and color" })] })) : null, _jsxs("div", { className: styles.workspaceEditorField, children: [_jsxs("label", { htmlFor: titleInputId, children: [label, " title"] }), _jsx("input", { ref: titleInputRef, id: titleInputId, value: title, disabled: isSaving, required: true, "aria-invalid": titleError ? 'true' : undefined, "aria-describedby": titleError ? titleErrorId : undefined, onChange: (event) => {
|
|
76
85
|
setTitle(event.target.value);
|
|
77
86
|
if (titleError)
|
|
78
87
|
setTitleError(null);
|
|
@@ -80,24 +89,24 @@ function PlanningWorkspaceEditor({ entityType, mode, initialTitle = '', initialD
|
|
|
80
89
|
? 'Describe the broader outcome this initiative is meant to achieve.'
|
|
81
90
|
: 'Describe the lane of work this workstream will coordinate.' })] }), mode === 'create' && ownerOptions ? (_jsxs("div", { className: styles.workspaceEditorField, children: [_jsxs("span", { children: [label, " owner"] }), _jsx(PlanningOwnerDropdown, { value: ownerId, options: ownerOptions, onChange: setOwnerId, disabled: isSaving, ariaLabel: `${label} owner`, className: `${planningDropdownStyles.control} ${styles.selectedEntityOwnerDropdown}` })] })) : null, saveError ? _jsx("p", { className: "tf-text-error", role: "alert", children: saveError }) : null, showActions ? _jsxs("div", { className: styles.workspaceEditorActions, children: [_jsx("button", { type: "button", className: "tf-button-secondary tf-button-compact", onClick: onCancel, disabled: isSaving, children: "Cancel" }), _jsxs("button", { type: "submit", className: "tf-button-primary tf-button-compact", disabled: isSaving, children: [isSaving ? _jsx(Loader2, { size: 14, className: styles.spinner, "aria-hidden": "true" }) : null, mode === 'create' ? `Create ${label}` : 'Save'] })] }) : null] }));
|
|
82
91
|
}
|
|
83
|
-
function InitiativeButton({ initiative, ownerOption, ownerOptions, workstreamCount, selected, onSelect, onChangeOwner, }) {
|
|
92
|
+
function InitiativeButton({ initiative, ownerOption, ownerOptions, workstreamCount, selected, onSelect, onChangeOwner, onChangeIdentity, }) {
|
|
84
93
|
return (_jsxs("div", { className: `${styles.entityCard} ${initiative.isArchived ? styles.entityCardArchived : ''} ${selected ? styles.entityCardSelected : ''}`.trim(), children: [_jsx(PlanningReferenceBadge, { label: formatInitiativeReference(initiative), entityType: "initiative", entityName: initiative.title, interactive: false }), _jsx("div", { className: styles.entityCardBody, children: _jsx("div", { role: "button", tabIndex: 0, className: styles.entityCardContentButton, onClick: onSelect, onKeyDown: (event) => {
|
|
85
94
|
if (event.key !== 'Enter' && event.key !== ' ')
|
|
86
95
|
return;
|
|
87
96
|
event.preventDefault();
|
|
88
97
|
onSelect();
|
|
89
|
-
}, "aria-pressed": selected, children: _jsx(PlanningEntitySummary, { item: initiative, entityType: "initiative", workstreamCount: workstreamCount, ownerOption: ownerOption, ownerOptions: ownerOptions, onChangeOwner: onChangeOwner }) }) })] }));
|
|
98
|
+
}, "aria-pressed": selected, children: _jsx(PlanningEntitySummary, { item: initiative, entityType: "initiative", workstreamCount: workstreamCount, ownerOption: ownerOption, ownerOptions: ownerOptions, onChangeOwner: onChangeOwner, onChangeIdentity: onChangeIdentity }) }) })] }));
|
|
90
99
|
}
|
|
91
|
-
function WorkstreamButton({ workstream, ownerOption, ownerOptions, selected, onSelect, onChangeOwner, referenceAction, feedback, }) {
|
|
100
|
+
function WorkstreamButton({ workstream, ownerOption, ownerOptions, selected, onSelect, onChangeOwner, onChangeIdentity, referenceAction, feedback, }) {
|
|
92
101
|
const referenceLabel = formatWorkstreamReference(workstream);
|
|
93
102
|
return (_jsxs("div", { className: `${styles.entityCard} ${workstream.isArchived ? styles.entityCardArchived : ''} ${selected ? styles.entityCardSelected : ''}`.trim(), children: [_jsxs("div", { className: styles.entityCardTopRow, children: [referenceAction, _jsx(PlanningReferenceBadge, { label: referenceLabel || 'Reference pending', entityType: "workstream", entityName: workstream.title, interactive: false, pending: !referenceLabel })] }), feedback, _jsx("div", { className: styles.entityCardBody, children: _jsx("div", { role: "button", tabIndex: 0, className: styles.entityCardContentButton, onClick: onSelect, onKeyDown: (event) => {
|
|
94
103
|
if (event.key !== 'Enter' && event.key !== ' ')
|
|
95
104
|
return;
|
|
96
105
|
event.preventDefault();
|
|
97
106
|
onSelect();
|
|
98
|
-
}, "aria-pressed": selected, children: _jsx(PlanningEntitySummary, { item: workstream, entityType: "workstream", ownerOption: ownerOption, ownerOptions: ownerOptions, onChangeOwner: onChangeOwner }) }) })] }));
|
|
107
|
+
}, "aria-pressed": selected, children: _jsx(PlanningEntitySummary, { item: workstream, entityType: "workstream", ownerOption: ownerOption, ownerOptions: ownerOptions, onChangeOwner: onChangeOwner, onChangeIdentity: onChangeIdentity }) }) })] }));
|
|
99
108
|
}
|
|
100
|
-
function SelectedEntityContext({ item, entityType, ownerOptions, onChangeOwner, workspaceId, currentActorId, taskReferences, hydrationState, onRetryHydration, onAddContextFile, onRemoveContextFile, onUpdateContextCaption, onUpdate, onArchive, onUnarchive, referenceAction, relationshipActions, relationshipContent, }) {
|
|
109
|
+
function SelectedEntityContext({ item, entityType, ownerOptions, onChangeOwner, onChangeIdentity, workspaceId, currentActorId, taskReferences, hydrationState, onRetryHydration, onAddContextFile, onRemoveContextFile, onUpdateContextCaption, onUpdate, onArchive, onUnarchive, referenceAction, relationshipActions, relationshipContent, }) {
|
|
101
110
|
const label = entityType === 'initiative' ? 'Initiative' : 'Workstream';
|
|
102
111
|
const referenceLabel = entityType === 'initiative'
|
|
103
112
|
? formatInitiativeReference(item)
|
|
@@ -181,7 +190,9 @@ function SelectedEntityContext({ item, entityType, ownerOptions, onChangeOwner,
|
|
|
181
190
|
setArchiveBusy(false);
|
|
182
191
|
}
|
|
183
192
|
};
|
|
184
|
-
return (_jsxs("section", { className: `${styles.selectedEntityContext} ${item.isArchived ? styles.selectedEntityContextArchived : ''}`.trim(), "aria-label": `Selected ${label.toLowerCase()}`, "aria-busy": hydrationState?.status === 'loading', children: [_jsxs("div", { className: styles.selectedEntityReferenceRow, children: [_jsxs("div", { className: styles.selectedEntityReferenceIdentity, children: [
|
|
193
|
+
return (_jsxs("section", { className: `${styles.selectedEntityContext} ${item.isArchived ? styles.selectedEntityContextArchived : ''}`.trim(), "aria-label": `Selected ${label.toLowerCase()}`, "aria-busy": hydrationState?.status === 'loading', children: [_jsxs("div", { className: styles.selectedEntityReferenceRow, children: [_jsxs("div", { className: styles.selectedEntityReferenceIdentity, children: [_jsx(PlanningVisualIdentity, { entityType: entityType, entityId: item.id, icon: item.icon, color: item.color, size: "detail", disabled: Boolean(item.isArchived), onChange: onChangeIdentity
|
|
194
|
+
? (patch) => onChangeIdentity(entityType, item.id, patch)
|
|
195
|
+
: undefined }), referenceAction, _jsx(PlanningReferenceBadge, { label: referenceLabel || 'Reference pending', entityType: entityType, entityName: item.title, interactive: false, pending: !referenceLabel })] }), _jsxs("div", { className: styles.selectedEntityReferenceActions, children: [_jsx("span", { children: label }), relationshipActions, !isEditing && (onArchive || onUnarchive) ? (_jsx("button", { type: "button", className: "tf-control-icon", disabled: archiveBusy
|
|
185
196
|
|| archiveState.mode === 'blocked'
|
|
186
197
|
|| archiveState.mode === 'checking'
|
|
187
198
|
|| (archiveState.mode === 'archived' ? !onUnarchive : !onArchive), onClick: () => void handleArchiveAction(), "aria-label": archiveActionLabel, title: archiveActionLabel, "aria-expanded": archiveState.mode === 'ready'
|
|
@@ -500,12 +511,16 @@ export function PlanningModule({ model }) {
|
|
|
500
511
|
return (_jsxs("main", { className: styles.module, "aria-label": "Planning workspace", "aria-busy": model.loadState.isRefreshing, children: [model.loadState.isRefreshing ? (_jsx("span", { className: styles.visuallyHidden, role: "status", "aria-live": "polite", children: "Refreshing planning" })) : null, (model.loadState.status === 'partial' || model.loadState.status === 'error') && (_jsxs("div", { className: styles.inlineError, role: "alert", children: [_jsx(AlertTriangle, { size: 16 }), _jsx("span", { children: model.loadState.error || 'Some planning information could not be loaded.' }), _jsx("button", { type: "button", className: "tf-button-secondary tf-button-compact", onClick: model.retry, children: "Retry" })] })), _jsxs("div", { className: styles.columns, "data-active-pane": activePane, children: [_jsxs("section", { className: `${styles.column} ${styles.columnPortfolio} ${activePane === 'portfolio' ? styles.columnActive : ''}`.trim(), "aria-labelledby": "planning-portfolio-heading", children: [_jsxs("div", { className: styles.columnHeader, children: [_jsx("h2", { ref: portfolioHeadingRef, id: "planning-portfolio-heading", className: `tf-heading-section ${styles.paneHeading}`, tabIndex: -1, children: "Portfolio" }), _jsxs("div", { className: styles.headerActions, children: [_jsx("span", { children: visibleInitiatives.length + visibleStandaloneWorkstreams.length }), _jsx("button", { type: "button", className: `tf-control-icon ${showArchivedPlanning ? styles.controlActive : ''}`.trim(), onClick: () => setShowArchivedPlanning((current) => !current), disabled: !hasArchivedPlanning, "aria-label": showArchivedPlanning ? 'Hide archived planning items' : 'Show archived planning items', "aria-pressed": showArchivedPlanning, title: hasArchivedPlanning ? (showArchivedPlanning ? 'Hide archived planning items' : 'Show archived planning items') : 'No archived planning items', children: showArchivedPlanning ? _jsx(EyeOff, { size: 15, "aria-hidden": "true" }) : _jsx(Eye, { size: 15, "aria-hidden": "true" }) })] })] }), _jsxs("div", { className: styles.portfolioControls, children: [_jsxs("div", { className: styles.searchRow, children: [_jsxs("div", { className: styles.searchShell, children: [_jsx(Search, { size: 14, "aria-hidden": "true" }), _jsx("input", { type: "search", value: searchQuery, onChange: (event) => setSearchQuery(event.target.value), placeholder: "Search initiatives and workstreams", "aria-label": "Search planning" }), searchQuery ? (_jsx("button", { type: "button", className: styles.clearSearch, onClick: () => setSearchQuery(''), "aria-label": "Clear planning search", title: "Clear search", children: _jsx(X, { size: 13, "aria-hidden": "true" }) })) : null] }), _jsx("button", { type: "button", className: `tf-control-icon ${filtersExpanded || ownerFilter !== 'all' || sort !== 'default' ? styles.controlActive : ''}`.trim(), "aria-label": "Filter and sort planning", title: "Filter and sort planning", "aria-expanded": filtersExpanded, "aria-controls": "planning-workspace-navigator-options", onClick: () => setFiltersExpanded((current) => !current), children: _jsx(Filter, { size: 15, "aria-hidden": "true" }) })] }), filtersExpanded ? (_jsxs("div", { id: "planning-workspace-navigator-options", className: styles.filterPanel, children: [_jsxs("div", { className: styles.filterField, children: [_jsx("span", { className: styles.filterLabel, children: "Owner" }), _jsx(PlanningOwnerDropdown, { value: ownerFilter, options: model.assigneeOptions, leadingOptions: ownerFilterLeadingOptions, includeUnassigned: false, onChange: setOwnerFilter, disabled: false, ariaLabel: "Filter planning by owner", className: styles.filterDropdown })] }), _jsxs("div", { className: styles.filterField, children: [_jsx("span", { className: styles.filterLabel, children: "Sort" }), _jsx(TaxonomyDropdown, { value: sort, options: PLANNING_NAVIGATOR_SORT_OPTIONS, onChange: (value) => setSort(String(value)), ariaLabel: "Sort planning", className: styles.filterDropdown, portalPanel: true, panelAlign: "start", triggerContent: (_jsxs("span", { className: styles.sortTrigger, children: [_jsx(ArrowUpDown, { size: 14, "aria-hidden": "true" }), _jsx("span", { children: PLANNING_NAVIGATOR_SORT_OPTIONS.find((option) => option.value === sort)?.label })] })), renderOptionContent: (option) => option.label })] }), _jsx("button", { type: "button", className: "tf-control-icon", onClick: clearNavigator, disabled: !hasAdjustedNavigator, "aria-label": "Reset planning filters and sort", title: "Reset planning filters and sort", children: _jsx(RotateCcw, { size: 14, "aria-hidden": "true" }) })] })) : null] }), _jsxs("div", { className: styles.scrollRegion, children: [_jsxs("div", { className: styles.sectionHeadingRow, children: [_jsx("h3", { className: `tf-heading-section ${styles.sectionLabel}`, children: "Initiatives" }), model.createEntity ? (_jsx("button", { type: "button", className: "tf-control-icon", onClick: () => setCreateState({ entityType: 'initiative' }), "aria-label": "Create initiative", title: "Create initiative", children: _jsx(Plus, { size: 15, "aria-hidden": "true" }) })) : null] }), createState?.entityType === 'initiative' ? (_jsx(PlanningWorkspaceEditor, { entityType: "initiative", mode: "create", ownerOptions: model.assigneeOptions, onCancel: () => setCreateState(null), onSave: createPlanningEntity })) : null, navigatorResults.initiatives.map(({ initiative, workstreams: resultWorkstreams }) => {
|
|
501
512
|
return (_jsx(InitiativeButton, { initiative: initiative, workstreamCount: resultWorkstreams.length, ownerOption: initiative.ownerId ? assigneeOptionByValue.get(initiative.ownerId) : undefined, ownerOptions: model.assigneeOptions, selected: selectedInitiativeId === initiative.id, onSelect: () => selectInitiative(initiative.id), onChangeOwner: model.changeOwner
|
|
502
513
|
? (ownerId) => model.changeOwner?.('initiative', initiative.id, ownerId === 'unassigned' ? '' : ownerId)
|
|
514
|
+
: undefined, onChangeIdentity: model.changeIdentity
|
|
515
|
+
? (patch) => model.changeIdentity?.('initiative', initiative.id, patch)
|
|
503
516
|
: undefined }, initiative.id));
|
|
504
517
|
}), _jsxs("div", { className: styles.sectionHeadingRow, children: [_jsx("h3", { className: `tf-heading-section ${styles.sectionLabel}`, children: "Standalone workstreams" }), model.createEntity ? (_jsx("button", { type: "button", className: "tf-control-icon", onClick: () => setCreateState({ entityType: 'workstream', initiativeId: null }), "aria-label": "Create standalone workstream", title: "Create standalone workstream", children: _jsx(Plus, { size: 15, "aria-hidden": "true" }) })) : null] }), createState?.entityType === 'workstream' && !createState.initiativeId ? (_jsx(PlanningWorkspaceEditor, { entityType: "workstream", mode: "create", ownerOptions: model.assigneeOptions, fixedInitiativeId: null, onCancel: () => setCreateState(null), onSave: createPlanningEntity })) : null, visibleStandaloneWorkstreams.map((workstream) => (_jsx(WorkstreamButton, { workstream: workstream, ownerOption: workstream.ownerId ? assigneeOptionByValue.get(workstream.ownerId) : undefined, ownerOptions: model.assigneeOptions, selected: selectedWorkstreamId === workstream.id, onSelect: () => selectWorkstream(workstream), onChangeOwner: model.changeOwner
|
|
505
518
|
? (ownerId) => model.changeOwner?.('workstream', workstream.id, ownerId === 'unassigned' ? '' : ownerId)
|
|
519
|
+
: undefined, onChangeIdentity: model.changeIdentity
|
|
520
|
+
? (patch) => model.changeIdentity?.('workstream', workstream.id, patch)
|
|
506
521
|
: undefined }, workstream.id))), visibleInitiatives.length === 0 && visibleStandaloneWorkstreams.length === 0 && (_jsx("p", { className: `tf-text-helper ${styles.empty}`, children: navigatorResults.hasCriteria ? 'No planning matches.' : 'No planning items yet.' }))] })] }), _jsxs("section", { className: `${styles.column} ${styles.columnWorkstreams} ${activePane === 'workstreams' ? styles.columnActive : ''}`.trim(), "aria-labelledby": "planning-workstreams-heading", children: [_jsxs("div", { className: styles.columnHeader, children: [_jsxs("div", { className: styles.columnHeadingGroup, children: [_jsx("button", { type: "button", className: `tf-control-icon ${styles.responsiveBack}`.trim(), onClick: () => navigateToPane('portfolio'), "aria-label": "Back to Portfolio", title: "Back to Portfolio", children: _jsx(ChevronLeft, { size: 15, "aria-hidden": "true" }) }), _jsxs("h2", { ref: workstreamsHeadingRef, id: "planning-workstreams-heading", className: `tf-heading-section ${styles.paneHeading}`, tabIndex: -1, children: ["Workstreams (", workstreams.length, ")"] })] }), _jsx("div", { className: styles.headerActions, children: selectedInitiative && model.createEntity ? (_jsx("button", { type: "button", className: "tf-control-icon", onClick: () => setCreateState({ entityType: 'workstream', initiativeId: selectedInitiative.id }), "aria-label": `Create workstream in ${selectedInitiative.title}`, title: "Create workstream", children: _jsx(Plus, { size: 15, "aria-hidden": "true" }) })) : null })] }), _jsxs("div", { className: styles.scrollRegion, children: [selectedInitiative ? (_jsxs(_Fragment, { children: [_jsx(SelectedEntityContext, { item: selectedInitiative, entityType: "initiative", ownerOptions: model.assigneeOptions, onChangeOwner: model.changeOwner
|
|
507
522
|
? (ownerId) => model.changeOwner?.('initiative', selectedInitiative.id, ownerId)
|
|
508
|
-
: undefined, workspaceId: model.currentWorkspaceId, currentActorId: model.currentOwnerId, taskReferences: model.taskReferences, hydrationState: selectedWorkstream ? undefined : selectedEntityHydrationState, onRetryHydration: model.hydrateEntity
|
|
523
|
+
: undefined, onChangeIdentity: model.changeIdentity, workspaceId: model.currentWorkspaceId, currentActorId: model.currentOwnerId, taskReferences: model.taskReferences, hydrationState: selectedWorkstream ? undefined : selectedEntityHydrationState, onRetryHydration: model.hydrateEntity
|
|
509
524
|
? () => { void model.hydrateEntity?.('initiative', selectedInitiative.id); }
|
|
510
525
|
: undefined, onAddContextFile: model.addContextFile, onRemoveContextFile: model.removeContextFile, onUpdateContextCaption: model.updateContextCaption, onUpdate: model.updateEntity
|
|
511
526
|
? (input) => model.updateEntity('initiative', selectedInitiative.id, input)
|
|
@@ -515,6 +530,8 @@ export function PlanningModule({ model }) {
|
|
|
515
530
|
? () => model.unarchiveEntity('initiative', selectedInitiative.id)
|
|
516
531
|
: undefined }), _jsx(PlanningLinkPicker, { options: availableWorkstreamLinks, entityLabel: "workstream", onSelect: (option) => attachRelationship('workstream', option), disabled: !model.attachWorkstreamToInitiative || isChangingRelationship })] })) : null, createState?.entityType === 'workstream' && createState.initiativeId === selectedInitiative?.id ? (_jsx(PlanningWorkspaceEditor, { entityType: "workstream", mode: "create", ownerOptions: model.assigneeOptions, fixedInitiativeId: selectedInitiative.id, onCancel: () => setCreateState(null), onSave: createPlanningEntity })) : null, workstreams.map((workstream) => (_jsx(WorkstreamButton, { workstream: workstream, ownerOption: workstream.ownerId ? assigneeOptionByValue.get(workstream.ownerId) : undefined, ownerOptions: model.assigneeOptions, selected: selectedWorkstreamId === workstream.id, onSelect: () => selectWorkstream(workstream), onChangeOwner: model.changeOwner
|
|
517
532
|
? (ownerId) => model.changeOwner?.('workstream', workstream.id, ownerId === 'unassigned' ? '' : ownerId)
|
|
533
|
+
: undefined, onChangeIdentity: model.changeIdentity
|
|
534
|
+
? (patch) => model.changeIdentity?.('workstream', workstream.id, patch)
|
|
518
535
|
: undefined, referenceAction: selectedInitiative ? (_jsx(HierarchyActionBadge, { disabled: !model.assignWorkstreamToInitiative || isChangingRelationship, onClick: () => setPendingUnlink({
|
|
519
536
|
entityType: 'workstream',
|
|
520
537
|
id: workstream.id,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type PlanningIdentityColor, type PlanningIdentityEntityType, type PlanningIdentityIcon } from '../../../shared/planningIdentity';
|
|
2
|
+
export type PlanningIdentityPatch = {
|
|
3
|
+
icon?: PlanningIdentityIcon | null;
|
|
4
|
+
color?: PlanningIdentityColor | null;
|
|
5
|
+
};
|
|
6
|
+
export declare function PlanningVisualIdentity({ entityType, entityId, icon, color, disabled, size, onChange, }: {
|
|
7
|
+
entityType: PlanningIdentityEntityType;
|
|
8
|
+
entityId: string;
|
|
9
|
+
icon?: PlanningIdentityIcon | null;
|
|
10
|
+
color?: PlanningIdentityColor | null;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
size?: 'card' | 'inline' | 'detail';
|
|
13
|
+
onChange?: (patch: PlanningIdentityPatch) => void | Promise<void>;
|
|
14
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
import { BookOpen, Briefcase, Bug, Camera, Code, Compass, Flag, FlaskConical, Folder, Globe, Heart, Landmark, Layers, Lightbulb, Map, Music, Palette, Rocket, Route, Shield, Sparkles, Target, Users, Wrench, } from 'lucide-react';
|
|
5
|
+
import { PLANNING_IDENTITY_COLORS, PLANNING_IDENTITY_ICONS, planningIdentityColorToken, resolvePlanningIdentity, } from '../../../shared/planningIdentity';
|
|
6
|
+
import styles from './PlanningVisualIdentity.module.css';
|
|
7
|
+
const ICONS = {
|
|
8
|
+
Target, Route, Flag, Rocket, Briefcase, Folder, Layers, Map, Compass, Landmark,
|
|
9
|
+
BookOpen, Lightbulb, Wrench, Shield, Bug, Palette, Camera, Music, Heart, Globe,
|
|
10
|
+
Users, Sparkles, FlaskConical, Code,
|
|
11
|
+
};
|
|
12
|
+
export function PlanningVisualIdentity({ entityType, entityId, icon, color, disabled = false, size = 'card', onChange, }) {
|
|
13
|
+
const [open, setOpen] = React.useState(false);
|
|
14
|
+
const [pending, setPending] = React.useState(false);
|
|
15
|
+
const [optimisticPatch, setOptimisticPatch] = React.useState(null);
|
|
16
|
+
const [portalTheme, setPortalTheme] = React.useState();
|
|
17
|
+
const [popoverPosition, setPopoverPosition] = React.useState({ top: 0, left: 0 });
|
|
18
|
+
const rootRef = React.useRef(null);
|
|
19
|
+
const triggerRef = React.useRef(null);
|
|
20
|
+
const popoverRef = React.useRef(null);
|
|
21
|
+
const optimisticIcon = optimisticPatch && Object.prototype.hasOwnProperty.call(optimisticPatch, 'icon')
|
|
22
|
+
? optimisticPatch.icon
|
|
23
|
+
: icon;
|
|
24
|
+
const optimisticColor = optimisticPatch && Object.prototype.hasOwnProperty.call(optimisticPatch, 'color')
|
|
25
|
+
? optimisticPatch.color
|
|
26
|
+
: color;
|
|
27
|
+
const resolved = resolvePlanningIdentity({
|
|
28
|
+
entityType,
|
|
29
|
+
entityId,
|
|
30
|
+
icon: optimisticIcon,
|
|
31
|
+
color: optimisticColor,
|
|
32
|
+
});
|
|
33
|
+
const Icon = ICONS[resolved.icon];
|
|
34
|
+
const editable = !disabled && Boolean(onChange);
|
|
35
|
+
React.useEffect(() => {
|
|
36
|
+
if (!open)
|
|
37
|
+
return undefined;
|
|
38
|
+
const close = (event) => {
|
|
39
|
+
const target = event.target;
|
|
40
|
+
if (!rootRef.current?.contains(target) && !popoverRef.current?.contains(target))
|
|
41
|
+
setOpen(false);
|
|
42
|
+
};
|
|
43
|
+
document.addEventListener('pointerdown', close);
|
|
44
|
+
return () => document.removeEventListener('pointerdown', close);
|
|
45
|
+
}, [open]);
|
|
46
|
+
React.useLayoutEffect(() => {
|
|
47
|
+
if (!open)
|
|
48
|
+
return undefined;
|
|
49
|
+
const positionPopover = () => {
|
|
50
|
+
const triggerRect = triggerRef.current?.getBoundingClientRect();
|
|
51
|
+
if (!triggerRect)
|
|
52
|
+
return;
|
|
53
|
+
setPortalTheme(triggerRef.current?.closest('[data-theme]')?.dataset.theme);
|
|
54
|
+
const popoverRect = popoverRef.current?.getBoundingClientRect();
|
|
55
|
+
const width = popoverRect?.width || 248;
|
|
56
|
+
const height = popoverRect?.height || 320;
|
|
57
|
+
const gap = 8;
|
|
58
|
+
const viewportPadding = 8;
|
|
59
|
+
const left = Math.max(viewportPadding, Math.min(triggerRect.left, window.innerWidth - width - viewportPadding));
|
|
60
|
+
const preferredTop = triggerRect.bottom + gap;
|
|
61
|
+
const top = preferredTop + height <= window.innerHeight - viewportPadding
|
|
62
|
+
? preferredTop
|
|
63
|
+
: Math.max(viewportPadding, triggerRect.top - height - gap);
|
|
64
|
+
setPopoverPosition({ top, left });
|
|
65
|
+
};
|
|
66
|
+
positionPopover();
|
|
67
|
+
window.addEventListener('resize', positionPopover);
|
|
68
|
+
window.addEventListener('scroll', positionPopover, true);
|
|
69
|
+
return () => {
|
|
70
|
+
window.removeEventListener('resize', positionPopover);
|
|
71
|
+
window.removeEventListener('scroll', positionPopover, true);
|
|
72
|
+
};
|
|
73
|
+
}, [open]);
|
|
74
|
+
React.useEffect(() => {
|
|
75
|
+
if (!optimisticPatch)
|
|
76
|
+
return;
|
|
77
|
+
const iconSettled = !Object.prototype.hasOwnProperty.call(optimisticPatch, 'icon')
|
|
78
|
+
|| (optimisticPatch.icon ?? null) === (icon ?? null);
|
|
79
|
+
const colorSettled = !Object.prototype.hasOwnProperty.call(optimisticPatch, 'color')
|
|
80
|
+
|| (optimisticPatch.color ?? null) === (color ?? null);
|
|
81
|
+
if (iconSettled && colorSettled)
|
|
82
|
+
setOptimisticPatch(null);
|
|
83
|
+
}, [color, icon, optimisticPatch]);
|
|
84
|
+
const commit = async (patch) => {
|
|
85
|
+
if (!onChange || pending)
|
|
86
|
+
return;
|
|
87
|
+
setOptimisticPatch((current) => ({ ...current, ...patch }));
|
|
88
|
+
setPending(true);
|
|
89
|
+
try {
|
|
90
|
+
await onChange(patch);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
setOptimisticPatch(null);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
setPending(false);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const marker = (_jsx("span", { className: `${styles.marker} ${styles[size]} ${disabled ? styles.disabled : ''}`.trim(), style: { color: planningIdentityColorToken(resolved.color) }, "aria-hidden": "true", children: _jsx(Icon, { size: size === 'detail' ? 26 : size === 'inline' ? 18 : 24, strokeWidth: 1.9 }) }));
|
|
100
|
+
if (!editable)
|
|
101
|
+
return marker;
|
|
102
|
+
return (_jsxs("div", { ref: rootRef, className: styles.root, onClick: (event) => event.stopPropagation(), children: [_jsx("button", { ref: triggerRef, type: "button", className: styles.trigger, onClick: () => setOpen((current) => !current), onKeyDown: (event) => event.stopPropagation(), "aria-label": `Change ${entityType} icon and color`, "aria-expanded": open, disabled: pending, children: marker }), open ? createPortal((_jsxs("div", { ref: popoverRef, className: styles.popover, role: "dialog", "aria-label": `${entityType} icon and color`, "aria-busy": pending, "data-theme": portalTheme, style: popoverPosition, onClick: (event) => event.stopPropagation(), onKeyDown: (event) => event.stopPropagation(), children: [_jsx("div", { className: styles.colorGrid, "aria-label": "Colors", children: PLANNING_IDENTITY_COLORS.map((option) => (_jsx("button", { type: "button", className: `${styles.colorOption} ${resolved.color === option ? styles.selected : ''}`.trim(), style: { backgroundColor: planningIdentityColorToken(option) }, onClick: () => void commit({ color: option }), "aria-label": `${option} color`, "aria-pressed": resolved.color === option, disabled: pending }, option))) }), _jsx("div", { className: styles.divider }), _jsx("div", { className: styles.iconGrid, "aria-label": "Icons", children: PLANNING_IDENTITY_ICONS.map((option) => {
|
|
103
|
+
const OptionIcon = ICONS[option];
|
|
104
|
+
return (_jsx("button", { type: "button", className: `${styles.iconOption} ${resolved.icon === option ? styles.selected : ''}`.trim(), onClick: () => void commit({ icon: option }), "aria-label": option, "aria-pressed": resolved.icon === option, disabled: pending, children: _jsx(OptionIcon, { size: 18, strokeWidth: 1.8 }) }, option));
|
|
105
|
+
}) }), _jsx("div", { className: styles.divider }), _jsx("button", { type: "button", className: styles.reset, onClick: () => void commit({ icon: null, color: null }), disabled: !resolved.customized || pending, children: "Reset to default" })] })), document.body) : null] }));
|
|
106
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { TaskAssigneeOption } from '../../../utils/assignees';
|
|
|
3
3
|
import type { TaskReferenceContract } from '../../../utils/taskReferences';
|
|
4
4
|
import type { TaskforceTheme } from '../../../utils/theme';
|
|
5
5
|
import type { PlanningTaskArrangeMode } from './planningTaskArrangement';
|
|
6
|
+
import type { PlanningIdentityColor, PlanningIdentityIcon } from '../../../shared/planningIdentity';
|
|
6
7
|
export type PlanningStatusCounts = Record<TaskStatus, number>;
|
|
7
8
|
export declare function shouldUsePlanningBootstrapTaskSummaries(input: {
|
|
8
9
|
taskScope: 'open' | 'archived' | 'deleted';
|
|
@@ -34,6 +35,8 @@ export type PlanningNodeSummary = {
|
|
|
34
35
|
description?: string;
|
|
35
36
|
ownerId?: string | null;
|
|
36
37
|
ownerLabel?: string | null;
|
|
38
|
+
icon?: PlanningIdentityIcon | null;
|
|
39
|
+
color?: PlanningIdentityColor | null;
|
|
37
40
|
progressPercent: number;
|
|
38
41
|
taskCount: number;
|
|
39
42
|
completedTaskCount: number;
|
|
@@ -67,12 +70,16 @@ export type PlanningEntitySaveInput = {
|
|
|
67
70
|
description: string | null;
|
|
68
71
|
ownerId?: string | null;
|
|
69
72
|
initiativeId?: string | null;
|
|
73
|
+
icon?: PlanningIdentityIcon | null;
|
|
74
|
+
color?: PlanningIdentityColor | null;
|
|
70
75
|
};
|
|
71
76
|
export type PlanningLinkOption = {
|
|
72
77
|
id: string;
|
|
73
78
|
referenceLabel: string;
|
|
74
79
|
title: string;
|
|
75
80
|
meta?: string;
|
|
81
|
+
icon?: PlanningIdentityIcon | null;
|
|
82
|
+
color?: PlanningIdentityColor | null;
|
|
76
83
|
};
|
|
77
84
|
export type PlanningLinkOptions = {
|
|
78
85
|
initiatives: PlanningLinkOption[];
|
|
@@ -107,6 +114,10 @@ export type PlanningWorkspaceModel = {
|
|
|
107
114
|
archiveEntity?: (entityType: 'initiative' | 'workstream', entityId: string) => boolean | Promise<boolean>;
|
|
108
115
|
unarchiveEntity?: (entityType: 'initiative' | 'workstream', entityId: string) => boolean | Promise<boolean>;
|
|
109
116
|
changeOwner?: (entityType: 'initiative' | 'workstream', entityId: string, ownerId: string) => void | Promise<void>;
|
|
117
|
+
changeIdentity?: (entityType: 'initiative' | 'workstream', entityId: string, patch: {
|
|
118
|
+
icon?: PlanningIdentityIcon | null;
|
|
119
|
+
color?: PlanningIdentityColor | null;
|
|
120
|
+
}) => void | Promise<void>;
|
|
110
121
|
createEntity?: (entityType: 'initiative' | 'workstream', input: PlanningEntitySaveInput) => Promise<{
|
|
111
122
|
id: string;
|
|
112
123
|
}>;
|
|
@@ -4,6 +4,7 @@ import type { TaskAutoSaveState } from '../../hooks/tasks/useTaskFormActions';
|
|
|
4
4
|
interface TaskActionHeaderProps {
|
|
5
5
|
editingTaskId: string | null;
|
|
6
6
|
loading: boolean;
|
|
7
|
+
submitDisabled?: boolean;
|
|
7
8
|
autoSaveState?: TaskAutoSaveState;
|
|
8
9
|
title: string;
|
|
9
10
|
status?: TaskStatus;
|
|
@@ -30,5 +31,5 @@ interface TaskActionHeaderProps {
|
|
|
30
31
|
handlePermanentlyDeleteDeletedTask?: (deletedRecordId: string) => void;
|
|
31
32
|
onAskAgent?: (task: TaskItem) => void;
|
|
32
33
|
}
|
|
33
|
-
export declare function TaskActionHeader({ editingTaskId, loading, autoSaveState, title, status, onStatusChange, showCreateSubmit, handleSubmit, handleCopyId, copiedId, tasks, currentTask: currentTaskProp, currentTaskWorkstream, currentTaskInitiative, workstreams, onWorkstreamInputChange, onSetWorkstreamForCurrentTask, handleToggleInProgress, handleToggleReview, handleToggleComplete, handleToggleCancel, handleSetStatus, handleArchiveTask, handleUnarchiveTask, handleRestoreDeletedTask, handlePermanentlyDeleteDeletedTask, onAskAgent }: TaskActionHeaderProps): import("react/jsx-runtime").JSX.Element;
|
|
34
|
+
export declare function TaskActionHeader({ editingTaskId, loading, submitDisabled, autoSaveState, title, status, onStatusChange, showCreateSubmit, handleSubmit, handleCopyId, copiedId, tasks, currentTask: currentTaskProp, currentTaskWorkstream, currentTaskInitiative, workstreams, onWorkstreamInputChange, onSetWorkstreamForCurrentTask, handleToggleInProgress, handleToggleReview, handleToggleComplete, handleToggleCancel, handleSetStatus, handleArchiveTask, handleUnarchiveTask, handleRestoreDeletedTask, handlePermanentlyDeleteDeletedTask, onAskAgent }: TaskActionHeaderProps): import("react/jsx-runtime").JSX.Element;
|
|
34
35
|
export {};
|
|
@@ -9,7 +9,7 @@ import { TaskReferenceBadge } from './TaskReferenceBadge';
|
|
|
9
9
|
import { HierarchyActionBadge } from '../ui/HierarchyActionBadge';
|
|
10
10
|
import { formatInitiativeReference, getWorkstreamReferenceLabel } from '../../utils/planningReferences';
|
|
11
11
|
import { TaskWorkstreamPicker } from './TaskWorkstreamPicker';
|
|
12
|
-
export function TaskActionHeader({ editingTaskId, loading, autoSaveState = 'idle', title, status = 'task', onStatusChange, showCreateSubmit = true, handleSubmit, handleCopyId, copiedId, tasks, currentTask: currentTaskProp, currentTaskWorkstream, currentTaskInitiative, workstreams = [], onWorkstreamInputChange, onSetWorkstreamForCurrentTask, handleToggleInProgress, handleToggleReview, handleToggleComplete, handleToggleCancel, handleSetStatus, handleArchiveTask, handleUnarchiveTask, handleRestoreDeletedTask, handlePermanentlyDeleteDeletedTask, onAskAgent }) {
|
|
12
|
+
export function TaskActionHeader({ editingTaskId, loading, submitDisabled = false, autoSaveState = 'idle', title, status = 'task', onStatusChange, showCreateSubmit = true, handleSubmit, handleCopyId, copiedId, tasks, currentTask: currentTaskProp, currentTaskWorkstream, currentTaskInitiative, workstreams = [], onWorkstreamInputChange, onSetWorkstreamForCurrentTask, handleToggleInProgress, handleToggleReview, handleToggleComplete, handleToggleCancel, handleSetStatus, handleArchiveTask, handleUnarchiveTask, handleRestoreDeletedTask, handlePermanentlyDeleteDeletedTask, onAskAgent }) {
|
|
13
13
|
const currentTask = currentTaskProp ?? (editingTaskId ? tasks.find(t => t.id === editingTaskId) || null : null);
|
|
14
14
|
const isArchivedReadOnly = Boolean(currentTask?.isArchived);
|
|
15
15
|
const isDeletedReadOnly = Boolean(currentTask?.isDeleted);
|
|
@@ -67,5 +67,5 @@ export function TaskActionHeader({ editingTaskId, loading, autoSaveState = 'idle
|
|
|
67
67
|
if (status === 'cancelled') {
|
|
68
68
|
handleToggleCancel(task);
|
|
69
69
|
}
|
|
70
|
-
}, onArchiveTask: handleArchiveTask, onUnarchiveTask: (task) => handleUnarchiveTask?.(task.id) }))] })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.taskSaveStatusCenter }), _jsxs("div", { className: styles.editActionsGroup, children: [_jsx(TaskStatusActions, { status: status, disabled: loading, archiveActionMode: "none", onStatusChange: onStatusChange }), showCreateSubmit ? (_jsxs("button", { type: "button", onClick: (e) => handleSubmit(e), disabled: loading || !title.trim(), className: styles.primaryUpdateBtn, title: t('actionHeader.addTaskTitle'), children: [loading ? _jsx(Loader2, { size: 16, className: styles.spinner }) : _jsx(Plus, { size: 16 }), t('actionHeader.addTask')] })) : null] })] }))] }));
|
|
70
|
+
}, onArchiveTask: handleArchiveTask, onUnarchiveTask: (task) => handleUnarchiveTask?.(task.id) }))] })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.taskSaveStatusCenter }), _jsxs("div", { className: styles.editActionsGroup, children: [_jsx(TaskStatusActions, { status: status, disabled: loading, archiveActionMode: "none", onStatusChange: onStatusChange }), showCreateSubmit ? (_jsxs("button", { type: "button", onClick: (e) => handleSubmit(e), disabled: loading || submitDisabled || !title.trim(), className: styles.primaryUpdateBtn, title: t('actionHeader.addTaskTitle'), children: [loading ? _jsx(Loader2, { size: 16, className: styles.spinner }) : _jsx(Plus, { size: 16 }), t('actionHeader.addTask')] })) : null] })] }))] }));
|
|
71
71
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import * as Icons from 'lucide-react';
|
|
4
|
-
const { MessageSquare, Gauge, ClipboardList, HelpCircle
|
|
4
|
+
const { MessageSquare, Gauge, ClipboardList, HelpCircle } = Icons;
|
|
5
5
|
import styles from '../../Taskforce.module.css';
|
|
6
6
|
import cardStyles from './TaskCard.module.css';
|
|
7
7
|
import { Markdown } from '../ui/Markdown';
|
|
@@ -22,6 +22,7 @@ import { getInitiativeReferenceLabel, getWorkstreamReferenceLabel } from '../../
|
|
|
22
22
|
import { TaxonomyDropdown } from '../ui/TaxonomyDropdown';
|
|
23
23
|
import { AssigneeAvatar, AssigneeIdentity } from '../ui/AssigneeIdentity';
|
|
24
24
|
import { TaskWorkstreamPicker } from './TaskWorkstreamPicker';
|
|
25
|
+
import { DocumentTypeIcon } from '../ui/DocumentTypeIcon';
|
|
25
26
|
const TaskCardInner = ({ task, searchQuery = '', copiedId = null, taxonomies = [], types = [], priorities = [], categories = [], assigneeOptions = [], workstreams = [], taskWorkstream = null, taskInitiative = null, workspaceId = null, isOverlay = false, isArchived = false, readOnlyMode = null, isRecentlyChanged = false, selectable = false, selected = false, onSelectionChange, onClick, onCopyId, onToggleInProgress, onToggleReview, onToggleComplete, onToggleCancel, onSetStatus, onArchiveTask, onUnarchive, onDelete, deleteActionTitle, taskReferences, onAssigneeChange, onWorkstreamChange, compressed = false, showStatusLabel = false }) => {
|
|
26
27
|
const taskReference = getTaskReferenceDisplayState(task);
|
|
27
28
|
const taskReferenceLabel = taskReference.label || (taskReference.isProvisional ? 'Pending' : '');
|
|
@@ -228,6 +229,24 @@ const TaskCardInner = ({ task, searchQuery = '', copiedId = null, taxonomies = [
|
|
|
228
229
|
workspaceId,
|
|
229
230
|
});
|
|
230
231
|
};
|
|
232
|
+
const cardCoverAttachment = (() => {
|
|
233
|
+
if (compressed || isOverlay || !task.cardCoverAssetId)
|
|
234
|
+
return null;
|
|
235
|
+
return (task.attachments || []).find((attachment) => (typeof attachment !== 'string'
|
|
236
|
+
&& String(attachment.assetId || '').trim() === task.cardCoverAssetId
|
|
237
|
+
&& attachment.linkRole === 'image'
|
|
238
|
+
&& isImageAttachment(attachment))) || null;
|
|
239
|
+
})();
|
|
240
|
+
const cardCoverIdentity = cardCoverAttachment
|
|
241
|
+
? `${cardCoverAttachment.assetId || ''}:${cardCoverAttachment.path}`
|
|
242
|
+
: '';
|
|
243
|
+
const cardAttachments = (task.attachments || []).filter((attachment) => (!cardCoverAttachment
|
|
244
|
+
|| typeof attachment === 'string'
|
|
245
|
+
|| attachment.assetId !== cardCoverAttachment.assetId));
|
|
246
|
+
const [cardCoverLoadFailed, setCardCoverLoadFailed] = React.useState(false);
|
|
247
|
+
React.useEffect(() => {
|
|
248
|
+
setCardCoverLoadFailed(false);
|
|
249
|
+
}, [cardCoverIdentity]);
|
|
231
250
|
return (_jsxs("div", { className: cardClasses, onClick: () => onClick?.(task), style: cardStyle, "data-task-card": "true", children: [_jsxs("div", { className: cardStyles.taskHeader, children: [selectable && (_jsx("label", { className: cardStyles.selectionControl, onClick: (event) => event.stopPropagation(), onPointerDown: (event) => event.stopPropagation(), children: _jsx("input", { type: "checkbox", checked: selected, onChange: (event) => onSelectionChange?.(event.target.checked), "aria-label": `Select ${taskReferenceLabel || task.title} for restore` }) })), _jsxs("div", { className: cardStyles.taskReferenceCluster, children: [taskInitiative ? (_jsxs(_Fragment, { children: [_jsx(TaskReferenceBadge, { copied: copiedId === initiativeReferenceLabel, onClick: (event) => handleReferenceCopy(event, initiativeReferenceLabel), disabled: !initiativeReferenceLabel, title: taskInitiative.title, ariaLabel: `Copy initiative reference ${initiativeReferenceLabel}: ${taskInitiative.title}`, className: isArchived ? cardStyles.archiveBadge : '', label: initiativeReferenceLabel }), _jsx("span", { className: cardStyles.taskReferenceDivider, children: "/" })] })) : null, taskWorkstream ? (_jsxs(_Fragment, { children: [_jsx(TaskReferenceBadge, { copied: copiedId === workstreamReferenceLabel, onClick: (event) => handleReferenceCopy(event, workstreamReferenceLabel), disabled: !workstreamReferenceLabel, title: taskWorkstream.title, ariaLabel: `Copy workstream reference ${workstreamReferenceLabel}: ${taskWorkstream.title}`, className: isArchived ? cardStyles.archiveBadge : '', label: workstreamReferenceLabel }), _jsx("span", { className: cardStyles.taskReferenceDivider, children: "/" })] })) : null, canChangeWorkstream && !taskWorkstream ? (_jsx("div", { className: cardStyles.taskReferenceSegment, onClick: (event) => event.stopPropagation(), onPointerDown: (event) => event.stopPropagation(), onKeyDown: (event) => event.stopPropagation(), children: _jsx(TaskWorkstreamPicker, { workstreams: workstreams, disabled: workstreamChangeBusy, onSelect: (workstream) => {
|
|
232
251
|
void handleWorkstreamChange(workstream.id);
|
|
233
252
|
} }) })) : null, taskReferenceLabel ? (_jsx("div", { className: cardStyles.taskReferenceSegment, onClick: (event) => event.stopPropagation(), onPointerDown: (event) => event.stopPropagation(), onKeyDown: (event) => event.stopPropagation(), children: _jsx(TaskReferenceBadge, { copied: canCopyTaskReference && copiedId === taskReference.label, onClick: (e) => handleReferenceCopy(e, taskReference.label), disabled: !canCopyTaskReference, title: isProvisionalReference
|
|
@@ -256,7 +275,20 @@ const TaskCardInner = ({ task, searchQuery = '', copiedId = null, taxonomies = [
|
|
|
256
275
|
}
|
|
257
276
|
}, onArchiveTask: onArchiveTask })) : ((onUnarchive || onDelete) ? (_jsxs(_Fragment, { children: [onUnarchive && (_jsx("button", { type: "button", className: styles.actionBtn, onClick: () => onUnarchive(task.id), title: resolvedReadOnlyMode === 'deleted'
|
|
258
277
|
? `Restore to ${task.isArchived === true ? 'Archived' : 'Active'}`
|
|
259
|
-
: 'Restore archived task', children: _jsx(Icons.RotateCcw, { size: 16 }) })), onDelete && (_jsx("button", { type: "button", className: `${styles.actionBtn} ${styles.deleteBtn}`, onClick: () => onDelete(task.id), title: deleteActionTitle || (resolvedReadOnlyMode === 'deleted' ? 'Delete Permanently' : 'Move to Trash'), children: _jsx(Icons.Trash2, { size: 16 }) }))] })) : null) })] }),
|
|
278
|
+
: 'Restore archived task', children: _jsx(Icons.RotateCcw, { size: 16 }) })), onDelete && (_jsx("button", { type: "button", className: `${styles.actionBtn} ${styles.deleteBtn}`, onClick: () => onDelete(task.id), title: deleteActionTitle || (resolvedReadOnlyMode === 'deleted' ? 'Delete Permanently' : 'Move to Trash'), children: _jsx(Icons.Trash2, { size: 16 }) }))] })) : null) })] }), cardCoverAttachment && !cardCoverLoadFailed && (_jsx("button", { type: "button", className: cardStyles.taskCover, "aria-label": `Preview ${getContextName(cardCoverAttachment)}`, onPointerDown: (event) => event.stopPropagation(), onClick: (event) => {
|
|
279
|
+
event.stopPropagation();
|
|
280
|
+
if (handleAttachmentActivation(cardCoverAttachment))
|
|
281
|
+
return;
|
|
282
|
+
if (dispatchOpenAnnotatedAttachment(cardCoverAttachment, {
|
|
283
|
+
taskId: task.id,
|
|
284
|
+
taskReferenceLabel,
|
|
285
|
+
}))
|
|
286
|
+
return;
|
|
287
|
+
window.open(cardCoverAttachment.path, '_blank');
|
|
288
|
+
}, children: _jsx("img", { src: cardCoverAttachment.path, alt: cardCoverAttachment.caption
|
|
289
|
+
|| cardCoverAttachment.displayName
|
|
290
|
+
|| cardCoverAttachment.originalFilename
|
|
291
|
+
|| task.title, loading: "lazy", decoding: "async", onError: () => setCardCoverLoadFailed(true) }) })), _jsxs("div", { className: cardStyles.taskContent, children: [/* Meta Badges moved to bottom */ isReadOnly && (_jsx("div", { className: `${cardStyles.taskMeta} ${cardStyles.taskMetaCompact}` })), _jsxs("div", { children: [_jsxs("div", { className: cardStyles.taskTitle, style: compressed ? { fontSize: '13px', lineHeight: '1.4' } : {}, children: [resolvedReadOnlyMode === 'archived' && '✓ ', resolvedReadOnlyMode === 'deleted' && 'Deleted: ', renderHighlighted(task.title, searchQuery)] }), deletedExpiryLabel && (_jsx("div", { className: cardStyles.deletedExpiry, title: "Trash items are permanently deleted 90 days after deletion.", children: deletedExpiryLabel })), task.description && !isReadOnly && !compressed && (_jsx("div", { className: cardStyles.taskDescription, children: _jsx(Markdown, { variant: "compact", taskReferences: taskReferences, children: task.description }) }))] }), cardAttachments.length > 0 && !isReadOnly && !compressed && (_jsxs("div", { className: cardStyles.attachmentThumbnails, children: [_jsx("div", { className: cardStyles.attachmentImageRow, children: cardAttachments.map((attachment, idx) => {
|
|
260
292
|
const path = typeof attachment === 'string' ? attachment : attachment.path;
|
|
261
293
|
if (!isImageAttachment(attachment))
|
|
262
294
|
return null;
|
|
@@ -268,13 +300,14 @@ const TaskCardInner = ({ task, searchQuery = '', copiedId = null, taxonomies = [
|
|
|
268
300
|
return;
|
|
269
301
|
window.open(path, '_blank');
|
|
270
302
|
}, children: _jsx("img", { src: path, alt: `Attachment ${idx}` }) }, idx));
|
|
271
|
-
}) }), _jsx("div", { className: cardStyles.attachmentDocumentList, children:
|
|
303
|
+
}) }), _jsx("div", { className: cardStyles.attachmentDocumentList, children: cardAttachments.map((attachment, idx) => {
|
|
272
304
|
const path = typeof attachment === 'string' ? attachment : attachment.path;
|
|
273
305
|
const fsPath = typeof attachment === 'string' ? undefined : attachment.fsPath;
|
|
274
306
|
const name = getContextName(attachment);
|
|
275
307
|
if (isImageAttachment(attachment))
|
|
276
308
|
return null;
|
|
277
309
|
const fileType = getContextType(attachment);
|
|
310
|
+
const fileAccentColor = getFileTypeAccentColor(fileType);
|
|
278
311
|
return (_jsxs("button", { type: "button", className: cardStyles.contextDocThumb, onPointerDown: (event) => event.stopPropagation(), onClick: (e) => {
|
|
279
312
|
e.stopPropagation();
|
|
280
313
|
if (handleAttachmentActivation(attachment))
|
|
@@ -285,7 +318,7 @@ const TaskCardInner = ({ task, searchQuery = '', copiedId = null, taxonomies = [
|
|
|
285
318
|
if (dispatchOpenMarkdownDocument(markdownTarget, fsPath))
|
|
286
319
|
return;
|
|
287
320
|
window.open(path, '_blank');
|
|
288
|
-
}, title: name, style: { '--context-doc-accent':
|
|
321
|
+
}, title: name, "aria-label": `${name} ${fileType}`, style: { '--context-doc-accent': fileAccentColor }, children: [_jsx(DocumentTypeIcon, { label: fileType, accentColor: fileAccentColor, compact: true }), _jsx("span", { className: cardStyles.contextDocMain, children: _jsx("span", { className: cardStyles.contextDocName, children: name }) })] }, idx));
|
|
289
322
|
}) })] })), task.status === 'cancelled' && task.canceledReason && !compressed && (_jsxs("div", { className: cardStyles.taskCancellationReason, children: [_jsx("strong", { children: "Cancellation Reason:" }), " ", renderHighlighted(task.canceledReason, searchQuery)] })), (latestActivityText || commentCount > 0) && !isReadOnly && !compressed && (_jsxs("div", { className: cardStyles.taskLatestComment, "data-task-activity-summary": "true", children: [_jsxs("div", { className: cardStyles.taskLatestCommentHeader, children: [_jsx("strong", { children: latestActivityText ? 'Latest Activity:' : 'Activity:' }), commentCount > 0 && (_jsxs("span", { className: `${styles.metaBadge} ${cardStyles.taskCommentCount}`, title: `${commentCount} ${commentCount === 1 ? 'comment' : 'comments'}`, "aria-label": `${commentCount} ${commentCount === 1 ? 'comment' : 'comments'}`, children: [_jsx(MessageSquare, { size: 12, "aria-hidden": "true" }), _jsx("span", { children: commentCount })] }))] }), latestActivityText && (_jsx("div", { className: cardStyles.taskLatestCommentText, title: latestActivityText, children: renderHighlighted(latestActivityText, searchQuery) }))] })), !isReadOnly && visibleTaxonomies.length > 0 && !compressed && (_jsx("div", { className: `${cardStyles.taskSpecialists} ${cardStyles.taxonomiesList}`, children: visibleTaxonomies.map(tax => {
|
|
290
323
|
const val = task.taxonomies?.[tax.id];
|
|
291
324
|
if (!val)
|
|
@@ -9,6 +9,9 @@ interface TaskContextUploadProps {
|
|
|
9
9
|
onAddContextFile: (file: AttachmentItem) => void;
|
|
10
10
|
onRemoveContextFile: (index: number) => void;
|
|
11
11
|
onUpdateContextCaption: (index: number, caption: string) => void;
|
|
12
|
+
cardCoverAssetId?: string | null;
|
|
13
|
+
onSetCardCover?: (assetId: string | null) => void | Promise<void>;
|
|
14
|
+
cardCoverDisabled?: boolean;
|
|
12
15
|
}
|
|
13
16
|
export declare function TaskContextUpload(props: TaskContextUploadProps): import("react/jsx-runtime").JSX.Element;
|
|
14
17
|
export {};
|
|
@@ -2,6 +2,6 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { ContextAttachmentManager } from '../context/ContextAttachmentManager';
|
|
3
3
|
import { TaskImageReviews } from './TaskImageReviews';
|
|
4
4
|
export function TaskContextUpload(props) {
|
|
5
|
-
const { taskId, taskReferenceLabel, workspaceId, apiBaseUrl, showImageReviews = false, contextFiles, onAddContextFile, onRemoveContextFile, onUpdateContextCaption, } = props;
|
|
6
|
-
return (_jsx(ContextAttachmentManager, { ownerType: "task", ownerId: taskId, ownerReferenceLabel: taskReferenceLabel, workspaceId: workspaceId, apiBaseUrl: apiBaseUrl, attachments: contextFiles, onAddAttachment: onAddContextFile, onRemoveAttachment: onRemoveContextFile, onUpdateAttachmentCaption: onUpdateContextCaption, supplementalContent: showImageReviews && taskId && workspaceId ? (_jsx(TaskImageReviews, { taskId: taskId, taskReferenceLabel: taskReferenceLabel, workspaceId: workspaceId, apiBaseUrl: apiBaseUrl })) : null }));
|
|
5
|
+
const { taskId, taskReferenceLabel, workspaceId, apiBaseUrl, showImageReviews = false, contextFiles, onAddContextFile, onRemoveContextFile, onUpdateContextCaption, cardCoverAssetId, onSetCardCover, cardCoverDisabled, } = props;
|
|
6
|
+
return (_jsx(ContextAttachmentManager, { ownerType: "task", ownerId: taskId, ownerReferenceLabel: taskReferenceLabel, workspaceId: workspaceId, apiBaseUrl: apiBaseUrl, attachments: contextFiles, onAddAttachment: onAddContextFile, onRemoveAttachment: onRemoveContextFile, onUpdateAttachmentCaption: onUpdateContextCaption, cardCoverAssetId: cardCoverAssetId, onSetCardCover: onSetCardCover, cardCoverDisabled: cardCoverDisabled, supplementalContent: showImageReviews && taskId && workspaceId ? (_jsx(TaskImageReviews, { taskId: taskId, taskReferenceLabel: taskReferenceLabel, workspaceId: workspaceId, apiBaseUrl: apiBaseUrl })) : null }));
|
|
7
7
|
}
|
|
@@ -22,6 +22,7 @@ interface TaskFormProps {
|
|
|
22
22
|
newCommentText: string;
|
|
23
23
|
contextFiles: Array<string | AttachmentItem>;
|
|
24
24
|
currentWorkspaceId?: string | null;
|
|
25
|
+
descriptionImageDraftId?: string | null;
|
|
25
26
|
currentActorId?: string | null;
|
|
26
27
|
apiBaseUrl?: string;
|
|
27
28
|
runtimeMode?: 'local' | 'cloud';
|
|
@@ -73,9 +74,12 @@ interface TaskFormProps {
|
|
|
73
74
|
onOpenSettings?: (section: "categories" | "types") => void;
|
|
74
75
|
onSubmit: (e: React.FormEvent) => void;
|
|
75
76
|
onAddComment: () => void;
|
|
77
|
+
onDescriptionImageUploadStart?: (upload: Promise<void>) => void;
|
|
76
78
|
onAddContextFile: (file: AttachmentItem) => void;
|
|
77
79
|
onRemoveContextFile: (index: number) => void;
|
|
78
80
|
onUpdateContextCaption: (index: number, caption: string) => void;
|
|
81
|
+
onSetCardCover?: (assetId: string | null) => void | Promise<void>;
|
|
82
|
+
cardCoverDisabled?: boolean;
|
|
79
83
|
onOpenContextImage?: (attachment: AttachmentItem) => void;
|
|
80
84
|
onCopyId: (e: React.MouseEvent, id: string) => void;
|
|
81
85
|
onToggleInProgress?: (task: TaskItem) => void;
|
|
@@ -50,7 +50,7 @@ function SortableChecklistRow({ id, item, onToggle, onRemove, }) {
|
|
|
50
50
|
: "Mark checklist item complete", children: item.isCompleted ? _jsx(Check, { size: 14, strokeWidth: 2.1 }) : null }), _jsx("span", { className: `${formStyles.checklistItemText} ${item.isCompleted ? formStyles.checklistItemTextCompleted : ""}`.trim(), children: item.title }), _jsx("button", { type: "button", className: formStyles.checklistRemoveBtn, onClick: onRemove, title: "Remove checklist item", "aria-label": "Remove checklist item", children: _jsx(X, { size: 15, strokeWidth: 1.9 }) })] }));
|
|
51
51
|
}
|
|
52
52
|
export function TaskForm(props) {
|
|
53
|
-
const { editingTaskId, initialActivityEntryId, title, description, category, type, priority, complexity, manualComplexityEnabled = false, assignee, scheduledDate, dueDate, checklistItems, comments, newCommentText, contextFiles, currentWorkspaceId, currentActorId, apiBaseUrl, runtimeMode = 'local', showImageReviews = false, descriptionFocused, showMarkdownHelp, checklistEnabled = true, formTaxonomies, onTaxonomyChange, onOpenSettings, categories, types, priorities, taxonomyDisplayLabels, assigneeOptions, taxonomies, workstreams = [], initiatives = [], copiedId, onTitleChange, onTitleDraftChange, onDescriptionChange, onDescriptionDraftChange, onCategoryChange, onTypeChange, onPriorityChange, onComplexityChange, onAssigneeChange, onScheduledDateChange, onDueDateChange, onChecklistItemsChange, onNewCommentTextChange, onDescriptionFocusedChange, onShowMarkdownHelpChange, onSubmit, onAddComment, onAddContextFile, onRemoveContextFile, onUpdateContextCaption, onOpenContextImage, onCopyId, onToggleInProgress, onToggleReview, onToggleComplete, onToggleCancel, onSetStatus, onArchiveTask, onUnarchive, taskReferences, currentTask, commentsEndRef, } = props;
|
|
53
|
+
const { editingTaskId, initialActivityEntryId, title, description, category, type, priority, complexity, manualComplexityEnabled = false, assignee, scheduledDate, dueDate, checklistItems, comments, newCommentText, contextFiles, currentWorkspaceId, descriptionImageDraftId, currentActorId, apiBaseUrl, runtimeMode = 'local', showImageReviews = false, descriptionFocused, showMarkdownHelp, checklistEnabled = true, formTaxonomies, onTaxonomyChange, onOpenSettings, categories, types, priorities, taxonomyDisplayLabels, assigneeOptions, taxonomies, workstreams = [], initiatives = [], copiedId, onTitleChange, onTitleDraftChange, onDescriptionChange, onDescriptionDraftChange, onCategoryChange, onTypeChange, onPriorityChange, onComplexityChange, onAssigneeChange, onScheduledDateChange, onDueDateChange, onChecklistItemsChange, onNewCommentTextChange, onDescriptionFocusedChange, onShowMarkdownHelpChange, onSubmit, onAddComment, onDescriptionImageUploadStart, onAddContextFile, onRemoveContextFile, onUpdateContextCaption, onSetCardCover, cardCoverDisabled, onOpenContextImage, onCopyId, onToggleInProgress, onToggleReview, onToggleComplete, onToggleCancel, onSetStatus, onArchiveTask, onUnarchive, taskReferences, currentTask, commentsEndRef, } = props;
|
|
54
54
|
const emitTitleDraftChange = onTitleDraftChange || onTitleChange;
|
|
55
55
|
const emitDescriptionDraftChange = onDescriptionDraftChange || onDescriptionChange;
|
|
56
56
|
const currentTaskReferenceLabel = getTaskReferenceLabel(currentTask);
|
|
@@ -66,6 +66,7 @@ export function TaskForm(props) {
|
|
|
66
66
|
descriptionImageGenerationRef.current += 1;
|
|
67
67
|
}
|
|
68
68
|
const [descriptionImageUploading, setDescriptionImageUploading] = React.useState(false);
|
|
69
|
+
const descriptionImageUploadRef = React.useRef(null);
|
|
69
70
|
const [descriptionImageError, setDescriptionImageError] = React.useState(null);
|
|
70
71
|
React.useLayoutEffect(() => {
|
|
71
72
|
const taskChanged = textDraftOwnerRef.current !== editingTaskId;
|
|
@@ -80,6 +81,7 @@ export function TaskForm(props) {
|
|
|
80
81
|
textDraftOwnerRef.current = editingTaskId;
|
|
81
82
|
}, [description, editingTaskId, title]);
|
|
82
83
|
React.useEffect(() => {
|
|
84
|
+
descriptionImageUploadRef.current = null;
|
|
83
85
|
setDescriptionImageUploading(false);
|
|
84
86
|
setDescriptionImageError(null);
|
|
85
87
|
return () => {
|
|
@@ -260,14 +262,11 @@ export function TaskForm(props) {
|
|
|
260
262
|
window.requestAnimationFrame(() => checklistAddTriggerRef.current?.focus());
|
|
261
263
|
}
|
|
262
264
|
};
|
|
263
|
-
const
|
|
264
|
-
if (files.length === 0 || descriptionImageUploading)
|
|
265
|
-
return;
|
|
265
|
+
const performDescriptionImageUpload = async (files, selectionStart, selectionEnd) => {
|
|
266
266
|
const uploadGeneration = descriptionImageGenerationRef.current;
|
|
267
267
|
const uploadOwnerId = editingTaskId;
|
|
268
268
|
const uploadIsCurrent = () => descriptionImageGenerationRef.current === uploadGeneration &&
|
|
269
269
|
descriptionImageOwnerRef.current === descriptionImageOwnerKey;
|
|
270
|
-
setDescriptionImageUploading(true);
|
|
271
270
|
setDescriptionImageError(null);
|
|
272
271
|
const uploadedReferences = [];
|
|
273
272
|
let failedCount = 0;
|
|
@@ -283,6 +282,12 @@ export function TaskForm(props) {
|
|
|
283
282
|
ownerType: "task",
|
|
284
283
|
ownerId: uploadOwnerId,
|
|
285
284
|
workspaceId: currentWorkspaceId,
|
|
285
|
+
...(!uploadOwnerId && descriptionImageDraftId
|
|
286
|
+
? {
|
|
287
|
+
draftUploadId: descriptionImageDraftId,
|
|
288
|
+
uploadPurpose: "task-description-draft",
|
|
289
|
+
}
|
|
290
|
+
: {}),
|
|
286
291
|
});
|
|
287
292
|
if (!uploadIsCurrent())
|
|
288
293
|
return;
|
|
@@ -338,7 +343,27 @@ export function TaskForm(props) {
|
|
|
338
343
|
: "The images could not be added.");
|
|
339
344
|
setDescriptionImageError(failureMessage);
|
|
340
345
|
}
|
|
341
|
-
|
|
346
|
+
};
|
|
347
|
+
const addDescriptionImages = (files, selectionStart, selectionEnd) => {
|
|
348
|
+
if (files.length === 0 || descriptionImageUploadRef.current) {
|
|
349
|
+
return descriptionImageUploadRef.current || Promise.resolve();
|
|
350
|
+
}
|
|
351
|
+
setDescriptionImageUploading(true);
|
|
352
|
+
const upload = performDescriptionImageUpload(files, selectionStart, selectionEnd);
|
|
353
|
+
descriptionImageUploadRef.current = upload;
|
|
354
|
+
onDescriptionImageUploadStart?.(upload);
|
|
355
|
+
void upload.then(() => {
|
|
356
|
+
if (descriptionImageUploadRef.current !== upload)
|
|
357
|
+
return;
|
|
358
|
+
descriptionImageUploadRef.current = null;
|
|
359
|
+
setDescriptionImageUploading(false);
|
|
360
|
+
}, () => {
|
|
361
|
+
if (descriptionImageUploadRef.current !== upload)
|
|
362
|
+
return;
|
|
363
|
+
descriptionImageUploadRef.current = null;
|
|
364
|
+
setDescriptionImageUploading(false);
|
|
365
|
+
});
|
|
366
|
+
return upload;
|
|
342
367
|
};
|
|
343
368
|
const getDescriptionClipboardImages = (dataTransfer) => Array.from(dataTransfer.items)
|
|
344
369
|
.filter((item) => item.kind === "file" &&
|
|
@@ -855,7 +880,7 @@ export function TaskForm(props) {
|
|
|
855
880
|
.map((entry, entryIndex) => ({
|
|
856
881
|
...entry,
|
|
857
882
|
order: entryIndex,
|
|
858
|
-
}))) }, item.id || `checklist-${index}`))) }) }))] }))] }))] }), _jsxs("div", { children: [_jsx(React.Suspense, { fallback: null, children: _jsx(DeferredTaskContextUpload, { taskId: editingTaskId, taskReferenceLabel: currentTaskReferenceLabel, workspaceId: currentWorkspaceId, apiBaseUrl: apiBaseUrl, showImageReviews: showImageReviews && Boolean(currentTask), contextFiles: contextFiles, onAddContextFile: onAddContextFile, onRemoveContextFile: onRemoveContextFile, onUpdateContextCaption: onUpdateContextCaption }) }), _jsx("div", { className: `${formStyles.taskFormLifecycle} ${formStyles.taskFormMetaDivider}`, children: _jsx("div", { className: formStyles.taskFormMetaGrid, children: currentTask && (_jsxs(_Fragment, { children: [_jsxs("div", { className: formStyles.taskFormDateRow, children: [_jsx("span", { className: formStyles.taskFormDateLabel, children: t("taskForm.createdLabel") }), _jsxs("span", { className: formStyles.taskFormMetaStack, children: [_jsx("span", { className: formStyles.taskFormDateValue, children: createdLabel || t("taskForm.emptyValue") }), _jsx("span", { className: formStyles.taskFormMetaActor, children: createdByLabel
|
|
883
|
+
}))) }, item.id || `checklist-${index}`))) }) }))] }))] }))] }), _jsxs("div", { children: [_jsx(React.Suspense, { fallback: null, children: _jsx(DeferredTaskContextUpload, { taskId: editingTaskId, taskReferenceLabel: currentTaskReferenceLabel, workspaceId: currentWorkspaceId, apiBaseUrl: apiBaseUrl, showImageReviews: showImageReviews && Boolean(currentTask), contextFiles: contextFiles, onAddContextFile: onAddContextFile, onRemoveContextFile: onRemoveContextFile, onUpdateContextCaption: onUpdateContextCaption, cardCoverAssetId: currentTask?.cardCoverAssetId, onSetCardCover: onSetCardCover, cardCoverDisabled: cardCoverDisabled }) }), _jsx("div", { className: `${formStyles.taskFormLifecycle} ${formStyles.taskFormMetaDivider}`, children: _jsx("div", { className: formStyles.taskFormMetaGrid, children: currentTask && (_jsxs(_Fragment, { children: [_jsxs("div", { className: formStyles.taskFormDateRow, children: [_jsx("span", { className: formStyles.taskFormDateLabel, children: t("taskForm.createdLabel") }), _jsxs("span", { className: formStyles.taskFormMetaStack, children: [_jsx("span", { className: formStyles.taskFormDateValue, children: createdLabel || t("taskForm.emptyValue") }), _jsx("span", { className: formStyles.taskFormMetaActor, children: createdByLabel
|
|
859
884
|
? `by ${createdByLabel}`
|
|
860
885
|
: t("taskForm.emptyValue") })] })] }), _jsxs("div", { className: formStyles.taskFormDateRow, children: [_jsx("span", { className: formStyles.taskFormDateLabel, children: t("taskForm.updatedLabel") }), _jsxs("span", { className: formStyles.taskFormMetaStack, children: [_jsx("span", { className: formStyles.taskFormDateValue, children: updatedLabel || t("taskForm.emptyValue") }), _jsx("span", { className: formStyles.taskFormMetaActor, children: updatedByLabel
|
|
861
886
|
? `by ${updatedByLabel}`
|