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:
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 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):
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue