Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions client/src/components/category-overview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import type { Category } from "@shared/schema";

interface CategoryOverviewProps {
categories: Category[];
loading: boolean;
}

export function CategoryOverview({ categories, loading }: CategoryOverviewProps) {
if (loading) {
return (
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<Skeleton className="h-6 w-32" />
<Skeleton className="h-8 w-16" />
</div>
</CardHeader>
<CardContent>
<div className="space-y-4">
{[...Array(4)].map((_, i) => (
<div key={i} className="flex items-center justify-between p-4 bg-gray-50 rounded-lg">
<div className="flex items-center space-x-3">
<Skeleton className="w-4 h-4 rounded-full" />
<div className="space-y-1">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-3 w-32" />
</div>
</div>
<div className="text-right space-y-1">
<Skeleton className="h-4 w-8" />
<Skeleton className="h-3 w-8" />
</div>
</div>
))}
</div>
</CardContent>
</Card>
);
}

const getCategoryColor = (color: string) => {
switch (color) {
case "#34A853": return "bg-green-500";
case "#4285F4": return "bg-blue-500";
case "#FBBC04": return "bg-yellow-500";
case "#EA4335": return "bg-red-500";
case "#9C27B0": return "bg-purple-500";
case "#00BCD4": return "bg-cyan-500";
default: return "bg-gray-500";
}
};

const totalEmails = categories.reduce((sum, category) => sum + (category.count || 0), 0);

return (
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="text-lg font-medium text-gray-900">Email Categories</CardTitle>
<Button variant="ghost" size="sm" className="text-blue-600 hover:text-blue-700">
View All
</Button>
</div>
</CardHeader>
<CardContent>
<div className="space-y-4">
{categories.map((category) => {
const percentage = totalEmails > 0 ? Math.round(((category.count || 0) / totalEmails) * 100) : 0;

return (
<div key={category.id} className="flex items-center justify-between p-4 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors">
<div className="flex items-center space-x-3">
<div className={`w-4 h-4 rounded-full ${getCategoryColor(category.color)}`}></div>
<div>
<p className="font-medium text-gray-900">{category.name}</p>
<p className="text-sm text-gray-600">{category.description}</p>
</div>
</div>
<div className="text-right">
<p className="font-semibold text-gray-900">{category.count || 0}</p>
<p className="text-sm text-gray-600">{percentage}%</p>
</div>
</div>
);
})}
</div>
</CardContent>
</Card>
);
}
7 changes: 3 additions & 4 deletions client/src/components/email-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import type { EmailWithCategory } from "@shared/schema";
interface EmailListProps {
emails: EmailWithCategory[];
loading: boolean;
onEmailSelect: (email: EmailWithCategory) => void;
}

