Skip to content

Commit dfeda94

Browse files
authored
Add report percentages to dashboard and details view (#5923)
* Update report percentages on dashboard and details view * Add percentages to Countries, Regions, and Cities reports * Add percentages to Channels, Sources, and UTM reports * Add percentages to top pages, entry pages, and exit pages reports * Update tests to include percentages * Change dashboard copy from title case to sentence case * Update details modal style * Make animations snappier * Introduce max height to modal and make inner content scrollable * Improve modal mobile design - Enable horizontal scroll for details modal on mobile - Add responsive spacing and positioning to modal * Added mobile tap behavior to external link in list report * Show tooltips only when in comparison mode or when the number is abbreviated * remove previously added showTooltip prop - This isn't needed anymore since we now handle the tooltip logic in the MetricValue component * Show long format upon hovering detailed view metrics * Added mobile tapping behaviour to detailed view * Added percentages to all detailed views * Add mobile swipe-to-close behavior for modal * Adjust sensitivity of modal drag to close * Use hammerjs for swipe-to-close modal behaviour * Prevent dragging if gesture starts inside table * Show 2 decimal places for percentages < 0.1% across dashboard * Adjust dark mode styles * Add hover effect to external link icon * Update tests to expect two-decimal percentages * Undo hammer install and revert to old modal styling * Remove CR and % columns from goals and custom props reports on dashboard, and show on hover in detailed view * Remove unused constants * Undo conversion rate on hover behaviour - Unlike percentages, CR should show permanently. * Show percentages permanently in custom props detailed view * Adjust width of conversion metrics column * Updated metric-value test * Update top-bar test * Added changelog entry * Fix test expectations for percentages with imported data - Update tests to expect correct percentages (≤100%) when imported data is included. These tests will fail until the percentage calculation bug is fixed, documenting the expected behavior. * Add imported_visitors to tests to ensure correct total_visitors calculation * Correct imported_visitors count in test
1 parent 6446e15 commit dfeda94

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+1170
-674
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ All notable changes to this project will be documented in this file.
66

77
### Added
88

9+
- A visitor percentage breakdown is now shown on all reports, both on the dashboard and in the detailed breakdown
10+
911
### Removed
1012

1113
### Changed

assets/css/app.css

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
--color-gray-950: var(--color-zinc-950);
9191

9292
/* Custom gray shades from config (override some zinc values) */
93+
--color-gray-75: rgb(247 247 248);
9394
--color-gray-150: rgb(236 236 238);
9495
--color-gray-750: rgb(50 50 54);
9596
--color-gray-825: rgb(35 35 38);
@@ -294,16 +295,12 @@ blockquote {
294295
display: inline;
295296
}
296297

297-
.table-striped tbody tr:nth-child(odd) {
298-
background-color: var(--color-gray-100);
298+
.table-striped tbody tr:nth-child(odd) td {
299+
background-color: var(--color-gray-75);
299300
}
300301

301-
.dark .table-striped tbody tr:nth-child(odd) {
302-
background-color: var(--color-gray-800);
303-
}
304-
305-
.dark .table-striped tbody tr:nth-child(even) {
306-
background-color: var(--color-gray-900);
302+
.dark .table-striped tbody tr:nth-child(odd) td {
303+
background-color: var(--color-gray-850);
307304
}
308305

309306
.fade-enter {

assets/css/modal.css

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -32,33 +32,6 @@
3232
overflow: auto;
3333
}
3434

35-
.modal__container {
36-
background-color: #fff;
37-
padding: 1rem 2rem;
38-
border-radius: 4px;
39-
margin: 50px auto;
40-
box-sizing: border-box;
41-
min-height: 509px;
42-
transition: height 200ms ease-in;
43-
}
44-
45-
.modal__close {
46-
position: fixed;
47-
color: #b8c2cc;
48-
font-size: 48px;
49-
font-weight: bold;
50-
top: 12px;
51-
right: 24px;
52-
}
53-
54-
.modal__close::before {
55-
content: '\2715';
56-
}
57-
58-
.modal__content {
59-
margin-bottom: 2rem;
60-
}
61-
6235
@keyframes mm-fade-in {
6336
from {
6437
opacity: 0;

assets/js/dashboard/components/search-input.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export const SearchInput = ({
6666
type="text"
6767
placeholder={isFocused ? placeholderFocused : placeholderUnfocused}
6868
className={classNames(
69-
'dark:text-gray-100 block border-gray-300 dark:border-gray-750 rounded-md dark:bg-gray-750 w-48 dark:placeholder:text-gray-400 focus:outline-none focus:ring-3 focus:ring-indigo-500/20 dark:focus:ring-indigo-500/25 focus:border-indigo-500',
69+
'text-sm dark:text-gray-100 block border-gray-300 dark:border-gray-750 rounded-md dark:bg-gray-750 max-w-64 w-full dark:placeholder:text-gray-400 focus:outline-none focus:ring-3 focus:ring-indigo-500/20 dark:focus:ring-indigo-500/25 focus:border-indigo-500',
7070
className
7171
)}
7272
onChange={debouncedOnSearchInputChange}

assets/js/dashboard/components/sort-button.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,27 @@ export const SortButton = ({
1515
return (
1616
<button
1717
onClick={toggleSort}
18-
className={classNames('group', 'hover:underline', 'relative')}
18+
className={classNames(
19+
'group',
20+
'hover:text-gray-700 dark:hover:text-gray-200 transition-colors duration-100',
21+
'relative'
22+
)}
1923
>
2024
{children}
2125
<span
2226
title={next.hint}
2327
className={classNames(
2428
'absolute',
25-
'rounded inline-block h-4 w-4',
29+
'rounded inline-block size-4',
2630
'ml-1',
2731
{
2832
[SortDirection.asc]: 'rotate-180',
2933
[SortDirection.desc]: 'rotate-0'
3034
}[sortDirection ?? next.direction],
3135
!sortDirection && 'opacity-0',
3236
!sortDirection && 'group-hover:opacity-100',
33-
sortDirection &&
34-
'group-hover:bg-gray-100 dark:group-hover:bg-gray-900',
35-
'transition'
37+
'group-hover:bg-gray-100 dark:group-hover:bg-gray-900',
38+
'transition-all duration-100'
3639
)}
3740
>
3841

assets/js/dashboard/components/table.tsx

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export type ColumnConfiguraton<T extends Record<string, unknown>> = {
2121
/**
2222
* Function used to transform the value found at item[key] for the cell. Superseded by renderItem if present. @example 1120 => "1.1k"
2323
*/
24-
renderValue?: (item: T) => ReactNode
24+
renderValue?: (item: T, isRowHovered?: boolean) => ReactNode
2525
/** Function used to create richer cells */
2626
renderItem?: (item: T) => ReactNode
2727
}
@@ -38,7 +38,7 @@ export const TableHeaderCell = ({
3838
return (
3939
<th
4040
className={classNames(
41-
'p-2 text-xs font-bold text-gray-500 dark:text-gray-400 tracking-wide',
41+
'p-2 text-xs font-semibold text-gray-500 dark:text-gray-400',
4242
className
4343
)}
4444
align={align}
@@ -58,7 +58,13 @@ export const TableCell = ({
5858
align?: 'left' | 'right'
5959
}) => {
6060
return (
61-
<td className={classNames('p-2 font-medium', className)} align={align}>
61+
<td
62+
className={classNames(
63+
'p-2 font-medium first:rounded-s-sm last:rounded-e-sm',
64+
className
65+
)}
66+
align={align}
67+
>
6268
{children}
6369
</td>
6470
)
@@ -68,15 +74,42 @@ export const ItemRow = <T extends Record<string, string | number | ReactNode>>({
6874
rowIndex,
6975
pageIndex,
7076
item,
71-
columns
77+
columns,
78+
tappedRowName,
79+
onRowTap
7280
}: {
7381
rowIndex: number
7482
pageIndex?: number
7583
item: T
7684
columns: ColumnConfiguraton<T>[]
85+
tappedRowName?: string | null
86+
onRowTap?: (rowName: string | null) => void
7787
}) => {
88+
const [isHovered, setIsHovered] = React.useState(false)
89+
90+
const rowName = (item as unknown as { name: string }).name
91+
const isTapped = tappedRowName === rowName
92+
const isRowActive = isHovered || isTapped
93+
94+
const handleRowClick = (e: React.MouseEvent) => {
95+
if (window.innerWidth < 768 && !(e.target as HTMLElement).closest('a')) {
96+
if (onRowTap) {
97+
if (isTapped) {
98+
onRowTap(null)
99+
} else {
100+
onRowTap(rowName)
101+
}
102+
}
103+
}
104+
}
105+
78106
return (
79-
<tr className="text-sm dark:text-gray-200">
107+
<tr
108+
className="group text-sm dark:text-gray-200 md:cursor-default cursor-pointer"
109+
onMouseEnter={() => setIsHovered(true)}
110+
onMouseLeave={() => setIsHovered(false)}
111+
onClick={handleRowClick}
112+
>
80113
{columns.map(({ key, width, align, renderValue, renderItem }) => (
81114
<TableCell
82115
key={`${(pageIndex ?? null) === null ? '' : `page_${pageIndex}_`}row_${rowIndex}_${String(key)}`}
@@ -86,7 +119,7 @@ export const ItemRow = <T extends Record<string, string | number | ReactNode>>({
86119
{renderItem
87120
? renderItem(item)
88121
: renderValue
89-
? renderValue(item)
122+
? renderValue(item, isRowActive)
90123
: (item[key] ?? '')}
91124
</TableCell>
92125
))}
@@ -101,6 +134,8 @@ export const Table = <T extends Record<string, string | number | ReactNode>>({
101134
columns: ColumnConfiguraton<T>[]
102135
data: T[] | { pages: T[][] }
103136
}) => {
137+
const [tappedRowName, setTappedRowName] = React.useState<string | null>(null)
138+
104139
const renderColumnLabel = (column: ColumnConfiguraton<T>) => {
105140
if (column.metricWarning) {
106141
return (
@@ -125,13 +160,13 @@ export const Table = <T extends Record<string, string | number | ReactNode>>({
125160
}
126161

127162
return (
128-
<table className="w-max overflow-x-auto md:w-full table-striped table-fixed">
129-
<thead>
130-
<tr className="text-xs font-bold text-gray-500 dark:text-gray-400">
163+
<table className="border-collapse table-striped table-fixed w-max min-w-full">
164+
<thead className="sticky top-0 bg-white dark:bg-gray-900 z-10">
165+
<tr className="text-xs font-semibold text-gray-500 dark:text-gray-400">
131166
{columns.map((column) => (
132167
<TableHeaderCell
133168
key={`header_${String(column.key)}`}
134-
className={classNames('p-2 tracking-wide', column.width)}
169+
className={classNames('p-2', column.width)}
135170
align={column.align}
136171
>
137172
{column.onSort ? (
@@ -156,6 +191,8 @@ export const Table = <T extends Record<string, string | number | ReactNode>>({
156191
columns={columns}
157192
rowIndex={rowIndex}
158193
key={rowIndex}
194+
tappedRowName={tappedRowName}
195+
onRowTap={setTappedRowName}
159196
/>
160197
))
161198
: data.pages.map((page, pageIndex) =>
@@ -166,6 +203,8 @@ export const Table = <T extends Record<string, string | number | ReactNode>>({
166203
rowIndex={rowIndex}
167204
pageIndex={pageIndex}
168205
key={`page_${pageIndex}_row_${rowIndex}`}
206+
tappedRowName={tappedRowName}
207+
onRowTap={setTappedRowName}
169208
/>
170209
))
171210
)}

assets/js/dashboard/components/tabs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ const Items = ({
160160
<SearchInput
161161
searchRef={searchRef}
162162
placeholderUnfocused="Press / to search"
163-
className="ml-auto w-full py-1 text-sm"
163+
className="ml-auto w-full py-1"
164164
onSearch={handleSearchInput}
165165
/>
166166
</div>

assets/js/dashboard/nav-menu/segments/searchable-segments-section.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export const SearchableSegmentsSection = ({
8484
<SearchInput
8585
searchRef={searchRef}
8686
placeholderUnfocused="Press / to search"
87-
className="ml-auto w-full py-1 text-sm"
87+
className="ml-auto w-full py-1"
8888
onSearch={handleSearchInput}
8989
/>
9090
)}

assets/js/dashboard/nav-menu/top-bar.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ test('user can open and close filters dropdown', async () => {
9292
'Location',
9393
'Screen size',
9494
'Browser',
95-
'Operating System',
95+
'Operating system',
9696
'Goal'
9797
])
9898
await userEvent.click(toggleFilters)

assets/js/dashboard/stats/bar.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default function Bar({
2626
return (
2727
<div className="w-full h-full relative" style={style}>
2828
<div
29-
className={`absolute top-0 left-0 h-full rounded-sm transition-colors duration-150 ${bg || ''}`}
29+
className={`absolute top-0 left-0 h-full rounded-sm ${bg || ''}`}
3030
style={{ width: `${width}%` }}
3131
></div>
3232
{children}

0 commit comments

Comments
 (0)