330 lines
9.5 KiB
Markdown
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.
|