@taskforcehq/taskforce 0.3.303 → 0.3.305
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 +140 -247
- package/dist/Taskforce.module.css +102 -5
- package/dist/TaskforceCore.js +635 -238
- package/dist/TaskforceCore.test.js +2243 -495
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/compat/workspaceSyncCompat.js +6 -0
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/DocumentIndex.d.ts +1 -1
- package/dist/components/features/DocumentIndex.js +1 -1
- package/dist/components/features/DocumentViewer.d.ts +1 -1
- package/dist/components/features/DocumentViewer.js +32 -9
- package/dist/components/features/DocumentWorkspace.d.ts +5 -22
- package/dist/components/features/DocumentWorkspace.js +12 -155
- package/dist/components/features/DocumentWorkspace.test.js +193 -6
- package/dist/components/features/TaskSettings.js +87 -41
- package/dist/components/features/TaskSettings.test.js +180 -40
- package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
- package/dist/components/features/documentWorkspaceModel.js +57 -0
- package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
- package/dist/components/features/useDocumentWorkspaceController.js +113 -0
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +3 -3
- package/dist/components/task/TaskActionHeader.test.js +8 -0
- package/dist/components/task/TaskCard.js +16 -1
- package/dist/components/task/TaskCard.test.js +31 -0
- package/dist/components/task/TaskForm.d.ts +9 -8
- package/dist/components/task/TaskForm.js +374 -219
- package/dist/components/task/TaskForm.test.js +482 -361
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +33 -161
- package/dist/components/task/TaskStatusActions.d.ts +6 -1
- package/dist/components/task/TaskStatusActions.js +8 -3
- package/dist/components/task/TaskStatusActions.test.js +9 -0
- package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
- package/dist/components/task/taskKanbanDropRules.js +187 -0
- package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
- package/dist/components/task/taskKanbanDropRules.test.js +114 -0
- package/dist/components/ui/Modal.d.ts +1 -1
- package/dist/components/ui/Modal.js +1 -0
- package/dist/components/views/AppShellHeader.js +5 -1
- package/dist/components/views/AppShellHeader.test.js +4 -0
- package/dist/components/views/PlanComparisonPage.d.ts +3 -2
- package/dist/components/views/PlanComparisonPage.js +9 -10
- package/dist/components/views/PlanComparisonPage.test.js +55 -41
- package/dist/components/views/PlansPage.d.ts +5 -1
- package/dist/components/views/PlansPage.js +150 -81
- package/dist/components/views/PlansPage.test.js +367 -41
- package/dist/components/views/StandaloneLayout.js +135 -54
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/panels/FilterToolbar.test.js +6 -4
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +2 -14
- package/dist/components/views/standalone/modals/AccountHubModal.js +12 -14
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +24 -1
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/config/envSchema.js +1 -1
- package/dist/core/AiProfileService.d.ts +6 -1
- package/dist/core/AiProfileService.js +161 -16
- package/dist/core/AiProfileService.test.js +3 -1
- package/dist/core/AiProfiles.test.js +200 -0
- package/dist/core/AuthTokenService.test.d.ts +1 -0
- package/dist/core/AuthTokenService.test.js +78 -0
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/EntitlementsPolicy.test.js +75 -13
- package/dist/core/PlanEntitlementService.d.ts +9 -1
- package/dist/core/PlanEntitlementService.js +144 -19
- package/dist/core/PlanVersionPolicy.test.js +19 -26
- package/dist/core/SignupMonetizationSettings.test.js +75 -15
- package/dist/core/SystemAdmin.test.js +212 -56
- package/dist/core/TaskActivityService.d.ts +4 -1
- package/dist/core/TaskActivityService.js +45 -0
- package/dist/core/TaskAuditTimeline.test.js +52 -1
- package/dist/core/TaskTaxonomyValidation.test.js +53 -0
- package/dist/core/Taskforce.d.ts +18 -11
- package/dist/core/Taskforce.js +93 -12
- package/dist/core/WorkspacePlanMode.test.js +73 -8
- package/dist/core/shared.d.ts +2 -0
- package/dist/core/shared.js +11 -0
- package/dist/documentReviews/service.d.ts +1 -1
- package/dist/documentReviews/types.d.ts +1 -1
- package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
- package/dist/hooks/sync/auth.d.ts +37 -0
- package/dist/hooks/sync/auth.js +63 -0
- package/dist/hooks/sync/bootstrap.d.ts +34 -0
- package/dist/hooks/sync/bootstrap.js +52 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
- package/dist/hooks/sync/orchestratorShared.js +209 -0
- package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
- package/dist/hooks/sync/orchestratorShared.test.js +49 -0
- package/dist/hooks/sync/realtime.d.ts +25 -0
- package/dist/hooks/sync/realtime.js +60 -0
- package/dist/hooks/sync/recovery.d.ts +69 -0
- package/dist/hooks/sync/recovery.js +118 -0
- package/dist/hooks/sync/transfers.d.ts +123 -0
- package/dist/hooks/sync/transfers.js +435 -0
- package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
- package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
- package/dist/hooks/ui/useResourceExport.d.ts +3 -1
- package/dist/hooks/ui/useResourceExport.js +5 -2
- package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
- package/dist/hooks/useRealtimeSync.js +2 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +12 -8
- package/dist/hooks/useSyncOrchestrator.js +388 -879
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +641 -6
- package/dist/hooks/useTaskMutations.d.ts +2 -1
- package/dist/hooks/useTaskMutations.js +57 -7
- package/dist/hooks/useTaskMutations.test.js +176 -0
- package/dist/hooks/useTaskforce.d.ts +13 -4
- package/dist/hooks/useTaskforce.js +302 -817
- package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
- package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
- package/dist/hooks/useWorkspaceSyncController.js +33 -11
- package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
- package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
- package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
- package/dist/hooks/workspace/workspaceLocalState.js +100 -0
- package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
- package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
- package/dist/localization/locales/en-US.js +2 -2
- package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
- package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
- package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
- package/dist/mcp/canonicalAssetHelpers.js +259 -0
- package/dist/mcp/clientIntegrations.d.ts +12 -1
- package/dist/mcp/clientIntegrations.js +161 -6
- package/dist/mcp/clientIntegrations.test.js +76 -4
- package/dist/mcp/collaborationRegistrar.d.ts +2 -0
- package/dist/mcp/collaborationRegistrar.js +71 -0
- package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
- package/dist/mcp/documentAssetRegistrar.js +346 -0
- package/dist/mcp/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +2455 -3925
- package/dist/mcp/runtime.test.js +240 -8
- package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
- package/dist/mcp/taskAttachmentHelpers.js +60 -0
- package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
- package/dist/mcp/taskAttachmentTypes.js +1 -0
- package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
- package/dist/mcp/taskPlanningRegistrar.js +418 -0
- package/dist/mcp/toolCatalog.d.ts +35 -0
- package/dist/mcp/toolCatalog.js +3 -0
- package/dist/plugins/vite.js +1 -1
- package/dist/runtime/appGateDefinitions.d.ts +1 -1
- package/dist/runtime/appGateDefinitions.js +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +46 -3
- package/dist/server/index.test.js +12 -1
- package/dist/server/routes/admin.d.ts +1 -5
- package/dist/server/routes/admin.js +151 -80
- package/dist/server/routes/annotatedAttachments.d.ts +1 -1
- package/dist/server/routes/annotatedAttachments.js +1 -1
- package/dist/server/routes/auth.d.ts +1 -4
- package/dist/server/routes/auth.js +59 -64
- package/dist/server/routes/authSupport.d.ts +30 -0
- package/dist/server/routes/authSupport.js +81 -0
- package/dist/server/routes/billing.d.ts +1 -1
- package/dist/server/routes/billing.js +122 -255
- package/dist/server/routes/billing.test.js +257 -86
- package/dist/server/routes/documentReviews.d.ts +1 -1
- package/dist/server/routes/documentReviews.js +1 -1
- package/dist/server/routes/documents.d.ts +4 -2
- package/dist/server/routes/documents.js +73 -7
- package/dist/server/routes/primitives.d.ts +11 -0
- package/dist/server/routes/primitives.js +73 -0
- package/dist/server/routes/resources.d.ts +1 -1
- package/dist/server/routes/resources.js +1 -1
- package/dist/server/routes/shared.d.ts +31 -1
- package/dist/server/routes/shared.js +26 -0
- package/dist/server/routes/sync.d.ts +1 -1
- package/dist/server/routes/sync.integration.test.js +20 -0
- package/dist/server/routes/sync.js +111 -238
- package/dist/server/routes/tasks.d.ts +1 -1
- package/dist/server/routes/tasks.js +50 -8
- package/dist/server/routes/workspaces.d.ts +1 -1
- package/dist/server/routes/workspaces.js +1 -1
- package/dist/server/routes.d.ts +2 -9
- package/dist/server/routes.js +84 -181
- package/dist/server/routes.test.js +912 -32
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +1 -0
- package/dist/sync/collaborationSyncPayload.js +4 -0
- package/dist/sync/collaborationSyncPayload.test.js +8 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -1
- package/dist/sync/collaborationSyncState.js +19 -0
- package/dist/sync/contentSyncState.js +28 -17
- package/dist/sync/syncApplyHandlers.d.ts +87 -1
- package/dist/sync/syncApplyHandlers.js +370 -8
- package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
- package/dist/sync/syncApplyHandlers.test.js +379 -0
- package/dist/sync/syncService.d.ts +9 -1
- package/dist/sync/syncService.js +44 -2
- package/dist/sync/syncService.test.js +62 -0
- package/dist/sync/workspacePullFeed.d.ts +1 -0
- package/dist/sync/workspacePullFeed.js +66 -59
- package/dist/sync/workspacePullFeed.test.js +72 -4
- package/dist/sync/workspaceSyncModel.d.ts +25 -1
- package/dist/sync/workspaceSyncModel.js +77 -3
- package/dist/sync/workspaceSyncModel.test.js +153 -0
- package/dist/sync/workspaceSyncState.d.ts +2 -0
- package/dist/sync/workspaceSyncState.js +1 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-N2MnQBZk.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BG6P_GVW.js} +1 -1
- package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +252 -0
- package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-Dhm7M8e7.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-Cq0zXj3I.js +1 -0
- package/dist/ui/assets/TaskSettings-ln0aF7xc.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-BcS4afRn.js} +1 -1
- package/dist/ui/assets/index-C2-OXZV7.css +1 -0
- package/dist/ui/assets/index-DcSI5F86.js +6 -0
- package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-BSUaRwF8.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
- package/dist/utils/commercialLifecyclePresentation.js +196 -0
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +10 -1
- package/scripts/check-cloud-mcp.mjs +83 -0
- package/scripts/playwright-auth.sh +9 -1
- package/scripts/run-billing-performance-smoke.sh +24 -0
- package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
- package/dist/ui/assets/DocumentWorkspace-C3GyzrWm.js +0 -250
- package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
- package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
- package/dist/ui/assets/index-CLIMgR6g.js +0 -6
- package/dist/ui/assets/index-DneETxcu.css +0 -1
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import type { TaskItem, CategoryConfig, TaxonomyDefinition, PriorityDefinition, TaskAssignee, WorkstreamItem, InitiativeItem } from '../../types';
|
|
3
3
|
import type { TaskAssigneeOption } from '../../utils/assignees';
|
|
4
4
|
import { type TaskSortKey } from '../../utils/taxonomySorting.js';
|
|
5
|
+
import { type ScheduleDayKey } from './taskKanbanDropRules';
|
|
5
6
|
export interface KanbanColumnDef {
|
|
6
7
|
value: string | number;
|
|
7
8
|
label: string;
|
|
@@ -11,7 +12,6 @@ export interface KanbanColumnDef {
|
|
|
11
12
|
isSelected?: boolean;
|
|
12
13
|
isWeekend?: boolean;
|
|
13
14
|
}
|
|
14
|
-
type ScheduleDayKey = 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' | 'sun';
|
|
15
15
|
interface TaskKanbanProps {
|
|
16
16
|
tasks: TaskItem[];
|
|
17
17
|
allTasks?: TaskItem[];
|
|
@@ -7,6 +7,7 @@ import { createPortal } from 'react-dom';
|
|
|
7
7
|
import styles from '../../Taskforce.module.css';
|
|
8
8
|
import { TaskCard } from './TaskCard';
|
|
9
9
|
import { compareTasksBySortKey } from '../../utils/taxonomySorting.js';
|
|
10
|
+
import { buildScheduleDropResult, buildStatusTransitionUpdates, getGroupedTaskStatus, } from './taskKanbanDropRules';
|
|
10
11
|
import * as Icons from 'lucide-react';
|
|
11
12
|
import { IconMap, resolveColor } from '../../utils/constants';
|
|
12
13
|
const EXPIRED_CARD_ID_PREFIX = 'expired::';
|
|
@@ -52,18 +53,14 @@ export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery
|
|
|
52
53
|
// Sensors
|
|
53
54
|
const sensors = useSensors(useSensor(PointerSensor, {
|
|
54
55
|
activationConstraint: {
|
|
55
|
-
|
|
56
|
+
// Give normal clicks a bit more tolerance so slight trackpad drift still opens the task.
|
|
57
|
+
distance: 10,
|
|
56
58
|
},
|
|
57
59
|
}), useSensor(KeyboardSensor, {
|
|
58
60
|
coordinateGetter: sortableKeyboardCoordinates,
|
|
59
61
|
}));
|
|
60
62
|
// Helper to determine status string from task flags
|
|
61
|
-
const getTaskStatus =
|
|
62
|
-
const status = task.status || 'task';
|
|
63
|
-
if (status === 'done' || status === 'cancelled')
|
|
64
|
-
return 'completed';
|
|
65
|
-
return status;
|
|
66
|
-
};
|
|
63
|
+
const getTaskStatus = getGroupedTaskStatus;
|
|
67
64
|
const todayDateOnly = useMemo(() => {
|
|
68
65
|
const now = new Date();
|
|
69
66
|
const year = now.getFullYear();
|
|
@@ -481,171 +478,46 @@ export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery
|
|
|
481
478
|
if (column) {
|
|
482
479
|
if (groupBy === 'schedule') {
|
|
483
480
|
const targetColumn = String(column.value);
|
|
484
|
-
const
|
|
485
|
-
const
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
const sourceTasks = (groupedTasks[String(sourceColumnId)] || []).filter(t => t.id !== activeTaskId);
|
|
507
|
-
sourceTasks.forEach((task, index) => {
|
|
508
|
-
pendingUpdates.set(task.id, {
|
|
509
|
-
...(pendingUpdates.get(task.id) || {}),
|
|
510
|
-
orderInDay: index
|
|
511
|
-
});
|
|
512
|
-
});
|
|
513
|
-
};
|
|
514
|
-
if (targetColumn === 'expired') {
|
|
515
|
-
// Expired is virtual and source-only: allow reordering within Expired,
|
|
516
|
-
// but do not allow drops from other columns into Expired.
|
|
517
|
-
if (resolvedSourceContainerId !== 'expired')
|
|
518
|
-
return;
|
|
519
|
-
const pendingUpdates = new Map();
|
|
520
|
-
const targetTasks = (groupedTasks.expired || []).filter(t => t.id !== activeTaskId);
|
|
521
|
-
const insertAt = buildInsertIndex(targetTasks);
|
|
522
|
-
const reorderedTarget = [...targetTasks];
|
|
523
|
-
reorderedTarget.splice(insertAt, 0, activeTask);
|
|
524
|
-
reorderedTarget.forEach((task, index) => {
|
|
525
|
-
pendingUpdates.set(task.id, {
|
|
526
|
-
...(pendingUpdates.get(task.id) || {}),
|
|
527
|
-
orderInDay: index
|
|
528
|
-
});
|
|
529
|
-
});
|
|
530
|
-
pendingUpdates.forEach((updates, taskId) => onUpdateTask(taskId, updates));
|
|
481
|
+
const candidateOverTaskId = overId && isTaskCardId(overId) ? getTaskIdFromCardId(overId) : null;
|
|
482
|
+
const overRect = over?.rect;
|
|
483
|
+
const translatedActiveRect = active.rect.current.translated;
|
|
484
|
+
const activeRect = translatedActiveRect || active.rect.current.initial;
|
|
485
|
+
const isBelowOverItem = Boolean(overRect
|
|
486
|
+
&& activeRect
|
|
487
|
+
&& (activeRect.top + activeRect.height / 2) > (overRect.top + overRect.height / 2));
|
|
488
|
+
const targetTasks = (groupedTasks[targetColumn] || []).filter((task) => task.id !== activeTaskId);
|
|
489
|
+
const sourceTasks = (groupedTasks[String(resolvedSourceContainerId)] || []).filter((task) => task.id !== activeTaskId);
|
|
490
|
+
const scheduleDropResult = buildScheduleDropResult({
|
|
491
|
+
activeTask,
|
|
492
|
+
sourceColumnId: resolvedSourceContainerId,
|
|
493
|
+
targetColumnId: targetColumn,
|
|
494
|
+
targetTasks,
|
|
495
|
+
sourceTasks,
|
|
496
|
+
overTaskId: candidateOverTaskId,
|
|
497
|
+
isBelowOverItem,
|
|
498
|
+
scheduleDates: scheduleContext.dates,
|
|
499
|
+
parseToIsoWeekKey: scheduleContext.parseToIsoWeekKey,
|
|
500
|
+
todayDate: todayDateOnly,
|
|
501
|
+
});
|
|
502
|
+
if (!scheduleDropResult)
|
|
531
503
|
return;
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
const pendingUpdates = new Map();
|
|
535
|
-
const targetTasks = (groupedTasks.backlog || []).filter(t => t.id !== activeTaskId);
|
|
536
|
-
const insertAt = buildInsertIndex(targetTasks);
|
|
537
|
-
const reorderedTarget = [...targetTasks];
|
|
538
|
-
reorderedTarget.splice(insertAt, 0, activeTask);
|
|
539
|
-
reorderedTarget.forEach((task, index) => {
|
|
540
|
-
if (task.id === activeTaskId) {
|
|
541
|
-
if (activeTask.scheduledDate) {
|
|
542
|
-
pendingUpdates.set(task.id, {
|
|
543
|
-
...(pendingUpdates.get(task.id) || {}),
|
|
544
|
-
scheduledDate: null,
|
|
545
|
-
scheduledWeekKey: null,
|
|
546
|
-
orderInDay: index
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
else {
|
|
550
|
-
pendingUpdates.set(task.id, {
|
|
551
|
-
...(pendingUpdates.get(task.id) || {}),
|
|
552
|
-
orderInDay: index
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
else {
|
|
557
|
-
pendingUpdates.set(task.id, {
|
|
558
|
-
...(pendingUpdates.get(task.id) || {}),
|
|
559
|
-
orderInDay: index
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
});
|
|
563
|
-
reindexSourceColumnAfterMove(resolvedSourceContainerId, targetColumn, pendingUpdates);
|
|
564
|
-
pendingUpdates.forEach((updates, taskId) => onUpdateTask(taskId, updates));
|
|
504
|
+
if (scheduleDropResult.kind === 'blocked') {
|
|
505
|
+
window.alert(scheduleDropResult.reason);
|
|
565
506
|
return;
|
|
566
507
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
if (!scheduledDate)
|
|
571
|
-
return;
|
|
572
|
-
const isTerminal = activeTask.status === 'done' || activeTask.status === 'cancelled';
|
|
573
|
-
if (!isTerminal && scheduledDate < todayDateOnly) {
|
|
574
|
-
window.alert('Only completed work can be scheduled in the past.');
|
|
575
|
-
return;
|
|
508
|
+
scheduleDropResult.updates.forEach(({ taskId, updates }) => onUpdateTask(taskId, updates));
|
|
509
|
+
if (scheduleDropResult.selectedDate) {
|
|
510
|
+
onScheduleDaySelected?.(scheduleDropResult.selectedDate);
|
|
576
511
|
}
|
|
577
|
-
const scheduledWeekKey = scheduleContext.parseToIsoWeekKey(scheduledDate);
|
|
578
|
-
const pendingUpdates = new Map();
|
|
579
|
-
const targetTasks = (groupedTasks[targetColumn] || []).filter(t => t.id !== activeTaskId);
|
|
580
|
-
const insertAt = buildInsertIndex(targetTasks);
|
|
581
|
-
const reorderedTarget = [...targetTasks];
|
|
582
|
-
reorderedTarget.splice(insertAt, 0, activeTask);
|
|
583
|
-
reorderedTarget.forEach((task, index) => {
|
|
584
|
-
if (task.id === activeTaskId) {
|
|
585
|
-
pendingUpdates.set(task.id, {
|
|
586
|
-
...(pendingUpdates.get(task.id) || {}),
|
|
587
|
-
scheduledDate,
|
|
588
|
-
scheduledWeekKey,
|
|
589
|
-
orderInDay: index
|
|
590
|
-
});
|
|
591
|
-
}
|
|
592
|
-
else {
|
|
593
|
-
pendingUpdates.set(task.id, {
|
|
594
|
-
...(pendingUpdates.get(task.id) || {}),
|
|
595
|
-
orderInDay: index
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
});
|
|
599
|
-
reindexSourceColumnAfterMove(resolvedSourceContainerId, targetColumn, pendingUpdates);
|
|
600
|
-
pendingUpdates.forEach((updates, taskId) => onUpdateTask(taskId, updates));
|
|
601
|
-
onScheduleDaySelected?.(scheduledDate);
|
|
602
512
|
return;
|
|
603
513
|
}
|
|
604
514
|
// Determine the value to update based on the grouping
|
|
605
515
|
let targetValue = column.value;
|
|
606
516
|
// STATUS HANDLING
|
|
607
517
|
if (groupBy === 'status') {
|
|
608
|
-
const currentStatus = getTaskStatus(activeTask);
|
|
609
518
|
const targetStatus = String(targetValue);
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
status: targetStatus
|
|
613
|
-
};
|
|
614
|
-
if (targetStatus === 'task') {
|
|
615
|
-
updates.inProgress = false;
|
|
616
|
-
updates.readyForReview = false;
|
|
617
|
-
updates.completed = false;
|
|
618
|
-
updates.cancelled = false;
|
|
619
|
-
}
|
|
620
|
-
else if (targetStatus === 'on-hold') {
|
|
621
|
-
updates.inProgress = false;
|
|
622
|
-
updates.readyForReview = false;
|
|
623
|
-
updates.completed = false;
|
|
624
|
-
updates.cancelled = false;
|
|
625
|
-
}
|
|
626
|
-
else if (targetStatus === 'in-progress') {
|
|
627
|
-
updates.inProgress = true;
|
|
628
|
-
updates.readyForReview = false;
|
|
629
|
-
updates.completed = false;
|
|
630
|
-
updates.cancelled = false;
|
|
631
|
-
}
|
|
632
|
-
else if (targetStatus === 'review') {
|
|
633
|
-
updates.inProgress = false;
|
|
634
|
-
updates.readyForReview = true;
|
|
635
|
-
updates.completed = false;
|
|
636
|
-
updates.cancelled = false;
|
|
637
|
-
}
|
|
638
|
-
else if (targetStatus === 'done' || targetStatus === 'completed') {
|
|
639
|
-
updates.status = 'done';
|
|
640
|
-
updates.inProgress = false;
|
|
641
|
-
updates.readyForReview = false;
|
|
642
|
-
updates.completed = true;
|
|
643
|
-
updates.cancelled = false;
|
|
644
|
-
updates.completedAt = new Date().toISOString();
|
|
645
|
-
}
|
|
646
|
-
else if (targetStatus === 'cancelled') {
|
|
647
|
-
updates.status = 'cancelled';
|
|
648
|
-
}
|
|
519
|
+
const updates = buildStatusTransitionUpdates(activeTask, targetStatus, new Date().toISOString());
|
|
520
|
+
if (updates) {
|
|
649
521
|
onUpdateTask(activeTask.id, updates);
|
|
650
522
|
}
|
|
651
523
|
return;
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import type { TaskItem, TaskStatus } from '../../types';
|
|
2
|
+
type ArchiveActionMode = 'auto' | 'archive' | 'unarchive' | 'none';
|
|
2
3
|
interface TaskStatusActionsProps {
|
|
3
4
|
task?: TaskItem | null;
|
|
4
5
|
disabled?: boolean;
|
|
6
|
+
actionDisabled?: boolean;
|
|
7
|
+
statusDisabled?: boolean;
|
|
5
8
|
compressed?: boolean;
|
|
6
9
|
shortLabels?: boolean;
|
|
7
10
|
showLabel?: boolean;
|
|
8
11
|
onSetStatus?: (task: TaskItem, status: TaskStatus) => void;
|
|
9
12
|
onArchiveTask?: (task: TaskItem) => void;
|
|
13
|
+
onUnarchiveTask?: (task: TaskItem) => void;
|
|
14
|
+
archiveActionMode?: ArchiveActionMode;
|
|
10
15
|
}
|
|
11
|
-
export declare function TaskStatusActions({ task, disabled, compressed, shortLabels, showLabel, onSetStatus, onArchiveTask }: TaskStatusActionsProps): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export declare function TaskStatusActions({ task, disabled, actionDisabled, statusDisabled, compressed, shortLabels, showLabel, onSetStatus, onArchiveTask, onUnarchiveTask, archiveActionMode }: TaskStatusActionsProps): import("react/jsx-runtime").JSX.Element;
|
|
12
17
|
export {};
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Archive } from 'lucide-react';
|
|
2
|
+
import { Archive, RotateCcw } from 'lucide-react';
|
|
3
3
|
import styles from '../../Taskforce.module.css';
|
|
4
4
|
import { STATUS_DEFINITIONS } from '../../utils/constants';
|
|
5
5
|
import { TaxonomyDropdown } from '../ui/TaxonomyDropdown';
|
|
6
|
-
export function TaskStatusActions({ task, disabled = false, compressed = false, shortLabels = false, showLabel = true, onSetStatus, onArchiveTask }) {
|
|
6
|
+
export function TaskStatusActions({ task, disabled = false, actionDisabled, statusDisabled, compressed = false, shortLabels = false, showLabel = true, onSetStatus, onArchiveTask, onUnarchiveTask, archiveActionMode = 'auto' }) {
|
|
7
7
|
const currentStatus = task?.status || 'task';
|
|
8
8
|
const canArchive = !!task && (currentStatus === 'done' || currentStatus === 'cancelled');
|
|
9
|
+
const resolvedArchiveActionMode = archiveActionMode === 'auto'
|
|
10
|
+
? (canArchive ? 'archive' : 'none')
|
|
11
|
+
: archiveActionMode;
|
|
12
|
+
const resolvedActionDisabled = actionDisabled ?? disabled;
|
|
13
|
+
const resolvedStatusDisabled = statusDisabled ?? disabled;
|
|
9
14
|
const statusOptions = STATUS_DEFINITIONS.map((option) => ({
|
|
10
15
|
...option,
|
|
11
16
|
label: (compressed || shortLabels) ? (option.shortLabel || option.label) : option.label
|
|
12
17
|
}));
|
|
13
|
-
return (_jsxs("div", { className: `${styles.statusActionsGroup} ${compressed ? styles.statusActionsGroupCompact : ''}`, children: [
|
|
18
|
+
return (_jsxs("div", { className: `${styles.statusActionsGroup} ${compressed ? styles.statusActionsGroupCompact : ''}`, children: [resolvedArchiveActionMode === 'archive' && (_jsx("button", { type: "button", className: `${styles.actionBtn} ${styles.archiveTaskBtn}`, onClick: () => task && !resolvedActionDisabled && onArchiveTask?.(task), disabled: resolvedActionDisabled, title: "Archive Now", "aria-label": "Archive task", children: _jsx(Archive, { size: compressed ? 12 : 16 }) })), resolvedArchiveActionMode === 'unarchive' && (_jsx("button", { type: "button", className: `${styles.actionBtn} ${styles.archiveTaskBtn}`, onClick: () => task && !resolvedActionDisabled && onUnarchiveTask?.(task), disabled: resolvedActionDisabled, title: "Unarchive task", "aria-label": "Unarchive task", children: _jsx(RotateCcw, { size: compressed ? 12 : 16 }) })), _jsx("div", { className: `${styles.statusSelectWrap} ${styles.statusTaxonomyDropdownWrap}`, children: _jsx(TaxonomyDropdown, { value: currentStatus, options: statusOptions, disabled: resolvedStatusDisabled || !task, ariaLabel: "Status", hideSelectedLabel: !showLabel, hideChevron: !showLabel, className: `${styles.statusTaxonomyDropdown} ${compressed ? styles.statusTaxonomyDropdownCompact : ''} ${!showLabel ? styles.statusTaxonomyDropdownIconOnly : ''}`, panelClassName: !showLabel ? styles.statusTaxonomyDropdownIconOnlyPanel : '', portalPanel: !showLabel, panelMinWidth: !showLabel ? 144 : undefined, onChange: (value) => {
|
|
14
19
|
if (!task)
|
|
15
20
|
return;
|
|
16
21
|
onSetStatus?.(task, value);
|
|
@@ -25,6 +25,15 @@ describe('TaskStatusActions', () => {
|
|
|
25
25
|
rerender(_jsx(TaskStatusActions, { task: makeTask({ status: 'done' }) }));
|
|
26
26
|
expect(screen.getByRole('button', { name: 'Archive task' })).toBeInTheDocument();
|
|
27
27
|
});
|
|
28
|
+
it('can render an unarchive action for archived read-only views', async () => {
|
|
29
|
+
const user = userEvent.setup();
|
|
30
|
+
const task = makeTask({ status: 'done' });
|
|
31
|
+
const onUnarchiveTask = vi.fn();
|
|
32
|
+
render(_jsx(TaskStatusActions, { task: task, archiveActionMode: "unarchive", onUnarchiveTask: onUnarchiveTask }));
|
|
33
|
+
await user.click(screen.getByRole('button', { name: 'Unarchive task' }));
|
|
34
|
+
expect(screen.queryByRole('button', { name: 'Archive task' })).not.toBeInTheDocument();
|
|
35
|
+
expect(onUnarchiveTask).toHaveBeenCalledWith(task);
|
|
36
|
+
});
|
|
28
37
|
it('uses the compact short label when compressed', () => {
|
|
29
38
|
render(_jsx(TaskStatusActions, { task: makeTask({ status: 'in-progress' }), compressed: true }));
|
|
30
39
|
expect(screen.getByRole('combobox', { name: 'Status' })).toHaveTextContent('Working');
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { TaskItem } from '../../types';
|
|
2
|
+
export type ScheduleDayKey = 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' | 'sun';
|
|
3
|
+
export type TaskUpdatePair = {
|
|
4
|
+
taskId: string;
|
|
5
|
+
updates: Partial<TaskItem>;
|
|
6
|
+
};
|
|
7
|
+
export type ScheduleDropBlockedResult = {
|
|
8
|
+
kind: 'blocked';
|
|
9
|
+
reason: string;
|
|
10
|
+
};
|
|
11
|
+
export type ScheduleDropUpdatesResult = {
|
|
12
|
+
kind: 'updates';
|
|
13
|
+
updates: TaskUpdatePair[];
|
|
14
|
+
selectedDate?: string;
|
|
15
|
+
};
|
|
16
|
+
export type ScheduleDropResult = ScheduleDropBlockedResult | ScheduleDropUpdatesResult;
|
|
17
|
+
export declare const getGroupedTaskStatus: (task: TaskItem) => string;
|
|
18
|
+
export declare const buildStatusTransitionUpdates: (task: TaskItem, targetStatus: string, nowIso: string) => Partial<TaskItem> | null;
|
|
19
|
+
export declare const buildInsertIndex: ({ targetTasks, overTaskId, isBelowOverItem, }: {
|
|
20
|
+
targetTasks: TaskItem[];
|
|
21
|
+
overTaskId: string | null;
|
|
22
|
+
isBelowOverItem: boolean;
|
|
23
|
+
}) => number;
|
|
24
|
+
export declare const reindexTasks: (tasks: TaskItem[], baseUpdates?: Map<string, Partial<TaskItem>>) => Map<string, Partial<TaskItem>>;
|
|
25
|
+
export declare const buildBacklogDropUpdates: ({ activeTask, sourceColumnId, targetTasks, sourceTasks, insertAt, }: {
|
|
26
|
+
activeTask: TaskItem;
|
|
27
|
+
sourceColumnId: string | null;
|
|
28
|
+
targetTasks: TaskItem[];
|
|
29
|
+
sourceTasks: TaskItem[];
|
|
30
|
+
insertAt: number;
|
|
31
|
+
}) => TaskUpdatePair[];
|
|
32
|
+
export declare const buildExpiredReorderUpdates: ({ activeTask, targetTasks, insertAt, }: {
|
|
33
|
+
activeTask: TaskItem;
|
|
34
|
+
targetTasks: TaskItem[];
|
|
35
|
+
insertAt: number;
|
|
36
|
+
}) => TaskUpdatePair[];
|
|
37
|
+
export declare const buildScheduledDayDropUpdates: ({ activeTask, sourceColumnId, targetColumnId, targetTasks, sourceTasks, insertAt, scheduledDate, scheduledWeekKey, }: {
|
|
38
|
+
activeTask: TaskItem;
|
|
39
|
+
sourceColumnId: string | null;
|
|
40
|
+
targetColumnId: string;
|
|
41
|
+
targetTasks: TaskItem[];
|
|
42
|
+
sourceTasks: TaskItem[];
|
|
43
|
+
insertAt: number;
|
|
44
|
+
scheduledDate: string;
|
|
45
|
+
scheduledWeekKey: string | null;
|
|
46
|
+
}) => TaskUpdatePair[];
|
|
47
|
+
export declare const buildScheduleDropResult: ({ activeTask, sourceColumnId, targetColumnId, targetTasks, sourceTasks, overTaskId, isBelowOverItem, scheduleDates, parseToIsoWeekKey, todayDate, }: {
|
|
48
|
+
activeTask: TaskItem;
|
|
49
|
+
sourceColumnId: string | null;
|
|
50
|
+
targetColumnId: string;
|
|
51
|
+
targetTasks: TaskItem[];
|
|
52
|
+
sourceTasks: TaskItem[];
|
|
53
|
+
overTaskId: string | null;
|
|
54
|
+
isBelowOverItem: boolean;
|
|
55
|
+
scheduleDates: Record<ScheduleDayKey, string>;
|
|
56
|
+
parseToIsoWeekKey: (dateOnly: string) => string | null;
|
|
57
|
+
todayDate: string;
|
|
58
|
+
}) => ScheduleDropResult | null;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
export const getGroupedTaskStatus = (task) => {
|
|
2
|
+
const status = task.status || 'task';
|
|
3
|
+
if (status === 'done' || status === 'cancelled')
|
|
4
|
+
return 'completed';
|
|
5
|
+
return status;
|
|
6
|
+
};
|
|
7
|
+
export const buildStatusTransitionUpdates = (task, targetStatus, nowIso) => {
|
|
8
|
+
const currentStatus = getGroupedTaskStatus(task);
|
|
9
|
+
if (currentStatus === targetStatus)
|
|
10
|
+
return null;
|
|
11
|
+
const updates = {
|
|
12
|
+
status: targetStatus
|
|
13
|
+
};
|
|
14
|
+
if (targetStatus === 'task' || targetStatus === 'on-hold') {
|
|
15
|
+
updates.inProgress = false;
|
|
16
|
+
updates.readyForReview = false;
|
|
17
|
+
updates.completed = false;
|
|
18
|
+
updates.cancelled = false;
|
|
19
|
+
updates.completedAt = null;
|
|
20
|
+
return updates;
|
|
21
|
+
}
|
|
22
|
+
if (targetStatus === 'in-progress') {
|
|
23
|
+
updates.inProgress = true;
|
|
24
|
+
updates.readyForReview = false;
|
|
25
|
+
updates.completed = false;
|
|
26
|
+
updates.cancelled = false;
|
|
27
|
+
updates.completedAt = null;
|
|
28
|
+
return updates;
|
|
29
|
+
}
|
|
30
|
+
if (targetStatus === 'review') {
|
|
31
|
+
updates.inProgress = false;
|
|
32
|
+
updates.readyForReview = true;
|
|
33
|
+
updates.completed = false;
|
|
34
|
+
updates.cancelled = false;
|
|
35
|
+
updates.completedAt = null;
|
|
36
|
+
return updates;
|
|
37
|
+
}
|
|
38
|
+
if (targetStatus === 'done' || targetStatus === 'completed') {
|
|
39
|
+
updates.status = 'done';
|
|
40
|
+
updates.inProgress = false;
|
|
41
|
+
updates.readyForReview = false;
|
|
42
|
+
updates.completed = true;
|
|
43
|
+
updates.cancelled = false;
|
|
44
|
+
updates.completedAt = nowIso;
|
|
45
|
+
return updates;
|
|
46
|
+
}
|
|
47
|
+
if (targetStatus === 'cancelled') {
|
|
48
|
+
updates.status = 'cancelled';
|
|
49
|
+
return updates;
|
|
50
|
+
}
|
|
51
|
+
return updates;
|
|
52
|
+
};
|
|
53
|
+
export const buildInsertIndex = ({ targetTasks, overTaskId, isBelowOverItem, }) => {
|
|
54
|
+
if (!overTaskId)
|
|
55
|
+
return targetTasks.length;
|
|
56
|
+
const overIndex = targetTasks.findIndex((task) => task.id === overTaskId);
|
|
57
|
+
if (overIndex < 0)
|
|
58
|
+
return targetTasks.length;
|
|
59
|
+
return overIndex + (isBelowOverItem ? 1 : 0);
|
|
60
|
+
};
|
|
61
|
+
export const reindexTasks = (tasks, baseUpdates = new Map()) => {
|
|
62
|
+
const updates = new Map(baseUpdates);
|
|
63
|
+
tasks.forEach((task, index) => {
|
|
64
|
+
updates.set(task.id, {
|
|
65
|
+
...(updates.get(task.id) || {}),
|
|
66
|
+
orderInDay: index,
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
return updates;
|
|
70
|
+
};
|
|
71
|
+
const toUpdatePairs = (updates) => (Array.from(updates.entries()).map(([taskId, taskUpdates]) => ({ taskId, updates: taskUpdates })));
|
|
72
|
+
const reindexSourceColumnAfterMove = ({ sourceColumnId, targetColumnId, sourceTasks, updates, }) => {
|
|
73
|
+
if (!sourceColumnId || sourceColumnId === targetColumnId)
|
|
74
|
+
return updates;
|
|
75
|
+
return reindexTasks(sourceTasks, updates);
|
|
76
|
+
};
|
|
77
|
+
export const buildBacklogDropUpdates = ({ activeTask, sourceColumnId, targetTasks, sourceTasks, insertAt, }) => {
|
|
78
|
+
const reorderedTarget = [...targetTasks];
|
|
79
|
+
reorderedTarget.splice(insertAt, 0, activeTask);
|
|
80
|
+
let updates = new Map();
|
|
81
|
+
reorderedTarget.forEach((task, index) => {
|
|
82
|
+
if (task.id === activeTask.id) {
|
|
83
|
+
updates.set(task.id, {
|
|
84
|
+
...(updates.get(task.id) || {}),
|
|
85
|
+
...(activeTask.scheduledDate ? { scheduledDate: null, scheduledWeekKey: null } : {}),
|
|
86
|
+
orderInDay: index,
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
updates.set(task.id, {
|
|
91
|
+
...(updates.get(task.id) || {}),
|
|
92
|
+
orderInDay: index,
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
updates = reindexSourceColumnAfterMove({
|
|
96
|
+
sourceColumnId,
|
|
97
|
+
targetColumnId: 'backlog',
|
|
98
|
+
sourceTasks,
|
|
99
|
+
updates,
|
|
100
|
+
});
|
|
101
|
+
return toUpdatePairs(updates);
|
|
102
|
+
};
|
|
103
|
+
export const buildExpiredReorderUpdates = ({ activeTask, targetTasks, insertAt, }) => {
|
|
104
|
+
const reorderedTarget = [...targetTasks];
|
|
105
|
+
reorderedTarget.splice(insertAt, 0, activeTask);
|
|
106
|
+
return toUpdatePairs(reindexTasks(reorderedTarget));
|
|
107
|
+
};
|
|
108
|
+
export const buildScheduledDayDropUpdates = ({ activeTask, sourceColumnId, targetColumnId, targetTasks, sourceTasks, insertAt, scheduledDate, scheduledWeekKey, }) => {
|
|
109
|
+
const reorderedTarget = [...targetTasks];
|
|
110
|
+
reorderedTarget.splice(insertAt, 0, activeTask);
|
|
111
|
+
let updates = new Map();
|
|
112
|
+
reorderedTarget.forEach((task, index) => {
|
|
113
|
+
if (task.id === activeTask.id) {
|
|
114
|
+
updates.set(task.id, {
|
|
115
|
+
...(updates.get(task.id) || {}),
|
|
116
|
+
scheduledDate,
|
|
117
|
+
scheduledWeekKey,
|
|
118
|
+
orderInDay: index,
|
|
119
|
+
});
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
updates.set(task.id, {
|
|
123
|
+
...(updates.get(task.id) || {}),
|
|
124
|
+
orderInDay: index,
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
updates = reindexSourceColumnAfterMove({
|
|
128
|
+
sourceColumnId,
|
|
129
|
+
targetColumnId,
|
|
130
|
+
sourceTasks,
|
|
131
|
+
updates,
|
|
132
|
+
});
|
|
133
|
+
return toUpdatePairs(updates);
|
|
134
|
+
};
|
|
135
|
+
export const buildScheduleDropResult = ({ activeTask, sourceColumnId, targetColumnId, targetTasks, sourceTasks, overTaskId, isBelowOverItem, scheduleDates, parseToIsoWeekKey, todayDate, }) => {
|
|
136
|
+
const scheduleKeys = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
|
|
137
|
+
const isScheduleReorderColumn = targetColumnId === 'backlog' || targetColumnId === 'expired' || scheduleKeys.includes(targetColumnId);
|
|
138
|
+
if (!isScheduleReorderColumn)
|
|
139
|
+
return null;
|
|
140
|
+
const insertAt = buildInsertIndex({ targetTasks, overTaskId, isBelowOverItem });
|
|
141
|
+
if (targetColumnId === 'expired') {
|
|
142
|
+
if (sourceColumnId !== 'expired')
|
|
143
|
+
return null;
|
|
144
|
+
return {
|
|
145
|
+
kind: 'updates',
|
|
146
|
+
updates: buildExpiredReorderUpdates({ activeTask, targetTasks, insertAt }),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (targetColumnId === 'backlog') {
|
|
150
|
+
return {
|
|
151
|
+
kind: 'updates',
|
|
152
|
+
updates: buildBacklogDropUpdates({
|
|
153
|
+
activeTask,
|
|
154
|
+
sourceColumnId,
|
|
155
|
+
targetTasks,
|
|
156
|
+
sourceTasks,
|
|
157
|
+
insertAt,
|
|
158
|
+
}),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (!scheduleKeys.includes(targetColumnId))
|
|
162
|
+
return null;
|
|
163
|
+
const scheduledDate = scheduleDates[targetColumnId];
|
|
164
|
+
if (!scheduledDate)
|
|
165
|
+
return null;
|
|
166
|
+
const isTerminal = activeTask.status === 'done' || activeTask.status === 'cancelled';
|
|
167
|
+
if (!isTerminal && scheduledDate < todayDate) {
|
|
168
|
+
return {
|
|
169
|
+
kind: 'blocked',
|
|
170
|
+
reason: 'Only completed work can be scheduled in the past.',
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
kind: 'updates',
|
|
175
|
+
updates: buildScheduledDayDropUpdates({
|
|
176
|
+
activeTask,
|
|
177
|
+
sourceColumnId,
|
|
178
|
+
targetColumnId,
|
|
179
|
+
targetTasks,
|
|
180
|
+
sourceTasks,
|
|
181
|
+
insertAt,
|
|
182
|
+
scheduledDate,
|
|
183
|
+
scheduledWeekKey: parseToIsoWeekKey(scheduledDate),
|
|
184
|
+
}),
|
|
185
|
+
selectedDate: scheduledDate,
|
|
186
|
+
};
|
|
187
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|