254 lines
9.3 KiB
TypeScript
254 lines
9.3 KiB
TypeScript
'use client';
|
||
|
||
import { useState, useEffect } from 'react';
|
||
import { useAuth } from '@/contexts/AuthContext';
|
||
import {
|
||
Users,
|
||
Shield,
|
||
Database,
|
||
Activity,
|
||
AlertCircle,
|
||
CheckCircle,
|
||
Server,
|
||
} from 'lucide-react';
|
||
import { statsAPI, auditLogsAPI } from '@/lib/api/admin';
|
||
import LoadingSpinner from '@/components/LoadingSpinner';
|
||
|
||
export default function AdminDashboard() {
|
||
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);
|
||
|
||
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 | null | undefined) => {
|
||
if (!d) return '-';
|
||
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: stats?.totalUsers ?? '0',
|
||
change: stats ? `+${stats.activeUsers} نشط` : '-',
|
||
color: 'bg-blue-500',
|
||
},
|
||
{
|
||
icon: Shield,
|
||
label: 'الأدوار النشطة',
|
||
value: '-',
|
||
change: 'من الأدوار',
|
||
color: 'bg-purple-500',
|
||
},
|
||
{
|
||
icon: Database,
|
||
label: 'آخر نسخة احتياطية',
|
||
value: 'قريباً',
|
||
change: 'تلقائي يومياً',
|
||
color: 'bg-green-500',
|
||
},
|
||
{
|
||
icon: Activity,
|
||
label: 'تسجيل الدخول اليوم',
|
||
value: String(stats?.loginsToday ?? 0),
|
||
change: 'مستخدم',
|
||
color: 'bg-teal-500',
|
||
},
|
||
];
|
||
|
||
const systemAlerts: { type: 'info' | 'warning'; message: string; time: string }[] = [
|
||
{
|
||
type: 'info',
|
||
message: 'النسخ الاحتياطي والتصدير ستكون متاحة قريباً',
|
||
time: '-',
|
||
},
|
||
];
|
||
|
||
return (
|
||
<div>
|
||
<div className="mb-8">
|
||
<h1 className="text-3xl font-bold text-gray-900 mb-2">لوحة تحكم المدير</h1>
|
||
<p className="text-gray-600">مرحباً {user?.username}، إليك نظرة عامة على النظام</p>
|
||
</div>
|
||
|
||
{/* Stats Grid */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||
{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">
|
||
<div className={`${stat.color} p-3 rounded-lg`}>
|
||
<Icon className="h-6 w-6 text-white" />
|
||
</div>
|
||
</div>
|
||
<h3 className="text-2xl font-bold text-gray-900 mb-1">{stat.value}</h3>
|
||
<p className="text-sm text-gray-600 mb-1">{stat.label}</p>
|
||
<p className="text-xs text-gray-500">{stat.change}</p>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||
{/* System Alerts */}
|
||
<div className="bg-white rounded-xl shadow-md p-6 border border-gray-100">
|
||
<h2 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-2">
|
||
<AlertCircle className="h-6 w-6 text-orange-500" />
|
||
تنبيهات النظام
|
||
</h2>
|
||
<div className="space-y-4">
|
||
{systemAlerts.map((alert, index) => (
|
||
<div
|
||
key={index}
|
||
className={`p-4 rounded-lg border ${
|
||
alert.type === 'warning'
|
||
? 'bg-yellow-50 border-yellow-200'
|
||
: 'bg-blue-50 border-blue-200'
|
||
}`}
|
||
>
|
||
<div className="flex items-start gap-3">
|
||
{alert.type === 'warning' ? (
|
||
<AlertCircle className="h-5 w-5 text-yellow-600 flex-shrink-0 mt-0.5" />
|
||
) : (
|
||
<Activity className="h-5 w-5 text-blue-600 flex-shrink-0 mt-0.5" />
|
||
)}
|
||
<div className="flex-1">
|
||
<p className="text-sm font-medium text-gray-900">{alert.message}</p>
|
||
<p className="text-xs text-gray-600 mt-1">{alert.time}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Recent Activities */}
|
||
<div className="bg-white rounded-xl shadow-md p-6 border border-gray-100">
|
||
<h2 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-2">
|
||
<Activity className="h-6 w-6 text-green-500" />
|
||
النشاطات الأخيرة
|
||
</h2>
|
||
<div className="space-y-4">
|
||
{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>
|
||
|
||
{/* System Status */}
|
||
<div className="mt-8 bg-white rounded-xl shadow-md p-6 border border-gray-100">
|
||
<h2 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-2">
|
||
<Server className="h-6 w-6 text-teal-500" />
|
||
حالة الخدمات
|
||
</h2>
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
{[
|
||
{ name: 'قاعدة البيانات', status: 'operational', uptime: '99.99%' },
|
||
{ name: 'خادم التطبيق', status: 'operational', uptime: '99.95%' },
|
||
{ 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">
|
||
<p className="font-semibold text-gray-900">{service.name}</p>
|
||
<CheckCircle className="h-5 w-5 text-green-500" />
|
||
</div>
|
||
<p className="text-sm text-gray-600">Uptime: {service.uptime}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Quick Actions */}
|
||
<div className="mt-8 grid grid-cols-1 md:grid-cols-3 gap-6">
|
||
<a
|
||
href="/admin/users"
|
||
className="bg-gradient-to-br from-blue-500 to-blue-600 text-white p-6 rounded-xl shadow-lg hover:shadow-xl transition-all transform hover:-translate-y-1"
|
||
>
|
||
<Users className="h-8 w-8 mb-3" />
|
||
<h3 className="text-lg font-bold mb-2">إدارة المستخدمين</h3>
|
||
<p className="text-sm text-blue-100">إضافة وتعديل المستخدمين</p>
|
||
</a>
|
||
|
||
<a
|
||
href="/admin/roles"
|
||
className="bg-gradient-to-br from-purple-500 to-purple-600 text-white p-6 rounded-xl shadow-lg hover:shadow-xl transition-all transform hover:-translate-y-1"
|
||
>
|
||
<Shield className="h-8 w-8 mb-3" />
|
||
<h3 className="text-lg font-bold mb-2">الأدوار والصلاحيات</h3>
|
||
<p className="text-sm text-purple-100">إدارة صلاحيات المستخدمين</p>
|
||
</a>
|
||
|
||
<a
|
||
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"
|
||
>
|
||
<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>
|
||
);
|
||
}
|