source: chapterx-frontend/src/components/writer/StoryAnalytics.tsx@ a8f4a2d

main
Last change on this file since a8f4a2d was a8f4a2d, checked in by kikisrbinoska <srbinoskakristina07@…>, 11 days ago

Fixed views count

  • Property mode set to 100644
File size: 6.2 KB
Line 
1import React from 'react'
2import {
3 BarChart,
4 Bar,
5 XAxis,
6 YAxis,
7 CartesianGrid,
8 Tooltip,
9 ResponsiveContainer,
10} from 'recharts'
11import { Heart, MessageCircle, BookOpen, Eye } from 'lucide-react'
12import { Story } from '../../types'
13
14interface Props {
15 stories: Story[]
16}
17
18const StatCard: React.FC<{ icon: React.ReactNode; label: string; value: string | number; color: string }> = ({
19 icon, label, value, color,
20}) => (
21 <div className="p-4 bg-slate-800 rounded-xl border border-slate-700">
22 <div className={`w-10 h-10 rounded-xl ${color} flex items-center justify-center mb-3`}>
23 {icon}
24 </div>
25 <p className="text-2xl font-bold text-white">{typeof value === 'number' ? value.toLocaleString() : value}</p>
26 <p className="text-slate-400 text-sm mt-0.5">{label}</p>
27 </div>
28)
29
30const CustomTooltip = ({ active, payload, label }: any) => {
31 if (active && payload && payload.length) {
32 return (
33 <div className="bg-slate-800 border border-slate-700 rounded-xl px-3 py-2 text-sm">
34 <p className="text-slate-400 mb-1">{label}</p>
35 {payload.map((p: any) => (
36 <p key={p.dataKey} className="text-white font-medium">
37 {p.name}: {p.value.toLocaleString()}
38 </p>
39 ))}
40 </div>
41 )
42 }
43 return null
44}
45
46export 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 const totalViews = stories.reduce((a, s) => a + s.total_views, 0)
53
54 const sortedPublished = [...published].sort(
55 (a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
56 )
57
58 const likesData = sortedPublished.map(s => ({
59 date: new Date(s.created_at).toLocaleDateString('en-US', { month: 'short', year: '2-digit' }),
60 likes: s.total_likes,
61 story: s.title,
62 }))
63
64 const viewsData = sortedPublished.map(s => ({
65 date: new Date(s.created_at).toLocaleDateString('en-US', { month: 'short', year: '2-digit' }),
66 views: s.total_views,
67 story: s.title,
68 }))
69
70 if (published.length === 0) {
71 return (
72 <div className="text-center py-12 text-slate-500">
73 <p>No published stories yet — analytics will appear here once you publish.</p>
74 </div>
75 )
76 }
77
78 return (
79 <div className="space-y-6">
80 {/* Stat cards */}
81 <div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
82 <StatCard icon={<Eye size={18} className="text-cyan-300" />} label="Total Views" value={totalViews} color="bg-cyan-500/20" />
83 <StatCard icon={<Heart size={18} className="text-rose-300" />} label="Total Likes" value={totalLikes} color="bg-rose-500/20" />
84 <StatCard icon={<MessageCircle size={18} className="text-violet-300" />} label="Total Comments" value={totalComments} color="bg-violet-500/20" />
85 <StatCard icon={<BookOpen size={18} className="text-emerald-300" />} label="Total Chapters" value={totalChapters} color="bg-emerald-500/20" />
86 </div>
87
88 {/* Views chart */}
89 <div className="bg-slate-800 border border-slate-700 rounded-2xl p-6">
90 <div className="flex items-center gap-2 mb-4">
91 <Eye size={16} className="text-cyan-400" />
92 <h3 className="text-white font-semibold">Views per Story</h3>
93 </div>
94 {viewsData.length > 0 ? (
95 <ResponsiveContainer width="100%" height={200}>
96 <BarChart data={viewsData}>
97 <CartesianGrid strokeDasharray="3 3" stroke="#334155" />
98 <XAxis dataKey="date" tick={{ fill: '#64748b', fontSize: 11 }} tickLine={false} axisLine={false} />
99 <YAxis tick={{ fill: '#64748b', fontSize: 11 }} tickLine={false} axisLine={false} />
100 <Tooltip content={<CustomTooltip />} />
101 <Bar dataKey="views" fill="#06b6d4" radius={[4, 4, 0, 0]} name="Views" />
102 </BarChart>
103 </ResponsiveContainer>
104 ) : (
105 <p className="text-slate-500 text-sm text-center py-8">No views yet</p>
106 )}
107 </div>
108
109 {/* Likes chart */}
110 <div className="bg-slate-800 border border-slate-700 rounded-2xl p-6">
111 <div className="flex items-center gap-2 mb-4">
112 <Heart size={16} className="text-rose-400" />
113 <h3 className="text-white font-semibold">Likes per Story</h3>
114 </div>
115 {likesData.length > 0 ? (
116 <ResponsiveContainer width="100%" height={200}>
117 <BarChart data={likesData}>
118 <CartesianGrid strokeDasharray="3 3" stroke="#334155" />
119 <XAxis dataKey="date" tick={{ fill: '#64748b', fontSize: 11 }} tickLine={false} axisLine={false} />
120 <YAxis tick={{ fill: '#64748b', fontSize: 11 }} tickLine={false} axisLine={false} />
121 <Tooltip content={<CustomTooltip />} />
122 <Bar dataKey="likes" fill="#f43f5e" radius={[4, 4, 0, 0]} name="Likes" />
123 </BarChart>
124 </ResponsiveContainer>
125 ) : (
126 <p className="text-slate-500 text-sm text-center py-8">No likes data yet</p>
127 )}
128 </div>
129
130 {/* Per-story breakdown */}
131 {published.length > 1 && (
132 <div className="bg-slate-800 border border-slate-700 rounded-2xl p-6">
133 <h3 className="text-white font-semibold mb-4">Story Breakdown</h3>
134 <div className="space-y-3">
135 {[...published]
136 .sort((a, b) => b.total_likes - a.total_likes)
137 .map(s => (
138 <div key={s.story_id} className="flex items-center justify-between text-sm">
139 <span className="text-slate-300 truncate max-w-xs">{s.title}</span>
140 <div className="flex items-center gap-4 text-slate-400 flex-shrink-0">
141 <span className="flex items-center gap-1"><Eye size={12} /> {s.total_views.toLocaleString()}</span>
142 <span className="flex items-center gap-1"><Heart size={12} /> {s.total_likes}</span>
143 <span className="flex items-center gap-1"><MessageCircle size={12} /> {s.total_comments}</span>
144 </div>
145 </div>
146 ))}
147 </div>
148 </div>
149 )}
150 </div>
151 )
152}
Note: See TracBrowser for help on using the repository browser.