الملفات
curated-learning-video-plat…/src/Dashboard.tsx
2026-06-17 00:21:29 +00:00

298 أسطر
16 KiB
TypeScript
خام اللوم التاريخ

هذا الملف يحتوي على أحرف Unicode غامضة

هذا الملف يحتوي على أحرف Unicode قد تُخلط مع أحرف أخرى. إذا كنت تعتقد أن هذا مقصود، يمكنك تجاهل هذا التحذير بأمان. استخدم زر الهروب للكشف عنها.

import { useApp } from '../store';
import { channels, playlists, videos, standaloneVideos, formatNumber, getChannel, getPlaylistVideos } from '../data';
import { Play, Clock, CheckCircle, Trophy, Flame, Star, BookOpen } from 'lucide-react';
export default function Dashboard() {
const { progress, setView, followedChannelIds, markVideoWatched } = useApp();
const followedChannels = channels.filter(c => followedChannelIds.includes(c.id));
const followedPlaylists = playlists.filter(p => followedChannelIds.includes(p.channelId));
// Get playlists with progress
const playlistsInProgress = followedPlaylists
.map(pl => ({
playlist: pl,
channel: getChannel(pl.channelId),
plVideos: getPlaylistVideos(pl.id),
currentIndex: progress.playlistProgress[pl.id] || 0,
completedCount: getPlaylistVideos(pl.id).filter(v => progress.watchedVideos.includes(v.id)).length,
}))
.filter(p => p.plVideos.length > 0)
.sort((a, b) => {
// Prioritize playlists in progress
const aProgress = a.completedCount / a.plVideos.length;
const bProgress = b.completedCount / b.plVideos.length;
if (aProgress > 0 && aProgress < 1 && bProgress > 0 && bProgress < 1) return bProgress - aProgress;
if (aProgress > 0 && aProgress < 1) return -1;
if (bProgress > 0 && bProgress < 1) return 1;
return 0;
});
// Get recent videos from followed channels
const recentVideos = standaloneVideos
.filter(v => followedChannelIds.includes(v.channelId))
.concat(
followedPlaylists.flatMap(pl =>
(videos[pl.id] || []).slice(-2)
)
)
.sort(() => Math.random() - 0.5)
.slice(0, 8);
const totalVideosAvailable = followedPlaylists.reduce((sum, pl) => sum + (videos[pl.id]?.length || 0), 0) + standaloneVideos.filter(v => followedChannelIds.includes(v.channelId)).length;
const overallProgress = totalVideosAvailable > 0 ? Math.round((progress.watchedVideos.length / totalVideosAvailable) * 100) : 0;
return (
<div className="h-screen overflow-y-auto custom-scrollbar p-6 space-y-6 animate-slide-up">
{/* Hero Stats */}
<div className="bg-gradient-to-l from-orange-500 via-rose-500 to-purple-600 rounded-2xl p-6 text-white relative overflow-hidden">
<div className="absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48Y2lyY2xlIGN4PSIyMCIgY3k9IjIwIiByPSIxIiBmaWxsPSJyZ2JhKDI1NSwyNTUsMjU1LDAuMSkiLz48L3N2Zz4=')] opacity-50" />
<div className="relative">
<div className="flex items-start justify-between mb-4">
<div>
<h2 className="text-2xl font-black mb-1">مرحبًا بك في مساري! 🎯</h2>
<p className="text-white/80 text-sm">واصل رحلة التعلم من حيث توقفت</p>
</div>
<div className="flex items-center gap-2 bg-white/20 rounded-xl px-4 py-2">
<Flame className="w-5 h-5 text-yellow-300" />
<div className="text-right">
<div className="text-xl font-black">{progress.streak}</div>
<div className="text-[10px] text-white/70">أيام متتالية</div>
</div>
</div>
</div>
<div className="grid grid-cols-4 gap-3">
<div className="bg-white/15 backdrop-blur-sm rounded-xl p-3 text-center">
<div className="text-2xl font-black">{progress.watchedVideos.length}</div>
<div className="text-[10px] text-white/70">فيديو مُشاهد</div>
</div>
<div className="bg-white/15 backdrop-blur-sm rounded-xl p-3 text-center">
<div className="text-2xl font-black">{progress.completedPlaylists.length}</div>
<div className="text-[10px] text-white/70">قائمة مكتملة</div>
</div>
<div className="bg-white/15 backdrop-blur-sm rounded-xl p-3 text-center">
<div className="text-2xl font-black">{followedChannels.length}</div>
<div className="text-[10px] text-white/70">قناة متابعة</div>
</div>
<div className="bg-white/15 backdrop-blur-sm rounded-xl p-3 text-center">
<div className="text-2xl font-black">{overallProgress}%</div>
<div className="text-[10px] text-white/70">نسبة الإنجاز</div>
</div>
</div>
</div>
</div>
{/* Playlists Progress */}
<section>
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-bold text-gray-800 flex items-center gap-2">
<BookOpen className="w-5 h-5 text-purple-500" />
قوائم التشغيل الخاصة بك
</h3>
<span className="text-xs text-gray-400">{playlistsInProgress.length} قائمة</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{playlistsInProgress.slice(0, 9).map(({ playlist, channel, plVideos, completedCount }) => {
const percentage = plVideos.length > 0 ? Math.round((completedCount / plVideos.length) * 100) : 0;
const isComplete = progress.completedPlaylists.includes(playlist.id);
const nextVideoIndex = completedCount;
const nextVideo = plVideos[nextVideoIndex];
return (
<button
key={playlist.id}
onClick={() => setView({ type: 'playlist', playlistId: playlist.id })}
className="group bg-white rounded-2xl shadow-sm hover:shadow-xl transition-all duration-300 overflow-hidden text-right border border-gray-100 hover:border-orange-200"
>
<div className={`h-2 bg-gradient-to-l ${playlist.gradient}`} />
<div className="p-4">
<div className="flex items-start gap-3 mb-3">
<div className={`w-12 h-12 rounded-xl bg-gradient-to-br ${playlist.gradient} flex items-center justify-center text-2xl shrink-0 shadow-lg`}>
{playlist.emoji}
</div>
<div className="flex-1 min-w-0">
<h4 className="font-bold text-gray-800 text-sm truncate group-hover:text-orange-600 transition-colors">
{playlist.title}
</h4>
<p className="text-[11px] text-gray-400 mt-0.5">{channel?.name}</p>
</div>
{isComplete && (
<div className="bg-emerald-100 rounded-lg px-2 py-1">
<CheckCircle className="w-4 h-4 text-emerald-500" />
</div>
)}
</div>
{/* Progress */}
<div className="space-y-2">
<div className="flex justify-between text-[11px]">
<span className="text-gray-400">{completedCount} من {plVideos.length} فيديو</span>
<span className={`font-bold ${percentage === 100 ? 'text-emerald-500' : percentage > 0 ? 'text-orange-500' : 'text-gray-400'}`}>
{percentage}%
</span>
</div>
<div className="h-2 bg-gray-100 rounded-full overflow-hidden">
<div
className={`h-full rounded-full transition-all duration-1000 ${
percentage === 100 ? 'bg-gradient-to-l from-emerald-400 to-green-500' : 'bg-gradient-to-l from-orange-400 to-rose-500'
}`}
style={{ width: `${percentage}%` }}
/>
</div>
{nextVideo && !isComplete && (
<div className="flex items-center gap-2 bg-orange-50 rounded-lg p-2 mt-2">
<Play className="w-3.5 h-3.5 text-orange-500 shrink-0" />
<span className="text-[11px] text-orange-700 truncate">التالي: {nextVideo.title}</span>
</div>
)}
</div>
</div>
</button>
);
})}
</div>
</section>
{/* Channel Grid */}
<section>
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-bold text-gray-800 flex items-center gap-2">
<Star className="w-5 h-5 text-amber-500" />
قنواتك المتابعة
</h3>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
{followedChannels.map(ch => {
const chPlaylists = playlists.filter(p => p.channelId === ch.id);
const chVideosWatched = [...standaloneVideos, ...Object.values(videos).flat()].filter(
v => v.channelId === ch.id && progress.watchedVideos.includes(v.id)
).length;
return (
<button
key={ch.id}
onClick={() => setView({ type: 'channel', channelId: ch.id })}
className="group bg-white rounded-2xl p-4 shadow-sm hover:shadow-xl transition-all duration-300 text-right border border-gray-100 hover:border-purple-200"
>
<div className={`w-14 h-14 rounded-2xl bg-gradient-to-br ${ch.gradient} flex items-center justify-center text-3xl mx-auto mb-3 shadow-lg group-hover:scale-110 transition-transform`}>
{ch.emoji}
</div>
<h4 className="font-bold text-gray-800 text-sm truncate mb-1">{ch.name}</h4>
<p className="text-[10px] text-gray-400 mb-2">{formatNumber(ch.subscriberCount)} مشترك</p>
<div className="flex items-center justify-center gap-2 text-[10px]">
<span className="bg-blue-50 text-blue-600 px-2 py-0.5 rounded-full">{chPlaylists.length} دورة</span>
<span className="bg-emerald-50 text-emerald-600 px-2 py-0.5 rounded-full">{chVideosWatched} مُشاهد</span>
</div>
</button>
);
})}
<button
onClick={() => setView({ type: 'browser' })}
className="bg-gradient-to-br from-purple-50 to-pink-50 rounded-2xl p-4 shadow-sm hover:shadow-xl transition-all duration-300 text-center border-2 border-dashed border-purple-200 hover:border-purple-400 group"
>
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-purple-400 to-pink-400 flex items-center justify-center text-3xl mx-auto mb-3 shadow-lg group-hover:scale-110 transition-transform">
</div>
<h4 className="font-bold text-purple-700 text-sm">استكشف قنوات جديدة</h4>
<p className="text-[10px] text-purple-400 mt-1">أضف قنوات لبدء التعلم</p>
</button>
</div>
</section>
{/* Recent Videos */}
{recentVideos.length > 0 && (
<section>
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-bold text-gray-800 flex items-center gap-2">
<Clock className="w-5 h-5 text-blue-500" />
جديد من قنواتك
</h3>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{recentVideos.map(video => {
const channel = getChannel(video.channelId);
const isWatched = progress.watchedVideos.includes(video.id);
return (
<button
key={video.id}
onClick={() => {
markVideoWatched(video.id);
setView({ type: 'video', videoId: video.id, playlistId: video.playlistId || undefined });
}}
className="group bg-white rounded-2xl shadow-sm hover:shadow-xl transition-all duration-300 overflow-hidden text-right border border-gray-100 hover:border-blue-200 video-card"
>
<div className="relative">
<div className={`h-32 bg-gradient-to-br ${channel?.gradient || 'from-gray-400 to-gray-500'} flex items-center justify-center`}>
<span className="text-5xl opacity-50 group-hover:opacity-80 transition-opacity">{channel?.emoji || '🎬'}</span>
<div className="video-overlay absolute inset-0 bg-black/30 flex items-center justify-center opacity-0 transition-opacity">
<Play className="w-10 h-10 text-white" />
</div>
</div>
<span className="absolute bottom-2 left-2 bg-black/70 text-white text-[10px] px-1.5 py-0.5 rounded-md">
{video.duration}
</span>
{isWatched && (
<div className="absolute top-2 right-2">
<CheckCircle className="w-5 h-5 text-emerald-400 drop-shadow-lg" />
</div>
)}
</div>
<div className="p-3">
<h4 className="text-xs font-bold text-gray-800 line-clamp-2 mb-1.5 group-hover:text-orange-600 transition-colors">{video.title}</h4>
<div className="flex items-center gap-1.5">
<span className={`w-5 h-5 rounded-md bg-gradient-to-br ${channel?.gradient || 'from-gray-400 to-gray-500'} flex items-center justify-center text-[10px]`}>
{channel?.emoji}
</span>
<span className="text-[10px] text-gray-400">{channel?.name}</span>
</div>
</div>
</button>
);
})}
</div>
</section>
)}
{/* Achievements */}
<section className="pb-6">
<h3 className="text-lg font-bold text-gray-800 flex items-center gap-2 mb-4">
<Trophy className="w-5 h-5 text-amber-500" />
الإنجازات
</h3>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{[
{ emoji: '🌟', title: 'أول فيديو', desc: 'شاهد أول فيديو', done: progress.watchedVideos.length >= 1 },
{ emoji: '📚', title: 'طالب مجتهد', desc: 'شاهد 10 فيديوهات', done: progress.watchedVideos.length >= 10 },
{ emoji: '🔥', title: 'متسلسل', desc: '3 أيام متتالية', done: progress.streak >= 3 },
{ emoji: '🏆', title: 'منتهٍ', desc: 'أكمل أول قائمة', done: progress.completedPlaylists.length >= 1 },
{ emoji: '💎', title: 'محترف', desc: 'أكمل 3 قوائم', done: progress.completedPlaylists.length >= 3 },
{ emoji: '🎯', title: 'هادف', desc: 'شاهد 50 فيديو', done: progress.watchedVideos.length >= 50 },
{ emoji: '🚀', title: 'صاروخي', desc: '7 أيام متتالية', done: progress.streak >= 7 },
{ emoji: '👑', title: 'ملك التعلم', desc: 'أكمل 10 قوائم', done: progress.completedPlaylists.length >= 10 },
].map((ach, i) => (
<div
key={i}
className={`rounded-2xl p-4 text-center transition-all ${
ach.done
? 'bg-gradient-to-br from-amber-50 to-orange-50 border-2 border-amber-200 shadow-lg'
: 'bg-gray-50 border-2 border-gray-100 opacity-60'
}`}
>
<div className="text-3xl mb-2">{ach.emoji}</div>
<div className="text-sm font-bold text-gray-800">{ach.title}</div>
<div className="text-[10px] text-gray-400 mt-0.5">{ach.desc}</div>
{ach.done && <div className="text-[10px] text-emerald-500 font-bold mt-1"> مُحقق</div>}
</div>
))}
</div>
</section>
</div>
);
}