1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-23 14:59:36 +02:00

notes beta

This commit is contained in:
Sean Morley 2024-08-03 22:03:27 -04:00
parent 4f1ad09470
commit 3f900bc41a
7 changed files with 202 additions and 10 deletions

View file

@ -0,0 +1,18 @@
# Generated by Django 5.0.7 on 2024-08-04 02:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('adventures', '0015_alter_note_date'),
]
operations = [
migrations.AlterField(
model_name='note',
name='date',
field=models.DateTimeField(blank=True, null=True),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 5.0.7 on 2024-08-04 02:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('adventures', '0016_alter_note_date'),
]
operations = [
migrations.AlterField(
model_name='note',
name='date',
field=models.DateField(blank=True, null=True),
),
]

View file

@ -1,6 +1,6 @@
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from .views import AdventureViewSet, CollectionViewSet, StatsViewSet, GenerateDescription, ActivityTypesView, TransportationViewSet
from .views import AdventureViewSet, CollectionViewSet, NoteViewSet, StatsViewSet, GenerateDescription, ActivityTypesView, TransportationViewSet
router = DefaultRouter()
router.register(r'adventures', AdventureViewSet, basename='adventures')
@ -9,6 +9,7 @@ router.register(r'stats', StatsViewSet, basename='stats')
router.register(r'generate', GenerateDescription, basename='generate')
router.register(r'activity-types', ActivityTypesView, basename='activity-types')
router.register(r'transportations', TransportationViewSet, basename='transportations')
router.register(r'notes', NoteViewSet, basename='notes')
urlpatterns = [

View file

@ -6,7 +6,7 @@ from django.db.models.functions import Lower
from rest_framework.response import Response
from .models import Adventure, Collection, Transportation, Note
from worldtravel.models import VisitedRegion, Region, Country
from .serializers import AdventureSerializer, CollectionSerializer, TransportationSerializer
from .serializers import AdventureSerializer, CollectionSerializer, NoteSerializer, TransportationSerializer
from rest_framework.permissions import IsAuthenticated
from django.db.models import Q, Prefetch
from .permissions import IsOwnerOrReadOnly, IsPublicReadOnly
@ -431,7 +431,7 @@ class TransportationViewSet(viewsets.ModelViewSet):
# return error message if user is not authenticated on the root endpoint
def list(self, request, *args, **kwargs):
# Prevent listing all adventures
return Response({"detail": "Listing all adventures is not allowed."},
return Response({"detail": "Listing all transportations is not allowed."},
status=status.HTTP_403_FORBIDDEN)
@action(detail=False, methods=['get'])
@ -457,4 +457,37 @@ class TransportationViewSet(viewsets.ModelViewSet):
def perform_create(self, serializer):
serializer.save(user_id=self.request.user)
class NoteViewSet(viewsets.ModelViewSet):
queryset = Note.objects.all()
serializer_class = NoteSerializer
permission_classes = [IsAuthenticated]
filterset_fields = ['is_public', 'collection']
# return error message if user is not authenticated on the root endpoint
def list(self, request, *args, **kwargs):
# Prevent listing all adventures
return Response({"detail": "Listing all notes is not allowed."},
status=status.HTTP_403_FORBIDDEN)
@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 = Note.objects.filter(
Q(user_id=request.user.id)
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
def get_queryset(self):
"""
This view should return a list of all notes
for the currently authenticated user.
"""
user = self.request.user
return Note.objects.filter(user_id=user)
def perform_create(self, serializer):
serializer.save(user_id=self.request.user)

View file

@ -0,0 +1,32 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { addToast } from '$lib/toasts';
import type { Note } from '$lib/types';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
import Launch from '~icons/mdi/launch';
import FileDocumentEdit from '~icons/mdi/file-document-edit';
export let note: Note;
function editNote() {
dispatch('edit', note);
}
</script>
<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-primary-content shadow-xl overflow-hidden text-base-content"
>
<div class="card-body">
<h2 class="card-title overflow-ellipsis">{note.name}</h2>
<div class="card-actions justify-end">
<button class="btn btn-neutral mb-2" on:click={() => goto(`/notes/${note.id}`)}
><Launch class="w-6 h-6" />Open Details</button
>
<button class="btn btn-neutral mb-2" on:click={editNote}>
<FileDocumentEdit class="w-6 h-6" />Edit note
</button>
</div>
</div>
</div>

View file

@ -0,0 +1,75 @@
<script lang="ts">
import type { Note } from '$lib/types';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
import { onMount } from 'svelte';
let modal: HTMLDialogElement;
export let note: Note;
export let startDate: string | null = null;
export let endDate: string | null = null;
let initialName: string = note.name;
onMount(() => {
modal = document.getElementById('my_modal_1') as HTMLDialogElement;
if (modal) {
modal.showModal();
}
});
function close() {
dispatch('close');
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape') {
dispatch('close');
}
}
async function save() {}
</script>
<dialog id="my_modal_1" class="modal">
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div class="modal-box" role="dialog" on:keydown={handleKeydown} tabindex="0">
<h3 class="font-bold text-lg">Note Editor</h3>
{#if initialName !== note.name}
<p>Editing note {initialName}</p>
{/if}
<form>
<div class="form-control mb-2">
<label for="name">Name</label>
<input
type="text"
id="name"
class="input input-bordered w-full max-w-xs"
bind:value={note.name}
/>
</div>
<div class="form-control mb-2">
<label for="content">Date</label>
<input
type="date"
id="date"
name="date"
min={startDate || ''}
max={endDate || ''}
bind:value={note.date}
class="input input-bordered w-full max-w-xs mt-1"
/>
</div>
<div class="form-control mb-2">
<label for="content">Content</label>
<textarea id="content" class="textarea textarea-bordered" bind:value={note.content} rows="5"
></textarea>
</div>
<button class="btn btn-neutral" on:click={close}>Close</button>
<button class="btn btn-primary" on:click={save}>Save</button>
</form>
</div>
</dialog>

View file

@ -15,6 +15,8 @@
import TransportationCard from '$lib/components/TransportationCard.svelte';
import EditTransportation from '$lib/components/EditTransportation.svelte';
import NewTransportation from '$lib/components/NewTransportation.svelte';
import NoteCard from '$lib/components/NoteCard.svelte';
import NoteModal from '$lib/components/NoteModal.svelte';
export let data: PageData;
console.log(data);
@ -180,6 +182,8 @@
let transportationToEdit: Transportation;
let isEditModalOpen: boolean = false;
let isTransportationEditModalOpen: boolean = false;
let isNoteModalOpen: boolean = false;
let noteToEdit: Note;
let newType: string;
@ -239,6 +243,15 @@
/>
{/if}
{#if isNoteModalOpen}
<NoteModal
note={noteToEdit}
on:close={() => (isNoteModalOpen = false)}
startDate={collection.start_date}
endDate={collection.end_date}
/>
{/if}
{#if isShowingCreateModal}
<NewAdventure
type={newType}
@ -484,11 +497,13 @@
{#if notes.length > 0}
{#each notes as note}
{#if note.date && new Date(note.date).toISOString().split('T')[0] === dateString}
<div class="bg-base-300 p-4 rounded-lg w-full">
<p class="text-lg font-semibold">{note.name}</p>
<p class="text-md">{note.date}</p>
<p>{note.content}</p>
</div>
<NoteCard
{note}
on:edit={(event) => {
noteToEdit = event.detail;
isNoteModalOpen = true;
}}
/>
{/if}
{/each}
{/if}
@ -502,7 +517,7 @@
<MapLibre
style="https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
class="flex items-center self-center justify-center aspect-[9/16] max-h-[70vh] sm:aspect-video sm:max-h-full w-10/12"
class="flex items-center self-center justify-center aspect-[9/16] max-h-[70vh] sm:aspect-video sm:max-h-full w-10/12 mt-4"
standardControls
>
<!-- MapEvents gives you access to map events even from other components inside the map,