Admin panel: real data - backend API, users/audit/roles/stats, frontend wired

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Talal Sharabi
2026-02-19 15:40:34 +04:00
parent 680ba3871e
commit 842678674b
11 changed files with 2159 additions and 809 deletions

View File

@@ -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>
)
);
}