8.8 KiB
Bulk Status Update with Custom Status Support
Overview
This document describes the implementation of custom task status support in the bulk status update feature. The TaskBulkActionsMenu component has been enhanced to fetch and display both system and custom statuses, with proper validation to ensure all selected tasks belong to the same project.
Requirements
- 10.1: Modify
TaskBulkActionsMenu.vuecomponent - 10.2: Fetch custom statuses for current project
- 10.3: Include custom statuses in bulk update dropdown
- 10.4: Validate all selected tasks are from same project
- 10.5: Show color indicators in dropdown
Implementation Details
1. Component Props Enhancement
File: frontend/src/components/task/TaskBulkActionsMenu.vue
Added selectedTasks prop to receive task data for validation:
interface Props {
open: boolean
position: { x: number; y: number }
selectedCount: number
selectedTasks: Task[] // NEW: For project validation
projectMembers: ProjectMember[]
isProcessing?: boolean
}
2. Custom Status Fetching
Implemented status fetching logic that:
- Fetches both system and custom statuses from the API
- Triggers when the menu opens
- Refetches when the project changes
- Handles loading and error states
const fetchStatuses = async () => {
if (!currentProjectId.value || hasMultipleProjects.value) {
systemStatuses.value = []
customStatuses.value = []
return
}
try {
isLoadingStatuses.value = true
const response = await customTaskStatusService.getAllStatuses(currentProjectId.value)
systemStatuses.value = response.system_statuses
customStatuses.value = response.statuses
} catch (error) {
console.error('Failed to fetch task statuses:', error)
systemStatuses.value = []
customStatuses.value = []
} finally {
isLoadingStatuses.value = false
}
}
3. Multi-Project Validation
Implemented validation to ensure all selected tasks belong to the same project:
const hasMultipleProjects = computed(() => {
if (props.selectedTasks.length === 0) return false
const projectIds = new Set(props.selectedTasks.map(task => task.project_id))
return projectIds.size > 1
})
const currentProjectId = computed(() => {
if (props.selectedTasks.length === 0) return null
return props.selectedTasks[0].project_id
})
When multiple projects are detected:
- A warning message is displayed: "Selected tasks are from different projects"
- The "Set Status" button is disabled
- The "Assign To" buttons are disabled
4. Status Display with Color Indicators
The dropdown now displays statuses with:
- Section labels ("System Statuses" and "Custom Statuses")
- Color indicator dots for each status
- Proper separation between system and custom statuses
<template>
<!-- System statuses -->
<div v-if="systemStatuses.length > 0" class="px-2 py-1 text-xs font-semibold text-muted-foreground">
System Statuses
</div>
<DropdownMenuItem
v-for="status in systemStatuses"
:key="status.id"
@click="handleStatusSelected(status.id)"
class="flex items-center gap-2"
>
<div
class="w-2 h-2 rounded-full flex-shrink-0"
:style="{ backgroundColor: status.color }"
/>
<span>{{ status.name }}</span>
</DropdownMenuItem>
<!-- Divider -->
<div v-if="systemStatuses.length > 0 && customStatuses.length > 0" class="h-px bg-border my-1" />
<!-- Custom statuses -->
<div v-if="customStatuses.length > 0" class="px-2 py-1 text-xs font-semibold text-muted-foreground">
Custom Statuses
</div>
<DropdownMenuItem
v-for="status in customStatuses"
:key="status.id"
@click="handleStatusSelected(status.id)"
class="flex items-center gap-2"
>
<div
class="w-2 h-2 rounded-full flex-shrink-0"
:style="{ backgroundColor: status.color }"
/>
<span>{{ status.name }}</span>
</DropdownMenuItem>
</template>
5. Service Layer Updates
File: frontend/src/services/task.ts
Updated the bulk status update service to accept string status IDs instead of enum values:
// Before
async bulkUpdateStatus(taskIds: number[], status: TaskStatus): Promise<BulkActionResult>
// After
async bulkUpdateStatus(taskIds: number[], status: string): Promise<BulkActionResult>
This change allows the service to handle both system status IDs (e.g., "not_started") and custom status IDs (e.g., "custom_status_123").
6. Parent Component Integration
File: frontend/src/components/task/TaskBrowser.vue
Updated to pass the selectedTasks prop:
<TaskBulkActionsMenu
v-model:open="showContextMenu"
:position="contextMenuPosition"
:selected-count="selectedCount"
:selected-tasks="selectedTasks" <!-- NEW -->
:project-members="projectMembers"
@status-selected="handleBulkStatusUpdate"
@assignee-selected="handleBulkAssignment"
/>
Updated the status update handler to accept string:
const handleBulkStatusUpdate = async (status: string) => {
// ... implementation
}
User Experience
Normal Flow (Single Project)
- User selects multiple tasks from the same project
- User right-clicks to open context menu
- User clicks "Set Status" to open submenu
- Statuses load (brief loading indicator)
- System statuses appear first with label
- Custom statuses appear below with label
- Each status shows a colored dot indicator
- User clicks a status
- All selected tasks are updated
- Success toast shows count of updated tasks
Multi-Project Detection
- User selects tasks from different projects
- User right-clicks to open context menu
- Warning message appears: "Selected tasks are from different projects"
- "Set Status" button is disabled
- "Assign To" buttons are disabled
- User must adjust selection to single project
Testing
Manual Testing Steps
-
Setup:
- Create a project with custom task statuses
- Create multiple tasks in the project
- Navigate to the Tasks view
-
Test Single Project Selection:
- Select multiple tasks from the same project
- Right-click to open context menu
- Click "Set Status" submenu
- Verify system statuses appear with label
- Verify custom statuses appear with label
- Verify color indicators are displayed
- Click a custom status
- Verify tasks are updated successfully
-
Test Multi-Project Validation:
- Select tasks from different projects
- Right-click to open context menu
- Verify warning message appears
- Verify "Set Status" is disabled
- Verify "Assign To" is disabled
-
Test Status Loading:
- Select tasks and open context menu
- Verify loading indicator appears briefly
- Verify statuses load correctly
-
Test Color Indicators:
- Verify each status has a colored dot
- Verify colors match configured status colors
Files Modified
-
frontend/src/components/task/TaskBulkActionsMenu.vue- Added selectedTasks prop
- Implemented status fetching logic
- Added multi-project validation
- Updated UI to show color indicators
- Added loading and error states
-
frontend/src/components/task/TaskBrowser.vue- Pass selectedTasks to TaskBulkActionsMenu
- Updated handleBulkStatusUpdate to accept string
-
frontend/src/services/task.ts- Updated bulkUpdateStatus signature to accept string
- Updated BulkStatusUpdateRequest interface
API Integration
The component uses the customTaskStatusService.getAllStatuses() method which returns:
interface AllTaskStatusesResponse {
statuses: CustomTaskStatus[] // Custom statuses
system_statuses: SystemTaskStatus[] // System statuses
default_status_id: string
}
interface CustomTaskStatus {
id: string
name: string
color: string
order: number
is_default: boolean
}
interface SystemTaskStatus {
id: string
name: string
color: string
is_system: boolean
}
Benefits
- Flexibility: Users can now use custom statuses in bulk operations
- Consistency: Same status options available in bulk and individual updates
- Safety: Multi-project validation prevents accidental cross-project updates
- Usability: Color indicators help users quickly identify statuses
- Organization: Clear separation between system and custom statuses
Future Enhancements
Potential improvements for future iterations:
- Add status search/filter for projects with many custom statuses
- Show status usage count in dropdown
- Add keyboard shortcuts for common status changes
- Support status presets for common bulk operations
- Add undo functionality for bulk status changes