Redesign note display as message cards; fix reply and Textarea v-model bugs

- NoteItem.vue: render each note as a rounded card (own notes tinted)
  instead of a flat list row.
- TaskNotes.vue: show a "Replying to X" banner with cancel and
  auto-focus the composer when Reply is clicked, so replying is
  actually visible instead of silently setting hidden state.
- ShotDetailPanel/ShotBrowser/TaskDetailPanel: carry the target note
  id through select-task so replying from the shot-level aggregated
  notes view pre-fills the reply in the destination task's panel
  instead of just switching tabs.
- Textarea.vue: fix v-model, which was never wired up (modelValue/
  update:modelValue fell through as dead attrs on the native
  textarea), so typed content never reached the bound ref anywhere
  it's used - notes, submissions, and shot/asset/episode/project
  description fields.
- AssetDetailPanel/ShotDetailPanel/TaskDetailPanel: drop the header
  status badge and switch tab labels to icons (with an unread-style
  badge on Notes), fixing the TabsTrigger clipping/vertical alignment
  it introduced.
This commit is contained in:
2026-07-19 00:00:11 +08:00
parent 4bffec642e
commit 762bd34f74
8 changed files with 262 additions and 133 deletions
@@ -12,31 +12,39 @@
<!-- Task Details -->
<div v-else-if="task" class="flex-1 flex flex-col min-h-0">
<DetailPanelHeader class="flex-shrink-0" :title="task.name" @close="emit('close')">
<template #badges>
<TaskStatusBadge :status="task.status" class="flex-shrink-0" />
</template>
</DetailPanelHeader>
<!-- Tabbed Content -->
<Tabs :default-value="initialTab || 'infos'" class="flex-1 flex flex-col min-h-0">
<!-- Tabs List (Fixed) -->
<TabsList class="flex-shrink-0 mx-0 mt-0 grid w-full grid-cols-4 rounded-none border-b">
<TabsTrigger value="infos">Infos</TabsTrigger>
<TabsTrigger value="notes">
Notes
<Badge v-if="notes.length > 0" variant="secondary" class="ml-2">
{{ notes.length }}
</Badge>
<TabsTrigger value="infos" title="Infos">
<Info class="h-4 w-4" />
<span class="sr-only">Infos</span>
</TabsTrigger>
<TabsTrigger value="attachments">
Attachments
<Badge v-if="attachments.length > 0" variant="secondary" class="ml-2">
<TabsTrigger value="notes" title="Notes">
<span class="relative inline-flex">
<MessageSquare class="h-4 w-4" />
<span
v-if="notes.length > 0"
class="absolute -top-1.5 -right-1.5 h-3.5 w-3.5 rounded-full bg-red-500 text-white text-[9px] leading-none flex items-center justify-center"
>
{{ notes.length > 99 ? '99+' : notes.length }}
</span>
</span>
<span class="sr-only">Notes</span>
</TabsTrigger>
<TabsTrigger value="attachments" title="Attachments">
<Paperclip class="h-4 w-4" />
<span class="sr-only">Attachments</span>
<Badge v-if="attachments.length > 0" variant="secondary" class="ml-1 h-4 px-1 text-[10px]">
{{ attachments.length }}
</Badge>
</TabsTrigger>
<TabsTrigger value="submissions">
Submissions
<Badge v-if="submissions.length > 0" variant="secondary" class="ml-2">
<TabsTrigger value="submissions" title="Submissions">
<Upload class="h-4 w-4" />
<span class="sr-only">Submissions</span>
<Badge v-if="submissions.length > 0" variant="secondary" class="ml-1 h-4 px-1 text-[10px]">
{{ submissions.length }}
</Badge>
</TabsTrigger>
@@ -178,7 +186,12 @@
<!-- Notes Tab -->
<TabsContent value="notes" class="flex-1 m-0 overflow-hidden">
<TaskNotes :task-id="taskId" :notes="notes" @notes-updated="loadNotes" />
<TaskNotes
:task-id="taskId"
:notes="notes"
:initial-reply-note-id="initialReplyNoteId"
@notes-updated="loadNotes"
/>
</TabsContent>
<!-- Attachments Tab -->
@@ -262,7 +275,7 @@
<script setup lang="ts">
import { ref, onMounted, watch, computed } from 'vue'
import { Play, Upload, UserPlus, Calendar, User } from 'lucide-vue-next'
import { Play, Upload, UserPlus, Calendar, User, Info, MessageSquare, Paperclip } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Label } from '@/components/ui/label'
@@ -299,7 +312,6 @@ import {
CommandItem,
CommandList,
} from '@/components/ui/command'
import TaskStatusBadge from './TaskStatusBadge.vue'
import TaskNotes from './TaskNotes.vue'
import TaskAttachments from './TaskAttachments.vue'
import TaskSubmissions from './TaskSubmissions.vue'
@@ -311,6 +323,7 @@ import { useToast } from '@/components/ui/toast/use-toast'
const props = defineProps<{
taskId: number
initialTab?: string
initialReplyNoteId?: number
}>()
const emit = defineEmits<{