diff --git a/backend/server/achievements/migrations/0002_achievement_key_achievement_type.py b/backend/server/achievements/migrations/0002_achievement_key_achievement_type.py new file mode 100644 index 0000000..f97de29 --- /dev/null +++ b/backend/server/achievements/migrations/0002_achievement_key_achievement_type.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.8 on 2025-02-08 01:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('achievements', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='achievement', + name='key', + field=models.CharField(default='achievements.other', max_length=255, unique=True), + ), + migrations.AddField( + model_name='achievement', + name='type', + field=models.CharField(choices=[('adventure_count', 'adventure_count'), ('country_count', 'country_count')], default='adventure_count', max_length=255), + ), + ] diff --git a/backend/server/achievements/models.py b/backend/server/achievements/models.py index 8659bc2..c23bb31 100644 --- a/backend/server/achievements/models.py +++ b/backend/server/achievements/models.py @@ -1,3 +1,4 @@ +import uuid from django.db import models from django.contrib.auth import get_user_model @@ -11,8 +12,8 @@ VALID_ACHIEVEMENT_TYPES = [ class Achievement(models.Model): """Stores all possible achievements""" name = models.CharField(max_length=255, unique=True) - key = models.CharField(max_length=255, unique=True) # Used for frontend lookups, e.g. "achievements.first_adventure" - type = models.CharField(max_length=255) # adventure_count, country_count, etc. + key = models.CharField(max_length=255, unique=True, default='achievements.other') # Used for frontend lookups, e.g. "achievements.first_adventure" + type = models.CharField(max_length=255, choices=[(tag, tag) for tag in VALID_ACHIEVEMENT_TYPES], default='adventure_count') # adventure_count, country_count, etc. description = models.TextField() icon = models.ImageField(upload_to="achievements/", null=True, blank=True) condition = models.JSONField() # Stores rules like {"type": "adventure_count", "value": 10} diff --git a/backend/server/adventures/admin.py b/backend/server/adventures/admin.py index 51c9bac..67b3d2e 100644 --- a/backend/server/adventures/admin.py +++ b/backend/server/adventures/admin.py @@ -1,7 +1,7 @@ import os from django.contrib import admin from django.utils.html import mark_safe -from .models import Adventure, Checklist, ChecklistItem, Collection, Transportation, Note, AdventureImage, Visit, Category, Attachment, Hotel +from .models import Adventure, Checklist, ChecklistItem, Collection, Transportation, Note, AdventureImage, Visit, Category, Attachment, Lodging from worldtravel.models import Country, Region, VisitedRegion, City, VisitedCity from allauth.account.decorators import secure_admin_login @@ -140,7 +140,7 @@ admin.site.register(Category, CategoryAdmin) admin.site.register(City, CityAdmin) admin.site.register(VisitedCity) admin.site.register(Attachment) -admin.site.register(Hotel) +admin.site.register(Lodging) admin.site.site_header = 'AdventureLog Admin' admin.site.site_title = 'AdventureLog Admin Site' diff --git a/backend/server/adventures/migrations/0023_lodging_delete_hotel.py b/backend/server/adventures/migrations/0023_lodging_delete_hotel.py new file mode 100644 index 0000000..44e502c --- /dev/null +++ b/backend/server/adventures/migrations/0023_lodging_delete_hotel.py @@ -0,0 +1,43 @@ +# Generated by Django 5.0.8 on 2025-02-08 01:50 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('adventures', '0022_hotel'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Lodging', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('name', models.CharField(max_length=200)), + ('type', models.CharField(choices=[('hotel', 'Hotel'), ('hostel', 'Hostel'), ('resort', 'Resort'), ('bnb', 'Bed & Breakfast'), ('campground', 'Campground'), ('cabin', 'Cabin'), ('apartment', 'Apartment'), ('house', 'House'), ('villa', 'Villa'), ('motel', 'Motel'), ('other', 'Other')], default='other', max_length=100)), + ('description', models.TextField(blank=True, null=True)), + ('rating', models.FloatField(blank=True, null=True)), + ('link', models.URLField(blank=True, max_length=2083, null=True)), + ('check_in', models.DateTimeField(blank=True, null=True)), + ('check_out', models.DateTimeField(blank=True, null=True)), + ('reservation_number', models.CharField(blank=True, max_length=100, null=True)), + ('price', models.DecimalField(blank=True, decimal_places=2, max_digits=9, null=True)), + ('latitude', models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True)), + ('longitude', models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True)), + ('location', models.CharField(blank=True, max_length=200, null=True)), + ('is_public', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('collection', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='adventures.collection')), + ('user_id', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.DeleteModel( + name='Hotel', + ), + ] diff --git a/backend/server/adventures/models.py b/backend/server/adventures/models.py index 96d439b..ad52e89 100644 --- a/backend/server/adventures/models.py +++ b/backend/server/adventures/models.py @@ -36,6 +36,20 @@ ADVENTURE_TYPES = [ ('other', 'Other') ] +LODGING_TYPES = [ + ('hotel', 'Hotel'), + ('hostel', 'Hostel'), + ('resort', 'Resort'), + ('bnb', 'Bed & Breakfast'), + ('campground', 'Campground'), + ('cabin', 'Cabin'), + ('apartment', 'Apartment'), + ('house', 'House'), + ('villa', 'Villa'), + ('motel', 'Motel'), + ('other', 'Other') +] + TRANSPORTATION_TYPES = [ ('car', 'Car'), ('plane', 'Plane'), @@ -320,11 +334,12 @@ class Category(models.Model): def __str__(self): return self.name + ' - ' + self.display_name + ' - ' + self.icon -class Hotel(models.Model): +class Lodging(models.Model): id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) user_id = models.ForeignKey( User, on_delete=models.CASCADE, default=default_user_id) name = models.CharField(max_length=200) + type = models.CharField(max_length=100, choices=LODGING_TYPES, default='other') description = models.TextField(blank=True, null=True) rating = models.FloatField(blank=True, null=True) link = models.URLField(blank=True, null=True, max_length=2083) @@ -346,9 +361,9 @@ class Hotel(models.Model): if self.collection: if self.collection.is_public and not self.is_public: - raise ValidationError('Hotels associated with a public collection must be public. Collection: ' + self.collection.name + ' Hotel: ' + self.name) + raise ValidationError('Lodging associated with a public collection must be public. Collection: ' + self.collection.name + ' Loging: ' + self.name) if self.user_id != self.collection.user_id: - raise ValidationError('Hotels must be associated with collections owned by the same user. Collection owner: ' + self.collection.user_id.username + ' Hotel owner: ' + self.user_id.username) + raise ValidationError('Lodging must be associated with collections owned by the same user. Collection owner: ' + self.collection.user_id.username + ' Lodging owner: ' + self.user_id.username) def __str__(self): return self.name \ No newline at end of file diff --git a/backend/server/adventures/serializers.py b/backend/server/adventures/serializers.py index 5771c7c..97dd633 100644 --- a/backend/server/adventures/serializers.py +++ b/backend/server/adventures/serializers.py @@ -1,6 +1,6 @@ from django.utils import timezone import os -from .models import Adventure, AdventureImage, ChecklistItem, Collection, Note, Transportation, Checklist, Visit, Category, Attachment, Hotel +from .models import Adventure, AdventureImage, ChecklistItem, Collection, Note, Transportation, Checklist, Visit, Category, Attachment, Lodging from rest_framework import serializers from main.utils import CustomModelSerializer from users.serializers import CustomUserDetailsSerializer @@ -203,14 +203,14 @@ class TransportationSerializer(CustomModelSerializer): ] read_only_fields = ['id', 'created_at', 'updated_at', 'user_id'] -class HotelSerializer(CustomModelSerializer): +class LodgingSerializer(CustomModelSerializer): class Meta: - model = Hotel + model = Lodging fields = [ 'id', 'user_id', 'name', 'description', 'rating', 'link', 'check_in', 'check_out', 'reservation_number', 'price', 'latitude', 'longitude', 'location', 'is_public', - 'collection', 'created_at', 'updated_at' + 'collection', 'created_at', 'updated_at', 'type' ] read_only_fields = ['id', 'created_at', 'updated_at', 'user_id'] @@ -300,11 +300,11 @@ class CollectionSerializer(CustomModelSerializer): transportations = TransportationSerializer(many=True, read_only=True, source='transportation_set') notes = NoteSerializer(many=True, read_only=True, source='note_set') checklists = ChecklistSerializer(many=True, read_only=True, source='checklist_set') - hotels = HotelSerializer(many=True, read_only=True, source='hotel_set') + lodging = LodgingSerializer(many=True, read_only=True, source='lodging_set') class Meta: model = Collection - fields = ['id', 'description', 'user_id', 'name', 'is_public', 'adventures', 'created_at', 'start_date', 'end_date', 'transportations', 'notes', 'updated_at', 'checklists', 'is_archived', 'shared_with', 'link', 'hotels'] + fields = ['id', 'description', 'user_id', 'name', 'is_public', 'adventures', 'created_at', 'start_date', 'end_date', 'transportations', 'notes', 'updated_at', 'checklists', 'is_archived', 'shared_with', 'link', 'lodging'] read_only_fields = ['id', 'created_at', 'updated_at', 'user_id'] def to_representation(self, instance): diff --git a/backend/server/adventures/urls.py b/backend/server/adventures/urls.py index 3c7eff8..1a98273 100644 --- a/backend/server/adventures/urls.py +++ b/backend/server/adventures/urls.py @@ -18,7 +18,7 @@ router.register(r'ics-calendar', IcsCalendarGeneratorViewSet, basename='ics-cale router.register(r'overpass', OverpassViewSet, basename='overpass') router.register(r'search', GlobalSearchView, basename='search') router.register(r'attachments', AttachmentViewSet, basename='attachments') -router.register(r'hotels', HotelViewSet, basename='hotels') +router.register(r'lodging', LodgingViewSet, basename='lodging') urlpatterns = [ diff --git a/backend/server/adventures/views/__init__.py b/backend/server/adventures/views/__init__.py index 957af52..8f531f7 100644 --- a/backend/server/adventures/views/__init__.py +++ b/backend/server/adventures/views/__init__.py @@ -13,4 +13,4 @@ from .stats_view import * from .transportation_view import * from .global_search_view import * from .attachment_view import * -from .hotel_view import * \ No newline at end of file +from .lodging_view import * \ No newline at end of file diff --git a/backend/server/adventures/views/hotel_view.py b/backend/server/adventures/views/lodging_view.py similarity index 91% rename from backend/server/adventures/views/hotel_view.py rename to backend/server/adventures/views/lodging_view.py index 4f5b0eb..16114ba 100644 --- a/backend/server/adventures/views/hotel_view.py +++ b/backend/server/adventures/views/lodging_view.py @@ -2,21 +2,21 @@ from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response from django.db.models import Q -from adventures.models import Hotel -from adventures.serializers import HotelSerializer +from adventures.models import Lodging +from adventures.serializers import LodgingSerializer from rest_framework.exceptions import PermissionDenied from adventures.permissions import IsOwnerOrSharedWithFullAccess from rest_framework.permissions import IsAuthenticated -class HotelViewSet(viewsets.ModelViewSet): - queryset = Hotel.objects.all() - serializer_class = HotelSerializer +class LodgingViewSet(viewsets.ModelViewSet): + queryset = Lodging.objects.all() + serializer_class = LodgingSerializer permission_classes = [IsOwnerOrSharedWithFullAccess] def list(self, request, *args, **kwargs): if not request.user.is_authenticated: return Response(status=status.HTTP_403_FORBIDDEN) - queryset = Hotel.objects.filter( + queryset = Lodging.objects.filter( Q(user_id=request.user.id) ) serializer = self.get_serializer(queryset, many=True) @@ -26,11 +26,11 @@ class HotelViewSet(viewsets.ModelViewSet): user = self.request.user if self.action == 'retrieve': # For individual adventure retrieval, include public adventures, user's own adventures and shared adventures - return Hotel.objects.filter( + return Lodging.objects.filter( Q(is_public=True) | Q(user_id=user.id) | Q(collection__shared_with=user.id) ).distinct().order_by('-updated_at') # For other actions, include user's own adventures and shared adventures - return Hotel.objects.filter( + return Lodging.objects.filter( Q(user_id=user.id) | Q(collection__shared_with=user.id) ).distinct().order_by('-updated_at') diff --git a/documentation/.vitepress/config.mts b/documentation/.vitepress/config.mts index 5638acf..dbd9299 100644 --- a/documentation/.vitepress/config.mts +++ b/documentation/.vitepress/config.mts @@ -178,6 +178,8 @@ export default defineConfig({ { icon: "github", link: "https://github.com/seanmorley15/AdventureLog" }, { icon: "discord", link: "https://discord.gg/wRbQ9Egr8C" }, { icon: "buymeacoffee", link: "https://buymeacoffee.com/seanmorley15" }, + { icon: "x", link: "https://x.com/AdventureLogApp" }, + { icon: "mastodon", link: "https://mastodon.social/@adventurelog" }, ], }, }); diff --git a/frontend/src/lib/components/LocationDropdown.svelte b/frontend/src/lib/components/LocationDropdown.svelte index 42328f4..83a2837 100644 --- a/frontend/src/lib/components/LocationDropdown.svelte +++ b/frontend/src/lib/components/LocationDropdown.svelte @@ -1,11 +1,11 @@ + +{#if isWarningModalOpen} + (isWarningModalOpen = false)} + on:confirm={deleteTransportation} + /> +{/if} + + + + + + {lodging.name} + + + {lodging.type} + + + + + {#if unlinked} + {$t('adventures.out_of_range')} + {/if} + + + + {#if lodging.location} + + {$t('adventures.from')}: + {lodging.location} + + {/if} + {#if lodging.check_in && lodging.check_out} + + {$t('adventures.start')}: + {new Date(lodging.check_in).toLocaleDateString(undefined, { timeZone: 'UTC' })} + + {/if} + + + + + {#if lodging.location} + + + {$t('adventures.to')}: + + {lodging.location} + + {/if} + {#if lodging.check_out} + + {$t('adventures.end')}: + {new Date(lodging.check_out).toLocaleDateString(undefined, { timeZone: 'UTC' })} + + {/if} + + + + {#if lodging.user_id == user?.uuid || (collection && user && collection.shared_with && collection.shared_with.includes(user.uuid))} + + + + {$t('transportation.edit')} + + (isWarningModalOpen = true)} + class="btn btn-secondary btn-sm flex items-center gap-1" + title="Delete" + > + + {$t('adventures.delete')} + + + {/if} + + diff --git a/frontend/src/lib/components/HotelModal.svelte b/frontend/src/lib/components/LodgingModal.svelte similarity index 72% rename from frontend/src/lib/components/HotelModal.svelte rename to frontend/src/lib/components/LodgingModal.svelte index 241be48..f4a5c66 100644 --- a/frontend/src/lib/components/HotelModal.svelte +++ b/frontend/src/lib/components/LodgingModal.svelte @@ -3,27 +3,19 @@ import { addToast } from '$lib/toasts'; import { t } from 'svelte-i18n'; import MarkdownEditor from './MarkdownEditor.svelte'; - import { appVersion } from '$lib/config'; - import { DefaultMarker, MapEvents, MapLibre } from 'svelte-maplibre'; - import type { Collection, Hotel, ReverseGeocode, OpenStreetMapPlace, Point } from '$lib/types'; + import type { Collection, Lodging } from '$lib/types'; import LocationDropdown from './LocationDropdown.svelte'; const dispatch = createEventDispatcher(); export let collection: Collection; - export let hotelToEdit: Hotel | null = null; + export let lodgingToEdit: Lodging | null = null; let modal: HTMLDialogElement; let constrainDates: boolean = false; - let hotel: Hotel = { ...initializeHotel(hotelToEdit) }; + let lodging: Lodging = { ...initializeLodging(lodgingToEdit) }; let fullStartDate: string = ''; let fullEndDate: string = ''; - let reverseGeocodePlace: any | null = null; - let query: string = ''; - let places: OpenStreetMapPlace[] = []; - let noPlaces: boolean = false; - let is_custom_location: boolean = false; - let markers: Point[] = []; // Format date as local datetime function toLocalDatetime(value: string | null): string { @@ -32,12 +24,32 @@ return date.toISOString().slice(0, 16); // Format: YYYY-MM-DDTHH:mm } + type LodgingType = { + value: string; + label: string; + }; + + const LODGING_TYPES: LodgingType[] = [ + { value: 'hotel', label: 'Hotel' }, + { value: 'hostel', label: 'Hostel' }, + { value: 'resort', label: 'Resort' }, + { value: 'bnb', label: 'Bed & Breakfast' }, + { value: 'campground', label: 'Campground' }, + { value: 'cabin', label: 'Cabin' }, + { value: 'apartment', label: 'Apartment' }, + { value: 'house', label: 'House' }, + { value: 'villa', label: 'Villa' }, + { value: 'motel', label: 'Motel' }, + { value: 'other', label: 'Other' } + ]; + // Initialize hotel with values from hotelToEdit or default values - function initializeHotel(hotelToEdit: Hotel | null): Hotel { + function initializeLodging(hotelToEdit: Lodging | null): Lodging { return { id: hotelToEdit?.id || '', user_id: hotelToEdit?.user_id || '', name: hotelToEdit?.name || '', + type: hotelToEdit?.type || 'other', description: hotelToEdit?.description || '', rating: hotelToEdit?.rating || NaN, link: hotelToEdit?.link || '', @@ -49,7 +61,7 @@ longitude: hotelToEdit?.longitude || null, location: hotelToEdit?.location || '', is_public: hotelToEdit?.is_public || false, - collection: hotelToEdit?.collection || '', + collection: hotelToEdit?.collection || collection.id, created_at: hotelToEdit?.created_at || '', updated_at: hotelToEdit?.updated_at || '' }; @@ -63,8 +75,8 @@ // Handle rating change $: { - if (!hotel.rating) { - hotel.rating = NaN; + if (!lodging.rating) { + lodging.rating = NaN; } } @@ -88,35 +100,37 @@ async function handleSubmit(event: Event) { event.preventDefault(); - if (hotel.check_in && !hotel.check_out) { - const checkInDate = new Date(hotel.check_in); + if (lodging.check_in && !lodging.check_out) { + const checkInDate = new Date(lodging.check_in); checkInDate.setDate(checkInDate.getDate() + 1); - hotel.check_out = checkInDate.toISOString(); + lodging.check_out = checkInDate.toISOString(); } - if (hotel.check_in && hotel.check_out && hotel.check_in > hotel.check_out) { + if (lodging.check_in && lodging.check_out && lodging.check_in > lodging.check_out) { addToast('error', $t('adventures.start_before_end_error')); return; } // Create or update hotel - const url = hotel.id === '' ? '/api/hotels' : `/api/hotels/${hotel.id}`; - const method = hotel.id === '' ? 'POST' : 'PATCH'; + const url = lodging.id === '' ? '/api/lodging' : `/api/lodging/${lodging.id}`; + const method = lodging.id === '' ? 'POST' : 'PATCH'; const res = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(hotel) + body: JSON.stringify(lodging) }); const data = await res.json(); if (data.id) { - hotel = data as Hotel; + lodging = data as Lodging; const toastMessage = - hotel.id === '' ? 'adventures.adventure_created' : 'adventures.adventure_updated'; + lodging.id === '' ? 'adventures.adventure_created' : 'adventures.adventure_updated'; addToast('success', $t(toastMessage)); - dispatch('save', hotel); + dispatch('save', lodging); } else { const errorMessage = - hotel.id === '' ? 'adventures.adventure_create_error' : 'adventures.adventure_update_error'; + lodging.id === '' + ? 'adventures.adventure_create_error' + : 'adventures.adventure_update_error'; addToast('error', $t(errorMessage)); } } @@ -127,9 +141,7 @@ - {hotelToEdit - ? $t('transportation.edit_transportation') - : $t('transportation.new_transportation')} + {lodgingToEdit ? $t('lodging.edit_lodging') : $t('lodging.new_lodging')} @@ -149,7 +161,7 @@ type="text" id="name" name="name" - bind:value={hotel.name} + bind:value={lodging.name} class="input input-bordered w-full" required /> @@ -157,7 +169,7 @@ {$t('adventures.description')} - + @@ -167,7 +179,7 @@ min="0" max="5" hidden - bind:value={hotel.rating} + bind:value={lodging.rating} id="rating" name="rating" class="input input-bordered w-full max-w-xs mt-1" @@ -177,48 +189,48 @@ type="radio" name="rating-2" class="rating-hidden" - checked={Number.isNaN(hotel.rating)} + checked={Number.isNaN(lodging.rating)} /> (hotel.rating = 1)} - checked={hotel.rating === 1} + on:click={() => (lodging.rating = 1)} + checked={lodging.rating === 1} /> (hotel.rating = 2)} - checked={hotel.rating === 2} + on:click={() => (lodging.rating = 2)} + checked={lodging.rating === 2} /> (hotel.rating = 3)} - checked={hotel.rating === 3} + on:click={() => (lodging.rating = 3)} + checked={lodging.rating === 3} /> (hotel.rating = 4)} - checked={hotel.rating === 4} + on:click={() => (lodging.rating = 4)} + checked={lodging.rating === 4} /> (hotel.rating = 5)} - checked={hotel.rating === 5} + on:click={() => (lodging.rating = 5)} + checked={lodging.rating === 5} /> - {#if hotel.rating} + {#if lodging.rating} (hotel.rating = NaN)} + on:click={() => (lodging.rating = NaN)} > {$t('adventures.remove')} @@ -232,7 +244,7 @@ type="url" id="link" name="link" - bind:value={hotel.link} + bind:value={lodging.link} class="input input-bordered w-full" /> @@ -247,7 +259,7 @@ - {$t('adventures.start_date')} + {$t('lodging.check_in')} {#if collection && collection.start_date && collection.end_date} - {#if hotel.check_in} + {#if lodging.check_out} - {$t('adventures.end_date')} + {$t('lodging.check_out')} @@ -298,7 +310,7 @@ - + diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index 30f5b27..b86b670 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -113,7 +113,7 @@ export type Collection = { end_date: string | null; transportations?: Transportation[]; notes?: Note[]; - hotels?: Hotel[]; + lodging?: Lodging[]; checklists?: Checklist[]; is_archived?: boolean; shared_with: string[] | undefined; @@ -264,10 +264,11 @@ export type Attachment = { name: string; }; -export type Hotel = { +export type Lodging = { id: string; user_id: string; name: string; + type: string; description: string | null; rating: number | null; link: string | null; diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index c01123e..85b7d08 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -235,7 +235,12 @@ "primary": "Primär", "upload": "Hochladen", "view_attachment": "Anhang anzeigen", - "of": "von" + "of": "von", + "city": "Stadt", + "display_name": "Anzeigename", + "location_details": "Standortdetails", + "lodging": "Unterkunft", + "region": "Region" }, "home": { "desc_1": "Entdecken, planen und erkunden Sie mit Leichtigkeit", diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 011dfbf..df04d4c 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -191,7 +191,7 @@ "no_description_found": "No description found", "adventure_created": "Adventure created", "adventure_create_error": "Failed to create adventure", - "hotel": "Hotel", + "lodging": "Lodging", "create_adventure": "Create Adventure", "adventure_updated": "Adventure updated", "adventure_update_error": "Failed to update adventure", @@ -200,6 +200,7 @@ "new_adventure": "New Adventure", "basic_information": "Basic Information", "no_adventures_to_recommendations": "No adventures found. Add at leat one adventure to get recommendations.", + "display_name": "Display Name", "adventure_not_found": "There are no adventures to display. Add some using the plus button at the bottom right or try changing filters!", "no_adventures_found": "No adventures found", "mark_region_as_visited": "Mark region {region}, {country} as visited?", @@ -250,6 +251,9 @@ "out_of_range": "Not in itinerary date range", "preview": "Preview", "finding_recommendations": "Discovering hidden gems for your next adventure", + "location_details": "Location Details", + "city": "City", + "region": "Region", "md_instructions": "Write your markdown here...", "days": "days", "attachment_upload_success": "Attachment uploaded successfully!", @@ -489,6 +493,30 @@ "start": "Start", "date_and_time": "Date & Time" }, + "lodging": { + "lodging_deleted": "Lodging deleted successfully!", + "lodging_delete_error": "Error deleting lodging", + "provide_start_date": "Please provide a start date", + "lodging_type": "Lodging Type", + "type": "Type", + "lodging_added": "Lodging added successfully!", + "error_editing_lodging": "Error editing lodging", + "new_lodging": "New Lodging", + "check_in": "Check In", + "check_out": "Check Out", + "edit": "Edit", + "modes": { + "hotel": "Hotel", + "hostel": "Hostel", + "airbnb": "Airbnb", + "camping": "Camping", + "other": "Other" + }, + "lodging_edit_success": "Lodging edited successfully!", + "edit_lodging": "Edit Lodging", + "start": "Start", + "date_and_time": "Date & Time" + }, "search": { "adventurelog_results": "AdventureLog Results", "public_adventures": "Public Adventures", diff --git a/frontend/src/routes/collections/[id]/+page.svelte b/frontend/src/routes/collections/[id]/+page.svelte index f19f7c7..6cbbcd1 100644 --- a/frontend/src/routes/collections/[id]/+page.svelte +++ b/frontend/src/routes/collections/[id]/+page.svelte @@ -1,5 +1,5 @@ @@ -404,11 +418,11 @@ /> {/if} -{#if isShowingHotelModal} - (isShowingHotelModal = false)} - on:save={saveOrCreateHotel} +{#if isShowingLodgingModal} + (isShowingLodgingModal = false)} + on:save={saveOrCreateLodging} {collection} /> {/if} @@ -541,12 +555,12 @@ { - isShowingHotelModal = true; + isShowingLodgingModal = true; newType = ''; - hotelToEdit = null; + lodgingToEdit = null; }} > - {$t('adventures.hotel')} - {#if adventures.length == 0 && transportations.length == 0 && notes.length == 0 && checklists.length == 0} + {#if adventures.length == 0 && transportations.length == 0 && notes.length == 0 && checklists.length == 0 && lodging.length == 0} {/if} {/if}
{lodging.location}
{new Date(lodging.check_in).toLocaleDateString(undefined, { timeZone: 'UTC' })}
{new Date(lodging.check_out).toLocaleDateString(undefined, { timeZone: 'UTC' })}