/**
Expand All @@ -17,9 +16,8 @@ interface EmailListProps {
*
* @param emails - Array of email objects with category and metadata to display.
* @param loading - Whether to show loading skeletons instead of email content.
* @param onEmailSelect - Callback function to execute when an email is selected.
*/
export function EmailList({ emails, loading, onEmailSelect }: EmailListProps) {
export function EmailList({ emails, loading }: EmailListProps) {
if (loading) {
return (
<div className="divide-y divide-gray-200">
Expand Down Expand Up @@ -66,7 +64,8 @@ export function EmailList({ emails, loading, onEmailSelect }: EmailListProps) {
};

const handleEmailClick = (email: EmailWithCategory) => {
onEmailSelect(email);
// In a real app, this would open the email detail view or redirect to Gmail
console.log("Opening email:", email.subject);
};

if (emails.length === 0) {
Expand Down
63 changes: 63 additions & 0 deletions client/src/components/recent-activity.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { formatDistanceToNow } from "date-fns";
import type { Activity } from "@shared/schema";

interface RecentActivityProps {
activities: Activity[];
}

export function RecentActivity({ activities }: RecentActivityProps) {
const getActivityIcon = (type: string) => {
switch (type) {
case "label": return "🏷️";
case "category": return "🧠";
case "sync": return "🔄";
case "review": return "⚠️";
default: return "📧";
}
};

const getActivityBadgeColor = (type: string) => {
switch (type) {
case "label": return "bg-green-50 text-green-700";
case "category": return "bg-blue-50 text-blue-700";
case "sync": return "bg-purple-50 text-purple-700";
case "review": return "bg-yellow-50 text-yellow-700";
default: return "bg-gray-50 text-gray-700";
}
};

return (
<Card>
<CardHeader>
<CardTitle className="text-lg font-medium text-gray-900">Recent AI Activity</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{activities.slice(0, 6).map((activity) => (
<div key={activity.id} className="flex items-start space-x-3">
<div className={`w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 ${getActivityBadgeColor(activity.type)}`}>
<span className="text-xs">{getActivityIcon(activity.type)}</span>
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-900">{activity.description}</p>
{activity.details && (
<p className="text-xs text-gray-600">{activity.details}</p>
)}
<p className="text-xs text-gray-500 mt-1">
{formatDistanceToNow(new Date(activity.timestamp), { addSuffix: true })}
</p>
</div>
</div>
))}
</div>

<Button variant="ghost" className="w-full mt-4 text-blue-600 hover:text-blue-700">
View All Activity
</Button>
</CardContent>
</Card>
);
}
106 changes: 106 additions & 0 deletions client/src/components/stats-cards.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { Card, CardContent } from "@/components/ui/card";
import { Mail, Tag, Brain, Clock } from "lucide-react";
import { Skeleton } from "@/components/ui/skeleton";
import type { DashboardStats } from "@shared/schema";

interface StatsCardsProps {
stats?: DashboardStats;
loading: boolean;
}

export function StatsCards({ stats, loading }: StatsCardsProps) {
if (loading) {
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
{[...Array(4)].map((_, i) => (
<Card key={i}>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="space-y-2">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-8 w-16" />
</div>
<Skeleton className="w-12 h-12 rounded-full" />
</div>
<div className="mt-4">
<Skeleton className="h-4 w-20" />
</div>
</CardContent>
</Card>
))}
</div>
);
}

if (!stats) return null;

const statsData = [
{
title: "Total Emails Analyzed",
value: stats.totalEmails.toLocaleString(),
icon: Mail,
iconBg: "bg-blue-50",
iconColor: "text-blue-600",
change: `↑ ${stats.weeklyGrowth.totalEmails}%`,
changeText: "from last week",
changeColor: "text-green-600",
},
{
title: "Auto-Labeled",
value: stats.autoLabeled.toLocaleString(),
icon: Tag,
iconBg: "bg-green-50",
iconColor: "text-green-600",
change: `↑ ${stats.weeklyGrowth.autoLabeled}%`,
changeText: "accuracy rate",
changeColor: "text-green-600",
},
{
title: "Categories Created",
value: stats.categories.toString(),
icon: Brain,
iconBg: "bg-purple-50",
iconColor: "text-purple-600",
change: `+${stats.weeklyGrowth.categories}`,
changeText: "this week",
changeColor: "text-green-600",
},
{
title: "Time Saved",
value: stats.timeSaved,
icon: Clock,
iconBg: "bg-yellow-50",
iconColor: "text-yellow-600",
change: `↑ ${stats.weeklyGrowth.timeSaved}%`,
changeText: "this month",
changeColor: "text-green-600",
},
];

return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
{statsData.map((stat, index) => {
const Icon = stat.icon;
return (
<Card key={index} className="hover:shadow-md transition-shadow">
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-gray-600 text-sm font-medium">{stat.title}</p>
<p className="text-2xl font-semibold text-gray-900 mt-1">{stat.value}</p>
</div>
<div className={`w-12 h-12 ${stat.iconBg} rounded-full flex items-center justify-center`}>
<Icon className={`h-6 w-6 ${stat.iconColor}`} />
</div>
</div>
<div className="mt-4 flex items-center text-sm">
<span className={stat.changeColor}>{stat.change}</span>
<span className="text-gray-600 ml-1">{stat.changeText}</span>
</div>
</CardContent>
</Card>
);
})}
</div>
);
}
Loading