| // src/components/analytics/AnalyticsView.tsx | |
| import { useState } from "react"; | |
| import { Link } from "react-router-dom"; | |
| import { | |
| LineChart, Line, AreaChart, Area, BarChart, Bar, | |
| XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, | |
| PieChart, Pie, Cell | |
| } from "recharts"; | |
| import { motion } from "framer-motion"; | |
| import { AnalyticsSummary, AnalyticsDataPoint, ArticleAnalytics } from "@/hooks/useAnalytics"; | |
| import { Calendar, TrendingUp, BookOpen, Users, ArrowUp } from "lucide-react"; | |
| interface AnalyticsViewProps { | |
| data: AnalyticsSummary | null; | |
| onRefresh: () => void; | |
| } | |
| const COLORS = ["#6366f1", "#ec4899", "#f59e0b", "#10b981"]; | |
| export function AnalyticsView({ data, onRefresh }: AnalyticsViewProps) { | |
| const [chartType, setChartType] = useState<"line" | "area" | "bar">("line"); | |
| if (!data) return null; | |
| return ( | |
| <motion.div | |
| initial={{ opacity: 0, y: 20 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| transition={{ duration: 0.4 }} | |
| className="space-y-8" | |
| > | |
| {/* Period selector (if needed) – we can leave it outside the component, but here it's fixed */} | |
| <div className="flex justify-end items-center gap-2 text-sm"> | |
| <span className="text-gray-500">Chart type:</span> | |
| <button | |
| onClick={() => setChartType("line")} | |
| className={`px-3 py-1 rounded-full ${chartType === "line" ? "bg-primary text-white" : "bg-gray-100 dark:bg-gray-800"}`} | |
| > | |
| Line | |
| </button> | |
| <button | |
| onClick={() => setChartType("area")} | |
| className={`px-3 py-1 rounded-full ${chartType === "area" ? "bg-primary text-white" : "bg-gray-100 dark:bg-gray-800"}`} | |
| > | |
| Area | |
| </button> | |
| <button | |
| onClick={() => setChartType("bar")} | |
| className={`px-3 py-1 rounded-full ${chartType === "bar" ? "bg-primary text-white" : "bg-gray-100 dark:bg-gray-800"}`} | |
| > | |
| Bar | |
| </button> | |
| </div> | |
| {/* Daily trends chart */} | |
| <div className="bg-white dark:bg-gray-800 rounded-2xl shadow p-5"> | |
| <h3 className="font-semibold text-lg mb-4 flex items-center gap-2"> | |
| <TrendingUp className="w-5 h-5 text-primary" /> | |
| Daily engagement | |
| </h3> | |
| <div className="h-80"> | |
| <ResponsiveContainer width="100%" height="100%"> | |
| {chartType === "line" && ( | |
| <LineChart data={data.dailyData}> | |
| <CartesianGrid strokeDasharray="3 3" stroke="#374151" /> | |
| <XAxis dataKey="date" stroke="#9ca3af" /> | |
| <YAxis stroke="#9ca3af" /> | |
| <Tooltip contentStyle={{ backgroundColor: "#1f2937", border: "none", borderRadius: "8px" }} /> | |
| <Legend /> | |
| <Line type="monotone" dataKey="views" stroke="#6366f1" name="Views" /> | |
| <Line type="monotone" dataKey="likes" stroke="#ec4899" name="Likes" /> | |
| <Line type="monotone" dataKey="comments" stroke="#10b981" name="Comments" /> | |
| </LineChart> | |
| )} | |
| {chartType === "area" && ( | |
| <AreaChart data={data.dailyData}> | |
| <CartesianGrid strokeDasharray="3 3" /> | |
| <XAxis dataKey="date" /> | |
| <YAxis /> | |
| <Tooltip /> | |
| <Legend /> | |
| <Area type="monotone" dataKey="views" stackId="1" stroke="#6366f1" fill="#6366f1" fillOpacity={0.3} /> | |
| <Area type="monotone" dataKey="likes" stackId="2" stroke="#ec4899" fill="#ec4899" fillOpacity={0.3} /> | |
| <Area type="monotone" dataKey="comments" stackId="3" stroke="#10b981" fill="#10b981" fillOpacity={0.3} /> | |
| </AreaChart> | |
| )} | |
| {chartType === "bar" && ( | |
| <BarChart data={data.dailyData}> | |
| <CartesianGrid strokeDasharray="3 3" /> | |
| <XAxis dataKey="date" /> | |
| <YAxis /> | |
| <Tooltip /> | |
| <Legend /> | |
| <Bar dataKey="views" fill="#6366f1" /> | |
| <Bar dataKey="likes" fill="#ec4899" /> | |
| <Bar dataKey="comments" fill="#10b981" /> | |
| </BarChart> | |
| )} | |
| </ResponsiveContainer> | |
| </div> | |
| </div> | |
| {/* Top performing articles */} | |
| <div className="bg-white dark:bg-gray-800 rounded-2xl shadow p-5"> | |
| <h3 className="font-semibold text-lg mb-4 flex items-center gap-2"> | |
| <BookOpen className="w-5 h-5 text-primary" /> | |
| Top performing articles | |
| </h3> | |
| {data.topPerforming.length === 0 ? ( | |
| <p className="text-gray-500 text-sm">No data yet. Publish articles to see stats.</p> | |
| ) : ( | |
| <div className="space-y-3"> | |
| {data.topPerforming.map((article, idx) => ( | |
| <Link | |
| key={article.articleId} | |
| to={`/articles/${article.articleId}`} | |
| className="block p-3 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-700/50 transition" | |
| > | |
| <div className="flex flex-wrap justify-between items-start gap-2"> | |
| <div className="flex-1 min-w-0"> | |
| <div className="font-medium truncate">{article.title}</div> | |
| <div className="text-xs text-gray-500 mt-1"> | |
| {article.views} views · {article.likes} likes · {article.comments} comments | |
| </div> | |
| </div> | |
| {idx === 0 && <span className="text-yellow-500 text-xs font-bold">🏆 Top</span>} | |
| </div> | |
| </Link> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| {/* Engagement distribution (pie) */} | |
| <div className="bg-white dark:bg-gray-800 rounded-2xl shadow p-5"> | |
| <h3 className="font-semibold text-lg mb-4 flex items-center gap-2"> | |
| <Users className="w-5 h-5 text-primary" /> | |
| Engagement breakdown | |
| </h3> | |
| <div className="h-64"> | |
| <ResponsiveContainer width="100%" height="100%"> | |
| <PieChart> | |
| <Pie | |
| data={[ | |
| { name: "Views", value: data.totalViews }, | |
| { name: "Likes", value: data.totalLikes }, | |
| { name: "Comments", value: data.totalComments }, | |
| ]} | |
| cx="50%" | |
| cy="50%" | |
| labelLine={false} | |
| label={({ name, percent }) => `${name}: ${((percent ?? 0) * 100).toFixed(0)}%`} | |
| outerRadius={80} | |
| fill="#8884d8" | |
| dataKey="value" | |
| > | |
| {COLORS.map((color, i) => ( | |
| <Cell key={`cell-${i}`} fill={color} /> | |
| ))} | |
| </Pie> | |
| <Tooltip /> | |
| </PieChart> | |
| </ResponsiveContainer> | |
| </div> | |
| </div> | |
| </motion.div> | |
| ); | |
| } | |
Xet Storage Details
- Size:
- 6.88 kB
- Xet hash:
- 96f621cbeb79a446d05d6ee028fffe007c4d46f29e9402026540be35c37e7331
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.