From 5050422a600940420a49a66beaf85a41b8efe2fb Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Thu, 12 Mar 2026 18:27:20 +0100 Subject: [PATCH] refactor: match frustration tables to dashboard pattern - Remove column headers for cleaner look - Show secondary info (avg, sessions, last seen) on hover - Add orange percentage badge that slides in on hover - Add empty row padding for consistent card height --- app/sites/[id]/behavior/page.tsx | 2 + components/behavior/FrustrationTable.tsx | 87 ++++++++++++------------ 2 files changed, 46 insertions(+), 43 deletions(-) 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) => ( +