feat(frontend): add Dashboard page and components for #53
Implement the main dashboard homepage with: - WelcomeHeader: Personalized greeting with user name - DashboardQuickStats: Stats cards for projects, agents, issues, approvals - RecentProjects: Dynamic grid showing 3-6 recent projects - PendingApprovals: Action-required approvals section - EmptyState: Onboarding experience for new users - useDashboard hook: Mock data fetching with React Query The dashboard serves as the authenticated homepage at /(authenticated)/ and provides quick access to all project management features. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
131
frontend/src/components/dashboard/Dashboard.tsx
Normal file
131
frontend/src/components/dashboard/Dashboard.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Dashboard Component
|
||||
*
|
||||
* Main dashboard layout orchestrator.
|
||||
* Combines all dashboard sub-components into a cohesive layout.
|
||||
*
|
||||
* Layout:
|
||||
* +------------------------------------------+------------------+
|
||||
* | Welcome Header | ACTIVITY |
|
||||
* +------------------------------------------+ FEED |
|
||||
* | Quick Stats (4 cards) | SIDEBAR |
|
||||
* +------------------------------------------+ |
|
||||
* | Recent Projects (3-6 cards) | |
|
||||
* +------------------------------------------+ |
|
||||
* | Pending Approvals (if any) | |
|
||||
* +------------------------------------------+------------------+
|
||||
*
|
||||
* @see Issue #53
|
||||
*/
|
||||
|
||||
'use client';
|
||||
|
||||
import { useCallback } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { ActivityFeed } from '@/components/activity/ActivityFeed';
|
||||
import { WelcomeHeader } from './WelcomeHeader';
|
||||
import { DashboardQuickStats } from './DashboardQuickStats';
|
||||
import { RecentProjects } from './RecentProjects';
|
||||
import { PendingApprovals } from './PendingApprovals';
|
||||
import { EmptyState } from './EmptyState';
|
||||
import { useDashboard, type PendingApproval } from '@/lib/api/hooks/useDashboard';
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
import { useProjectEvents } from '@/lib/hooks/useProjectEvents';
|
||||
import { useProjectEventsFromStore } from '@/lib/stores/eventStore';
|
||||
|
||||
export interface DashboardProps {
|
||||
/** Additional CSS classes */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Dashboard({ className }: DashboardProps) {
|
||||
const { user } = useAuth();
|
||||
const { data, isLoading, error } = useDashboard();
|
||||
|
||||
// Real-time events - using a generic project ID for dashboard-wide events
|
||||
// In production, this would be a dedicated dashboard events endpoint
|
||||
const { connectionState } = useProjectEvents('dashboard', {
|
||||
autoConnect: true,
|
||||
});
|
||||
const events = useProjectEventsFromStore('dashboard');
|
||||
|
||||
// Get user's first name for empty state
|
||||
const firstName = user?.first_name || user?.email?.split('@')[0] || 'there';
|
||||
|
||||
// Handle approval actions
|
||||
const handleApprove = useCallback((approval: PendingApproval) => {
|
||||
// TODO: Implement actual approval API call
|
||||
toast.success(`Approved: ${approval.title}`, {
|
||||
description: `${approval.projectName}`,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleReject = useCallback((approval: PendingApproval) => {
|
||||
// TODO: Implement actual rejection API call
|
||||
toast.info(`Rejected: ${approval.title}`, {
|
||||
description: `${approval.projectName}`,
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Show error state
|
||||
if (error) {
|
||||
toast.error('Failed to load dashboard data', {
|
||||
description: 'Please try refreshing the page',
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user has no projects (empty state)
|
||||
const hasNoProjects = !isLoading && (!data?.recentProjects || data.recentProjects.length === 0);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="container mx-auto px-4 py-6">
|
||||
{/* Welcome Header - always shown */}
|
||||
<WelcomeHeader className="mb-6" />
|
||||
|
||||
{hasNoProjects ? (
|
||||
// Empty state for new users
|
||||
<EmptyState userName={firstName} />
|
||||
) : (
|
||||
// Main dashboard layout
|
||||
<div className="grid gap-6 lg:grid-cols-[1fr_350px]">
|
||||
{/* Main Content */}
|
||||
<div className="space-y-6">
|
||||
{/* Quick Stats */}
|
||||
<DashboardQuickStats stats={data?.stats} isLoading={isLoading} />
|
||||
|
||||
{/* Recent Projects */}
|
||||
<RecentProjects projects={data?.recentProjects} isLoading={isLoading} />
|
||||
|
||||
{/* Pending Approvals */}
|
||||
<PendingApprovals
|
||||
approvals={data?.pendingApprovals}
|
||||
isLoading={isLoading}
|
||||
onApprove={handleApprove}
|
||||
onReject={handleReject}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Activity Feed Sidebar */}
|
||||
<div className="hidden lg:block">
|
||||
<Card className="sticky top-4">
|
||||
<ActivityFeed
|
||||
events={events}
|
||||
connectionState={connectionState}
|
||||
isLoading={isLoading}
|
||||
maxHeight={600}
|
||||
showHeader
|
||||
title="Recent Activity"
|
||||
enableFiltering={false}
|
||||
enableSearch={false}
|
||||
compact
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user