mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-07-23 06:49:37 +02:00
is visited
This commit is contained in:
parent
9d42dbac98
commit
727daf0cfd
10 changed files with 284 additions and 281 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
from django.utils import timezone
|
||||||
import os
|
import os
|
||||||
from .models import Adventure, AdventureImage, ChecklistItem, Collection, Note, Transportation, Checklist, Visit
|
from .models import Adventure, AdventureImage, ChecklistItem, Collection, Note, Transportation, Checklist, Visit
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
@ -19,6 +20,7 @@ class AdventureImageSerializer(serializers.ModelSerializer):
|
||||||
return representation
|
return representation
|
||||||
|
|
||||||
class VisitSerializer(serializers.ModelSerializer):
|
class VisitSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Visit
|
model = Visit
|
||||||
fields = ['id', 'start_date', 'end_date', 'notes']
|
fields = ['id', 'start_date', 'end_date', 'notes']
|
||||||
|
@ -27,10 +29,19 @@ class VisitSerializer(serializers.ModelSerializer):
|
||||||
class AdventureSerializer(serializers.ModelSerializer):
|
class AdventureSerializer(serializers.ModelSerializer):
|
||||||
images = AdventureImageSerializer(many=True, read_only=True)
|
images = AdventureImageSerializer(many=True, read_only=True)
|
||||||
visits = VisitSerializer(many=True, read_only=False)
|
visits = VisitSerializer(many=True, read_only=False)
|
||||||
|
is_visited = serializers.SerializerMethodField()
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Adventure
|
model = Adventure
|
||||||
fields = ['id', 'user_id', 'name', 'description', 'rating', 'activity_types', 'location', 'is_public', 'collection', 'created_at', 'updated_at', 'images', 'link', 'type', 'longitude', 'latitude', 'visits']
|
fields = ['id', 'user_id', 'name', 'description', 'rating', 'activity_types', 'location', 'is_public', 'collection', 'created_at', 'updated_at', 'images', 'link', 'type', 'longitude', 'latitude', 'visits', 'is_visited']
|
||||||
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id']
|
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id', 'is_visited']
|
||||||
|
|
||||||
|
def get_is_visited(self, obj):
|
||||||
|
current_date = timezone.now().date()
|
||||||
|
for visit in obj.visits.all():
|
||||||
|
if visit.start_date and visit.end_date and (visit.start_date <= current_date):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def to_representation(self, instance):
|
def to_representation(self, instance):
|
||||||
representation = super().to_representation(instance)
|
representation = super().to_representation(instance)
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
import CollectionLink from './CollectionLink.svelte';
|
import CollectionLink from './CollectionLink.svelte';
|
||||||
import DotsHorizontal from '~icons/mdi/dots-horizontal';
|
import DotsHorizontal from '~icons/mdi/dots-horizontal';
|
||||||
import DeleteWarning from './DeleteWarning.svelte';
|
import DeleteWarning from './DeleteWarning.svelte';
|
||||||
import { isAdventureVisited } from '$lib';
|
|
||||||
import CardCarousel from './CardCarousel.svelte';
|
import CardCarousel from './CardCarousel.svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
|
@ -132,7 +131,7 @@
|
||||||
<div>
|
<div>
|
||||||
<div class="badge badge-primary">{$t(`adventures.activities.${adventure.type}`)}</div>
|
<div class="badge badge-primary">{$t(`adventures.activities.${adventure.type}`)}</div>
|
||||||
<div class="badge badge-success">
|
<div class="badge badge-success">
|
||||||
{isAdventureVisited(adventure) ? $t('adventures.visited') : $t('adventures.planned')}
|
{adventure.is_visited ? $t('adventures.visited') : $t('adventures.planned')}
|
||||||
</div>
|
</div>
|
||||||
<div class="badge badge-secondary">
|
<div class="badge badge-secondary">
|
||||||
{adventure.is_public ? $t('adventures.public') : $t('adventures.private')}
|
{adventure.is_public ? $t('adventures.public') : $t('adventures.private')}
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
import ActivityComplete from './ActivityComplete.svelte';
|
import ActivityComplete from './ActivityComplete.svelte';
|
||||||
import { appVersion } from '$lib/config';
|
import { appVersion } from '$lib/config';
|
||||||
import { ADVENTURE_TYPES } from '$lib';
|
import { ADVENTURE_TYPES } from '$lib';
|
||||||
|
import RegionCard from './RegionCard.svelte';
|
||||||
|
|
||||||
let wikiError: string = '';
|
let wikiError: string = '';
|
||||||
|
|
||||||
|
@ -629,7 +630,11 @@ it would also work to just use on:click on the MapLibre component itself. -->
|
||||||
{#if reverseGeocodePlace}
|
{#if reverseGeocodePlace}
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<p>{reverseGeocodePlace.region}, {reverseGeocodePlace.country}</p>
|
<p>{reverseGeocodePlace.region}, {reverseGeocodePlace.country}</p>
|
||||||
<p>{reverseGeocodePlace.is_visited ? 'Visited' : 'Not Visited'}</p>
|
<p>
|
||||||
|
{reverseGeocodePlace.is_visited
|
||||||
|
? $t('adventures.visited')
|
||||||
|
: $t('adventures.not_visited')}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{#if !reverseGeocodePlace.is_visited}
|
{#if !reverseGeocodePlace.is_visited}
|
||||||
<div role="alert" class="alert alert-info mt-2">
|
<div role="alert" class="alert alert-info mt-2">
|
||||||
|
@ -647,10 +652,15 @@ it would also work to just use on:click on the MapLibre component itself. -->
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
<span
|
<span
|
||||||
>Mark region {reverseGeocodePlace.region}, {reverseGeocodePlace.country} as visited?</span
|
>{$t('adventures.mark_region_as_visited', {
|
||||||
|
values: {
|
||||||
|
region: reverseGeocodePlace.region,
|
||||||
|
country: reverseGeocodePlace.country
|
||||||
|
}
|
||||||
|
})}</span
|
||||||
>
|
>
|
||||||
<button type="button" class="btn btn-neutral" on:click={markVisited}>
|
<button type="button" class="btn btn-neutral" on:click={markVisited}>
|
||||||
Mark as Visited
|
{$t('adventures.mark_visited')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -253,28 +253,6 @@ export let ADVENTURE_TYPES = [
|
||||||
{ type: 'other', label: 'Other' }
|
{ type: 'other', label: 'Other' }
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if an adventure has been visited.
|
|
||||||
*
|
|
||||||
* This function determines if the `adventure.visits` array contains at least one visit
|
|
||||||
* with a `start_date` that is before the current date.
|
|
||||||
*
|
|
||||||
* @param adventure - The adventure object to check.
|
|
||||||
* @returns `true` if the adventure has been visited, otherwise `false`.
|
|
||||||
*/
|
|
||||||
export function isAdventureVisited(adventure: Adventure) {
|
|
||||||
const currentTime = Date.now();
|
|
||||||
|
|
||||||
// Check if any visit's start_date is before the current time.
|
|
||||||
return (
|
|
||||||
adventure.visits &&
|
|
||||||
adventure.visits.some((visit) => {
|
|
||||||
const visitStartTime = new Date(visit.start_date).getTime();
|
|
||||||
return visit.start_date && visitStartTime <= currentTime;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getRandomBackground() {
|
export function getRandomBackground() {
|
||||||
const randomIndex = Math.floor(Math.random() * randomBackgrounds.backgrounds.length);
|
const randomIndex = Math.floor(Math.random() * randomBackgrounds.backgrounds.length);
|
||||||
return randomBackgrounds.backgrounds[randomIndex] as Background;
|
return randomBackgrounds.backgrounds[randomIndex] as Background;
|
||||||
|
|
|
@ -37,6 +37,7 @@ export type Adventure = {
|
||||||
is_public: boolean;
|
is_public: boolean;
|
||||||
created_at?: string | null;
|
created_at?: string | null;
|
||||||
updated_at?: string | null;
|
updated_at?: string | null;
|
||||||
|
is_visited?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Country = {
|
export type Country = {
|
||||||
|
|
|
@ -139,6 +139,7 @@
|
||||||
"unarchive": "Unarchive",
|
"unarchive": "Unarchive",
|
||||||
"archive": "Archive",
|
"archive": "Archive",
|
||||||
"no_collections_found": "No collections found to add this adventure to.",
|
"no_collections_found": "No collections found to add this adventure to.",
|
||||||
|
"not_visited": "Not Visited",
|
||||||
"archived_collection_message": "Collection archived successfully!",
|
"archived_collection_message": "Collection archived successfully!",
|
||||||
"unarchived_collection_message": "Collection unarchived successfully!",
|
"unarchived_collection_message": "Collection unarchived successfully!",
|
||||||
"delete_collection_success": "Collection deleted successfully!",
|
"delete_collection_success": "Collection deleted successfully!",
|
||||||
|
@ -173,6 +174,8 @@
|
||||||
"basic_information": "Basic Information",
|
"basic_information": "Basic Information",
|
||||||
"adventure_not_found": "There are no adventures to display. Add some using the plus button at the bottom right or try changing filters!",
|
"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",
|
"no_adventures_found": "No adventures found",
|
||||||
|
"mark_region_as_visited": "Mark region {region}, {country} as visited?",
|
||||||
|
"mark_visited": "Mark Visited",
|
||||||
"activities": {
|
"activities": {
|
||||||
"general": "General 🌍",
|
"general": "General 🌍",
|
||||||
"outdoor": "Outdoor 🏞️",
|
"outdoor": "Outdoor 🏞️",
|
||||||
|
|
|
@ -196,7 +196,10 @@
|
||||||
"no_adventures_found": "No se encontraron aventuras",
|
"no_adventures_found": "No se encontraron aventuras",
|
||||||
"no_collections_found": "No se encontraron colecciones para agregar esta aventura.",
|
"no_collections_found": "No se encontraron colecciones para agregar esta aventura.",
|
||||||
"my_adventures": "mis aventuras",
|
"my_adventures": "mis aventuras",
|
||||||
"no_linkable_adventures": "No se encontraron aventuras que puedan vincularse a esta colección."
|
"no_linkable_adventures": "No se encontraron aventuras que puedan vincularse a esta colección.",
|
||||||
|
"mark_region_as_visited": "¿Marcar región {region}, {country} como visitada?",
|
||||||
|
"mark_visited": "Marcos visitó",
|
||||||
|
"not_visited": "No visitado"
|
||||||
},
|
},
|
||||||
"worldtravel": {
|
"worldtravel": {
|
||||||
"all": "Todo",
|
"all": "Todo",
|
||||||
|
|
|
@ -20,8 +20,7 @@
|
||||||
groupAdventuresByDate,
|
groupAdventuresByDate,
|
||||||
groupNotesByDate,
|
groupNotesByDate,
|
||||||
groupTransportationsByDate,
|
groupTransportationsByDate,
|
||||||
groupChecklistsByDate,
|
groupChecklistsByDate
|
||||||
isAdventureVisited
|
|
||||||
} from '$lib';
|
} from '$lib';
|
||||||
import ChecklistCard from '$lib/components/ChecklistCard.svelte';
|
import ChecklistCard from '$lib/components/ChecklistCard.svelte';
|
||||||
import ChecklistModal from '$lib/components/ChecklistModal.svelte';
|
import ChecklistModal from '$lib/components/ChecklistModal.svelte';
|
||||||
|
@ -45,7 +44,7 @@
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
numAdventures = adventures.length;
|
numAdventures = adventures.length;
|
||||||
numVisited = adventures.filter(isAdventureVisited).length;
|
numVisited = adventures.filter((adventure) => adventure.is_visited).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
let notFound: boolean = false;
|
let notFound: boolean = false;
|
||||||
|
|
|
@ -46,6 +46,7 @@ export const load = (async (event) => {
|
||||||
name: adventure.name,
|
name: adventure.name,
|
||||||
visits: adventure.visits,
|
visits: adventure.visits,
|
||||||
type: adventure.type,
|
type: adventure.type,
|
||||||
|
is_visited: adventure.is_visited
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
|
||||||
import { isAdventureVisited } from '$lib';
|
|
||||||
import AdventureModal from '$lib/components/AdventureModal.svelte';
|
import AdventureModal from '$lib/components/AdventureModal.svelte';
|
||||||
import {
|
import {
|
||||||
DefaultMarker,
|
DefaultMarker,
|
||||||
|
@ -25,8 +24,7 @@
|
||||||
let showPlanned = true;
|
let showPlanned = true;
|
||||||
|
|
||||||
$: filteredMarkers = markers.filter(
|
$: filteredMarkers = markers.filter(
|
||||||
(marker) =>
|
(marker) => (showVisited && marker.is_visited) || (showPlanned && !marker.is_visited)
|
||||||
(showVisited && isAdventureVisited(marker)) || (showPlanned && !isAdventureVisited(marker))
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let newMarker = [];
|
let newMarker = [];
|
||||||
|
@ -145,7 +143,7 @@
|
||||||
standardControls
|
standardControls
|
||||||
>
|
>
|
||||||
{#each filteredMarkers as marker}
|
{#each filteredMarkers as marker}
|
||||||
{#if isAdventureVisited(marker)}
|
{#if marker.is_visited}
|
||||||
<Marker
|
<Marker
|
||||||
lngLat={marker.lngLat}
|
lngLat={marker.lngLat}
|
||||||
on:click={() => (clickedName = marker.name)}
|
on:click={() => (clickedName = marker.name)}
|
||||||
|
@ -205,7 +203,7 @@
|
||||||
<div class="text-lg text-black font-bold">{marker.name}</div>
|
<div class="text-lg text-black font-bold">{marker.name}</div>
|
||||||
<p class="font-semibold text-black text-md">Planned</p>
|
<p class="font-semibold text-black text-md">Planned</p>
|
||||||
<p class="font-semibold text-black text-md">
|
<p class="font-semibold text-black text-md">
|
||||||
{$t(`adventures.activities.${marker.type}`)}}
|
{$t(`adventures.activities.${marker.type}`)}
|
||||||
</p>
|
</p>
|
||||||
{#if marker.visits && marker.visits.length > 0}
|
{#if marker.visits && marker.visits.length > 0}
|
||||||
<p class="text-black text-sm">
|
<p class="text-black text-sm">
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue