diff --git a/app/sites/[id]/behavior/page.tsx b/app/sites/[id]/behavior/page.tsx
index 6de0d44..8c93ffa 100644
--- a/app/sites/[id]/behavior/page.tsx
+++ b/app/sites/[id]/behavior/page.tsx
@@ -166,6 +166,7 @@ export default function BehaviorPage() {
description="Elements users clicked repeatedly in frustration"
items={rageClicks.items}
total={rageClicks.total}
+ totalSignals={summary?.rage_clicks ?? 0}
showAvgClicks
loading={loading}
fetchAll={fetchAllRage}
@@ -175,6 +176,7 @@ export default function BehaviorPage() {
description="Elements users clicked that produced no response"
items={deadClicks.items}
total={deadClicks.total}
+ totalSignals={summary?.dead_clicks ?? 0}
loading={loading}
fetchAll={fetchAllDead}
/>
diff --git a/components/behavior/FrustrationTable.tsx b/components/behavior/FrustrationTable.tsx
index 789ce5d..5fedd0f 100644
--- a/components/behavior/FrustrationTable.tsx
+++ b/components/behavior/FrustrationTable.tsx
@@ -8,11 +8,14 @@ import type { FrustrationElement } from '@/lib/api/stats'
import { formatRelativeTime } from '@/lib/utils/formatDate'
import { ListSkeleton } from '@/components/skeletons'
+const DISPLAY_LIMIT = 7
+
interface FrustrationTableProps {
title: string
description: string
items: FrustrationElement[]
total: number
+ totalSignals: number
showAvgClicks?: boolean
loading: boolean
fetchAll?: () => Promise<{ items: FrustrationElement[]; total: number }>
@@ -21,7 +24,7 @@ interface FrustrationTableProps {
function SkeletonRows() {
return (
- {Array.from({ length: 5 }).map((_, i) => (
+ {Array.from({ length: DISPLAY_LIMIT }).map((_, i) => (
@@ -51,7 +54,7 @@ function SelectorCell({ selector }: { selector: string }) {
className="flex items-center gap-1 min-w-0 group/copy cursor-pointer"
title={selector}
>
-
+
{selector}
@@ -65,41 +68,44 @@ function SelectorCell({ selector }: { selector: string }) {
)
}
-const GRID_WITH_AVG = 'grid grid-cols-[1fr_60px_50px_64px_64px_40px] items-center gap-2 h-9 px-2 -mx-2'
-const GRID_NO_AVG = 'grid grid-cols-[1fr_60px_64px_64px_40px] items-center gap-2 h-9 px-2 -mx-2'
-
function Row({
item,
showAvgClicks,
+ totalSignals,
}: {
item: FrustrationElement
showAvgClicks?: boolean
+ totalSignals: number
}) {
+ const pct = totalSignals > 0 ? `${Math.round((item.count / totalSignals) * 100)}%` : ''
+
return (
-
-
-
-
- {item.page_path}
+
+
+
+
+
+ {item.page_path}
+
+
+
+
+ {/* Secondary info: visible on hover */}
+
+ {showAvgClicks && item.avg_click_count != null ? `avg ${item.avg_click_count.toFixed(1)} · ` : ''}
+ {item.sessions} {item.sessions === 1 ? 'sess' : 'sess'} · {formatRelativeTime(item.last_seen)}
+
+ {/* Percentage badge: slides in on hover */}
+
+ {pct}
+
+
+ {formatNumber(item.count)}
- {showAvgClicks && (
-
- {item.avg_click_count != null ? item.avg_click_count.toFixed(1) : '–'}
-
- )}
-
- {item.sessions}
-
-
- {formatRelativeTime(item.last_seen)}
-
-
- {formatNumber(item.count)}
-
)
}
@@ -109,6 +115,7 @@ export default function FrustrationTable({
description,
items,
total,
+ totalSignals,
showAvgClicks,
loading,
fetchAll,
@@ -118,6 +125,7 @@ export default function FrustrationTable({
const [isLoadingFull, setIsLoadingFull] = useState(false)
const hasData = items.length > 0
const showViewAll = hasData && total > items.length
+ const emptySlots = Math.max(0, DISPLAY_LIMIT - items.length)
useEffect(() => {
if (isModalOpen && fetchAll) {
@@ -165,21 +173,14 @@ export default function FrustrationTable({
{loading ? (
) : hasData ? (
-
- {/* Column headers */}
-
- Selector / Page
- {showAvgClicks && Avg}
- Sessions
- Last Seen
- Count
-
-
- {items.map((item, i) => (
-
- ))}
-
-
+ <>
+ {items.map((item, i) => (
+
+ ))}
+ {Array.from({ length: emptySlots }).map((_, i) => (
+
+ ))}
+ >
) : (
@@ -210,7 +211,7 @@ export default function FrustrationTable({
) : fullData.length > 0 ? (
{fullData.map((item, i) => (
-
+
))}
) : (