refactor: replace bar chart with pie chart for frustration trend

This commit is contained in:
Usman Baig
2026-03-12 18:09:34 +01:00
parent 2f01be1c67
commit 0889079372

View File

@@ -1,7 +1,22 @@
'use client' 'use client'
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Cell } from 'recharts'
import { TrendUp } from '@phosphor-icons/react' import { TrendUp } from '@phosphor-icons/react'
import { Pie, PieChart } from 'recharts'
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from '@/components/ui/card'
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from '@/components/charts'
import type { FrustrationSummary } from '@/lib/api/stats' import type { FrustrationSummary } from '@/lib/api/stats'
interface FrustrationTrendProps { interface FrustrationTrendProps {
@@ -16,131 +31,65 @@ function SkeletonCard() {
<div className="h-5 w-36 bg-neutral-200 dark:bg-neutral-700 rounded" /> <div className="h-5 w-36 bg-neutral-200 dark:bg-neutral-700 rounded" />
<div className="h-4 w-48 bg-neutral-200 dark:bg-neutral-700 rounded" /> <div className="h-4 w-48 bg-neutral-200 dark:bg-neutral-700 rounded" />
</div> </div>
<div className="flex-1 min-h-[270px] animate-pulse flex items-end gap-6 justify-center pb-8"> <div className="flex-1 min-h-[270px] animate-pulse flex items-center justify-center">
{[120, 80, 140, 100].map((h, i) => ( <div className="w-[200px] h-[200px] rounded-full bg-neutral-200 dark:bg-neutral-700" />
<div key={i} className="w-12 bg-neutral-200 dark:bg-neutral-700 rounded-t" style={{ height: h }} />
))}
</div> </div>
</div> </div>
) )
} }
const chartConfig = {
count: {
label: 'Count',
},
rage_clicks: {
label: 'Rage Clicks',
color: '#FD5E0F',
},
dead_clicks: {
label: 'Dead Clicks',
color: '#F59E0B',
},
prev_rage_clicks: {
label: 'Prev Rage Clicks',
color: '#78350F',
},
prev_dead_clicks: {
label: 'Prev Dead Clicks',
color: '#92400E',
},
} satisfies ChartConfig
export default function FrustrationTrend({ summary, loading }: FrustrationTrendProps) { export default function FrustrationTrend({ summary, loading }: FrustrationTrendProps) {
if (loading || !summary) return <SkeletonCard /> if (loading || !summary) return <SkeletonCard />
const hasData = summary.rage_clicks > 0 || summary.dead_clicks > 0 || const hasData = summary.rage_clicks > 0 || summary.dead_clicks > 0 ||
summary.prev_rage_clicks > 0 || summary.prev_dead_clicks > 0 summary.prev_rage_clicks > 0 || summary.prev_dead_clicks > 0
const chartData = [
{
label: 'Rage',
current: summary.rage_clicks,
previous: summary.prev_rage_clicks,
},
{
label: 'Dead',
current: summary.dead_clicks,
previous: summary.prev_dead_clicks,
},
]
const totalCurrent = summary.rage_clicks + summary.dead_clicks const totalCurrent = summary.rage_clicks + summary.dead_clicks
const totalPrevious = summary.prev_rage_clicks + summary.prev_dead_clicks const totalPrevious = summary.prev_rage_clicks + summary.prev_dead_clicks
const totalChange = totalPrevious > 0 const totalChange = totalPrevious > 0
? Math.round(((totalCurrent - totalPrevious) / totalPrevious) * 100) ? Math.round(((totalCurrent - totalPrevious) / totalPrevious) * 100)
: null : null
return ( const chartData = [
<div className="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-2xl p-6 h-full flex flex-col"> { type: 'rage_clicks', count: summary.rage_clicks, fill: 'var(--color-rage_clicks)' },
<div className="flex items-center justify-between mb-1"> { type: 'dead_clicks', count: summary.dead_clicks, fill: 'var(--color-dead_clicks)' },
<h3 className="text-lg font-semibold text-neutral-900 dark:text-white"> { type: 'prev_rage_clicks', count: summary.prev_rage_clicks, fill: 'var(--color-prev_rage_clicks)' },
Frustration Trend { type: 'prev_dead_clicks', count: summary.prev_dead_clicks, fill: 'var(--color-prev_dead_clicks)' },
</h3> ]
</div>
<p className="text-sm text-neutral-500 dark:text-neutral-400 mb-4">
Current vs. previous period comparison
</p>
{hasData ? ( if (!hasData) {
<div className="flex-1 min-h-[270px] flex flex-col"> return (
{/* Summary line */} <div className="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-2xl p-6 h-full flex flex-col">
<div className="flex items-baseline gap-2 mb-6"> <div className="flex items-center justify-between mb-1">
<span className="text-2xl font-bold text-neutral-900 dark:text-white tabular-nums"> <h3 className="text-lg font-semibold text-neutral-900 dark:text-white">
{totalCurrent.toLocaleString()} Frustration Trend
</span> </h3>
<span className="text-sm text-neutral-400 dark:text-neutral-500">
total signals
</span>
{totalChange !== null && (
<span className={`text-xs font-medium ${
totalChange > 0
? 'text-red-600 dark:text-red-400'
: totalChange < 0
? 'text-green-600 dark:text-green-400'
: 'text-neutral-500 dark:text-neutral-400'
}`}>
{totalChange > 0 ? '+' : ''}{totalChange}%
</span>
)}
{totalChange === null && totalCurrent > 0 && (
<span className="text-xs font-medium bg-brand-orange/10 text-brand-orange px-1.5 py-0.5 rounded">
New
</span>
)}
</div>
{/* Chart */}
<div className="flex-1">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={chartData} barGap={4} barCategoryGap="30%">
<XAxis
dataKey="label"
axisLine={false}
tickLine={false}
tick={{ fill: '#a3a3a3', fontSize: 12 }}
/>
<YAxis hide />
<Tooltip
cursor={{ fill: 'transparent' }}
contentStyle={{
backgroundColor: '#171717',
border: '1px solid #404040',
borderRadius: 8,
fontSize: 12,
color: '#fff',
}}
formatter={(value: number, name: string) => [
value.toLocaleString(),
name === 'current' ? 'Current' : 'Previous',
]}
/>
<Bar dataKey="previous" radius={[4, 4, 0, 0]} maxBarSize={40}>
{chartData.map((_, i) => (
<Cell key={i} fill="#404040" />
))}
</Bar>
<Bar dataKey="current" radius={[4, 4, 0, 0]} maxBarSize={40}>
{chartData.map((_, i) => (
<Cell key={i} fill="#FD5E0F" />
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
{/* Legend */}
<div className="flex items-center justify-center gap-6 mt-4 text-xs text-neutral-400 dark:text-neutral-500">
<div className="flex items-center gap-1.5">
<div className="w-2.5 h-2.5 rounded-sm bg-[#FD5E0F]" />
<span>Current period</span>
</div>
<div className="flex items-center gap-1.5">
<div className="w-2.5 h-2.5 rounded-sm bg-[#404040]" />
<span>Previous period</span>
</div>
</div>
</div> </div>
) : ( <p className="text-sm text-neutral-500 dark:text-neutral-400 mb-4">
Current vs. previous period comparison
</p>
<div className="flex-1 min-h-[270px] flex flex-col items-center justify-center text-center px-6 py-8 gap-4"> <div className="flex-1 min-h-[270px] flex flex-col items-center justify-center text-center px-6 py-8 gap-4">
<div className="rounded-full bg-neutral-100 dark:bg-neutral-800 p-4"> <div className="rounded-full bg-neutral-100 dark:bg-neutral-800 p-4">
<TrendUp className="w-8 h-8 text-neutral-500 dark:text-neutral-400" /> <TrendUp className="w-8 h-8 text-neutral-500 dark:text-neutral-400" />
@@ -152,7 +101,53 @@ export default function FrustrationTrend({ summary, loading }: FrustrationTrendP
Frustration trend data will appear here once rage clicks or dead clicks are detected across periods. Frustration trend data will appear here once rage clicks or dead clicks are detected across periods.
</p> </p>
</div> </div>
)} </div>
</div> )
}
return (
<Card className="flex flex-col h-full">
<CardHeader className="items-center pb-0">
<CardTitle>Frustration Trend</CardTitle>
<CardDescription>Current vs. previous period</CardDescription>
</CardHeader>
<CardContent className="flex-1 pb-0">
<ChartContainer
config={chartConfig}
className="mx-auto aspect-square max-h-[250px]"
>
<PieChart>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel />}
/>
<Pie
data={chartData}
dataKey="count"
nameKey="type"
stroke="0"
/>
</PieChart>
</ChartContainer>
</CardContent>
<CardFooter className="flex-col gap-2 text-sm">
<div className="flex items-center gap-2 leading-none font-medium">
{totalChange !== null ? (
<>
{totalChange > 0 ? 'Up' : totalChange < 0 ? 'Down' : 'No change'} by {Math.abs(totalChange)}% vs previous period <TrendUp className="h-4 w-4" />
</>
) : totalCurrent > 0 ? (
<>
{totalCurrent.toLocaleString()} new signals this period <TrendUp className="h-4 w-4" />
</>
) : (
'No frustration signals detected'
)}
</div>
<div className="leading-none text-muted-foreground">
{totalCurrent.toLocaleString()} total signals in current period
</div>
</CardFooter>
</Card>
) )
} }