import { useEffect, useState, useMemo, lazy, Suspense } from "react"; import { Link, useNavigate } from "react-router-dom"; import PageMeta from "../../components/common/PageMeta"; import ComponentCard from "../../components/common/ComponentCard"; import { ProgressBar } from "../../components/ui/progress"; import { ApexOptions } from "apexcharts"; import EnhancedMetricCard from "../../components/dashboard/EnhancedMetricCard"; import PageHeader from "../../components/common/PageHeader"; const Chart = lazy(() => import("react-apexcharts").then((mod) => ({ default: mod.default }))); import { FileTextIcon, BoxIcon, CheckCircleIcon, ClockIcon, PencilIcon, BoltIcon, ArrowRightIcon, PaperPlaneIcon, PlugInIcon, } from "../../icons"; import { fetchTasks, fetchContent, fetchContentImages, fetchTaxonomies, } from "../../services/api"; import { useSiteStore } from "../../store/siteStore"; import { useSectorStore } from "../../store/sectorStore"; interface WriterStats { tasks: { total: number; byStatus: Record; pending: number; inProgress: number; completed: number; avgWordCount: number; totalWordCount: number; }; content: { total: number; drafts: 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; }; taxonomies: number; attributes: 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 fetchDashboardData = async () => { try { setLoading(true); const [tasksRes, contentRes, imagesRes, taxonomiesRes] = await Promise.all([ fetchTasks({ page_size: 1000, sector_id: activeSector?.id }), fetchContent({ page_size: 1000, sector_id: activeSector?.id }), fetchContentImages({ sector_id: activeSector?.id }), fetchTaxonomies({ page_size: 1000, sector_id: activeSector?.id }), ]); const tasks = tasksRes.results || []; const tasksByStatus: Record = {}; let pendingTasks = 0; let inProgressTasks = 0; let completedTasks = 0; let totalWordCount = 0; tasks.forEach(t => { tasksByStatus[t.status || 'queued'] = (tasksByStatus[t.status || 'queued'] || 0) + 1; if (t.status === 'queued') pendingTasks++; else if (t.status === 'completed') completedTasks++; if (t.word_count) totalWordCount += t.word_count; }); const avgWordCount = tasks.length > 0 ? Math.round(totalWordCount / tasks.length) : 0; const content = contentRes.results || []; let drafts = 0; let published = 0; let contentTotalWordCount = 0; const contentByType: Record = {}; content.forEach(c => { if (c.status === 'draft') drafts++; else if (c.status === 'published') published++; if (c.word_count) contentTotalWordCount += c.word_count; }); const contentAvgWordCount = content.length > 0 ? Math.round(contentTotalWordCount / content.length) : 0; const images = imagesRes.results || []; let generatedImages = 0; let pendingImages = 0; let failedImages = 0; const imagesByType: Record = {}; images.forEach(imgGroup => { if (imgGroup.overall_status === 'complete') generatedImages++; else if (imgGroup.overall_status === 'pending' || imgGroup.overall_status === 'partial') pendingImages++; else if (imgGroup.overall_status === 'failed') failedImages++; if (imgGroup.featured_image) { imagesByType['featured'] = (imagesByType['featured'] || 0) + 1; } if (imgGroup.in_article_images && imgGroup.in_article_images.length > 0) { imagesByType['in_article'] = (imagesByType['in_article'] || 0) + imgGroup.in_article_images.length; } }); const contentThisWeek = Math.floor(content.length * 0.3); const contentThisMonth = Math.floor(content.length * 0.7); const publishRate = content.length > 0 ? Math.round((published / content.length) * 100) : 0; const taxonomies = taxonomiesRes.results || []; const taxonomyCount = taxonomies.length; // Note: Attributes are a subset of taxonomies with type 'product_attribute' const attributeCount = taxonomies.filter(t => t.taxonomy_type === 'product_attribute').length; setStats({ tasks: { total: tasks.length, byStatus: tasksByStatus, pending: pendingTasks, inProgress: inProgressTasks, completed: completedTasks, avgWordCount, totalWordCount }, content: { total: content.length, drafts, published, totalWordCount: contentTotalWordCount, 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, publishRate }, taxonomies: taxonomyCount, attributes: attributeCount, }); setLastUpdated(new Date()); } catch (error) { console.error('Error fetching dashboard data:', error); } finally { setLoading(false); } }; useEffect(() => { fetchDashboardData(); const interval = setInterval(fetchDashboardData, 30000); return () => clearInterval(interval); }, [activeSector?.id, activeSite?.id]); const completionRate = useMemo(() => { if (!stats || stats.tasks.total === 0) return 0; return Math.round((stats.tasks.completed / stats.tasks.total) * 100); }, [stats]); const writerModules = [ { title: "Tasks", description: "Content writing tasks and assignments", icon: FileTextIcon, color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]", path: "/writer/tasks", count: stats?.tasks.total || 0, metric: `${stats?.tasks.completed || 0} completed`, }, { title: "Content", description: "Generated content and drafts", icon: PencilIcon, color: "from-[var(--color-success)] to-[var(--color-success-dark)]", path: "/writer/content", count: stats?.content.total || 0, metric: `${stats?.content.published || 0} published`, }, { title: "Images", description: "Generated images and assets", icon: BoxIcon, color: "from-[var(--color-warning)] to-[var(--color-warning-dark)]", path: "/writer/images", count: stats?.images.generated || 0, metric: `${stats?.images.pending || 0} pending`, }, { title: "Published", description: "Published content and posts", icon: PaperPlaneIcon, color: "from-[var(--color-purple)] to-[var(--color-purple-dark)]", path: "/writer/published", count: stats?.content.published || 0, metric: "View all published", }, { title: "Taxonomies", description: "Manage content taxonomies", icon: BoltIcon, color: "from-[var(--color-info)] to-[var(--color-info-dark)]", path: "/writer/taxonomies", count: stats?.taxonomies || 0, metric: `${stats?.taxonomies || 0} total`, }, { title: "Attributes", description: "Manage content attributes", icon: PlugInIcon, color: "from-[var(--color-secondary)] to-[var(--color-secondary-dark)]", path: "/writer/attributes", count: stats?.attributes || 0, metric: `${stats?.attributes || 0} total`, }, ]; const recentActivity = [ { id: 1, type: "Content Published", description: `${stats?.content.published || 0} pieces published to WordPress`, timestamp: new Date(Date.now() - 30 * 60 * 1000), icon: PaperPlaneIcon, color: "text-green-600", }, { id: 2, type: "Content Generated", description: `${stats?.content.total || 0} content pieces created`, timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000), icon: PencilIcon, color: "text-blue-600", }, { id: 3, type: "Images Generated", description: `${stats?.images.generated || 0} images created`, timestamp: new Date(Date.now() - 4 * 60 * 60 * 1000), icon: BoxIcon, color: "text-orange-600", }, ]; const chartOptions: ApexOptions = { chart: { type: "area", height: 300, toolbar: { show: false }, zoom: { enabled: false }, }, stroke: { curve: "smooth", width: 3, }, xaxis: { categories: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], labels: { style: { colors: "#6b7280" } }, }, yaxis: { labels: { style: { colors: "#6b7280" } }, }, legend: { position: "top", labels: { colors: "#6b7280" }, }, colors: ["var(--color-primary)", "var(--color-success)", "var(--color-warning)"], grid: { borderColor: "#e5e7eb", }, fill: { type: "gradient", gradient: { opacityFrom: 0.6, opacityTo: 0.1, }, }, }; const chartSeries = [ { name: "Content Created", data: [12, 19, 15, 25, 22, 18, 24], }, { name: "Tasks Completed", data: [8, 12, 10, 15, 14, 11, 16], }, { name: "Images Generated", data: [5, 8, 6, 10, 9, 7, 11], }, ]; 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 }, plotOptions: { pie: { donut: { size: '70%', labels: { show: true, name: { show: false }, value: { show: true, fontSize: '24px', fontWeight: 700, color: '#465FFF', fontFamily: 'Outfit', formatter: () => { const total = Object.values(stats.tasks.byStatus).reduce((a: number, b: number) => a + b, 0); return total > 0 ? total.toString() : '0'; } }, total: { show: false } } } } } }; const series = Object.keys(stats.tasks.byStatus) .filter(key => stats.tasks.byStatus[key] > 0) .map(key => stats.tasks.byStatus[key]); return { options, series }; }, [stats]); const contentStatusChart = useMemo(() => { if (!stats) return null; const options: ApexOptions = { chart: { type: 'bar', fontFamily: 'Outfit, sans-serif', toolbar: { show: false }, height: 300 }, colors: ['#465FFF', '#F59E0B', '#10B981'], plotOptions: { bar: { horizontal: false, columnWidth: '55%', borderRadius: 5 } }, dataLabels: { enabled: true }, xaxis: { categories: ['Drafts', 'Published'], labels: { style: { fontFamily: 'Outfit' } } }, yaxis: { labels: { style: { fontFamily: 'Outfit' } } }, grid: { strokeDashArray: 4 } }; const series = [{ name: 'Content', data: [stats.content.drafts, stats.content.published] }]; return { options, series }; }, [stats]); const formatTimeAgo = (date: Date) => { const minutes = Math.floor((Date.now() - date.getTime()) / 60000); if (minutes < 60) return `${minutes}m ago`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours}h ago`; const days = Math.floor(hours / 24); return `${days}d ago`; }; 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; return ( <>
{/* Key Metrics */}
} accentColor="blue" trend={0} href="/writer/tasks" /> } accentColor="green" trend={0} href="/writer/content" /> } accentColor="orange" trend={0} href="/writer/images" /> } accentColor="purple" trend={0} href="/writer/published" />
{/* Writer Modules */}
{writerModules.map((module) => { const Icon = module.icon; return (

{module.title}

{module.description}

{module.count}
{module.metric}
); })}
{/* Activity Chart & Recent Activity */}
Loading chart...
}>
{recentActivity.map((activity) => { const Icon = activity.icon; return (

{activity.type}

{formatTimeAgo(activity.timestamp)}

{activity.description}

); })}
{/* Charts */}
{tasksStatusChart && ( Loading chart...
}> )} {contentStatusChart && ( Loading chart...}> )} {/* 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

Image Generation {stats.images.total > 0 ? Math.round((stats.images.generated / stats.images.total) * 100) : 0}%
0 ? Math.round((stats.images.generated / stats.images.total) * 100) : 0} color="warning" size="md" />

{stats.images.generated} of {stats.images.total} images generated

{/* Quick Actions */}

Create Task

New writing task

Generate Content

AI content creation

Generate Images

Create visuals

Publish Content

Publish to WordPress

{/* Info Cards */}

Task Creation

Create writing tasks from content ideas. Each task includes target keywords, outline, and word count requirements.

AI Content Generation

Generate full content pieces using AI. Content is created based on your prompts, author profiles, and brand guidelines.

Image Generation

Automatically generate featured images and in-article images for your content. Images are optimized for SEO and engagement.

1

Create Tasks

Start by creating writing tasks from content ideas in the Planner module. Tasks define what content needs to be written.

2

Generate Content

Use AI to generate content from tasks. Review and edit generated content before publishing.

3

Publish

Once content is reviewed and images are generated, publish directly to WordPress or export for manual publishing.

); }