erp-platform/ui/src/components/intranet/Training/index.tsx
2025-10-19 01:33:28 +03:00

354 lines
16 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.

import React, { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import {
HiAcademicCap,
HiClock,
HiUsers,
HiMapPin,
HiXMark,
HiCheckBadge,
HiCalendar
} from 'react-icons/hi2'
import dayjs from 'dayjs'
import { mockTrainings, mockCertificates, Training } from '../../../mocks/mockIntranetData'
const TrainingModule: React.FC = () => {
const [selectedTraining, setSelectedTraining] = useState<Training | null>(null)
const [selectedTab, setSelectedTab] = useState<'all' | 'upcoming' | 'ongoing' | 'completed'>('all')
const [trainings, setTrainings] = useState<Training[]>(mockTrainings)
const [showEnrollSuccess, setShowEnrollSuccess] = useState(false)
const filteredTrainings = selectedTab === 'all'
? trainings
: trainings.filter(t => t.status === selectedTab)
const handleEnroll = (trainingId: string) => {
setTrainings(prev => prev.map(t =>
t.id === trainingId && t.enrolled < t.maxParticipants
? { ...t, enrolled: t.enrolled + 1 }
: t
))
// Seçili eğitimi de güncelle
if (selectedTraining?.id === trainingId && selectedTraining.enrolled < selectedTraining.maxParticipants) {
setSelectedTraining(prev => prev ? { ...prev, enrolled: prev.enrolled + 1 } : null)
}
setShowEnrollSuccess(true)
setTimeout(() => setShowEnrollSuccess(false), 3000)
}
const getCategoryColor = (category: string) => {
const colors: Record<string, string> = {
technical: 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300',
'soft-skills': 'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300',
management: 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300',
compliance: 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300',
other: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300'
}
return colors[category] || colors.other
}
const getStatusBadge = (status: string) => {
const badges: Record<string, { bg: string; text: string }> = {
upcoming: { bg: 'bg-blue-100 dark:bg-blue-900/30', text: 'text-blue-700 dark:text-blue-300' },
ongoing: { bg: 'bg-green-100 dark:bg-green-900/30', text: 'text-green-700 dark:text-green-300' },
completed: { bg: 'bg-gray-100 dark:bg-gray-700', text: 'text-gray-700 dark:text-gray-300' }
}
return badges[status] || badges.upcoming
}
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
<div className="max-w-7xl mx-auto space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
🎓 Eğitimler & Sertifikalar
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Kişisel gelişim ve eğitim kayıtları
</p>
</div>
</div>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
{[
{ label: 'Tümü', value: mockTrainings.length, tab: 'all' as const },
{ label: 'Yaklaşan', value: mockTrainings.filter(t => t.status === 'upcoming').length, tab: 'upcoming' as const },
{ label: 'Devam Eden', value: mockTrainings.filter(t => t.status === 'ongoing').length, tab: 'ongoing' as const },
{ label: 'Tamamlanan', value: mockTrainings.filter(t => t.status === 'completed').length, tab: 'completed' as const }
].map((stat, idx) => (
<motion.button
key={idx}
onClick={() => setSelectedTab(stat.tab)}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: idx * 0.1 }}
className={`p-4 rounded-lg border-2 transition-all ${
selectedTab === stat.tab
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
: 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:border-blue-300'
}`}
>
<div className="text-3xl font-bold text-gray-900 dark:text-white">{stat.value}</div>
<div className="text-sm text-gray-600 dark:text-gray-400 mt-1">{stat.label}</div>
</motion.button>
))}
</div>
{/* Eğitimler */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{filteredTrainings.map((training, idx) => {
const badge = getStatusBadge(training.status)
return (
<motion.div
key={training.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: idx * 0.1 }}
onClick={() => setSelectedTraining(training)}
className="bg-white dark:bg-gray-800 rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700 hover:shadow-lg transition-all cursor-pointer"
>
{training.thumbnail && (
<img
src={training.thumbnail}
alt={training.title}
className="w-full h-48 object-cover"
/>
)}
<div className="p-6">
<div className="flex items-start justify-between mb-3">
<span className={`px-3 py-1 rounded-full text-xs font-medium ${getCategoryColor(training.category)}`}>
{training.category}
</span>
<span className={`px-3 py-1 rounded-full text-xs font-medium ${badge.bg} ${badge.text}`}>
{training.status === 'upcoming' ? 'Yaklaşan' : training.status === 'ongoing' ? 'Devam Ediyor' : 'Tamamlandı'}
</span>
</div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
{training.title}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4 line-clamp-2">
{training.description}
</p>
<div className="space-y-2 text-sm text-gray-700 dark:text-gray-300">
<div className="flex items-center gap-2">
<HiAcademicCap className="w-4 h-4" />
<span>{training.instructor}</span>
</div>
<div className="flex items-center gap-2">
<HiCalendar className="w-4 h-4" />
<span>{dayjs(training.startDate).format('DD MMM YYYY')}</span>
</div>
<div className="flex items-center gap-2">
<HiClock className="w-4 h-4" />
<span>{training.duration} saat</span>
</div>
<div className="flex items-center gap-2">
<HiUsers className="w-4 h-4" />
<span>{training.enrolled} / {training.maxParticipants} katılımcı</span>
</div>
</div>
{training.enrolled < training.maxParticipants && training.status === 'upcoming' && (
<button
onClick={() => handleEnroll(training.id)}
className="mt-4 w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
Kayıt Ol
</button>
)}
</div>
</motion.div>
)
})}
</div>
{/* Sertifikalar */}
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 border border-gray-200 dark:border-gray-700">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
<HiCheckBadge className="w-6 h-6 text-blue-500" />
Sertifikalarım
</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{mockCertificates.map((cert, idx) => (
<motion.div
key={cert.id}
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: idx * 0.1 }}
className="p-4 rounded-lg border-2 border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900/20"
>
<div className="flex items-start justify-between mb-3">
<HiCheckBadge className="w-8 h-8 text-blue-600 dark:text-blue-400" />
{cert.score && (
<span className="px-2 py-1 bg-blue-600 text-white rounded text-xs font-bold">
{cert.score}
</span>
)}
</div>
<h3 className="font-semibold text-gray-900 dark:text-white mb-2">
{cert.trainingTitle}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
{dayjs(cert.issueDate).format('DD MMMM YYYY')}
</p>
{cert.expiryDate && (
<p className="text-xs text-gray-500 dark:text-gray-500 mt-1">
Geçerlilik: {dayjs(cert.expiryDate).format('DD/MM/YYYY')}
</p>
)}
<button className="mt-3 w-full px-3 py-1 text-sm text-blue-600 dark:text-blue-400 border border-blue-600 dark:border-blue-400 rounded hover:bg-blue-600 hover:text-white dark:hover:bg-blue-600 transition-colors">
Görüntüle
</button>
</motion.div>
))}
</div>
</div>
{/* Training Detail Modal */}
<AnimatePresence>
{selectedTraining && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={() => setSelectedTraining(null)}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 20 }}
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto"
>
<div className="sticky top-0 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-6 flex items-center justify-between z-10">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
Eğitim Detayları
</h2>
<button
onClick={() => setSelectedTraining(null)}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<HiXMark className="w-5 h-5 text-gray-500" />
</button>
</div>
<div className="p-6 space-y-6">
{selectedTraining.thumbnail && (
<img
src={selectedTraining.thumbnail}
alt={selectedTraining.title}
className="w-full h-64 object-cover rounded-lg"
/>
)}
<div>
<h3 className="text-2xl font-bold text-gray-900 dark:text-white mb-2">
{selectedTraining.title}
</h3>
<p className="text-gray-700 dark:text-gray-300">
{selectedTraining.description}
</p>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Eğitmen</p>
<p className="font-medium text-gray-900 dark:text-white">{selectedTraining.instructor}</p>
</div>
<div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Kategori</p>
<span className={`inline-block px-3 py-1 rounded-full text-sm ${getCategoryColor(selectedTraining.category)}`}>
{selectedTraining.category}
</span>
</div>
<div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Tarih</p>
<p className="font-medium text-gray-900 dark:text-white">
{dayjs(selectedTraining.startDate).format('DD MMM')} - {dayjs(selectedTraining.endDate).format('DD MMM YYYY')}
</p>
</div>
<div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Süre</p>
<p className="font-medium text-gray-900 dark:text-white">{selectedTraining.duration} saat</p>
</div>
<div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Tip</p>
<p className="font-medium text-gray-900 dark:text-white capitalize">{selectedTraining.type}</p>
</div>
{selectedTraining.location && (
<div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Konum</p>
<p className="font-medium text-gray-900 dark:text-white">{selectedTraining.location}</p>
</div>
)}
</div>
<div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-2">Doluluk Oranı</p>
<div className="flex items-center gap-3">
<div className="flex-1 bg-gray-200 dark:bg-gray-700 rounded-full h-3">
<div
className="bg-blue-600 h-3 rounded-full transition-all"
style={{ width: `${(selectedTraining.enrolled / selectedTraining.maxParticipants) * 100}%` }}
/>
</div>
<span className="text-sm font-medium text-gray-900 dark:text-white">
{selectedTraining.enrolled} / {selectedTraining.maxParticipants}
</span>
</div>
</div>
{selectedTraining.enrolled < selectedTraining.maxParticipants && selectedTraining.status === 'upcoming' && (
<button
onClick={() => handleEnroll(selectedTraining.id)}
className="w-full px-4 py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors font-medium"
>
Eğitime Kayıt Ol
</button>
)}
</div>
</motion.div>
</div>
</>
)}
</AnimatePresence>
{/* Success Notification */}
<AnimatePresence>
{showEnrollSuccess && (
<motion.div
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 50 }}
className="fixed bottom-6 right-6 bg-green-600 text-white px-6 py-4 rounded-lg shadow-xl flex items-center gap-3 z-50"
>
<HiCheckBadge className="w-6 h-6" />
<div>
<p className="font-semibold">Kayıt Başarılı!</p>
<p className="text-sm text-green-100">Eğitime kayıt oldunuz.</p>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
</div>
)
}
export default TrainingModule