Files
igny8/frontend/src/components/dashboard/RecentActivityWidget.tsx
IGNY8 VPS (Salman) e2d462d8b6 Update dashboard and automation colors to new module scheme
Dashboard widgets:
- WorkflowPipelineWidget: Sites now has transparent bg with colored icon
- Tasks stage uses navy (gray-700/800), Content/Drafts use blue (brand)
- AIOperationsWidget: Content now uses blue (brand) instead of green
- RecentActivityWidget: Content activity now uses blue (brand)
- QuickActionsWidget: Tasks step uses navy, Content uses blue

Automation components:
- AutomationPage STAGE_CONFIG: Tasks→Content now navy, Content→Prompts blue
- GlobalProgressBar: Updated stage colors to match new scheme
- CurrentProcessingCard: Stage colors match new module scheme

Color scheme:
- Planner Pipeline (Blue → Pink → Amber): Keywords, Clusters, Ideas
- Writer Pipeline (Navy → Blue → Pink → Green): Tasks, Content, Images, Published
2026-01-03 00:52:18 +00:00

152 lines
5.5 KiB
TypeScript

/**
* RecentActivityWidget - Shows last 5 significant operations
* Displays AI task completions, publishing events, etc.
*/
import { Link } from 'react-router-dom';
import {
GroupIcon,
BoltIcon,
FileTextIcon,
FileIcon,
PaperPlaneIcon,
ListIcon,
AlertIcon,
CheckCircleIcon,
} from '../../icons';
export interface ActivityItem {
id: string;
type: 'clustering' | 'ideas' | 'content' | 'images' | 'published' | 'keywords' | 'error' | 'sync';
title: string;
description: string;
timestamp: Date;
href?: string;
success?: boolean;
}
interface RecentActivityWidgetProps {
activities: ActivityItem[];
loading?: boolean;
}
/**
* Activity config with solid colored backgrounds for visual distinction:
* - clustering: purple/pink (matches Clusters module)
* - ideas: warning/amber (matches Ideas module)
* - content: brand/primary (blue) - matches Writer content module
* - images: purple/pink (matches Images module)
* - published: success/green (matches Published module)
* - keywords: brand/primary (blue) - matches Keywords module
* - error: error/red
* - sync: success/green
*/
const activityConfig: Record<string, { icon: typeof GroupIcon; gradient: string }> = {
clustering: { icon: GroupIcon, gradient: 'from-purple-500 to-purple-600' },
ideas: { icon: BoltIcon, gradient: 'from-warning-500 to-warning-600' },
content: { icon: FileTextIcon, gradient: 'from-brand-500 to-brand-600' },
images: { icon: FileIcon, gradient: 'from-purple-500 to-purple-600' },
published: { icon: PaperPlaneIcon, gradient: 'from-success-500 to-success-600' },
keywords: { icon: ListIcon, gradient: 'from-brand-500 to-brand-600' },
error: { icon: AlertIcon, gradient: 'from-error-500 to-error-600' },
sync: { icon: CheckCircleIcon, gradient: 'from-success-500 to-success-600' },
};
// Default config for unknown activity types
const defaultActivityConfig = { icon: BoltIcon, gradient: 'from-gray-500 to-gray-600' };
function formatRelativeTime(date: Date): string {
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);
if (diffMins < 1) return 'Just now';
if (diffMins < 60) return `${diffMins}m ago`;
if (diffHours < 24) return `${diffHours}h ago`;
if (diffDays === 1) return 'Yesterday';
if (diffDays < 7) return `${diffDays}d ago`;
return date.toLocaleDateString();
}
export default function RecentActivityWidget({ activities, loading }: RecentActivityWidgetProps) {
return (
<div className="bg-white dark:bg-gray-900 rounded-xl border border-gray-200 dark:border-gray-800 p-5">
{/* Header */}
<h3 className="text-base font-semibold text-gray-800 dark:text-gray-200 uppercase tracking-wide mb-4">
Recent Activity
</h3>
{/* Activity List */}
<div className="space-y-3">
{loading ? (
// Loading skeleton
Array.from({ length: 5 }).map((_, i) => (
<div key={i} className="flex items-start gap-3 animate-pulse">
<div className="w-9 h-9 rounded-lg bg-gray-100 dark:bg-gray-800"></div>
<div className="flex-1">
<div className="h-4 w-3/4 bg-gray-100 dark:bg-gray-800 rounded mb-2"></div>
<div className="h-3 w-1/4 bg-gray-100 dark:bg-gray-800 rounded"></div>
</div>
</div>
))
) : activities.length === 0 ? (
<div className="text-center py-8">
<p className="text-base text-gray-600 dark:text-gray-400">No recent activity</p>
<p className="text-sm text-gray-500 dark:text-gray-500 mt-1">
AI operations will appear here
</p>
</div>
) : (
activities.slice(0, 5).map((activity) => {
const config = activityConfig[activity.type] || defaultActivityConfig;
const Icon = config.icon;
const content = (
<div className="flex items-start gap-3">
<div className={`w-9 h-9 rounded-lg bg-gradient-to-br ${config.gradient} flex items-center justify-center flex-shrink-0 shadow-md`}>
<Icon className="w-5 h-5 text-white" />
</div>
<div className="flex-1 min-w-0">
<p className="text-base text-gray-800 dark:text-gray-200 line-clamp-1">
{activity.title}
</p>
<p className="text-sm text-gray-600 dark:text-gray-400">
{formatRelativeTime(activity.timestamp)}
</p>
</div>
</div>
);
return activity.href ? (
<Link
key={activity.id}
to={activity.href}
className="block hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-lg p-1 -m-1 transition-colors"
>
{content}
</Link>
) : (
<div key={activity.id} className="p-1 -m-1">
{content}
</div>
);
})
)}
</div>
{/* View All Link */}
{activities.length > 0 && (
<Link
to="/account/activity"
className="block mt-3 pt-3 border-t border-gray-100 dark:border-gray-800 text-xs font-medium text-brand-600 hover:text-brand-700 dark:text-brand-400 dark:hover:text-brand-300 text-center"
>
View All Activity
</Link>
)}
</div>
);
}