import React, { useEffect, useState } from 'react'; import { __ } from '@wordpress/i18n'; import { useNavigate } from 'react-router-dom'; import { applyFilters } from '@wordpress/hooks'; import { FileImage, Download, Search, Maximize, Copy, ToggleLeft, Zap, FileType, } from 'lucide-react'; import { TotalImagesIcon, TotalThumbnailsIcon, UnoptimizedIcon, SpaceSavedIcon, WebpFileIcon, AvifFileIcon, ThumbnailDisabledIcon, LazyLoadingIcon, LargeImagesIcon, UnusedImagesIconPro, CompressedImagesIcon, DuplicateImagesIcon, } from '../components/icons'; import Header from '../components/layout/Header'; import PluginPage from '../components/layout/PluginPage'; import { getDashboardCountsStats, getDashboardOptimizationStats, getDashboardAnalysisStats, type DashboardCountsStats, type DashboardOptimizationStats, type DashboardAnalysisStats, type DashboardStats, } from '../api'; import LockedFeatureCard from '../components/dashboard/LockedFeatureCard'; import InfoCard from '../components/dashboard/InfoCard'; import HealthScoreCard from '../components/dashboard/HealthScoreCard'; import PromotionalNotice from '../components/dashboard/PromotionalNotice'; function formatBytes(bytes: number): string { if (bytes === 0) return '0 B'; const units = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(1024)); const value = bytes / Math.pow(1024, i); return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[i]}`; } function InfoCardSkeleton({ hasArrow = false }: { hasArrow?: boolean }) { return (
{hasArrow &&
}
); } function HealthScoreCardSkeleton() { return (
{[1, 2, 3].map(i => (
))}
); } function DashboardSkeleton() { return ( <>
{[...Array(4)].map((_, i) => )}
{[...Array(8)].map((_, i) => )}
); } export default function Dashboard() { const [countsStats, setCountsStats] = useState(null); const [optimizationStats, setOptimizationStats] = useState(null); const [analysisStats, setAnalysisStats] = useState(null); const [countsLoading, setCountsLoading] = useState(true); const [optimizationLoading, setOptimizationLoading] = useState(true); const [analysisLoading, setAnalysisLoading] = useState(true); const navigate = useNavigate(); useEffect(() => { (async () => { try { const res: any = await getDashboardCountsStats(); if (res?.data) setCountsStats(res.data); } finally { setCountsLoading(false); } try { const res: any = await getDashboardOptimizationStats(); if (res?.data) setOptimizationStats(res.data); } finally { setOptimizationLoading(false); } try { const res: any = await getDashboardAnalysisStats(); if (res?.data) setAnalysisStats(res.data); } finally { setAnalysisLoading(false); } })(); }, []); if (countsLoading) { return ; } if (!countsStats) { return ( <>
); } const mergedStats: DashboardStats = { ...countsStats, total_thumbnails: optimizationStats?.total_thumbnails ?? 0, unoptimized_images: optimizationStats?.unoptimized_images ?? 0, compressed: optimizationStats?.compressed ?? 0, not_compressed: optimizationStats?.not_compressed ?? 0, large_images: analysisStats?.large_images ?? 0, duplicate_images: analysisStats?.duplicate_images ?? 0, unused_images: analysisStats?.unused_images ?? 0, health_score: analysisStats?.health_score ?? 0, health_issue: analysisStats?.health_issue ?? '', quick_facts: analysisStats?.quick_facts ?? [], }; return ( <>
{/* Top row: Health Score + stat cards */}
{/* ANALYSIS — needs health_score + quick_facts */} {(analysisLoading || !analysisStats) ? : }
{/* COUNTS */} } title={__('Total Images', 'image-sizes')} value={countsStats.total_images.toLocaleString()} sub={__('In media library', 'image-sizes')} /> {/* OPTIMIZATION */} {optimizationLoading ? : ( } title={__('Total Thumbnails', 'image-sizes')} value={optimizationStats!.total_thumbnails.toLocaleString()} sub={__('Thumbnails found', 'image-sizes')} /> )} {/* OPTIMIZATION */} {optimizationLoading ? : ( } title={__('Unoptimized Images', 'image-sizes')} value={optimizationStats!.unoptimized_images.toLocaleString()} sub={__('Images yet to be fixed', 'image-sizes')} /> )} {/* COUNTS */} } title={__('Space Saved', 'image-sizes')} value={formatBytes(countsStats.total_space_saved)} sub={__('Worth of space saved', 'image-sizes')} />
{/* Feature Cards Grid — per-card filters, each gated on its own loading group */}
{/* ANALYSIS */} {analysisLoading ? : applyFilters( 'thumbpress_dashboard_card_large', } title={__('Large Images', 'image-sizes')} description={__('Images (over 1 MB)', 'image-sizes')} value={mergedStats.large_images.toLocaleString()} actionLabel={__('Upgrade to Compress', 'image-sizes')} />, mergedStats, navigate, ) as React.ReactNode} {/* ANALYSIS */} {analysisLoading ? : applyFilters( 'thumbpress_dashboard_card_unused', } title={__('Unused Images', 'image-sizes')} description={__('Unattached images', 'image-sizes')} />, mergedStats, navigate, ) as React.ReactNode} {/* OPTIMIZATION */} {optimizationLoading ? : applyFilters( 'thumbpress_dashboard_card_compress', } title={__('Uncompressed Images', 'image-sizes')} description={__('Images need compression', 'image-sizes')} value={mergedStats.not_compressed.toLocaleString()} actionLabel={__('Upgrade to Compress', 'image-sizes')} />, mergedStats, navigate, ) as React.ReactNode} {/* ANALYSIS */} {analysisLoading ? : applyFilters( 'thumbpress_dashboard_card_duplicate', } title={__('Duplicate Images', 'image-sizes')} description={__('Duplicate images found', 'image-sizes')} value={mergedStats.duplicate_images.toLocaleString()} actionLabel={__('Upgrade to Merge', 'image-sizes')} />, mergedStats, navigate, ) as React.ReactNode} {/* COUNTS — always ready */} {applyFilters( 'thumbpress_dashboard_card_avif', } title={__('Convert to AVIF', 'image-sizes')} description={__('Non-AVIF images found', 'image-sizes')} value={mergedStats.not_avif.toLocaleString()} actionLabel={__('Upgrade to Convert', 'image-sizes')} />, mergedStats, navigate, ) as React.ReactNode} {/* COUNTS */} } title={__('Convert to WebP', 'image-sizes')} value={(countsStats.not_webp ?? 0).toLocaleString()} sub={__('Non-WebP images found', 'image-sizes')} onClick={() => navigate('/convert-to-webp')} /> {/* COUNTS */} } title={__('Thumbnail Disabled', 'image-sizes')} value={`${countsStats.disabled_sizes}/${countsStats.total_sizes}`} sub={__("These sizes won't be generated", 'image-sizes')} onClick={() => navigate('/settings?tab=thumbnails')} /> {/* COUNTS */} } title={__('Lazy Loading', 'image-sizes')} value={countsStats.lazy_load ? __('Active', 'image-sizes') : __('Inactive', 'image-sizes')} sub={__(`Status: ${countsStats.lazy_load ? 'On' : 'Off'}`, 'image-sizes')} onClick={() => navigate('/settings?tab=general')} />
); }