1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-19 04:49:37 +02:00

feat: add distance calculation to Transportation model and update TransportationCard to display distance in km and miles

This commit is contained in:
Sean Morley 2025-05-30 12:33:30 -04:00
parent 53d370297e
commit 514ee85767
3 changed files with 62 additions and 31 deletions

View file

@ -5,6 +5,7 @@ from rest_framework import serializers
from main.utils import CustomModelSerializer from main.utils import CustomModelSerializer
from users.serializers import CustomUserDetailsSerializer from users.serializers import CustomUserDetailsSerializer
from worldtravel.serializers import CountrySerializer, RegionSerializer, CitySerializer from worldtravel.serializers import CountrySerializer, RegionSerializer, CitySerializer
from geopy.distance import geodesic
class AdventureImageSerializer(CustomModelSerializer): class AdventureImageSerializer(CustomModelSerializer):
@ -194,15 +195,31 @@ class AdventureSerializer(CustomModelSerializer):
return instance return instance
class TransportationSerializer(CustomModelSerializer): class TransportationSerializer(CustomModelSerializer):
distance = serializers.SerializerMethodField()
class Meta: class Meta:
model = Transportation model = Transportation
fields = [ fields = [
'id', 'user_id', 'type', 'name', 'description', 'rating', 'id', 'user_id', 'type', 'name', 'description', 'rating',
'link', 'date', 'flight_number', 'from_location', 'to_location', '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): class LodgingSerializer(CustomModelSerializer):

View file

@ -23,6 +23,8 @@
export let user: User | null = null; export let user: User | null = null;
export let collection: Collection | null = null; export let collection: Collection | null = null;
const toMiles = (km: any) => (Number(km) * 0.621371).toFixed(1);
let isWarningModalOpen: boolean = false; let isWarningModalOpen: boolean = false;
function editTransportation() { function editTransportation() {
@ -109,14 +111,14 @@
<div <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" 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"> <div class="card-body p-6 space-y-6">
<!-- Title & Mode --> <!-- Header -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2"> <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="flex flex-wrap gap-2">
<div class="badge badge-secondary"> <div class="badge badge-secondary">
{$t(`transportation.modes.${transportation.type}`)} {$t(`transportation.modes.${transportation.type}`)}
{' '}{getTransportationIcon(transportation.type)} {getTransportationIcon(transportation.type)}
</div> </div>
{#if transportation.type === 'plane' && transportation.flight_number} {#if transportation.type === 'plane' && transportation.flight_number}
<div class="badge badge-neutral">{transportation.flight_number}</div> <div class="badge badge-neutral">{transportation.flight_number}</div>
@ -127,50 +129,61 @@
</div> </div>
</div> </div>
<!-- Start Section --> <!-- Route Info -->
<div class="space-y-2"> <div class="space-y-3">
{#if transportation.from_location} {#if transportation.from_location}
<div class="flex items-center gap-2"> <div class="flex gap-2 text-sm">
<span class="text-sm font-medium">{$t('adventures.from')}:</span> <span class="font-medium whitespace-nowrap">{$t('adventures.from')}:</span>
<p class="text-sm break-words">{transportation.from_location}</p> <span class="break-words">{transportation.from_location}</span>
</div> </div>
{/if} {/if}
{#if transportation.date}
<div class="flex items-center gap-2"> {#if transportation.to_location}
<span class="text-sm font-medium">{$t('adventures.start')}:</span> <div class="flex gap-2 text-sm">
<p class="text-sm"> <span class="font-medium whitespace-nowrap">{$t('adventures.to')}:</span>
{formatDateInTimezone(transportation.date, transportation.start_timezone)} <span class="break-words">{transportation.to_location}</span>
{#if transportation.start_timezone} </div>
<span class="ml-1 text-xs opacity-60">({transportation.start_timezone})</span> {/if}
{/if}
</p> {#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> </div>
{/if} {/if}
</div> </div>
<!-- End Section --> <!-- Time Info -->
<div class="space-y-2"> <div class="space-y-3">
{#if transportation.to_location} {#if transportation.date}
<div class="flex items-center gap-2"> <div class="flex gap-2 text-sm">
<span class="text-sm font-medium">{$t('adventures.to')}:</span> <span class="font-medium whitespace-nowrap">{$t('adventures.start')}:</span>
<p class="text-sm break-words">{transportation.to_location}</p> <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> </div>
{/if} {/if}
{#if transportation.end_date} {#if transportation.end_date}
<div class="flex items-center gap-2"> <div class="flex gap-2 text-sm">
<span class="text-sm font-medium">{$t('adventures.end')}:</span> <span class="font-medium whitespace-nowrap">{$t('adventures.end')}:</span>
<p class="text-sm"> <span>
{formatDateInTimezone(transportation.end_date, transportation.end_timezone)} {formatDateInTimezone(transportation.end_date, transportation.end_timezone)}
{#if transportation.end_timezone} {#if transportation.end_timezone}
<span class="ml-1 text-xs opacity-60">({transportation.end_timezone})</span> <span class="ml-1 text-xs opacity-60">({transportation.end_timezone})</span>
{/if} {/if}
</p> </span>
</div> </div>
{/if} {/if}
</div> </div>
<!-- Actions --> <!-- 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"> <div class="pt-4 border-t border-base-300 flex justify-end gap-2">
<button <button
class="btn btn-neutral btn-sm flex items-center gap-1" class="btn btn-neutral btn-sm flex items-center gap-1"

View file

@ -170,6 +170,7 @@ export type Transportation = {
destination_latitude: number | null; destination_latitude: number | null;
destination_longitude: number | null; destination_longitude: number | null;
is_public: boolean; is_public: boolean;
distance: number | null; // in kilometers
collection: Collection | null | string; collection: Collection | null | string;
created_at: string; // ISO 8601 date string created_at: string; // ISO 8601 date string
updated_at: string; // ISO 8601 date string updated_at: string; // ISO 8601 date string