mission-control/components/activity/recent-activity-widget.tsx

141 lines
4.1 KiB
TypeScript

"use client";
import Link from "next/link";
import { formatDistanceToNow } from "date-fns";
import {
Activity,
CheckCircle2,
PlusCircle,
MessageSquare,
UserPlus,
Edit3,
ArrowRight,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { useActivityFeed } from "@/hooks/use-activity-feed";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import type { ActivityItem } from "@/lib/supabase/database.types";
const activityTypeConfig: Record<
ActivityItem["type"],
{ label: string; icon: React.ElementType; color: string }
> = {
task_created: {
label: "Created",
icon: PlusCircle,
color: "bg-blue-500/10 text-blue-500",
},
task_completed: {
label: "Completed",
icon: CheckCircle2,
color: "bg-green-500/10 text-green-500",
},
task_updated: {
label: "Updated",
icon: Edit3,
color: "bg-yellow-500/10 text-yellow-500",
},
comment_added: {
label: "Comment",
icon: MessageSquare,
color: "bg-purple-500/10 text-purple-500",
},
task_assigned: {
label: "Assigned",
icon: UserPlus,
color: "bg-pink-500/10 text-pink-500",
},
};
function RecentActivityItem({ activity }: { activity: ActivityItem }) {
const config = activityTypeConfig[activity.type];
const Icon = config.icon;
const timeAgo = formatDistanceToNow(new Date(activity.timestamp), { addSuffix: true });
return (
<div className="flex gap-3 py-3 border-b border-border last:border-0">
<div
className={cn(
"w-8 h-8 rounded-full flex items-center justify-center shrink-0",
config.color
)}
>
<Icon className="w-4 h-4" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<span className="font-medium text-sm truncate">{activity.user_name}</span>
<Badge variant="secondary" className="text-xs font-normal">
{config.label}
</Badge>
</div>
<p className="text-sm text-muted-foreground truncate">
{activity.task_title}
</p>
<p className="text-xs text-muted-foreground">{timeAgo}</p>
</div>
</div>
);
}
function RecentActivitySkeleton() {
return (
<div className="flex gap-3 py-3 border-b border-border">
<Skeleton className="w-8 h-8 rounded-full shrink-0" />
<div className="flex-1 space-y-2">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-3 w-32" />
</div>
</div>
);
}
export function RecentActivityWidget() {
const { activities, loading, error } = useActivityFeed({ limit: 5 });
return (
<Card className="h-full">
<CardHeader className="pb-3">
<CardTitle className="flex items-center gap-2 text-base">
<Activity className="w-4 h-4" />
Recent Activity
</CardTitle>
</CardHeader>
<CardContent>
{loading ? (
<div className="space-y-1">
{Array.from({ length: 4 }).map((_, i) => (
<RecentActivitySkeleton key={i} />
))}
</div>
) : error ? (
<div className="text-center py-6">
<p className="text-sm text-muted-foreground">Failed to load</p>
</div>
) : activities.length === 0 ? (
<div className="text-center py-6">
<Activity className="w-8 h-8 text-muted-foreground mx-auto mb-2" />
<p className="text-sm text-muted-foreground">No recent activity</p>
</div>
) : (
<div className="space-y-1">
{activities.map((activity) => (
<RecentActivityItem key={activity.id} activity={activity} />
))}
</div>
)}
<Link href="/activity">
<Button variant="ghost" className="w-full mt-4 text-sm" size="sm">
View all activity
<ArrowRight className="w-4 h-4 ml-2" />
</Button>
</Link>
</CardContent>
</Card>
);
}