Files
zerp/frontend/src/app/admin/page.tsx

254 lines
9.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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>
);
}