Add ThreeWidgetFooter component and hook for 3-column table footer layout

- ThreeWidgetFooter.tsx: 3-column layout matching Section 3 of audit report
  - Widget 1: Page Progress (current page metrics + progress bar + hint)
  - Widget 2: Module Stats (workflow pipeline with progress bars)
  - Widget 3: Completion (both Planner/Writer stats + credits)
- useThreeWidgetFooter.ts: Hook to build widget props from data
  - Builds page progress for Keywords, Clusters, Ideas, Tasks, Content
  - Builds Planner/Writer module pipelines
  - Calculates completion stats from data

Uses CSS tokens from styles/tokens.css for consistent styling
This commit is contained in:
IGNY8 VPS (Salman)
2025-12-27 17:51:46 +00:00
parent 24cdb4fdf9
commit a145e6742e
2 changed files with 775 additions and 0 deletions

View File

@@ -0,0 +1,385 @@
/**
* ThreeWidgetFooter - 3-Column Layout for Table Page Footers
*
* Design from Section 3 of COMPREHENSIVE-AUDIT-REPORT.md:
* ┌─────────────────────────────────────────────────────────────────────────────────────┐
* │ WIDGET 1: PAGE METRICS │ WIDGET 2: MODULE STATS │ WIDGET 3: COMPLETION │
* │ (Current Page Progress) │ (Full Module Overview) │ (Both Modules Stats) │
* │ ~33.3% width │ ~33.3% width │ ~33.3% width │
* └─────────────────────────────────────────────────────────────────────────────────────┘
*
* STYLING: Uses CSS tokens from styles/tokens.css:
* - --color-primary: Brand blue for primary actions/bars
* - --color-success: Green for success states
* - --color-warning: Amber for warnings
* - --color-purple: Purple accent
*/
import React from 'react';
import { Link } from 'react-router-dom';
import { Card } from '../ui/card/Card';
import { LightBulbIcon, ChevronRightIcon } from '@heroicons/react/24/solid';
// ============================================================================
// TYPE DEFINITIONS
// ============================================================================
/** Submodule color type - matches headerMetrics accentColor */
export type SubmoduleColor = 'blue' | 'green' | 'amber' | 'purple';
/** Widget 1: Page Progress - metrics in 2x2 grid + progress bar + hint */
export interface PageProgressWidget {
title: string;
metrics: Array<{ label: string; value: string | number; percentage?: string }>;
progress: { value: number; label: string; color?: SubmoduleColor };
hint?: string;
/** The submodule's accent color - progress bar uses this */
submoduleColor?: SubmoduleColor;
}
/** Widget 2: Module Stats - Pipeline flow with arrows and progress bars */
export interface ModulePipelineRow {
fromLabel: string;
fromValue: number;
fromHref?: string;
actionLabel: string;
toLabel: string;
toValue: number;
toHref?: string;
progress: number; // 0-100
/** Color for this pipeline row's progress bar */
color?: SubmoduleColor;
}
export interface ModuleStatsWidget {
title: string;
pipeline: ModulePipelineRow[];
links: Array<{ label: string; href: string }>;
}
/** Widget 3: Completion - Tree structure with bars for both modules */
export interface CompletionItem {
label: string;
value: number;
color?: SubmoduleColor;
}
export interface CompletionWidget {
title: string;
plannerItems: CompletionItem[];
writerItems: CompletionItem[];
creditsUsed?: number;
operationsCount?: number;
analyticsHref?: string;
}
/** Main component props */
export interface ThreeWidgetFooterProps {
pageProgress: PageProgressWidget;
moduleStats: ModuleStatsWidget;
completion: CompletionWidget;
submoduleColor?: SubmoduleColor;
className?: string;
}
// ============================================================================
// COLOR UTILITIES
// ============================================================================
const getProgressBarStyle = (color: SubmoduleColor = 'blue'): React.CSSProperties => {
const colorMap: Record<SubmoduleColor, string> = {
blue: 'var(--color-primary)',
green: 'var(--color-success)',
amber: 'var(--color-warning)',
purple: 'var(--color-purple)',
};
return { backgroundColor: colorMap[color] };
};
// ============================================================================
// WIDGET 1: PAGE PROGRESS
// ============================================================================
function PageProgressCard({ widget, submoduleColor = 'blue' }: { widget: PageProgressWidget; submoduleColor?: SubmoduleColor }) {
const progressColor = widget.submoduleColor || widget.progress.color || submoduleColor;
return (
<Card className="p-5 bg-white dark:bg-gray-900 border border-[color:var(--color-stroke)] dark:border-gray-700">
{/* Header */}
<h3 className="text-sm font-semibold text-[color:var(--color-text)] dark:text-gray-200 uppercase tracking-wide mb-4">
{widget.title}
</h3>
{/* 2x2 Metrics Grid */}
<div className="grid grid-cols-2 gap-x-6 gap-y-3 mb-5">
{widget.metrics.slice(0, 4).map((metric, idx) => (
<div key={idx} className="flex items-baseline justify-between">
<span className="text-sm text-[color:var(--color-text-dim)] dark:text-gray-400">{metric.label}</span>
<div className="flex items-baseline gap-1.5">
<span className="text-lg font-bold text-[color:var(--color-text)] dark:text-white tabular-nums">
{typeof metric.value === 'number' ? metric.value.toLocaleString() : metric.value}
</span>
{metric.percentage && (
<span className="text-xs text-[color:var(--color-text-dim)] dark:text-gray-400">({metric.percentage})</span>
)}
</div>
</div>
))}
</div>
{/* Progress Bar */}
<div className="mb-4">
<div className="h-3 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
<div
className="h-full rounded-full transition-all duration-500"
style={{
...getProgressBarStyle(progressColor),
width: `${Math.min(100, Math.max(0, widget.progress.value))}%`
}}
/>
</div>
<div className="flex justify-between items-center mt-2">
<span className="text-sm font-medium text-[color:var(--color-text)] dark:text-gray-300">{widget.progress.label}</span>
<span className="text-sm font-bold text-[color:var(--color-text)] dark:text-white">{widget.progress.value}%</span>
</div>
</div>
{/* Hint with icon */}
{widget.hint && (
<div className="flex items-start gap-2 pt-3 border-t border-[color:var(--color-stroke)] dark:border-gray-800">
<LightBulbIcon className="w-5 h-5 flex-shrink-0 mt-0.5" style={{ color: 'var(--color-warning)' }} />
<span className="text-sm font-medium" style={{ color: 'var(--color-primary)' }}>{widget.hint}</span>
</div>
)}
</Card>
);
}
// ============================================================================
// WIDGET 2: MODULE STATS
// ============================================================================
function ModuleStatsCard({ widget }: { widget: ModuleStatsWidget }) {
return (
<Card className="p-5 bg-white dark:bg-gray-900 border border-[color:var(--color-stroke)] dark:border-gray-700">
{/* Header */}
<h3 className="text-sm font-semibold text-[color:var(--color-text)] dark:text-gray-200 uppercase tracking-wide mb-4">
{widget.title}
</h3>
{/* Pipeline Rows */}
<div className="space-y-4 mb-4">
{widget.pipeline.map((row, idx) => (
<div key={idx}>
{/* Row header: FromLabel Value ► ToLabel Value */}
<div className="flex items-center justify-between mb-2">
{/* From side */}
<div className="flex items-center gap-2">
{row.fromHref ? (
<Link
to={row.fromHref}
className="text-sm font-medium hover:underline"
style={{ color: 'var(--color-primary)' }}
>
{row.fromLabel}
</Link>
) : (
<span className="text-sm font-medium text-[color:var(--color-text)] dark:text-gray-300">{row.fromLabel}</span>
)}
<span className="text-lg font-bold tabular-nums" style={{ color: 'var(--color-primary)' }}>
{row.fromValue}
</span>
</div>
{/* Arrow icon */}
<ChevronRightIcon
className="w-6 h-6 flex-shrink-0 mx-2"
style={{ color: 'var(--color-primary)' }}
/>
{/* To side */}
<div className="flex items-center gap-2">
{row.toHref ? (
<Link
to={row.toHref}
className="text-sm font-medium hover:underline"
style={{ color: 'var(--color-primary)' }}
>
{row.toLabel}
</Link>
) : (
<span className="text-sm font-medium text-[color:var(--color-text)] dark:text-gray-300">{row.toLabel}</span>
)}
<span className="text-lg font-bold text-[color:var(--color-text)] dark:text-white tabular-nums">
{row.toValue}
</span>
</div>
</div>
{/* Progress bar */}
<div className="h-2.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
<div
className="h-full rounded-full transition-all duration-500"
style={{
...getProgressBarStyle(row.color || 'blue'),
width: `${Math.min(100, Math.max(0, row.progress))}%`
}}
/>
</div>
</div>
))}
</div>
{/* Navigation Links */}
<div className="flex flex-wrap gap-3 pt-3 border-t border-[color:var(--color-stroke)] dark:border-gray-800">
{widget.links.map((link, idx) => (
<Link
key={idx}
to={link.href}
className="text-sm font-medium hover:underline flex items-center gap-1"
style={{ color: 'var(--color-primary)' }}
>
<ChevronRightIcon className="w-4 h-4" />
<span>{link.label}</span>
</Link>
))}
</div>
</Card>
);
}
// ============================================================================
// WIDGET 3: COMPLETION
// ============================================================================
function CompletionCard({ widget }: { widget: CompletionWidget }) {
// Calculate max for proportional bars (across both columns)
const allValues = [...widget.plannerItems, ...widget.writerItems].map(i => i.value);
const maxValue = Math.max(...allValues, 1);
const renderItem = (item: CompletionItem, isLast: boolean) => {
const barWidth = (item.value / maxValue) * 100;
const prefix = isLast ? '└─' : '├─';
const color = item.color || 'blue';
return (
<div key={item.label} className="flex items-center gap-2 py-1">
{/* Tree prefix */}
<span className="text-[color:var(--color-text-dim)] dark:text-gray-500 font-mono text-xs w-5 flex-shrink-0">{prefix}</span>
{/* Label */}
<span className="text-sm text-[color:var(--color-text)] dark:text-gray-300 flex-1 truncate">{item.label}</span>
{/* Progress bar */}
<div className="w-16 h-2.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden flex-shrink-0">
<div
className="h-full rounded-full transition-all duration-500"
style={{
...getProgressBarStyle(color),
width: `${Math.min(100, barWidth)}%`
}}
/>
</div>
{/* Value */}
<span className="text-sm font-bold text-[color:var(--color-text)] dark:text-white tabular-nums w-10 text-right flex-shrink-0">
{item.value}
</span>
</div>
);
};
return (
<Card className="p-5 bg-white dark:bg-gray-900 border border-[color:var(--color-stroke)] dark:border-gray-700">
{/* Header */}
<h3 className="text-sm font-semibold text-[color:var(--color-text)] dark:text-gray-200 uppercase tracking-wide mb-4">
{widget.title}
</h3>
{/* Two-column layout: Planner | Writer */}
<div className="grid grid-cols-2 gap-6 mb-4">
{/* Planner Column */}
<div>
<div className="text-xs font-bold uppercase tracking-wide mb-2" style={{ color: 'var(--color-primary)' }}>
Planner
</div>
<div className="space-y-0.5">
{widget.plannerItems.map((item, idx) =>
renderItem(item, idx === widget.plannerItems.length - 1)
)}
</div>
</div>
{/* Writer Column */}
<div>
<div className="text-xs font-bold uppercase tracking-wide mb-2" style={{ color: 'var(--color-success)' }}>
Writer
</div>
<div className="space-y-0.5">
{widget.writerItems.map((item, idx) =>
renderItem(item, idx === widget.writerItems.length - 1)
)}
</div>
</div>
</div>
{/* Footer Stats - Credits Used & Operations */}
{(widget.creditsUsed !== undefined || widget.operationsCount !== undefined) && (
<div className="flex items-center gap-4 pt-3 border-t border-[color:var(--color-stroke)] dark:border-gray-800 text-sm">
{widget.creditsUsed !== undefined && (
<span className="text-[color:var(--color-text-dim)] dark:text-gray-400">
Credits Used: <strong className="text-[color:var(--color-text)] dark:text-white font-bold">{widget.creditsUsed.toLocaleString()}</strong>
</span>
)}
{widget.creditsUsed !== undefined && widget.operationsCount !== undefined && (
<span className="text-[color:var(--color-stroke)] dark:text-gray-600"></span>
)}
{widget.operationsCount !== undefined && (
<span className="text-[color:var(--color-text-dim)] dark:text-gray-400">
Operations: <strong className="text-[color:var(--color-text)] dark:text-white font-bold">{widget.operationsCount}</strong>
</span>
)}
</div>
)}
{/* Analytics Link */}
{widget.analyticsHref && (
<div className="pt-3 mt-3 border-t border-[color:var(--color-stroke)] dark:border-gray-800">
<Link
to={widget.analyticsHref}
className="text-sm font-medium hover:underline flex items-center gap-1"
style={{ color: 'var(--color-primary)' }}
>
View Full Analytics
<ChevronRightIcon className="w-4 h-4" />
</Link>
</div>
)}
</Card>
);
}
// ============================================================================
// MAIN COMPONENT
// ============================================================================
export default function ThreeWidgetFooter({
pageProgress,
moduleStats,
completion,
submoduleColor = 'blue',
className = '',
}: ThreeWidgetFooterProps) {
return (
<div className={`mt-8 pt-6 border-t border-gray-200 dark:border-gray-700 ${className}`}>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
<PageProgressCard widget={pageProgress} submoduleColor={submoduleColor} />
<ModuleStatsCard widget={moduleStats} />
<CompletionCard widget={completion} />
</div>
</div>
);
}
// Also export sub-components for flexibility
export { PageProgressCard, ModuleStatsCard, CompletionCard };

