Skip to content

Commit bd04d96

Browse files
committed
Update report percentages on dashboard and details view
1 parent 5b69061 commit bd04d96

11 files changed

Lines changed: 191 additions & 99 deletions

File tree

assets/js/dashboard/stats/behaviours/conversions.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export default function Conversions({ afterFetchData, onGoalFilterClick }) {
5454
path: conversionsRoute.path,
5555
search: (search) => search
5656
}}
57-
color="bg-red-50 group-hover:bg-red-100"
57+
color="bg-red-50 group-hover/row:bg-red-100"
5858
colMinWidth={90}
5959
/>
6060
)

assets/js/dashboard/stats/behaviours/props.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export default function Properties({ afterFetchData }) {
138138
search: (search) => search
139139
}}
140140
maybeHideDetails={true}
141-
color="bg-red-50 group-hover:bg-red-100"
141+
color="bg-red-50 group-hover/row:bg-red-100"
142142
colMinWidth={90}
143143
/>
144144
)

assets/js/dashboard/stats/devices/index.js

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,10 @@ function Browsers({ afterFetchData }) {
7676
function chooseMetrics() {
7777
return [
7878
metrics.createVisitors({ meta: { plot: true } }),
79-
hasConversionGoalFilter(query) && metrics.createConversionRate(),
80-
!hasConversionGoalFilter(query) && metrics.createPercentage()
79+
!hasConversionGoalFilter(query) &&
80+
metrics.createPercentage({ meta: { showOnHover: true } }),
81+
hasConversionGoalFilter(query) &&
82+
metrics.createConversionRate({ meta: { showOnHover: true } })
8183
].filter((metric) => !!metric)
8284
}
8385

@@ -121,8 +123,10 @@ function BrowserVersions({ afterFetchData }) {
121123
function chooseMetrics() {
122124
return [
123125
metrics.createVisitors({ meta: { plot: true } }),
124-
hasConversionGoalFilter(query) && metrics.createConversionRate(),
125-
!hasConversionGoalFilter(query) && metrics.createPercentage()
126+
!hasConversionGoalFilter(query) &&
127+
metrics.createPercentage({ meta: { showOnHover: true } }),
128+
hasConversionGoalFilter(query) &&
129+
metrics.createConversionRate({ meta: { showOnHover: true } })
126130
].filter((metric) => !!metric)
127131
}
128132

@@ -187,9 +191,12 @@ function OperatingSystems({ afterFetchData }) {
187191
function chooseMetrics() {
188192
return [
189193
metrics.createVisitors({ meta: { plot: true } }),
190-
hasConversionGoalFilter(query) && metrics.createConversionRate(),
191194
!hasConversionGoalFilter(query) &&
192-
metrics.createPercentage({ meta: { hiddenonMobile: true } })
195+
metrics.createPercentage({
196+
meta: { showOnHover: true, hiddenOnMobile: true }
197+
}),
198+
hasConversionGoalFilter(query) &&
199+
metrics.createConversionRate({ meta: { showOnHover: true } })
193200
].filter((metric) => !!metric)
194201
}
195202

@@ -238,8 +245,10 @@ function OperatingSystemVersions({ afterFetchData }) {
238245
function chooseMetrics() {
239246
return [
240247
metrics.createVisitors({ meta: { plot: true } }),
241-
hasConversionGoalFilter(query) && metrics.createConversionRate(),
242-
!hasConversionGoalFilter(query) && metrics.createPercentage()
248+
!hasConversionGoalFilter(query) &&
249+
metrics.createPercentage({ meta: { showOnHover: true } }),
250+
hasConversionGoalFilter(query) &&
251+
metrics.createConversionRate({ meta: { showOnHover: true } })
243252
].filter((metric) => !!metric)
244253
}
245254

@@ -281,8 +290,10 @@ function ScreenSizes({ afterFetchData }) {
281290
function chooseMetrics() {
282291
return [
283292
metrics.createVisitors({ meta: { plot: true } }),
284-
hasConversionGoalFilter(query) && metrics.createConversionRate(),
285-
!hasConversionGoalFilter(query) && metrics.createPercentage()
293+
!hasConversionGoalFilter(query) &&
294+
metrics.createPercentage({ meta: { showOnHover: true } }),
295+
hasConversionGoalFilter(query) &&
296+
metrics.createConversionRate({ meta: { showOnHover: true } })
286297
].filter((metric) => !!metric)
287298
}
288299

@@ -432,7 +443,7 @@ export default function Devices() {
432443
}
433444

434445
return (
435-
<div>
446+
<div className="group/devices overflow-x-hidden">
436447
<div className="flex justify-between w-full">
437448
<div className="flex gap-x-1">
438449
<h3 className="font-bold dark:text-gray-100">Devices</h3>

assets/js/dashboard/stats/locations/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ function Countries({ query, site, onClick, afterFetchData }) {
5454
search: (search) => search
5555
}}
5656
renderIcon={renderIcon}
57-
color="bg-orange-50 group-hover:bg-orange-100"
57+
color="bg-orange-50 group-hover/row:bg-orange-100"
5858
/>
5959
)
6060
}
@@ -93,7 +93,7 @@ function Regions({ query, site, onClick, afterFetchData }) {
9393
metrics={chooseMetrics()}
9494
detailsLinkProps={{ path: regionsRoute.path, search: (search) => search }}
9595
renderIcon={renderIcon}
96-
color="bg-orange-50 group-hover:bg-orange-100"
96+
color="bg-orange-50 group-hover/row:bg-orange-100"
9797
/>
9898
)
9999
}
@@ -131,7 +131,7 @@ function Cities({ query, site, afterFetchData }) {
131131
metrics={chooseMetrics()}
132132
detailsLinkProps={{ path: citiesRoute.path, search: (search) => search }}
133133
renderIcon={renderIcon}
134-
color="bg-orange-50 group-hover:bg-orange-100"
134+
color="bg-orange-50 group-hover/row:bg-orange-100"
135135
/>
136136
)
137137
}

assets/js/dashboard/stats/pages/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ function EntryPages({ afterFetchData }) {
5353
search: (search) => search
5454
}}
5555
getExternalLinkUrl={getExternalLinkUrl}
56-
color="bg-orange-50 group-hover:bg-orange-100"
56+
color="bg-orange-50 group-hover/row:bg-orange-100"
5757
/>
5858
)
5959
}
@@ -99,7 +99,7 @@ function ExitPages({ afterFetchData }) {
9999
search: (search) => search
100100
}}
101101
getExternalLinkUrl={getExternalLinkUrl}
102-
color="bg-orange-50 group-hover:bg-orange-100"
102+
color="bg-orange-50 group-hover/row:bg-orange-100"
103103
/>
104104
)
105105
}
@@ -141,7 +141,7 @@ function TopPages({ afterFetchData }) {
141141
search: (search) => search
142142
}}
143143
getExternalLinkUrl={getExternalLinkUrl}
144-
color="bg-orange-50 group-hover:bg-orange-100"
144+
color="bg-orange-50 group-hover/row:bg-orange-100"
145145
/>
146146
)
147147
}

assets/js/dashboard/stats/reports/change-arrow.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,13 @@ export function ChangeArrow({
2222
let icon = null
2323
const arrowClassName = classNames(
2424
color(change, metric),
25-
'inline-block h-3 w-3 stroke-[1px] stroke-current'
25+
'mb-0.5 inline-block size-3 stroke-[1px] stroke-current'
2626
)
2727

2828
if (change > 0) {
2929
icon = <ArrowUpRightIcon className={arrowClassName} />
3030
} else if (change < 0) {
3131
icon = <ArrowDownRightIcon className={arrowClassName} />
32-
} else if (change === 0 && !hideNumber) {
33-
icon = <>&#12336;</>
3432
}
3533

3634
return (

assets/js/dashboard/stats/reports/list.tsx

Lines changed: 101 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const ROW_HEIGHT = 32
2222
const ROW_GAP_HEIGHT = 4
2323
const DATA_CONTAINER_HEIGHT =
2424
(ROW_HEIGHT + ROW_GAP_HEIGHT) * (MAX_ITEMS - 1) + ROW_HEIGHT
25-
const COL_MIN_WIDTH = 70
25+
const COL_MIN_WIDTH = 64
2626

2727
function ExternalLink<T>({
2828
item,
@@ -38,15 +38,21 @@ function ExternalLink<T>({
3838
target="_blank"
3939
rel="noreferrer"
4040
href={dest}
41-
className="w-4 h-4 invisible group-hover:visible"
41+
className="invisible group-hover:visible"
4242
>
4343
<svg
44-
className="inline w-full h-full ml-1 -mt-1 text-gray-600 dark:text-gray-400"
45-
fill="currentColor"
46-
viewBox="0 0 20 20"
44+
xmlns="http://www.w3.org/2000/svg"
45+
fill="none"
46+
viewBox="0 0 24 24"
47+
className="inline size-3.5 mb-0.5 text-gray-600 dark:text-gray-400"
4748
>
48-
<path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"></path>
49-
<path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"></path>
49+
<path
50+
stroke="currentColor"
51+
strokeLinecap="round"
52+
strokeLinejoin="round"
53+
strokeWidth="2"
54+
d="M9 5H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-4M12 12l9-9-.303.303M14 3h7v7"
55+
/>
5056
</svg>
5157
</a>
5258
)
@@ -129,6 +135,7 @@ export default function ListReport<
129135
meta: BreakdownResultMeta | null
130136
}>({ loading: true, list: null, meta: null })
131137
const [visible, setVisible] = useState(false)
138+
const [tappedRow, setTappedRow] = useState<string | null>(null)
132139

133140
const isRealtime = isRealTimeDashboard(query)
134141
const goalFilterApplied = hasConversionGoalFilter(query)
@@ -194,6 +201,38 @@ export default function ListReport<
194201
}
195202
}
196203

204+
function showOnHoverClass(metric: Metric, listItemName: string) {
205+
if (!metric.meta.showOnHover) {
206+
return ''
207+
}
208+
209+
// On mobile: show if row is tapped, hide otherwise
210+
// On desktop: slide in from right when hovering
211+
if (tappedRow === listItemName) {
212+
return 'translate-x-0 opacity-100 transition-all duration-300'
213+
} else {
214+
return 'translate-x-[100%] opacity-0 transition-all duration-300 md:group-hover/devices:translate-x-0 md:group-hover/devices:opacity-100'
215+
}
216+
}
217+
218+
function slideLeftClass(
219+
metricIndex: number,
220+
showOnHoverIndex: number,
221+
hasShowOnHoverMetric: boolean,
222+
listItemName: string
223+
) {
224+
// Columns before the showOnHover column should slide left when it appears
225+
if (!hasShowOnHoverMetric || metricIndex >= showOnHoverIndex) {
226+
return ''
227+
}
228+
229+
if (tappedRow === listItemName) {
230+
return 'transition-transform duration-300 translate-x-0'
231+
} else {
232+
return 'transition-transform duration-300 translate-x-[100%] md:group-hover/devices:translate-x-0'
233+
}
234+
}
235+
197236
function renderReport() {
198237
if (state.list && state.list.length > 0) {
199238
return (
@@ -223,17 +262,19 @@ export default function ListReport<
223262
}
224263

225264
function renderReportHeader() {
226-
const metricLabels = getAvailableMetrics().map((metric) => {
227-
return (
228-
<div
229-
key={metric.key}
230-
className={`${metric.key} text-right ${hiddenOnMobileClass(metric)}`}
231-
style={{ minWidth: colMinWidth }}
232-
>
233-
{metric.renderLabel(query)}
234-
</div>
235-
)
236-
})
265+
const metricLabels = getAvailableMetrics()
266+
.filter((metric) => !metric.meta.showOnHover)
267+
.map((metric) => {
268+
return (
269+
<div
270+
key={metric.key}
271+
className={`${metric.key} text-right ${hiddenOnMobileClass(metric)}`}
272+
style={{ minWidth: colMinWidth }}
273+
>
274+
{metric.renderLabel(query)}
275+
</div>
276+
)
277+
})
237278

238279
return (
239280
<div className="pt-3 w-full text-xs font-bold tracking-wide text-gray-500 flex items-center dark:text-gray-400">
@@ -244,11 +285,22 @@ export default function ListReport<
244285
}
245286

246287
function renderRow(listItem: TListItem) {
288+
const handleRowClick = (e: React.MouseEvent) => {
289+
if (window.innerWidth < 768 && !(e.target as HTMLElement).closest('a')) {
290+
if (tappedRow === listItem.name) {
291+
setTappedRow(null)
292+
} else {
293+
setTappedRow(listItem.name)
294+
}
295+
}
296+
}
297+
247298
return (
248299
<div key={listItem.name} style={{ minHeight: ROW_HEIGHT }}>
249300
<div
250-
className="group flex w-full items-center hover:bg-gray-100/60 dark:hover:bg-gray-850 rounded-sm transition-colors duration-150"
301+
className="group/row flex w-full items-center hover:bg-gray-100/60 dark:hover:bg-gray-850 rounded-sm transition-colors duration-150 md:cursor-default cursor-pointer"
251302
style={{ marginTop: ROW_GAP_HEIGHT }}
303+
onClick={handleRowClick}
252304
>
253305
{renderBarFor(listItem)}
254306
{renderMetricValuesFor(listItem)}
@@ -258,7 +310,7 @@ export default function ListReport<
258310
}
259311

260312
function renderBarFor(listItem: TListItem) {
261-
const lightBackground = color || 'bg-green-50 group-hover:bg-green-100'
313+
const lightBackground = color || 'bg-green-50 group-hover/row:bg-green-100'
262314
const metricToPlot = metrics.find((metric) => metric.meta.plot)?.key
263315

264316
return (
@@ -267,10 +319,10 @@ export default function ListReport<
267319
maxWidthDeduction={undefined}
268320
count={listItem[metricToPlot]}
269321
all={state.list}
270-
bg={`${lightBackground} dark:bg-gray-500/15 dark:group-hover:bg-gray-500/30`}
322+
bg={`${lightBackground} dark:bg-gray-500/15 dark:group-hover/row:bg-gray-500/30`}
271323
plot={metricToPlot}
272324
>
273-
<div className="flex justify-start px-2 py-1.5 group text-sm dark:text-gray-300 relative z-9 break-all w-full">
325+
<div className="flex justify-start items-center gap-x-1.5 px-2 py-1.5 text-sm dark:text-gray-300 relative z-9 break-all w-full">
274326
<DrilldownLink
275327
filterInfo={getFilterInfo(listItem)}
276328
onClick={onClick}
@@ -299,19 +351,33 @@ export default function ListReport<
299351
}
300352

301353
function renderMetricValuesFor(listItem: TListItem) {
302-
return getAvailableMetrics().map((metric) => {
303-
return (
304-
<div
305-
key={`${listItem.name}__${metric.key}`}
306-
className={`text-right ${hiddenOnMobileClass(metric)}`}
307-
style={{ width: colMinWidth, minWidth: colMinWidth }}
308-
>
309-
<span className="font-medium text-sm dark:text-gray-200 text-right">
310-
{metric.renderValue(listItem, state.meta)}
311-
</span>
312-
</div>
313-
)
314-
})
354+
const availableMetrics = getAvailableMetrics()
355+
const showOnHoverIndex = availableMetrics.findIndex(
356+
(m) => m.meta.showOnHover
357+
)
358+
const hasShowOnHoverMetric = showOnHoverIndex !== -1
359+
360+
return (
361+
<>
362+
{availableMetrics.map((metric, index) => {
363+
const isShowOnHover = metric.meta.showOnHover
364+
365+
return (
366+
<div
367+
key={`${listItem.name}__${metric.key}`}
368+
className={`text-right ${hiddenOnMobileClass(metric)} ${showOnHoverClass(metric, listItem.name)} ${slideLeftClass(index, showOnHoverIndex, hasShowOnHoverMetric, listItem.name)}`}
369+
style={{ width: colMinWidth, minWidth: colMinWidth }}
370+
>
371+
<span
372+
className={`font-medium text-sm text-right ${isShowOnHover ? 'text-gray-500 dark:text-gray-400' : 'text-gray-800 dark:text-gray-200'}`}
373+
>
374+
{metric.renderValue(listItem, state.meta)}
375+
</span>
376+
</div>
377+
)
378+
})}
379+
</>
380+
)
315381
}
316382

317383
function renderLoading() {

0 commit comments

Comments
 (0)