refactor: update video components to use AppButton and improve UI consistency
- Refactored CardPopover.vue to enhance menu positioning and accessibility. - Replaced Button components with AppButton in VideoEditForm.vue and VideoInfoHeader.vue for consistent styling. - Simplified VideoSkeleton.vue by removing unused Skeleton imports and improving loading states. - Updated VideoFilters.vue to replace PrimeVue components with native HTML elements for better performance. - Enhanced VideoGrid.vue and VideoTable.vue with improved selection handling and UI updates. - Removed unused PrimeVue styles and imports in SSR routes and configuration files.
This commit is contained in:
@@ -5,11 +5,8 @@ import PencilIcon from '@/components/icons/PencilIcon.vue';
|
||||
import TrashIcon from '@/components/icons/TrashIcon.vue';
|
||||
import VideoIcon from '@/components/icons/VideoIcon.vue';
|
||||
import { formatBytes, formatDate, getStatusSeverity } from '@/lib/utils';
|
||||
import Button from 'primevue/button';
|
||||
import Column from 'primevue/column';
|
||||
import DataTable from 'primevue/datatable';
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
videos: ModelVideo[];
|
||||
selectedVideos: ModelVideo[];
|
||||
loading: boolean;
|
||||
@@ -21,6 +18,39 @@ const emit = defineEmits<{
|
||||
(e: 'edit', videoId: string): void;
|
||||
(e: 'copy', videoId: string): void;
|
||||
}>();
|
||||
|
||||
const severityClasses: Record<string, string> = {
|
||||
success: 'bg-green-100 text-green-800',
|
||||
info: 'bg-blue-100 text-blue-800',
|
||||
warn: 'bg-yellow-100 text-yellow-800',
|
||||
warning: 'bg-yellow-100 text-yellow-800',
|
||||
danger: 'bg-red-100 text-red-800',
|
||||
secondary: 'bg-gray-100 text-gray-800',
|
||||
};
|
||||
|
||||
const isAllSelected = computed(() =>
|
||||
props.videos.length > 0 && props.selectedVideos.length === props.videos.length
|
||||
);
|
||||
|
||||
const toggleAll = () => {
|
||||
if (isAllSelected.value) {
|
||||
emit('update:selectedVideos', []);
|
||||
} else {
|
||||
emit('update:selectedVideos', [...props.videos]);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleRow = (video: ModelVideo) => {
|
||||
const exists = props.selectedVideos.some(v => v.id === video.id);
|
||||
if (exists) {
|
||||
emit('update:selectedVideos', props.selectedVideos.filter(v => v.id !== video.id));
|
||||
} else {
|
||||
emit('update:selectedVideos', [...props.selectedVideos, video]);
|
||||
}
|
||||
};
|
||||
|
||||
const isSelected = (video: ModelVideo) =>
|
||||
props.selectedVideos.some(v => v.id === video.id);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -28,83 +58,85 @@ const emit = defineEmits<{
|
||||
<div v-if="loading">
|
||||
<div class="p-4 border-b border-gray-200 last:border-b-0" v-for="i in 10" :key="i">
|
||||
<div class="flex gap-4 items-center">
|
||||
<Skeleton width="5rem" height="3rem" borderRadius="6px" />
|
||||
<div class="w-20 h-12 bg-gray-200 rounded-md animate-pulse" />
|
||||
<div class="flex-1">
|
||||
<Skeleton width="40%" height="1rem" class="mb-2" />
|
||||
<Skeleton width="25%" height="0.75rem" />
|
||||
<div class="w-2/5 h-4 bg-gray-200 rounded animate-pulse mb-2" />
|
||||
<div class="w-1/4 h-3 bg-gray-200 rounded animate-pulse" />
|
||||
</div>
|
||||
<Skeleton width="8%" height="0.75rem" />
|
||||
<Skeleton width="8%" height="0.75rem" />
|
||||
<Skeleton width="4rem" height="1.5rem" borderRadius="16px" />
|
||||
<Skeleton width="5.5rem" height="1.75rem" borderRadius="6px" />
|
||||
<div class="w-[8%] h-3 bg-gray-200 rounded animate-pulse" />
|
||||
<div class="w-[8%] h-3 bg-gray-200 rounded animate-pulse" />
|
||||
<div class="w-16 h-6 bg-gray-200 rounded-full animate-pulse" />
|
||||
<div class="w-22 h-7 bg-gray-200 rounded-md animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DataTable v-else :value="videos" dataKey="id" tableStyle="min-width: 50rem" :selection="selectedVideos"
|
||||
@update:selection="emit('update:selectedVideos', $event)">
|
||||
<Column selectionMode="multiple" headerStyle="width: 3rem"></Column>
|
||||
|
||||
<Column header="Video">
|
||||
<template #body="{ data }">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-20 h-12 bg-gray-200 rounded overflow-hidden flex-shrink-0">
|
||||
<img v-if="data.thumbnail" :src="data.thumbnail" :alt="data.title"
|
||||
class="w-full h-full object-cover" />
|
||||
<div v-else class="w-full h-full flex items-center justify-center">
|
||||
<VideoIcon class="text-gray-400 text-xl w-5 h-5" />
|
||||
<table v-else class="w-full min-w-[50rem]">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200 bg-gray-50">
|
||||
<th class="w-12 px-4 py-3">
|
||||
<input type="checkbox" :checked="isAllSelected" @change="toggleAll"
|
||||
class="w-4 h-4 rounded border-gray-300 text-primary focus:ring-primary" />
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">Video</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">Status</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">Size</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">Created</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="data in videos" :key="data.id"
|
||||
class="border-b border-gray-200 last:border-b-0 hover:bg-gray-50 transition-colors"
|
||||
:class="{ 'bg-primary/5': isSelected(data) }">
|
||||
<td class="px-4 py-3">
|
||||
<input type="checkbox" :checked="isSelected(data)" @change="toggleRow(data)"
|
||||
class="w-4 h-4 rounded border-gray-300 text-primary focus:ring-primary" />
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-20 h-12 bg-gray-200 rounded overflow-hidden flex-shrink-0">
|
||||
<img v-if="data.thumbnail" :src="data.thumbnail" :alt="data.title"
|
||||
class="w-full h-full object-cover" />
|
||||
<div v-else class="w-full h-full flex items-center justify-center">
|
||||
<VideoIcon class="text-gray-400 text-xl w-5 h-5" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="font-medium text-gray-900 truncate">{{ data.title }}</p>
|
||||
<p class="text-sm text-gray-500 truncate">{{ data.description || 'No description' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="font-medium text-gray-900 truncate">{{ data.title }}</p>
|
||||
<p class="text-sm text-gray-500 truncate">{{ data.description || 'No description' }}</p>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<span class="capitalize px-2 py-0.5 text-xs font-medium rounded-full"
|
||||
:class="severityClasses[getStatusSeverity(data.status) || 'secondary']">
|
||||
{{ data.status }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<span class="text-sm text-gray-500">{{ formatBytes(data.size) }}</span>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<span class="text-sm text-gray-500">{{ formatDate(data.created_at, true) }}</span>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex items-center gap-0.5">
|
||||
<button class="p-1.5 rounded-md hover:bg-gray-100 text-gray-500 hover:text-gray-700 transition-colors"
|
||||
title="Copy link" @click="emit('copy', data.id!)">
|
||||
<LinkIcon class="w-4 h-4" />
|
||||
</button>
|
||||
<button class="p-1.5 rounded-md hover:bg-gray-100 text-gray-500 hover:text-primary transition-colors"
|
||||
title="Edit" @click="emit('edit', data.id!)">
|
||||
<PencilIcon class="w-4 h-4" />
|
||||
</button>
|
||||
<button class="p-1.5 rounded-md hover:bg-red-50 text-gray-500 hover:text-red-500 transition-colors"
|
||||
title="Delete" @click="emit('delete', data.id!)">
|
||||
<TrashIcon class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column header="Status">
|
||||
<template #body="{ data }">
|
||||
<Tag :value="data.status" :severity="getStatusSeverity(data.status)"
|
||||
class="capitalize px-2 py-0.5 text-xs" />
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<!-- <Column header="Duration">
|
||||
<template #body="{ data }">
|
||||
<span class="text-sm text-gray-500">{{ formatDuration(data.duration) }}</span>
|
||||
</template>
|
||||
</Column> -->
|
||||
|
||||
<Column header="Size">
|
||||
<template #body="{ data }">
|
||||
<span class="text-sm text-gray-500">{{ formatBytes(data.size) }}</span>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column header="Created">
|
||||
<template #body="{ data }">
|
||||
<span class="text-sm text-gray-500">{{ formatDate(data.created_at, true) }}</span>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column header="Actions">
|
||||
<template #body="{ data }">
|
||||
<div class="flex items-center gap-0.5">
|
||||
<Button text rounded size="small" severity="secondary" title="Copy link"
|
||||
@click="emit('copy', data.id)">
|
||||
<LinkIcon class="w-4 h-4" />
|
||||
</Button>
|
||||
<Button text rounded size="small" title="Edit"
|
||||
@click="emit('edit', data.id)">
|
||||
<PencilIcon class="w-4 h-4" />
|
||||
</Button>
|
||||
<Button text rounded size="small" severity="danger" title="Delete"
|
||||
@click="emit('delete', data.id)">
|
||||
<TrashIcon class="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user