- Database: Add Loan, LoanInstallment, PurchaseRequest, LeaveEntitlement, EmployeeContract models - Database: Extend Attendance with ZK Tico fields (sourceDeviceId, externalId, rawData) - Database: Add Employee.attendancePin for device mapping - Backend: HR admin - Loans, Purchase Requests, Leave entitlements, Employee contracts CRUD - Backend: Leave reject, bulk attendance sync (ZK Tico ready) - Backend: Employee Portal API - scoped by employeeId (loans, leaves, purchase-requests, attendance, salaries) - Frontend: Employee Portal - dashboard, loans, leave, purchase-requests, attendance, salaries - Frontend: HR Admin - new tabs for Leaves, Loans, Purchase Requests, Contracts (approve/reject) - Dashboard: Add My Portal link - No destructive schema changes; additive migrations only Made-with: Cursor
310 lines
11 KiB
TypeScript
310 lines
11 KiB
TypeScript
'use client'
|
||
|
||
import { useState, useEffect } from 'react'
|
||
import ProtectedRoute from '@/components/ProtectedRoute'
|
||
import { useAuth } from '@/contexts/AuthContext'
|
||
import { useLanguage } from '@/contexts/LanguageContext'
|
||
import LanguageSwitcher from '@/components/LanguageSwitcher'
|
||
import Link from 'next/link'
|
||
import {
|
||
Users,
|
||
User,
|
||
TrendingUp,
|
||
Package,
|
||
CheckSquare,
|
||
UserCheck,
|
||
Megaphone,
|
||
LogOut,
|
||
Building2,
|
||
Settings,
|
||
Bell,
|
||
Shield
|
||
} from 'lucide-react'
|
||
import { dashboardAPI } from '@/lib/api'
|
||
|
||
function DashboardContent() {
|
||
const { user, logout, hasPermission } = useAuth()
|
||
const { t, language, dir } = useLanguage()
|
||
const [stats, setStats] = useState({ contacts: 0, activeTasks: 0, notifications: 0 })
|
||
|
||
useEffect(() => {
|
||
dashboardAPI.getStats()
|
||
.then((res) => {
|
||
if (res.data?.data) setStats(res.data.data)
|
||
})
|
||
.catch(() => {})
|
||
}, [])
|
||
|
||
const allModules = [
|
||
{
|
||
id: 'contacts',
|
||
name: 'إدارة جهات الاتصال',
|
||
nameEn: 'Contact Management',
|
||
icon: Users,
|
||
color: 'bg-blue-500',
|
||
href: '/contacts',
|
||
description: 'إدارة العملاء والموردين وجهات الاتصال',
|
||
permission: 'contacts'
|
||
},
|
||
{
|
||
id: 'crm',
|
||
name: 'إدارة علاقات العملاء',
|
||
nameEn: 'CRM',
|
||
icon: TrendingUp,
|
||
color: 'bg-green-500',
|
||
href: '/crm',
|
||
description: 'الفرص التجارية والعروض والصفقات',
|
||
permission: 'crm'
|
||
},
|
||
{
|
||
id: 'inventory',
|
||
name: 'المخزون والأصول',
|
||
nameEn: 'Inventory & Assets',
|
||
icon: Package,
|
||
color: 'bg-purple-500',
|
||
href: '/inventory',
|
||
description: 'المنتجات والمخازن والأصول الثابتة',
|
||
permission: 'inventory'
|
||
},
|
||
{
|
||
id: 'projects',
|
||
name: 'المهام والمشاريع',
|
||
nameEn: 'Tasks & Projects',
|
||
icon: CheckSquare,
|
||
color: 'bg-orange-500',
|
||
href: '/projects',
|
||
description: 'إدارة المشاريع والمهام والموارد',
|
||
permission: 'projects'
|
||
},
|
||
{
|
||
id: 'hr',
|
||
name: 'الموارد البشرية',
|
||
nameEn: 'Human Resources',
|
||
icon: UserCheck,
|
||
color: 'bg-teal-500',
|
||
href: '/hr',
|
||
description: 'الموظفين والإجازات والرواتب',
|
||
permission: 'hr'
|
||
},
|
||
{
|
||
id: 'portal',
|
||
name: 'البوابة الذاتية',
|
||
nameEn: 'My Portal',
|
||
icon: User,
|
||
color: 'bg-cyan-500',
|
||
href: '/portal',
|
||
description: 'قروضي، إجازاتي، طلبات الشراء والرواتب',
|
||
permission: 'hr'
|
||
},
|
||
{
|
||
id: 'marketing',
|
||
name: 'التسويق',
|
||
nameEn: 'Marketing',
|
||
icon: Megaphone,
|
||
color: 'bg-pink-500',
|
||
href: '/marketing',
|
||
description: 'الحملات التسويقية والعملاء المحتملين',
|
||
permission: 'marketing'
|
||
},
|
||
{
|
||
id: 'admin',
|
||
name: 'لوحة الإدارة',
|
||
nameEn: 'Admin Panel',
|
||
icon: Shield,
|
||
color: 'bg-red-500',
|
||
href: '/admin',
|
||
description: 'إدارة المستخدمين والأدوار وسجل العمليات',
|
||
permission: 'admin'
|
||
}
|
||
]
|
||
|
||
// Filter modules based on user permissions
|
||
const availableModules = allModules.filter(module =>
|
||
hasPermission(module.permission, 'view')
|
||
)
|
||
|
||
return (
|
||
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100">
|
||
{/* Header */}
|
||
<header className="bg-white shadow-sm border-b sticky top-0 z-50">
|
||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center gap-3">
|
||
<div className="bg-primary-600 p-2 rounded-lg">
|
||
<Building2 className="h-8 w-8 text-white" />
|
||
</div>
|
||
<div>
|
||
<h1 className="text-2xl font-bold text-gray-900">Z.CRM</h1>
|
||
<p className="text-sm text-gray-600">نظام إدارة علاقات العملاء</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-4">
|
||
{/* Language Switcher */}
|
||
<LanguageSwitcher />
|
||
|
||
{/* User Info */}
|
||
<div className="text-right">
|
||
<p className="text-sm font-semibold text-gray-900">{user?.username}</p>
|
||
<p className="text-xs text-gray-600">{user?.role?.name || 'مستخدم'}</p>
|
||
</div>
|
||
|
||
{/* Admin Panel Link - Only for admins */}
|
||
{hasPermission('admin', 'view') && (
|
||
<Link
|
||
href="/admin"
|
||
className="p-2 hover:bg-red-50 rounded-lg transition-colors relative group"
|
||
title="لوحة تحكم المدير"
|
||
>
|
||
<Shield className="h-5 w-5 text-red-600" />
|
||
<span className="absolute -bottom-8 right-0 bg-red-600 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap">
|
||
لوحة الإدارة
|
||
</span>
|
||
</Link>
|
||
)}
|
||
|
||
{/* Notifications */}
|
||
<button className="p-2 hover:bg-gray-100 rounded-lg transition-colors relative">
|
||
<Bell className="h-5 w-5 text-gray-600" />
|
||
{stats.notifications > 0 && (
|
||
<span className="absolute top-1 right-1 h-2 w-2 bg-red-500 rounded-full"></span>
|
||
)}
|
||
</button>
|
||
|
||
{/* Settings */}
|
||
<button className="p-2 hover:bg-gray-100 rounded-lg transition-colors">
|
||
<Settings className="h-5 w-5 text-gray-600" />
|
||
</button>
|
||
|
||
{/* Logout */}
|
||
<button
|
||
onClick={logout}
|
||
className="flex items-center gap-2 px-4 py-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
|
||
>
|
||
<LogOut className="h-5 w-5" />
|
||
<span className="font-medium">خروج</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
{/* Main Content */}
|
||
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||
{/* Welcome Section */}
|
||
<div className="bg-gradient-to-l from-primary-600 to-primary-700 rounded-2xl shadow-lg p-8 mb-8 text-white">
|
||
<h2 className="text-3xl font-bold mb-2">مرحباً، {user?.username}! 👋</h2>
|
||
<p className="text-primary-100 text-lg">
|
||
{user?.role?.name} - {availableModules.length} وحدة متاحة
|
||
</p>
|
||
</div>
|
||
|
||
{/* Stats Cards */}
|
||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
||
<div className="bg-white rounded-xl shadow-md p-6 border border-gray-100">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm text-gray-600">الوحدات المتاحة</p>
|
||
<p className="text-3xl font-bold text-gray-900 mt-1">{availableModules.length}</p>
|
||
</div>
|
||
<div className="bg-blue-100 p-3 rounded-lg">
|
||
<Package className="h-8 w-8 text-blue-600" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-white rounded-xl shadow-md p-6 border border-gray-100">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm text-gray-600">المهام النشطة</p>
|
||
<p className="text-3xl font-bold text-gray-900 mt-1">{stats.activeTasks}</p>
|
||
</div>
|
||
<div className="bg-green-100 p-3 rounded-lg">
|
||
<CheckSquare className="h-8 w-8 text-green-600" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-white rounded-xl shadow-md p-6 border border-gray-100">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm text-gray-600">الإشعارات</p>
|
||
<p className="text-3xl font-bold text-gray-900 mt-1">{stats.notifications}</p>
|
||
</div>
|
||
<div className="bg-orange-100 p-3 rounded-lg">
|
||
<Bell className="h-8 w-8 text-orange-600" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-white rounded-xl shadow-md p-6 border border-gray-100">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm text-gray-600">جهات الاتصال</p>
|
||
<p className="text-3xl font-bold text-gray-900 mt-1">{stats.contacts}</p>
|
||
</div>
|
||
<div className="bg-purple-100 p-3 rounded-lg">
|
||
<Users className="h-8 w-8 text-purple-600" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Available Modules */}
|
||
<div className="mb-8">
|
||
<h3 className="text-2xl font-bold text-gray-900 mb-6">الوحدات المتاحة</h3>
|
||
|
||
{availableModules.length > 0 ? (
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||
{availableModules.map((module) => {
|
||
const Icon = module.icon
|
||
return (
|
||
<Link
|
||
key={module.id}
|
||
href={module.href}
|
||
className="bg-white rounded-xl shadow-md hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1 p-6 border border-gray-200 group"
|
||
>
|
||
<div className="flex items-start gap-4">
|
||
<div className={`${module.color} p-3 rounded-lg group-hover:scale-110 transition-transform`}>
|
||
<Icon className="h-6 w-6 text-white" />
|
||
</div>
|
||
<div className="flex-1">
|
||
<h4 className="text-lg font-bold text-gray-900 mb-1">
|
||
{module.name}
|
||
</h4>
|
||
<p className="text-sm text-gray-500 mb-2">{module.nameEn}</p>
|
||
<p className="text-sm text-gray-600">{module.description}</p>
|
||
</div>
|
||
</div>
|
||
</Link>
|
||
)
|
||
})}
|
||
</div>
|
||
) : (
|
||
<div className="bg-yellow-50 border border-yellow-200 rounded-xl p-8 text-center">
|
||
<p className="text-yellow-800 text-lg">
|
||
لا توجد وحدات متاحة لحسابك. الرجاء التواصل مع المسؤول لمنح الصلاحيات المناسبة.
|
||
</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Recent Activity */}
|
||
<div className="bg-white rounded-xl shadow-md p-6 border border-gray-100">
|
||
<h3 className="text-xl font-bold text-gray-900 mb-4">النشاط الأخير</h3>
|
||
<p className="text-gray-500 text-center py-6">لا يوجد نشاط حديث</p>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default function DashboardPage() {
|
||
return (
|
||
<ProtectedRoute>
|
||
<DashboardContent />
|
||
</ProtectedRoute>
|
||
)
|
||
}
|
||
|