26807984ee
Extracts duplication that built up as shot/asset/task features reached parity: CheckableCommandItem/ColumnToggleList replace 14+ hand-rolled checkbox-list blocks, shared toolbar pieces (debounced search, detail-panel toggle, clear-filters, segmented view/context toggle) replace copy-pasted markup in the three table toolbars, icon-only buttons standardize on the icon-sm size, TaskBulkActionsMenu's Assign To submenu matches Set Status, and a shared DetailPanelOverlay/Header/Loading/Error shell backs all three detail panels (adding a previously-missing error state to the task panel).
120 lines
3.9 KiB
Vue
120 lines
3.9 KiB
Vue
<template>
|
|
<div class="flex flex-col h-full">
|
|
<!-- References List -->
|
|
<div class="flex-1 overflow-y-auto p-4">
|
|
<div v-if="references.length === 0" class="flex flex-col items-center justify-center h-full text-muted-foreground">
|
|
<Image class="h-10 w-10 mb-2 opacity-50" />
|
|
<p class="text-sm">No reference files yet for this asset's tasks.</p>
|
|
</div>
|
|
|
|
<div v-else class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div
|
|
v-for="reference in references"
|
|
:key="reference.id"
|
|
class="border rounded-lg overflow-hidden hover:shadow-md transition-shadow"
|
|
>
|
|
<!-- File Preview -->
|
|
<div class="aspect-video bg-muted flex items-center justify-center relative">
|
|
<img
|
|
v-if="isImage(reference.file_path)"
|
|
:src="getFileUrl(reference.file_path)"
|
|
:alt="reference.file_name"
|
|
class="w-full h-full object-cover"
|
|
/>
|
|
<div v-else class="flex flex-col items-center gap-2 text-muted-foreground">
|
|
<FileIcon class="h-12 w-12" />
|
|
<span class="text-xs">{{ getFileExtension(reference.file_name) }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- File Info -->
|
|
<div class="p-3 space-y-2">
|
|
<div class="flex items-start justify-between gap-2">
|
|
<div class="flex-1 min-w-0">
|
|
<p class="text-sm font-medium truncate">{{ reference.file_name }}</p>
|
|
<div class="flex items-center gap-2 mt-1">
|
|
<Badge variant="outline" class="text-xs">
|
|
{{ formatTaskType(reference.task_type) }}
|
|
</Badge>
|
|
</div>
|
|
<p class="text-xs text-muted-foreground mt-1">
|
|
{{ reference.task_name }}
|
|
</p>
|
|
</div>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
class="flex-shrink-0"
|
|
@click="downloadFile(reference)"
|
|
>
|
|
<Download class="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
<p class="text-xs text-muted-foreground">
|
|
Uploaded {{ formatDate(reference.created_at) }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { Image, FileIcon, Download } from 'lucide-vue-next'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { Button } from '@/components/ui/button'
|
|
|
|
const props = defineProps<{
|
|
assetId: number
|
|
references: any[]
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
referencesUpdated: []
|
|
}>()
|
|
|
|
function isImage(filePath: string): boolean {
|
|
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg']
|
|
return imageExtensions.some(ext => filePath.toLowerCase().endsWith(ext))
|
|
}
|
|
|
|
function getFileUrl(filePath: string): string {
|
|
if (!filePath) return ''
|
|
if (filePath.startsWith('http')) return filePath
|
|
const cleanPath = filePath.replace(/^backend[\/\\]/, '').replace(/\\/g, '/')
|
|
return `http://localhost:8000/${cleanPath}`
|
|
}
|
|
|
|
function getFileExtension(fileName: string): string {
|
|
const parts = fileName.split('.')
|
|
return parts.length > 1 ? parts[parts.length - 1].toUpperCase() : 'FILE'
|
|
}
|
|
|
|
function formatTaskType(taskType: string): string {
|
|
return taskType.split('_').map(word =>
|
|
word.charAt(0).toUpperCase() + word.slice(1)
|
|
).join(' ')
|
|
}
|
|
|
|
function formatDate(dateString: string): string {
|
|
const date = new Date(dateString)
|
|
return date.toLocaleDateString('en-US', {
|
|
month: 'short',
|
|
day: 'numeric',
|
|
year: 'numeric'
|
|
})
|
|
}
|
|
|
|
function downloadFile(reference: any) {
|
|
const url = getFileUrl(reference.file_path)
|
|
const link = document.createElement('a')
|
|
link.href = url
|
|
link.download = reference.file_name
|
|
link.target = '_blank'
|
|
document.body.appendChild(link)
|
|
link.click()
|
|
document.body.removeChild(link)
|
|
}
|
|
</script>
|