UI changes and updates, collection page refresh
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
view: 'dayGridMonth',
|
||||
events: [...dates]
|
||||
};
|
||||
console.log(dates);
|
||||
</script>
|
||||
|
||||
<h1 class="text-center text-2xl font-bold">{$t('adventures.adventure_calendar')}</h1>
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
import { enhance } from '$app/forms';
|
||||
import { goto } from '$app/navigation';
|
||||
import CollectionCard from '$lib/components/CollectionCard.svelte';
|
||||
import EditCollection from '$lib/components/EditCollection.svelte';
|
||||
import NewCollection from '$lib/components/NewCollection.svelte';
|
||||
import CollectionModal from '$lib/components/CollectionModal.svelte';
|
||||
import NotFound from '$lib/components/NotFound.svelte';
|
||||
import type { Collection } from '$lib/types';
|
||||
import { t } from 'svelte-i18n';
|
||||
@@ -17,10 +16,10 @@
|
||||
|
||||
let currentSort = { attribute: 'name', order: 'asc' };
|
||||
|
||||
let isShowingCreateModal: boolean = false;
|
||||
let newType: string = '';
|
||||
|
||||
let resultsPerPage: number = 25;
|
||||
let isShowingCollectionModal: boolean = false;
|
||||
|
||||
let next: string | null = data.props.next || null;
|
||||
let previous: string | null = data.props.previous || null;
|
||||
@@ -86,8 +85,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
let collectionToEdit: Collection;
|
||||
let isEditModalOpen: boolean = false;
|
||||
let collectionToEdit: Collection | null = null;
|
||||
|
||||
function deleteAdventure(event: CustomEvent<string>) {
|
||||
collections = collections.filter((adventure) => adventure.id !== event.detail);
|
||||
@@ -95,12 +93,12 @@
|
||||
|
||||
function createAdventure(event: CustomEvent<Collection>) {
|
||||
collections = [event.detail, ...collections];
|
||||
isShowingCreateModal = false;
|
||||
isShowingCollectionModal = false;
|
||||
}
|
||||
|
||||
function editCollection(event: CustomEvent<Collection>) {
|
||||
collectionToEdit = event.detail;
|
||||
isEditModalOpen = true;
|
||||
isShowingCollectionModal = true;
|
||||
}
|
||||
|
||||
function saveEdit(event: CustomEvent<Collection>) {
|
||||
@@ -110,7 +108,7 @@
|
||||
}
|
||||
return adventure;
|
||||
});
|
||||
isEditModalOpen = false;
|
||||
isShowingCollectionModal = false;
|
||||
}
|
||||
|
||||
let sidebarOpen = false;
|
||||
@@ -120,18 +118,13 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if isShowingCreateModal}
|
||||
<NewCollection on:create={createAdventure} on:close={() => (isShowingCreateModal = false)} />
|
||||
{/if}
|
||||
|
||||
{#if isEditModalOpen}
|
||||
<EditCollection
|
||||
{#if isShowingCollectionModal}
|
||||
<CollectionModal
|
||||
{collectionToEdit}
|
||||
on:close={() => (isEditModalOpen = false)}
|
||||
on:close={() => (isShowingCollectionModal = false)}
|
||||
on:saveEdit={saveEdit}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<div class="fixed bottom-4 right-4 z-[999]">
|
||||
<div class="flex flex-row items-center justify-center gap-4">
|
||||
<div class="dropdown dropdown-top dropdown-end">
|
||||
@@ -147,17 +140,13 @@
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
on:click={() => {
|
||||
isShowingCreateModal = true;
|
||||
collectionToEdit = null;
|
||||
isShowingCollectionModal = true;
|
||||
newType = 'visited';
|
||||
}}
|
||||
>
|
||||
{$t(`adventures.collection`)}</button
|
||||
>
|
||||
|
||||
<!-- <button
|
||||
class="btn btn-primary"
|
||||
on:click={() => (isShowingNewTrip = true)}>Trip Planner</button
|
||||
> -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,10 +2,17 @@
|
||||
import type { Adventure, Checklist, Collection, Note, Transportation } from '$lib/types';
|
||||
import { onMount } from 'svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { goto } from '$app/navigation';
|
||||
import Lost from '$lib/assets/undraw_lost.svg';
|
||||
import { marked } from 'marked'; // Import the markdown parser
|
||||
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
// @ts-ignore
|
||||
import Calendar from '@event-calendar/core';
|
||||
// @ts-ignore
|
||||
import TimeGrid from '@event-calendar/time-grid';
|
||||
// @ts-ignore
|
||||
import DayGrid from '@event-calendar/day-grid';
|
||||
|
||||
import Plus from '~icons/mdi/plus';
|
||||
import AdventureCard from '$lib/components/AdventureCard.svelte';
|
||||
import AdventureLink from '$lib/components/AdventureLink.svelte';
|
||||
@@ -29,8 +36,58 @@
|
||||
export let data: PageData;
|
||||
console.log(data);
|
||||
|
||||
const renderMarkdown = (markdown: string) => {
|
||||
return marked(markdown);
|
||||
};
|
||||
|
||||
let collection: Collection;
|
||||
|
||||
// add christmas and new years
|
||||
// dates = Array.from({ length: 25 }, (_, i) => {
|
||||
// const date = new Date();
|
||||
// date.setMonth(11);
|
||||
// date.setDate(i + 1);
|
||||
// return {
|
||||
// id: i.toString(),
|
||||
// start: date.toISOString(),
|
||||
// end: date.toISOString(),
|
||||
// title: '🎄'
|
||||
// };
|
||||
// });
|
||||
|
||||
let dates: Array<{
|
||||
id: string;
|
||||
start: string;
|
||||
end: string;
|
||||
title: string;
|
||||
backgroundColor?: string;
|
||||
}> = [];
|
||||
|
||||
// Initialize calendar plugins and options
|
||||
let plugins = [TimeGrid, DayGrid];
|
||||
let options = {
|
||||
view: 'dayGridMonth',
|
||||
events: dates // Assign `dates` reactively
|
||||
};
|
||||
|
||||
// Compute `dates` array reactively
|
||||
$: {
|
||||
if (adventures) {
|
||||
dates = adventures.flatMap((adventure) =>
|
||||
adventure.visits.map((visit) => ({
|
||||
id: adventure.id,
|
||||
start: visit.start_date, // Convert to ISO format if needed
|
||||
end: visit.end_date || visit.start_date,
|
||||
title: adventure.name + (adventure.category?.icon ? ' ' + adventure.category.icon : '')
|
||||
}))
|
||||
);
|
||||
}
|
||||
// Update `options.events` when `dates` changes
|
||||
options = { ...options, events: dates };
|
||||
}
|
||||
|
||||
let currentView: string = 'itinerary';
|
||||
|
||||
let adventures: Adventure[] = [];
|
||||
|
||||
let numVisited: number = 0;
|
||||
@@ -364,9 +421,20 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if collection.description}
|
||||
<p class="text-center text-lg mb-2">{collection.description}</p>
|
||||
{#if collection && !collection.start_date && adventures.length == 0 && transportations.length == 0 && notes.length == 0 && checklists.length == 0}
|
||||
<NotFound error={undefined} />
|
||||
{/if}
|
||||
|
||||
{#if collection.description}
|
||||
<div class="flex justify-center mt-4">
|
||||
<article
|
||||
class="prose overflow-auto h-96 max-w-full p-4 border border-base-300 rounded-lg bg-base-300"
|
||||
>
|
||||
{@html renderMarkdown(collection.description)}
|
||||
</article>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if adventures.length > 0}
|
||||
<div class="flex items-center justify-center mb-4">
|
||||
<div class="stats shadow bg-base-300">
|
||||
@@ -383,269 +451,323 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if adventures.length > 0}
|
||||
<h1 class="text-center font-bold text-4xl mt-4 mb-2">{$t('adventures.linked_adventures')}</h1>
|
||||
|
||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||
{#each adventures as adventure}
|
||||
<AdventureCard
|
||||
user={data.user}
|
||||
on:edit={editAdventure}
|
||||
on:delete={deleteAdventure}
|
||||
{adventure}
|
||||
{collection}
|
||||
/>
|
||||
{/each}
|
||||
<div class="flex justify-center mx-auto">
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
<div role="tablist" class="tabs tabs-boxed tabs-lg max-w-xl">
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
<a
|
||||
role="tab"
|
||||
class="tab {currentView === 'itinerary' ? 'tab-active' : ''}"
|
||||
tabindex="0"
|
||||
on:click={() => (currentView = 'itinerary')}
|
||||
on:keydown={(e) => e.key === 'Enter' && (currentView = 'itinerary')}>Itinerary</a
|
||||
>
|
||||
<a
|
||||
role="tab"
|
||||
class="tab {currentView === 'all' ? 'tab-active' : ''}"
|
||||
tabindex="0"
|
||||
on:click={() => (currentView = 'all')}
|
||||
on:keydown={(e) => e.key === 'Enter' && (currentView = 'all')}>All Linked Items</a
|
||||
>
|
||||
<a
|
||||
role="tab"
|
||||
class="tab {currentView === 'calendar' ? 'tab-active' : ''}"
|
||||
tabindex="0"
|
||||
on:click={() => (currentView = 'calendar')}
|
||||
on:keydown={(e) => e.key === 'Enter' && (currentView = 'calendar')}>Calendar</a
|
||||
>
|
||||
<a
|
||||
role="tab"
|
||||
class="tab {currentView === 'map' ? 'tab-active' : ''}"
|
||||
tabindex="0"
|
||||
on:click={() => (currentView = 'map')}
|
||||
on:keydown={(e) => e.key === 'Enter' && (currentView = 'map')}>Map</a
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if transportations.length > 0}
|
||||
<h1 class="text-center font-bold text-4xl mt-4 mb-4">{$t('adventures.transportations')}</h1>
|
||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||
{#each transportations as transportation}
|
||||
<TransportationCard
|
||||
{transportation}
|
||||
user={data?.user}
|
||||
on:delete={(event) => {
|
||||
transportations = transportations.filter((t) => t.id != event.detail);
|
||||
}}
|
||||
on:edit={editTransportation}
|
||||
{collection}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if currentView == 'all'}
|
||||
{#if adventures.length > 0}
|
||||
<h1 class="text-center font-bold text-4xl mt-4 mb-2">{$t('adventures.linked_adventures')}</h1>
|
||||
|
||||
{#if notes.length > 0}
|
||||
<h1 class="text-center font-bold text-4xl mt-4 mb-4">{$t('adventures.notes')}</h1>
|
||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||
{#each notes as note}
|
||||
<NoteCard
|
||||
{note}
|
||||
user={data.user || null}
|
||||
on:edit={(event) => {
|
||||
noteToEdit = event.detail;
|
||||
isNoteModalOpen = true;
|
||||
}}
|
||||
on:delete={(event) => {
|
||||
notes = notes.filter((n) => n.id != event.detail);
|
||||
}}
|
||||
{collection}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||
{#each adventures as adventure}
|
||||
<AdventureCard
|
||||
user={data.user}
|
||||
on:edit={editAdventure}
|
||||
on:delete={deleteAdventure}
|
||||
{adventure}
|
||||
{collection}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if checklists.length > 0}
|
||||
<h1 class="text-center font-bold text-4xl mt-4 mb-4">{$t('adventures.checklists')}</h1>
|
||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||
{#each checklists as checklist}
|
||||
<ChecklistCard
|
||||
{checklist}
|
||||
user={data.user || null}
|
||||
on:delete={(event) => {
|
||||
checklists = checklists.filter((n) => n.id != event.detail);
|
||||
}}
|
||||
on:edit={(event) => {
|
||||
checklistToEdit = event.detail;
|
||||
isShowingChecklistModal = true;
|
||||
}}
|
||||
{collection}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{#if transportations.length > 0}
|
||||
<h1 class="text-center font-bold text-4xl mt-4 mb-4">{$t('adventures.transportations')}</h1>
|
||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||
{#each transportations as transportation}
|
||||
<TransportationCard
|
||||
{transportation}
|
||||
user={data?.user}
|
||||
on:delete={(event) => {
|
||||
transportations = transportations.filter((t) => t.id != event.detail);
|
||||
}}
|
||||
on:edit={editTransportation}
|
||||
{collection}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if notes.length > 0}
|
||||
<h1 class="text-center font-bold text-4xl mt-4 mb-4">{$t('adventures.notes')}</h1>
|
||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||
{#each notes as note}
|
||||
<NoteCard
|
||||
{note}
|
||||
user={data.user || null}
|
||||
on:edit={(event) => {
|
||||
noteToEdit = event.detail;
|
||||
isNoteModalOpen = true;
|
||||
}}
|
||||
on:delete={(event) => {
|
||||
notes = notes.filter((n) => n.id != event.detail);
|
||||
}}
|
||||
{collection}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if checklists.length > 0}
|
||||
<h1 class="text-center font-bold text-4xl mt-4 mb-4">{$t('adventures.checklists')}</h1>
|
||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||
{#each checklists as checklist}
|
||||
<ChecklistCard
|
||||
{checklist}
|
||||
user={data.user || null}
|
||||
on:delete={(event) => {
|
||||
checklists = checklists.filter((n) => n.id != event.detail);
|
||||
}}
|
||||
on:edit={(event) => {
|
||||
checklistToEdit = event.detail;
|
||||
isShowingChecklistModal = true;
|
||||
}}
|
||||
{collection}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if collection.start_date && collection.end_date}
|
||||
<div class="hero bg-base-200 py-8 mt-8">
|
||||
<div class="hero-content text-center">
|
||||
<div class="max-w-md">
|
||||
<h1 class="text-5xl font-bold mb-4">{$t('adventures.itineary_by_date')}</h1>
|
||||
{#if numberOfDays}
|
||||
<p class="text-lg mb-2">
|
||||
{$t('adventures.duration')}:
|
||||
<span class="badge badge-primary">{numberOfDays} {$t('adventures.days')}</span>
|
||||
</p>
|
||||
{/if}
|
||||
<p class="text-lg">
|
||||
Dates: <span class="font-semibold"
|
||||
>{new Date(collection.start_date).toLocaleDateString(undefined, { timeZone: 'UTC' })} -
|
||||
{new Date(collection.end_date).toLocaleDateString(undefined, {
|
||||
timeZone: 'UTC'
|
||||
})}</span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container mx-auto px-4">
|
||||
{#each Array(numberOfDays) as _, i}
|
||||
{@const startDate = new Date(collection.start_date)}
|
||||
{@const tempDate = new Date(startDate.getTime())}
|
||||
{@const adjustedDate = new Date(tempDate.setUTCDate(tempDate.getUTCDate() + i))}
|
||||
{@const dateString = adjustedDate.toISOString().split('T')[0]}
|
||||
|
||||
{@const dayAdventures =
|
||||
groupAdventuresByDate(adventures, new Date(collection.start_date), numberOfDays)[
|
||||
dateString
|
||||
] || []}
|
||||
{@const dayTransportations =
|
||||
groupTransportationsByDate(
|
||||
transportations,
|
||||
new Date(collection.start_date),
|
||||
numberOfDays
|
||||
)[dateString] || []}
|
||||
{@const dayNotes =
|
||||
groupNotesByDate(notes, new Date(collection.start_date), numberOfDays)[dateString] || []}
|
||||
{@const dayChecklists =
|
||||
groupChecklistsByDate(checklists, new Date(collection.start_date), numberOfDays)[
|
||||
dateString
|
||||
] || []}
|
||||
|
||||
<div class="card bg-base-100 shadow-xl my-8">
|
||||
<div class="card-body bg-base-200">
|
||||
<h2 class="card-title text-3xl justify-center g">
|
||||
{$t('adventures.day')}
|
||||
{i + 1}
|
||||
<div class="badge badge-lg">
|
||||
{adjustedDate.toLocaleDateString(undefined, { timeZone: 'UTC' })}
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{#if dayAdventures.length > 0}
|
||||
{#each dayAdventures as adventure}
|
||||
<AdventureCard
|
||||
user={data.user}
|
||||
on:edit={editAdventure}
|
||||
on:delete={deleteAdventure}
|
||||
{adventure}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if dayTransportations.length > 0}
|
||||
{#each dayTransportations as transportation}
|
||||
<TransportationCard
|
||||
{transportation}
|
||||
user={data?.user}
|
||||
on:delete={(event) => {
|
||||
transportations = transportations.filter((t) => t.id != event.detail);
|
||||
}}
|
||||
on:edit={(event) => {
|
||||
transportationToEdit = event.detail;
|
||||
isShowingTransportationModal = true;
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if dayNotes.length > 0}
|
||||
{#each dayNotes as note}
|
||||
<NoteCard
|
||||
{note}
|
||||
user={data.user || null}
|
||||
on:edit={(event) => {
|
||||
noteToEdit = event.detail;
|
||||
isNoteModalOpen = true;
|
||||
}}
|
||||
on:delete={(event) => {
|
||||
notes = notes.filter((n) => n.id != event.detail);
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if dayChecklists.length > 0}
|
||||
{#each dayChecklists as checklist}
|
||||
<ChecklistCard
|
||||
{checklist}
|
||||
user={data.user || null}
|
||||
on:delete={(event) => {
|
||||
notes = notes.filter((n) => n.id != event.detail);
|
||||
}}
|
||||
on:edit={(event) => {
|
||||
checklistToEdit = event.detail;
|
||||
isShowingChecklistModal = true;
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if dayAdventures.length == 0 && dayTransportations.length == 0 && dayNotes.length == 0 && dayChecklists.length == 0}
|
||||
<p class="text-center text-lg mt-2 italic">{$t('adventures.nothing_planned')}</p>
|
||||
{#if currentView == 'itinerary'}
|
||||
<div class="hero bg-base-200 py-8 mt-8">
|
||||
<div class="hero-content text-center">
|
||||
<div class="max-w-md">
|
||||
<h1 class="text-5xl font-bold mb-4">{$t('adventures.itineary_by_date')}</h1>
|
||||
{#if numberOfDays}
|
||||
<p class="text-lg mb-2">
|
||||
{$t('adventures.duration')}:
|
||||
<span class="badge badge-primary">{numberOfDays} {$t('adventures.days')}</span>
|
||||
</p>
|
||||
{/if}
|
||||
<p class="text-lg">
|
||||
Dates: <span class="font-semibold"
|
||||
>{new Date(collection.start_date).toLocaleDateString(undefined, {
|
||||
timeZone: 'UTC'
|
||||
})} -
|
||||
{new Date(collection.end_date).toLocaleDateString(undefined, {
|
||||
timeZone: 'UTC'
|
||||
})}</span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="card bg-base-200 shadow-xl my-8 mx-auto w-10/12">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-3xl justify-center mb-4">Trip Map</h2>
|
||||
<MapLibre
|
||||
style="https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"
|
||||
class="aspect-[9/16] max-h-[70vh] sm:aspect-video sm:max-h-full w-full rounded-lg"
|
||||
standardControls
|
||||
>
|
||||
{#each adventures as adventure}
|
||||
{#if adventure.longitude && adventure.latitude}
|
||||
<DefaultMarker lngLat={{ lng: adventure.longitude, lat: adventure.latitude }}>
|
||||
<Popup openOn="click" offset={[0, -10]}>
|
||||
<div class="text-lg text-black font-bold">{adventure.name}</div>
|
||||
<p class="font-semibold text-black text-md">
|
||||
{adventure.category?.display_name + ' ' + adventure.category?.icon}
|
||||
</p>
|
||||
</Popup>
|
||||
</DefaultMarker>
|
||||
{/if}
|
||||
{/each}
|
||||
{#each transportations as transportation}
|
||||
{#if transportation.destination_latitude && transportation.destination_longitude}
|
||||
<Marker
|
||||
lngLat={{
|
||||
lng: transportation.destination_longitude,
|
||||
lat: transportation.destination_latitude
|
||||
}}
|
||||
class="grid h-8 w-8 place-items-center rounded-full border border-gray-200
|
||||
bg-red-300 text-black focus:outline-6 focus:outline-black"
|
||||
>
|
||||
<span class="text-xl">
|
||||
{getTransportationEmoji(transportation.type)}
|
||||
</span>
|
||||
<Popup openOn="click" offset={[0, -10]}>
|
||||
<div class="text-lg text-black font-bold">{transportation.name}</div>
|
||||
<p class="font-semibold text-black text-md">
|
||||
{transportation.type}
|
||||
</p>
|
||||
</Popup>
|
||||
</Marker>
|
||||
{/if}
|
||||
{#if transportation.origin_latitude && transportation.origin_longitude}
|
||||
<Marker
|
||||
lngLat={{
|
||||
lng: transportation.origin_longitude,
|
||||
lat: transportation.origin_latitude
|
||||
}}
|
||||
class="grid h-8 w-8 place-items-center rounded-full border border-gray-200
|
||||
bg-green-300 text-black focus:outline-6 focus:outline-black"
|
||||
>
|
||||
<span class="text-xl">
|
||||
{getTransportationEmoji(transportation.type)}
|
||||
</span>
|
||||
<Popup openOn="click" offset={[0, -10]}>
|
||||
<div class="text-lg text-black font-bold">{transportation.name}</div>
|
||||
<p class="font-semibold text-black text-md">
|
||||
{transportation.type}
|
||||
</p>
|
||||
</Popup>
|
||||
</Marker>
|
||||
{/if}
|
||||
{/each}
|
||||
</MapLibre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container mx-auto px-4">
|
||||
{#each Array(numberOfDays) as _, i}
|
||||
{@const startDate = new Date(collection.start_date)}
|
||||
{@const tempDate = new Date(startDate.getTime())}
|
||||
{@const adjustedDate = new Date(tempDate.setUTCDate(tempDate.getUTCDate() + i))}
|
||||
{@const dateString = adjustedDate.toISOString().split('T')[0]}
|
||||
|
||||
{@const dayAdventures =
|
||||
groupAdventuresByDate(adventures, new Date(collection.start_date), numberOfDays)[
|
||||
dateString
|
||||
] || []}
|
||||
{@const dayTransportations =
|
||||
groupTransportationsByDate(
|
||||
transportations,
|
||||
new Date(collection.start_date),
|
||||
numberOfDays
|
||||
)[dateString] || []}
|
||||
{@const dayNotes =
|
||||
groupNotesByDate(notes, new Date(collection.start_date), numberOfDays)[dateString] ||
|
||||
[]}
|
||||
{@const dayChecklists =
|
||||
groupChecklistsByDate(checklists, new Date(collection.start_date), numberOfDays)[
|
||||
dateString
|
||||
] || []}
|
||||
|
||||
<div class="card bg-base-100 shadow-xl my-8">
|
||||
<div class="card-body bg-base-200">
|
||||
<h2 class="card-title text-3xl justify-center g">
|
||||
{$t('adventures.day')}
|
||||
{i + 1}
|
||||
<div class="badge badge-lg">
|
||||
{adjustedDate.toLocaleDateString(undefined, { timeZone: 'UTC' })}
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{#if dayAdventures.length > 0}
|
||||
{#each dayAdventures as adventure}
|
||||
<AdventureCard
|
||||
user={data.user}
|
||||
on:edit={editAdventure}
|
||||
on:delete={deleteAdventure}
|
||||
{adventure}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if dayTransportations.length > 0}
|
||||
{#each dayTransportations as transportation}
|
||||
<TransportationCard
|
||||
{transportation}
|
||||
user={data?.user}
|
||||
on:delete={(event) => {
|
||||
transportations = transportations.filter((t) => t.id != event.detail);
|
||||
}}
|
||||
on:edit={(event) => {
|
||||
transportationToEdit = event.detail;
|
||||
isShowingTransportationModal = true;
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if dayNotes.length > 0}
|
||||
{#each dayNotes as note}
|
||||
<NoteCard
|
||||
{note}
|
||||
user={data.user || null}
|
||||
on:edit={(event) => {
|
||||
noteToEdit = event.detail;
|
||||
isNoteModalOpen = true;
|
||||
}}
|
||||
on:delete={(event) => {
|
||||
notes = notes.filter((n) => n.id != event.detail);
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if dayChecklists.length > 0}
|
||||
{#each dayChecklists as checklist}
|
||||
<ChecklistCard
|
||||
{checklist}
|
||||
user={data.user || null}
|
||||
on:delete={(event) => {
|
||||
notes = notes.filter((n) => n.id != event.detail);
|
||||
}}
|
||||
on:edit={(event) => {
|
||||
checklistToEdit = event.detail;
|
||||
isShowingChecklistModal = true;
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if dayAdventures.length == 0 && dayTransportations.length == 0 && dayNotes.length == 0 && dayChecklists.length == 0}
|
||||
<p class="text-center text-lg mt-2 italic">{$t('adventures.nothing_planned')}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if currentView == 'map'}
|
||||
<div class="card bg-base-200 shadow-xl my-8 mx-auto w-10/12">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-3xl justify-center mb-4">Trip Map</h2>
|
||||
<MapLibre
|
||||
style="https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"
|
||||
class="aspect-[9/16] max-h-[70vh] sm:aspect-video sm:max-h-full w-full rounded-lg"
|
||||
standardControls
|
||||
>
|
||||
{#each adventures as adventure}
|
||||
{#if adventure.longitude && adventure.latitude}
|
||||
<DefaultMarker lngLat={{ lng: adventure.longitude, lat: adventure.latitude }}>
|
||||
<Popup openOn="click" offset={[0, -10]}>
|
||||
<div class="text-lg text-black font-bold">{adventure.name}</div>
|
||||
<p class="font-semibold text-black text-md">
|
||||
{adventure.category?.display_name + ' ' + adventure.category?.icon}
|
||||
</p>
|
||||
</Popup>
|
||||
</DefaultMarker>
|
||||
{/if}
|
||||
{/each}
|
||||
{#each transportations as transportation}
|
||||
{#if transportation.destination_latitude && transportation.destination_longitude}
|
||||
<Marker
|
||||
lngLat={{
|
||||
lng: transportation.destination_longitude,
|
||||
lat: transportation.destination_latitude
|
||||
}}
|
||||
class="grid h-8 w-8 place-items-center rounded-full border border-gray-200
|
||||
bg-red-300 text-black focus:outline-6 focus:outline-black"
|
||||
>
|
||||
<span class="text-xl">
|
||||
{getTransportationEmoji(transportation.type)}
|
||||
</span>
|
||||
<Popup openOn="click" offset={[0, -10]}>
|
||||
<div class="text-lg text-black font-bold">{transportation.name}</div>
|
||||
<p class="font-semibold text-black text-md">
|
||||
{transportation.type}
|
||||
</p>
|
||||
</Popup>
|
||||
</Marker>
|
||||
{/if}
|
||||
{#if transportation.origin_latitude && transportation.origin_longitude}
|
||||
<Marker
|
||||
lngLat={{
|
||||
lng: transportation.origin_longitude,
|
||||
lat: transportation.origin_latitude
|
||||
}}
|
||||
class="grid h-8 w-8 place-items-center rounded-full border border-gray-200
|
||||
bg-green-300 text-black focus:outline-6 focus:outline-black"
|
||||
>
|
||||
<span class="text-xl">
|
||||
{getTransportationEmoji(transportation.type)}
|
||||
</span>
|
||||
<Popup openOn="click" offset={[0, -10]}>
|
||||
<div class="text-lg text-black font-bold">{transportation.name}</div>
|
||||
<p class="font-semibold text-black text-md">
|
||||
{transportation.type}
|
||||
</p>
|
||||
</Popup>
|
||||
</Marker>
|
||||
{/if}
|
||||
{/each}
|
||||
</MapLibre>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if currentView == 'calendar'}
|
||||
<div class="card bg-base-200 shadow-xl my-8 mx-auto w-10/12">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-3xl justify-center mb-4">
|
||||
{$t('adventures.adventure_calendar')}
|
||||
</h2>
|
||||
<Calendar {plugins} {options} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -11,82 +11,88 @@
|
||||
total_countries: number;
|
||||
} | null;
|
||||
|
||||
if (data.stats) {
|
||||
stats = data.stats;
|
||||
} else {
|
||||
stats = null;
|
||||
}
|
||||
console.log(stats);
|
||||
stats = data.stats || null;
|
||||
</script>
|
||||
|
||||
{#if data.user.profile_pic}
|
||||
<div class="avatar flex items-center justify-center">
|
||||
<div class="w-24 rounded">
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
<img src={data.user.profile_pic} class="w-24 rounded-full" />
|
||||
</div>
|
||||
<section class="min-h-screen bg-base-100 py-8 px-4">
|
||||
<div class="flex flex-col items-center">
|
||||
<!-- Profile Picture -->
|
||||
{#if data.user.profile_pic}
|
||||
<div class="avatar">
|
||||
<div
|
||||
class="w-24 rounded-full ring ring-primary ring-offset-base-100 ring-offset-2 shadow-md"
|
||||
>
|
||||
<img src={data.user.profile_pic} alt="Profile" />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- User Name -->
|
||||
{#if data.user && data.user.first_name && data.user.last_name}
|
||||
<h1 class="text-4xl font-bold text-primary mt-4">
|
||||
{data.user.first_name}
|
||||
{data.user.last_name}
|
||||
</h1>
|
||||
{/if}
|
||||
<p class="text-lg text-base-content mt-2">{data.user.username}</p>
|
||||
|
||||
<!-- Member Since -->
|
||||
{#if data.user && data.user.date_joined}
|
||||
<div class="mt-4 flex items-center text-center text-base-content">
|
||||
<p class="text-lg font-medium">{$t('profile.member_since')}</p>
|
||||
<div class="flex items-center ml-2">
|
||||
<iconify-icon icon="mdi:calendar" class="text-2xl text-primary"></iconify-icon>
|
||||
<p class="ml-2 text-lg">
|
||||
{new Date(data.user.date_joined).toLocaleDateString(undefined, { timeZone: 'UTC' })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if data.user && data.user.first_name && data.user.last_name}
|
||||
<h1 class="text-center text-4xl font-bold">
|
||||
{data.user.first_name}
|
||||
{data.user.last_name}
|
||||
</h1>
|
||||
{/if}
|
||||
<p class="text-center text-lg mt-2">{data.user.username}</p>
|
||||
<!-- Stats Section -->
|
||||
{#if stats}
|
||||
<div class="divider my-8"></div>
|
||||
|
||||
{#if data.user && data.user.date_joined}
|
||||
<p class="ml-1 text-lg text-center mt-4">{$t('profile.member_since')}</p>
|
||||
<div class="flex items-center justify-center text-center">
|
||||
<iconify-icon icon="mdi:calendar" class="text-2xl"></iconify-icon>
|
||||
<p class="ml-1 text-xl">
|
||||
{new Date(data.user.date_joined).toLocaleDateString(undefined, { timeZone: 'UTC' })}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
<h2 class="text-2xl font-bold text-center mb-6 text-primary">
|
||||
{$t('profile.user_stats')}
|
||||
</h2>
|
||||
|
||||
{#if stats}
|
||||
<!-- divider -->
|
||||
<div class="divider pr-8 pl-8"></div>
|
||||
|
||||
<h1 class="text-center text-2xl font-bold mt-8 mb-2">{$t('profile.user_stats')}</h1>
|
||||
|
||||
<div class="flex justify-center items-center">
|
||||
<div class="stats stats-vertical lg:stats-horizontal shadow bg-base-200">
|
||||
<div class="stat">
|
||||
<div class="stat-title">{$t('navbar.adventures')}</div>
|
||||
<div class="stat-value text-center">{stats.adventure_count}</div>
|
||||
<!-- <div class="stat-desc">Jan 1st - Feb 1st</div> -->
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-title">{$t('navbar.collections')}</div>
|
||||
<div class="stat-value text-center">{stats.trips_count}</div>
|
||||
<!-- <div class="stat-desc">↘︎ 90 (14%)</div> -->
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-title">{$t('profile.visited_countries')}</div>
|
||||
<div class="stat-value text-center">
|
||||
{Math.round((stats.country_count / stats.total_countries) * 100)}%
|
||||
<div class="flex justify-center">
|
||||
<div class="stats stats-vertical lg:stats-horizontal shadow bg-base-200">
|
||||
<div class="stat">
|
||||
<div class="stat-title">{$t('navbar.adventures')}</div>
|
||||
<div class="stat-value text-center">{stats.adventure_count}</div>
|
||||
</div>
|
||||
<div class="stat-desc">
|
||||
{stats.country_count}/{stats.total_countries}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-title">{$t('profile.visited_regions')}</div>
|
||||
<div class="stat-value text-center">
|
||||
{Math.round((stats.visited_region_count / stats.total_regions) * 100)}%
|
||||
<div class="stat">
|
||||
<div class="stat-title">{$t('navbar.collections')}</div>
|
||||
<div class="stat-value text-center">{stats.trips_count}</div>
|
||||
</div>
|
||||
<div class="stat-desc">
|
||||
{stats.visited_region_count}/{stats.total_regions}
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-title">{$t('profile.visited_countries')}</div>
|
||||
<div class="stat-value text-center">
|
||||
{Math.round((stats.country_count / stats.total_countries) * 100)}%
|
||||
</div>
|
||||
<div class="stat-desc text-center">
|
||||
{stats.country_count}/{stats.total_countries}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-title">{$t('profile.visited_regions')}</div>
|
||||
<div class="stat-value text-center">
|
||||
{Math.round((stats.visited_region_count / stats.total_regions) * 100)}%
|
||||
</div>
|
||||
<div class="stat-desc text-center">
|
||||
{stats.visited_region_count}/{stats.total_regions}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<svelte:head>
|
||||
<title>Profile | AdventureLog</title>
|
||||
|
||||
@@ -4,32 +4,47 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
</script>
|
||||
|
||||
<h1 class="text-center font-extrabold text-4xl mb-6">{$t('settings.reset_password')}</h1>
|
||||
<section class="flex flex-col items-center justify-center min-h-screen px-4 py-8 bg-base-100">
|
||||
<h1 class="text-4xl font-bold text-center mb-6 text-primary">{$t('settings.reset_password')}</h1>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<form method="post" action="?/forgotPassword" class="w-full max-w-xs" use:enhance>
|
||||
<label for="email">{$t('auth.email')}</label>
|
||||
<input
|
||||
name="email"
|
||||
type="email"
|
||||
id="email"
|
||||
class="block mb-2 input input-bordered w-full max-w-xs"
|
||||
/><br />
|
||||
<button class="py-2 px-4 btn btn-primary mr-2">{$t('settings.reset_password')}</button>
|
||||
{#if $page.form?.message}
|
||||
<div class="text-center text-error mt-4">
|
||||
{$t(`settings.${$page.form?.message}`)}
|
||||
<div class="w-full max-w-md p-6 shadow-lg rounded-lg bg-base-200">
|
||||
<form method="post" action="?/forgotPassword" class="flex flex-col space-y-4" use:enhance>
|
||||
<div class="form-control">
|
||||
<label for="email" class="label">
|
||||
<span class="label-text">{$t('auth.email')}</span>
|
||||
</label>
|
||||
<input
|
||||
name="email"
|
||||
type="email"
|
||||
id="email"
|
||||
placeholder="Enter your email"
|
||||
class="input input-bordered w-full"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $page.form?.success}
|
||||
<div class="text-center text-success mt-4">
|
||||
{$t('settings.possible_reset')}
|
||||
|
||||
<div class="form-control mt-4">
|
||||
<button type="submit" class="btn btn-primary w-full">
|
||||
{$t('settings.reset_password')}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{#if $page.form?.message}
|
||||
<div class="mt-4 text-center text-error">
|
||||
{$t(`settings.${$page.form?.message}`)}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $page.form?.success}
|
||||
<div class="mt-4 text-center text-success">
|
||||
{$t('settings.possible_reset')}
|
||||
</div>
|
||||
{/if}
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<svelte:head>
|
||||
<title>Forgot Password</title>
|
||||
<title>Reset Password</title>
|
||||
<meta name="description" content="Reset your password for AdventureLog." />
|
||||
</svelte:head>
|
||||
|
||||
@@ -1,53 +1,66 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import { page } from '$app/stores';
|
||||
import type { PageData } from '../../../$types';
|
||||
// import type { PageData } from '../../../$types';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: PageData;
|
||||
// export let data: PageData;
|
||||
</script>
|
||||
|
||||
<h1 class="text-center font-bold text-4xl mb-4">{$t('settings.change_password')}</h1>
|
||||
<section class="flex flex-col items-center justify-center min-h-screen px-4 py-8 bg-base-100">
|
||||
<h1 class="text-4xl font-bold text-center mb-6 text-primary">
|
||||
{$t('settings.change_password')}
|
||||
</h1>
|
||||
|
||||
<form method="POST" use:enhance class="flex flex-col items-center justify-center space-y-4">
|
||||
<div class="w-full max-w-xs">
|
||||
<label for="password" class="label">
|
||||
<span class="label-text">{$t('auth.new_password')}</span>
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
required
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
<div class="w-full max-w-md p-6 shadow-lg rounded-lg bg-base-200">
|
||||
<form method="POST" use:enhance class="flex flex-col space-y-6">
|
||||
<div class="form-control">
|
||||
<label for="password" class="label">
|
||||
<span class="label-text">{$t('auth.new_password')}</span>
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="Enter new password"
|
||||
required
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label for="confirm_password" class="label">
|
||||
<span class="label-text">{$t('auth.confirm_password')}</span>
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="confirm_password"
|
||||
name="confirm_password"
|
||||
placeholder="Confirm new password"
|
||||
required
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control mt-4">
|
||||
<button type="submit" class="btn btn-primary w-full">
|
||||
{$t('settings.reset_password')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if $page.form?.message}
|
||||
<div class="mt-4 text-center text-error">
|
||||
{$t($page.form?.message)}
|
||||
</div>
|
||||
{/if}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="w-full max-w-xs">
|
||||
<label for="confirm_password" class="label">
|
||||
<span class="label-text">{$t('auth.confirm_password')}</span>
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="confirm_password"
|
||||
name="confirm_password"
|
||||
required
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{$t('settings.reset_password')}
|
||||
</button>
|
||||
|
||||
{#if $page.form?.message}
|
||||
<div class="text-error">
|
||||
{$t($page.form?.message)}
|
||||
</div>
|
||||
{/if}
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<svelte:head>
|
||||
<title>Password Reset Confirm</title>
|
||||
<meta name="description" content="Confirm your password reset and make a new password." />
|
||||
<title>Change Password</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Confirm your password reset and create a new password for AdventureLog."
|
||||
/>
|
||||
</svelte:head>
|
||||
|
||||
@@ -5,10 +5,27 @@
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
{#if data.verified}
|
||||
<h1>{$t('settings.email_verified')}</h1>
|
||||
<p>{$t('settings.email_verified_success')}</p>
|
||||
{:else}
|
||||
<h1>{$t('settings.email_verified_error')}</h1>
|
||||
<p>{$t('settings.email_verified_erorr_desc')}</p>
|
||||
{/if}
|
||||
<section class="flex flex-col items-center justify-center min-h-screen px-4 py-8 bg-base-100">
|
||||
<div class="w-full max-w-lg p-6 shadow-lg rounded-lg bg-base-200 text-center">
|
||||
{#if data.verified}
|
||||
<h1 class="text-4xl font-bold text-success mb-4">
|
||||
{$t('settings.email_verified')}
|
||||
</h1>
|
||||
<p class="text-lg text-base-content">
|
||||
{$t('settings.email_verified_success')}
|
||||
</p>
|
||||
{:else}
|
||||
<h1 class="text-4xl font-bold text-error mb-4">
|
||||
{$t('settings.email_verified_error')}
|
||||
</h1>
|
||||
<p class="text-lg text-base-content">
|
||||
{$t('settings.email_verified_erorr_desc')}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<svelte:head>
|
||||
<title>Email Verification</title>
|
||||
<meta name="description" content="View your email verification status for AdventureLog." />
|
||||
</svelte:head>
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { PageServerLoad } from './$types';
|
||||
const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
|
||||
|
||||
export const load = (async (event) => {
|
||||
const id = event.params.id;
|
||||
const id = event.params.id.toUpperCase();
|
||||
|
||||
let regions: Region[] = [];
|
||||
let visitedRegions: VisitedRegion[] = [];
|
||||
|
||||
Reference in New Issue
Block a user