Spaces:
Configuration error
Configuration error
| "use client" | |
| import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" | |
| import { Badge } from "@/components/ui/badge" | |
| import { Play, Star, GitFork, FileText, TrendingUp, Zap, BarChart3 } from "lucide-react" | |
| import Link from "next/link" | |
| interface AnalyticsData { | |
| prompts: { | |
| id: string | |
| title: string | |
| slug: string | |
| totalRuns: number | |
| starsCount: number | |
| remixesCount: number | |
| createdAt: Date | |
| }[] | |
| dailyData: { date: string; runs: number }[] | |
| modelUsage: Record<string, number> | |
| totalTokens: number | |
| totals: { | |
| runs: number | |
| stars: number | |
| remixes: number | |
| prompts: number | |
| } | |
| } | |
| interface AnalyticsDashboardProps { | |
| data: AnalyticsData | |
| } | |
| export function AnalyticsDashboard({ data }: AnalyticsDashboardProps) { | |
| const maxRuns = Math.max(...data.dailyData.map((d) => d.runs), 1) | |
| return ( | |
| <div className="space-y-8"> | |
| {/* Summary Stats */} | |
| <div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-4"> | |
| <Card> | |
| <CardContent className="pt-6"> | |
| <div className="flex items-center justify-between"> | |
| <div> | |
| <p className="text-sm text-muted-foreground">Total Prompts</p> | |
| <p className="text-3xl font-bold">{data.totals.prompts}</p> | |
| </div> | |
| <FileText className="h-8 w-8 text-muted-foreground/30" /> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| <Card> | |
| <CardContent className="pt-6"> | |
| <div className="flex items-center justify-between"> | |
| <div> | |
| <p className="text-sm text-muted-foreground">Total Runs</p> | |
| <p className="text-3xl font-bold">{data.totals.runs.toLocaleString()}</p> | |
| </div> | |
| <Play className="h-8 w-8 text-muted-foreground/30" /> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| <Card> | |
| <CardContent className="pt-6"> | |
| <div className="flex items-center justify-between"> | |
| <div> | |
| <p className="text-sm text-muted-foreground">Total Stars</p> | |
| <p className="text-3xl font-bold">{data.totals.stars.toLocaleString()}</p> | |
| </div> | |
| <Star className="h-8 w-8 text-muted-foreground/30" /> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| <Card> | |
| <CardContent className="pt-6"> | |
| <div className="flex items-center justify-between"> | |
| <div> | |
| <p className="text-sm text-muted-foreground">Total Remixes</p> | |
| <p className="text-3xl font-bold">{data.totals.remixes.toLocaleString()}</p> | |
| </div> | |
| <GitFork className="h-8 w-8 text-muted-foreground/30" /> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| </div> | |
| {/* Charts Row */} | |
| <div className="grid lg:grid-cols-2 gap-6"> | |
| {/* Runs per Day Chart */} | |
| <Card> | |
| <CardHeader> | |
| <CardTitle className="flex items-center gap-2"> | |
| <TrendingUp className="h-5 w-5" /> | |
| Runs Per Day | |
| </CardTitle> | |
| <CardDescription>Last 30 days</CardDescription> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="flex items-end gap-1 h-[200px]"> | |
| {data.dailyData.map((day, i) => ( | |
| <div | |
| key={day.date} | |
| className="flex-1 group relative" | |
| title={`${day.date}: ${day.runs} runs`} | |
| > | |
| <div | |
| className="bg-primary/80 hover:bg-primary rounded-t transition-colors" | |
| style={{ | |
| height: `${(day.runs / maxRuns) * 100}%`, | |
| minHeight: day.runs > 0 ? "4px" : "1px", | |
| }} | |
| /> | |
| <div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-foreground text-background px-2 py-1 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap"> | |
| {day.runs} runs | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| <div className="flex justify-between text-xs text-muted-foreground mt-2"> | |
| <span>30 days ago</span> | |
| <span>Today</span> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| {/* Model Usage */} | |
| <Card> | |
| <CardHeader> | |
| <CardTitle className="flex items-center gap-2"> | |
| <Zap className="h-5 w-5" /> | |
| Model Usage | |
| </CardTitle> | |
| <CardDescription> | |
| {data.totalTokens.toLocaleString()} total tokens used | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="space-y-4"> | |
| {Object.entries(data.modelUsage).length > 0 ? ( | |
| Object.entries(data.modelUsage) | |
| .sort(([, a], [, b]) => b - a) | |
| .map(([model, count]) => { | |
| const percentage = Math.round( | |
| (count / Object.values(data.modelUsage).reduce((a, b) => a + b, 0)) * 100 | |
| ) | |
| return ( | |
| <div key={model}> | |
| <div className="flex justify-between text-sm mb-1"> | |
| <span className="font-medium">{model}</span> | |
| <span className="text-muted-foreground"> | |
| {count} runs ({percentage}%) | |
| </span> | |
| </div> | |
| <div className="h-2 bg-muted rounded-full overflow-hidden"> | |
| <div | |
| className="h-full bg-primary rounded-full transition-all" | |
| style={{ width: `${percentage}%` }} | |
| /> | |
| </div> | |
| </div> | |
| ) | |
| }) | |
| ) : ( | |
| <div className="text-center text-muted-foreground py-8"> | |
| No usage data yet | |
| </div> | |
| )} | |
| </div> | |
| </CardContent> | |
| </Card> | |
| </div> | |
| {/* Top Prompts */} | |
| <Card> | |
| <CardHeader> | |
| <CardTitle className="flex items-center gap-2"> | |
| <BarChart3 className="h-5 w-5" /> | |
| Top Performing Prompts | |
| </CardTitle> | |
| <CardDescription>Sorted by total runs</CardDescription> | |
| </CardHeader> | |
| <CardContent> | |
| {data.prompts.length > 0 ? ( | |
| <div className="space-y-3"> | |
| {data.prompts.slice(0, 10).map((prompt, i) => ( | |
| <Link | |
| key={prompt.id} | |
| href={`/p/${prompt.slug}`} | |
| className="flex items-center gap-4 p-3 rounded-lg hover:bg-muted/50 transition-colors" | |
| > | |
| <span className="text-2xl font-bold text-muted-foreground w-8"> | |
| {i + 1} | |
| </span> | |
| <div className="flex-1 min-w-0"> | |
| <p className="font-medium truncate">{prompt.title}</p> | |
| <div className="flex gap-4 text-xs text-muted-foreground mt-1"> | |
| <span className="flex items-center gap-1"> | |
| <Play className="h-3 w-3" /> | |
| {prompt.totalRuns.toLocaleString()} runs | |
| </span> | |
| <span className="flex items-center gap-1"> | |
| <Star className="h-3 w-3" /> | |
| {prompt.starsCount} stars | |
| </span> | |
| <span className="flex items-center gap-1"> | |
| <GitFork className="h-3 w-3" /> | |
| {prompt.remixesCount} remixes | |
| </span> | |
| </div> | |
| </div> | |
| <Badge variant="outline">{prompt.totalRuns} runs</Badge> | |
| </Link> | |
| ))} | |
| </div> | |
| ) : ( | |
| <div className="text-center text-muted-foreground py-12"> | |
| <FileText className="h-12 w-12 mx-auto mb-4 opacity-20" /> | |
| <p>No prompts yet</p> | |
| <p className="text-sm mt-1"> | |
| <Link href="/create" className="text-primary hover:underline"> | |
| Create your first prompt | |
| </Link> | |
| </p> | |
| </div> | |
| )} | |
| </CardContent> | |
| </Card> | |
| </div> | |
| ) | |
| } | |