import React, { useEffect, useState } from 'react'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; import { Progress } from "@/components/ui/progress" import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" import { toast, Toaster } from 'sonner'; import { FileText, Clock, Settings as SettingsIcon, CheckCircle2, AlertTriangle } from 'lucide-react'; import { Bar, BarChart, XAxis, YAxis, LabelList } from "recharts" import { ChartConfig, ChartContainer } from "@/components/ui/chart" interface DashboardData { last_export: { timestamp?: number; count?: number }; exported_files_count: number; export_dir: string; counts: Record; next_scheduled: number | null; auto_export: boolean; } declare global { interface Window { worddown_variables?: { restUrl?: string; restNonce?: string; strings?: Record; }; } } // Use the full API base path from PHP-localized variable const API_BASE = (window.worddown_variables?.restUrl || '/wp-json/worddown/v1/').replace(/\/$/, ''); const REST_NONCE: string = window.worddown_variables?.restNonce || ''; // Custom translation function to use localized strings from PHP function __(text: string, domain = 'worddown') { if ( window.worddown_variables && window.worddown_variables.strings && window.worddown_variables.strings[text] ) { return window.worddown_variables.strings[text]; } return text; } export default function DashboardPanel() { const [data, setData] = useState(null); const [settings, setSettings] = useState | null>(null); const [loading, setLoading] = useState(true); const [exporting, setExporting] = useState(false); const [exportStatus, setExportStatus] = useState(null); const [progress, setProgress] = useState(0); // Poll export status every 5 seconds when export is running useEffect(() => { let interval: NodeJS.Timeout; if (exporting && exportStatus?.status === 'running') { interval = setInterval(async () => { await checkExportStatus(); }, 5000); } return () => { if (interval) clearInterval(interval); }; }, [exporting, exportStatus]); useEffect(() => { fetchData(); // Check if there's already an export running checkExportStatus(); }, []); async function checkExportStatus() { try { const res = await fetch(`${API_BASE}/export-status`, { headers: { 'X-WP-Nonce': REST_NONCE }, }); const status = await res.json(); if (status.status === 'running') { // Update export status and progress setExportStatus(status); setProgress(status.progress_percentage || 0); // If we're not currently exporting, start the export state if (!exporting) { setExporting(true); } } else if (status.status === 'completed' || status.status === 'cancelled' || status.status === 'failed') { // Export is finished, stop polling and update UI setExporting(false); setExportStatus(null); setProgress(0); // Refresh dashboard data to update last export alert and bar chart try { setLoading(true); // Show loading state during refresh await fetchData(); } catch (error) { console.error('Failed to refresh dashboard data:', error); } finally { setLoading(false); // Hide loading state } // Show appropriate toast message if (status.status === 'completed') { toast.success(__('Export completed successfully!', 'worddown')); } else if (status.status === 'cancelled') { toast.info(__('Export was cancelled.', 'worddown')); } else { toast.error(__('Export failed.', 'worddown')); } } else if (status.status === 'idle') { if (exporting) { setExporting(false); setExportStatus(null); setProgress(0); } } } catch (e) { console.error('Failed to check export status:', e); } } async function fetchData() { try { const [dashboardRes, settingsRes] = await Promise.all([ fetch(`${API_BASE}/dashboard`, { headers: { 'X-WP-Nonce': REST_NONCE }, }).then(r => r.json()), fetch(`${API_BASE}/settings`, { headers: { 'X-WP-Nonce': REST_NONCE }, }).then(r => r.json()), ]); setData(dashboardRes); setSettings(settingsRes); setLoading(false); } catch (e) { toast.error(__('Failed to load dashboard data.', 'worddown')); setLoading(false); } } async function handleExport(e: React.FormEvent) { e.preventDefault(); setExporting(true); setProgress(0); try { const res = await fetch(`${API_BASE}/local-export`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': REST_NONCE }, }); const result = await res.json(); if (result.status === 'started') { toast.success(__('Export started successfully!', 'worddown')); setExportStatus({ status: 'running', total_posts: result.total_posts, processed: 0, exported: 0, progress_percentage: 0 }); } else { toast.error(result.message || __('Export failed to start.', 'worddown')); setExporting(false); } } catch (e) { toast.error(__('Export failed to start.', 'worddown')); setExporting(false); } } async function handleCancelExport() { try { const res = await fetch(`${API_BASE}/cancel-export`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': REST_NONCE }, }); const result = await res.json(); if (result.success) { toast.success(__('Export cancelled successfully.', 'worddown')); setExporting(false); setExportStatus(null); setProgress(0); } else { toast.error(result.message || __('Failed to cancel export.', 'worddown')); } } catch (e) { toast.error(__('Failed to cancel export.', 'worddown')); } } if (loading) return (
{/* Top stats grid skeleton */}
{[0, 1, 2].map(i => (
))}
{/* Bar chart skeleton */}
{[0, 1, 2, 3, 4].map(i => (
))}
{/* Export form card skeleton */}
{/* Static export info card skeleton */}
{[0,1,2].map(i => (
))}
); if (!data) return null; const lastExportDate = data.last_export?.timestamp ? new Date(data.last_export.timestamp * 1000) : null; const nextScheduledDate = data.next_scheduled ? new Date(data.next_scheduled * 1000) : null; let chartData = Object.entries(data.counts).map(([type, count]) => ({ type, count })); chartData = chartData.sort((a, b) => b.count - a.count); const maxValue = Math.max(...chartData.map(d => d.count), 1); return (
{/* Top stats grid */}
{/* Exported Files */}

{data.exported_files_count}

{__('Exported Files', 'worddown')}

{/* Last Export */}

{lastExportDate ? lastExportDate.toLocaleDateString() : __('Never', 'worddown')}

{__('Last Export', 'worddown')}

{/* Auto Export */}

{data.auto_export ? __('Enabled', 'worddown') : __('Disabled', 'worddown')}

{__('Auto Export', 'worddown')}

{/* Exported Content by Post Type Bar Chart */} {chartData.length > 0 && ( {__('Exported files by Post Type', 'worddown')} v} padding={{ top: 10, bottom: 8 }} tickMargin={8} /> )} {/* Export form card - use Card, white bg, default shadow */} {__('Export Now', 'worddown')}
{lastExportDate && ( {__('Last Export', 'worddown')} {lastExportDate.toLocaleString()} - {data.last_export?.count || 0} {__('files exported', 'worddown')} )} {nextScheduledDate && (
{__('Next Scheduled Export:', 'worddown')}{' '} {nextScheduledDate.toLocaleString()}
)}
{exporting && exportStatus && (
{exportStatus.current_operation || __('Export in progress...', 'worddown')}
{exportStatus.progress_percentage?.toFixed(1)}%
{exportStatus.total_posts}
{__('Total Posts', 'worddown')}
{exportStatus.exported || 0}
{__('Exported', 'worddown')}
{exportStatus.failed || 0}
{__('Failed', 'worddown')}
{exportStatus.estimated_completion && (
{__('Estimated completion:', 'worddown')} {new Date(exportStatus.estimated_completion * 1000).toLocaleTimeString()}
)}
)}
{!exporting ? ( ) : ( )}

{exporting ? __('Export is running in the background. You can safely close this page.', 'worddown') : __('Start a background export of all content based on your current settings.', 'worddown') }

{/* Static export info card - use Card, white bg, default shadow */} {__('Export Information', 'worddown')}
{__('Export Directory:', 'worddown')} {data.export_dir}
{data.export_dir && data.exported_files_count >= 0 ? ( ) : ( )} {data.export_dir && data.exported_files_count >= 0 ? __('Directory exists and is writable', 'worddown') : __('Directory does not exist or is not writable', 'worddown')}
{__('File Format:', 'worddown')} {'{post-type}-{title}-{id}.md'}
{/* Content Statistics removed */}
); }