Skip to content

Commit 8c76d60

Browse files
committed
Add RecentActivity component
Introduces a RecentActivity React component with mock activity data, filtering by type, and timeline UI. Supports multiple activity types and displays user information and timestamps.
1 parent 3e2d964 commit 8c76d60

1 file changed

Lines changed: 213 additions & 0 deletions

File tree

components/recent-activity.tsx

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
"use client"
2+
3+
import { useState, useEffect } from "react"
4+
import { formatDistanceToNow } from "date-fns"
5+
import {
6+
FileText,
7+
CheckSquare,
8+
Calendar,
9+
Upload,
10+
GitBranch,
11+
MessageSquare,
12+
UserPlus,
13+
Settings,
14+
Palette,
15+
FolderKanban
16+
} from "lucide-react"
17+
18+
export interface ActivityItem {
19+
id: string
20+
type: "project" | "todo" | "meeting" | "file" | "chat" | "user" | "settings" | "whiteboard"
21+
action: string
22+
description: string
23+
timestamp: Date
24+
user?: {
25+
name: string
26+
avatar?: string
27+
}
28+
}
29+
30+
export interface RecentActivityProps {
31+
userId?: string
32+
limit?: number
33+
className?: string
34+
}
35+
36+
export function RecentActivity({ userId, limit = 10, className = "" }: RecentActivityProps) {
37+
const [activities, setActivities] = useState<ActivityItem[]>([])
38+
const [filter, setFilter] = useState<string>("all")
39+
40+
useEffect(() => {
41+
loadActivities()
42+
}, [userId, limit])
43+
44+
async function loadActivities() {
45+
// Simulated activity data - in production, fetch from database
46+
const mockActivities: ActivityItem[] = [
47+
{
48+
id: "1",
49+
type: "project",
50+
action: "created",
51+
description: "Created new project 'E-Commerce Platform'",
52+
timestamp: new Date(Date.now() - 1000 * 60 * 15), // 15 minutes ago
53+
user: { name: "You" },
54+
},
55+
{
56+
id: "2",
57+
type: "todo",
58+
action: "completed",
59+
description: "Completed task 'Setup database schema'",
60+
timestamp: new Date(Date.now() - 1000 * 60 * 45), // 45 minutes ago
61+
user: { name: "You" },
62+
},
63+
{
64+
id: "3",
65+
type: "meeting",
66+
action: "scheduled",
67+
description: "Scheduled meeting 'Sprint Planning' for tomorrow",
68+
timestamp: new Date(Date.now() - 1000 * 60 * 120), // 2 hours ago
69+
user: { name: "You" },
70+
},
71+
{
72+
id: "4",
73+
type: "file",
74+
action: "uploaded",
75+
description: "Uploaded file 'design-mockup.fig'",
76+
timestamp: new Date(Date.now() - 1000 * 60 * 180), // 3 hours ago
77+
user: { name: "You" },
78+
},
79+
{
80+
id: "5",
81+
type: "whiteboard",
82+
action: "created",
83+
description: "Created whiteboard 'System Architecture'",
84+
timestamp: new Date(Date.now() - 1000 * 60 * 240), // 4 hours ago
85+
user: { name: "You" },
86+
},
87+
]
88+
89+
setActivities(mockActivities.slice(0, limit))
90+
}
91+
92+
const getActivityIcon = (type: string) => {
93+
const iconClass = "h-4 w-4"
94+
switch (type) {
95+
case "project":
96+
return <FolderKanban className={iconClass} />
97+
case "todo":
98+
return <CheckSquare className={iconClass} />
99+
case "meeting":
100+
return <Calendar className={iconClass} />
101+
case "file":
102+
return <Upload className={iconClass} />
103+
case "chat":
104+
return <MessageSquare className={iconClass} />
105+
case "user":
106+
return <UserPlus className={iconClass} />
107+
case "settings":
108+
return <Settings className={iconClass} />
109+
case "whiteboard":
110+
return <Palette className={iconClass} />
111+
default:
112+
return <FileText className={iconClass} />
113+
}
114+
}
115+
116+
const getActivityColor = (type: string) => {
117+
switch (type) {
118+
case "project":
119+
return "text-blue-500 bg-blue-500/10 border-blue-500/20"
120+
case "todo":
121+
return "text-green-500 bg-green-500/10 border-green-500/20"
122+
case "meeting":
123+
return "text-purple-500 bg-purple-500/10 border-purple-500/20"
124+
case "file":
125+
return "text-orange-500 bg-orange-500/10 border-orange-500/20"
126+
case "chat":
127+
return "text-cyan-500 bg-cyan-500/10 border-cyan-500/20"
128+
case "whiteboard":
129+
return "text-pink-500 bg-pink-500/10 border-pink-500/20"
130+
default:
131+
return "text-muted-foreground bg-muted border-border"
132+
}
133+
}
134+
135+
const filteredActivities = filter === "all"
136+
? activities
137+
: activities.filter((a) => a.type === filter)
138+
139+
return (
140+
<div className={`space-y-4 ${className}`}>
141+
{/* Filter Tabs */}
142+
<div className="flex items-center gap-2 overflow-x-auto pb-2 scrollbar-hide">
143+
{["all", "project", "todo", "meeting", "file", "whiteboard"].map((f) => (
144+
<button
145+
key={f}
146+
onClick={() => setFilter(f)}
147+
className={`px-3 py-1.5 text-xs font-medium border transition-colors whitespace-nowrap ${
148+
filter === f
149+
? "bg-primary text-primary-foreground border-primary"
150+
: "bg-card border-border hover:border-primary"
151+
}`}
152+
>
153+
{f.charAt(0).toUpperCase() + f.slice(1)}
154+
</button>
155+
))}
156+
</div>
157+
158+
{/* Activity Timeline */}
159+
<div className="space-y-3">
160+
{filteredActivities.length === 0 ? (
161+
<div className="text-center py-8 text-sm text-muted-foreground">
162+
No recent activity
163+
</div>
164+
) : (
165+
filteredActivities.map((activity, index) => (
166+
<div key={activity.id} className="flex gap-3 group">
167+
{/* Timeline Line */}
168+
<div className="flex flex-col items-center">
169+
<div
170+
className={`p-2 border ${getActivityColor(activity.type)} transition-all group-hover:scale-110`}
171+
>
172+
{getActivityIcon(activity.type)}
173+
</div>
174+
{index < filteredActivities.length - 1 && (
175+
<div className="w-0.5 flex-1 bg-border mt-2" />
176+
)}
177+
</div>
178+
179+
{/* Activity Content */}
180+
<div className="flex-1 pb-4">
181+
<div className="flex items-start justify-between gap-2">
182+
<div className="flex-1">
183+
<p className="text-sm font-medium leading-tight">
184+
{activity.description}
185+
</p>
186+
<p className="text-xs text-muted-foreground mt-1">
187+
{formatDistanceToNow(activity.timestamp, { addSuffix: true })}
188+
</p>
189+
</div>
190+
{activity.user && (
191+
<div className="flex items-center gap-2">
192+
{activity.user.avatar ? (
193+
<img
194+
src={activity.user.avatar}
195+
alt={activity.user.name}
196+
className="w-6 h-6 rounded-full border border-border"
197+
/>
198+
) : (
199+
<div className="w-6 h-6 rounded-full bg-primary/10 border border-primary/20 flex items-center justify-center text-xs font-medium text-primary">
200+
{activity.user.name.charAt(0)}
201+
</div>
202+
)}
203+
</div>
204+
)}
205+
</div>
206+
</div>
207+
</div>
208+
))
209+
)}
210+
</div>
211+
</div>
212+
)
213+
}

0 commit comments

Comments
 (0)