From 4f4f2f4f9ac12b2df69fcc9e4341d74ceaa8f40f Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Sun, 15 Mar 2026 20:31:57 +0100 Subject: [PATCH] refactor: redesign top paths table to match Pulse patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace card-per-row with compact list rows + background bars - Drop rank badges (order already communicates rank) - Inline path sequence + stats into single row - Truncate sequences longer than 7 steps (first 3 + … + last 2) - Duration shows on hover with slide-in animation - Use brand-orange bars proportional to top path count --- components/journeys/TopPathsTable.tsx | 105 ++++++++++++++++---------- 1 file changed, 65 insertions(+), 40 deletions(-) diff --git a/components/journeys/TopPathsTable.tsx b/components/journeys/TopPathsTable.tsx index f85e57c..72bdcd1 100644 --- a/components/journeys/TopPathsTable.tsx +++ b/components/journeys/TopPathsTable.tsx @@ -2,7 +2,7 @@ import type { TopPath } from '@/lib/api/journeys' import { TableSkeleton } from '@/components/skeletons' -import { Path, ArrowRight, Clock, Users } from '@phosphor-icons/react' +import { Path, ArrowRight, Clock } from '@phosphor-icons/react' interface TopPathsTableProps { paths: TopPath[] @@ -24,8 +24,16 @@ function smartLabel(path: string): string { return `…/${segments[segments.length - 1]}` } +function truncateSequence(seq: string[], max: number): (string | null)[] { + if (seq.length <= max) return seq + const head = seq.slice(0, 3) + const tail = seq.slice(-2) + return [...head, null, ...tail] +} + export default function TopPathsTable({ paths, loading }: TopPathsTableProps) { const hasData = paths.length > 0 + const maxCount = hasData ? paths[0].session_count : 0 return (
@@ -41,49 +49,66 @@ export default function TopPathsTable({ paths, loading }: TopPathsTableProps) { {loading ? ( ) : hasData ? ( -
- {paths.map((path, i) => ( -
-
- - #{i + 1} - -
- - - {path.session_count.toLocaleString()} - - {path.avg_duration > 0 && ( - - - {formatDuration(path.avg_duration)} - - )} -
-
-
- {path.page_sequence.map((page, j) => ( -
- {j > 0 && ( - +
+ {paths.map((path, i) => { + const barWidth = maxCount > 0 ? (path.session_count / maxCount) * 100 : 0 + const displaySeq = truncateSequence(path.page_sequence, 7) + + return ( +
+ {/* Background bar */} +
+ + {/* Content */} +
+ {/* Path sequence */} +
+ {displaySeq.map((page, j) => ( +
+ {j > 0 && ( + + )} + {page === null ? ( + + … + + ) : ( + + {smartLabel(page)} + + )} +
+ ))} +
+ + {/* Stats */} +
+ {path.avg_duration > 0 && ( + + + {formatDuration(path.avg_duration)} + )} - - {smartLabel(page)} + + {path.session_count.toLocaleString()}
- ))} +
-
- ))} + ) + })}
) : (