import React from 'react'; import { useNavigate } from 'react-router-dom'; import { Calendar, AlertTriangle, Download, Eye, Clock, Plus, HardDrive, Image, Archive, Heart } from 'lucide-react'; import { differenceInDays, parseISO } from 'date-fns'; import { useTranslation } from 'react-i18next'; import { useLocalizedDate } from '../../hooks/useLocalizedDate'; import { Button, Card, Loading } from '../../components/common'; import { useQuery } from '@tanstack/react-query'; import { eventsService } from '../../services/events.service'; import { adminService } from '../../services/admin.service'; interface StatCard { title: string; value: string | number; change?: string; icon: React.ComponentType<{ className?: string }>; color: string; } export const AdminDashboard: React.FC = () => { const { t } = useTranslation(); const navigate = useNavigate(); const { format, formatDistanceToNow } = useLocalizedDate(); // Fetch dashboard statistics const { data: dashboardStats, isLoading: statsLoading } = useQuery({ queryKey: ['admin-dashboard-stats'], queryFn: () => adminService.getDashboardStats(), }); // Fetch recent activity const { data: recentActivity } = useQuery({ queryKey: ['admin-recent-activity'], queryFn: () => adminService.getRecentActivity(10), }); // Fetch system health const { data: systemHealth } = useQuery({ queryKey: ['admin-system-health'], queryFn: () => adminService.getSystemHealth(), refetchInterval: 30000, // Refresh every 30 seconds }); // Fetch events data for expiring events const { data: eventsData, isLoading: eventsLoading } = useQuery({ queryKey: ['admin-events-summary'], queryFn: () => eventsService.getEvents(1, 100), }); const isLoading = statsLoading || eventsLoading; if (isLoading) { return (
); } // Calculate expiring events const activeEvents = eventsData?.events.filter(e => e.is_active && !e.is_archived) || []; const expiringEvents = activeEvents.filter(e => { const days = differenceInDays(parseISO(e.expires_at), new Date()); return days <= 7 && days > 0; }); // Format numbers for display const formatNumber = (num: number): string => { if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`; if (num >= 1000) return `${(num / 1000).toFixed(1)}K`; return num.toString(); }; // Build statistics cards - always show 8 cards in 2x4 grid const stats: StatCard[] = [ { title: t('admin.activeEvents'), value: dashboardStats?.activeEvents || 0, icon: Calendar, color: 'text-green-600', }, { title: t('admin.expiringSoon'), value: dashboardStats?.expiringEvents || 0, change: t('admin.next7Days'), icon: AlertTriangle, color: 'text-orange-600', }, { title: t('admin.totalPhotos'), value: formatNumber(dashboardStats?.totalPhotos || 0), icon: Image, color: 'text-blue-600', }, { title: t('admin.storageUsed'), value: adminService.formatBytes(dashboardStats?.storageUsed || 0), icon: HardDrive, color: 'text-purple-600', }, { title: t('admin.totalViews'), value: formatNumber(dashboardStats?.totalViews || 0), change: dashboardStats?.viewsTrend ? t('admin.percentFromLastWeek', { percent: `${dashboardStats.viewsTrend > 0 ? '+' : ''}${dashboardStats.viewsTrend}` }) : undefined, icon: Eye, color: 'text-indigo-600', }, { title: t('admin.downloads'), value: formatNumber(dashboardStats?.totalDownloads || 0), change: dashboardStats?.downloadsTrend ? t('admin.percentFromLastWeek', { percent: `${dashboardStats.downloadsTrend > 0 ? '+' : ''}${dashboardStats.downloadsTrend}` }) : undefined, icon: Download, color: 'text-pink-600', }, { title: t('admin.archivedEvents'), value: dashboardStats?.archivedEvents || 0, icon: Archive, color: 'text-gray-600', }, { title: t('admin.systemHealth'), value: systemHealth ? t(`admin.health.${systemHealth.overall}`) : t('admin.health.checking'), icon: Heart, color: systemHealth?.overall === 'healthy' ? 'text-green-600' : systemHealth?.overall === 'warning' ? 'text-yellow-600' : 'text-red-600', }, ]; return (
{/* Page Header */}

{t('navigation.dashboard')}

{t('admin.dashboardSubtitle')}

{/* Statistics Grid */}
{stats.map((stat) => (

{stat.title}

{stat.value}

{stat.change && (

{stat.change}

)}
))}
{/* Main Content Grid */}
{/* Expiring Events */}

{t('admin.eventsExpiringSoon')}

{expiringEvents.length === 0 ? (

{t('admin.noEventsExpiring')}

) : (
{expiringEvents.slice(0, 5).map((event) => { const daysLeft = differenceInDays(parseISO(event.expires_at), new Date()); return (
navigate(`/admin/events/${event.id}`)} >

{event.event_name}

{format(parseISO(event.event_date), 'PP')}

{t('admin.daysLeft', { count: daysLeft })}

{t('gallery.expires')} {format(parseISO(event.expires_at), 'PP')}

); })}
)} {expiringEvents.length > 5 && ( )}
{/* Recent Activity */}

{t('admin.recentActivity')}

{!recentActivity || recentActivity.length === 0 ? (

{t('admin.noRecentActivity')}

) : ( recentActivity.slice(0, 5).map((activity) => { // Get color based on activity type const getActivityColor = (type: string) => { const colors: Record = { 'event_created': 'bg-green-500', 'photos_uploaded': 'bg-blue-500', 'event_archived': 'bg-purple-500', 'archive_restored': 'bg-indigo-500', 'archive_deleted': 'bg-red-500', 'bulk_download': 'bg-blue-500', 'email_config_updated': 'bg-yellow-500', 'branding_updated': 'bg-pink-500', 'theme_updated': 'bg-purple-500', 'gallery_password_entry': 'bg-gray-500', }; return colors[type] || 'bg-gray-500'; }; // Format activity message with translations const getActivityMessage = (): string => { const translationKey = `admin.activities.${activity.type}`; const params: Record = { eventName: activity.eventName || t('common.unknown'), count: activity.metadata?.count || 0, template: activity.metadata?.template_key || '', categoryName: activity.metadata?.category_name || '' }; // Translate; if key missing i18n returns the key string itself const translated = t(translationKey, params) as string; if (!translated || translated === translationKey) { // Fallback: format a readable English message return adminService.formatActivityMessage(activity); } return translated; }; return (

{getActivityMessage()}

{activity.actorName}

{formatDistanceToNow(parseISO(activity.createdAt), { addSuffix: true })}

); }) )}
); }; AdminDashboard.displayName = 'AdminDashboard';