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

9.5 KiB

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:

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:

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:

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:

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:

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:

@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):

{
  "task_type": "animation",
  "status": "not_started",
  "task_id": 123,
  "assigned_user_id": null
}

Update Task Status

PUT /tasks/{task_id}/status

Request Body:

{
  "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
  • 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.