diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f3c659..a356cb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Improved +- **Polished Goals & Events panel.** The Goals & Events block on your dashboard got a visual refresh to match the style of the Pages, Referrers, and Locations panels. Events are now ranked with a number on the left, counts are shown in a consistent style, and hovering any row reveals what percentage of total events that action accounts for — sliding in smoothly from the right. - **Smarter bot protection.** The security checks on shared dashboard access and organization settings now use action-specific tokens tied to each page. A token earned on one page can't be reused on another, making it harder for automated tools to bypass the captcha. - **More resilient under Redis outages.** If the caching layer goes down temporarily, Pulse now continues enforcing rate limits using an in-memory fallback instead of letting all traffic through unchecked. This prevents one infrastructure hiccup from snowballing into a bigger problem. - **Better handling of traffic bursts.** The system can now absorb 5x larger spikes of incoming events before applying backpressure. When events are dropped during extreme bursts, the system now tracks and logs exactly how many — so we can detect and respond to sustained overload before it affects your data. diff --git a/components/dashboard/GoalStats.tsx b/components/dashboard/GoalStats.tsx index f641af7..03c3554 100644 --- a/components/dashboard/GoalStats.tsx +++ b/components/dashboard/GoalStats.tsx @@ -15,6 +15,8 @@ const LIMIT = 10 export default function GoalStats({ goalCounts, onSelectEvent }: GoalStatsProps) { const list = (goalCounts || []).slice(0, LIMIT) const hasData = list.length > 0 + const total = list.reduce((sum, r) => sum + r.count, 0) + const emptySlots = Math.max(0, 6 - list.length) return (
@@ -25,21 +27,34 @@ export default function GoalStats({ goalCounts, onSelectEvent }: GoalStatsProps)
{hasData ? ( -
- {list.map((row) => ( +
+ {list.map((row, i) => (
onSelectEvent?.(row.event_name)} - className={`flex items-center justify-between py-2 px-3 rounded-lg bg-neutral-50 dark:bg-neutral-800/50 hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors${onSelectEvent ? ' cursor-pointer' : ''}`} + className={`flex items-center justify-between h-9 group hover:bg-neutral-50 dark:hover:bg-neutral-800 rounded-lg px-2 -mx-2 transition-colors${onSelectEvent ? ' cursor-pointer' : ''}`} > - - {row.display_name ?? row.event_name.replace(/_/g, ' ')} - - - {formatNumber(row.count)} - +
+ + {i + 1} + + + {row.display_name ?? row.event_name.replace(/_/g, ' ')} + +
+
+ + {total > 0 ? `${Math.round((row.count / total) * 100)}%` : ''} + + + {formatNumber(row.count)} + +
))} + {Array.from({ length: emptySlots }).map((_, i) => ( + ) : (