Phase 2: asset/shot feature parity, task actions, member management

Phase 2 of frontend_tasks.md - close feature gaps between domains:

- Asset user-assignment popover, ported from shot's EditableTaskStatus,
  wired through columns.ts and AssetBrowser.vue.
- Asset column-locking toggle: two-pane frozen-column layout ported
  into AssetsDataTable.vue (adapted for asset's page-scroll layout,
  which has no bounded-height container like shot's).
- Task table row-actions menu (View Details + Reassign only - no
  Delete/Edit, since no backend support exists for either).
- Real select-task/create-task behavior on asset and shot detail
  panels: clicking a task swaps in the actual TaskDetailPanel in
  place; "Add Task" opens a task-type picker that creates real tasks
  via the existing createAssetTask/createShotTask services.
- create-note/upload-reference/publish-version implemented via a
  task-picker that deep-links into TaskDetailPanel's Notes/
  Attachments/Submissions tabs (new initialTab prop), reusing the
  already-working task-level components instead of building three new
  bespoke forms. Also fixed shot's pre-existing dead "Add Note"/
  "Upload Reference" buttons the same way.
- Consolidated ProjectMembersManager.vue and ProjectMemberManagement.vue
  into one component, combining remove-confirmation and approved-user
  filtering with toast feedback and the shared Select/Dialog UI kit.
- Wired ProjectDetailView's "Manage Members" to navigate to the
  project's Settings > Team tab.

Also fixed a bug in this session's own new code: TaskBrowser.vue's
row-click handler is a no-op by design, so the new row-actions menu
needed to emit row-double-click (which actually opens the panel)
instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 08:45:06 +08:00
parent e8b26487db
commit cd3628a255
15 changed files with 1034 additions and 292 deletions
+88 -1
View File
@@ -1,6 +1,6 @@
import { h, ref } from 'vue'
import type { ColumnDef } from '@tanstack/vue-table'
import { ArrowUpDown, Film, Package, ChevronDown } from 'lucide-vue-next'
import { ArrowUpDown, Film, Package, ChevronDown, MoreHorizontal, Eye, UserCog } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Checkbox } from '@/components/ui/checkbox'
@@ -9,6 +9,12 @@ import {
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import TaskStatusBadge from '@/components/asset/TaskStatusBadge.vue'
import EditableTaskStatus from '@/components/task/EditableTaskStatus.vue'
import { type Task } from '@/services/task'
@@ -27,6 +33,8 @@ interface ColumnCallbacks {
onBulkStatusChange?: (status: TaskStatus) => void
onStatusUpdated?: (taskId: number, newStatus: TaskStatus) => void
getSelectedCount?: () => number
onViewDetails?: (task: Task) => void
onReassign?: (task: Task) => void
}
export const createColumns = (callbacks?: ColumnCallbacks): ColumnDef<Task>[] => {
@@ -308,5 +316,84 @@ export const createColumns = (callbacks?: ColumnCallbacks): ColumnDef<Task>[] =>
return h('div', { class: 'text-sm text-muted-foreground' }, formatDate(row.getValue('created_at')))
},
},
{
id: 'actions',
cell: ({ row }) => {
const task = row.original
return h(
DropdownMenu,
{},
{
default: () => [
h(
DropdownMenuTrigger,
{ asChild: true },
{
default: () =>
h(
Button,
{
variant: 'ghost',
size: 'sm',
class: 'h-8 w-8 p-0',
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()
callbacks?.onViewDetails?.(task)
},
},
{
default: () => [
h(Eye, { class: 'h-4 w-4 mr-2' }),
'View Details',
],
}
),
h(
DropdownMenuItem,
{
onClick: (e: Event) => {
e.stopPropagation()
e.preventDefault()
callbacks?.onReassign?.(task)
},
},
{
default: () => [
h(UserCog, { class: 'h-4 w-4 mr-2' }),
'Reassign',
],
}
),
],
}
),
],
}
)
},
},
]
}