1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-08-08 06:35:19 +02:00

feat: add measurement system support across ActivityCard, StravaActivityCard, NewLocationModal, LocationVisits, and related utility functions

This commit is contained in:
Sean Morley 2025-08-05 12:42:43 -04:00
parent 31630de4fd
commit 65f99c5a50
6 changed files with 79 additions and 16 deletions

View file

@ -6,11 +6,14 @@
import FileIcon from '~icons/mdi/file'; import FileIcon from '~icons/mdi/file';
import TrashIcon from '~icons/mdi/trash-can'; import TrashIcon from '~icons/mdi/trash-can';
import { formatDateInTimezone } from '$lib/dateUtils'; import { formatDateInTimezone } from '$lib/dateUtils';
import { getDistance, getElevation } from '$lib';
export let activity: Activity; export let activity: Activity;
export let trails: Trail[]; export let trails: Trail[];
export let visit: Visit | TransportationVisit; export let visit: Visit | TransportationVisit;
export let measurementSystem: 'metric' | 'imperial' = 'metric';
export let readOnly: boolean = false; export let readOnly: boolean = false;
$: trail = activity.trail ? trails.find((t) => t.id === activity.trail) : null; $: trail = activity.trail ? trails.find((t) => t.id === activity.trail) : null;
@ -35,7 +38,10 @@
<div class="text-xs text-base-content/70 space-y-1"> <div class="text-xs text-base-content/70 space-y-1">
{#if activity.distance} {#if activity.distance}
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<span>Distance: {activity.distance} km</span> <span
>Distance: {getDistance(measurementSystem, activity.distance)}
{measurementSystem === 'imperial' ? 'miles' : 'km'}</span
>
{#if activity.moving_time} {#if activity.moving_time}
<span>Time: {activity.moving_time}</span> <span>Time: {activity.moving_time}</span>
{/if} {/if}
@ -45,10 +51,16 @@
{#if activity.elevation_gain || activity.elevation_loss} {#if activity.elevation_gain || activity.elevation_loss}
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
{#if activity.elevation_gain} {#if activity.elevation_gain}
<span>{activity.elevation_gain}m</span> <span
>↗ {getElevation(measurementSystem, activity.elevation_gain)}
{measurementSystem === 'imperial' ? 'feet' : 'm'}</span
>
{/if} {/if}
{#if activity.elevation_loss} {#if activity.elevation_loss}
<span>{activity.elevation_loss}m</span> <span
>↘ {getElevation(measurementSystem, activity.elevation_loss)}
{measurementSystem === 'imperial' ? 'feet' : 'm'}</span
>
{/if} {/if}
</div> </div>
{/if} {/if}

View file

@ -294,6 +294,7 @@
steps[2].selected = true; steps[2].selected = true;
}} }}
on:close={() => close()} on:close={() => close()}
measurementSystem={user?.measurement_system || 'metric'}
/> />
{/if} {/if}
</div> </div>

View file

@ -6,6 +6,7 @@
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
export let activity: StravaActivity; export let activity: StravaActivity;
export let measurementSystem: 'metric' | 'imperial' = 'metric';
interface SportConfig { interface SportConfig {
color: string; color: string;
@ -48,10 +49,21 @@
}); });
} }
function formatPace(seconds: number): string { function formatPace(seconds: number, system: 'metric' | 'imperial'): string {
const minutes = Math.floor(seconds / 60); const minutes = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60); const secs = Math.floor(seconds % 60);
return `${minutes}:${secs.toString().padStart(2, '0')}`; const unit = system === 'metric' ? 'km' : 'mi';
return `${minutes}:${secs.toString().padStart(2, '0')}/${unit}`;
}
function convertElevation(
meters: number,
system: 'metric' | 'imperial'
): { value: number; unit: string } {
if (system === 'imperial') {
return { value: meters * 3.28084, unit: 'ft' };
}
return { value: meters, unit: 'm' };
} }
function handleImportActivity() { function handleImportActivity() {
@ -59,6 +71,21 @@
} }
$: typeConfig = getTypeConfig(activity.sport_type); $: typeConfig = getTypeConfig(activity.sport_type);
$: distance =
measurementSystem === 'metric'
? { value: activity.distance_km, unit: 'km' }
: { value: activity.distance_miles, unit: 'mi' };
$: speed =
measurementSystem === 'metric'
? { value: activity.average_speed_kmh, unit: 'km/h' }
: { value: activity.average_speed_mph, unit: 'mph' };
$: maxSpeed =
measurementSystem === 'metric'
? { value: activity.max_speed_kmh, unit: 'km/h' }
: { value: activity.max_speed_mph, unit: 'mph' };
$: elevation = convertElevation(activity.total_elevation_gain, measurementSystem);
$: paceSeconds =
measurementSystem === 'metric' ? activity.pace_per_km_seconds : activity.pace_per_mile_seconds;
</script> </script>
<div class="card bg-base-50 border border-base-200 hover:shadow-md transition-shadow"> <div class="card bg-base-50 border border-base-200 hover:shadow-md transition-shadow">
@ -147,8 +174,8 @@
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4"> <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4">
<div class="stat bg-base-100 rounded-lg p-3"> <div class="stat bg-base-100 rounded-lg p-3">
<div class="stat-title text-xs">Distance</div> <div class="stat-title text-xs">Distance</div>
<div class="stat-value text-lg">{activity.distance_km.toFixed(2)}</div> <div class="stat-value text-lg">{distance.value.toFixed(2)}</div>
<div class="stat-desc">km ({activity.distance_miles.toFixed(2)} mi)</div> <div class="stat-desc">{distance.unit}</div>
</div> </div>
<div class="stat bg-base-100 rounded-lg p-3"> <div class="stat bg-base-100 rounded-lg p-3">
<div class="stat-title text-xs">Time</div> <div class="stat-title text-xs">Time</div>
@ -157,13 +184,13 @@
</div> </div>
<div class="stat bg-base-100 rounded-lg p-3"> <div class="stat bg-base-100 rounded-lg p-3">
<div class="stat-title text-xs">Avg Speed</div> <div class="stat-title text-xs">Avg Speed</div>
<div class="stat-value text-lg">{activity.average_speed_kmh.toFixed(1)}</div> <div class="stat-value text-lg">{speed.value.toFixed(1)}</div>
<div class="stat-desc">km/h ({activity.average_speed_mph.toFixed(1)} mph)</div> <div class="stat-desc">{speed.unit}</div>
</div> </div>
<div class="stat bg-base-100 rounded-lg p-3"> <div class="stat bg-base-100 rounded-lg p-3">
<div class="stat-title text-xs">Elevation</div> <div class="stat-title text-xs">Elevation</div>
<div class="stat-value text-lg">{activity.total_elevation_gain.toFixed(0)}</div> <div class="stat-value text-lg">{elevation.value.toFixed(0)}</div>
<div class="stat-desc">m gain</div> <div class="stat-desc">{elevation.unit} gain</div>
</div> </div>
</div> </div>
@ -197,15 +224,16 @@
</div> </div>
<!-- Footer with pace and max speed --> <!-- Footer with pace and max speed -->
{#if activity.pace_per_km_seconds} {#if paceSeconds}
<div class="flex justify-between items-center mt-3 pt-3 border-t border-base-300"> <div class="flex justify-between items-center mt-3 pt-3 border-t border-base-300">
<div class="text-sm"> <div class="text-sm">
<span class="font-medium">Pace:</span> <span class="font-medium">Pace:</span>
{formatPace(activity.pace_per_km_seconds)}/km {formatPace(paceSeconds, measurementSystem)}
</div> </div>
<div class="text-sm"> <div class="text-sm">
<span class="font-medium">Max Speed:</span> <span class="font-medium">Max Speed:</span>
{activity.max_speed_kmh.toFixed(1)} km/h {maxSpeed.value.toFixed(1)}
{maxSpeed.unit}
</div> </div>
</div> </div>
{/if} {/if}

View file

@ -45,6 +45,7 @@
export let visits: (Visit | TransportationVisit)[] | null = null; export let visits: (Visit | TransportationVisit)[] | null = null;
export let objectId: string; export let objectId: string;
export let trails: Trail[] = []; export let trails: Trail[] = [];
export let measurementSystem: 'metric' | 'imperial' = 'metric';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@ -627,7 +628,7 @@
name: stravaActivity.name, name: stravaActivity.name,
type: stravaActivity.type, type: stravaActivity.type,
sport_type: stravaActivity.sport_type || stravaActivity.type, sport_type: stravaActivity.sport_type || stravaActivity.type,
distance: stravaActivity.distance ? stravaActivity.distance / 1000 : null, // Convert to km distance: stravaActivity.distance || null, // Keep in meters
moving_time: stravaActivity.moving_time ? formatDuration(stravaActivity.moving_time) : '', moving_time: stravaActivity.moving_time ? formatDuration(stravaActivity.moving_time) : '',
elapsed_time: stravaActivity.elapsed_time elapsed_time: stravaActivity.elapsed_time
? formatDuration(stravaActivity.elapsed_time) ? formatDuration(stravaActivity.elapsed_time)
@ -1175,7 +1176,6 @@
class="input input-bordered input-sm w-full mt-1" class="input input-bordered input-sm w-full mt-1"
placeholder="Morning Run" placeholder="Morning Run"
bind:value={activityForm.name} bind:value={activityForm.name}
readonly={!!pendingStravaImport[visit.id]}
/> />
</div> </div>
@ -1588,6 +1588,7 @@
{activity} {activity}
{trails} {trails}
{visit} {visit}
{measurementSystem}
on:delete={(event) => on:delete={(event) =>
deleteActivity(event.detail.visitId, event.detail.activityId)} deleteActivity(event.detail.visitId, event.detail.activityId)}
/> />
@ -1619,6 +1620,7 @@
<StravaActivityCard <StravaActivityCard
{activity} {activity}
on:import={(event) => handleStravaActivityImport(event, visit.id)} on:import={(event) => handleStravaActivityImport(event, visit.id)}
{measurementSystem}
/> />
</div> </div>
{/each} {/each}

View file

@ -623,3 +623,22 @@ export function getBasemapUrl() {
} }
return 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json'; return 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json';
} }
export function getDistance(measurementSystem: 'metric' | 'imperial', meters: number): string {
if (measurementSystem === 'imperial') {
const miles = meters / 1609.34;
return miles.toFixed(2);
} else {
const km = meters / 1000;
return km.toFixed(2);
}
}
export function getElevation(measurementSystem: 'metric' | 'imperial', elevation: number): string {
if (measurementSystem === 'imperial') {
const feet = elevation * 3.28084;
return Math.round(feet).toString();
} else {
return Math.round(elevation).toString();
}
}

View file

@ -613,6 +613,7 @@
readOnly={true} readOnly={true}
trails={adventure.trails} trails={adventure.trails}
{visit} {visit}
measurementSystem={data.user?.measurement_system || 'metric'}
/> />
{/each} {/each}
</div> </div>