main
This commit is contained in:
@@ -0,0 +1,430 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { useAuth } from '@/components/providers/MockAuthProvider'
|
||||
import { useLanguage } from '@/components/providers/LanguageProvider'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import {
|
||||
BarChart3,
|
||||
Users,
|
||||
MessageSquare,
|
||||
Star,
|
||||
TrendingUp,
|
||||
Clock,
|
||||
Shield,
|
||||
Heart,
|
||||
LogOut,
|
||||
Languages,
|
||||
ArrowLeft,
|
||||
Eye,
|
||||
CheckCircle,
|
||||
AlertTriangle
|
||||
} from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar } from 'recharts'
|
||||
|
||||
interface SurveyStats {
|
||||
totalSurveys: number
|
||||
averageRating: number
|
||||
ratingDistribution: Record<number, number>
|
||||
}
|
||||
|
||||
export default function AdminPage() {
|
||||
const { user, userProfile, logout } = useAuth()
|
||||
const { t, language, setLanguage } = useLanguage()
|
||||
const router = useRouter()
|
||||
const [surveyStats, setSurveyStats] = useState<SurveyStats | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
router.push('/')
|
||||
return
|
||||
}
|
||||
|
||||
if (userProfile && userProfile.role !== 'ADMIN') {
|
||||
router.push('/dashboard')
|
||||
return
|
||||
}
|
||||
|
||||
fetchDashboardData()
|
||||
}, [user, userProfile, router])
|
||||
|
||||
const fetchDashboardData = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/survey')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setSurveyStats(data.statistics)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching dashboard data:', error)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout()
|
||||
}
|
||||
|
||||
const toggleLanguage = () => {
|
||||
setLanguage(language === 'en' ? 'ar' : 'en')
|
||||
}
|
||||
|
||||
// Mock data for charts
|
||||
const responseTimeData = [
|
||||
{ name: 'Mon', time: 1.2 },
|
||||
{ name: 'Tue', time: 0.8 },
|
||||
{ name: 'Wed', time: 1.5 },
|
||||
{ name: 'Thu', time: 1.1 },
|
||||
{ name: 'Fri', time: 0.9 },
|
||||
{ name: 'Sat', time: 1.3 },
|
||||
{ name: 'Sun', time: 1.0 },
|
||||
]
|
||||
|
||||
const satisfactionData = [
|
||||
{ name: 'Week 1', satisfaction: 92 },
|
||||
{ name: 'Week 2', satisfaction: 88 },
|
||||
{ name: 'Week 3', satisfaction: 95 },
|
||||
{ name: 'Week 4', satisfaction: 91 },
|
||||
]
|
||||
|
||||
const ratingData = surveyStats ?
|
||||
Object.entries(surveyStats.ratingDistribution).map(([rating, count]) => ({
|
||||
rating: `${rating} Stars`,
|
||||
count
|
||||
})) : []
|
||||
|
||||
if (!user || !userProfile || isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
||||
<p className="text-gray-600">Loading admin dashboard...</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (userProfile.role !== 'ADMIN') {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<AlertTriangle className="mx-auto h-12 w-12 text-red-500 mb-4" />
|
||||
<h1 className="text-xl font-bold text-gray-900 mb-2">Access Denied</h1>
|
||||
<p className="text-gray-600">You don't have permission to access this page.</p>
|
||||
<Link href="/dashboard" className="mt-4 inline-block bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700">
|
||||
Go to Dashboard
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* Header */}
|
||||
<header className="bg-white shadow-sm border-b">
|
||||
<div className="container mx-auto px-4 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
<Link href="/dashboard" className="flex items-center space-x-2 text-blue-600 hover:text-blue-800">
|
||||
<ArrowLeft size={20} />
|
||||
<span>Back to Dashboard</span>
|
||||
</Link>
|
||||
<div className="text-gray-300">|</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<BarChart3 className="text-blue-600" size={24} />
|
||||
<h1 className="text-xl font-bold text-gray-900">
|
||||
Admin Dashboard
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
<button
|
||||
onClick={toggleLanguage}
|
||||
className="flex items-center space-x-2 px-3 py-2 rounded-lg bg-gray-100 hover:bg-gray-200 transition-colors"
|
||||
>
|
||||
<Languages size={16} />
|
||||
<span className="text-sm font-medium">{language.toUpperCase()}</span>
|
||||
</button>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-8 h-8 bg-blue-600 rounded-full flex items-center justify-center">
|
||||
<span className="text-white text-sm font-medium">
|
||||
{userProfile.name.charAt(0)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="hidden md:block">
|
||||
<p className="text-sm font-medium text-gray-900">{userProfile.name}</p>
|
||||
<p className="text-xs text-gray-500">{userProfile.role}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="flex items-center space-x-2 px-3 py-2 rounded-lg bg-red-100 hover:bg-red-200 transition-colors text-red-700"
|
||||
>
|
||||
<LogOut size={16} />
|
||||
<span className="text-sm font-medium">{t('logout')}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="container mx-auto px-4 py-8">
|
||||
{/* KPI Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
<KPICard
|
||||
title="Active Users"
|
||||
value="12,543"
|
||||
change="+5.2%"
|
||||
trend="up"
|
||||
icon={<Users size={24} />}
|
||||
color="blue"
|
||||
/>
|
||||
<KPICard
|
||||
title="Avg Response Time"
|
||||
value="1.2s"
|
||||
change="-0.3s"
|
||||
trend="down"
|
||||
icon={<Clock size={24} />}
|
||||
color="green"
|
||||
/>
|
||||
<KPICard
|
||||
title="Satisfaction Rate"
|
||||
value={surveyStats ? `${Math.round(surveyStats.averageRating * 20)}%` : '94%'}
|
||||
change="+2.1%"
|
||||
trend="up"
|
||||
icon={<Star size={24} />}
|
||||
color="yellow"
|
||||
/>
|
||||
<KPICard
|
||||
title="Ticket Deflection"
|
||||
value="87%"
|
||||
change="+4.3%"
|
||||
trend="up"
|
||||
icon={<MessageSquare size={24} />}
|
||||
color="purple"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Charts Section */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
|
||||
{/* Response Time Chart */}
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
Average Response Time (This Week)
|
||||
</h3>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<LineChart data={responseTimeData}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Line type="monotone" dataKey="time" stroke="#3B82F6" strokeWidth={2} />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
{/* Satisfaction Trend */}
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
User Satisfaction Trend
|
||||
</h3>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<LineChart data={satisfactionData}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Line type="monotone" dataKey="satisfaction" stroke="#10B981" strokeWidth={2} />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Rating Distribution */}
|
||||
{surveyStats && ratingData.length > 0 && (
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-8">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
Rating Distribution ({surveyStats.totalSurveys} responses)
|
||||
</h3>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<BarChart data={ratingData}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="rating" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Bar dataKey="count" fill="#F59E0B" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
{/* System Health */}
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
System Health
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<HealthIndicator
|
||||
label="Chat Service"
|
||||
status="healthy"
|
||||
value="99.8% uptime"
|
||||
/>
|
||||
<HealthIndicator
|
||||
label="Database"
|
||||
status="healthy"
|
||||
value="Response time: 12ms"
|
||||
/>
|
||||
<HealthIndicator
|
||||
label="AI Assistant"
|
||||
status="healthy"
|
||||
value="Processing normally"
|
||||
/>
|
||||
<HealthIndicator
|
||||
label="Accessibility Scanner"
|
||||
status="warning"
|
||||
value="High load detected"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Activity */}
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
Recent Activity
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<ActivityItem
|
||||
icon={<MessageSquare size={16} />}
|
||||
title="New chat session started"
|
||||
time="2 minutes ago"
|
||||
type="chat"
|
||||
/>
|
||||
<ActivityItem
|
||||
icon={<Star size={16} />}
|
||||
title="Survey response: 5 stars"
|
||||
time="5 minutes ago"
|
||||
type="survey"
|
||||
/>
|
||||
<ActivityItem
|
||||
icon={<Shield size={16} />}
|
||||
title="Accessibility scan completed"
|
||||
time="12 minutes ago"
|
||||
type="accessibility"
|
||||
/>
|
||||
<ActivityItem
|
||||
icon={<Heart size={16} />}
|
||||
title="Mental health escalation"
|
||||
time="25 minutes ago"
|
||||
type="mental-health"
|
||||
/>
|
||||
<ActivityItem
|
||||
icon={<Users size={16} />}
|
||||
title="New user registration"
|
||||
time="1 hour ago"
|
||||
type="user"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Helper Components
|
||||
const KPICard: React.FC<{
|
||||
title: string
|
||||
value: string
|
||||
change: string
|
||||
trend: 'up' | 'down'
|
||||
icon: React.ReactNode
|
||||
color: 'blue' | 'green' | 'yellow' | 'purple'
|
||||
}> = ({ title, value, change, trend, icon, color }) => {
|
||||
const colorClasses = {
|
||||
blue: 'bg-blue-50 border-blue-200 text-blue-600',
|
||||
green: 'bg-green-50 border-green-200 text-green-600',
|
||||
yellow: 'bg-yellow-50 border-yellow-200 text-yellow-600',
|
||||
purple: 'bg-purple-50 border-purple-200 text-purple-600',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className={`p-3 rounded-lg ${colorClasses[color]}`}>
|
||||
{icon}
|
||||
</div>
|
||||
<div className={`flex items-center space-x-1 text-sm ${
|
||||
trend === 'up' ? 'text-green-600' : 'text-red-600'
|
||||
}`}>
|
||||
<TrendingUp size={16} className={trend === 'down' ? 'rotate-180' : ''} />
|
||||
<span>{change}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-2xl font-bold text-gray-900">{value}</p>
|
||||
<p className="text-sm text-gray-600">{title}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const HealthIndicator: React.FC<{
|
||||
label: string
|
||||
status: 'healthy' | 'warning' | 'error'
|
||||
value: string
|
||||
}> = ({ label, status, value }) => {
|
||||
const statusColors = {
|
||||
healthy: 'text-green-600 bg-green-50',
|
||||
warning: 'text-yellow-600 bg-yellow-50',
|
||||
error: 'text-red-600 bg-red-50',
|
||||
}
|
||||
|
||||
const StatusIcon = status === 'healthy' ? CheckCircle : AlertTriangle
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
||||
<div className="flex items-center space-x-3">
|
||||
<StatusIcon className={`${statusColors[status].split(' ')[0]}`} size={16} />
|
||||
<span className="font-medium text-gray-900">{label}</span>
|
||||
</div>
|
||||
<span className="text-sm text-gray-600">{value}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ActivityItem: React.FC<{
|
||||
icon: React.ReactNode
|
||||
title: string
|
||||
time: string
|
||||
type: string
|
||||
}> = ({ icon, title, time, type }) => {
|
||||
const typeColors = {
|
||||
chat: 'text-blue-600 bg-blue-50',
|
||||
survey: 'text-yellow-600 bg-yellow-50',
|
||||
accessibility: 'text-green-600 bg-green-50',
|
||||
'mental-health': 'text-pink-600 bg-pink-50',
|
||||
user: 'text-purple-600 bg-purple-50',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg">
|
||||
<div className={`p-2 rounded-lg ${typeColors[type as keyof typeof typeColors]}`}>
|
||||
{icon}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium text-gray-900">{title}</p>
|
||||
<p className="text-xs text-gray-500">{time}</p>
|
||||
</div>
|
||||
<Eye className="text-gray-400" size={16} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user