import type { ColumnDef } from '@tanstack/vue-table' import { h, ref } from 'vue' import { Camera, MoreHorizontal, Edit, ListTodo, Trash2, ArrowUpDown, ArrowUp, ArrowDown, ChevronDown } from 'lucide-vue-next' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Checkbox } from '@/components/ui/checkbox' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { Popover, PopoverContent, PopoverTrigger, } from '@/components/ui/popover' import EditableTaskStatus from './EditableTaskStatus.vue' import TaskStatusBadge from '@/components/task/TaskStatusBadge.vue' import { type Shot, ShotStatus, TaskStatus } from '@/services/shot' // Helper function to get the appropriate sort icon const getSortIcon = (sortDirection: false | 'asc' | 'desc') => { if (sortDirection === 'asc') { return h(ArrowDown, { class: 'ml-2 h-4 w-4' }) } else if (sortDirection === 'desc') { return h(ArrowUp, { class: 'ml-2 h-4 w-4' }) } else { return h(ArrowUpDown, { class: 'ml-2 h-4 w-4' }) } } export interface ShotColumnMeta { projectId: number episodes: Array<{ id: number; name: string }> onEdit: (shot: Shot) => void onDelete: (shot: Shot) => void onViewTasks: (shot: Shot) => void onTaskStatusUpdated: (shotId: number, taskType: string, newStatus: TaskStatus) => void onTaskAssignmentUpdated?: (shotId: number, taskType: string, userId: number | null) => void onBulkTaskStatusChange?: (taskType: string, status: TaskStatus) => void getSelectedCount?: () => number getAllStatusOptions?: () => Array<{ id: string; name: string; color?: string; is_system?: boolean }> } export const createShotColumns = ( allTaskTypes: string[], meta: ShotColumnMeta ): ColumnDef[] => { const columns: ColumnDef[] = [ // Select column { id: 'select', header: ({ table }) => h(Checkbox, { modelValue: table.getIsAllPageRowsSelected(), 'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(value === true), ariaLabel: 'Select all', }), cell: ({ row }) => h(Checkbox, { modelValue: row.getIsSelected(), 'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(value === true), ariaLabel: 'Select row', onClick: (e: Event) => e.stopPropagation(), }), enableSorting: false, enableHiding: false, }, // Thumbnail column { id: 'thumbnail', header: 'Thumbnail', cell: () => { return h('div', { class: 'w-20 h-11 bg-gray-500/20 flex items-center justify-center' }, [ h(Camera, { class: 'h-6 w-6 text-muted-foreground text-gray-900' }), ]) }, enableSorting: false, }, // Shot Name column { accessorKey: 'name', header: ({ column }) => { return h( // Button, // { // variant: 'ghost', // onClick: () => column.toggleSorting(column.getIsSorted() === 'desc'), // }, // () => ['Shot Name', getSortIcon(column.getIsSorted())] 'div', {class:'flex items-center justify-center'}, ['Shot Name', getSortIcon(column.getIsSorted())] ) }, cell: ({ row }) => { const shot = row.original return h('div', { class: 'flex items-center gap-2' }, [ // h(Camera, { class: 'h-4 w-4 text-muted-foreground flex-shrink-0' }), h('span', { class: 'font-medium' }, shot.name), ]) }, }, // Episode column { id: 'episode', accessorFn: (row) => { const episode = meta.episodes.find((e) => e.id === row.episode_id) return episode ? episode.name : `Episode ${row.episode_id}` }, header: ({ column }) => { return h( 'div', {class:'flex items-center justify-center'}, ['Episode', getSortIcon(column.getIsSorted())] ) }, cell: ({ row }) => { const shot = row.original const episode = meta.episodes.find((e) => e.id === shot.episode_id) const episodeName = episode ? episode.name : `Episode ${shot.episode_id}` return h(Badge, { variant: 'outline', class: 'text-xs' }, () => episodeName) }, }, // Frames column (frame count) { id: 'frames', accessorFn: (row) => row.frame_end - row.frame_start + 1, header: ({ column }) => { return h( 'div', {class:'flex items-center justify-center'}, ['Frames', getSortIcon(column.getIsSorted())] ) }, cell: ({ row }) => { const shot = row.original const frameCount = shot.frame_end - shot.frame_start + 1 return h('span', { class: 'text-sm font-medium' }, frameCount.toString()) }, }, // Status column { accessorKey: 'status', header: ({ column }) => { return h( 'div', {class:'flex items-center justify-center'}, ['Status', getSortIcon(column.getIsSorted())] ) }, cell: ({ row }) => { const shot = row.original const variant = getStatusVariant(shot.status) const label = formatStatus(shot.status) return h(Badge, { variant, class: 'text-xs' }, () => label) }, }, ] // Add task status columns dynamically allTaskTypes.forEach((taskType) => { // Create ref for popover state for each task type const isPopoverOpen = ref(false) columns.push({ accessorKey: `task_status.${taskType}`, id: taskType, header: ({ column }) => { const selectedCount = meta.getSelectedCount?.() || 0 if (selectedCount > 0) { return h('div', { class: 'flex items-center gap-2' }, [ h( 'div', { class: 'flex items-center justify-center' }, [formatTaskType(taskType), getSortIcon(column.getIsSorted())] ), h('div', { onClick: (e: Event) => e.stopPropagation() }, [ h(Popover, { open: isPopoverOpen.value, 'onUpdate:open': (value: boolean) => { isPopoverOpen.value = value } }, { default: () => [ h(PopoverTrigger, {}, { default: () => h( Button, { variant: 'outline', size: 'sm', class: 'h-6 w-6 p-0', }, () => h(ChevronDown, { class: 'h-3 w-3' }) ), }), h(PopoverContent, { class: 'w-48 p-2', align: 'start' }, { default: () => { // Get task statuses from meta const allStatusOptions = meta.getAllStatusOptions?.() || [] return h('div', { class: 'flex flex-col gap-1' }, [ h('div', { class: 'px-2 py-1.5 text-sm font-semibold' }, `Change ${formatTaskType(taskType)} Status`), ...allStatusOptions.map((statusOption) => h( Button, { variant: 'ghost', size: 'sm', class: 'justify-start', onClick: () => { meta.onBulkTaskStatusChange?.(taskType, statusOption.id as TaskStatus) isPopoverOpen.value = false }, }, () => h(TaskStatusBadge, { status: statusOption, compact: true }) ) ), ]) }, }), ], }), ]), ]) } return h( 'div', { class: 'flex items-center justify-center' }, [formatTaskType(taskType), getSortIcon(column.getIsSorted())] ) }, cell: ({ row }) => { const shot = row.original const status = shot.task_status?.[taskType] || TaskStatus.NOT_STARTED const taskId = shot.task_ids?.[taskType] // Get assigned user ID from task_details const taskDetail = shot.task_details?.find(detail => detail.task_type === taskType) const assignedUserId = taskDetail?.assigned_user_id || null return h(EditableTaskStatus, { key: `${shot.id}-${taskType}`, // Add stable key to prevent unnecessary re-renders shotId: shot.id, taskType, status, taskId, projectId: meta.projectId, assignedUserId, onStatusUpdated: (shotId: number, taskType: string, newStatus: TaskStatus) => { meta.onTaskStatusUpdated(shotId, taskType, newStatus) }, onAssignmentUpdated: (shotId: number, taskType: string, userId: number | null) => { meta.onTaskAssignmentUpdated?.(shotId, taskType, userId) }, }) }, enableSorting: true, }) }) // Actions column columns.push({ id: 'actions', cell: ({ row }) => { const shot = row.original return h( DropdownMenu, {}, { default: () => [ h( DropdownMenuTrigger, { asChild: true }, { default: () => h( Button, { variant: 'ghost', size: 'icon-sm', onMouseDown: (e: Event) => { e.stopPropagation() }, onClick: (e: Event) => { e.stopPropagation() e.preventDefault() } }, { default: () => h(MoreHorizontal, { class: 'h-4 w-4' }), } ), } ), h( DropdownMenuContent, { align: 'end' }, { default: () => [ h( DropdownMenuItem, { onClick: (e: Event) => { e.stopPropagation() e.preventDefault() meta.onEdit(shot) } }, { default: () => [ h(Edit, { class: 'h-4 w-4 mr-2' }), 'Edit Shot', ], } ), h( DropdownMenuItem, { onClick: (e: Event) => { e.stopPropagation() e.preventDefault() meta.onViewTasks(shot) } }, { default: () => [ h(ListTodo, { class: 'h-4 w-4 mr-2' }), 'View Tasks', ], } ), h(DropdownMenuSeparator), h( DropdownMenuItem, { onClick: (e: Event) => { e.stopPropagation() e.preventDefault() meta.onDelete(shot) }, class: 'text-destructive focus:text-destructive', }, { default: () => [ h(Trash2, { class: 'h-4 w-4 mr-2' }), 'Delete Shot', ], } ), ], } ), ], } ) }, enableSorting: false, enableHiding: false, }) return columns } // Helper functions function formatTaskType(taskType: string): string { return taskType.charAt(0).toUpperCase() + taskType.slice(1) } function formatStatus(status: ShotStatus): string { return status .split('_') .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(' ') } function getStatusVariant(status: ShotStatus): 'default' | 'secondary' | 'outline' { switch (status) { case ShotStatus.NOT_STARTED: return 'secondary' case ShotStatus.IN_PROGRESS: return 'default' case ShotStatus.ON_HOLD: return 'outline' case ShotStatus.COMPLETED: return 'default' case ShotStatus.APPROVED: return 'default' default: return 'secondary' } }