Admin panel: real data - backend API, users/audit/roles/stats, frontend wired
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
'use client';
|
||||
|
||||
import { useAuth } from '@/contexts/AuthContext'
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
import {
|
||||
Users,
|
||||
Shield,
|
||||
@@ -8,83 +9,105 @@ import {
|
||||
Activity,
|
||||
AlertCircle,
|
||||
CheckCircle,
|
||||
TrendingUp,
|
||||
Server
|
||||
} from 'lucide-react'
|
||||
Server,
|
||||
} from 'lucide-react';
|
||||
import { statsAPI, auditLogsAPI } from '@/lib/api/admin';
|
||||
import LoadingSpinner from '@/components/LoadingSpinner';
|
||||
|
||||
export default function AdminDashboard() {
|
||||
const { user } = useAuth()
|
||||
const { user } = useAuth();
|
||||
const [stats, setStats] = useState<{ totalUsers: number; activeUsers: number; inactiveUsers: number; loginsToday: number } | null>(null);
|
||||
const [recentLogs, setRecentLogs] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const stats = [
|
||||
useEffect(() => {
|
||||
async function load() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [s, logsRes] = await Promise.all([
|
||||
statsAPI.get(),
|
||||
auditLogsAPI.getAll({ pageSize: 10, page: 1 }),
|
||||
]);
|
||||
setStats(s);
|
||||
setRecentLogs(logsRes.data || []);
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
load();
|
||||
}, []);
|
||||
|
||||
const getActionLabel = (a: string) => {
|
||||
const labels: Record<string, string> = {
|
||||
CREATE: 'قام بإنشاء',
|
||||
UPDATE: 'قام بتحديث',
|
||||
DELETE: 'قام بحذف',
|
||||
ACTIVATE: 'قام بتفعيل',
|
||||
DEACTIVATE: 'قام بتعطيل',
|
||||
};
|
||||
return labels[a] || a;
|
||||
};
|
||||
|
||||
const formatTime = (d: string) => {
|
||||
const diff = Date.now() - new Date(d).getTime();
|
||||
const mins = Math.floor(diff / 60000);
|
||||
const hours = Math.floor(diff / 3600000);
|
||||
if (mins < 60) return `منذ ${mins} دقيقة`;
|
||||
if (hours < 24) return `منذ ${hours} ساعة`;
|
||||
return new Date(d).toLocaleString('ar-SA', { dateStyle: 'short', timeStyle: 'short' });
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[400px]">
|
||||
<LoadingSpinner size="lg" message="جاري تحميل لوحة التحكم..." />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const statCards = [
|
||||
{
|
||||
icon: Users,
|
||||
label: 'إجمالي المستخدمين',
|
||||
value: '24',
|
||||
change: '+3 هذا الشهر',
|
||||
color: 'bg-blue-500'
|
||||
value: stats?.totalUsers ?? '0',
|
||||
change: stats ? `+${stats.activeUsers} نشط` : '-',
|
||||
color: 'bg-blue-500',
|
||||
},
|
||||
{
|
||||
icon: Shield,
|
||||
label: 'الأدوار النشطة',
|
||||
value: '8',
|
||||
change: '2 مخصص',
|
||||
color: 'bg-purple-500'
|
||||
value: '-',
|
||||
change: 'من الأدوار',
|
||||
color: 'bg-purple-500',
|
||||
},
|
||||
{
|
||||
icon: Database,
|
||||
label: 'آخر نسخة احتياطية',
|
||||
value: 'منذ ساعتين',
|
||||
value: 'قريباً',
|
||||
change: 'تلقائي يومياً',
|
||||
color: 'bg-green-500'
|
||||
color: 'bg-green-500',
|
||||
},
|
||||
{
|
||||
icon: Activity,
|
||||
label: 'صحة النظام',
|
||||
value: '99.9%',
|
||||
change: 'ممتاز',
|
||||
color: 'bg-teal-500'
|
||||
}
|
||||
]
|
||||
|
||||
const systemAlerts = [
|
||||
{
|
||||
type: 'warning',
|
||||
message: 'يوجد 3 مستخدمين لم يسجلوا الدخول منذ 30 يوم',
|
||||
time: 'منذ ساعة'
|
||||
label: 'تسجيل الدخول اليوم',
|
||||
value: String(stats?.loginsToday ?? 0),
|
||||
change: 'مستخدم',
|
||||
color: 'bg-teal-500',
|
||||
},
|
||||
];
|
||||
|
||||
const systemAlerts: { type: 'info' | 'warning'; message: string; time: string }[] = [
|
||||
{
|
||||
type: 'info',
|
||||
message: 'تحديث النظام متاح - الإصدار 1.1.0',
|
||||
time: 'منذ 3 ساعات'
|
||||
}
|
||||
]
|
||||
|
||||
const recentActivities = [
|
||||
{
|
||||
user: 'أحمد محمد',
|
||||
action: 'قام بإنشاء مستخدم جديد',
|
||||
time: 'منذ 10 دقائق'
|
||||
message: 'النسخ الاحتياطي والتصدير ستكون متاحة قريباً',
|
||||
time: '-',
|
||||
},
|
||||
{
|
||||
user: 'فاطمة علي',
|
||||
action: 'قام بتحديث صلاحيات الدور "مدير المبيعات"',
|
||||
time: 'منذ 25 دقيقة'
|
||||
},
|
||||
{
|
||||
user: 'النظام',
|
||||
action: 'تم إنشاء نسخة احتياطية تلقائية',
|
||||
time: 'منذ ساعتين'
|
||||
},
|
||||
{
|
||||
user: 'محمد خالد',
|
||||
action: 'قام بتغيير إعدادات البريد الإلكتروني',
|
||||
time: 'منذ 3 ساعات'
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">لوحة تحكم المدير</h1>
|
||||
<p className="text-gray-600">مرحباً {user?.username}، إليك نظرة عامة على النظام</p>
|
||||
@@ -92,8 +115,8 @@ export default function AdminDashboard() {
|
||||
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
{stats.map((stat, index) => {
|
||||
const Icon = stat.icon
|
||||
{statCards.map((stat, index) => {
|
||||
const Icon = stat.icon;
|
||||
return (
|
||||
<div key={index} className="bg-white rounded-xl shadow-md p-6 border border-gray-100">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
@@ -105,7 +128,7 @@ export default function AdminDashboard() {
|
||||
<p className="text-sm text-gray-600 mb-1">{stat.label}</p>
|
||||
<p className="text-xs text-gray-500">{stat.change}</p>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -149,17 +172,25 @@ export default function AdminDashboard() {
|
||||
النشاطات الأخيرة
|
||||
</h2>
|
||||
<div className="space-y-4">
|
||||
{recentActivities.map((activity, index) => (
|
||||
<div key={index} className="flex items-start gap-3 p-3 hover:bg-gray-50 rounded-lg transition-colors">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full mt-2"></div>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm text-gray-900">
|
||||
<span className="font-semibold">{activity.user}</span> {activity.action}
|
||||
</p>
|
||||
<p className="text-xs text-gray-600 mt-1">{activity.time}</p>
|
||||
{recentLogs.length === 0 ? (
|
||||
<p className="text-sm text-gray-500">لا توجد نشاطات حديثة</p>
|
||||
) : (
|
||||
recentLogs.map((log) => (
|
||||
<div
|
||||
key={log.id}
|
||||
className="flex items-start gap-3 p-3 hover:bg-gray-50 rounded-lg transition-colors"
|
||||
>
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full mt-2" />
|
||||
<div className="flex-1">
|
||||
<p className="text-sm text-gray-900">
|
||||
<span className="font-semibold">{log.user?.username || 'نظام'}</span>{' '}
|
||||
{getActionLabel(log.action)} {log.entityType}
|
||||
</p>
|
||||
<p className="text-xs text-gray-600 mt-1">{formatTime(log.createdAt)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -174,7 +205,7 @@ export default function AdminDashboard() {
|
||||
{[
|
||||
{ name: 'قاعدة البيانات', status: 'operational', uptime: '99.99%' },
|
||||
{ name: 'خادم التطبيق', status: 'operational', uptime: '99.95%' },
|
||||
{ name: 'خدمة البريد', status: 'operational', uptime: '99.90%' }
|
||||
{ name: 'خدمة البريد', status: 'قريباً', uptime: '-' },
|
||||
].map((service, index) => (
|
||||
<div key={index} className="p-4 border border-gray-200 rounded-lg">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
@@ -182,9 +213,6 @@ export default function AdminDashboard() {
|
||||
<CheckCircle className="h-5 w-5 text-green-500" />
|
||||
</div>
|
||||
<p className="text-sm text-gray-600">Uptime: {service.uptime}</p>
|
||||
<div className="mt-2 h-2 bg-gray-200 rounded-full overflow-hidden">
|
||||
<div className="h-full bg-green-500" style={{ width: service.uptime }}></div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -211,15 +239,14 @@ export default function AdminDashboard() {
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="/admin/backup"
|
||||
href="/admin/audit-logs"
|
||||
className="bg-gradient-to-br from-green-500 to-green-600 text-white p-6 rounded-xl shadow-lg hover:shadow-xl transition-all transform hover:-translate-y-1"
|
||||
>
|
||||
<Database className="h-8 w-8 mb-3" />
|
||||
<h3 className="text-lg font-bold mb-2">النسخ الاحتياطي</h3>
|
||||
<p className="text-sm text-green-100">نسخ واستعادة قاعدة البيانات</p>
|
||||
<Activity className="h-8 w-8 mb-3" />
|
||||
<h3 className="text-lg font-bold mb-2">سجل العمليات</h3>
|
||||
<p className="text-sm text-green-100">عرض وتتبع العمليات</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user