diff --git a/backend/server/adventures/views.py b/backend/server/adventures/views.py index 5737faa..8cfbcfe 100644 --- a/backend/server/adventures/views.py +++ b/backend/server/adventures/views.py @@ -120,7 +120,7 @@ class AdventureViewSet(viewsets.ModelViewSet): # Q(is_public=True) | Q(user_id=request.user.id), collection=None # ) queryset = Adventure.objects.filter( - Q(is_public=True) | Q(user_id=request.user.id) + Q(user_id=request.user.id) ) queryset = self.apply_sorting(queryset) @@ -167,6 +167,29 @@ class CollectionViewSet(viewsets.ModelViewSet): return queryset.order_by(ordering) + def list(self, request, *args, **kwargs): + # make sure the user is authenticated + if not request.user.is_authenticated: + return Response({"error": "User is not authenticated"}, status=400) + queryset = self.get_queryset() + queryset = self.apply_sorting(queryset) + collections = self.paginate_and_respond(queryset, request) + return collections + + @action(detail=False, methods=['get']) + def all(self, request): + if not request.user.is_authenticated: + return Response({"error": "User is not authenticated"}, status=400) + + queryset = Collection.objects.filter( + Q(user_id=request.user.id) + ) + + queryset = self.apply_sorting(queryset) + serializer = self.get_serializer(queryset, many=True) + + return Response(serializer.data) + # this make the is_public field of the collection cascade to the adventures @transaction.atomic def update(self, request, *args, **kwargs): diff --git a/frontend/src/lib/components/AdventureCard.svelte b/frontend/src/lib/components/AdventureCard.svelte index 6510832..1958b59 100644 --- a/frontend/src/lib/components/AdventureCard.svelte +++ b/frontend/src/lib/components/AdventureCard.svelte @@ -11,9 +11,16 @@ import MapMarker from '~icons/mdi/map-marker'; import { addToast } from '$lib/toasts'; import Link from '~icons/mdi/link-variant'; + import CheckBold from '~icons/mdi/check-bold'; + import FormatListBulletedSquare from '~icons/mdi/format-list-bulleted-square'; + import LinkVariantRemove from '~icons/mdi/link-variant-remove'; + import Plus from '~icons/mdi/plus'; + import CollectionLink from './CollectionLink.svelte'; export let type: string; + let isCollectionModalOpen: boolean = false; + export let adventure: Adventure; async function deleteAdventure() { @@ -32,6 +39,61 @@ } } + async function removeFromCollection() { + let res = await fetch(`/api/adventures/${adventure.id}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ collection: null }) + }); + if (res.ok) { + console.log('Adventure removed from collection'); + addToast('info', 'Adventure removed from collection successfully!'); + dispatch('delete', adventure.id); + } else { + console.log('Error removing adventure from collection'); + } + } + + function changeType(newType: string) { + return async () => { + let res = await fetch(`/api/adventures/${adventure.id}/`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ type: newType }) + }); + if (res.ok) { + console.log('Adventure type changed'); + addToast('info', 'Adventure type changed successfully!'); + adventure.type = newType; + } else { + console.log('Error changing adventure type'); + } + }; + } + + async function linkCollection(event: CustomEvent) { + let collectionId = event.detail; + let res = await fetch(`/api/adventures/${adventure.id}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ collection: collectionId }) + }); + if (res.ok) { + console.log('Adventure linked to collection'); + addToast('info', 'Adventure linked to collection successfully!'); + isCollectionModalOpen = false; + dispatch('delete', adventure.id); + } else { + console.log('Error linking adventure to collection'); + } + } + function editAdventure() { dispatch('edit', adventure); } @@ -41,6 +103,10 @@ } +{#if isCollectionModalOpen} + (isCollectionModalOpen = false)} /> +{/if} +
@@ -61,6 +127,11 @@

{adventure.name}

+ {#if adventure.type == 'visited'} +
Visited
+ {:else} +
Planned
+ {/if} {#if adventure.location && adventure.location !== ''}
@@ -108,6 +179,27 @@ {#if type == 'link'} {/if} + {#if adventure.type == 'visited'} + + {/if} + + {#if adventure.type == 'planned'} + + {/if} + {#if adventure.collection} + + {/if} + {#if !adventure.collection} + + {/if}
diff --git a/frontend/src/lib/components/CollectionCard.svelte b/frontend/src/lib/components/CollectionCard.svelte index 3bc7d05..4860a76 100644 --- a/frontend/src/lib/components/CollectionCard.svelte +++ b/frontend/src/lib/components/CollectionCard.svelte @@ -9,8 +9,13 @@ import { goto } from '$app/navigation'; import type { Collection } from '$lib/types'; import { addToast } from '$lib/toasts'; + + import Plus from '~icons/mdi/plus'; + const dispatch = createEventDispatcher(); + export let type: String; + // export let type: String; function editAdventure() { @@ -43,15 +48,22 @@

{collection.name}

{collection.adventures.length} Adventures

- - - + {#if type != 'link'} + + + + {/if} + {#if type == 'link'} + + {/if}
diff --git a/frontend/src/lib/components/CollectionLink.svelte b/frontend/src/lib/components/CollectionLink.svelte new file mode 100644 index 0000000..aa99e7a --- /dev/null +++ b/frontend/src/lib/components/CollectionLink.svelte @@ -0,0 +1,58 @@ + + + + + + + diff --git a/frontend/src/routes/api/[...path]/+server.ts b/frontend/src/routes/api/[...path]/+server.ts index e6333ce..ca1b2a6 100644 --- a/frontend/src/routes/api/[...path]/+server.ts +++ b/frontend/src/routes/api/[...path]/+server.ts @@ -12,11 +12,23 @@ export async function POST({ url, params, request, fetch, cookies }) { return handleRequest(url, params, request, fetch, cookies); } +export async function PATCH({ url, params, request, fetch, cookies }) { + return handleRequest(url, params, request, fetch, cookies); +} + +export async function PUT({ url, params, request, fetch, cookies }) { + return handleRequest(url, params, request, fetch, cookies); +} + +export async function DELETE({ url, params, request, fetch, cookies }) { + return handleRequest(url, params, request, fetch, cookies); +} + // Implement other HTTP methods as needed (PUT, DELETE, etc.) async function handleRequest(url: any, params: any, request: any, fetch: any, cookies: any) { const path = params.path; - const targetUrl = `${endpoint}/api/${path}${url.search}&format=json`; + const targetUrl = `${endpoint}/api/${path}${url.search}/`; const headers = new Headers(request.headers);