Add API endpoints and frontend components for tool management and analytics

Implement CRUD operations for tools and ratings, introduce analytics endpoints, and develop frontend components for displaying tools, ratings, and analytics data.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 776963d0-f75d-42e2-a57b-cc36bdff8495
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: feaa4ce1-5aed-4cc0-bcea-47855b615b48
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/0683fb79-a27c-485c-9333-5f4b288c4567/776963d0-f75d-42e2-a57b-cc36bdff8495/rx9K7bW
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
cheffe01
2026-05-25 13:04:53 +00:00
parent 1de22a6979
commit 036973ea32
111 changed files with 11006 additions and 96 deletions
+201
View File
@@ -0,0 +1,201 @@
import {
useGetAnalyticsSummary,
useGetTopTools,
useGetAnalyticsByCategory,
useGetRatingDistribution,
GetTopToolsMetric
} from "@workspace/api-client-react";
import { Layout } from "@/components/layout";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import {
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer,
RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, Radar, Legend,
LineChart, Line
} from "recharts";
import { BarChart3, TrendingUp, Layers, Activity } from "lucide-react";
export default function Analytics() {
const { data: summary, isLoading: loadingSummary } = useGetAnalyticsSummary();
const { data: topTools, isLoading: loadingTopTools } = useGetTopTools({
limit: 8,
metric: GetTopToolsMetric.combined
});
const { data: categoryStats, isLoading: loadingCategories } = useGetAnalyticsByCategory();
const { data: distribution, isLoading: loadingDistribution } = useGetRatingDistribution();
const topToolsChartData = topTools?.map(t => ({
name: t.tool.name,
score: Number((t.score).toFixed(2)),
ratings: t.ratingCount
})) || [];
const categoryChartData = categoryStats?.map(c => ({
category: c.category,
tools: c.toolCount,
avgScore: c.avgCombined ? Number(c.avgCombined.toFixed(2)) : 0
})) || [];
const usefulnessData = distribution?.usefulness.map(b => ({ score: `${b.score} Star`, count: b.count })) || [];
const usabilityData = distribution?.usability.map(b => ({ score: `${b.score} Star`, count: b.count })) || [];
return (
<Layout>
<div className="space-y-6 pb-10">
<div>
<h1 className="text-3xl font-bold tracking-tight mb-2">Platform Analytics</h1>
<p className="text-muted-foreground">Macro-level insights into tool performance and community engagement.</p>
</div>
{/* Top KPI Cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between mb-4">
<span className="text-sm font-medium text-muted-foreground">Total Tools Indexed</span>
<Layers className="w-4 h-4 text-muted-foreground" />
</div>
{loadingSummary ? <Skeleton className="h-8 w-16" /> : (
<div className="text-3xl font-bold">{summary?.totalTools || 0}</div>
)}
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between mb-4">
<span className="text-sm font-medium text-muted-foreground">Total Ratings Cast</span>
<Activity className="w-4 h-4 text-muted-foreground" />
</div>
{loadingSummary ? <Skeleton className="h-8 w-16" /> : (
<div className="text-3xl font-bold">{summary?.totalRatings || 0}</div>
)}
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between mb-4">
<span className="text-sm font-medium text-muted-foreground">Active Categories</span>
<BarChart3 className="w-4 h-4 text-muted-foreground" />
</div>
{loadingSummary ? <Skeleton className="h-8 w-16" /> : (
<div className="text-3xl font-bold">{summary?.categoriesCount || 0}</div>
)}
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between mb-4">
<span className="text-sm font-medium text-muted-foreground">Avg Global Score</span>
<TrendingUp className="w-4 h-4 text-muted-foreground" />
</div>
{loadingSummary ? <Skeleton className="h-8 w-16" /> : (
<div className="text-3xl font-bold">{summary?.avgCombined?.toFixed(2) || "0.00"}</div>
)}
</CardContent>
</Card>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Top Tools Chart */}
<Card className="col-span-1 lg:col-span-2">
<CardHeader>
<CardTitle>Top 8 Tools by Combined Score</CardTitle>
<CardDescription>Highest rated tools across the platform</CardDescription>
</CardHeader>
<CardContent>
{loadingTopTools ? (
<Skeleton className="h-[300px] w-full" />
) : (
<div className="h-[300px] w-full">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={topToolsChartData} margin={{ top: 20, right: 30, left: 0, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="hsl(var(--border))" />
<XAxis dataKey="name" tickLine={false} axisLine={false} />
<YAxis domain={[0, 5]} tickLine={false} axisLine={false} tickFormatter={(v) => `${v}`} />
<RechartsTooltip
cursor={{ fill: 'hsl(var(--muted))' }}
contentStyle={{ borderRadius: '8px', border: '1px solid hsl(var(--border))' }}
/>
<Bar dataKey="score" fill="hsl(var(--primary))" radius={[4, 4, 0, 0]} maxBarSize={50} />
</BarChart>
</ResponsiveContainer>
</div>
)}
</CardContent>
</Card>
{/* Category Breakdown */}
<Card>
<CardHeader>
<CardTitle>Tools by Category</CardTitle>
<CardDescription>Distribution of tools across categories</CardDescription>
</CardHeader>
<CardContent>
{loadingCategories ? (
<Skeleton className="h-[300px] w-full" />
) : (
<div className="h-[300px] w-full">
<ResponsiveContainer width="100%" height="100%">
<RadarChart cx="50%" cy="50%" outerRadius="70%" data={categoryChartData}>
<PolarGrid stroke="hsl(var(--border))" />
<PolarAngleAxis dataKey="category" tick={{ fill: 'hsl(var(--foreground))', fontSize: 12 }} />
<PolarRadiusAxis angle={30} domain={[0, 'auto']} tick={false} axisLine={false} />
<Radar name="Tools" dataKey="tools" stroke="hsl(var(--primary))" fill="hsl(var(--primary))" fillOpacity={0.3} />
<RechartsTooltip />
</RadarChart>
</ResponsiveContainer>
</div>
)}
</CardContent>
</Card>
{/* Rating Distributions */}
<Card>
<CardHeader>
<CardTitle>Global Rating Distribution</CardTitle>
<CardDescription>How users are voting across all tools</CardDescription>
</CardHeader>
<CardContent>
{loadingDistribution ? (
<Skeleton className="h-[300px] w-full" />
) : (
<div className="h-[300px] w-full flex flex-col gap-4">
<div className="flex-1">
<span className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2 block">Usefulness</span>
<ResponsiveContainer width="100%" height="100%">
<BarChart data={usefulnessData} margin={{ top: 0, right: 0, left: -20, bottom: 0 }}>
<XAxis dataKey="score" tickLine={false} axisLine={false} tick={{fontSize: 12}} />
<YAxis hide />
<RechartsTooltip cursor={{ fill: 'hsl(var(--muted))' }} />
<Bar dataKey="count" fill="hsl(var(--primary))" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</div>
<div className="flex-1">
<span className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2 block">Usability</span>
<ResponsiveContainer width="100%" height="100%">
<BarChart data={usabilityData} margin={{ top: 0, right: 0, left: -20, bottom: 0 }}>
<XAxis dataKey="score" tickLine={false} axisLine={false} tick={{fontSize: 12}} />
<YAxis hide />
<RechartsTooltip cursor={{ fill: 'hsl(var(--muted))' }} />
<Bar dataKey="count" fill="hsl(var(--chart-3))" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</div>
</div>
)}
</CardContent>
</Card>
</div>
</div>
</Layout>
);
}