1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-08-05 05:05:17 +02:00

Refactor adventure references to locations across the backend and frontend

- Updated CategoryViewSet to reflect location context instead of adventures.
- Modified ChecklistViewSet to include locations in retrieval logic.
- Changed GlobalSearchView to search for locations instead of adventures.
- Adjusted IcsCalendarGeneratorViewSet to handle locations instead of adventures.
- Refactored LocationImageViewSet to remove unused import.
- Updated LocationViewSet to clarify public access for locations.
- Changed LodgingViewSet to reference locations instead of adventures.
- Modified NoteViewSet to prevent listing all locations.
- Updated RecommendationsViewSet to handle locations in parsing and response.
- Adjusted ReverseGeocodeViewSet to search through user locations.
- Updated StatsViewSet to count locations instead of adventures.
- Changed TagsView to reflect activity types for locations.
- Updated TransportationViewSet to reference locations instead of adventures.
- Added new translations for search results related to locations in multiple languages.
- Updated dashboard and profile pages to reflect location counts instead of adventure counts.
- Adjusted search routes to handle locations instead of adventures.
This commit is contained in:
Sean Morley 2025-06-24 17:23:02 -04:00
parent 5c74203103
commit fae928252a
29 changed files with 173 additions and 109 deletions

View file

@ -14,7 +14,7 @@ class CategoryViewSet(viewsets.ModelViewSet):
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
""" """
Retrieve a list of distinct categories for adventures associated with the current user. Retrieve a list of distinct categories for locations associated with the current user.
""" """
categories = self.get_queryset().distinct() categories = self.get_queryset().distinct()
serializer = self.get_serializer(categories, many=True) serializer = self.get_serializer(categories, many=True)
@ -29,7 +29,7 @@ class CategoryViewSet(viewsets.ModelViewSet):
if instance.name == 'general': if instance.name == 'general':
return Response({"error": "Cannot delete the general category"}, status=400) return Response({"error": "Cannot delete the general category"}, status=400)
# set any adventures with this category to a default category called general before deleting the category, if general does not exist create it for the user # set any locations with this category to a default category called general before deleting the category, if general does not exist create it for the user
general_category = Category.objects.filter(user=request.user, name='general').first() general_category = Category.objects.filter(user=request.user, name='general').first()
if not general_category: if not general_category:

View file

@ -29,12 +29,12 @@ class ChecklistViewSet(viewsets.ModelViewSet):
if self.action == 'retrieve': if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures # For individual adventure retrieval, include public locations
return Checklist.objects.filter( return Checklist.objects.filter(
Q(is_public=True) | Q(user=self.request.user) | Q(collection__shared_with=self.request.user) Q(is_public=True) | Q(user=self.request.user) | Q(collection__shared_with=self.request.user)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')
else: else:
# For other actions, include user's own adventures and shared adventures # For other actions, include user's own locations and shared locations
return Checklist.objects.filter( return Checklist.objects.filter(
Q(user=self.request.user) | Q(collection__shared_with=self.request.user) Q(user=self.request.user) | Q(collection__shared_with=self.request.user)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')

View file

@ -20,7 +20,7 @@ class GlobalSearchView(viewsets.ViewSet):
# Initialize empty results # Initialize empty results
results = { results = {
"adventures": [], "locations": [],
"collections": [], "collections": [],
"users": [], "users": [],
"countries": [], "countries": [],
@ -30,11 +30,11 @@ class GlobalSearchView(viewsets.ViewSet):
"visited_cities": [] "visited_cities": []
} }
# Adventures: Full-Text Search # Locations: Full-Text Search
adventures = Location.objects.annotate( locations = Location.objects.annotate(
search=SearchVector('name', 'description', 'location') search=SearchVector('name', 'description', 'location')
).filter(search=SearchQuery(search_term), user=request.user) ).filter(search=SearchQuery(search_term), user=request.user)
results["adventures"] = LocationSerializer(adventures, many=True).data results["locations"] = LocationSerializer(locations, many=True).data
# Collections: Partial Match Search # Collections: Partial Match Search
collections = Collection.objects.filter( collections = Collection.objects.filter(

View file

@ -12,8 +12,8 @@ class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
@action(detail=False, methods=['get']) @action(detail=False, methods=['get'])
def generate(self, request): def generate(self, request):
adventures = Location.objects.filter(user=request.user) locations = Location.objects.filter(user=request.user)
serializer = LocationSerializer(adventures, many=True) serializer = LocationSerializer(locations, many=True)
user = request.user user = request.user
name = f"{user.first_name} {user.last_name}" name = f"{user.first_name} {user.last_name}"
@ -21,9 +21,9 @@ class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
cal.add('prodid', '-//My Adventure Calendar//example.com//') cal.add('prodid', '-//My Adventure Calendar//example.com//')
cal.add('version', '2.0') cal.add('version', '2.0')
for adventure in serializer.data: for location in serializer.data:
if adventure['visits']: if location['visits']:
for visit in adventure['visits']: for visit in location['visits']:
# Skip if start_date is missing # Skip if start_date is missing
if not visit.get('start_date'): if not visit.get('start_date'):
continue continue
@ -41,7 +41,7 @@ class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
# Create event # Create event
event = Event() event = Event()
event.add('summary', adventure['name']) event.add('summary', location['name'])
event.add('dtstart', start_date) event.add('dtstart', start_date)
event.add('dtend', end_date) event.add('dtend', end_date)
event.add('dtstamp', datetime.now()) event.add('dtstamp', datetime.now())
@ -49,11 +49,11 @@ class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
event.add('class', 'PUBLIC') event.add('class', 'PUBLIC')
event.add('created', datetime.now()) event.add('created', datetime.now())
event.add('last-modified', datetime.now()) event.add('last-modified', datetime.now())
event.add('description', adventure['description']) event.add('description', location['description'])
if adventure.get('location'): if location.get('location'):
event.add('location', adventure['location']) event.add('location', location['location'])
if adventure.get('link'): if location.get('link'):
event.add('url', adventure['link']) event.add('url', location['link'])
organizer = vCalAddress(f'MAILTO:{user.email}') organizer = vCalAddress(f'MAILTO:{user.email}')
organizer.params['cn'] = vText(name) organizer.params['cn'] = vText(name)

View file

@ -9,7 +9,6 @@ from adventures.serializers import LocationImageSerializer
from integrations.models import ImmichIntegration from integrations.models import ImmichIntegration
import uuid import uuid
import requests import requests
import os
class AdventureImageViewSet(viewsets.ModelViewSet): class AdventureImageViewSet(viewsets.ModelViewSet):
serializer_class = LocationImageSerializer serializer_class = LocationImageSerializer

View file

@ -7,10 +7,9 @@ from rest_framework import viewsets, status
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
import requests import requests
from adventures.models import Location, Category
from adventures.models import Location, Category, Transportation, Lodging
from adventures.permissions import IsOwnerOrSharedWithFullAccess from adventures.permissions import IsOwnerOrSharedWithFullAccess
from adventures.serializers import LocationSerializer, TransportationSerializer, LodgingSerializer from adventures.serializers import LocationSerializer
from adventures.utils import pagination from adventures.utils import pagination
@ -28,7 +27,7 @@ class LocationViewSet(viewsets.ModelViewSet):
def get_queryset(self): def get_queryset(self):
""" """
Returns queryset based on user authentication and action type. Returns queryset based on user authentication and action type.
Public actions allow unauthenticated access to public adventures. Public actions allow unauthenticated access to public locations.
""" """
user = self.request.user user = self.request.user
public_allowed_actions = {'retrieve', 'additional_info'} public_allowed_actions = {'retrieve', 'additional_info'}
@ -67,7 +66,7 @@ class LocationViewSet(viewsets.ModelViewSet):
# Apply sorting logic # Apply sorting logic
queryset = self._apply_ordering(queryset, order_by, order_direction) queryset = self._apply_ordering(queryset, order_by, order_direction)
# Filter adventures without collections if requested # Filter locations without collections if requested
if include_collections == 'false': if include_collections == 'false':
queryset = queryset.filter(collections__isnull=True) queryset = queryset.filter(collections__isnull=True)
@ -147,7 +146,7 @@ class LocationViewSet(viewsets.ModelViewSet):
@action(detail=False, methods=['get']) @action(detail=False, methods=['get'])
def filtered(self, request): def filtered(self, request):
"""Filter adventures by category types and visit status.""" """Filter locations by category types and visit status."""
types = request.query_params.get('types', '').split(',') types = request.query_params.get('types', '').split(',')
# Handle 'all' types # Handle 'all' types
@ -180,7 +179,7 @@ class LocationViewSet(viewsets.ModelViewSet):
@action(detail=False, methods=['get']) @action(detail=False, methods=['get'])
def all(self, request): def all(self, request):
"""Get all adventures (public and owned) with optional collection filtering.""" """Get all locations (public and owned) with optional collection filtering."""
if not request.user.is_authenticated: if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400) return Response({"error": "User is not authenticated"}, status=400)

View file

@ -25,11 +25,11 @@ class LodgingViewSet(viewsets.ModelViewSet):
def get_queryset(self): def get_queryset(self):
user = self.request.user user = self.request.user
if self.action == 'retrieve': if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures, user's own adventures and shared adventures # For individual adventure retrieval, include public locations, user's own locations and shared locations
return Lodging.objects.filter( return Lodging.objects.filter(
Q(is_public=True) | Q(user=user.id) | Q(collection__shared_with=user.id) Q(is_public=True) | Q(user=user.id) | Q(collection__shared_with=user.id)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')
# For other actions, include user's own adventures and shared adventures # For other actions, include user's own locations and shared locations
return Lodging.objects.filter( return Lodging.objects.filter(
Q(user=user.id) | Q(collection__shared_with=user.id) Q(user=user.id) | Q(collection__shared_with=user.id)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')

View file

@ -15,7 +15,7 @@ class NoteViewSet(viewsets.ModelViewSet):
# return error message if user is not authenticated on the root endpoint # return error message if user is not authenticated on the root endpoint
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
# Prevent listing all adventures # Prevent listing all locations
return Response({"detail": "Listing all notes is not allowed."}, return Response({"detail": "Listing all notes is not allowed."},
status=status.HTTP_403_FORBIDDEN) status=status.HTTP_403_FORBIDDEN)
@ -39,12 +39,12 @@ class NoteViewSet(viewsets.ModelViewSet):
if self.action == 'retrieve': if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures # For individual adventure retrieval, include public locations
return Note.objects.filter( return Note.objects.filter(
Q(is_public=True) | Q(user=self.request.user.id) | Q(collection__shared_with=self.request.user) Q(is_public=True) | Q(user=self.request.user.id) | Q(collection__shared_with=self.request.user)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')
else: else:
# For other actions, include user's own adventures and shared adventures # For other actions, include user's own locations and shared locations
return Note.objects.filter( return Note.objects.filter(
Q(user=self.request.user.id) | Q(collection__shared_with=self.request.user) Q(user=self.request.user.id) | Q(collection__shared_with=self.request.user)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')

View file

@ -5,8 +5,6 @@ from rest_framework.response import Response
from django.conf import settings from django.conf import settings
import requests import requests
from geopy.distance import geodesic from geopy.distance import geodesic
import time
class RecommendationsViewSet(viewsets.ViewSet): class RecommendationsViewSet(viewsets.ViewSet):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
@ -14,7 +12,7 @@ class RecommendationsViewSet(viewsets.ViewSet):
HEADERS = {'User-Agent': 'AdventureLog Server'} HEADERS = {'User-Agent': 'AdventureLog Server'}
def parse_google_places(self, places, origin): def parse_google_places(self, places, origin):
adventures = [] locations = []
for place in places: for place in places:
location = place.get('location', {}) location = place.get('location', {})
@ -45,16 +43,16 @@ class RecommendationsViewSet(viewsets.ViewSet):
"distance_km": round(distance_km, 2), "distance_km": round(distance_km, 2),
} }
adventures.append(adventure) locations.append(adventure)
# Sort by distance ascending # Sort by distance ascending
adventures.sort(key=lambda x: x["distance_km"]) locations.sort(key=lambda x: x["distance_km"])
return adventures return locations
def parse_overpass_response(self, data, request): def parse_overpass_response(self, data, request):
nodes = data.get('elements', []) nodes = data.get('elements', [])
adventures = [] locations = []
all = request.query_params.get('all', False) all = request.query_params.get('all', False)
origin = None origin = None
@ -102,13 +100,13 @@ class RecommendationsViewSet(viewsets.ViewSet):
"powered_by": "osm" "powered_by": "osm"
} }
adventures.append(adventure) locations.append(adventure)
# Sort by distance if available # Sort by distance if available
if origin: if origin:
adventures.sort(key=lambda x: x.get("distance_km") or float("inf")) locations.sort(key=lambda x: x.get("distance_km") or float("inf"))
return adventures return locations
def query_overpass(self, lat, lon, radius, category, request): def query_overpass(self, lat, lon, radius, category, request):
@ -172,8 +170,8 @@ class RecommendationsViewSet(viewsets.ViewSet):
print("Overpass API error:", e) print("Overpass API error:", e)
return Response({"error": "Failed to retrieve data from Overpass API."}, status=500) return Response({"error": "Failed to retrieve data from Overpass API."}, status=500)
adventures = self.parse_overpass_response(data, request) locations = self.parse_overpass_response(data, request)
return Response(adventures) return Response(locations)
def query_google_nearby(self, lat, lon, radius, category, request): def query_google_nearby(self, lat, lon, radius, category, request):
"""Query Google Places API (New) for nearby places""" """Query Google Places API (New) for nearby places"""
@ -216,9 +214,9 @@ class RecommendationsViewSet(viewsets.ViewSet):
places = data.get('places', []) places = data.get('places', [])
origin = (float(lat), float(lon)) origin = (float(lat), float(lon))
adventures = self.parse_google_places(places, origin) locations = self.parse_google_places(places, origin)
return Response(adventures) return Response(locations)
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
print(f"Google Places API error: {e}") print(f"Google Places API error: {e}")

View file

@ -5,9 +5,7 @@ from rest_framework.response import Response
from worldtravel.models import Region, City, VisitedRegion, VisitedCity from worldtravel.models import Region, City, VisitedRegion, VisitedCity
from adventures.models import Location from adventures.models import Location
from adventures.serializers import LocationSerializer from adventures.serializers import LocationSerializer
import requests
from adventures.geocoding import reverse_geocode from adventures.geocoding import reverse_geocode
from adventures.geocoding import extractIsoCode
from django.conf import settings from django.conf import settings
from adventures.geocoding import search_google, search_osm from adventures.geocoding import search_google, search_osm
@ -47,14 +45,14 @@ class ReverseGeocodeViewSet(viewsets.ViewSet):
@action(detail=False, methods=['post']) @action(detail=False, methods=['post'])
def mark_visited_region(self, request): def mark_visited_region(self, request):
# searches through all of the users adventures, if the serialized data is_visited, is true, runs reverse geocode on the adventures and if a region is found, marks it as visited. Use the extractIsoCode function to get the region # searches through all of the users locations, if the serialized data is_visited, is true, runs reverse geocode on the locations and if a region is found, marks it as visited. Use the extractIsoCode function to get the region
new_region_count = 0 new_region_count = 0
new_regions = {} new_regions = {}
new_city_count = 0 new_city_count = 0
new_cities = {} new_cities = {}
adventures = Location.objects.filter(user=self.request.user) locations = Location.objects.filter(user=self.request.user)
serializer = LocationSerializer(adventures, many=True) serializer = LocationSerializer(locations, many=True)
for adventure, serialized_adventure in zip(adventures, serializer.data): for adventure, serialized_adventure in zip(locations, serializer.data):
if serialized_adventure['is_visited'] == True: if serialized_adventure['is_visited'] == True:
lat = adventure.latitude lat = adventure.latitude
lon = adventure.longitude lon = adventure.longitude

View file

@ -1,11 +1,9 @@
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.decorators import action from rest_framework.decorators import action
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from worldtravel.models import City, Region, Country, VisitedCity, VisitedRegion from worldtravel.models import City, Region, Country, VisitedCity, VisitedRegion
from adventures.models import Location, Collection from adventures.models import Location, Collection
from users.serializers import CustomUserDetailsSerializer as PublicUserSerializer
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
User = get_user_model() User = get_user_model()
@ -26,7 +24,7 @@ class StatsViewSet(viewsets.ViewSet):
user.email = None user.email = None
# get the counts for the user # get the counts for the user
adventure_count = Location.objects.filter( location_count = Location.objects.filter(
user=user.id).count() user=user.id).count()
trips_count = Collection.objects.filter( trips_count = Collection.objects.filter(
user=user.id).count() user=user.id).count()
@ -40,7 +38,7 @@ class StatsViewSet(viewsets.ViewSet):
user=user.id).values('region__country').distinct().count() user=user.id).values('region__country').distinct().count()
total_countries = Country.objects.count() total_countries = Country.objects.count()
return Response({ return Response({
'adventure_count': adventure_count, 'location_count': location_count,
'trips_count': trips_count, 'trips_count': trips_count,
'visited_city_count': visited_city_count, 'visited_city_count': visited_city_count,
'total_cities': total_cities, 'total_cities': total_cities,

View file

@ -10,7 +10,7 @@ class ActivityTypesView(viewsets.ViewSet):
@action(detail=False, methods=['get']) @action(detail=False, methods=['get'])
def types(self, request): def types(self, request):
""" """
Retrieve a list of distinct activity types for adventures associated with the current user. Retrieve a list of distinct activity types for locations associated with the current user.
Args: Args:
request (HttpRequest): The HTTP request object. request (HttpRequest): The HTTP request object.

View file

@ -1,12 +1,10 @@
from rest_framework import viewsets, status from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from django.db.models import Q from django.db.models import Q
from adventures.models import Transportation from adventures.models import Transportation
from adventures.serializers import TransportationSerializer from adventures.serializers import TransportationSerializer
from rest_framework.exceptions import PermissionDenied from rest_framework.exceptions import PermissionDenied
from adventures.permissions import IsOwnerOrSharedWithFullAccess from adventures.permissions import IsOwnerOrSharedWithFullAccess
from rest_framework.permissions import IsAuthenticated
class TransportationViewSet(viewsets.ModelViewSet): class TransportationViewSet(viewsets.ModelViewSet):
queryset = Transportation.objects.all() queryset = Transportation.objects.all()
@ -25,11 +23,11 @@ class TransportationViewSet(viewsets.ModelViewSet):
def get_queryset(self): def get_queryset(self):
user = self.request.user user = self.request.user
if self.action == 'retrieve': if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures, user's own adventures and shared adventures # For individual adventure retrieval, include public locations, user's own locations and shared locations
return Transportation.objects.filter( return Transportation.objects.filter(
Q(is_public=True) | Q(user=user.id) | Q(collection__shared_with=user.id) Q(is_public=True) | Q(user=user.id) | Q(collection__shared_with=user.id)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')
# For other actions, include user's own adventures and shared adventures # For other actions, include user's own locations and shared locations
return Transportation.objects.filter( return Transportation.objects.filter(
Q(user=user.id) | Q(collection__shared_with=user.id) Q(user=user.id) | Q(collection__shared_with=user.id)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')

View file

@ -572,7 +572,13 @@
"search": { "search": {
"adventurelog_results": "AdventureLog-Ergebnisse", "adventurelog_results": "AdventureLog-Ergebnisse",
"online_results": "Online-Ergebnisse", "online_results": "Online-Ergebnisse",
"public_adventures": "Öffentliche Abenteuer" "public_adventures": "Öffentliche Abenteuer",
"cities": "Städte",
"countries": "Länder",
"found": "gefunden",
"result": "Ergebnis",
"results": "Ergebnisse",
"try_searching_desc": "Versuchen Sie, nach Abenteuern, Sammlungen, Ländern, Regionen, Städten oder Nutzern zu suchen."
}, },
"map": { "map": {
"add_adventure": "Neues Abenteuer hinzufügen", "add_adventure": "Neues Abenteuer hinzufügen",

View file

@ -589,7 +589,13 @@
"search": { "search": {
"adventurelog_results": "AdventureLog Results", "adventurelog_results": "AdventureLog Results",
"public_adventures": "Public Adventures", "public_adventures": "Public Adventures",
"online_results": "Online Results" "online_results": "Online Results",
"result": "Result",
"results": "Results",
"found": "found",
"try_searching_desc": "Try searching for adventures, collections, countries, regions, cities, or users.",
"countries": "Countries",
"cities": "Cities"
}, },
"map": { "map": {
"view_details": "View Details", "view_details": "View Details",

View file

@ -572,7 +572,13 @@
"search": { "search": {
"adventurelog_results": "Resultados del registro de aventuras", "adventurelog_results": "Resultados del registro de aventuras",
"online_results": "Resultados en línea", "online_results": "Resultados en línea",
"public_adventures": "Aventuras públicas" "public_adventures": "Aventuras públicas",
"cities": "Ciudades",
"countries": "Países",
"found": "encontró",
"result": "Resultado",
"results": "Resultados",
"try_searching_desc": "Intente buscar aventuras, colecciones, países, regiones, ciudades o usuarios."
}, },
"map": { "map": {
"add_adventure": "Agregar nueva aventura", "add_adventure": "Agregar nueva aventura",

View file

@ -572,7 +572,13 @@
"search": { "search": {
"adventurelog_results": "Résultats dans AdventureLog", "adventurelog_results": "Résultats dans AdventureLog",
"online_results": "Résultats en ligne", "online_results": "Résultats en ligne",
"public_adventures": "Aventures publiques" "public_adventures": "Aventures publiques",
"cities": "Villes",
"countries": "Pays",
"found": "trouvé",
"result": "Résultat",
"results": "Résultats",
"try_searching_desc": "Essayez de rechercher des aventures, des collections, des pays, des régions, des villes ou des utilisateurs."
}, },
"map": { "map": {
"add_adventure": "Ajouter une nouvelle aventure", "add_adventure": "Ajouter une nouvelle aventure",

View file

@ -572,7 +572,13 @@
"search": { "search": {
"adventurelog_results": "Risultati di AdventureLog", "adventurelog_results": "Risultati di AdventureLog",
"online_results": "Risultati in linea", "online_results": "Risultati in linea",
"public_adventures": "Avventure pubbliche" "public_adventures": "Avventure pubbliche",
"cities": "Città",
"countries": "Paesi",
"found": "trovato",
"result": "Risultato",
"results": "Risultati",
"try_searching_desc": "Prova a cercare avventure, collezioni, paesi, regioni, città o utenti."
}, },
"map": { "map": {
"add_adventure": "Aggiungi nuova avventura", "add_adventure": "Aggiungi nuova avventura",

View file

@ -469,7 +469,13 @@
"search": { "search": {
"adventurelog_results": "Adventurelog 결과", "adventurelog_results": "Adventurelog 결과",
"online_results": "온라인 결과", "online_results": "온라인 결과",
"public_adventures": "공개 모험" "public_adventures": "공개 모험",
"cities": "도시",
"countries": "국가",
"found": "설립하다",
"result": "결과",
"results": "결과",
"try_searching_desc": "모험, 컬렉션, 국가, 지역, 도시 또는 사용자를 검색하십시오."
}, },
"settings": { "settings": {
"about_this_background": "이 배경에 대해", "about_this_background": "이 배경에 대해",

View file

@ -572,7 +572,13 @@
"search": { "search": {
"adventurelog_results": "AdventureLog resultaten", "adventurelog_results": "AdventureLog resultaten",
"online_results": "Online resultaten", "online_results": "Online resultaten",
"public_adventures": "Openbare avonturen" "public_adventures": "Openbare avonturen",
"cities": "Steden",
"countries": "Landen",
"found": "gevonden",
"result": "Resultaat",
"results": "Resultaat",
"try_searching_desc": "Probeer op zoek naar avonturen, collecties, landen, regio's, steden of gebruikers."
}, },
"map": { "map": {
"add_adventure": "Voeg nieuw avontuur toe", "add_adventure": "Voeg nieuw avontuur toe",

View file

@ -589,7 +589,13 @@
"search": { "search": {
"adventurelog_results": "AdventureLog-resultater", "adventurelog_results": "AdventureLog-resultater",
"public_adventures": "Offentlige eventyr", "public_adventures": "Offentlige eventyr",
"online_results": "Nettresultater" "online_results": "Nettresultater",
"cities": "Byer",
"countries": "Land",
"found": "funnet",
"result": "Resultat",
"results": "Resultater",
"try_searching_desc": "Prøv å søke etter eventyr, samlinger, land, regioner, byer eller brukere."
}, },
"map": { "map": {
"view_details": "Vis detaljer", "view_details": "Vis detaljer",

View file

@ -572,7 +572,13 @@
"search": { "search": {
"adventurelog_results": "Wyniki AdventureLog", "adventurelog_results": "Wyniki AdventureLog",
"public_adventures": "Publiczne podróże", "public_adventures": "Publiczne podróże",
"online_results": "Wyniki online" "online_results": "Wyniki online",
"cities": "Miasta",
"countries": "Kraje",
"found": "znaleziony",
"result": "Wynik",
"results": "Wyniki",
"try_searching_desc": "Spróbuj szukać przygód, kolekcji, krajów, regionów, miast lub użytkowników."
}, },
"map": { "map": {
"view_details": "Zobacz szczegóły", "view_details": "Zobacz szczegóły",

View file

@ -589,7 +589,13 @@
"search": { "search": {
"adventurelog_results": "Результаты AdventureLog", "adventurelog_results": "Результаты AdventureLog",
"public_adventures": "Публичные приключения", "public_adventures": "Публичные приключения",
"online_results": "Онлайн результаты" "online_results": "Онлайн результаты",
"cities": "Города",
"countries": "Страны",
"found": "найденный",
"result": "Результат",
"results": "Результаты",
"try_searching_desc": "Попробуйте искать приключения, коллекции, страны, регионы, города или пользователей."
}, },
"map": { "map": {
"view_details": "Подробности", "view_details": "Подробности",

View file

@ -572,7 +572,13 @@
"search": { "search": {
"adventurelog_results": "AdventureLog-resultat", "adventurelog_results": "AdventureLog-resultat",
"online_results": "Online resultat", "online_results": "Online resultat",
"public_adventures": "Offentliga äventyr" "public_adventures": "Offentliga äventyr",
"cities": "Städer",
"countries": "Länder",
"found": "funnna",
"result": "Resultat",
"results": "Resultat",
"try_searching_desc": "Försök att söka efter äventyr, samlingar, länder, regioner, städer eller användare."
}, },
"map": { "map": {
"add_adventure": "Lägg till nytt äventyr", "add_adventure": "Lägg till nytt äventyr",

View file

@ -572,7 +572,13 @@
"search": { "search": {
"adventurelog_results": "AdventureLog 结果", "adventurelog_results": "AdventureLog 结果",
"online_results": "在线结果", "online_results": "在线结果",
"public_adventures": "已公开的冒险" "public_adventures": "已公开的冒险",
"cities": "城市",
"countries": "国家",
"found": "成立",
"result": "结果",
"results": "结果",
"try_searching_desc": "尝试搜索冒险,收藏,国家,地区,城市或用户。"
}, },
"map": { "map": {
"add_adventure": "添加新冒险", "add_adventure": "添加新冒险",

View file

@ -51,9 +51,9 @@
: user?.username}! : user?.username}!
</h1> </h1>
<p class="text-lg text-base-content/60 mt-2"> <p class="text-lg text-base-content/60 mt-2">
{#if stats.adventure_count > 0} {#if stats.location_count > 0}
{$t('dashboard.welcome_text_1')} {$t('dashboard.welcome_text_1')}
<span class="font-semibold text-primary">{stats.adventure_count}</span> <span class="font-semibold text-primary">{stats.location_count}</span>
{$t('dashboard.welcome_text_2')} {$t('dashboard.welcome_text_2')}
{:else} {:else}
{$t('dashboard.welcome_text_3')} {$t('dashboard.welcome_text_3')}
@ -171,7 +171,7 @@
</div> </div>
<a href="/locations" class="btn btn-ghost gap-2"> <a href="/locations" class="btn btn-ghost gap-2">
{$t('dashboard.view_all')} {$t('dashboard.view_all')}
<span class="badge badge-primary">{stats.adventure_count}</span> <span class="badge badge-primary">{stats.location_count}</span>
</a> </a>
</div> </div>

View file

@ -23,7 +23,7 @@
visited_country_count: number; visited_country_count: number;
total_regions: number; total_regions: number;
trips_count: number; trips_count: number;
adventure_count: number; location_count: number;
visited_region_count: number; visited_region_count: number;
total_countries: number; total_countries: number;
visited_city_count: number; visited_city_count: number;
@ -48,34 +48,34 @@
// Achievement levels // Achievement levels
$: achievementLevel = $: achievementLevel =
(stats?.adventure_count ?? 0) >= 100 (stats?.location_count ?? 0) >= 100
? 'Legendary Explorer' ? 'Legendary Explorer'
: (stats?.adventure_count ?? 0) >= 75 : (stats?.location_count ?? 0) >= 75
? 'World Wanderer' ? 'World Wanderer'
: (stats?.adventure_count ?? 0) >= 50 : (stats?.location_count ?? 0) >= 50
? 'Explorer Master' ? 'Explorer Master'
: (stats?.adventure_count ?? 0) >= 35 : (stats?.location_count ?? 0) >= 35
? 'Globetrotter' ? 'Globetrotter'
: (stats?.adventure_count ?? 0) >= 25 : (stats?.location_count ?? 0) >= 25
? 'Seasoned Traveler' ? 'Seasoned Traveler'
: (stats?.adventure_count ?? 0) >= 15 : (stats?.location_count ?? 0) >= 15
? 'Adventure Seeker' ? 'Adventure Seeker'
: (stats?.adventure_count ?? 0) >= 10 : (stats?.location_count ?? 0) >= 10
? 'Trailblazer' ? 'Trailblazer'
: (stats?.adventure_count ?? 0) >= 5 : (stats?.location_count ?? 0) >= 5
? 'Journey Starter' ? 'Journey Starter'
: (stats?.adventure_count ?? 0) >= 1 : (stats?.location_count ?? 0) >= 1
? 'Travel Enthusiast' ? 'Travel Enthusiast'
: 'New Explorer'; : 'New Explorer';
$: achievementColor = $: achievementColor =
(stats?.adventure_count ?? 0) >= 50 (stats?.location_count ?? 0) >= 50
? 'text-warning' ? 'text-warning'
: (stats?.adventure_count ?? 0) >= 25 : (stats?.location_count ?? 0) >= 25
? 'text-success' ? 'text-success'
: (stats?.adventure_count ?? 0) >= 10 : (stats?.location_count ?? 0) >= 10
? 'text-info' ? 'text-info'
: (stats?.adventure_count ?? 0) >= 5 : (stats?.location_count ?? 0) >= 5
? 'text-secondary' ? 'text-secondary'
: 'text-primary'; : 'text-primary';
</script> </script>
@ -159,7 +159,7 @@
{/if} {/if}
<!-- User rank achievement --> <!-- User rank achievement -->
{#if stats && stats.adventure_count > 0} {#if stats && stats.location_count > 0}
<div class="flex items-center justify-center gap-2 text-base-content/70"> <div class="flex items-center justify-center gap-2 text-base-content/70">
<Award class="w-5 h-5" /> <Award class="w-5 h-5" />
<span class={`text-lg ${achievementColor}`}>{achievementLevel}</span> <span class={`text-lg ${achievementColor}`}>{achievementLevel}</span>
@ -191,7 +191,7 @@
<div class="text-primary/70 font-medium text-sm uppercase tracking-wide"> <div class="text-primary/70 font-medium text-sm uppercase tracking-wide">
{$t('locations.locations')} {$t('locations.locations')}
</div> </div>
<div class="text-4xl font-bold text-primary">{stats.adventure_count}</div> <div class="text-4xl font-bold text-primary">{stats.location_count}</div>
<div class="text-primary/60 mt-2 flex items-center gap-1"> <div class="text-primary/60 mt-2 flex items-center gap-1">
<TrendingUp class="w-4 h-4" /> <TrendingUp class="w-4 h-4" />
{achievementLevel} {achievementLevel}

View file

@ -33,7 +33,7 @@ export const load = (async (event) => {
let data = await res.json(); let data = await res.json();
return { return {
adventures: data.adventures, locations: data.locations,
collections: data.collections, collections: data.collections,
users: data.users, users: data.users,
countries: data.countries, countries: data.countries,

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import AdventureCard from '$lib/components/LocationCard.svelte'; import LocationCard from '$lib/components/LocationCard.svelte';
import RegionCard from '$lib/components/RegionCard.svelte'; import RegionCard from '$lib/components/RegionCard.svelte';
import CityCard from '$lib/components/CityCard.svelte'; import CityCard from '$lib/components/CityCard.svelte';
import CountryCard from '$lib/components/CountryCard.svelte'; import CountryCard from '$lib/components/CountryCard.svelte';
@ -27,7 +27,7 @@
$: query = $page.url.searchParams.get('query') ?? ''; $: query = $page.url.searchParams.get('query') ?? '';
// Assign updated results from data, so when data changes, the displayed items update: // Assign updated results from data, so when data changes, the displayed items update:
$: adventures = data.adventures as Location[]; $: locations = data.locations as Location[];
$: collections = data.collections as Collection[]; $: collections = data.collections as Collection[];
$: users = data.users as User[]; $: users = data.users as User[];
$: countries = data.countries as Country[]; $: countries = data.countries as Country[];
@ -38,7 +38,7 @@
// new stats // new stats
$: totalResults = $: totalResults =
adventures.length + locations.length +
collections.length + collections.length +
users.length + users.length +
countries.length + countries.length +
@ -64,11 +64,13 @@
<h1 <h1
class="text-3xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent" class="text-3xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent"
> >
Search{query ? `: ${query}` : ''} {$t('navbar.search')}{query ? `: ${query}` : ''}
</h1> </h1>
{#if hasResults} {#if hasResults}
<p class="text-sm text-base-content/60"> <p class="text-sm text-base-content/60">
{totalResults} result{totalResults !== 1 ? 's' : ''} found {totalResults}
{totalResults !== 1 ? $t('search.results') : $t('search.result')}
{$t('search.found')}
</p> </p>
{/if} {/if}
</div> </div>
@ -87,22 +89,22 @@
{$t('adventures.no_results')} {$t('adventures.no_results')}
</h3> </h3>
<p class="text-base-content/50 text-center max-w-md"> <p class="text-base-content/50 text-center max-w-md">
Try searching for adventures, collections, countries, regions, cities, or users. {$t('search.try_searching_desc')}
</p> </p>
</div> </div>
{:else} {:else}
{#if adventures.length > 0} {#if locations.length > 0}
<div class="mb-12"> <div class="mb-12">
<div class="flex items-center gap-3 mb-6"> <div class="flex items-center gap-3 mb-6">
<div class="p-2 bg-primary/10 rounded-lg"> <div class="p-2 bg-primary/10 rounded-lg">
<SearchIcon class="w-6 h-6 text-primary" /> <SearchIcon class="w-6 h-6 text-primary" />
</div> </div>
<h2 class="text-2xl font-bold">Adventures</h2> <h2 class="text-2xl font-bold">{$t('locations.locations')}</h2>
<div class="badge badge-primary">{adventures.length}</div> <div class="badge badge-primary">{locations.length}</div>
</div> </div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{#each adventures as adventure} {#each locations as adventure}
<AdventureCard {adventure} user={null} /> <LocationCard {adventure} user={null} />
{/each} {/each}
</div> </div>
</div> </div>
@ -115,7 +117,7 @@
<!-- you can replace with a CollectionIcon --> <!-- you can replace with a CollectionIcon -->
<SearchIcon class="w-6 h-6 text-secondary" /> <SearchIcon class="w-6 h-6 text-secondary" />
</div> </div>
<h2 class="text-2xl font-bold">Collections</h2> <h2 class="text-2xl font-bold">{$t('navbar.collections')}</h2>
<div class="badge badge-secondary">{collections.length}</div> <div class="badge badge-secondary">{collections.length}</div>
</div> </div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
@ -133,7 +135,7 @@
<!-- you can replace with a GlobeIcon --> <!-- you can replace with a GlobeIcon -->
<SearchIcon class="w-6 h-6 text-accent" /> <SearchIcon class="w-6 h-6 text-accent" />
</div> </div>
<h2 class="text-2xl font-bold">Countries</h2> <h2 class="text-2xl font-bold">{$t('search.countries')}</h2>
<div class="badge badge-accent">{countries.length}</div> <div class="badge badge-accent">{countries.length}</div>
</div> </div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
@ -151,7 +153,7 @@
<!-- MapIcon --> <!-- MapIcon -->
<SearchIcon class="w-6 h-6 text-info" /> <SearchIcon class="w-6 h-6 text-info" />
</div> </div>
<h2 class="text-2xl font-bold">Regions</h2> <h2 class="text-2xl font-bold">{$t('map.regions')}</h2>
<div class="badge badge-info">{regions.length}</div> <div class="badge badge-info">{regions.length}</div>
</div> </div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
@ -172,7 +174,7 @@
<!-- CityIcon --> <!-- CityIcon -->
<SearchIcon class="w-6 h-6 text-warning" /> <SearchIcon class="w-6 h-6 text-warning" />
</div> </div>
<h2 class="text-2xl font-bold">Cities</h2> <h2 class="text-2xl font-bold">{$t('search.cities')}</h2>
<div class="badge badge-warning">{cities.length}</div> <div class="badge badge-warning">{cities.length}</div>
</div> </div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
@ -190,7 +192,7 @@
<!-- UserIcon --> <!-- UserIcon -->
<SearchIcon class="w-6 h-6 text-success" /> <SearchIcon class="w-6 h-6 text-success" />
</div> </div>
<h2 class="text-2xl font-bold">Users</h2> <h2 class="text-2xl font-bold">{$t('navbar.users')}</h2>
<div class="badge badge-success">{users.length}</div> <div class="badge badge-success">{users.length}</div>
</div> </div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">