diff --git a/components/behavior/FrustrationSummaryCards.tsx b/components/behavior/FrustrationSummaryCards.tsx new file mode 100644 index 0000000..d1bd8a2 --- /dev/null +++ b/components/behavior/FrustrationSummaryCards.tsx @@ -0,0 +1,119 @@ +'use client' + +import type { FrustrationSummary } from '@/lib/api/stats' + +interface FrustrationSummaryCardsProps { + data: FrustrationSummary | null + loading: boolean +} + +function pctChange(current: number, previous: number): number | null { + if (previous === 0 && current === 0) return null + if (previous === 0) return 100 + return Math.round(((current - previous) / previous) * 100) +} + +function ChangeIndicator({ change }: { change: number | null }) { + if (change === null) return null + const isUp = change > 0 + const isDown = change < 0 + return ( + + {isUp ? '+' : ''}{change}% + + ) +} + +function SkeletonCard() { + return ( +
+
+
+
+
+
+
+ ) +} + +export default function FrustrationSummaryCards({ data, loading }: FrustrationSummaryCardsProps) { + if (loading || !data) { + return ( +
+ + + +
+ ) + } + + const rageChange = pctChange(data.rage_clicks, data.prev_rage_clicks) + const deadChange = pctChange(data.dead_clicks, data.prev_dead_clicks) + const topPage = data.rage_top_page || data.dead_top_page + const topPageTotal = data.rage_clicks + data.dead_clicks + + return ( +
+ {/* Rage Clicks */} +
+

+ Rage Clicks +

+
+ + {data.rage_clicks.toLocaleString()} + + +
+

+ {data.rage_unique_elements} unique elements +

+
+ + {/* Dead Clicks */} +
+

+ Dead Clicks +

+
+ + {data.dead_clicks.toLocaleString()} + + +
+

+ {data.dead_unique_elements} unique elements +

+
+ + {/* Most Frustrated Page */} +
+

+ Most Frustrated Page +

+ {topPage ? ( + <> +

+ {topPage} +

+

+ {topPageTotal.toLocaleString()} total signals +

+ + ) : ( +

+ No data in this period +

+ )} +
+
+ ) +}