Init Repo

This commit is contained in:
2026-02-28 03:22:04 +08:00
commit de59b57ee7
883 changed files with 156857 additions and 0 deletions
+180
View File
@@ -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>