Files
igny8/frontend/src/components/dashboard/StandardThreeWidgetFooter.tsx
IGNY8 VPS (Salman) 75deda304e reanme purple to info
2026-01-24 15:27:51 +00:00

298 lines
12 KiB
TypeScript

/**
* StandardThreeWidgetFooter - Enhanced 3-Column Layout with Standardized Widgets
*
* This component provides a consistent 3-widget footer layout:
* - Widget 1: Page Progress (page-specific, passed as prop)
* - Widget 2: Module Stats (standardized via useModuleStats hook)
* - Widget 3: Workflow Completion (standardized via useWorkflowStats hook)
*
* Use this component for consistent data across all Planner and Writer module pages.
*/
import React from 'react';
import { Card } from '../ui/card/Card';
import { Link } from 'react-router-dom';
import { LightBulbIcon, ChevronRightIcon } from '@heroicons/react/24/solid';
import WorkflowCompletionWidget from './WorkflowCompletionWidget';
import StandardizedModuleWidget, { ModuleType } from './StandardizedModuleWidget';
// ============================================================================
// TYPE DEFINITIONS (moved from ThreeWidgetFooter.tsx)
// ============================================================================
/** 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;
/** Optional credits consumed for AI functions on this page */
creditsConsumed?: number;
/** Contextual status insight - guidance message based on current state */
statusInsight?: string;
}
/** 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 }>;
}
// ============================================================================
// TYPE DEFINITIONS
// ============================================================================
export interface StandardThreeWidgetFooterProps {
pageProgress: PageProgressWidget;
/** @deprecated Use `module` prop instead for standardized data. This is kept for backward compatibility. */
moduleStats?: ModuleStatsWidget;
/** Module type for standardized module stats widget */
module?: ModuleType;
submoduleColor?: SubmoduleColor;
/** Show credits consumption in workflow widget */
showCredits?: boolean;
/** Analytics href for the workflow widget */
analyticsHref?: string;
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-info)',
};
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 min-w-0 overflow-hidden">
{/* 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>
{/* Credits consumed (for AI function pages) */}
{widget.creditsConsumed !== undefined && widget.creditsConsumed > 0 && (
<div className="flex items-center justify-between pt-3 border-t border-[color:var(--color-stroke)] dark:border-gray-800 mb-3">
<span className="text-sm font-medium text-[color:var(--color-text-dim)] dark:text-gray-400">Credits Consumed</span>
<span className="text-sm font-bold" style={{ color: 'var(--color-warning)' }}>{widget.creditsConsumed.toLocaleString()}</span>
</div>
)}
{/* Hint with icon */}
{widget.hint && (
<div className={`flex items-start gap-2 ${widget.creditsConsumed === undefined || widget.creditsConsumed === 0 ? '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 text-warning-500" />
<span className="text-sm font-medium text-brand-500">{widget.hint}</span>
</div>
)}
{/* Status Insight - contextual guidance */}
{widget.statusInsight && (
<div className="mt-3 pt-3 border-t border-[color:var(--color-stroke)] dark:border-gray-800">
<p className="text-xs text-[color:var(--color-text-dim)] dark:text-gray-400 leading-relaxed">
<span className="font-semibold" style={{ color: 'var(--color-primary)' }}>Next:</span>{' '}
{widget.statusInsight}
</p>
</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 min-w-0 overflow-hidden">
{/* 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 text-brand-500"
>
{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 text-brand-500">
{row.fromValue}
</span>
</div>
{/* Arrow icon */}
<ChevronRightIcon
className="w-6 h-6 flex-shrink-0 mx-2 text-brand-500"
/>
{/* To side */}
<div className="flex items-center gap-2">
{row.toHref ? (
<Link
to={row.toHref}
className="text-sm font-medium hover:underline text-brand-500"
>
{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 text-brand-500"
>
<ChevronRightIcon className="w-4 h-4" />
<span>{link.label}</span>
</Link>
))}
</div>
</Card>
);
}
// ============================================================================
// MAIN COMPONENT
// ============================================================================
export default function StandardThreeWidgetFooter({
pageProgress,
moduleStats,
module,
submoduleColor = 'blue',
showCredits = true,
analyticsHref = '/account/usage',
className = '',
}: StandardThreeWidgetFooterProps) {
// Determine which module to show - prefer explicit module prop
const moduleType: ModuleType = module || (moduleStats?.title?.toLowerCase().includes('planner') ? 'planner' : 'writer');
return (
<div className={`pt-4 border-t border-gray-200 dark:border-gray-700 w-full max-w-full overflow-hidden ${className}`}>
{/*
* Widget widths adjusted per requirements:
* - Widget 1 (Page Progress): 28.3% (reduced by 5%)
* - Widget 2 (Module Stats): 28.3% (reduced by 5%)
* - Widget 3 (Workflow Completion): 43.4% (increased by 10%)
* Using fr units to prevent overflow
*/}
<div className="grid grid-cols-1 lg:grid-cols-[minmax(0,28.3fr)_minmax(0,28.3fr)_minmax(0,43.4fr)] gap-4 w-full">
<PageProgressCard widget={pageProgress} submoduleColor={submoduleColor} />
{/* Use standardized module widget for consistent data */}
<StandardizedModuleWidget module={moduleType} />
<WorkflowCompletionWidget
showCredits={showCredits}
analyticsHref={analyticsHref}
/>
</div>
</div>
);
}
// Export sub-components
export { PageProgressCard, ModuleStatsCard };