@taskforcehq/taskforce 0.3.302 → 0.3.304
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 +113 -5
- package/dist/TaskforceCore.js +560 -208
- package/dist/TaskforceCore.test.js +1833 -91
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/TaskSettings.js +58 -38
- package/dist/components/features/TaskSettings.test.js +139 -40
- 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 +2 -1
- package/dist/components/task/TaskForm.js +22 -4
- package/dist/components/task/TaskForm.test.js +46 -0
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +31 -160
- package/dist/components/task/TaskKanban.test.js +17 -0
- 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 +4 -2
- package/dist/components/views/PlanComparisonPage.test.js +4 -0
- package/dist/components/views/PlansPage.d.ts +7 -1
- package/dist/components/views/PlansPage.js +139 -53
- package/dist/components/views/PlansPage.test.d.ts +1 -0
- package/dist/components/views/PlansPage.test.js +157 -0
- package/dist/components/views/StandaloneLayout.d.ts +1 -0
- package/dist/components/views/StandaloneLayout.js +216 -186
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +8 -3
- package/dist/components/views/standalone/modals/AccountHubModal.js +16 -2
- package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +78 -18
- package/dist/core/PlanEntitlementService.d.ts +31 -5
- package/dist/core/PlanEntitlementService.js +350 -222
- package/dist/core/SignupMonetizationSettings.test.js +208 -49
- package/dist/core/SystemAdmin.test.js +186 -96
- 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/Taskforce.d.ts +65 -20
- package/dist/core/Taskforce.js +261 -51
- package/dist/core/Taskforce.mcpAuth.test.js +0 -2
- package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
- package/dist/core/WorkspacePlanMode.test.js +65 -0
- package/dist/core/types.d.ts +11 -12
- 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 +61 -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 +122 -0
- package/dist/hooks/sync/transfers.js +431 -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 +5 -4
- package/dist/hooks/useSyncOrchestrator.js +315 -860
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +665 -8
- package/dist/hooks/useTaskMutations.js +45 -6
- package/dist/hooks/useTaskMutations.test.js +147 -0
- package/dist/hooks/useTaskforce.d.ts +11 -2
- package/dist/hooks/useTaskforce.js +290 -812
- package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
- package/dist/hooks/useWorkspaceSyncController.js +22 -10
- 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/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/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +264 -505
- package/dist/mcp/runtime.test.js +353 -89
- 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/migrations/billingSchemaParity.test.js +3 -0
- package/dist/migrations/taskSchemaMigrations.js +15 -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.cookieProxy.test.js +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +48 -3
- package/dist/server/index.test.js +14 -2
- package/dist/server/mockStripe.d.ts +1 -0
- package/dist/server/mockStripe.js +2 -0
- package/dist/server/routes/admin.js +91 -40
- package/dist/server/routes/auth.d.ts +1 -0
- package/dist/server/routes/auth.js +231 -55
- package/dist/server/routes/billing.js +473 -39
- package/dist/server/routes/billing.test.js +972 -37
- package/dist/server/routes/shared.d.ts +30 -0
- package/dist/server/routes/shared.js +31 -3
- package/dist/server/routes/sync.integration.test.js +180 -0
- package/dist/server/routes/sync.js +125 -242
- package/dist/server/routes/tasks.js +49 -7
- package/dist/server/routes.js +11 -22
- package/dist/server/routes.test.js +530 -81
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +1 -0
- package/dist/sync/cloudSyncApi.d.ts +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.d.ts +10 -0
- package/dist/sync/contentSyncState.js +277 -14
- 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 +377 -0
- package/dist/sync/syncService.d.ts +9 -1
- package/dist/sync/syncService.js +57 -10
- package/dist/sync/syncService.test.js +48 -0
- package/dist/sync/workspacePullFeed.d.ts +11 -0
- package/dist/sync/workspacePullFeed.js +123 -68
- package/dist/sync/workspacePullFeed.test.js +180 -0
- package/dist/sync/workspaceSyncModel.d.ts +22 -1
- package/dist/sync/workspaceSyncModel.js +65 -0
- package/dist/sync/workspaceSyncModel.test.js +119 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-BjR6iBf9.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
- package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-BguHeQaV.js → WorkflowsModule-DnFqdUME.js} +1 -1
- package/dist/ui/assets/index-DXUdtH1B.js +6 -0
- package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
- package/dist/ui/assets/{vendor-icons-C8EED3Oh.js → vendor-icons-I7ZwG1z_.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
- package/dist/utils/accountProfileSummaryCache.js +97 -0
- package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
- package/dist/utils/accountProfileSummaryCache.test.js +63 -0
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +5 -3
- package/scripts/playwright-auth.sh +4 -0
- package/dist/ui/assets/AgentsModule-JG5jc3he.js +0 -1
- package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
- package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +0 -8
- package/dist/ui/assets/index-MXeWoebC.css +0 -1
- package/dist/ui/assets/index-YJ4k5QNa.js +0 -6
- package/dist/utils/billingStatusCache.d.ts +0 -21
- package/dist/utils/billingStatusCache.js +0 -70
|
@@ -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::';
|
|
@@ -58,12 +59,7 @@ export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery
|
|
|
58
59
|
coordinateGetter: sortableKeyboardCoordinates,
|
|
59
60
|
}));
|
|
60
61
|
// 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
|
-
};
|
|
62
|
+
const getTaskStatus = getGroupedTaskStatus;
|
|
67
63
|
const todayDateOnly = useMemo(() => {
|
|
68
64
|
const now = new Date();
|
|
69
65
|
const year = now.getFullYear();
|
|
@@ -481,171 +477,46 @@ export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery
|
|
|
481
477
|
if (column) {
|
|
482
478
|
if (groupBy === 'schedule') {
|
|
483
479
|
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));
|
|
480
|
+
const candidateOverTaskId = overId && isTaskCardId(overId) ? getTaskIdFromCardId(overId) : null;
|
|
481
|
+
const overRect = over?.rect;
|
|
482
|
+
const translatedActiveRect = active.rect.current.translated;
|
|
483
|
+
const activeRect = translatedActiveRect || active.rect.current.initial;
|
|
484
|
+
const isBelowOverItem = Boolean(overRect
|
|
485
|
+
&& activeRect
|
|
486
|
+
&& (activeRect.top + activeRect.height / 2) > (overRect.top + overRect.height / 2));
|
|
487
|
+
const targetTasks = (groupedTasks[targetColumn] || []).filter((task) => task.id !== activeTaskId);
|
|
488
|
+
const sourceTasks = (groupedTasks[String(resolvedSourceContainerId)] || []).filter((task) => task.id !== activeTaskId);
|
|
489
|
+
const scheduleDropResult = buildScheduleDropResult({
|
|
490
|
+
activeTask,
|
|
491
|
+
sourceColumnId: resolvedSourceContainerId,
|
|
492
|
+
targetColumnId: targetColumn,
|
|
493
|
+
targetTasks,
|
|
494
|
+
sourceTasks,
|
|
495
|
+
overTaskId: candidateOverTaskId,
|
|
496
|
+
isBelowOverItem,
|
|
497
|
+
scheduleDates: scheduleContext.dates,
|
|
498
|
+
parseToIsoWeekKey: scheduleContext.parseToIsoWeekKey,
|
|
499
|
+
todayDate: todayDateOnly,
|
|
500
|
+
});
|
|
501
|
+
if (!scheduleDropResult)
|
|
531
502
|
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));
|
|
503
|
+
if (scheduleDropResult.kind === 'blocked') {
|
|
504
|
+
window.alert(scheduleDropResult.reason);
|
|
565
505
|
return;
|
|
566
506
|
}
|
|
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;
|
|
507
|
+
scheduleDropResult.updates.forEach(({ taskId, updates }) => onUpdateTask(taskId, updates));
|
|
508
|
+
if (scheduleDropResult.selectedDate) {
|
|
509
|
+
onScheduleDaySelected?.(scheduleDropResult.selectedDate);
|
|
576
510
|
}
|
|
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
511
|
return;
|
|
603
512
|
}
|
|
604
513
|
// Determine the value to update based on the grouping
|
|
605
514
|
let targetValue = column.value;
|
|
606
515
|
// STATUS HANDLING
|
|
607
516
|
if (groupBy === 'status') {
|
|
608
|
-
const currentStatus = getTaskStatus(activeTask);
|
|
609
517
|
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
|
-
}
|
|
518
|
+
const updates = buildStatusTransitionUpdates(activeTask, targetStatus, new Date().toISOString());
|
|
519
|
+
if (updates) {
|
|
649
520
|
onUpdateTask(activeTask.id, updates);
|
|
650
521
|
}
|
|
651
522
|
return;
|
|
@@ -43,4 +43,21 @@ describe('TaskKanban schedule expired behavior', () => {
|
|
|
43
43
|
expect(screen.getByText('Mon')).toBeInTheDocument();
|
|
44
44
|
expect(screen.getAllByText('Expired dual visibility task')).toHaveLength(2);
|
|
45
45
|
});
|
|
46
|
+
it('shows filtered and total counts per column', () => {
|
|
47
|
+
const mon = dateOffset(1);
|
|
48
|
+
const tue = dateOffset(2);
|
|
49
|
+
const wed = dateOffset(3);
|
|
50
|
+
const thu = dateOffset(4);
|
|
51
|
+
const fri = dateOffset(5);
|
|
52
|
+
const sat = dateOffset(6);
|
|
53
|
+
const sun = dateOffset(7);
|
|
54
|
+
const visibleTask = makeTask({ id: 'task-visible', title: 'Visible task', scheduledDate: mon });
|
|
55
|
+
const hiddenTask = makeTask({ id: 'task-hidden', title: 'Hidden task', scheduledDate: mon });
|
|
56
|
+
render(_jsx(TaskKanban, { tasks: [visibleTask], allTasks: [visibleTask, hiddenTask], columns: [
|
|
57
|
+
{ value: 'backlog', label: 'Backlog', color: '#64748b', icon: 'Inbox' },
|
|
58
|
+
{ value: 'expired', label: 'Expired', color: '#ef4444', icon: 'AlertTriangle' },
|
|
59
|
+
{ value: 'mon', label: 'Mon', color: '#3b82f6', icon: 'Calendar' }
|
|
60
|
+
], groupBy: "schedule", scheduleDates: { mon, tue, wed, thu, fri, sat, sun }, scheduleFilteredTaskIds: [visibleTask.id], onUpdateTask: vi.fn(), onTaskClick: vi.fn() }));
|
|
61
|
+
expect(screen.getByText('1 / 2')).toBeInTheDocument();
|
|
62
|
+
});
|
|
46
63
|
});
|
|
@@ -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 {};
|