import { useEffect, useState, useMemo, lazy, Suspense } from "react"; import { Link, useNavigate } from "react-router"; import PageMeta from "../../components/common/PageMeta"; import ComponentCard from "../../components/common/ComponentCard"; import { ProgressBar } from "../../components/ui/progress"; import { ApexOptions } from "apexcharts"; import WorkflowPipeline, { WorkflowStep } from "../../components/dashboard/WorkflowPipeline"; import EnhancedMetricCard from "../../components/dashboard/EnhancedMetricCard"; const Chart = lazy(() => import("react-apexcharts").then((mod) => ({ default: mod.default }))); import { FileTextIcon, BoxIcon, CheckCircleIcon, ClockIcon, PencilIcon, BoltIcon, ArrowUpIcon, ArrowDownIcon, ArrowRightIcon } from "../../icons"; import { fetchTasks, fetchContent, fetchImages } from "../../services/api"; import { useSiteStore } from "../../store/siteStore"; import { useSectorStore } from "../../store/sectorStore"; import PageHeader from "../../components/common/PageHeader"; interface WriterStats { tasks: { total: number; byStatus: Record; pending: number; inProgress: number; completed: number; avgWordCount: number; totalWordCount: number; }; content: { total: number; drafts: number; review: number; published: number; totalWordCount: number; avgWordCount: number; byContentType: Record; }; images: { total: number; generated: number; pending: number; failed: number; byType: Record; }; workflow: { tasksCreated: boolean; contentGenerated: boolean; imagesGenerated: boolean; readyToPublish: boolean; }; productivity: { contentThisWeek: number; contentThisMonth: number; avgGenerationTime: number; publishRate: number; }; } export default function WriterDashboard() { const navigate = useNavigate(); const { activeSite } = useSiteStore(); const { activeSector } = useSectorStore(); const [stats, setStats] = useState(null); const [loading, setLoading] = useState(true); const [lastUpdated, setLastUpdated] = useState(new Date()); const [trends, setTrends] = useState<{ tasks: number; content: number; images: number; }>({ tasks: 0, content: 0, images: 0 }); // Fetch real data const fetchDashboardData = async () => { try { setLoading(true); // Fetch all data in parallel const [tasksRes, contentRes, imagesRes] = await Promise.all([ fetchTasks({ page_size: 1000, sector_id: activeSector?.id }), fetchContent({ page_size: 1000, sector_id: activeSector?.id }), fetchImages({ page_size: 1000, sector_id: activeSector?.id }) ]); // Process tasks const tasks = tasksRes.results || []; const tasksByStatus: Record = {}; let totalWordCount = 0; let wordCountCount = 0; tasks.forEach(t => { tasksByStatus[t.status || 'draft'] = (tasksByStatus[t.status || 'draft'] || 0) + 1; if (t.word_count) { totalWordCount += t.word_count; wordCountCount++; } }); const pendingTasks = tasks.filter(t => t.status === 'draft' || t.status === 'pending').length; const inProgressTasks = tasks.filter(t => t.status === 'in_progress' || t.status === 'review').length; const completedTasks = tasks.filter(t => t.status === 'completed' || t.status === 'published').length; const avgWordCount = wordCountCount > 0 ? Math.round(totalWordCount / wordCountCount) : 0; // Process content const content = contentRes.results || []; const contentByStatus: Record = {}; const contentByType: Record = {}; let contentTotalWords = 0; let contentWordCount = 0; content.forEach(c => { contentByStatus[c.status || 'draft'] = (contentByStatus[c.status || 'draft'] || 0) + 1; if (c.word_count) { contentTotalWords += c.word_count; contentWordCount++; } }); const drafts = content.filter(c => c.status === 'draft').length; const review = content.filter(c => c.status === 'review').length; const published = content.filter(c => c.status === 'published').length; const contentAvgWordCount = contentWordCount > 0 ? Math.round(contentTotalWords / contentWordCount) : 0; // Process images const images = imagesRes.results || []; const imagesByStatus: Record = {}; const imagesByType: Record = {}; images.forEach(img => { imagesByStatus[img.status || 'pending'] = (imagesByStatus[img.status || 'pending'] || 0) + 1; if (img.image_type) { imagesByType[img.image_type] = (imagesByType[img.image_type] || 0) + 1; } }); const generatedImages = images.filter(img => img.status === 'generated' && img.image_url).length; const pendingImages = images.filter(img => (img.status === 'pending' || img.status === 'draft') || !img.image_url).length; const failedImages = images.filter(img => img.status === 'failed' || img.status === 'error').length; // Calculate productivity metrics const now = new Date(); const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); const monthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); const contentThisWeek = content.filter(c => { if (!c.generated_at) return false; const created = new Date(c.generated_at); return created >= weekAgo; }).length; const contentThisMonth = content.filter(c => { if (!c.generated_at) return false; const created = new Date(c.generated_at); return created >= monthAgo; }).length; const publishRate = content.length > 0 ? Math.round((published / content.length) * 100) : 0; // Calculate trends if (stats) { setTrends({ tasks: tasks.length - stats.tasks.total, content: content.length - stats.content.total, images: images.length - stats.images.total }); } setStats({ tasks: { total: tasks.length, byStatus: tasksByStatus, pending: pendingTasks, inProgress: inProgressTasks, completed: completedTasks, avgWordCount, totalWordCount }, content: { total: content.length, drafts, review, published, totalWordCount: contentTotalWords, avgWordCount: contentAvgWordCount, byContentType: contentByType }, images: { total: images.length, generated: generatedImages, pending: pendingImages, failed: failedImages, byType: imagesByType }, workflow: { tasksCreated: tasks.length > 0, contentGenerated: content.length > 0, imagesGenerated: generatedImages > 0, readyToPublish: published > 0 }, productivity: { contentThisWeek, contentThisMonth, avgGenerationTime: 0, // Would need task timestamps to calculate publishRate } }); setLastUpdated(new Date()); } catch (error) { console.error('Error fetching dashboard data:', error); } finally { setLoading(false); } }; // Initial load and periodic refresh useEffect(() => { // Allow loading for all sectors (when activeSector is null) or specific sector fetchDashboardData(); // Refresh every 30 seconds const interval = setInterval(fetchDashboardData, 30000); return () => clearInterval(interval); }, [activeSector?.id, activeSite?.id]); // Chart data for tasks by status const tasksStatusChart = useMemo(() => { if (!stats) return null; const options: ApexOptions = { chart: { type: 'donut', fontFamily: 'Outfit, sans-serif', toolbar: { show: false } }, labels: Object.keys(stats.tasks.byStatus).filter(key => stats.tasks.byStatus[key] > 0), colors: ['#465FFF', '#F59E0B', '#10B981', '#EF4444', '#8B5CF6'], legend: { position: 'bottom', fontFamily: 'Outfit', show: true }, dataLabels: { enabled: false // Disable labels on pie slices }, tooltip: { enabled: true, y: { formatter: (val: number, { seriesIndex, w }: any) => { const label = w.globals.labels[seriesIndex] || ''; return `${label}: ${val}`; } } }, plotOptions: { pie: { donut: { size: '70%', labels: { show: true, name: { show: false // Hide "Total" label }, value: { show: true, fontSize: '24px', fontWeight: 700, color: '#465FFF', fontFamily: 'Outfit', formatter: () => { const total = Object.values(stats.tasks.byStatus).reduce((a, b) => a + b, 0); return total > 0 ? total.toString() : '0'; } }, total: { show: false // Hide total label } } } } } }; const series = Object.keys(stats.tasks.byStatus) .filter(key => stats.tasks.byStatus[key] > 0) .map(key => stats.tasks.byStatus[key]); return { options, series }; }, [stats]); // Chart data for content by status const contentStatusChart = useMemo(() => { if (!stats) return null; const options: ApexOptions = { chart: { type: 'bar', fontFamily: 'Outfit, sans-serif', toolbar: { show: false }, height: 250 }, colors: ['#465FFF', '#F59E0B', '#10B981'], plotOptions: { bar: { horizontal: false, columnWidth: '55%', borderRadius: 5 } }, dataLabels: { enabled: true }, xaxis: { categories: ['Drafts', 'In Review', 'Published'], labels: { style: { fontFamily: 'Outfit' } } }, yaxis: { labels: { style: { fontFamily: 'Outfit' } } }, grid: { strokeDashArray: 4 } }; const series = [{ name: 'Content', data: [stats.content.drafts, stats.content.review, stats.content.published] }]; return { options, series }; }, [stats]); // Chart data for images by type const imagesTypeChart = useMemo(() => { if (!stats || Object.keys(stats.images.byType).length === 0) return null; const options: ApexOptions = { chart: { type: 'bar', fontFamily: 'Outfit, sans-serif', toolbar: { show: false }, height: 250 }, colors: ['#10B981'], plotOptions: { bar: { horizontal: true, borderRadius: 5 } }, dataLabels: { enabled: true }, xaxis: { categories: Object.keys(stats.images.byType), labels: { style: { fontFamily: 'Outfit', fontSize: '12px' } } }, yaxis: { labels: { style: { fontFamily: 'Outfit' } } } }; const series = [{ name: 'Images', data: Object.values(stats.images.byType) }]; return { options, series }; }, [stats]); // Productivity chart (content over time - simplified) const productivityChart = useMemo(() => { if (!stats) return null; const options: ApexOptions = { chart: { type: 'area', fontFamily: 'Outfit, sans-serif', toolbar: { show: false }, height: 200 }, colors: ['#465FFF'], stroke: { curve: 'smooth', width: 2 }, fill: { type: 'gradient', gradient: { opacityFrom: 0.6, opacityTo: 0.1 } }, xaxis: { categories: ['Week', 'Month'], labels: { style: { fontFamily: 'Outfit' } } }, yaxis: { labels: { style: { fontFamily: 'Outfit' } } }, grid: { strokeDashArray: 4 }, dataLabels: { enabled: true } }; const series = [{ name: 'Content Created', data: [stats.productivity.contentThisWeek, stats.productivity.contentThisMonth] }]; return { options, series }; }, [stats]); if (loading && !stats) { return ( <>

Loading dashboard data...

); } if (!stats && !loading) { return ( <>

{activeSector ? 'No data available for the selected sector.' : 'No data available. Select a sector or wait for data to load.'}

); } if (!stats) { return null; // Still loading } const workflowSteps = [ { number: 1, title: "Create Tasks", status: stats.workflow.tasksCreated ? "completed" : "pending", count: stats.tasks.total, path: "/writer/tasks" }, { number: 2, title: "Generate Content", status: stats.workflow.contentGenerated ? "completed" : "pending", count: stats.content.total, path: "/writer/content" }, { number: 3, title: "Generate Images", status: stats.workflow.imagesGenerated ? "completed" : "pending", count: stats.images.generated, path: "/writer/images" }, { number: 4, title: "Publish", status: stats.workflow.readyToPublish ? "completed" : "pending", count: stats.content.published, path: "/writer/published" }, ]; const completionRate = stats.tasks.total > 0 ? Math.round((stats.tasks.completed / stats.tasks.total) * 100) : 0; const nextActions = [ ...(stats.tasks.pending > 0 ? [{ text: `${stats.tasks.pending} tasks pending content generation`, action: "Generate Content", path: "/writer/tasks" }] : []), ...(stats.content.drafts > 0 ? [{ text: `${stats.content.drafts} drafts ready for review`, action: "Review Content", path: "/writer/content" }] : []), ...(stats.images.pending > 0 ? [{ text: `${stats.images.pending} images pending generation`, action: "Generate Images", path: "/writer/images" }] : []), ...(stats.content.review > 0 ? [{ text: `${stats.content.review} content pieces ready to publish`, action: "Publish Content", path: "/writer/published" }] : []) ]; return ( <>
{/* Header with site/sector info and controls */} , color: 'blue' }} /> {/* Hero Section - Key Metric */}

