import React, { useMemo, useState, useCallback } from 'react'; import { clsx } from 'clsx'; import { Avatar, Badge, Button } from '../../ui'; export interface ActivityUser { id: string; name: string; avatar?: string; role?: string; } export interface ActivityItem { id: string; type: ActivityType; title: string; description: string; timestamp: string; user?: ActivityUser; metadata?: Record; status?: ActivityStatus; category?: string; // Navigation support href?: string; onClick?: () => void; // Visual styling icon?: React.ReactNode; color?: string; priority?: ActivityPriority; } export enum ActivityType { ORDER = 'order', PRODUCTION = 'production', INVENTORY = 'inventory', SALES = 'sales', USER = 'user', SYSTEM = 'system', QUALITY = 'quality', SUPPLIER = 'supplier', FINANCE = 'finance', ALERT = 'alert' } export enum ActivityStatus { SUCCESS = 'success', WARNING = 'warning', ERROR = 'error', INFO = 'info', PENDING = 'pending', IN_PROGRESS = 'in_progress', CANCELLED = 'cancelled' } export enum ActivityPriority { LOW = 'low', MEDIUM = 'medium', HIGH = 'high', URGENT = 'urgent' } export interface RecentActivityProps { activities: ActivityItem[]; // Display configuration maxItems?: number; showTimestamp?: boolean; showUserAvatar?: boolean; showTypeIcons?: boolean; compact?: boolean; // Filtering allowFiltering?: boolean; filterTypes?: ActivityType[]; defaultFilter?: ActivityType | 'all'; // Pagination and loading hasMore?: boolean; isLoading?: boolean; onLoadMore?: () => void; // Event handlers onActivityClick?: (activity: ActivityItem) => void; onRefresh?: () => void; // Accessibility and styling className?: string; 'aria-label'?: string; emptyMessage?: string; } // Spanish activity type labels and icons const ACTIVITY_CONFIG = { [ActivityType.ORDER]: { label: 'Pedidos', icon: ( ), color: 'blue', bgColor: 'bg-[var(--color-info)]/10', textColor: 'text-[var(--color-info)]', borderColor: 'border-[var(--color-info)]/20' }, [ActivityType.PRODUCTION]: { label: 'Producción', icon: ( ), color: 'orange', bgColor: 'bg-[var(--color-primary)]/10', textColor: 'text-[var(--color-primary)]', borderColor: 'border-orange-200' }, [ActivityType.INVENTORY]: { label: 'Inventario', icon: ( ), color: 'purple', bgColor: 'bg-purple-100', textColor: 'text-purple-600', borderColor: 'border-purple-200' }, [ActivityType.SALES]: { label: 'Ventas', icon: ( ), color: 'green', bgColor: 'bg-[var(--color-success)]/10', textColor: 'text-[var(--color-success)]', borderColor: 'border-green-200' }, [ActivityType.USER]: { label: 'Usuarios', icon: ( ), color: 'indigo', bgColor: 'bg-indigo-100', textColor: 'text-indigo-600', borderColor: 'border-indigo-200' }, [ActivityType.SYSTEM]: { label: 'Sistema', icon: ( ), color: 'gray', bgColor: 'bg-[var(--bg-tertiary)]', textColor: 'text-[var(--text-secondary)]', borderColor: 'border-[var(--border-primary)]' }, [ActivityType.QUALITY]: { label: 'Calidad', icon: ( ), color: 'green', bgColor: 'bg-[var(--color-success)]/10', textColor: 'text-[var(--color-success)]', borderColor: 'border-green-200' }, [ActivityType.SUPPLIER]: { label: 'Proveedores', icon: ( ), color: 'teal', bgColor: 'bg-teal-100', textColor: 'text-teal-600', borderColor: 'border-teal-200' }, [ActivityType.FINANCE]: { label: 'Finanzas', icon: ( ), color: 'emerald', bgColor: 'bg-emerald-100', textColor: 'text-emerald-600', borderColor: 'border-emerald-200' }, [ActivityType.ALERT]: { label: 'Alertas', icon: ( ), color: 'red', bgColor: 'bg-[var(--color-error)]/10', textColor: 'text-[var(--color-error)]', borderColor: 'border-red-200' } }; const STATUS_CONFIG = { [ActivityStatus.SUCCESS]: { color: 'green', bgColor: 'bg-green-500' }, [ActivityStatus.WARNING]: { color: 'yellow', bgColor: 'bg-yellow-500' }, [ActivityStatus.ERROR]: { color: 'red', bgColor: 'bg-red-500' }, [ActivityStatus.INFO]: { color: 'blue', bgColor: 'bg-[var(--color-info)]/50' }, [ActivityStatus.PENDING]: { color: 'gray', bgColor: 'bg-[var(--bg-secondary)]0' }, [ActivityStatus.IN_PROGRESS]: { color: 'purple', bgColor: 'bg-purple-500' }, [ActivityStatus.CANCELLED]: { color: 'gray', bgColor: 'bg-gray-400' } }; const formatRelativeTime = (timestamp: string): string => { const date = new Date(timestamp); const now = new Date(); const diffInMs = now.getTime() - date.getTime(); const diffInMinutes = Math.floor(diffInMs / (1000 * 60)); const diffInHours = Math.floor(diffInMinutes / 60); const diffInDays = Math.floor(diffInHours / 24); if (diffInMinutes < 1) { return 'Ahora mismo'; } else if (diffInMinutes < 60) { return `Hace ${diffInMinutes} min`; } else if (diffInHours < 24) { return `Hace ${diffInHours}h`; } else if (diffInDays === 1) { return 'Ayer'; } else if (diffInDays < 7) { return `Hace ${diffInDays} días`; } else { return date.toLocaleDateString('es-ES', { day: 'numeric', month: 'short' }); } }; const RecentActivity: React.FC = ({ activities, maxItems = 10, showTimestamp = true, showUserAvatar = true, showTypeIcons = true, compact = false, allowFiltering = true, filterTypes = Object.values(ActivityType), defaultFilter = 'all', hasMore = false, isLoading = false, onLoadMore, onActivityClick, onRefresh, className, 'aria-label': ariaLabel = 'Actividad reciente', emptyMessage = 'No hay actividad reciente' }) => { const [activeFilter, setActiveFilter] = useState(defaultFilter); const filteredActivities = useMemo(() => { let filtered = activities; if (activeFilter !== 'all') { filtered = activities.filter(activity => activity.type === activeFilter); } return filtered.slice(0, maxItems); }, [activities, activeFilter, maxItems]); const handleActivityClick = useCallback((activity: ActivityItem) => { onActivityClick?.(activity); if (activity.onClick) { activity.onClick(); } }, [onActivityClick]); const renderActivityItem = (activity: ActivityItem) => { const config = ACTIVITY_CONFIG[activity.type] || { label: 'Actividad', icon:
, color: 'gray', bgColor: 'bg-[var(--bg-tertiary)]', textColor: 'text-[var(--text-secondary)]', borderColor: 'border-[var(--border-primary)]' }; const statusConfig = activity.status ? STATUS_CONFIG[activity.status] : null; const itemClasses = clsx( 'group relative flex items-start gap-3 p-3 rounded-lg transition-all duration-200', 'hover:bg-[var(--bg-secondary)] hover:shadow-sm', { 'cursor-pointer': activity.onClick || activity.href, 'p-2': compact, 'border-l-4': !compact, [config.borderColor]: !compact } ); return (
handleActivityClick(activity) : undefined} role={activity.onClick || activity.href ? 'button' : undefined} tabIndex={activity.onClick || activity.href ? 0 : undefined} > {/* Timeline indicator */}
{showTypeIcons && (
{activity.icon || config.icon}
)} {/* Status indicator */} {activity.status && statusConfig && (
)}
{/* Content */}

{activity.title}

{activity.description}

{/* User info */} {activity.user && showUserAvatar && (
{activity.user.name}
)}
{/* Timestamp */} {showTimestamp && ( )}
); }; if (activities.length === 0) { return (

{emptyMessage}

); } return (
{/* Filters */} {allowFiltering && filterTypes.length > 1 && (
{filterTypes.map((type) => { const config = ACTIVITY_CONFIG[type] || { label: 'Actividad', icon:
, color: 'gray', bgColor: 'bg-[var(--bg-tertiary)]', textColor: 'text-[var(--text-secondary)]', borderColor: 'border-[var(--border-primary)]' }; const count = activities.filter(a => a.type === type).length; return ( ); })} {onRefresh && (
)}
)} {/* Activity list */}
{filteredActivities.map(renderActivityItem)}
{/* Loading state */} {isLoading && (
)} {/* Load more */} {hasMore && onLoadMore && !isLoading && (
)}
); }; RecentActivity.displayName = 'RecentActivity'; export default RecentActivity;