This commit is contained in:
IGNY8 VPS (Salman)
2025-12-31 23:52:58 +00:00
parent 89b64cd737
commit a02e485f7d

View File

@@ -0,0 +1,387 @@
/**
* 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 text-warning-500" />
<span className="text-sm font-medium text-brand-500">{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 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>
);
}
// ============================================================================
// 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 text-brand-500">
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 text-success-500">
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 text-brand-500"
>
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}`}>
{/*
* 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%)
*/}
<div className="grid grid-cols-1 lg:grid-cols-[28.3%_28.3%_43.4%] 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 };