admin_users_personels

This commit is contained in:
Roo
2026-06-29 14:11:15 +00:00
parent 383e5511b6
commit df4a0f07ff
237 changed files with 17849 additions and 2934 deletions

View File

@@ -2,114 +2,182 @@
<div>
<!-- Page Header -->
<div class="mb-8">
<h1 class="text-2xl font-bold text-white">Dashboard</h1>
<p class="text-slate-400 mt-1">ServiceFinder administration overview</p>
<h1 class="text-2xl font-bold text-white">{{ t('dashboard.title') }}</h1>
<p class="text-slate-400 mt-1">{{ t('dashboard.subtitle') }}</p>
</div>
<!-- Stat Tiles Grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-6 mb-8">
<!-- Active Users -->
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6 hover:border-indigo-500/50 transition group">
<div class="flex items-center justify-between mb-4">
<div class="p-2 rounded-lg bg-indigo-500/10 text-indigo-400 group-hover:bg-indigo-500/20 transition">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" />
</svg>
</div>
<span class="text-xs font-medium text-emerald-400 bg-emerald-400/10 px-2 py-1 rounded-full">+12%</span>
</div>
<h3 class="text-sm font-medium text-slate-400 uppercase tracking-wider">Active Users</h3>
<p class="text-3xl font-bold text-white mt-2">2,847</p>
<p class="text-xs text-slate-500 mt-1">1,240 new this month</p>
</div>
<!-- Total Vehicles -->
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6 hover:border-cyan-500/50 transition group">
<div class="flex items-center justify-between mb-4">
<div class="p-2 rounded-lg bg-cyan-500/10 text-cyan-400 group-hover:bg-cyan-500/20 transition">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.5 21v-7.5a.75.75 0 01.75-.75h3a.75.75 0 01.75.75V21m-4.5 0H2.36m11.14 0H18m0 0h3.64m-1.39 0V9.349m-16.5 11.65V9.35m0 0a3.001 3.001 0 003.75-.615A2.993 2.993 0 009.75 9.75c.896 0 1.7-.393 2.25-1.016a2.993 2.993 0 002.25 1.016c.896 0 1.7-.393 2.25-1.016a3.001 3.001 0 003.75.614m-16.5 0a3.004 3.004 0 01-.621-4.72L4.318 3.44A1.5 1.5 0 015.378 3h13.243a1.5 1.5 0 011.06.44l1.19 1.189a3 3 0 01-.621 4.72m-13.5 8.65h3.75a.75.75 0 00.75-.75V13.5a.75.75 0 00-.75-.75H6.75a.75.75 0 00-.75.75v3.75c0 .415.336.75.75.75z" />
</svg>
</div>
<span class="text-xs font-medium text-emerald-400 bg-emerald-400/10 px-2 py-1 rounded-full">+5%</span>
</div>
<h3 class="text-sm font-medium text-slate-400 uppercase tracking-wider">Total Vehicles</h3>
<p class="text-3xl font-bold text-white mt-2">12,493</p>
<p class="text-xs text-slate-500 mt-1">342 added this month</p>
</div>
<!-- Monthly Revenue -->
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6 hover:border-emerald-500/50 transition group">
<div class="flex items-center justify-between mb-4">
<div class="p-2 rounded-lg bg-emerald-500/10 text-emerald-400 group-hover:bg-emerald-500/20 transition">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v12m-3-2.818l.879.659c1.171.879 3.07.879 4.242 0 1.172-.879 1.172-2.303 0-3.182C13.536 12.219 12.768 12 12 12c-.725 0-1.45-.22-2.003-.659-1.106-.879-1.106-2.303 0-3.182s2.9-.879 4.006 0l.415.33M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<span class="text-xs font-medium text-emerald-400 bg-emerald-400/10 px-2 py-1 rounded-full">+18%</span>
</div>
<h3 class="text-sm font-medium text-slate-400 uppercase tracking-wider">Monthly Revenue</h3>
<p class="text-3xl font-bold text-white mt-2">48,250</p>
<p class="text-xs text-slate-500 mt-1">6,200 from premium</p>
</div>
<!-- Error Logs -->
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6 hover:border-rose-500/50 transition group">
<div class="flex items-center justify-between mb-4">
<div class="p-2 rounded-lg bg-rose-500/10 text-rose-400 group-hover:bg-rose-500/20 transition">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
</svg>
</div>
<span class="text-xs font-medium text-rose-400 bg-rose-400/10 px-2 py-1 rounded-full">+3</span>
</div>
<h3 class="text-sm font-medium text-slate-400 uppercase tracking-wider">Error Logs</h3>
<p class="text-3xl font-bold text-white mt-2">23</p>
<p class="text-xs text-slate-500 mt-1">3 critical today</p>
<!-- Loading State -->
<div v-if="loading" class="flex items-center justify-center py-20">
<div class="text-slate-400 flex items-center gap-3">
<svg class="w-5 h-5 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
{{ t('dashboard.loading') }}
</div>
</div>
<!-- Quick Actions & Recent Activity -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Quick Actions -->
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6">
<h2 class="text-lg font-semibold text-white mb-4">Quick Actions</h2>
<div class="space-y-3">
<button class="w-full flex items-center gap-3 px-4 py-3 bg-slate-700/50 hover:bg-slate-700 rounded-lg text-sm text-slate-300 hover:text-white transition text-left">
<svg class="w-5 h-5 text-indigo-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" />
</svg>
Invite New Admin User
</button>
<button class="w-full flex items-center gap-3 px-4 py-3 bg-slate-700/50 hover:bg-slate-700 rounded-lg text-sm text-slate-300 hover:text-white transition text-left">
<svg class="w-5 h-5 text-cyan-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.5 21v-7.5a.75.75 0 01.75-.75h3a.75.75 0 01.75.75V21m-4.5 0H2.36m11.14 0H18m0 0h3.64m-1.39 0V9.349m-16.5 11.65V9.35m0 0a3.001 3.001 0 003.75-.615A2.993 2.993 0 009.75 9.75c.896 0 1.7-.393 2.25-1.016a2.993 2.993 0 002.25 1.016c.896 0 1.7-.393 2.25-1.016a3.001 3.001 0 003.75.614m-16.5 0a3.004 3.004 0 01-.621-4.72L4.318 3.44A1.5 1.5 0 015.378 3h13.243a1.5 1.5 0 011.06.44l1.19 1.189a3 3 0 01-.621 4.72m-13.5 8.65h3.75a.75.75 0 00.75-.75V13.5a.75.75 0 00-.75-.75H6.75a.75.75 0 00-.75.75v3.75c0 .415.336.75.75.75z" />
</svg>
Add New Vehicle
</button>
<button class="w-full flex items-center gap-3 px-4 py-3 bg-slate-700/50 hover:bg-slate-700 rounded-lg text-sm text-slate-300 hover:text-white transition text-left">
<svg class="w-5 h-5 text-emerald-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15a2.25 2.25 0 012.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25zM6.75 12h.008v.008H6.75V12zm0 3h.008v.008H6.75V15zm0 3h.008v.008H6.75V18z" />
</svg>
View Service Reports
</button>
<!-- Error State -->
<div v-else-if="error" class="bg-rose-500/10 border border-rose-500/30 rounded-xl p-6 mb-8">
<p class="text-rose-400">{{ t('dashboard.error') }}</p>
</div>
<!-- Dashboard Content -->
<template v-else>
<!-- Stat Tiles Grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-6 mb-8">
<!-- Active Users -->
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6 hover:border-indigo-500/50 transition group">
<div class="flex items-center justify-between mb-4">
<div class="p-2 rounded-lg bg-indigo-500/10 text-indigo-400 group-hover:bg-indigo-500/20 transition">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" />
</svg>
</div>
<span v-if="stats.new_users_this_month > 0" class="text-xs font-medium text-emerald-400 bg-emerald-400/10 px-2 py-1 rounded-full">+{{ stats.new_users_this_month }}</span>
</div>
<h3 class="text-sm font-medium text-slate-400 uppercase tracking-wider">{{ t('dashboard.active_users') }}</h3>
<p class="text-3xl font-bold text-white mt-2">{{ formatNumber(stats.active_users) }}</p>
<p class="text-xs text-slate-500 mt-1">{{ formatNumber(stats.new_users_this_month) }} {{ t('dashboard.new_this_month') }}</p>
</div>
<!-- Total Vehicles -->
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6 hover:border-cyan-500/50 transition group">
<div class="flex items-center justify-between mb-4">
<div class="p-2 rounded-lg bg-cyan-500/10 text-cyan-400 group-hover:bg-cyan-500/20 transition">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.5 21v-7.5a.75.75 0 01.75-.75h3a.75.75 0 01.75.75V21m-4.5 0H2.36m11.14 0H18m0 0h3.64m-1.39 0V9.349m-16.5 11.65V9.35m0 0a3.001 3.001 0 003.75-.615A2.993 2.993 0 009.75 9.75c.896 0 1.7-.393 2.25-1.016a2.993 2.993 0 002.25 1.016c.896 0 1.7-.393 2.25-1.016a3.001 3.001 0 003.75.614m-16.5 0a3.004 3.004 0 01-.621-4.72L4.318 3.44A1.5 1.5 0 015.378 3h13.243a1.5 1.5 0 011.06.44l1.19 1.189a3 3 0 01-.621 4.72m-13.5 8.65h3.75a.75.75 0 00.75-.75V13.5a.75.75 0 00-.75-.75H6.75a.75.75 0 00-.75.75v3.75c0 .415.336.75.75.75z" />
</svg>
</div>
</div>
<h3 class="text-sm font-medium text-slate-400 uppercase tracking-wider">{{ t('dashboard.total_vehicles') }}</h3>
<p class="text-3xl font-bold text-white mt-2">{{ formatNumber(stats.total_vehicles) }}</p>
</div>
<!-- Organizations -->
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6 hover:border-emerald-500/50 transition group">
<div class="flex items-center justify-between mb-4">
<div class="p-2 rounded-lg bg-emerald-500/10 text-emerald-400 group-hover:bg-emerald-500/20 transition">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.25 21h19.5m-18-18v18m10.5-18v18m6-13.5V21M6.75 6.75h.75m-.75 3h.75m-.75 3h.75m3-6h.75m-.75 3h.75m-.75 3h.75M6.75 21v-3.375c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21M3 3h12m-.75 4.5H21m-3.75 3.75h.008v.008h-.008v-.008zm0 3h.008v.008h-.008v-.008zm0 3h.008v.008h-.008v-.008z" />
</svg>
</div>
</div>
<h3 class="text-sm font-medium text-slate-400 uppercase tracking-wider">{{ t('dashboard.organizations') }}</h3>
<p class="text-3xl font-bold text-white mt-2">{{ formatNumber(stats.active_organizations_count) }}</p>
<p class="text-xs text-slate-500 mt-1">{{ formatNumber(stats.total_memberships) }} {{ t('dashboard.memberships') }}</p>
</div>
<!-- New Users (This Month) -->
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6 hover:border-violet-500/50 transition group">
<div class="flex items-center justify-between mb-4">
<div class="p-2 rounded-lg bg-violet-500/10 text-violet-400 group-hover:bg-violet-500/20 transition">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" />
</svg>
</div>
</div>
<h3 class="text-sm font-medium text-slate-400 uppercase tracking-wider">{{ t('dashboard.new_users') }}</h3>
<p class="text-3xl font-bold text-white mt-2">{{ formatNumber(stats.new_users_this_month) }}</p>
<p class="text-xs text-slate-500 mt-1">{{ formatNumber(stats.new_users_today) }} {{ t('dashboard.new_today') }}</p>
</div>
</div>
<!-- Recent Activity -->
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6">
<h2 class="text-lg font-semibold text-white mb-4">Recent Activity</h2>
<div class="space-y-4">
<div v-for="(activity, index) in recentActivities" :key="index" class="flex items-start gap-3">
<div class="w-2 h-2 mt-2 rounded-full flex-shrink-0" :class="activity.dotColor" />
<div class="flex-1 min-w-0">
<p class="text-sm text-slate-300">{{ activity.text }}</p>
<p class="text-xs text-slate-500 mt-0.5">{{ activity.time }}</p>
<!-- Secondary Stats Row -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
<!-- Users by Role -->
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6">
<h3 class="text-sm font-medium text-slate-400 uppercase tracking-wider mb-4">{{ t('dashboard.users_by_role') }}</h3>
<div class="space-y-3">
<div v-for="(count, role) in stats.users_by_role" :key="role" class="flex items-center justify-between">
<span class="text-sm text-slate-300">{{ role }}</span>
<span class="text-sm font-semibold text-white">{{ count }}</span>
</div>
</div>
</div>
<!-- Users by Plan -->
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6">
<h3 class="text-sm font-medium text-slate-400 uppercase tracking-wider mb-4">{{ t('dashboard.users_by_plan') }}</h3>
<div class="space-y-3">
<div v-for="(count, plan) in stats.users_by_plan" :key="plan" class="flex items-center justify-between">
<span class="text-sm text-slate-300">{{ plan }}</span>
<span class="text-sm font-semibold text-white">{{ count }}</span>
</div>
</div>
</div>
<!-- Users by Language -->
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6">
<h3 class="text-sm font-medium text-slate-400 uppercase tracking-wider mb-4">{{ t('dashboard.users_by_language') }}</h3>
<div class="space-y-3">
<div v-for="(count, lang) in stats.users_by_language" :key="lang" class="flex items-center justify-between">
<span class="text-sm text-slate-300">{{ lang }}</span>
<span class="text-sm font-semibold text-white">{{ count }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- Registration Trend -->
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6 mb-8">
<h3 class="text-sm font-medium text-slate-400 uppercase tracking-wider mb-4">{{ t('dashboard.registration_trend') }}</h3>
<div class="flex items-end gap-1 h-32">
<div
v-for="(day, idx) in registrationTrend"
:key="idx"
class="flex-1 bg-indigo-500/60 hover:bg-indigo-400/80 rounded-t transition-all cursor-pointer relative group"
:style="{ height: day.height + '%' }"
:title="day.date + ': ' + day.count + ' users'"
>
<div class="absolute -top-8 left-1/2 -translate-x-1/2 bg-slate-700 text-xs text-white px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition whitespace-nowrap">
{{ day.date }}: {{ day.count }}
</div>
</div>
</div>
</div>
<!-- Quick Actions & Recent Activity -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Quick Actions -->
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6">
<h2 class="text-lg font-semibold text-white mb-4">{{ t('dashboard.quick_actions') }}</h2>
<div class="space-y-3">
<button class="w-full flex items-center gap-3 px-4 py-3 bg-slate-700/50 hover:bg-slate-700 rounded-lg text-sm text-slate-300 hover:text-white transition text-left">
<svg class="w-5 h-5 text-indigo-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" />
</svg>
{{ t('dashboard.invite_admin') }}
</button>
<button class="w-full flex items-center gap-3 px-4 py-3 bg-slate-700/50 hover:bg-slate-700 rounded-lg text-sm text-slate-300 hover:text-white transition text-left">
<svg class="w-5 h-5 text-cyan-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.5 21v-7.5a.75.75 0 01.75-.75h3a.75.75 0 01.75.75V21m-4.5 0H2.36m11.14 0H18m0 0h3.64m-1.39 0V9.349m-16.5 11.65V9.35m0 0a3.001 3.001 0 003.75-.615A2.993 2.993 0 009.75 9.75c.896 0 1.7-.393 2.25-1.016a2.993 2.993 0 002.25 1.016c.896 0 1.7-.393 2.25-1.016a3.001 3.001 0 003.75.614m-16.5 0a3.004 3.004 0 01-.621-4.72L4.318 3.44A1.5 1.5 0 015.378 3h13.243a1.5 1.5 0 011.06.44l1.19 1.189a3 3 0 01-.621 4.72m-13.5 8.65h3.75a.75.75 0 00.75-.75V13.5a.75.75 0 00-.75-.75H6.75a.75.75 0 00-.75.75v3.75c0 .415.336.75.75.75z" />
</svg>
{{ t('dashboard.add_vehicle') }}
</button>
<button class="w-full flex items-center gap-3 px-4 py-3 bg-slate-700/50 hover:bg-slate-700 rounded-lg text-sm text-slate-300 hover:text-white transition text-left">
<svg class="w-5 h-5 text-emerald-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15a2.25 2.25 0 012.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25zM6.75 12h.008v.008H6.75V12zm0 3h.008v.008H6.75V15zm0 3h.008v.008H6.75V18z" />
</svg>
{{ t('dashboard.view_reports') }}
</button>
</div>
</div>
<!-- Recent Activity -->
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6">
<h2 class="text-lg font-semibold text-white mb-4">{{ t('dashboard.recent_activity') }}</h2>
<div class="space-y-4">
<div v-for="(activity, index) in recentActivities" :key="index" class="flex items-start gap-3">
<div class="w-2 h-2 mt-2 rounded-full flex-shrink-0" :class="activity.dotColor" />
<div class="flex-1 min-w-0">
<p class="text-sm text-slate-300">{{ activity.text }}</p>
<p class="text-xs text-slate-500 mt-0.5">{{ activity.time }}</p>
</div>
</div>
</div>
</div>
</div>
</template>
</div>
</template>
@@ -118,6 +186,63 @@ definePageMeta({
middleware: 'auth',
})
interface DashboardStats {
total_users: number
active_users: number
deleted_users: number
banned_users: number
new_users_today: number
new_users_this_week: number
new_users_this_month: number
users_by_role: Record<string, number>
users_by_plan: Record<string, number>
users_by_language: Record<string, number>
users_with_person: number
users_without_person: number
total_vehicles: number
active_organizations_count: number
total_memberships: number
registration_trend: { date: string; count: number }[]
}
const { t } = useI18n()
const loading = ref(true)
const error = ref(false)
const stats = ref<DashboardStats>({
total_users: 0,
active_users: 0,
deleted_users: 0,
banned_users: 0,
new_users_today: 0,
new_users_this_week: 0,
new_users_this_month: 0,
users_by_role: {},
users_by_plan: {},
users_by_language: {},
users_with_person: 0,
users_without_person: 0,
total_vehicles: 0,
active_organizations_count: 0,
total_memberships: 0,
registration_trend: [],
})
const registrationTrend = computed(() => {
const trend = stats.value.registration_trend
if (!trend.length) return []
const maxCount = Math.max(...trend.map(d => d.count))
return trend.map(d => ({
date: d.date,
count: d.count,
height: maxCount > 0 ? (d.count / maxCount) * 100 : 0,
}))
})
function formatNumber(num: number): string {
return new Intl.NumberFormat().format(num)
}
const recentActivities = [
{ text: 'New user registered: john.doe@example.com', time: '2 minutes ago', dotColor: 'bg-emerald-400' },
{ text: 'Vehicle #V-8842 added to catalog', time: '15 minutes ago', dotColor: 'bg-cyan-400' },
@@ -125,4 +250,21 @@ const recentActivities = [
{ text: 'System error: DVLA API timeout on request #4421', time: '2 hours ago', dotColor: 'bg-rose-400' },
{ text: 'Permission updated for user: superadmin@sf.hu', time: '3 hours ago', dotColor: 'bg-indigo-400' },
]
onMounted(async () => {
try {
// Read the access_token from cookie for authenticated API calls
const tokenCookie = useCookie('access_token')
const token = tokenCookie.value
const data = await $fetch('/api/v1/admin/users/stats', {
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
})
stats.value = data as DashboardStats
} catch (e) {
console.error('Failed to load dashboard stats:', e)
error.value = true
} finally {
loading.value = false
}
})
</script>