feat: add admin components for input, metrics, tables, and user forms

- Introduced AdminInput component for standardized input fields.
- Created AdminMetricCard for displaying metrics with customizable tones.
- Added AdminPlaceholderTable for loading states in tables.
- Developed AdminSectionCard for consistent section layouts.
- Implemented AdminSectionShell for organizing admin sections.
- Added AdminSelect for dropdown selections with v-model support.
- Created AdminTable for displaying tabular data with loading and empty states.
- Introduced AdminTextarea for multi-line text input.
- Developed AdminUserFormFields for user creation and editing forms.
- Added useAdminPageHeader composable for managing admin page header state.
This commit is contained in:
2026-03-24 07:08:44 +00:00
parent e854c68ad0
commit b60f65e4d1
100 changed files with 9270 additions and 2204 deletions

View File

@@ -8,17 +8,35 @@ import NameGradient from './components/NameGradient.vue';
import QuickActions from './components/QuickActions.vue';
import RecentVideos from './components/RecentVideos.vue';
import StatsOverview from './components/StatsOverview.vue';
import type { StatProps } from '@/components/dashboard/StatsCard.vue';
import { formatBytes, isAdmin } from '@/lib/utils';
import { useTranslation } from 'i18next-vue';
import { useAuthStore } from '@/stores/auth';
const AdminOverview = defineAsyncComponent(() => import('./components/AdminOverview.vue'));
const {t} = useTranslation()
const auth = useAuthStore();
const recentVideosLoading = ref(true);
const recentVideos = ref<ModelVideo[]>([]);
const { data: usageSnapshot, isPending: isUsagePending } = useUsageQuery();
const { data: usageSnapshot, isPending: isUsagePending, refresh } = useUsageQuery();
const stats = computed(() => ({
totalVideos: usageSnapshot.value?.totalVideos ?? 0,
totalViews: recentVideos.value.reduce((sum, v: any) => sum + (v.views || 0), 0),
storageUsed: usageSnapshot.value?.totalStorage ?? 0,
storageLimit: 10737418240,
}));
const stats = computed<StatProps[]>(() => [
{
title: 'overview.stats.totalVideos',
value: usageSnapshot.value?.totalVideos ?? 0,
trend: { value: 12, isPositive: true }
},
{
title: 'overview.stats.totalViews',
value: recentVideos.value.reduce((sum, v: any) => sum + (v.views || 0), 0),
trend: { value: 8, isPositive: true }
},
{
title: 'overview.stats.storageUsed',
value: `${formatBytes(usageSnapshot.value?.totalStorage ?? 0)} / ${t('overview.stats.unlimited')}`,
color: 'warning',
trend: { value: 5, isPositive: false }
}
]);
const statsLoading = computed(() => recentVideosLoading.value || (isUsagePending.value && !usageSnapshot.value));
const fetchDashboardData = async () => {
@@ -34,6 +52,7 @@ const fetchDashboardData = async () => {
};
onMounted(() => {
refresh();
fetchDashboardData();
});
</script>
@@ -44,12 +63,12 @@ onMounted(() => {
{ label: $t('pageHeader.dashboard') }
]" />
<StatsOverview :loading="statsLoading" :stats="stats" />
<QuickActions :loading="recentVideosLoading" />
<RecentVideos :loading="recentVideosLoading" :videos="recentVideos" />
<AdminOverview v-if="isAdmin(auth.user?.role)" />
<template v-else>
<StatsOverview :loading="statsLoading" :stats="stats" />
<QuickActions :loading="recentVideosLoading" />
<RecentVideos :loading="recentVideosLoading" :videos="recentVideos" />
</template>
<!-- <StorageUsage :loading="loading" :stats="stats" /> -->
</div>
</template>