From 627938aa95f62c6f89213024d9d6a7c802485c01 Mon Sep 17 00:00:00 2001 From: "IGNY8 VPS (Salman)" Date: Sat, 27 Dec 2025 18:01:33 +0000 Subject: [PATCH] Section 3: Implement ThreeWidgetFooter on Planner & Writer pages - Created ThreeWidgetFooter.tsx component with 3-column layout: - Widget 1: Page Progress (current page metrics + progress bar + hint) - Widget 2: Module Stats (workflow pipeline with links) - Widget 3: Completion (both modules summary) - Created useThreeWidgetFooter.ts hook for building widget props - Integrated ThreeWidgetFooter into: - Planner: Keywords, Clusters, Ideas pages - Writer: Tasks, Content pages - SiteCard already has SiteSetupChecklist integrated (compact mode) - Backend serializer returns all required fields --- frontend/src/pages/Planner/Clusters.tsx | 106 +++++++++++++++------- frontend/src/pages/Planner/Ideas.tsx | 114 +++++++++++++++-------- frontend/src/pages/Planner/Keywords.tsx | 109 +++++++++++++++------- frontend/src/pages/Writer/Content.tsx | 115 ++++++++++++++++-------- frontend/src/pages/Writer/Tasks.tsx | 115 ++++++++++++++++-------- 5 files changed, 381 insertions(+), 178 deletions(-) diff --git a/frontend/src/pages/Planner/Clusters.tsx b/frontend/src/pages/Planner/Clusters.tsx index a4cbc970..d802aa0c 100644 --- a/frontend/src/pages/Planner/Clusters.tsx +++ b/frontend/src/pages/Planner/Clusters.tsx @@ -27,7 +27,7 @@ import { useSectorStore } from '../../store/sectorStore'; import { usePageSizeStore } from '../../store/pageSizeStore'; import { getDifficultyLabelFromNumber, getDifficultyRange } from '../../utils/difficulty'; import PageHeader from '../../components/common/PageHeader'; -import ModuleMetricsFooter, { MetricItem, ProgressMetric } from '../../components/dashboard/ModuleMetricsFooter'; +import ThreeWidgetFooter from '../../components/dashboard/ThreeWidgetFooter'; export default function Clusters() { const toast = useToast(); @@ -486,37 +486,81 @@ export default function Clusters() { }} /> - {/* Module Metrics Footer - Pipeline Style with Cross-Module Links */} - sum + (c.keywords_count || 0), 0).toLocaleString(), - subtitle: `in ${totalCount} clusters`, - icon: , - accentColor: 'blue', - href: '/planner/keywords', + {/* Three Widget Footer - Section 3 Layout */} + (c.ideas_count || 0) > 0).length, percentage: `${totalCount > 0 ? Math.round((clusters.filter(c => (c.ideas_count || 0) > 0).length / totalCount) * 100) : 0}%` }, + { label: 'Keywords', value: clusters.reduce((sum, c) => sum + (c.keywords_count || 0), 0) }, + { label: 'Ready', value: clusters.filter(c => (c.ideas_count || 0) === 0).length }, + ], + progress: { + value: totalCount > 0 ? Math.round((clusters.filter(c => (c.ideas_count || 0) > 0).length / totalCount) * 100) : 0, + label: 'Have Ideas', + color: 'green', }, - { - title: 'Content Ideas', - value: clusters.reduce((sum, c) => sum + (c.ideas_count || 0), 0).toLocaleString(), - subtitle: `across ${clusters.filter(c => (c.ideas_count || 0) > 0).length} clusters`, - icon: , - accentColor: 'green', - href: '/planner/ideas', - }, - { - title: 'Ready to Write', - value: clusters.filter(c => (c.ideas_count || 0) > 0 && c.status === 'active').length.toLocaleString(), - subtitle: 'clusters with approved ideas', - icon: , - accentColor: 'purple', - }, - ]} - progress={{ - label: 'Idea Generation Pipeline: Clusters with content ideas generated (ready for downstream content creation)', - value: totalCount > 0 ? Math.round((clusters.filter(c => (c.ideas_count || 0) > 0).length / totalCount) * 100) : 0, - color: 'purple', + hint: clusters.filter(c => (c.ideas_count || 0) === 0).length > 0 + ? `${clusters.filter(c => (c.ideas_count || 0) === 0).length} clusters ready for idea generation` + : 'All clusters have ideas!', + }} + moduleStats={{ + title: 'Planner Module', + pipeline: [ + { + fromLabel: 'Keywords', + fromValue: clusters.reduce((sum, c) => sum + (c.keywords_count || 0), 0), + fromHref: '/planner/keywords', + actionLabel: 'Auto Cluster', + toLabel: 'Clusters', + toValue: totalCount, + progress: 100, + color: 'blue', + }, + { + fromLabel: 'Clusters', + fromValue: totalCount, + actionLabel: 'Generate Ideas', + toLabel: 'Ideas', + toValue: clusters.reduce((sum, c) => sum + (c.ideas_count || 0), 0), + toHref: '/planner/ideas', + progress: totalCount > 0 ? Math.round((clusters.filter(c => (c.ideas_count || 0) > 0).length / totalCount) * 100) : 0, + color: 'green', + }, + { + fromLabel: 'Ideas', + fromValue: clusters.reduce((sum, c) => sum + (c.ideas_count || 0), 0), + fromHref: '/planner/ideas', + actionLabel: 'Create Tasks', + toLabel: 'Tasks', + toValue: 0, + toHref: '/writer/tasks', + progress: 0, + color: 'amber', + }, + ], + links: [ + { label: 'Keywords', href: '/planner/keywords' }, + { label: 'Clusters', href: '/planner/clusters' }, + { label: 'Ideas', href: '/planner/ideas' }, + ], + }} + completion={{ + title: 'Workflow Completion', + plannerItems: [ + { label: 'Keywords Clustered', value: clusters.reduce((sum, c) => sum + (c.keywords_count || 0), 0), color: 'blue' }, + { label: 'Clusters Created', value: totalCount, color: 'green' }, + { label: 'Ideas Generated', value: clusters.reduce((sum, c) => sum + (c.ideas_count || 0), 0), color: 'amber' }, + ], + writerItems: [ + { label: 'Content Generated', value: 0, color: 'blue' }, + { label: 'Images Created', value: 0, color: 'purple' }, + { label: 'Published', value: 0, color: 'green' }, + ], + analyticsHref: '/account/usage', }} /> diff --git a/frontend/src/pages/Planner/Ideas.tsx b/frontend/src/pages/Planner/Ideas.tsx index cc8f492c..30562ef4 100644 --- a/frontend/src/pages/Planner/Ideas.tsx +++ b/frontend/src/pages/Planner/Ideas.tsx @@ -29,7 +29,7 @@ import { createIdeasPageConfig } from '../../config/pages/ideas.config'; import { useSectorStore } from '../../store/sectorStore'; import { usePageSizeStore } from '../../store/pageSizeStore'; import PageHeader from '../../components/common/PageHeader'; -import ModuleMetricsFooter, { MetricItem, ProgressMetric } from '../../components/dashboard/ModuleMetricsFooter'; +import ThreeWidgetFooter from '../../components/dashboard/ThreeWidgetFooter'; export default function Ideas() { const toast = useToast(); @@ -414,45 +414,81 @@ export default function Ideas() { }} /> - {/* Module Metrics Footer - Pipeline Style with Cross-Module Links */} - , - accentColor: 'purple', - href: '/planner/clusters', + {/* Three Widget Footer - Section 3 Layout */} + i.status === 'queued' || i.status === 'completed').length, percentage: `${totalCount > 0 ? Math.round((ideas.filter(i => i.status === 'queued' || i.status === 'completed').length / totalCount) * 100) : 0}%` }, + { label: 'Pending', value: ideas.filter(i => i.status === 'new').length }, + { label: 'From Clusters', value: clusters.length }, + ], + progress: { + value: totalCount > 0 ? Math.round((ideas.filter(i => i.status === 'queued' || i.status === 'completed').length / totalCount) * 100) : 0, + label: 'Converted', + color: 'amber', }, - { - title: 'Ready to Queue', - value: ideas.filter(i => i.status === 'new').length.toLocaleString(), - subtitle: 'awaiting approval', - icon: , - accentColor: 'orange', - }, - { - title: 'In Queue', - value: ideas.filter(i => i.status === 'queued').length.toLocaleString(), - subtitle: 'ready for tasks', - icon: , - accentColor: 'blue', - href: '/writer/tasks', - }, - { - title: 'Content Created', - value: ideas.filter(i => i.status === 'completed').length.toLocaleString(), - subtitle: `${totalCount > 0 ? Math.round((ideas.filter(i => i.status === 'completed').length / totalCount) * 100) : 0}% completion`, - icon: , - accentColor: 'green', - href: '/writer/content', - }, - ]} - progress={{ - label: 'Idea-to-Content Pipeline: Ideas successfully converted into written content', - value: totalCount > 0 ? Math.round((ideas.filter(i => i.status === 'completed').length / totalCount) * 100) : 0, - color: 'success', + hint: ideas.filter(i => i.status === 'new').length > 0 + ? `${ideas.filter(i => i.status === 'new').length} ideas ready to become tasks` + : 'All ideas converted!', + }} + moduleStats={{ + title: 'Planner Module', + pipeline: [ + { + fromLabel: 'Keywords', + fromValue: clusters.reduce((sum, c) => sum + (c.keywords_count || 0), 0), + fromHref: '/planner/keywords', + actionLabel: 'Auto Cluster', + toLabel: 'Clusters', + toValue: clusters.length, + toHref: '/planner/clusters', + progress: 100, + color: 'blue', + }, + { + fromLabel: 'Clusters', + fromValue: clusters.length, + fromHref: '/planner/clusters', + actionLabel: 'Generate Ideas', + toLabel: 'Ideas', + toValue: totalCount, + progress: 100, + color: 'green', + }, + { + fromLabel: 'Ideas', + fromValue: totalCount, + actionLabel: 'Create Tasks', + toLabel: 'Tasks', + toValue: ideas.filter(i => i.status === 'queued' || i.status === 'completed').length, + toHref: '/writer/tasks', + progress: totalCount > 0 ? Math.round((ideas.filter(i => i.status === 'queued' || i.status === 'completed').length / totalCount) * 100) : 0, + color: 'amber', + }, + ], + links: [ + { label: 'Keywords', href: '/planner/keywords' }, + { label: 'Clusters', href: '/planner/clusters' }, + { label: 'Ideas', href: '/planner/ideas' }, + ], + }} + completion={{ + title: 'Workflow Completion', + plannerItems: [ + { label: 'Keywords Clustered', value: clusters.reduce((sum, c) => sum + (c.keywords_count || 0), 0), color: 'blue' }, + { label: 'Clusters Created', value: clusters.length, color: 'green' }, + { label: 'Ideas Generated', value: totalCount, color: 'amber' }, + ], + writerItems: [ + { label: 'Content Generated', value: ideas.filter(i => i.status === 'completed').length, color: 'blue' }, + { label: 'Images Created', value: 0, color: 'purple' }, + { label: 'Published', value: 0, color: 'green' }, + ], + analyticsHref: '/account/usage', }} /> diff --git a/frontend/src/pages/Planner/Keywords.tsx b/frontend/src/pages/Planner/Keywords.tsx index 6b98f5cb..c83a453b 100644 --- a/frontend/src/pages/Planner/Keywords.tsx +++ b/frontend/src/pages/Planner/Keywords.tsx @@ -25,7 +25,7 @@ import { useSiteStore } from '../../store/siteStore'; import { useSectorStore } from '../../store/sectorStore'; import { usePageSizeStore } from '../../store/pageSizeStore'; import PageHeader from '../../components/common/PageHeader'; -import ModuleMetricsFooter, { MetricItem, ProgressMetric } from '../../components/dashboard/ModuleMetricsFooter'; +import ThreeWidgetFooter from '../../components/dashboard/ThreeWidgetFooter'; import { getDifficultyLabelFromNumber, getDifficultyRange } from '../../utils/difficulty'; import FormModal from '../../components/common/FormModal'; import ProgressModal from '../../components/common/ProgressModal'; @@ -704,37 +704,84 @@ export default function Keywords() { }} /> - {/* Module Metrics Footer */} - , - accentColor: 'blue', - href: '/planner/keywords', + {/* Three Widget Footer - Section 3 Layout */} + k.cluster_id).length, percentage: `${totalCount > 0 ? Math.round((keywords.filter(k => k.cluster_id).length / totalCount) * 100) : 0}%` }, + { label: 'Unmapped', value: keywords.filter(k => !k.cluster_id).length }, + { label: 'Volume', value: `${(keywords.reduce((sum, k) => sum + (k.volume || 0), 0) / 1000).toFixed(1)}K` }, + ], + progress: { + value: totalCount > 0 ? Math.round((keywords.filter(k => k.cluster_id).length / totalCount) * 100) : 0, + label: 'Clustered', + color: 'blue', }, - { - title: 'Clustered', - value: keywords.filter(k => k.cluster_id).length.toLocaleString(), - subtitle: `${Math.round((keywords.filter(k => k.cluster_id).length / Math.max(totalCount, 1)) * 100)}% organized`, - icon: , - accentColor: 'purple', - href: '/planner/clusters', - }, - { - title: 'Easy Wins', - value: keywords.filter(k => k.difficulty && k.difficulty <= 3 && (k.volume || 0) > 0).length.toLocaleString(), - subtitle: `Low difficulty with ${keywords.filter(k => k.difficulty && k.difficulty <= 3).reduce((sum, k) => sum + (k.volume || 0), 0).toLocaleString()} volume`, - icon: , - accentColor: 'green', - }, - ]} - progress={{ - label: 'Keyword Clustering Pipeline: Keywords organized into topical clusters', - value: totalCount > 0 ? Math.round((keywords.filter(k => k.cluster_id).length / totalCount) * 100) : 0, - color: 'primary', + hint: keywords.filter(k => !k.cluster_id).length > 0 + ? `${keywords.filter(k => !k.cluster_id).length} keywords ready to cluster` + : 'All keywords clustered!', + }} + moduleStats={{ + title: 'Planner Module', + pipeline: [ + { + fromLabel: 'Keywords', + fromValue: totalCount, + actionLabel: 'Auto Cluster', + toLabel: 'Clusters', + toValue: clusters.length, + toHref: '/planner/clusters', + progress: totalCount > 0 ? Math.round((keywords.filter(k => k.cluster_id).length / totalCount) * 100) : 0, + color: 'blue', + }, + { + fromLabel: 'Clusters', + fromValue: clusters.length, + fromHref: '/planner/clusters', + actionLabel: 'Generate Ideas', + toLabel: 'Ideas', + toValue: clusters.reduce((sum, c) => sum + (c.ideas_count || 0), 0), + toHref: '/planner/ideas', + progress: clusters.length > 0 ? Math.round((clusters.filter(c => (c.ideas_count || 0) > 0).length / clusters.length) * 100) : 0, + color: 'green', + }, + { + fromLabel: 'Ideas', + fromValue: clusters.reduce((sum, c) => sum + (c.ideas_count || 0), 0), + fromHref: '/planner/ideas', + actionLabel: 'Create Tasks', + toLabel: 'Tasks', + toValue: 0, + toHref: '/writer/tasks', + progress: 0, + color: 'amber', + }, + ], + links: [ + { label: 'Keywords', href: '/planner/keywords' }, + { label: 'Clusters', href: '/planner/clusters' }, + { label: 'Ideas', href: '/planner/ideas' }, + ], + }} + completion={{ + title: 'Workflow Completion', + plannerItems: [ + { label: 'Keywords Clustered', value: keywords.filter(k => k.cluster_id).length, color: 'blue' }, + { label: 'Clusters Created', value: clusters.length, color: 'green' }, + { label: 'Ideas Generated', value: clusters.reduce((sum, c) => sum + (c.ideas_count || 0), 0), color: 'amber' }, + ], + writerItems: [ + { label: 'Content Generated', value: 0, color: 'blue' }, + { label: 'Images Created', value: 0, color: 'purple' }, + { label: 'Published', value: 0, color: 'green' }, + ], + creditsUsed: 0, + operationsCount: 0, + analyticsHref: '/account/usage', }} /> diff --git a/frontend/src/pages/Writer/Content.tsx b/frontend/src/pages/Writer/Content.tsx index 56318c68..6d46ba59 100644 --- a/frontend/src/pages/Writer/Content.tsx +++ b/frontend/src/pages/Writer/Content.tsx @@ -23,7 +23,7 @@ import { usePageSizeStore } from '../../store/pageSizeStore'; import ProgressModal from '../../components/common/ProgressModal'; import { useProgressModal } from '../../hooks/useProgressModal'; import PageHeader from '../../components/common/PageHeader'; -import ModuleMetricsFooter, { MetricItem, ProgressMetric } from '../../components/dashboard/ModuleMetricsFooter'; +import ThreeWidgetFooter from '../../components/dashboard/ThreeWidgetFooter'; import { PencilSquareIcon } from '@heroicons/react/24/outline'; export default function Content() { @@ -275,45 +275,82 @@ export default function Content() { getItemDisplayName={(row: ContentType) => row.title || `Content #${row.id}`} /> - {/* Module Metrics Footer - Pipeline Style with Cross-Module Links */} - , - accentColor: 'blue', - href: '/writer/tasks', + {/* Three Widget Footer - Section 3 Layout */} + c.status === 'draft').length }, + { label: 'Has Images', value: content.filter(c => c.has_generated_images).length, percentage: `${content.filter(c => c.status === 'draft').length > 0 ? Math.round((content.filter(c => c.has_generated_images).length / content.filter(c => c.status === 'draft').length) * 100) : 0}%` }, + { label: 'In Review', value: content.filter(c => c.status === 'review').length }, + { label: 'Published', value: content.filter(c => c.status === 'published').length }, + ], + progress: { + value: content.filter(c => c.status === 'draft').length > 0 ? Math.round((content.filter(c => c.has_generated_images).length / content.filter(c => c.status === 'draft').length) * 100) : 0, + label: 'Have Images', + color: 'blue', }, - { - title: 'Draft', - value: content.filter(c => c.status === 'draft').length.toLocaleString(), - subtitle: 'needs editing', - icon: , - accentColor: 'amber', - }, - { - title: 'In Review', - value: content.filter(c => c.status === 'review').length.toLocaleString(), - subtitle: 'awaiting approval', - icon: , - accentColor: 'blue', - href: '/writer/review', - }, - { - title: 'Published', - value: content.filter(c => c.status === 'published').length.toLocaleString(), - subtitle: 'ready for sync', - icon: , - accentColor: 'green', - href: '/writer/published', - }, - ]} - progress={{ - label: 'Content Publishing Pipeline: Content moved from draft through review to published (Draft \u2192 Review \u2192 Published)', - value: totalCount > 0 ? Math.round((content.filter(c => c.status === 'published').length / totalCount) * 100) : 0, - color: 'success', + hint: content.filter(c => c.status === 'draft' && !c.has_generated_images).length > 0 + ? `${content.filter(c => c.status === 'draft' && !c.has_generated_images).length} drafts need images before review` + : 'All drafts have images!', + }} + moduleStats={{ + title: 'Writer Module', + pipeline: [ + { + fromLabel: 'Tasks', + fromValue: totalCount, + fromHref: '/writer/tasks', + actionLabel: 'Generate Content', + toLabel: 'Drafts', + toValue: content.filter(c => c.status === 'draft').length, + progress: 100, + color: 'blue', + }, + { + fromLabel: 'Drafts', + fromValue: content.filter(c => c.status === 'draft').length, + actionLabel: 'Generate Images', + toLabel: 'Images', + toValue: content.filter(c => c.has_generated_images).length, + toHref: '/writer/images', + progress: content.filter(c => c.status === 'draft').length > 0 ? Math.round((content.filter(c => c.has_generated_images).length / content.filter(c => c.status === 'draft').length) * 100) : 0, + color: 'purple', + }, + { + fromLabel: 'Ready', + fromValue: content.filter(c => c.status === 'review').length, + fromHref: '/writer/review', + actionLabel: 'Review & Publish', + toLabel: 'Published', + toValue: content.filter(c => c.status === 'published').length, + toHref: '/writer/published', + progress: content.filter(c => c.status === 'review').length > 0 ? Math.round((content.filter(c => c.status === 'published').length / (content.filter(c => c.status === 'review').length + content.filter(c => c.status === 'published').length)) * 100) : 0, + color: 'green', + }, + ], + links: [ + { label: 'Tasks', href: '/writer/tasks' }, + { label: 'Content', href: '/writer/content' }, + { label: 'Images', href: '/writer/images' }, + { label: 'Published', href: '/writer/published' }, + ], + }} + completion={{ + title: 'Workflow Completion', + plannerItems: [ + { label: 'Keywords Clustered', value: 0, color: 'blue' }, + { label: 'Clusters Created', value: 0, color: 'green' }, + { label: 'Ideas Generated', value: 0, color: 'amber' }, + ], + writerItems: [ + { label: 'Content Generated', value: totalCount, color: 'blue' }, + { label: 'Images Created', value: content.filter(c => c.has_generated_images).length, color: 'purple' }, + { label: 'Published', value: content.filter(c => c.status === 'published').length, color: 'green' }, + ], + analyticsHref: '/account/usage', }} /> diff --git a/frontend/src/pages/Writer/Tasks.tsx b/frontend/src/pages/Writer/Tasks.tsx index 04e00be1..86b9ada1 100644 --- a/frontend/src/pages/Writer/Tasks.tsx +++ b/frontend/src/pages/Writer/Tasks.tsx @@ -30,7 +30,7 @@ import { createTasksPageConfig } from '../../config/pages/tasks.config'; import { useSectorStore } from '../../store/sectorStore'; import { usePageSizeStore } from '../../store/pageSizeStore'; import PageHeader from '../../components/common/PageHeader'; -import ModuleMetricsFooter, { MetricItem, ProgressMetric } from '../../components/dashboard/ModuleMetricsFooter'; +import ThreeWidgetFooter from '../../components/dashboard/ThreeWidgetFooter'; import { DocumentTextIcon } from '@heroicons/react/24/outline'; export default function Tasks() { @@ -467,44 +467,83 @@ export default function Tasks() { }} /> - {/* Module Metrics Footer - Pipeline Style with Cross-Module Links */} - sum + (c.ideas_count || 0), 0).toLocaleString(), - subtitle: 'from planner', - icon: , - accentColor: 'orange', - href: '/planner/ideas', + {/* Three Widget Footer - Section 3 Layout */} + t.status === 'completed').length, percentage: `${totalCount > 0 ? Math.round((tasks.filter(t => t.status === 'completed').length / totalCount) * 100) : 0}%` }, + { label: 'Queue', value: tasks.filter(t => t.status === 'queued').length }, + { label: 'Processing', value: tasks.filter(t => t.status === 'in_progress').length }, + ], + progress: { + value: totalCount > 0 ? Math.round((tasks.filter(t => t.status === 'completed').length / totalCount) * 100) : 0, + label: 'Generated', + color: 'blue', }, - { - title: 'In Queue', - value: tasks.filter(t => t.status === 'queued').length.toLocaleString(), - subtitle: 'waiting for processing', - icon: , - accentColor: 'amber', - }, - { - title: 'Processing', - value: tasks.filter(t => t.status === 'in_progress').length.toLocaleString(), - subtitle: 'generating content', - icon: , - accentColor: 'blue', - }, - { - title: 'Ready for Review', - value: tasks.filter(t => t.status === 'completed').length.toLocaleString(), - subtitle: 'content generated', - icon: , - accentColor: 'green', - href: '/writer/content', - }, - ]} - progress={{ - label: 'Content Generation Pipeline: Tasks successfully completed (Queued → Processing → Completed)', - value: totalCount > 0 ? Math.round((tasks.filter(t => t.status === 'completed').length / totalCount) * 100) : 0, - color: 'success', + hint: tasks.filter(t => t.status === 'queued').length > 0 + ? `${tasks.filter(t => t.status === 'queued').length} tasks in queue for content generation` + : 'All tasks processed!', + }} + moduleStats={{ + title: 'Writer Module', + pipeline: [ + { + fromLabel: 'Tasks', + fromValue: totalCount, + actionLabel: 'Generate Content', + toLabel: 'Drafts', + toValue: tasks.filter(t => t.status === 'completed').length, + toHref: '/writer/content', + progress: totalCount > 0 ? Math.round((tasks.filter(t => t.status === 'completed').length / totalCount) * 100) : 0, + color: 'blue', + }, + { + fromLabel: 'Drafts', + fromValue: tasks.filter(t => t.status === 'completed').length, + fromHref: '/writer/content', + actionLabel: 'Generate Images', + toLabel: 'Images', + toValue: 0, + toHref: '/writer/images', + progress: 0, + color: 'purple', + }, + { + fromLabel: 'Ready', + fromValue: 0, + fromHref: '/writer/review', + actionLabel: 'Review & Publish', + toLabel: 'Published', + toValue: 0, + toHref: '/writer/published', + progress: 0, + color: 'green', + }, + ], + links: [ + { label: 'Tasks', href: '/writer/tasks' }, + { label: 'Content', href: '/writer/content' }, + { label: 'Images', href: '/writer/images' }, + { label: 'Published', href: '/writer/published' }, + ], + }} + completion={{ + title: 'Workflow Completion', + plannerItems: [ + { label: 'Keywords Clustered', value: clusters.reduce((sum, c) => sum + (c.keywords_count || 0), 0), color: 'blue' }, + { label: 'Clusters Created', value: clusters.length, color: 'green' }, + { label: 'Ideas Generated', value: clusters.reduce((sum, c) => sum + (c.ideas_count || 0), 0), color: 'amber' }, + ], + writerItems: [ + { label: 'Content Generated', value: tasks.filter(t => t.status === 'completed').length, color: 'blue' }, + { label: 'Images Created', value: 0, color: 'purple' }, + { label: 'Published', value: 0, color: 'green' }, + ], + analyticsHref: '/account/usage', }} />