LinkDesk/frontend/docs/shot-table-ajax-task-status.md

330 lines
9.5 KiB
Markdown

# Shot Table AJAX Task Status Update Implementation
## Overview
This document describes the implementation of AJAX-based task status editing in the shot table view. Previously, task status columns displayed read-only badges. Now they use editable dropdowns that update task status via API calls without page refresh.
## Changes Made
### 1. Created Shot-Specific EditableTaskStatus Component
**File**: `frontend/src/components/shot/EditableTaskStatus.vue`
Created a new component specifically for editing shot task statuses, similar to the existing asset version but adapted for shots:
**Features**:
- Dropdown select with status options
- Loading indicator during API calls
- Automatic task creation if task doesn't exist
- Status update via AJAX
- Error handling with status revert
- Visual feedback during updates
**Props**:
- `shotId: number` - The shot ID
- `taskType: string` - The task type (layout, animation, etc.)
- `status: TaskStatus` - Current task status
- `taskId?: number | null` - Existing task ID (if task exists)
**Events**:
- `status-updated` - Emitted after successful status update
**API Calls**:
1. `taskService.createShotTask()` - Creates task if it doesn't exist
2. `taskService.updateTaskStatus()` - Updates the task status
### 2. Added createShotTask Method to Task Service
**File**: `frontend/src/services/task.ts`
Added new method to create shot tasks:
```typescript
async createShotTask(shotId: number, taskType: string): Promise<TaskStatusInfo> {
const response = await apiClient.post(`/shots/${shotId}/tasks?task_type=${taskType}`)
return response.data
}
```
This mirrors the existing `createAssetTask` method but for shots.
### 3. Updated Shot Interface
**File**: `frontend/src/services/shot.ts`
Added `task_ids` field to Shot interface for easier access to task IDs:
```typescript
export interface Shot {
// ... existing fields ...
task_ids?: Record<string, number>
}
```
This allows the editable component to know if a task already exists and its ID.
### 4. Updated Shot Table Columns
**File**: `frontend/src/components/shot/columns.ts`
**Changes**:
1. Imported `EditableTaskStatus` component
2. Added `onTaskStatusUpdated` callback to `ShotColumnMeta` interface
3. Updated task status column cells to use `EditableTaskStatus` instead of `TaskStatusBadge`
**Column Cell Implementation**:
```typescript
cell: ({ row }) => {
const shot = row.original
const status = shot.task_status?.[taskType] || TaskStatus.NOT_STARTED
const taskId = shot.task_ids?.[taskType]
return h(EditableTaskStatus, {
shotId: shot.id,
taskType,
status,
taskId,
onStatusUpdated: (shotId: number, taskType: string, newStatus: TaskStatus) => {
meta.onTaskStatusUpdated(shotId, taskType, newStatus)
},
})
}
```
### 5. Updated ShotBrowser Component
**File**: `frontend/src/components/shot/ShotBrowser.vue`
**Changes**:
1. Imported `TaskStatus` enum
2. Added `handleTaskStatusUpdated` method
3. Passed callback to column meta
**Handler Implementation**:
```typescript
const handleTaskStatusUpdated = (shotId: number, taskType: string, newStatus: TaskStatus) => {
// Update local state instead of reloading all shots (optimistic update)
const shot = shots.value.find(s => s.id === shotId)
if (shot) {
if (!shot.task_status) {
shot.task_status = {}
}
shot.task_status[taskType] = newStatus
}
// Show success toast
toast({
title: 'Task status updated',
description: `${taskType} status updated successfully`,
})
}
```
**Key Improvement**: Uses optimistic local state update instead of reloading all shots from the server, matching the asset table behavior for better performance.
**Column Meta Update**:
```typescript
const shotColumns = computed(() => {
const meta: ShotColumnMeta = {
episodes: episodes.value,
onEdit: editShot,
onDelete: deleteShot,
onViewTasks: selectShot,
onTaskStatusUpdated: handleTaskStatusUpdated, // Added
}
return createShotColumns(allTaskTypes.value, meta)
})
```
## User Experience Flow
### Editing Task Status
1. User clicks on a task status cell in the shot table
2. Dropdown opens showing all available statuses
3. User selects a new status
4. Component shows loading indicator
5. If task doesn't exist:
- API call creates the task
- Task ID is returned
6. API call updates the task status
7. Success:
- Shot list refreshes with new status
- Toast notification appears
- Loading indicator disappears
8. Error:
- Status reverts to original
- Error is logged to console
- Parent component refreshes data
### Visual Feedback
- **Loading**: Spinner overlay on the dropdown
- **Success**: Toast notification + table refresh
- **Error**: Silent revert (status returns to original)
## Backend Implementation
### Added Endpoint: Create Shot Task
**File**: `backend/routers/shots.py`
Added new endpoint after `get_shot`:
```python
@router.post("/{shot_id}/tasks", response_model=TaskStatusInfo, status_code=status.HTTP_201_CREATED)
async def create_shot_task(
shot_id: int,
task_type: str,
db: Session = Depends(get_db),
current_user: User = Depends(require_coordinator_or_admin)
):
"""Create a new task for a shot"""
# Validates shot exists
# Checks episode access permissions
# Returns existing task if already exists (idempotent)
# Creates new task with default values
# Returns TaskStatusInfo with task_id
```
**Key Features**:
- Idempotent: Returns existing task if already created
- Permission check: Requires coordinator or admin role
- Access validation: Checks episode access via shot
- Auto-naming: Creates task name as "{shot_name} - {task_type}"
## API Endpoints Used
### Create Shot Task
```
POST /shots/{shot_id}/tasks?task_type={task_type}
```
**Response** (201 Created or 200 OK if exists):
```json
{
"task_type": "animation",
"status": "not_started",
"task_id": 123,
"assigned_user_id": null
}
```
### Update Task Status
```
PUT /tasks/{task_id}/status
```
**Request Body**:
```json
{
"status": "in_progress"
}
```
**Response**: Updated task object
### Get Shots (with task status)
```
GET /shots/?episode_id={episode_id}
```
**Response**: Array of shots with `task_status` and `task_ids` populated
## Benefits
1. **No Page Refresh**: Status updates happen instantly via AJAX
2. **Automatic Task Creation**: Tasks are created on-demand when status is first changed
3. **Visual Feedback**: Loading indicators and toast notifications
4. **Error Handling**: Graceful error handling with status revert
5. **Consistent UX**: Matches the asset table behavior
6. **Performance**: Only affected shot data is refreshed
## Technical Details
### Task Creation Flow
When a user changes status for a task that doesn't exist:
1. Component checks if `taskId` prop is provided
2. If not, calls `createShotTask(shotId, taskType)`
3. Backend creates task with default values
4. Returns task ID
5. Component then calls `updateTaskStatus(taskId, newStatus)`
6. Status is updated
7. Parent refreshes to show new data
### Status Update Flow
When a user changes status for an existing task:
1. Component has `taskId` from props
2. Directly calls `updateTaskStatus(taskId, newStatus)`
3. Status is updated
4. Parent refreshes to show new data
### Data Refresh Strategy
After status update:
- **Optimistic Update**: Local state is updated immediately
- Shot's `task_status` object is modified directly
- No server reload required
- Matches asset table behavior for consistent UX
- Much faster than reloading all shots
## Comparison with Asset Table
The shot table implementation mirrors the asset table:
| Feature | Asset Table | Shot Table |
|---------|-------------|------------|
| Component | `EditableTaskStatus.vue` (asset) | `EditableTaskStatus.vue` (shot) |
| Create Method | `createAssetTask()` | `createShotTask()` |
| Update Method | `updateTaskStatus()` | `updateTaskStatus()` (same) |
| Column Definition | `columns.ts` (asset) | `columns.ts` (shot) |
| Parent Handler | `AssetBrowser` | `ShotBrowser` |
## Future Enhancements
Potential improvements:
1. **Optimistic Updates**: Update UI immediately, revert on error
2. **Batch Updates**: Allow updating multiple tasks at once
3. **Undo/Redo**: Add ability to undo status changes
4. **Keyboard Shortcuts**: Quick status changes via keyboard
5. **Status History**: Track who changed status and when
6. **Validation**: Prevent invalid status transitions
7. **Permissions**: Check user permissions before allowing edits
## Testing
To test the implementation:
1. Navigate to a project's Shots tab
2. Switch to table view
3. Find a shot with tasks
4. Click on a task status cell
5. Select a different status
6. Verify:
- Loading indicator appears
- Status updates in table
- Toast notification shows
- No page refresh occurs
7. Test with a shot that has no tasks:
- Change status
- Verify task is created
- Verify status is set correctly
## Related Files
- `frontend/src/components/shot/EditableTaskStatus.vue` - Editable status component
- `frontend/src/components/shot/columns.ts` - Column definitions
- `frontend/src/components/shot/ShotBrowser.vue` - Parent component
- `frontend/src/services/task.ts` - Task service with API methods
- `frontend/src/services/shot.ts` - Shot service and types
- `frontend/src/components/asset/EditableTaskStatus.vue` - Asset version (reference)
## Conclusion
The shot table now supports AJAX-based task status editing, providing a seamless user experience without page refreshes. The implementation follows the same pattern as the asset table, ensuring consistency across the application.