diff --git a/src/components/DeviceStatistics.tsx b/src/components/DeviceStatistics.tsx index a91581961..e85943bf2 100644 --- a/src/components/DeviceStatistics.tsx +++ b/src/components/DeviceStatistics.tsx @@ -1,36 +1,23 @@ import { createResource } from 'solid-js' import type { VoidComponent } from 'solid-js' -import clsx from 'clsx' import { getDeviceStats } from '~/api/devices' import { formatDistance, formatDuration } from '~/utils/format' +import StatisticBar from './StatisticBar' -type DeviceStatisticsProps = { - class?: string - dongleId: string -} - -const DeviceStatistics: VoidComponent = (props) => { +const DeviceStatistics: VoidComponent<{ class?: string; dongleId: string }> = (props) => { const [statistics] = createResource(() => props.dongleId, getDeviceStats) const allTime = () => statistics()?.all return ( -
-
- Distance - {formatDistance(allTime()?.distance)} -
- -
- Duration - {formatDuration(allTime()?.minutes)} -
- -
- Routes - {allTime()?.routes ?? 0} -
-
+ formatDistance(allTime()?.distance) }, + { label: 'Duration', value: () => formatDuration(allTime()?.minutes) }, + { label: 'Routes', value: () => allTime()?.routes }, + ]} + /> ) } diff --git a/src/components/RouteStatistics.tsx b/src/components/RouteStatistics.tsx index f60a8cf6b..88035df3b 100644 --- a/src/components/RouteStatistics.tsx +++ b/src/components/RouteStatistics.tsx @@ -1,55 +1,30 @@ -import { createResource, Suspense } from 'solid-js' +import { createResource } from 'solid-js' import type { VoidComponent } from 'solid-js' -import clsx from 'clsx' import { TimelineStatistics, getTimelineStatistics } from '~/api/derived' import type { Route } from '~/types' import { formatDistance, formatRouteDuration } from '~/utils/format' +import StatisticBar from './StatisticBar' -const formatEngagement = (timeline?: TimelineStatistics): string => { - if (!timeline) return '' +const formatEngagement = (timeline?: TimelineStatistics): string | undefined => { + if (!timeline) return undefined const { engagedDuration, duration } = timeline return `${(100 * (engagedDuration / duration)).toFixed(0)}%` } -const formatUserFlags = (timeline?: TimelineStatistics): string => { - return timeline?.userFlags.toString() ?? '' -} - -type RouteStatisticsProps = { - class?: string - route?: Route -} - -const RouteStatistics: VoidComponent = (props) => { +const RouteStatistics: VoidComponent<{ class?: string; route: Route }> = (props) => { const [timeline] = createResource(() => props.route, getTimelineStatistics) return ( -
-
- Distance - {formatDistance(props.route?.length)} -
- -
- Duration - {formatRouteDuration(props.route)} -
- - - -
- User flags - - {formatUserFlags(timeline())} - -
-
+ formatDistance(props.route?.length) }, + { label: 'Duration', value: () => formatRouteDuration(props.route) }, + { label: 'Engaged', value: () => formatEngagement(timeline()) }, + { label: 'User flags', value: () => timeline()?.userFlags }, + ]} + /> ) } diff --git a/src/components/StatisticBar.tsx b/src/components/StatisticBar.tsx new file mode 100644 index 000000000..584eb6a0c --- /dev/null +++ b/src/components/StatisticBar.tsx @@ -0,0 +1,23 @@ +import clsx from 'clsx' +import { For, Suspense, VoidComponent } from 'solid-js' + +const StatisticBar: VoidComponent<{ class?: string; statistics: { label: string; value: () => unknown }[] }> = (props) => { + return ( +
+
+ + {(statistic) => ( +
+ {statistic.label} + }> + {statistic.value()?.toString() ?? '—'} + +
+ )} +
+
+
+ ) +} + +export default StatisticBar diff --git a/src/utils/format.spec.ts b/src/utils/format.spec.ts index bd6f0a74f..ec3965e13 100644 --- a/src/utils/format.spec.ts +++ b/src/utils/format.spec.ts @@ -7,8 +7,8 @@ describe('formatDistance', () => { expect(formatDistance(0)).toBe('0.0 mi') expect(formatDistance(1.234)).toBe('1.2 mi') }) - it('should be blank for undefined distance', () => { - expect(formatDistance(undefined)).toBe('') + it('should be undefined for undefined distance', () => { + expect(formatDistance(undefined)).toBe(undefined) }) }) @@ -20,8 +20,8 @@ describe('formatDuration', () => { expect(formatDuration(90)).toBe('1h 30m') expect(formatDuration(120)).toBe('2h 0m') }) - it('should be blank for undefined duration', () => { - expect(formatDuration(undefined)).toBe('') + it('should be undefined for undefined duration', () => { + expect(formatDuration(undefined)).toBe(undefined) }) }) diff --git a/src/utils/format.ts b/src/utils/format.ts index cb8cec1dd..3658ec38e 100644 --- a/src/utils/format.ts +++ b/src/utils/format.ts @@ -19,8 +19,8 @@ const isImperial = (): boolean => { return locale.startsWith('en-us') || locale.startsWith('en-gb') } -export const formatDistance = (miles: number | undefined): string => { - if (miles === undefined) return '' +export const formatDistance = (miles: number | undefined): string | undefined => { + if (miles === undefined) return undefined if (isImperial()) return `${miles.toFixed(1)} mi` return `${(miles * MI_TO_KM).toFixed(1)} km` } @@ -33,8 +33,8 @@ const _formatDuration = (duration: Duration): string => { } } -export const formatDuration = (minutes: number | undefined): string => { - if (minutes === undefined) return '' +export const formatDuration = (minutes: number | undefined): string | undefined => { + if (minutes === undefined) return undefined const duration = dayjs.duration({ hours: Math.floor(minutes / 60), minutes: Math.round(minutes % 60),