Init Repo
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<Card
|
||||
class="group hover:shadow-md transition-all duration-200 cursor-pointer border-border/50 hover:border-border"
|
||||
@click="$emit('select', shot)"
|
||||
>
|
||||
<CardHeader class="pb-3">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex items-center gap-2 min-w-0 flex-1">
|
||||
<div class="flex-shrink-0">
|
||||
<Camera class="h-5 w-5 text-muted-foreground" />
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<CardTitle class="text-base truncate">{{ shot.name }}</CardTitle>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Frames {{ shot.frame_start }}-{{ shot.frame_end }}
|
||||
<span class="ml-2">({{ frameCount }} frames)</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions Menu -->
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild @click.stop>
|
||||
<Button variant="ghost" size="sm" class="h-8 w-8 p-0 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<MoreHorizontal class="h-4 w-4" />
|
||||
<span class="sr-only">Shot actions</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem @click.stop="$emit('edit', shot)">
|
||||
<Edit class="h-4 w-4 mr-2" />
|
||||
Edit Shot
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click.stop="$emit('select', shot)">
|
||||
<ListTodo class="h-4 w-4 mr-2" />
|
||||
View Details
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
@click.stop="$emit('delete', shot)"
|
||||
class="text-destructive focus:text-destructive"
|
||||
>
|
||||
<Trash2 class="h-4 w-4 mr-2" />
|
||||
Delete Shot
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent class="pt-0">
|
||||
<!-- Status Badge -->
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<Badge :variant="getStatusVariant(shot.status)" class="text-xs">
|
||||
<div
|
||||
class="w-2 h-2 rounded-full mr-1"
|
||||
:class="getStatusColor(shot.status)"
|
||||
></div>
|
||||
{{ formatStatus(shot.status) }}
|
||||
</Badge>
|
||||
|
||||
<!-- Task Count -->
|
||||
<div class="flex items-center gap-1 text-sm text-muted-foreground">
|
||||
<ListTodo class="h-3 w-3" />
|
||||
<span>{{ shot.task_count }} tasks</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<p
|
||||
v-if="shot.description"
|
||||
class="text-sm text-muted-foreground line-clamp-2 mb-3"
|
||||
>
|
||||
{{ shot.description }}
|
||||
</p>
|
||||
|
||||
<!-- Metadata -->
|
||||
<div class="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<span>Created {{ formatDate(shot.created_at) }}</span>
|
||||
<span v-if="shot.updated_at !== shot.created_at">
|
||||
Updated {{ formatDate(shot.updated_at) }}
|
||||
</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Camera, MoreHorizontal, Edit, ListTodo, Trash2 } from 'lucide-vue-next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { ShotStatus, type Shot } from '@/services/shot'
|
||||
|
||||
interface Props {
|
||||
shot: Shot
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'select', shot: Shot): void
|
||||
(e: 'edit', shot: Shot): void
|
||||
(e: 'delete', shot: Shot): void
|
||||
(e: 'view-tasks', shot: Shot): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
defineEmits<Emits>()
|
||||
|
||||
// Computed properties
|
||||
const frameCount = computed(() => {
|
||||
return props.shot.frame_end - props.shot.frame_start + 1
|
||||
})
|
||||
|
||||
// Methods
|
||||
const formatStatus = (status: ShotStatus) => {
|
||||
return status.split('_').map(word =>
|
||||
word.charAt(0).toUpperCase() + word.slice(1)
|
||||
).join(' ')
|
||||
}
|
||||
|
||||
const getStatusVariant = (status: ShotStatus) => {
|
||||
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'
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusColor = (status: ShotStatus) => {
|
||||
switch (status) {
|
||||
case ShotStatus.NOT_STARTED:
|
||||
return 'bg-gray-400'
|
||||
case ShotStatus.IN_PROGRESS:
|
||||
return 'bg-blue-500'
|
||||
case ShotStatus.ON_HOLD:
|
||||
return 'bg-yellow-500'
|
||||
case ShotStatus.COMPLETED:
|
||||
return 'bg-green-500'
|
||||
case ShotStatus.APPROVED:
|
||||
return 'bg-emerald-600'
|
||||
default:
|
||||
return 'bg-gray-400'
|
||||
}
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString)
|
||||
return date.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user