View File

@@ -0,0 +1,390 @@
/**
* useThreeWidgetFooter - Hook to build ThreeWidgetFooter props
*
* Provides helper functions to construct the three widgets:
* - Page Progress (current page metrics)
* - Module Stats (workflow pipeline)
* - Completion Stats (both modules summary)
*
* Usage:
* const footerProps = useThreeWidgetFooter({
* module: 'planner',
* currentPage: 'keywords',
* plannerData: { keywords: [...], clusters: [...] },
* completionData: { ... }
* });
*/
import { useMemo } from 'react';
import type {
ThreeWidgetFooterProps,
PageProgressWidget,
ModuleStatsWidget,
CompletionWidget,
SubmoduleColor,
} from '../components/dashboard/ThreeWidgetFooter';
// ============================================================================
// DATA INTERFACES
// ============================================================================
interface PlannerPageData {
keywords?: Array<{ cluster_id?: number | null; volume?: number }>;
clusters?: Array<{ ideas_count?: number; keywords_count?: number }>;
ideas?: Array<{ status?: string }>;
totalKeywords?: number;
totalClusters?: number;
totalIdeas?: number;
}
interface WriterPageData {
tasks?: Array<{ status?: string }>;
content?: Array<{ status?: string; has_generated_images?: boolean }>;
totalTasks?: number;
totalContent?: number;
totalPublished?: number;
}
interface CompletionData {
keywordsClustered?: number;
clustersCreated?: number;
ideasGenerated?: number;
contentGenerated?: number;
imagesCreated?: number;
articlesPublished?: number;
creditsUsed?: number;
totalOperations?: number;
}
interface UseThreeWidgetFooterOptions {
module: 'planner' | 'writer';
currentPage: 'keywords' | 'clusters' | 'ideas' | 'tasks' | 'content' | 'images' | 'review' | 'published';
plannerData?: PlannerPageData;
writerData?: WriterPageData;
completionData?: CompletionData;
}
// ============================================================================
// PLANNER PAGE PROGRESS BUILDERS
// ============================================================================
function buildKeywordsPageProgress(data: PlannerPageData): PageProgressWidget {
const keywords = data.keywords || [];
const totalKeywords = data.totalKeywords || keywords.length;
const clusteredCount = keywords.filter(k => k.cluster_id).length;
const unmappedCount = keywords.filter(k => !k.cluster_id).length;
const totalVolume = keywords.reduce((sum, k) => sum + (k.volume || 0), 0);
const clusteredPercent = totalKeywords > 0 ? Math.round((clusteredCount / totalKeywords) * 100) : 0;
return {
title: 'Page Progress',
metrics: [
{ label: 'Keywords', value: totalKeywords },
{ label: 'Clustered', value: clusteredCount, percentage: `${clusteredPercent}%` },
{ label: 'Unmapped', value: unmappedCount },
{ label: 'Volume', value: totalVolume >= 1000 ? `${(totalVolume / 1000).toFixed(1)}K` : totalVolume },
],
progress: {
value: clusteredPercent,
label: `${clusteredPercent}% Clustered`,
color: clusteredPercent >= 80 ? 'green' : 'blue',
},
hint: unmappedCount > 0 ? `${unmappedCount} keywords ready to cluster` : 'All keywords clustered!',
};
}
function buildClustersPageProgress(data: PlannerPageData): PageProgressWidget {
const clusters = data.clusters || [];
const totalClusters = data.totalClusters || clusters.length;
const withIdeas = clusters.filter(c => (c.ideas_count || 0) > 0).length;
const totalKeywords = clusters.reduce((sum, c) => sum + (c.keywords_count || 0), 0);
const readyClusters = clusters.filter(c => (c.ideas_count || 0) === 0).length;
const ideasPercent = totalClusters > 0 ? Math.round((withIdeas / totalClusters) * 100) : 0;
return {
title: 'Page Progress',
metrics: [
{ label: 'Clusters', value: totalClusters },
{ label: 'With Ideas', value: withIdeas, percentage: `${ideasPercent}%` },
{ label: 'Keywords', value: totalKeywords },
{ label: 'Ready', value: readyClusters },
],
progress: {
value: ideasPercent,
label: `${ideasPercent}% Have Ideas`,
color: ideasPercent >= 70 ? 'green' : 'blue',
},
hint: readyClusters > 0 ? `${readyClusters} clusters ready for idea generation` : 'All clusters have ideas!',
};
}
function buildIdeasPageProgress(data: PlannerPageData): PageProgressWidget {
const ideas = data.ideas || [];
const totalIdeas = data.totalIdeas || ideas.length;
const inTasks = ideas.filter(i => i.status === 'completed' || i.status === 'queued').length;
const pending = ideas.filter(i => i.status === 'new').length;
const convertedPercent = totalIdeas > 0 ? Math.round((inTasks / totalIdeas) * 100) : 0;
return {
title: 'Page Progress',
metrics: [
{ label: 'Ideas', value: totalIdeas },
{ label: 'In Tasks', value: inTasks, percentage: `${convertedPercent}%` },
{ label: 'Pending', value: pending },
{ label: 'From Clusters', value: data.totalClusters || 0 },
],
progress: {
value: convertedPercent,
label: `${convertedPercent}% Converted`,
color: convertedPercent >= 60 ? 'green' : 'blue',
},
hint: pending > 0 ? `${pending} ideas ready to become tasks` : 'All ideas converted!',
};
}
// ============================================================================
// WRITER PAGE PROGRESS BUILDERS
// ============================================================================
function buildTasksPageProgress(data: WriterPageData): PageProgressWidget {
const tasks = data.tasks || [];
const total = data.totalTasks || tasks.length;
const completed = tasks.filter(t => t.status === 'completed').length;
const queue = tasks.filter(t => t.status === 'queued').length;
const processing = tasks.filter(t => t.status === 'in_progress').length;
const completedPercent = total > 0 ? Math.round((completed / total) * 100) : 0;
return {
title: 'Page Progress',
metrics: [
{ label: 'Total', value: total },
{ label: 'Complete', value: completed, percentage: `${completedPercent}%` },
{ label: 'Queue', value: queue },
{ label: 'Processing', value: processing },
],
progress: {
value: completedPercent,
label: `${completedPercent}% Generated`,
color: completedPercent >= 60 ? 'green' : 'blue',
},
hint: queue > 0 ? `${queue} tasks in queue for content generation` : 'All tasks processed!',
};
}
function buildContentPageProgress(data: WriterPageData): PageProgressWidget {
const content = data.content || [];
const drafts = content.filter(c => c.status === 'draft').length;
const hasImages = content.filter(c => c.has_generated_images).length;
const ready = content.filter(c => c.status === 'review' || c.status === 'published').length;
const imagesPercent = drafts > 0 ? Math.round((hasImages / drafts) * 100) : 0;
return {
title: 'Page Progress',
metrics: [
{ label: 'Drafts', value: drafts },
{ label: 'Has Images', value: hasImages, percentage: `${imagesPercent}%` },
{ label: 'Total Words', value: '—' }, // Would need word count from API
{ label: 'Ready', value: ready },
],
progress: {
value: imagesPercent,
label: `${imagesPercent}% Have Images`,
color: imagesPercent >= 70 ? 'green' : 'blue',
},
hint: drafts - hasImages > 0 ? `${drafts - hasImages} drafts need images before review` : 'All drafts have images!',
};
}
// ============================================================================
// MODULE STATS BUILDERS
// ============================================================================
function buildPlannerModuleStats(data: PlannerPageData): ModuleStatsWidget {
const keywords = data.keywords || [];
const clusters = data.clusters || [];
const ideas = data.ideas || [];
const totalKeywords = data.totalKeywords || keywords.length;
const totalClusters = data.totalClusters || clusters.length;
const totalIdeas = data.totalIdeas || ideas.length;
const clusteredKeywords = keywords.filter(k => k.cluster_id).length;
const clustersWithIdeas = clusters.filter(c => (c.ideas_count || 0) > 0).length;
const ideasInTasks = ideas.filter(i => i.status === 'completed' || i.status === 'queued').length;
return {
title: 'Planner Module',
pipeline: [
{
fromLabel: 'Keywords',
fromValue: totalKeywords,
toLabel: 'Clusters',
toValue: totalClusters,
actionLabel: 'Auto Cluster',
progress: totalKeywords > 0 ? Math.round((clusteredKeywords / totalKeywords) * 100) : 0,
color: 'blue',
},
{
fromLabel: 'Clusters',
fromValue: totalClusters,
toLabel: 'Ideas',
toValue: totalIdeas,
actionLabel: 'Generate Ideas',
progress: totalClusters > 0 ? Math.round((clustersWithIdeas / totalClusters) * 100) : 0,
color: 'green',
},
{
fromLabel: 'Ideas',
fromValue: totalIdeas,
toLabel: 'Tasks',
toValue: ideasInTasks,
actionLabel: 'Create Tasks',
progress: totalIdeas > 0 ? Math.round((ideasInTasks / totalIdeas) * 100) : 0,
color: 'amber',
},
],
links: [
{ label: 'Keywords', href: '/planner/keywords' },
{ label: 'Clusters', href: '/planner/clusters' },
{ label: 'Ideas', href: '/planner/ideas' },
],
};
}
function buildWriterModuleStats(data: WriterPageData): ModuleStatsWidget {
const tasks = data.tasks || [];
const content = data.content || [];
const totalTasks = data.totalTasks || tasks.length;
const completedTasks = tasks.filter(t => t.status === 'completed').length;
const drafts = content.filter(c => c.status === 'draft').length;
const withImages = content.filter(c => c.has_generated_images).length;
const ready = content.filter(c => c.status === 'review').length;
const published = data.totalPublished || content.filter(c => c.status === 'published').length;
return {
title: 'Writer Module',
pipeline: [
{
fromLabel: 'Tasks',
fromValue: totalTasks,
toLabel: 'Drafts',
toValue: drafts,
actionLabel: 'Generate Content',
progress: totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0,
color: 'blue',
},
{
fromLabel: 'Drafts',
fromValue: drafts,
toLabel: 'Images',
toValue: withImages,
actionLabel: 'Generate Images',
progress: drafts > 0 ? Math.round((withImages / drafts) * 100) : 0,
color: 'purple',
},
{
fromLabel: 'Ready',
fromValue: ready,
toLabel: 'Published',
toValue: published,
actionLabel: 'Review & Publish',
progress: ready > 0 ? Math.round((published / (ready + published)) * 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 STATS BUILDER
// ============================================================================
function buildCompletionStats(data: CompletionData): CompletionWidget {
return {
title: 'Workflow Completion',
plannerItems: [
{ label: 'Keywords Clustered', value: data.keywordsClustered || 0, color: 'blue' },
{ label: 'Clusters Created', value: data.clustersCreated || 0, color: 'green' },
{ label: 'Ideas Generated', value: data.ideasGenerated || 0, color: 'amber' },
],
writerItems: [
{ label: 'Content Generated', value: data.contentGenerated || 0, color: 'blue' },
{ label: 'Images Created', value: data.imagesCreated || 0, color: 'purple' },
{ label: 'Articles Published', value: data.articlesPublished || 0, color: 'green' },
],
creditsUsed: data.creditsUsed,
operationsCount: data.totalOperations,
analyticsHref: '/account/usage',
};
}
// ============================================================================
// MAIN HOOK
// ============================================================================
export function useThreeWidgetFooter(options: UseThreeWidgetFooterOptions): ThreeWidgetFooterProps {
const { module, currentPage, plannerData = {}, writerData = {}, completionData = {} } = options;
return useMemo(() => {
// Build page progress based on current page
let pageProgress: PageProgressWidget;
if (module === 'planner') {
switch (currentPage) {
case 'keywords':
pageProgress = buildKeywordsPageProgress(plannerData);
break;
case 'clusters':
pageProgress = buildClustersPageProgress(plannerData);
break;
case 'ideas':
pageProgress = buildIdeasPageProgress(plannerData);
break;
default:
pageProgress = buildKeywordsPageProgress(plannerData);
}
} else {
switch (currentPage) {
case 'tasks':
pageProgress = buildTasksPageProgress(writerData);
break;
case 'content':
case 'images':
case 'review':
pageProgress = buildContentPageProgress(writerData);
break;
default:
pageProgress = buildTasksPageProgress(writerData);
}
}
// Build module stats
const moduleStats = module === 'planner'
? buildPlannerModuleStats(plannerData)
: buildWriterModuleStats(writerData);
// Build completion stats
const completion = buildCompletionStats(completionData);
// Determine submodule color based on current page
let submoduleColor: SubmoduleColor = 'blue';
if (currentPage === 'clusters') submoduleColor = 'green';
if (currentPage === 'ideas') submoduleColor = 'amber';
if (currentPage === 'images') submoduleColor = 'purple';
return {
pageProgress,
moduleStats,
completion,
submoduleColor,
};
}, [module, currentPage, plannerData, writerData, completionData]);
}
export default useThreeWidgetFooter;