Content Creation Progress

{stats.content.published > 0 ? ( <> {stats.content.published} Content Pieces Published ) : stats.content.review > 0 ? ( <> {stats.content.review} Pieces Ready to Publish ) : stats.content.drafts > 0 ? ( <> {stats.content.drafts} Drafts Ready for Review ) : stats.tasks.total > 0 ? ( <> {stats.tasks.total} Tasks Created ) : ( <> Ready to Create Content )}

{stats.tasks.total} tasks • {stats.content.total} content pieces • {stats.images.generated} images generated

{completionRate}%
Complete
{stats.productivity.publishRate}%
Published
{stats.images.generated > 0 ? Math.round((stats.images.generated / stats.images.total) * 100) : 0}%
Images
{/* Enhanced Metric Cards */}
} accentColor="blue" href="/writer/tasks" details={[ { label: "Total Tasks", value: stats.tasks.total }, { label: "Completed", value: stats.tasks.completed }, { label: "Pending", value: stats.tasks.pending }, { label: "In Progress", value: stats.tasks.inProgress }, { label: "Avg Word Count", value: stats.tasks.avgWordCount }, ]} /> } accentColor="green" href="/writer/content" details={[ { label: "Total Content", value: stats.content.total }, { label: "Published", value: stats.content.published }, { label: "In Review", value: stats.content.review }, { label: "Drafts", value: stats.content.drafts }, { label: "Avg Word Count", value: stats.content.avgWordCount.toLocaleString() }, ]} /> } accentColor="orange" href="/writer/images" details={[ { label: "Generated", value: stats.images.generated }, { label: "Total Images", value: stats.images.total }, { label: "Pending", value: stats.images.pending }, { label: "Failed", value: stats.images.failed }, ]} /> } accentColor="purple" href="/writer/published" details={[ { label: "Publish Rate", value: `${stats.productivity.publishRate}%` }, { label: "Published", value: stats.content.published }, { label: "Total Content", value: stats.content.total }, { label: "This Week", value: stats.productivity.contentThisWeek }, { label: "This Month", value: stats.productivity.contentThisMonth }, ]} />
{/* Interactive Workflow Pipeline */} ({ number: step.number, title: step.title, status: step.status === "completed" ? "completed" : step.status === "in_progress" ? "in_progress" : "pending", count: step.count || 0, path: step.path, description: step.title, details: step.status === "completed" ? `✓ ${step.title} completed with ${step.count} items` : step.status === "pending" ? `→ ${step.title} pending - ${step.count} items ready` : `⟳ ${step.title} in progress`, }))} onStepClick={(step) => { navigate(step.path); }} showConnections={true} />
{/* Productivity Metrics */}
Task Completion {completionRate}%

{stats.tasks.completed} of {stats.tasks.total} tasks completed

Publish Rate {stats.productivity.publishRate}%

{stats.content.published} of {stats.content.total} content published

This Week

{stats.productivity.contentThisWeek}

This Month

{stats.productivity.contentThisMonth}

Avg Word Count

{stats.content.avgWordCount.toLocaleString()}

{stats.content.totalWordCount.toLocaleString()} total words

{/* Content Status Chart */} {contentStatusChart && (
}> )}
{/* Tasks by Status */} {tasksStatusChart && (
}> )} {/* Images by Type */} {imagesTypeChart ? (
}>
) : (
Generated {stats.images.generated}
Pending {stats.images.pending}
Failed {stats.images.failed}
)} {/* Productivity Chart */} {productivityChart && (
}>
)} {/* Next Actions */} {nextActions.length > 0 && (
{nextActions.map((action, index) => (
{action.text} {action.action}
))}
)} ); }