import { useEffect, useMemo, useRef, useState } from 'react'; import { useVirtualizer } from '@tanstack/react-virtual'; import type { MirrorJob } from '@/lib/db/schema'; import Fuse from 'fuse.js'; import { Button } from '../ui/button'; import { RefreshCw } from 'lucide-react'; import { Card } from '../ui/card'; import { formatDate, getStatusColor } from '@/lib/utils'; import { Skeleton } from '../ui/skeleton'; import type { FilterParams } from '@/types/filter'; type MirrorJobWithKey = MirrorJob & { _rowKey: string }; interface ActivityListProps { activities: MirrorJobWithKey[]; isLoading: boolean; filter: FilterParams; setFilter: (filter: FilterParams) => void; } export default function ActivityList({ activities, isLoading, filter, setFilter, }: ActivityListProps) { const [expandedItems, setExpandedItems] = useState>( () => new Set(), ); const parentRef = useRef(null); // We keep the ref only for possible future scroll-to-row logic. const rowRefs = useRef>(new Map()); // eslint-disable-line @typescript-eslint/no-unused-vars const filteredActivities = useMemo(() => { let result = activities; if (filter.status) { result = result.filter((a) => a.status === filter.status); } if (filter.type) { result = filter.type === 'repository' ? result.filter((a) => !!a.repositoryId) : filter.type === 'organization' ? result.filter((a) => !!a.organizationId) : result; } if (filter.name) { result = result.filter( (a) => a.repositoryName === filter.name || a.organizationName === filter.name, ); } if (filter.searchTerm) { const fuse = new Fuse(result, { keys: ['message', 'details', 'organizationName', 'repositoryName'], threshold: 0.3, }); result = fuse.search(filter.searchTerm).map((r) => r.item); } return result; }, [activities, filter]); const virtualizer = useVirtualizer({ count: filteredActivities.length, getScrollElement: () => parentRef.current, estimateSize: (idx) => expandedItems.has(filteredActivities[idx]._rowKey) ? 217 : 120, overscan: 5, measureElement: (el) => el.getBoundingClientRect().height + 8, }); useEffect(() => { virtualizer.measure(); }, [expandedItems, virtualizer]); /* ------------------------------ render ------------------------------ */ if (isLoading) { return (
{Array.from({ length: 5 }, (_, i) => ( ))}
); } if (filteredActivities.length === 0) { const hasFilter = filter.searchTerm || filter.status || filter.type || filter.name; return (

No activities found

{hasFilter ? 'Try adjusting your search or filter criteria.' : 'No mirroring activities have been recorded yet.'}

{hasFilter ? ( ) : ( )}
); } return (
{virtualizer.getVirtualItems().map((vRow) => { const activity = filteredActivities[vRow.index]; const isExpanded = expandedItems.has(activity._rowKey); return (
{ rowRefs.current.set(activity._rowKey, node); if (node) virtualizer.measureElement(node); }} style={{ position: 'absolute', top: 0, left: 0, width: '100%', transform: `translateY(${vRow.start}px)`, paddingBottom: '8px', }} className='border-b px-4 pt-4' >

{activity.message}

{formatDate(activity.timestamp)}

{activity.repositoryName && (

Repository: {activity.repositoryName}

)} {activity.organizationName && (

Organization: {activity.organizationName}

)} {activity.details && (
{isExpanded && (
                          {activity.details}
                        
)}
)}
); })}
); }