18
backend/server/adventures/migrations/0010_collection_link.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 5.0.8 on 2024-10-08 03:05
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('adventures', '0009_alter_adventure_type'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='collection',
|
||||||
|
name='link',
|
||||||
|
field=models.URLField(blank=True, max_length=2083, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -117,6 +117,7 @@ class Collection(models.Model):
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
is_archived = models.BooleanField(default=False)
|
is_archived = models.BooleanField(default=False)
|
||||||
shared_with = models.ManyToManyField(User, related_name='shared_with', blank=True)
|
shared_with = models.ManyToManyField(User, related_name='shared_with', blank=True)
|
||||||
|
link = models.URLField(blank=True, null=True, max_length=2083)
|
||||||
|
|
||||||
|
|
||||||
# if connected adventures are private and collection is public, raise an error
|
# if connected adventures are private and collection is public, raise an error
|
||||||
|
|
|
@ -176,8 +176,7 @@ class CollectionSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Collection
|
model = Collection
|
||||||
# fields are all plus the adventures field
|
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']
|
||||||
fields = ['id', 'description', 'user_id', 'name', 'is_public', 'adventures', 'created_at', 'start_date', 'end_date', 'transportations', 'notes', 'updated_at', 'checklists', 'is_archived', 'shared_with']
|
|
||||||
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id']
|
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id']
|
||||||
|
|
||||||
def to_representation(self, instance):
|
def to_representation(self, instance):
|
||||||
|
|
|
@ -511,10 +511,8 @@ class StatsViewSet(viewsets.ViewSet):
|
||||||
|
|
||||||
@action(detail=False, methods=['get'])
|
@action(detail=False, methods=['get'])
|
||||||
def counts(self, request):
|
def counts(self, request):
|
||||||
visited_count = Adventure.objects.filter(
|
adventure_count = Adventure.objects.filter(
|
||||||
type='visited', user_id=request.user.id).count()
|
user_id=request.user.id).count()
|
||||||
planned_count = Adventure.objects.filter(
|
|
||||||
type='planned', user_id=request.user.id).count()
|
|
||||||
trips_count = Collection.objects.filter(
|
trips_count = Collection.objects.filter(
|
||||||
user_id=request.user.id).count()
|
user_id=request.user.id).count()
|
||||||
visited_region_count = VisitedRegion.objects.filter(
|
visited_region_count = VisitedRegion.objects.filter(
|
||||||
|
@ -524,8 +522,7 @@ class StatsViewSet(viewsets.ViewSet):
|
||||||
user_id=request.user.id).values('region__country').distinct().count()
|
user_id=request.user.id).values('region__country').distinct().count()
|
||||||
total_countries = Country.objects.count()
|
total_countries = Country.objects.count()
|
||||||
return Response({
|
return Response({
|
||||||
'visited_count': visited_count,
|
'adventure_count': adventure_count,
|
||||||
'planned_count': planned_count,
|
|
||||||
'trips_count': trips_count,
|
'trips_count': trips_count,
|
||||||
'visited_region_count': visited_region_count,
|
'visited_region_count': visited_region_count,
|
||||||
'total_regions': total_regions,
|
'total_regions': total_regions,
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
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 ImageDisplayModal from './ImageDisplayModal.svelte';
|
|
||||||
import { isAdventureVisited, typeToString } from '$lib';
|
import { isAdventureVisited, typeToString } from '$lib';
|
||||||
|
import CardCarousel from './CardCarousel.svelte';
|
||||||
|
|
||||||
export let type: string;
|
export let type: string;
|
||||||
export let user: User | null;
|
export let user: User | null;
|
||||||
|
@ -28,7 +28,6 @@
|
||||||
let isCollectionModalOpen: boolean = false;
|
let isCollectionModalOpen: boolean = false;
|
||||||
let isWarningModalOpen: boolean = false;
|
let isWarningModalOpen: boolean = false;
|
||||||
|
|
||||||
let image_url: string | null = null;
|
|
||||||
export let adventure: Adventure;
|
export let adventure: Adventure;
|
||||||
|
|
||||||
let activityTypes: string[] = [];
|
let activityTypes: string[] = [];
|
||||||
|
@ -120,12 +119,6 @@
|
||||||
dispatch('edit', adventure);
|
dispatch('edit', adventure);
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentSlide = 0;
|
|
||||||
|
|
||||||
function goToSlide(index: number) {
|
|
||||||
currentSlide = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
function link() {
|
function link() {
|
||||||
dispatch('link', adventure);
|
dispatch('link', adventure);
|
||||||
}
|
}
|
||||||
|
@ -146,48 +139,10 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if image_url}
|
|
||||||
<ImageDisplayModal image={image_url} on:close={() => (image_url = null)} {adventure} />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="card w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-md xl:max-w-md bg-neutral text-neutral-content shadow-xl"
|
class="card w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-md xl:max-w-md bg-neutral text-neutral-content shadow-xl"
|
||||||
>
|
>
|
||||||
<figure>
|
<CardCarousel adventures={[adventure]} />
|
||||||
{#if adventure.images && adventure.images.length > 0}
|
|
||||||
<div class="carousel w-full">
|
|
||||||
{#each adventure.images as image, i}
|
|
||||||
<div
|
|
||||||
class="carousel-item w-full"
|
|
||||||
style="display: {i === currentSlide ? 'block' : 'none'}"
|
|
||||||
>
|
|
||||||
<!-- svelte-ignore a11y-invalid-attribute -->
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
||||||
<!-- svelte-ignore a11y-missing-attribute -->
|
|
||||||
<a on:click={() => (image_url = image.image)}
|
|
||||||
><img src={image.image} class="w-full h-48 object-cover" alt={adventure.name} /></a
|
|
||||||
>
|
|
||||||
<div class="flex justify-center w-full py-2 gap-2">
|
|
||||||
{#each adventure.images as _, i}
|
|
||||||
<button
|
|
||||||
on:click={() => goToSlide(i)}
|
|
||||||
class="btn btn-xs {i === currentSlide ? 'btn-active' : ''}">{i + 1}</button
|
|
||||||
>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<!-- svelte-ignore a11y-img-redundant-alt -->
|
|
||||||
<img
|
|
||||||
src={'https://placehold.co/300?text=No%20Image%20Found&font=roboto'}
|
|
||||||
alt="No image available"
|
|
||||||
class="w-full h-48 object-cover"
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
|
|
87
frontend/src/lib/components/CardCarousel.svelte
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { Adventure } from '$lib/types';
|
||||||
|
import ImageDisplayModal from './ImageDisplayModal.svelte';
|
||||||
|
|
||||||
|
export let adventures: Adventure[] = [];
|
||||||
|
|
||||||
|
let currentSlide = 0;
|
||||||
|
let image_url: string | null = null;
|
||||||
|
|
||||||
|
$: adventure_images = adventures.flatMap((adventure) =>
|
||||||
|
adventure.images.map((image) => ({ image: image.image, adventure: adventure }))
|
||||||
|
);
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (adventure_images.length > 0) {
|
||||||
|
currentSlide = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeSlide(direction: string) {
|
||||||
|
if (direction === 'next' && currentSlide < adventure_images.length - 1) {
|
||||||
|
currentSlide = currentSlide + 1;
|
||||||
|
} else if (direction === 'prev' && currentSlide > 0) {
|
||||||
|
currentSlide = currentSlide - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if image_url}
|
||||||
|
<ImageDisplayModal
|
||||||
|
adventure={adventure_images[currentSlide].adventure}
|
||||||
|
image={image_url}
|
||||||
|
on:close={() => (image_url = null)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
{#if adventure_images && adventure_images.length > 0}
|
||||||
|
<div class="carousel w-full relative">
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<div class="carousel-item w-full block">
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
<!-- svelte-ignore a11y-missing-attribute -->
|
||||||
|
<a
|
||||||
|
on:click|stopPropagation={() => (image_url = adventure_images[currentSlide].image)}
|
||||||
|
class="cursor-pointer"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={adventure_images[currentSlide].image}
|
||||||
|
class="w-full h-48 object-cover"
|
||||||
|
alt={adventure_images[currentSlide].adventure.name}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{#if adventure_images.length > 1}
|
||||||
|
<div class="absolute inset-0 flex items-center justify-between pointer-events-none">
|
||||||
|
{#if currentSlide > 0}
|
||||||
|
<button
|
||||||
|
on:click|stopPropagation={() => changeSlide('prev')}
|
||||||
|
class="btn btn-circle btn-sm ml-2 pointer-events-auto">❮</button
|
||||||
|
>
|
||||||
|
{:else}
|
||||||
|
<div class="w-12"></div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if currentSlide < adventure_images.length - 1}
|
||||||
|
<button
|
||||||
|
on:click|stopPropagation={() => changeSlide('next')}
|
||||||
|
class="btn btn-circle mr-2 btn-sm pointer-events-auto">❯</button
|
||||||
|
>
|
||||||
|
{:else}
|
||||||
|
<div class="w-12"></div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<!-- svelte-ignore a11y-img-redundant-alt -->
|
||||||
|
<img
|
||||||
|
src={'https://placehold.co/300?text=No%20Image%20Found&font=roboto'}
|
||||||
|
alt="No image available"
|
||||||
|
class="w-full h-48 object-cover"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</figure>
|
|
@ -9,7 +9,7 @@
|
||||||
import ArchiveArrowUp from '~icons/mdi/archive-arrow-up';
|
import ArchiveArrowUp from '~icons/mdi/archive-arrow-up';
|
||||||
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import type { Collection } from '$lib/types';
|
import type { Adventure, Collection } from '$lib/types';
|
||||||
import { addToast } from '$lib/toasts';
|
import { addToast } from '$lib/toasts';
|
||||||
|
|
||||||
import Plus from '~icons/mdi/plus';
|
import Plus from '~icons/mdi/plus';
|
||||||
|
@ -17,14 +17,14 @@
|
||||||
import TrashCan from '~icons/mdi/trashcan';
|
import TrashCan from '~icons/mdi/trashcan';
|
||||||
import DeleteWarning from './DeleteWarning.svelte';
|
import DeleteWarning from './DeleteWarning.svelte';
|
||||||
import ShareModal from './ShareModal.svelte';
|
import ShareModal from './ShareModal.svelte';
|
||||||
|
import CardCarousel from './CardCarousel.svelte';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export let type: String | undefined | null;
|
export let type: String | undefined | null;
|
||||||
|
export let adventures: Adventure[] = [];
|
||||||
let isShareModalOpen: boolean = false;
|
let isShareModalOpen: boolean = false;
|
||||||
|
|
||||||
// export let type: String;
|
|
||||||
|
|
||||||
function editAdventure() {
|
function editAdventure() {
|
||||||
dispatch('edit', collection);
|
dispatch('edit', collection);
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,7 @@
|
||||||
<div
|
<div
|
||||||
class="card min-w-max lg:w-96 md:w-80 sm:w-60 xs:w-40 bg-neutral text-neutral-content shadow-xl"
|
class="card min-w-max lg:w-96 md:w-80 sm:w-60 xs:w-40 bg-neutral text-neutral-content shadow-xl"
|
||||||
>
|
>
|
||||||
|
<CardCarousel {adventures} />
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -11,16 +11,8 @@
|
||||||
|
|
||||||
let originalName = collectionToEdit.name;
|
let originalName = collectionToEdit.name;
|
||||||
|
|
||||||
let isPointModalOpen: boolean = false;
|
|
||||||
|
|
||||||
import MapMarker from '~icons/mdi/map-marker';
|
|
||||||
import Calendar from '~icons/mdi/calendar';
|
import Calendar from '~icons/mdi/calendar';
|
||||||
import Notebook from '~icons/mdi/notebook';
|
import Notebook from '~icons/mdi/notebook';
|
||||||
import ClipboardList from '~icons/mdi/clipboard-list';
|
|
||||||
import Image from '~icons/mdi/image';
|
|
||||||
import Star from '~icons/mdi/star';
|
|
||||||
import Attachment from '~icons/mdi/attachment';
|
|
||||||
import PointSelectionModal from './PointSelectionModal.svelte';
|
|
||||||
import Earth from '~icons/mdi/earth';
|
import Earth from '~icons/mdi/earth';
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
@ -156,6 +148,16 @@
|
||||||
class="input input-bordered w-full max-w-xs mt-1"
|
class="input input-bordered w-full max-w-xs mt-1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="end_date">Link </label><br />
|
||||||
|
<input
|
||||||
|
type="url"
|
||||||
|
id="link"
|
||||||
|
name="link"
|
||||||
|
bind:value={collectionToEdit.link}
|
||||||
|
class="input input-bordered w-full max-w-xs mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label for="is_public">Public <Earth class="inline-block -mt-1 mb-1 w-6 h-6" /></label><br
|
<label for="is_public">Public <Earth class="inline-block -mt-1 mb-1 w-6 h-6" /></label><br
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import type { Adventure } from '$lib/types';
|
import type { Adventure } from '$lib/types';
|
||||||
|
|
||||||
export let image: string;
|
export let image: string;
|
||||||
export let adventure: Adventure;
|
export let adventure: Adventure | null = null;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
modal = document.getElementById('my_modal_1') as HTMLDialogElement;
|
modal = document.getElementById('my_modal_1') as HTMLDialogElement;
|
||||||
|
@ -42,34 +42,36 @@
|
||||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||||
<div class="modal-box w-11/12 max-w-5xl" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
<div class="modal-box w-11/12 max-w-5xl" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
||||||
<div class="modal-header flex justify-between items-center mb-4">
|
{#if adventure}
|
||||||
<h3 class="font-bold text-2xl">{adventure.name}</h3>
|
<div class="modal-header flex justify-between items-center mb-4">
|
||||||
<button class="btn btn-circle btn-neutral" on:click={close}>
|
<h3 class="font-bold text-2xl">{adventure.name}</h3>
|
||||||
<svg
|
<button class="btn btn-circle btn-neutral" on:click={close}>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<svg
|
||||||
class="h-6 w-6"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
class="h-6 w-6"
|
||||||
viewBox="0 0 24 24"
|
fill="none"
|
||||||
stroke="currentColor"
|
viewBox="0 0 24 24"
|
||||||
>
|
stroke="currentColor"
|
||||||
<path
|
>
|
||||||
stroke-linecap="round"
|
<path
|
||||||
stroke-linejoin="round"
|
stroke-linecap="round"
|
||||||
stroke-width="2"
|
stroke-linejoin="round"
|
||||||
d="M6 18L18 6M6 6l12 12"
|
stroke-width="2"
|
||||||
/>
|
d="M6 18L18 6M6 6l12 12"
|
||||||
</svg>
|
/>
|
||||||
</button>
|
</svg>
|
||||||
</div>
|
</button>
|
||||||
<div
|
</div>
|
||||||
class="flex justify-center items-center"
|
<div
|
||||||
style="display: flex; justify-content: center; align-items: center;"
|
class="flex justify-center items-center"
|
||||||
>
|
style="display: flex; justify-content: center; align-items: center;"
|
||||||
<img
|
>
|
||||||
src={image}
|
<img
|
||||||
alt={adventure.name}
|
src={image}
|
||||||
style="max-width: 100%; max-height: 75vh; object-fit: contain;"
|
alt={adventure.name}
|
||||||
/>
|
style="max-width: 100%; max-height: 75vh; object-fit: contain;"
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
|
@ -13,7 +13,9 @@
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
adventures: [] as Adventure[],
|
adventures: [] as Adventure[],
|
||||||
is_public: false
|
is_public: false,
|
||||||
|
shared_with: [],
|
||||||
|
link: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
@ -151,6 +153,16 @@
|
||||||
class="input input-bordered w-full max-w-xs mt-1"
|
class="input input-bordered w-full max-w-xs mt-1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="end_date">Link </label><br />
|
||||||
|
<input
|
||||||
|
type="url"
|
||||||
|
id="link"
|
||||||
|
name="link"
|
||||||
|
bind:value={newCollection.link}
|
||||||
|
class="input input-bordered w-full max-w-xs mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<button type="submit" class="btn btn-primary mr-4 mt-4">Create</button>
|
<button type="submit" class="btn btn-primary mr-4 mt-4">Create</button>
|
||||||
<button type="button" class="btn mt-4" on:click={close}>Close</button>
|
<button type="button" class="btn mt-4" on:click={close}>Close</button>
|
||||||
|
|
|
@ -57,11 +57,7 @@
|
||||||
class="card w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-md xl:max-w-md bg-neutral text-neutral-content shadow-xl overflow-hidden"
|
class="card w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-md xl:max-w-md bg-neutral text-neutral-content shadow-xl overflow-hidden"
|
||||||
>
|
>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{#if region.name_en && region.name !== region.name_en}
|
<h2 class="card-title overflow-ellipsis">{region.name}</h2>
|
||||||
<h2 class="card-title overflow-ellipsis">{region.name} ({region.name_en})</h2>
|
|
||||||
{:else}
|
|
||||||
<h2 class="card-title overflow-ellipsis">{region.name}</h2>
|
|
||||||
{/if}
|
|
||||||
<p>{region.id}</p>
|
<p>{region.id}</p>
|
||||||
<div class="card-actions justify-end">
|
<div class="card-actions justify-end">
|
||||||
<!-- <button class="btn btn-info" on:click={moreInfo}>More Info</button> -->
|
<!-- <button class="btn btn-info" on:click={moreInfo}>More Info</button> -->
|
||||||
|
|
|
@ -87,6 +87,7 @@ export type Collection = {
|
||||||
checklists?: Checklist[];
|
checklists?: Checklist[];
|
||||||
is_archived?: boolean;
|
is_archived?: boolean;
|
||||||
shared_with: string[];
|
shared_with: string[];
|
||||||
|
link?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OpenStreetMapPlace = {
|
export type OpenStreetMapPlace = {
|
||||||
|
|
|
@ -53,6 +53,11 @@ export const actions: Actions = {
|
||||||
const description = formData.get('description') as string | null;
|
const description = formData.get('description') as string | null;
|
||||||
const start_date = formData.get('start_date') as string | null;
|
const start_date = formData.get('start_date') as string | null;
|
||||||
const end_date = formData.get('end_date') as string | null;
|
const end_date = formData.get('end_date') as string | null;
|
||||||
|
let link = formData.get('link') as string | null;
|
||||||
|
|
||||||
|
if (link) {
|
||||||
|
link = checkLink(link);
|
||||||
|
}
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return {
|
return {
|
||||||
|
@ -66,6 +71,7 @@ export const actions: Actions = {
|
||||||
formDataToSend.append('description', description || '');
|
formDataToSend.append('description', description || '');
|
||||||
formDataToSend.append('start_date', start_date || '');
|
formDataToSend.append('start_date', start_date || '');
|
||||||
formDataToSend.append('end_date', end_date || '');
|
formDataToSend.append('end_date', end_date || '');
|
||||||
|
formDataToSend.append('link', link || '');
|
||||||
let auth = event.cookies.get('auth');
|
let auth = event.cookies.get('auth');
|
||||||
|
|
||||||
if (!auth) {
|
if (!auth) {
|
||||||
|
@ -142,6 +148,7 @@ export const actions: Actions = {
|
||||||
let is_public = formData.get('is_public') as string | null | boolean;
|
let is_public = formData.get('is_public') as string | null | boolean;
|
||||||
const start_date = formData.get('start_date') as string | null;
|
const start_date = formData.get('start_date') as string | null;
|
||||||
const end_date = formData.get('end_date') as string | null;
|
const end_date = formData.get('end_date') as string | null;
|
||||||
|
let link = formData.get('link') as string | null;
|
||||||
|
|
||||||
if (is_public) {
|
if (is_public) {
|
||||||
is_public = true;
|
is_public = true;
|
||||||
|
@ -149,6 +156,10 @@ export const actions: Actions = {
|
||||||
is_public = false;
|
is_public = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (link) {
|
||||||
|
link = checkLink(link);
|
||||||
|
}
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return {
|
return {
|
||||||
status: 400,
|
status: 400,
|
||||||
|
@ -162,6 +173,7 @@ export const actions: Actions = {
|
||||||
formDataToSend.append('is_public', is_public.toString());
|
formDataToSend.append('is_public', is_public.toString());
|
||||||
formDataToSend.append('start_date', start_date || '');
|
formDataToSend.append('start_date', start_date || '');
|
||||||
formDataToSend.append('end_date', end_date || '');
|
formDataToSend.append('end_date', end_date || '');
|
||||||
|
formDataToSend.append('link', link || '');
|
||||||
|
|
||||||
let auth = event.cookies.get('auth');
|
let auth = event.cookies.get('auth');
|
||||||
|
|
||||||
|
|
|
@ -180,6 +180,7 @@
|
||||||
{collection}
|
{collection}
|
||||||
on:delete={deleteCollection}
|
on:delete={deleteCollection}
|
||||||
on:edit={editCollection}
|
on:edit={editCollection}
|
||||||
|
adventures={collection.adventures}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -20,7 +20,8 @@
|
||||||
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';
|
||||||
|
@ -43,13 +44,12 @@
|
||||||
let numberOfDays: number = NaN;
|
let numberOfDays: number = NaN;
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
numAdventures = adventures.filter((a) => a.type === 'visited' || a.type === 'planned').length;
|
numAdventures = adventures.length;
|
||||||
numVisited = adventures.filter((a) => a.type === 'visited').length;
|
numVisited = adventures.filter(isAdventureVisited).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
let notFound: boolean = false;
|
let notFound: boolean = false;
|
||||||
let isShowingLinkModal: boolean = false;
|
let isShowingLinkModal: boolean = false;
|
||||||
let isShowingCreateModal: boolean = false;
|
|
||||||
let isShowingTransportationModal: boolean = false;
|
let isShowingTransportationModal: boolean = false;
|
||||||
let isShowingChecklistModal: boolean = false;
|
let isShowingChecklistModal: boolean = false;
|
||||||
|
|
||||||
|
@ -371,6 +371,14 @@
|
||||||
{#if collection.name}
|
{#if collection.name}
|
||||||
<h1 class="text-center font-extrabold text-4xl mb-2">{collection.name}</h1>
|
<h1 class="text-center font-extrabold text-4xl mb-2">{collection.name}</h1>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if collection.link}
|
||||||
|
<div class="flex items-center justify-center mb-2">
|
||||||
|
<a href={collection.link} target="_blank" rel="noopener noreferrer" class="btn btn-primary">
|
||||||
|
Visit Link
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if collection.description}
|
{#if collection.description}
|
||||||
<p class="text-center text-lg mb-2">{collection.description}</p>
|
<p class="text-center text-lg mb-2">{collection.description}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -3,10 +3,9 @@
|
||||||
|
|
||||||
let stats: {
|
let stats: {
|
||||||
country_count: number;
|
country_count: number;
|
||||||
planned_count: number;
|
|
||||||
total_regions: number;
|
total_regions: number;
|
||||||
trips_count: number;
|
trips_count: number;
|
||||||
visited_count: number;
|
adventure_count: number;
|
||||||
visited_region_count: number;
|
visited_region_count: number;
|
||||||
total_countries: number;
|
total_countries: number;
|
||||||
} | null;
|
} | null;
|
||||||
|
@ -19,16 +18,6 @@
|
||||||
console.log(stats);
|
console.log(stats);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!--
|
|
||||||
// v0 by Vercel.
|
|
||||||
// https://v0.dev/t/EtPnDdQYcbn
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!--
|
|
||||||
// v0 by Vercel.
|
|
||||||
// https://v0.dev/t/DYwTru570WN
|
|
||||||
-->
|
|
||||||
|
|
||||||
{#if data.user.profile_pic}
|
{#if data.user.profile_pic}
|
||||||
<div class="avatar flex items-center justify-center">
|
<div class="avatar flex items-center justify-center">
|
||||||
<div class="w-24 rounded">
|
<div class="w-24 rounded">
|
||||||
|
@ -65,17 +54,10 @@
|
||||||
<div class="flex justify-center items-center">
|
<div class="flex justify-center items-center">
|
||||||
<div class="stats stats-vertical lg:stats-horizontal shadow bg-base-200">
|
<div class="stats stats-vertical lg:stats-horizontal shadow bg-base-200">
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">Completed Adventures</div>
|
<div class="stat-title">Adventures</div>
|
||||||
<div class="stat-value text-center">{stats.visited_count}</div>
|
<div class="stat-value text-center">{stats.adventure_count}</div>
|
||||||
<!-- <div class="stat-desc">Jan 1st - Feb 1st</div> -->
|
<!-- <div class="stat-desc">Jan 1st - Feb 1st</div> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat">
|
|
||||||
<div class="stat-title">Planned Adventures</div>
|
|
||||||
<div class="stat-value text-center">{stats.planned_count}</div>
|
|
||||||
<!-- <div class="stat-desc">↗︎ 400 (22%)</div> -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">Collections</div>
|
<div class="stat-title">Collections</div>
|
||||||
<div class="stat-value text-center">{stats.trips_count}</div>
|
<div class="stat-value text-center">{stats.trips_count}</div>
|
||||||
|
|
|
@ -25,3 +25,8 @@
|
||||||
{/if}
|
{/if}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Shared Collections</title>
|
||||||
|
<meta name="description" content="Collections shared with you by other users." />
|
||||||
|
</svelte:head>
|
||||||
|
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 266 KiB |
Before Width: | Height: | Size: 908 KiB After Width: | Height: | Size: 534 KiB |
Before Width: | Height: | Size: 484 KiB After Width: | Height: | Size: 493 KiB |
Before Width: | Height: | Size: 656 KiB After Width: | Height: | Size: 410 KiB |
Before Width: | Height: | Size: 299 KiB After Width: | Height: | Size: 305 KiB |
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 62 KiB |