- Timestamp:
- 06/24/26 16:28:50 (11 days ago)
- Branches:
- main
- Children:
- a8f4a2d
- Parents:
- 0b502c2
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
chapterx-frontend/src/components/writer/StoryAnalytics.tsx
r0b502c2 r99c1e45 1 1 import React from 'react' 2 2 import { 3 LineChart,4 Line,5 3 BarChart, 6 4 Bar, … … 11 9 ResponsiveContainer, 12 10 } from 'recharts' 13 import { Eye, Heart, MessageCircle, TrendingUp, Clock, BarChart2 } from 'lucide-react' 14 import { mockAnalytics } from '../../data/mockData' 11 import { Heart, MessageCircle, BookOpen } from 'lucide-react' 12 import { Story } from '../../types' 13 14 interface Props { 15 stories: Story[] 16 } 15 17 16 18 const StatCard: React.FC<{ icon: React.ReactNode; label: string; value: string | number; color: string }> = ({ 17 19 icon, label, value, color, 18 20 }) => ( 19 <div className= {`p-4 bg-slate-800 rounded-xl border border-slate-700`}>21 <div className="p-4 bg-slate-800 rounded-xl border border-slate-700"> 20 22 <div className={`w-10 h-10 rounded-xl ${color} flex items-center justify-center mb-3`}> 21 23 {icon} 22 24 </div> 23 <p className="text-2xl font-bold text-white">{ value.toLocaleString()}</p>25 <p className="text-2xl font-bold text-white">{typeof value === 'number' ? value.toLocaleString() : value}</p> 24 26 <p className="text-slate-400 text-sm mt-0.5">{label}</p> 25 27 </div> … … 42 44 } 43 45 44 export const StoryAnalytics: React.FC = () => { 45 const analytics = mockAnalytics 46 const viewsData = analytics.views_over_time.filter((_, i) => i % 5 === 0) 47 const likesData = analytics.likes_over_time.filter((_, i) => i % 5 === 0) 46 export const StoryAnalytics: React.FC<Props> = ({ stories }) => { 47 const published = stories.filter(s => s.status === 'published') 48 49 const totalLikes = stories.reduce((a, s) => a + s.total_likes, 0) 50 const totalComments = stories.reduce((a, s) => a + s.total_comments, 0) 51 const totalChapters = stories.reduce((a, s) => a + s.total_chapters, 0) 52 53 // Likes per story (sorted by created_at) 54 const likesData = [...published] 55 .sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()) 56 .map(s => ({ 57 date: new Date(s.created_at).toLocaleDateString('en-US', { month: 'short', year: '2-digit' }), 58 likes: s.total_likes, 59 story: s.title, 60 })) 61 62 if (published.length === 0) { 63 return ( 64 <div className="text-center py-12 text-slate-500"> 65 <p>No published stories yet — analytics will appear here once you publish.</p> 66 </div> 67 ) 68 } 48 69 49 70 return ( 50 71 <div className="space-y-6"> 51 72 {/* Stat cards */} 52 <div className="grid grid-cols-2 lg:grid-cols-5 gap-4"> 53 <StatCard icon={<Eye size={18} className="text-blue-300" />} label="Total Views" value={analytics.total_views} color="bg-blue-500/20" /> 54 <StatCard icon={<Heart size={18} className="text-rose-300" />} label="Total Likes" value={analytics.total_likes} color="bg-rose-500/20" /> 55 <StatCard icon={<MessageCircle size={18} className="text-violet-300" />} label="Comments" value={analytics.total_comments} color="bg-violet-500/20" /> 56 <StatCard icon={<Clock size={18} className="text-amber-300" />} label="Avg Read (min)" value={analytics.avg_read_time} color="bg-amber-500/20" /> 57 <StatCard icon={<BarChart2 size={18} className="text-emerald-300" />} label="Completion %" value={`${analytics.completion_rate}%`} color="bg-emerald-500/20" /> 58 </div> 59 60 {/* Views chart */} 61 <div className="bg-slate-800 border border-slate-700 rounded-2xl p-6"> 62 <div className="flex items-center gap-2 mb-4"> 63 <TrendingUp size={16} className="text-blue-400" /> 64 <h3 className="text-white font-semibold">Views Over Time</h3> 65 </div> 66 <ResponsiveContainer width="100%" height={200}> 67 <LineChart data={viewsData}> 68 <CartesianGrid strokeDasharray="3 3" stroke="#334155" /> 69 <XAxis dataKey="date" tick={{ fill: '#64748b', fontSize: 11 }} tickLine={false} axisLine={false} /> 70 <YAxis tick={{ fill: '#64748b', fontSize: 11 }} tickLine={false} axisLine={false} /> 71 <Tooltip content={<CustomTooltip />} /> 72 <Line type="monotone" dataKey="views" stroke="#6366f1" strokeWidth={2} dot={false} name="Views" /> 73 </LineChart> 74 </ResponsiveContainer> 73 <div className="grid grid-cols-2 lg:grid-cols-3 gap-4"> 74 <StatCard icon={<Heart size={18} className="text-rose-300" />} label="Total Likes" value={totalLikes} color="bg-rose-500/20" /> 75 <StatCard icon={<MessageCircle size={18} className="text-violet-300" />} label="Total Comments" value={totalComments} color="bg-violet-500/20" /> 76 <StatCard icon={<BookOpen size={18} className="text-emerald-300" />} label="Total Chapters" value={totalChapters} color="bg-emerald-500/20" /> 75 77 </div> 76 78 … … 79 81 <div className="flex items-center gap-2 mb-4"> 80 82 <Heart size={16} className="text-rose-400" /> 81 <h3 className="text-white font-semibold">Likes Over Time</h3>83 <h3 className="text-white font-semibold">Likes per Story</h3> 82 84 </div> 83 <ResponsiveContainer width="100%" height={200}> 84 <BarChart data={likesData}> 85 <CartesianGrid strokeDasharray="3 3" stroke="#334155" /> 86 <XAxis dataKey="date" tick={{ fill: '#64748b', fontSize: 11 }} tickLine={false} axisLine={false} /> 87 <YAxis tick={{ fill: '#64748b', fontSize: 11 }} tickLine={false} axisLine={false} /> 88 <Tooltip content={<CustomTooltip />} /> 89 <Bar dataKey="likes" fill="#f43f5e" radius={[4, 4, 0, 0]} name="Likes" /> 90 </BarChart> 91 </ResponsiveContainer> 85 {likesData.length > 0 ? ( 86 <ResponsiveContainer width="100%" height={200}> 87 <BarChart data={likesData}> 88 <CartesianGrid strokeDasharray="3 3" stroke="#334155" /> 89 <XAxis dataKey="date" tick={{ fill: '#64748b', fontSize: 11 }} tickLine={false} axisLine={false} /> 90 <YAxis tick={{ fill: '#64748b', fontSize: 11 }} tickLine={false} axisLine={false} /> 91 <Tooltip content={<CustomTooltip />} /> 92 <Bar dataKey="likes" fill="#f43f5e" radius={[4, 4, 0, 0]} name="Likes" /> 93 </BarChart> 94 </ResponsiveContainer> 95 ) : ( 96 <p className="text-slate-500 text-sm text-center py-8">No likes data yet</p> 97 )} 92 98 </div> 99 100 {/* Per-story breakdown */} 101 {published.length > 1 && ( 102 <div className="bg-slate-800 border border-slate-700 rounded-2xl p-6"> 103 <h3 className="text-white font-semibold mb-4">Story Breakdown</h3> 104 <div className="space-y-3"> 105 {[...published] 106 .sort((a, b) => b.total_likes - a.total_likes) 107 .map(s => ( 108 <div key={s.story_id} className="flex items-center justify-between text-sm"> 109 <span className="text-slate-300 truncate max-w-xs">{s.title}</span> 110 <div className="flex items-center gap-4 text-slate-400 flex-shrink-0"> 111 <span className="flex items-center gap-1"><Heart size={12} /> {s.total_likes}</span> 112 <span className="flex items-center gap-1"><MessageCircle size={12} /> {s.total_comments}</span> 113 </div> 114 </div> 115 ))} 116 </div> 117 </div> 118 )} 93 119 </div> 94 120 )
Note:
See TracChangeset
for help on using the changeset viewer.
