147 lines
5.2 KiB
TypeScript
147 lines
5.2 KiB
TypeScript
import { ActivityFeedItem } from '@/components/feed/ActivityFeedItem';
|
|
import { useLeagueRaces } from "@/hooks/league/useLeagueRaces";
|
|
import { LeagueActivityService } from '@/lib/services/league/LeagueActivityService';
|
|
import { Icon } from '@/ui/Icon';
|
|
import { Text } from '@/ui/Text';
|
|
import { Stack } from '@/ui/Stack';
|
|
import { AlertTriangle, Calendar, Flag, Shield, UserMinus, UserPlus } from 'lucide-react';
|
|
import { useMemo } from 'react';
|
|
|
|
export type LeagueActivity =
|
|
| { type: 'race_completed'; raceId: string; raceName: string; timestamp: Date }
|
|
| { type: 'race_scheduled'; raceId: string; raceName: string; timestamp: Date }
|
|
| { type: 'penalty_applied'; penaltyId: string; driverName: string; reason: string; points: number; timestamp: Date }
|
|
| { type: 'member_joined'; driverId: string; driverName: string; timestamp: Date }
|
|
| { type: 'member_left'; driverId: string; driverName: string; timestamp: Date }
|
|
| { type: 'role_changed'; driverId: string; driverName: string; oldRole: string; newRole: string; timestamp: Date };
|
|
|
|
interface LeagueActivityFeedProps {
|
|
leagueId: string;
|
|
limit?: number;
|
|
}
|
|
|
|
function timeAgo(timestamp: Date): string {
|
|
const diffMs = Date.now() - timestamp.getTime();
|
|
const diffMinutes = Math.floor(diffMs / 60000);
|
|
if (diffMinutes < 1) return 'Just now';
|
|
if (diffMinutes < 60) return `${diffMinutes} min ago`;
|
|
const diffHours = Math.floor(diffMinutes / 60);
|
|
if (diffHours < 24) return `${diffHours}h ago`;
|
|
const diffDays = Math.floor(diffHours / 24);
|
|
if (diffDays === 1) return 'Yesterday';
|
|
if (diffDays < 7) return `${diffDays}d ago`;
|
|
return timestamp.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
}
|
|
|
|
export function LeagueActivityFeed({ leagueId, limit = 10 }: LeagueActivityFeedProps) {
|
|
const { data: raceList = [], isLoading } = useLeagueRaces(leagueId);
|
|
|
|
const activities = useMemo(() => {
|
|
if (isLoading || !Array.isArray(raceList) || raceList.length === 0) return [];
|
|
const service = new LeagueActivityService();
|
|
const result = service.processLeagueActivities(raceList, limit);
|
|
return result.isOk() ? result.unwrap() : [];
|
|
}, [raceList, isLoading, limit]);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<Text color="text-gray-400" textAlign="center" block py={8}>
|
|
Loading activities...
|
|
</Text>
|
|
);
|
|
}
|
|
|
|
if (activities.length === 0) {
|
|
return (
|
|
<Text color="text-gray-400" textAlign="center" block py={8}>
|
|
No recent activity
|
|
</Text>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Stack gap={0}>
|
|
{activities.map((activity, index) => (
|
|
<ActivityItem key={`${activity.type}-${index}`} activity={activity} />
|
|
))}
|
|
</Stack>
|
|
);
|
|
}
|
|
|
|
function ActivityItem({ activity }: { activity: LeagueActivity }) {
|
|
const getIcon = () => {
|
|
switch (activity.type) {
|
|
case 'race_completed':
|
|
return <Icon icon={Flag} size={4} color="var(--performance-green)" />;
|
|
case 'race_scheduled':
|
|
return <Icon icon={Calendar} size={4} color="var(--primary-blue)" />;
|
|
case 'penalty_applied':
|
|
return <Icon icon={AlertTriangle} size={4} color="var(--warning-amber)" />;
|
|
case 'member_joined':
|
|
return <Icon icon={UserPlus} size={4} color="var(--performance-green)" />;
|
|
case 'member_left':
|
|
return <Icon icon={UserMinus} size={4} color="var(--text-gray-400)" />;
|
|
case 'role_changed':
|
|
return <Icon icon={Shield} size={4} color="var(--primary-blue)" />;
|
|
}
|
|
};
|
|
|
|
const getContent = () => {
|
|
switch (activity.type) {
|
|
case 'race_completed':
|
|
return (
|
|
<>
|
|
<Text weight="medium" color="text-white">Race Completed</Text>
|
|
<Text color="text-gray-400"> · {activity.raceName}</Text>
|
|
</>
|
|
);
|
|
case 'race_scheduled':
|
|
return (
|
|
<>
|
|
<Text weight="medium" color="text-white">Race Scheduled</Text>
|
|
<Text color="text-gray-400"> · {activity.raceName}</Text>
|
|
</>
|
|
);
|
|
case 'penalty_applied':
|
|
return (
|
|
<>
|
|
<Text weight="medium" color="text-white">{activity.driverName}</Text>
|
|
<Text color="text-gray-400"> received a </Text>
|
|
<Text color="text-warning-amber">{activity.points}-point penalty</Text>
|
|
<Text color="text-gray-400"> · {activity.reason}</Text>
|
|
</>
|
|
);
|
|
case 'member_joined':
|
|
return (
|
|
<>
|
|
<Text weight="medium" color="text-white">{activity.driverName}</Text>
|
|
<Text color="text-gray-400"> joined the league</Text>
|
|
</>
|
|
);
|
|
case 'member_left':
|
|
return (
|
|
<>
|
|
<Text weight="medium" color="text-white">{activity.driverName}</Text>
|
|
<Text color="text-gray-400"> left the league</Text>
|
|
</>
|
|
);
|
|
case 'role_changed':
|
|
return (
|
|
<>
|
|
<Text weight="medium" color="text-white">{activity.driverName}</Text>
|
|
<Text color="text-gray-400"> promoted to </Text>
|
|
<Text color="text-primary-blue">{activity.newRole}</Text>
|
|
</>
|
|
);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<ActivityFeedItem
|
|
icon={getIcon()}
|
|
content={getContent()}
|
|
timestamp={timeAgo(activity.timestamp)}
|
|
/>
|
|
);
|
|
}
|