mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-07-19 12:59:36 +02:00
feat: add distance calculation to Transportation model and update TransportationCard to display distance in km and miles
This commit is contained in:
parent
53d370297e
commit
514ee85767
3 changed files with 62 additions and 31 deletions
|
@ -5,6 +5,7 @@ from rest_framework import serializers
|
|||
from main.utils import CustomModelSerializer
|
||||
from users.serializers import CustomUserDetailsSerializer
|
||||
from worldtravel.serializers import CountrySerializer, RegionSerializer, CitySerializer
|
||||
from geopy.distance import geodesic
|
||||
|
||||
|
||||
class AdventureImageSerializer(CustomModelSerializer):
|
||||
|
@ -194,15 +195,31 @@ class AdventureSerializer(CustomModelSerializer):
|
|||
return instance
|
||||
|
||||
class TransportationSerializer(CustomModelSerializer):
|
||||
distance = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Transportation
|
||||
fields = [
|
||||
'id', 'user_id', 'type', 'name', 'description', 'rating',
|
||||
'link', 'date', 'flight_number', 'from_location', 'to_location',
|
||||
'is_public', 'collection', 'created_at', 'updated_at', 'end_date', 'origin_latitude', 'origin_longitude', 'destination_latitude', 'destination_longitude', 'start_timezone', 'end_timezone'
|
||||
'is_public', 'collection', 'created_at', 'updated_at', 'end_date',
|
||||
'origin_latitude', 'origin_longitude', 'destination_latitude', 'destination_longitude',
|
||||
'start_timezone', 'end_timezone', 'distance' # ✅ Add distance here
|
||||
]
|
||||
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id']
|
||||
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id', 'distance']
|
||||
|
||||
def get_distance(self, obj):
|
||||
if (
|
||||
obj.origin_latitude and obj.origin_longitude and
|
||||
obj.destination_latitude and obj.destination_longitude
|
||||
):
|
||||
try:
|
||||
origin = (float(obj.origin_latitude), float(obj.origin_longitude))
|
||||
destination = (float(obj.destination_latitude), float(obj.destination_longitude))
|
||||
return round(geodesic(origin, destination).km, 2)
|
||||
except ValueError:
|
||||
return None
|
||||
return None
|
||||
|
||||
class LodgingSerializer(CustomModelSerializer):
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
export let user: User | null = null;
|
||||
export let collection: Collection | null = null;
|
||||
|
||||
const toMiles = (km: any) => (Number(km) * 0.621371).toFixed(1);
|
||||
|
||||
let isWarningModalOpen: boolean = false;
|
||||
|
||||
function editTransportation() {
|
||||
|
@ -109,14 +111,14 @@
|
|||
<div
|
||||
class="card w-full max-w-md bg-base-300 text-base-content shadow-2xl hover:shadow-3xl transition-all duration-300 border border-base-300 hover:border-primary/20 group"
|
||||
>
|
||||
<div class="card-body p-6 space-y-4">
|
||||
<!-- Title & Mode -->
|
||||
<div class="card-body p-6 space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
|
||||
<h2 class="card-title text-xl font-semibold truncate">{transportation.name}</h2>
|
||||
<h2 class="text-xl font-bold truncate">{transportation.name}</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div class="badge badge-secondary">
|
||||
{$t(`transportation.modes.${transportation.type}`)}
|
||||
{' '}{getTransportationIcon(transportation.type)}
|
||||
{getTransportationIcon(transportation.type)}
|
||||
</div>
|
||||
{#if transportation.type === 'plane' && transportation.flight_number}
|
||||
<div class="badge badge-neutral">{transportation.flight_number}</div>
|
||||
|
@ -127,50 +129,61 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Start Section -->
|
||||
<div class="space-y-2">
|
||||
<!-- Route Info -->
|
||||
<div class="space-y-3">
|
||||
{#if transportation.from_location}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-medium">{$t('adventures.from')}:</span>
|
||||
<p class="text-sm break-words">{transportation.from_location}</p>
|
||||
<div class="flex gap-2 text-sm">
|
||||
<span class="font-medium whitespace-nowrap">{$t('adventures.from')}:</span>
|
||||
<span class="break-words">{transportation.from_location}</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if transportation.date}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-medium">{$t('adventures.start')}:</span>
|
||||
<p class="text-sm">
|
||||
{formatDateInTimezone(transportation.date, transportation.start_timezone)}
|
||||
{#if transportation.start_timezone}
|
||||
<span class="ml-1 text-xs opacity-60">({transportation.start_timezone})</span>
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
{#if transportation.to_location}
|
||||
<div class="flex gap-2 text-sm">
|
||||
<span class="font-medium whitespace-nowrap">{$t('adventures.to')}:</span>
|
||||
<span class="break-words">{transportation.to_location}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if transportation.distance && !isNaN(+transportation.distance)}
|
||||
<div class="flex gap-2 text-sm">
|
||||
<span class="font-medium whitespace-nowrap">{$t('adventures.distance')}:</span>
|
||||
<span>
|
||||
{(+transportation.distance).toFixed(1)} km / {toMiles(transportation.distance)} mi
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- End Section -->
|
||||
<div class="space-y-2">
|
||||
{#if transportation.to_location}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-medium">{$t('adventures.to')}:</span>
|
||||
<p class="text-sm break-words">{transportation.to_location}</p>
|
||||
<!-- Time Info -->
|
||||
<div class="space-y-3">
|
||||
{#if transportation.date}
|
||||
<div class="flex gap-2 text-sm">
|
||||
<span class="font-medium whitespace-nowrap">{$t('adventures.start')}:</span>
|
||||
<span>
|
||||
{formatDateInTimezone(transportation.date, transportation.start_timezone)}
|
||||
{#if transportation.start_timezone}
|
||||
<span class="ml-1 text-xs opacity-60">({transportation.start_timezone})</span>
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if transportation.end_date}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-medium">{$t('adventures.end')}:</span>
|
||||
<p class="text-sm">
|
||||
<div class="flex gap-2 text-sm">
|
||||
<span class="font-medium whitespace-nowrap">{$t('adventures.end')}:</span>
|
||||
<span>
|
||||
{formatDateInTimezone(transportation.end_date, transportation.end_timezone)}
|
||||
{#if transportation.end_timezone}
|
||||
<span class="ml-1 text-xs opacity-60">({transportation.end_timezone})</span>
|
||||
{/if}
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
{#if transportation.user_id == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
|
||||
{#if transportation.user_id === user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
|
||||
<div class="pt-4 border-t border-base-300 flex justify-end gap-2">
|
||||
<button
|
||||
class="btn btn-neutral btn-sm flex items-center gap-1"
|
||||
|
|
|
@ -170,6 +170,7 @@ export type Transportation = {
|
|||
destination_latitude: number | null;
|
||||
destination_longitude: number | null;
|
||||
is_public: boolean;
|
||||
distance: number | null; // in kilometers
|
||||
collection: Collection | null | string;
|
||||
created_at: string; // ISO 8601 date string
|
||||
updated_at: string; // ISO 8601 date string
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue