diff --git a/components/behavior/FrustrationByPageTable.tsx b/components/behavior/FrustrationByPageTable.tsx
new file mode 100644
index 0000000..99ae877
--- /dev/null
+++ b/components/behavior/FrustrationByPageTable.tsx
@@ -0,0 +1,107 @@
+'use client'
+
+import { formatNumber } from '@ciphera-net/ui'
+import type { FrustrationByPage } from '@/lib/api/stats'
+
+interface FrustrationByPageTableProps {
+ pages: FrustrationByPage[]
+ loading: boolean
+}
+
+function SkeletonRows() {
+ return (
+
+ {Array.from({ length: 5 }).map((_, i) => (
+
+ ))}
+
+ )
+}
+
+export default function FrustrationByPageTable({ pages, loading }: FrustrationByPageTableProps) {
+ const hasData = pages.length > 0
+ const maxTotal = Math.max(...pages.map(p => p.total), 1)
+
+ return (
+
+
+
+ Frustration by Page
+
+
+
+ Pages with the most frustration signals
+
+
+ {loading ? (
+
+ ) : hasData ? (
+
+ {/* Header */}
+
+
Page
+
+ Rage
+ Dead
+ Total
+ Elements
+
+
+
+ {/* Rows */}
+
+ {pages.map((page) => {
+ const barWidth = (page.total / maxTotal) * 100
+ return (
+
+ {/* Background bar */}
+
+
+ {page.page_path}
+
+
+
+ {formatNumber(page.rage_clicks)}
+
+
+ {formatNumber(page.dead_clicks)}
+
+
+ {formatNumber(page.total)}
+
+
+ {page.unique_elements}
+
+
+
+ )
+ })}
+
+
+ ) : (
+
+
+ No frustration signals detected in this period
+
+
+ )}
+
+ )
+}