mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-08-07 06:05:19 +02:00
feat: add TrailCard component for displaying trail details with measurement system support
This commit is contained in:
parent
546ae7aa2e
commit
eb3c9c2b64
2 changed files with 160 additions and 27 deletions
155
frontend/src/lib/components/TrailCard.svelte
Normal file
155
frontend/src/lib/components/TrailCard.svelte
Normal file
|
@ -0,0 +1,155 @@
|
|||
<script lang="ts">
|
||||
import type { Trail } from '$lib/types';
|
||||
|
||||
// Icons (only those used)
|
||||
import Calendar from '~icons/mdi/calendar';
|
||||
import Camera from '~icons/mdi/camera';
|
||||
import Clock from '~icons/mdi/clock';
|
||||
import MapPin from '~icons/mdi/map-marker';
|
||||
import TrendingUp from '~icons/mdi/trending-up';
|
||||
import Users from '~icons/mdi/account-supervisor';
|
||||
|
||||
export let trail: Trail;
|
||||
export let measurementSystem: 'metric' | 'imperial' = 'metric';
|
||||
|
||||
function getDistance(meters: number) {
|
||||
return measurementSystem === 'imperial'
|
||||
? `${(meters * 0.000621371).toFixed(2)} mi`
|
||||
: `${(meters / 1000).toFixed(2)} km`;
|
||||
}
|
||||
|
||||
function getElevation(meters: number) {
|
||||
return measurementSystem === 'imperial'
|
||||
? `${(meters * 3.28084).toFixed(1)} ft`
|
||||
: `${meters.toFixed(1)} m`;
|
||||
}
|
||||
|
||||
function getDuration(minutes: number) {
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const mins = minutes % 60;
|
||||
return hours > 0 ? `${hours}h ${mins}m` : `${mins}m`;
|
||||
}
|
||||
|
||||
function formatDate(date: string | number | Date) {
|
||||
return new Date(date).toLocaleDateString();
|
||||
}
|
||||
|
||||
function pluralize(count: number, singular: string, plural = singular + 's') {
|
||||
return `${count} ${count === 1 ? singular : plural}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<!-- Trail Name -->
|
||||
<h2 class="text-xl font-bold leading-tight mb-1">{trail.name}</h2>
|
||||
|
||||
<!-- Provider + Created Date -->
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
{#if trail.provider}
|
||||
<span class="badge badge-outline badge-sm">{trail.provider}</span>
|
||||
{/if}
|
||||
<span class="text-sm opacity-70">
|
||||
Created: {formatDate(trail.created_at)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{#if trail.wanderer_data}
|
||||
<div class="mb-4 space-y-3">
|
||||
<!-- Trail Stats -->
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<MapPin class="w-3 h-3 text-base-content/60" />
|
||||
<span class="text-base-content/80">
|
||||
{getDistance(trail.wanderer_data.distance)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{#if trail.wanderer_data.duration > 0}
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<Clock class="w-3 h-3 text-base-content/60" />
|
||||
<span class="text-base-content/80">
|
||||
{getDuration(trail.wanderer_data.duration)}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if trail.wanderer_data.elevation_gain > 0}
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<TrendingUp class="w-3 h-3 text-base-content/60" />
|
||||
<span class="text-base-content/80">
|
||||
{getElevation(trail.wanderer_data.elevation_gain)} gain
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<Calendar class="w-3 h-3 text-base-content/60" />
|
||||
<span class="text-base-content/80">
|
||||
{formatDate(trail.wanderer_data.date)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Difficulty + Likes -->
|
||||
{#if trail.wanderer_data.difficulty}
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="badge badge-sm"
|
||||
class:badge-success={trail.wanderer_data.difficulty === 'easy'}
|
||||
class:badge-warning={trail.wanderer_data.difficulty === 'moderate'}
|
||||
class:badge-error={trail.wanderer_data.difficulty === 'hard'}
|
||||
>
|
||||
{trail.wanderer_data.difficulty}
|
||||
</span>
|
||||
|
||||
{#if trail.wanderer_data.like_count > 0}
|
||||
<div class="flex items-center gap-1 text-xs text-base-content/60">
|
||||
<Users class="w-3 h-3" />
|
||||
{pluralize(trail.wanderer_data.like_count, 'like')}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Description -->
|
||||
{#if trail.wanderer_data.description}
|
||||
<div class="text-sm text-base-content/70 leading-relaxed">
|
||||
{@html trail.wanderer_data.description}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Location -->
|
||||
{#if trail.wanderer_data.location}
|
||||
<div class="text-xs text-base-content/60 flex items-center gap-1">
|
||||
<MapPin class="w-3 h-3" />
|
||||
{trail.wanderer_data.location}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Photos -->
|
||||
{#if trail.wanderer_data.photos?.length > 0}
|
||||
<div class="flex items-center gap-1 text-xs text-base-content/60">
|
||||
<Camera class="w-3 h-3" />
|
||||
{pluralize(trail.wanderer_data.photos.length, 'photo')}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if trail.link}
|
||||
<a
|
||||
href={trail.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-sm btn-primary"
|
||||
>
|
||||
🔗 View Trail
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -21,6 +21,7 @@
|
|||
import AttachmentCard from '$lib/components/AttachmentCard.svelte';
|
||||
import { getBasemapUrl, isAllDay } from '$lib';
|
||||
import ActivityCard from '$lib/components/ActivityCard.svelte';
|
||||
import TrailCard from '$lib/components/TrailCard.svelte';
|
||||
|
||||
let geojson: any;
|
||||
|
||||
|
@ -504,33 +505,10 @@
|
|||
<h2 class="card-title text-2xl mb-6">🥾 {$t('adventures.trails')}</h2>
|
||||
<div class="grid gap-4">
|
||||
{#each adventure.trails as trail}
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<h3 class="font-bold text-lg">{trail.name}</h3>
|
||||
<div class="flex items-center gap-2 mt-2">
|
||||
{#if trail.provider}
|
||||
<span class="badge badge-outline badge-sm">{trail.provider}</span>
|
||||
{/if}
|
||||
<span class="text-sm opacity-70">
|
||||
Created: {new Date(trail.created_at).toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{#if trail.link}
|
||||
<a
|
||||
href={trail.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-sm btn-primary"
|
||||
>
|
||||
🔗 View Trail
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TrailCard
|
||||
{trail}
|
||||
measurementSystem={data.user?.measurement_system || 'metric'}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue