Fix task assignee resolution and align task status styling in ShotDetailPanel
The Tasks list embedded assigned_user_name: undefined with a "will be resolved if needed" comment that was never followed up on, so every task showed "Unassigned" regardless of actual assignment. Resolve it from the project members list, matching how the shot table's own assignment control does it, and show an avatar next to the name. Also swap the hardcoded status Badge/variant switch for the same TaskStatusBadge + task-statuses store the shot table uses, so status colors and labels (including custom per-project statuses) always match between the table and the panel.
This commit is contained in:
@@ -185,13 +185,15 @@
|
||||
@click="$emit('select-task', task, 'infos')"
|
||||
>
|
||||
<div class="text-sm font-medium">{{ formatTaskType(task.task_type) }}</div>
|
||||
<div class="text-sm text-muted-foreground">
|
||||
{{ task.assigned_user_name || 'Unassigned' }}
|
||||
<div class="flex items-center gap-2 text-sm text-muted-foreground min-w-0">
|
||||
<Avatar class="h-5 w-5 flex-shrink-0" v-if="task.assigned_user_name">
|
||||
<AvatarImage :src="getAvatarUrl(task.assigned_user_avatar_url, task.assigned_user_first_name, task.assigned_user_last_name)" />
|
||||
<AvatarFallback class="text-[9px]">{{ getTaskAssigneeInitials(task) }}</AvatarFallback>
|
||||
</Avatar>
|
||||
<span class="truncate">{{ task.assigned_user_name || 'Unassigned' }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<Badge :variant="getTaskStatusVariant(task.status)" class="text-xs">
|
||||
{{ formatTaskStatus(task.status.toString()) }}
|
||||
</Badge>
|
||||
<TaskStatusBadge :status="getTaskStatusObject(task)" compact />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -339,19 +341,27 @@ import { Badge } from '@/components/ui/badge'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import DetailPanelLoading from '@/components/shared/DetailPanelLoading.vue'
|
||||
import DetailPanelError from '@/components/shared/DetailPanelError.vue'
|
||||
import DetailPanelHeader from '@/components/shared/DetailPanelHeader.vue'
|
||||
import TaskStatusBadge from '@/components/task/TaskStatusBadge.vue'
|
||||
|
||||
import { shotService, ShotStatus, type Shot, type TaskStatusInfo, TaskStatus } from '@/services/shot'
|
||||
import { shotService, ShotStatus, type Shot, type TaskStatusInfo } from '@/services/shot'
|
||||
import { taskService } from '@/services/task'
|
||||
import { projectService, type ProjectMember } from '@/services/project'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useTaskStatusesStore } from '@/stores/taskStatuses'
|
||||
import { useAvatarUrl } from '@/composables/useAvatarUrl'
|
||||
|
||||
// Use TaskStatusInfo from shot service instead of local Task interface
|
||||
interface Task extends TaskStatusInfo {
|
||||
id: number
|
||||
name?: string
|
||||
assigned_user_name?: string
|
||||
assigned_user_avatar_url?: string | null
|
||||
assigned_user_first_name?: string
|
||||
assigned_user_last_name?: string
|
||||
deadline?: string
|
||||
}
|
||||
|
||||
@@ -375,6 +385,8 @@ const props = defineProps<Props>()
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const taskStatusesStore = useTaskStatusesStore()
|
||||
const { getAvatarUrl } = useAvatarUrl()
|
||||
|
||||
// Reactive state
|
||||
const shot = ref<Shot | null>(null)
|
||||
@@ -382,6 +394,7 @@ const tasks = ref<Task[]>([])
|
||||
const isLoading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
const isCreatingTask = ref(false)
|
||||
const projectMembers = ref<ProjectMember[]>([])
|
||||
|
||||
// Computed properties
|
||||
const frameCount = computed(() => {
|
||||
@@ -450,6 +463,10 @@ const loadShotDetails = async () => {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
shot.value = props.initialShot ?? await shotService.getShot(props.shotId)
|
||||
await Promise.all([
|
||||
taskStatusesStore.fetchProjectStatuses(props.projectId),
|
||||
loadProjectMembers()
|
||||
])
|
||||
loadTasks() // No longer async - uses embedded data
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : 'Failed to load shot details'
|
||||
@@ -459,23 +476,43 @@ const loadShotDetails = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const loadProjectMembers = async () => {
|
||||
try {
|
||||
projectMembers.value = await projectService.getProjectMembers(props.projectId)
|
||||
} catch (err) {
|
||||
console.error('Failed to load project members:', err)
|
||||
}
|
||||
}
|
||||
|
||||
const loadTasks = () => {
|
||||
// Use task_details already embedded in shot data - no API call needed!
|
||||
if (shot.value?.task_details) {
|
||||
tasks.value = shot.value.task_details.map(taskInfo => ({
|
||||
id: taskInfo.task_id || 0,
|
||||
task_type: taskInfo.task_type,
|
||||
status: taskInfo.status,
|
||||
assigned_user_id: taskInfo.assigned_user_id,
|
||||
// Add placeholder values for display compatibility
|
||||
name: taskInfo.task_type, // Use task_type as name for display
|
||||
assigned_user_name: undefined // Will be resolved if needed
|
||||
}))
|
||||
tasks.value = shot.value.task_details.map(taskInfo => {
|
||||
const assignedUser = projectMembers.value.find(member => member.user_id === taskInfo.assigned_user_id)
|
||||
return {
|
||||
id: taskInfo.task_id || 0,
|
||||
task_type: taskInfo.task_type,
|
||||
status: taskInfo.status,
|
||||
assigned_user_id: taskInfo.assigned_user_id,
|
||||
// Add placeholder values for display compatibility
|
||||
name: taskInfo.task_type, // Use task_type as name for display
|
||||
assigned_user_name: assignedUser ? `${assignedUser.user_first_name} ${assignedUser.user_last_name}` : undefined,
|
||||
assigned_user_avatar_url: assignedUser?.user_avatar_url,
|
||||
assigned_user_first_name: assignedUser?.user_first_name,
|
||||
assigned_user_last_name: assignedUser?.user_last_name
|
||||
}
|
||||
})
|
||||
} else {
|
||||
tasks.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the project's actual status object (name + color) for a task, matching the shot table
|
||||
const getTaskStatusObject = (task: Task) => {
|
||||
const statusId = task.status.toString()
|
||||
return taskStatusesStore.getStatusById(props.projectId, statusId) || statusId
|
||||
}
|
||||
|
||||
const handleAddTask = async (taskType: string) => {
|
||||
isCreatingTask.value = true
|
||||
try {
|
||||
@@ -496,18 +533,18 @@ const formatStatus = (status: ShotStatus) => {
|
||||
).join(' ')
|
||||
}
|
||||
|
||||
const formatTaskStatus = (status: string) => {
|
||||
return status.split('_').map(word =>
|
||||
word.charAt(0).toUpperCase() + word.slice(1)
|
||||
).join(' ')
|
||||
}
|
||||
|
||||
const formatTaskType = (taskType: string) => {
|
||||
return taskType.split('_').map(word =>
|
||||
word.charAt(0).toUpperCase() + word.slice(1)
|
||||
).join(' ')
|
||||
}
|
||||
|
||||
const getTaskAssigneeInitials = (task: Task) => {
|
||||
const first = task.assigned_user_first_name?.charAt(0) || ''
|
||||
const last = task.assigned_user_last_name?.charAt(0) || ''
|
||||
return (first + last).toUpperCase()
|
||||
}
|
||||
|
||||
const getStatusVariant = (status: ShotStatus) => {
|
||||
switch (status) {
|
||||
case ShotStatus.NOT_STARTED:
|
||||
@@ -542,24 +579,6 @@ const getStatusColor = (status: ShotStatus) => {
|
||||
}
|
||||
}
|
||||
|
||||
const getTaskStatusVariant = (status: string | TaskStatus) => {
|
||||
const statusStr = status.toString()
|
||||
switch (statusStr) {
|
||||
case 'not_started':
|
||||
return 'secondary'
|
||||
case 'in_progress':
|
||||
return 'default'
|
||||
case 'submitted':
|
||||
return 'outline'
|
||||
case 'approved':
|
||||
return 'default'
|
||||
case 'retake':
|
||||
return 'destructive'
|
||||
default:
|
||||
return 'secondary'
|
||||
}
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString)
|
||||
return date.toLocaleDateString('en-US', {
|
||||
|
||||
Reference in New Issue
Block a user