frontend-refactor-1
This commit is contained in:
85
frontend/src/components/dashboard/ModuleMetricsFooter.tsx
Normal file
85
frontend/src/components/dashboard/ModuleMetricsFooter.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* ModuleMetricsFooter - Compact metrics footer for table pages
|
||||
* Shows module-specific metrics at the bottom of table pages
|
||||
* Uses standard EnhancedMetricCard and ProgressBar components
|
||||
* Follows standard app design system and color scheme
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import EnhancedMetricCard, { MetricCardProps } from './EnhancedMetricCard';
|
||||
import { ProgressBar } from '../ui/progress';
|
||||
|
||||
export interface MetricItem {
|
||||
title: string;
|
||||
value: string | number;
|
||||
subtitle?: string;
|
||||
icon: React.ReactNode;
|
||||
accentColor: MetricCardProps['accentColor'];
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export interface ProgressMetric {
|
||||
label: string;
|
||||
value: number; // 0-100
|
||||
color?: 'primary' | 'success' | 'warning' | 'purple';
|
||||
}
|
||||
|
||||
interface ModuleMetricsFooterProps {
|
||||
metrics: MetricItem[];
|
||||
progress?: ProgressMetric;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function ModuleMetricsFooter({
|
||||
metrics,
|
||||
progress,
|
||||
className = ''
|
||||
}: ModuleMetricsFooterProps) {
|
||||
if (metrics.length === 0 && !progress) return null;
|
||||
|
||||
const progressColors = {
|
||||
primary: 'bg-[var(--color-primary)]',
|
||||
success: 'bg-[var(--color-success)]',
|
||||
warning: 'bg-[var(--color-warning)]',
|
||||
purple: 'bg-[var(--color-purple)]',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`mt-8 pt-6 border-t border-gray-200 dark:border-gray-800 ${className}`}>
|
||||
<div className="space-y-4">
|
||||
{/* Metrics Grid */}
|
||||
{metrics.length > 0 && (
|
||||
<div className={`grid grid-cols-1 sm:grid-cols-2 ${metrics.length > 2 ? 'lg:grid-cols-3' : 'lg:grid-cols-2'} ${metrics.length > 3 ? 'xl:grid-cols-4' : ''} gap-4`}>
|
||||
{metrics.map((metric, index) => (
|
||||
<EnhancedMetricCard
|
||||
key={index}
|
||||
title={metric.title}
|
||||
value={metric.value}
|
||||
subtitle={metric.subtitle}
|
||||
icon={metric.icon}
|
||||
accentColor={metric.accentColor}
|
||||
href={metric.href}
|
||||
onClick={metric.onClick}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Progress Bar */}
|
||||
{progress && (
|
||||
<div className="space-y-2">
|
||||
<ProgressBar
|
||||
value={progress.value}
|
||||
color={progress.color === 'success' ? 'success' : progress.color === 'warning' ? 'warning' : 'primary'}
|
||||
size="md"
|
||||
showLabel={true}
|
||||
label={progress.label}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
66
frontend/src/components/navigation/ModuleNavigationTabs.tsx
Normal file
66
frontend/src/components/navigation/ModuleNavigationTabs.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* ModuleNavigationTabs - Reusable in-page tab navigation component
|
||||
* Used across Planner, Writer, Linker, Optimizer, Thinker, Automation, Sites modules
|
||||
* Follows standard app design system and color scheme
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Link, useLocation } from 'react-router';
|
||||
import { Tabs, TabList, Tab } from '../ui/tabs/Tabs';
|
||||
|
||||
export interface NavigationTab {
|
||||
label: string;
|
||||
path: string;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
interface ModuleNavigationTabsProps {
|
||||
tabs: NavigationTab[];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function ModuleNavigationTabs({ tabs, className = '' }: ModuleNavigationTabsProps) {
|
||||
const location = useLocation();
|
||||
|
||||
// Find active tab based on current path
|
||||
const activeTab = tabs.find(tab => {
|
||||
if (tab.path === location.pathname) return true;
|
||||
// Handle nested routes (e.g., /planner/keywords/123 should match /planner/keywords)
|
||||
return location.pathname.startsWith(tab.path + '/');
|
||||
});
|
||||
|
||||
const activeTabId = activeTab?.path || tabs[0]?.path || '';
|
||||
|
||||
return (
|
||||
<div className={`mb-6 ${className}`}>
|
||||
<Tabs defaultTab={activeTabId}>
|
||||
{(activeTabId, setActiveTab) => (
|
||||
<TabList className="bg-gray-100 dark:bg-gray-900">
|
||||
{tabs.map((tab) => {
|
||||
const isActive = activeTabId === tab.path || location.pathname.startsWith(tab.path + '/');
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={tab.path}
|
||||
to={tab.path}
|
||||
onClick={() => setActiveTab(tab.path)}
|
||||
className="flex-1"
|
||||
>
|
||||
<Tab
|
||||
tabId={tab.path}
|
||||
isActive={isActive}
|
||||
className="flex items-center justify-center gap-2"
|
||||
>
|
||||
{tab.icon && <span className="flex-shrink-0">{tab.icon}</span>}
|
||||
<span>{tab.label}</span>
|
||||
</Tab>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</TabList>
|
||||
)}
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user