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
This commit is contained in:
IGNY8 VPS (Salman)
2025-12-27 18:01:33 +00:00
parent a145e6742e
commit 627938aa95
5 changed files with 381 additions and 178 deletions

View File

@@ -27,7 +27,7 @@ import { useSectorStore } from '../../store/sectorStore';
import { usePageSizeStore } from '../../store/pageSizeStore'; import { usePageSizeStore } from '../../store/pageSizeStore';
import { getDifficultyLabelFromNumber, getDifficultyRange } from '../../utils/difficulty'; import { getDifficultyLabelFromNumber, getDifficultyRange } from '../../utils/difficulty';
import PageHeader from '../../components/common/PageHeader'; import PageHeader from '../../components/common/PageHeader';
import ModuleMetricsFooter, { MetricItem, ProgressMetric } from '../../components/dashboard/ModuleMetricsFooter'; import ThreeWidgetFooter from '../../components/dashboard/ThreeWidgetFooter';
export default function Clusters() { export default function Clusters() {
const toast = useToast(); const toast = useToast();
@@ -486,37 +486,81 @@ export default function Clusters() {
}} }}
/> />
{/* Module Metrics Footer - Pipeline Style with Cross-Module Links */} {/* Three Widget Footer - Section 3 Layout */}
<ModuleMetricsFooter <ThreeWidgetFooter
metrics={[ submoduleColor="green"
{ pageProgress={{
title: 'Keywords', title: 'Page Progress',
value: clusters.reduce((sum, c) => sum + (c.keywords_count || 0), 0).toLocaleString(), submoduleColor: 'green',
subtitle: `in ${totalCount} clusters`, metrics: [
icon: <ListIcon className="w-5 h-5" />, { label: 'Clusters', value: totalCount },
accentColor: 'blue', { label: 'With Ideas', value: clusters.filter(c => (c.ideas_count || 0) > 0).length, percentage: `${totalCount > 0 ? Math.round((clusters.filter(c => (c.ideas_count || 0) > 0).length / totalCount) * 100) : 0}%` },
href: '/planner/keywords', { 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',
}, },
{ hint: clusters.filter(c => (c.ideas_count || 0) === 0).length > 0
title: 'Content Ideas', ? `${clusters.filter(c => (c.ideas_count || 0) === 0).length} clusters ready for idea generation`
value: clusters.reduce((sum, c) => sum + (c.ideas_count || 0), 0).toLocaleString(), : 'All clusters have ideas!',
subtitle: `across ${clusters.filter(c => (c.ideas_count || 0) > 0).length} clusters`, }}
icon: <BoltIcon className="w-5 h-5" />, moduleStats={{
accentColor: 'green', title: 'Planner Module',
href: '/planner/ideas', pipeline: [
}, {
{ fromLabel: 'Keywords',
title: 'Ready to Write', fromValue: clusters.reduce((sum, c) => sum + (c.keywords_count || 0), 0),
value: clusters.filter(c => (c.ideas_count || 0) > 0 && c.status === 'active').length.toLocaleString(), fromHref: '/planner/keywords',
subtitle: 'clusters with approved ideas', actionLabel: 'Auto Cluster',
icon: <GroupIcon className="w-5 h-5" />, toLabel: 'Clusters',
accentColor: 'purple', toValue: totalCount,
}, progress: 100,
]} color: 'blue',
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, fromLabel: 'Clusters',
color: 'purple', 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',
}} }}
/> />

View File

@@ -29,7 +29,7 @@ import { createIdeasPageConfig } from '../../config/pages/ideas.config';
import { useSectorStore } from '../../store/sectorStore'; import { useSectorStore } from '../../store/sectorStore';
import { usePageSizeStore } from '../../store/pageSizeStore'; import { usePageSizeStore } from '../../store/pageSizeStore';
import PageHeader from '../../components/common/PageHeader'; import PageHeader from '../../components/common/PageHeader';
import ModuleMetricsFooter, { MetricItem, ProgressMetric } from '../../components/dashboard/ModuleMetricsFooter'; import ThreeWidgetFooter from '../../components/dashboard/ThreeWidgetFooter';
export default function Ideas() { export default function Ideas() {
const toast = useToast(); const toast = useToast();
@@ -414,45 +414,81 @@ export default function Ideas() {
}} }}
/> />
{/* Module Metrics Footer - Pipeline Style with Cross-Module Links */} {/* Three Widget Footer - Section 3 Layout */}
<ModuleMetricsFooter <ThreeWidgetFooter
metrics={[ submoduleColor="amber"
{ pageProgress={{
title: 'Clusters', title: 'Page Progress',
value: clusters.length.toLocaleString(), submoduleColor: 'amber',
subtitle: 'keyword groups', metrics: [
icon: <GroupIcon className="w-5 h-5" />, { label: 'Ideas', value: totalCount },
accentColor: 'purple', { label: 'In Tasks', value: ideas.filter(i => 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}%` },
href: '/planner/clusters', { 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',
}, },
{ hint: ideas.filter(i => i.status === 'new').length > 0
title: 'Ready to Queue', ? `${ideas.filter(i => i.status === 'new').length} ideas ready to become tasks`
value: ideas.filter(i => i.status === 'new').length.toLocaleString(), : 'All ideas converted!',
subtitle: 'awaiting approval', }}
icon: <BoltIcon className="w-5 h-5" />, moduleStats={{
accentColor: 'orange', title: 'Planner Module',
}, pipeline: [
{ {
title: 'In Queue', fromLabel: 'Keywords',
value: ideas.filter(i => i.status === 'queued').length.toLocaleString(), fromValue: clusters.reduce((sum, c) => sum + (c.keywords_count || 0), 0),
subtitle: 'ready for tasks', fromHref: '/planner/keywords',
icon: <ListIcon className="w-5 h-5" />, actionLabel: 'Auto Cluster',
accentColor: 'blue', toLabel: 'Clusters',
href: '/writer/tasks', toValue: clusters.length,
}, toHref: '/planner/clusters',
{ progress: 100,
title: 'Content Created', color: 'blue',
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: <BoltIcon className="w-5 h-5" />, fromLabel: 'Clusters',
accentColor: 'green', fromValue: clusters.length,
href: '/writer/content', fromHref: '/planner/clusters',
}, actionLabel: 'Generate Ideas',
]} toLabel: 'Ideas',
progress={{ toValue: totalCount,
label: 'Idea-to-Content Pipeline: Ideas successfully converted into written content', progress: 100,
value: totalCount > 0 ? Math.round((ideas.filter(i => i.status === 'completed').length / totalCount) * 100) : 0, color: 'green',
color: 'success', },
{
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',
}} }}
/> />

View File

@@ -25,7 +25,7 @@ import { useSiteStore } from '../../store/siteStore';
import { useSectorStore } from '../../store/sectorStore'; import { useSectorStore } from '../../store/sectorStore';
import { usePageSizeStore } from '../../store/pageSizeStore'; import { usePageSizeStore } from '../../store/pageSizeStore';
import PageHeader from '../../components/common/PageHeader'; 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 { getDifficultyLabelFromNumber, getDifficultyRange } from '../../utils/difficulty';
import FormModal from '../../components/common/FormModal'; import FormModal from '../../components/common/FormModal';
import ProgressModal from '../../components/common/ProgressModal'; import ProgressModal from '../../components/common/ProgressModal';
@@ -704,37 +704,84 @@ export default function Keywords() {
}} }}
/> />
{/* Module Metrics Footer */} {/* Three Widget Footer - Section 3 Layout */}
<ModuleMetricsFooter <ThreeWidgetFooter
metrics={[ submoduleColor="blue"
{ pageProgress={{
title: 'Keywords', title: 'Page Progress',
value: totalCount.toLocaleString(), submoduleColor: 'blue',
subtitle: `in ${clusters.length} clusters`, metrics: [
icon: <ListIcon className="w-5 h-5" />, { label: 'Keywords', value: totalCount },
accentColor: 'blue', { label: 'Clustered', value: keywords.filter(k => k.cluster_id).length, percentage: `${totalCount > 0 ? Math.round((keywords.filter(k => k.cluster_id).length / totalCount) * 100) : 0}%` },
href: '/planner/keywords', { 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',
}, },
{ hint: keywords.filter(k => !k.cluster_id).length > 0
title: 'Clustered', ? `${keywords.filter(k => !k.cluster_id).length} keywords ready to cluster`
value: keywords.filter(k => k.cluster_id).length.toLocaleString(), : 'All keywords clustered!',
subtitle: `${Math.round((keywords.filter(k => k.cluster_id).length / Math.max(totalCount, 1)) * 100)}% organized`, }}
icon: <GroupIcon className="w-5 h-5" />, moduleStats={{
accentColor: 'purple', title: 'Planner Module',
href: '/planner/clusters', pipeline: [
}, {
{ fromLabel: 'Keywords',
title: 'Easy Wins', fromValue: totalCount,
value: keywords.filter(k => k.difficulty && k.difficulty <= 3 && (k.volume || 0) > 0).length.toLocaleString(), actionLabel: 'Auto Cluster',
subtitle: `Low difficulty with ${keywords.filter(k => k.difficulty && k.difficulty <= 3).reduce((sum, k) => sum + (k.volume || 0), 0).toLocaleString()} volume`, toLabel: 'Clusters',
icon: <BoltIcon className="w-5 h-5" />, toValue: clusters.length,
accentColor: 'green', toHref: '/planner/clusters',
}, progress: totalCount > 0 ? Math.round((keywords.filter(k => k.cluster_id).length / totalCount) * 100) : 0,
]} color: 'blue',
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, fromLabel: 'Clusters',
color: 'primary', 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',
}} }}
/> />

View File

@@ -23,7 +23,7 @@ import { usePageSizeStore } from '../../store/pageSizeStore';
import ProgressModal from '../../components/common/ProgressModal'; import ProgressModal from '../../components/common/ProgressModal';
import { useProgressModal } from '../../hooks/useProgressModal'; import { useProgressModal } from '../../hooks/useProgressModal';
import PageHeader from '../../components/common/PageHeader'; 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'; import { PencilSquareIcon } from '@heroicons/react/24/outline';
export default function Content() { export default function Content() {
@@ -275,45 +275,82 @@ export default function Content() {
getItemDisplayName={(row: ContentType) => row.title || `Content #${row.id}`} getItemDisplayName={(row: ContentType) => row.title || `Content #${row.id}`}
/> />
{/* Module Metrics Footer - Pipeline Style with Cross-Module Links */} {/* Three Widget Footer - Section 3 Layout */}
<ModuleMetricsFooter <ThreeWidgetFooter
metrics={[ submoduleColor="blue"
{ pageProgress={{
title: 'Tasks', title: 'Page Progress',
value: content.length.toLocaleString(), submoduleColor: 'blue',
subtitle: 'generated from queue', metrics: [
icon: <TaskIcon className="w-5 h-5" />, { label: 'Drafts', value: content.filter(c => c.status === 'draft').length },
accentColor: 'blue', { 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}%` },
href: '/writer/tasks', { 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',
}, },
{ hint: content.filter(c => c.status === 'draft' && !c.has_generated_images).length > 0
title: 'Draft', ? `${content.filter(c => c.status === 'draft' && !c.has_generated_images).length} drafts need images before review`
value: content.filter(c => c.status === 'draft').length.toLocaleString(), : 'All drafts have images!',
subtitle: 'needs editing', }}
icon: <FileIcon className="w-5 h-5" />, moduleStats={{
accentColor: 'amber', title: 'Writer Module',
}, pipeline: [
{ {
title: 'In Review', fromLabel: 'Tasks',
value: content.filter(c => c.status === 'review').length.toLocaleString(), fromValue: totalCount,
subtitle: 'awaiting approval', fromHref: '/writer/tasks',
icon: <CheckCircleIcon className="w-5 h-5" />, actionLabel: 'Generate Content',
accentColor: 'blue', toLabel: 'Drafts',
href: '/writer/review', toValue: content.filter(c => c.status === 'draft').length,
}, progress: 100,
{ color: 'blue',
title: 'Published', },
value: content.filter(c => c.status === 'published').length.toLocaleString(), {
subtitle: 'ready for sync', fromLabel: 'Drafts',
icon: <CheckCircleIcon className="w-5 h-5" />, fromValue: content.filter(c => c.status === 'draft').length,
accentColor: 'green', actionLabel: 'Generate Images',
href: '/writer/published', toLabel: 'Images',
}, toValue: content.filter(c => c.has_generated_images).length,
]} toHref: '/writer/images',
progress={{ 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,
label: 'Content Publishing Pipeline: Content moved from draft through review to published (Draft \u2192 Review \u2192 Published)', color: 'purple',
value: totalCount > 0 ? Math.round((content.filter(c => c.status === 'published').length / totalCount) * 100) : 0, },
color: 'success', {
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',
}} }}
/> />

View File

@@ -30,7 +30,7 @@ import { createTasksPageConfig } from '../../config/pages/tasks.config';
import { useSectorStore } from '../../store/sectorStore'; import { useSectorStore } from '../../store/sectorStore';
import { usePageSizeStore } from '../../store/pageSizeStore'; import { usePageSizeStore } from '../../store/pageSizeStore';
import PageHeader from '../../components/common/PageHeader'; 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'; import { DocumentTextIcon } from '@heroicons/react/24/outline';
export default function Tasks() { export default function Tasks() {
@@ -467,44 +467,83 @@ export default function Tasks() {
}} }}
/> />
{/* Module Metrics Footer - Pipeline Style with Cross-Module Links */} {/* Three Widget Footer - Section 3 Layout */}
<ModuleMetricsFooter <ThreeWidgetFooter
metrics={[ submoduleColor="blue"
{ pageProgress={{
title: 'Ideas', title: 'Page Progress',
value: clusters.reduce((sum, c) => sum + (c.ideas_count || 0), 0).toLocaleString(), submoduleColor: 'blue',
subtitle: 'from planner', metrics: [
icon: <TaskIcon className="w-5 h-5" />, { label: 'Total', value: totalCount },
accentColor: 'orange', { label: 'Complete', value: tasks.filter(t => t.status === 'completed').length, percentage: `${totalCount > 0 ? Math.round((tasks.filter(t => t.status === 'completed').length / totalCount) * 100) : 0}%` },
href: '/planner/ideas', { 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',
}, },
{ hint: tasks.filter(t => t.status === 'queued').length > 0
title: 'In Queue', ? `${tasks.filter(t => t.status === 'queued').length} tasks in queue for content generation`
value: tasks.filter(t => t.status === 'queued').length.toLocaleString(), : 'All tasks processed!',
subtitle: 'waiting for processing', }}
icon: <TaskIcon className="w-5 h-5" />, moduleStats={{
accentColor: 'amber', title: 'Writer Module',
}, pipeline: [
{ {
title: 'Processing', fromLabel: 'Tasks',
value: tasks.filter(t => t.status === 'in_progress').length.toLocaleString(), fromValue: totalCount,
subtitle: 'generating content', actionLabel: 'Generate Content',
icon: <TaskIcon className="w-5 h-5" />, toLabel: 'Drafts',
accentColor: 'blue', 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,
title: 'Ready for Review', color: 'blue',
value: tasks.filter(t => t.status === 'completed').length.toLocaleString(), },
subtitle: 'content generated', {
icon: <CheckCircleIcon className="w-5 h-5" />, fromLabel: 'Drafts',
accentColor: 'green', fromValue: tasks.filter(t => t.status === 'completed').length,
href: '/writer/content', fromHref: '/writer/content',
}, actionLabel: 'Generate Images',
]} toLabel: 'Images',
progress={{ toValue: 0,
label: 'Content Generation Pipeline: Tasks successfully completed (Queued → Processing → Completed)', toHref: '/writer/images',
value: totalCount > 0 ? Math.round((tasks.filter(t => t.status === 'completed').length / totalCount) * 100) : 0, progress: 0,
color: 'success', 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',
}} }}
/> />