diff --git a/frontend/src/components/shot/ShotDetailPanel.vue b/frontend/src/components/shot/ShotDetailPanel.vue index 93c5583..950d164 100644 --- a/frontend/src/components/shot/ShotDetailPanel.vue +++ b/frontend/src/components/shot/ShotDetailPanel.vue @@ -185,13 +185,15 @@ @click="$emit('select-task', task, 'infos')" >
{{ formatTaskType(task.task_type) }}
-
- {{ task.assigned_user_name || 'Unassigned' }} +
+ + + {{ getTaskAssigneeInitials(task) }} + + {{ task.assigned_user_name || 'Unassigned' }}
- - {{ formatTaskStatus(task.status.toString()) }} - +
@@ -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() const emit = defineEmits() const authStore = useAuthStore() +const taskStatusesStore = useTaskStatusesStore() +const { getAvatarUrl } = useAvatarUrl() // Reactive state const shot = ref(null) @@ -382,6 +394,7 @@ const tasks = ref([]) const isLoading = ref(false) const error = ref(null) const isCreatingTask = ref(false) +const projectMembers = ref([]) // 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', {