From 396453c18a2969fa9413b8fc4cfc2a0d1ab16550 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Tue, 3 Mar 2026 08:12:38 -0500 Subject: [PATCH] feat: add WeeklyCalendar, QuickActions, OpenSessions, RecentActivity dashboard components Co-Authored-By: Claude Opus 4.6 --- .../src/components/dashboard/OpenSessions.tsx | 65 +++++++++++++ .../src/components/dashboard/QuickActions.tsx | 41 +++++++++ .../components/dashboard/RecentActivity.tsx | 58 ++++++++++++ .../components/dashboard/WeeklyCalendar.tsx | 91 +++++++++++++++++++ 4 files changed, 255 insertions(+) create mode 100644 frontend/src/components/dashboard/OpenSessions.tsx create mode 100644 frontend/src/components/dashboard/QuickActions.tsx create mode 100644 frontend/src/components/dashboard/RecentActivity.tsx create mode 100644 frontend/src/components/dashboard/WeeklyCalendar.tsx diff --git a/frontend/src/components/dashboard/OpenSessions.tsx b/frontend/src/components/dashboard/OpenSessions.tsx new file mode 100644 index 00000000..88c77695 --- /dev/null +++ b/frontend/src/components/dashboard/OpenSessions.tsx @@ -0,0 +1,65 @@ +import { Link } from 'react-router-dom' +import { getTreeNavigatePath } from '@/lib/routing' + +interface OpenSession { + id: string + treeName: string + treeId: string + treeType?: string + stepNumber?: number + totalSteps?: number + timeAgo: string +} + +interface OpenSessionsProps { + sessions: OpenSession[] +} + +export function OpenSessions({ sessions }: OpenSessionsProps) { + return ( +
+
+

My Open Sessions

+ + View All + +
+
+ {sessions.length === 0 ? ( +
+

No open sessions

+
+ ) : ( + sessions.map((session, i) => ( +
+ +
+
{session.treeName}
+
+ {session.stepNumber && session.totalSteps + ? `Step ${session.stepNumber} of ${session.totalSteps}` + : 'In progress'} + · + {session.timeAgo} +
+
+ + Resume + +
+ )) + )} +
+
+ ) +} diff --git a/frontend/src/components/dashboard/QuickActions.tsx b/frontend/src/components/dashboard/QuickActions.tsx new file mode 100644 index 00000000..0062a1bb --- /dev/null +++ b/frontend/src/components/dashboard/QuickActions.tsx @@ -0,0 +1,41 @@ +import { useNavigate } from 'react-router-dom' +import { Plus, Play, BookOpen, UserPlus } from 'lucide-react' + +const ACTIONS = [ + { icon: Plus, label: 'New Flow', description: 'Create a new flow', href: '/trees/new', color: '#06b6d4' }, + { icon: Play, label: 'Resume Session', description: 'Continue where you left off', href: '/sessions', color: '#34d399' }, + { icon: BookOpen, label: 'Browse Library', description: 'Explore step library', href: '/step-library', color: '#fbbf24' }, + { icon: UserPlus, label: 'Invite Team', description: 'Add team members', href: '/account', color: '#818cf8' }, +] as const + +export function QuickActions() { + const navigate = useNavigate() + + return ( +
+
+

Quick Actions

+
+
+ {ACTIONS.map(({ icon: Icon, label, description, href, color }) => ( + + ))} +
+
+ ) +} diff --git a/frontend/src/components/dashboard/RecentActivity.tsx b/frontend/src/components/dashboard/RecentActivity.tsx new file mode 100644 index 00000000..157b592a --- /dev/null +++ b/frontend/src/components/dashboard/RecentActivity.tsx @@ -0,0 +1,58 @@ +import type { LucideIcon } from 'lucide-react' +import { GitBranch, Play, CheckCircle, FileText, Edit } from 'lucide-react' + +interface ActivityItem { + id: string + icon: LucideIcon + iconColor: string + iconBg: string + description: string + timestamp: string +} + +interface RecentActivityProps { + activities?: ActivityItem[] +} + +const DEFAULT_ACTIVITIES: ActivityItem[] = [ + { id: '1', icon: Play, iconColor: '#34d399', iconBg: 'rgba(52, 211, 153, 0.1)', description: 'Started VPN Connectivity Triage session', timestamp: '2 min ago' }, + { id: '2', icon: CheckCircle, iconColor: '#06b6d4', iconBg: 'rgba(6, 182, 212, 0.1)', description: 'Completed M365 License Provisioning', timestamp: '15 min ago' }, + { id: '3', icon: Edit, iconColor: '#fbbf24', iconBg: 'rgba(251, 191, 36, 0.1)', description: 'Updated Printer Troubleshooting flow', timestamp: '1 hr ago' }, + { id: '4', icon: GitBranch, iconColor: '#818cf8', iconBg: 'rgba(129, 140, 248, 0.1)', description: 'Created new DNS Resolution flow', timestamp: '3 hr ago' }, + { id: '5', icon: FileText, iconColor: '#8891a0', iconBg: 'rgba(136, 145, 160, 0.1)', description: 'Exported session report #TK-4821', timestamp: 'Yesterday' }, +] + +export function RecentActivity({ activities = DEFAULT_ACTIVITIES }: RecentActivityProps) { + return ( +
+
+

Recent Activity

+
+
+ {activities.map((item, i) => ( +
+ + + +
+

{item.description}

+
+ + {item.timestamp} + +
+ ))} +
+
+ ) +} diff --git a/frontend/src/components/dashboard/WeeklyCalendar.tsx b/frontend/src/components/dashboard/WeeklyCalendar.tsx new file mode 100644 index 00000000..b9a1e51d --- /dev/null +++ b/frontend/src/components/dashboard/WeeklyCalendar.tsx @@ -0,0 +1,91 @@ +import { useMemo } from 'react' +import { Calendar } from 'lucide-react' + +interface CalendarEvent { + id: string + title: string + time: string + type: 'default' | 'maintenance' +} + +interface WeeklyCalendarProps { + events?: Record +} + +const DAY_NAMES = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'] + +function getWeekDays(): { label: string; date: Date; dateStr: string; isToday: boolean }[] { + const now = new Date() + const day = now.getDay() + const mondayOffset = day === 0 ? 6 : day - 1 + const monday = new Date(now) + monday.setDate(now.getDate() - mondayOffset) + + return DAY_NAMES.map((label, i) => { + const d = new Date(monday) + d.setDate(monday.getDate() + i) + const dateStr = d.toISOString().split('T')[0] + const isToday = d.toDateString() === now.toDateString() + return { label, date: d, dateStr, isToday } + }) +} + +export function WeeklyCalendar({ events = {} }: WeeklyCalendarProps) { + const days = useMemo(() => getWeekDays(), []) + + return ( +
+
+ +

This Week

+
+
+ {days.map((day, i) => { + const dayEvents = events[day.dateStr] || [] + return ( +
+
+ + {day.label} + +
+ {day.date.getDate()} +
+
+
+ {dayEvents.length === 0 ? ( +

No events

+ ) : ( + dayEvents.map(event => ( +
+
{event.title}
+
{event.time}
+
+ )) + )} +
+
+ ) + })} +
+
+ ) +}