mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-07-19 12:59:36 +02:00
Transportation changes
This commit is contained in:
parent
2d4d98393c
commit
dd8999a45f
9 changed files with 150 additions and 52 deletions
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 5.0.8 on 2024-08-19 20:04
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('adventures', '0003_adventure_end_date'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='transportation',
|
||||||
|
name='end_date',
|
||||||
|
field=models.DateTimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -55,6 +55,10 @@ class Adventure(models.Model):
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
if self.date and self.end_date and self.date > self.end_date:
|
||||||
|
raise ValidationError('The start date must be before the end date. Start date: ' + str(self.date) + ' End date: ' + str(self.end_date))
|
||||||
|
if self.end_date and not self.date:
|
||||||
|
raise ValidationError('Adventures must have an end date. Adventure: ' + self.name)
|
||||||
if self.collection:
|
if self.collection:
|
||||||
if self.collection.is_public and not self.is_public:
|
if self.collection.is_public and not self.is_public:
|
||||||
raise ValidationError('Adventures associated with a public collection must be public. Collection: ' + self.trip.name + ' Adventure: ' + self.name)
|
raise ValidationError('Adventures associated with a public collection must be public. Collection: ' + self.trip.name + ' Adventure: ' + self.name)
|
||||||
|
@ -100,6 +104,7 @@ class Transportation(models.Model):
|
||||||
rating = models.FloatField(blank=True, null=True)
|
rating = models.FloatField(blank=True, null=True)
|
||||||
link = models.URLField(blank=True, null=True)
|
link = models.URLField(blank=True, null=True)
|
||||||
date = models.DateTimeField(blank=True, null=True)
|
date = models.DateTimeField(blank=True, null=True)
|
||||||
|
end_date = models.DateTimeField(blank=True, null=True)
|
||||||
flight_number = models.CharField(max_length=100, blank=True, null=True)
|
flight_number = models.CharField(max_length=100, blank=True, null=True)
|
||||||
from_location = models.CharField(max_length=200, blank=True, null=True)
|
from_location = models.CharField(max_length=200, blank=True, null=True)
|
||||||
to_location = models.CharField(max_length=200, blank=True, null=True)
|
to_location = models.CharField(max_length=200, blank=True, null=True)
|
||||||
|
@ -109,6 +114,11 @@ class Transportation(models.Model):
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
print(self.date)
|
||||||
|
if self.date and self.end_date and self.date > self.end_date:
|
||||||
|
raise ValidationError('The start date must be before the end date. Start date: ' + str(self.date) + ' End date: ' + str(self.end_date))
|
||||||
|
if self.end_date and self.date is 'null':
|
||||||
|
raise ValidationError('Transportations must have an end date. Transportation: ' + self.name)
|
||||||
if self.collection:
|
if self.collection:
|
||||||
if self.collection.is_public and not self.is_public:
|
if self.collection.is_public and not self.is_public:
|
||||||
raise ValidationError('Transportations associated with a public collection must be public. Collection: ' + self.collection.name + ' Transportation: ' + self.name)
|
raise ValidationError('Transportations associated with a public collection must be public. Collection: ' + self.collection.name + ' Transportation: ' + self.name)
|
||||||
|
|
|
@ -51,7 +51,7 @@ class TransportationSerializer(serializers.ModelSerializer):
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'user_id', 'type', 'name', 'description', 'rating',
|
'id', 'user_id', 'type', 'name', 'description', 'rating',
|
||||||
'link', 'date', 'flight_number', 'from_location', 'to_location',
|
'link', 'date', 'flight_number', 'from_location', 'to_location',
|
||||||
'is_public', 'collection', 'created_at', 'updated_at'
|
'is_public', 'collection', 'created_at', 'updated_at', 'end_date'
|
||||||
]
|
]
|
||||||
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id']
|
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id']
|
||||||
|
|
||||||
|
|
|
@ -383,7 +383,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="date">Date (or start date)</label><br />
|
<label for="date">{adventure.date ? 'Start Date' : 'Date'}</label><br />
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
id="date"
|
id="date"
|
||||||
|
@ -394,18 +394,20 @@
|
||||||
class="input input-bordered w-full"
|
class="input input-bordered w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
{#if adventure.date}
|
||||||
<label for="end_date">End Date</label><br />
|
<div>
|
||||||
<input
|
<label for="end_date">End Date</label><br />
|
||||||
type="date"
|
<input
|
||||||
id="end_date"
|
type="date"
|
||||||
name="end_date"
|
id="end_date"
|
||||||
min={startDate || ''}
|
name="end_date"
|
||||||
max={endDate || ''}
|
min={startDate || ''}
|
||||||
bind:value={adventure.end_date}
|
max={endDate || ''}
|
||||||
class="input input-bordered w-full"
|
bind:value={adventure.end_date}
|
||||||
/>
|
class="input input-bordered w-full"
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<div>
|
<div>
|
||||||
<!-- link -->
|
<!-- link -->
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -40,6 +40,9 @@
|
||||||
if (transportationToEdit.date) {
|
if (transportationToEdit.date) {
|
||||||
transportationToEdit.date = transportationToEdit.date.slice(0, 19);
|
transportationToEdit.date = transportationToEdit.date.slice(0, 19);
|
||||||
}
|
}
|
||||||
|
if (transportationToEdit.end_date) {
|
||||||
|
transportationToEdit.end_date = transportationToEdit.end_date.slice(0, 19);
|
||||||
|
}
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
dispatch('close');
|
dispatch('close');
|
||||||
|
@ -53,6 +56,20 @@
|
||||||
|
|
||||||
async function handleSubmit(event: Event) {
|
async function handleSubmit(event: Event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
// make sure end_date is not before start_date
|
||||||
|
if (
|
||||||
|
transportationToEdit.end_date &&
|
||||||
|
transportationToEdit.date &&
|
||||||
|
transportationToEdit.date > transportationToEdit.end_date
|
||||||
|
) {
|
||||||
|
addToast('error', 'End date cannot be before start date');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// make sure end_date has a start_date
|
||||||
|
if (transportationToEdit.end_date && !transportationToEdit.date) {
|
||||||
|
addToast('error', 'Please provide a start date');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const form = event.target as HTMLFormElement;
|
const form = event.target as HTMLFormElement;
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
|
|
||||||
|
@ -147,7 +164,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label for="start_date"
|
<label for="start_date"
|
||||||
>Date & Time <Calendar class="inline-block mb-1 w-6 h-6" /></label
|
>{transportationToEdit.date ? 'Start' : ''}Date & Time <Calendar
|
||||||
|
class="inline-block mb-1 w-6 h-6"
|
||||||
|
/></label
|
||||||
><br />
|
><br />
|
||||||
<input
|
<input
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
|
@ -159,6 +178,22 @@
|
||||||
class="input input-bordered w-full max-w-xs mt-1"
|
class="input input-bordered w-full max-w-xs mt-1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{#if transportationToEdit.date}
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="end_date"
|
||||||
|
>End Date & Time <Calendar class="inline-block mb-1 w-6 h-6" /></label
|
||||||
|
><br />
|
||||||
|
<input
|
||||||
|
type="datetime-local"
|
||||||
|
id="end_date"
|
||||||
|
name="end_date"
|
||||||
|
min={fullStartDate || ''}
|
||||||
|
max={fullEndDate || ''}
|
||||||
|
bind:value={transportationToEdit.end_date}
|
||||||
|
class="input input-bordered w-full max-w-xs mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label for="rating">Rating <Star class="inline-block mb-1 w-6 h-6" /></label><br />
|
<label for="rating">Rating <Star class="inline-block mb-1 w-6 h-6" /></label><br />
|
||||||
<input
|
<input
|
||||||
|
|
|
@ -72,6 +72,12 @@
|
||||||
const form = event.target as HTMLFormElement;
|
const form = event.target as HTMLFormElement;
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
|
|
||||||
|
// make sure there is a start date if there is an end date
|
||||||
|
if (formData.get('end_date') && !formData.get('date')) {
|
||||||
|
addToast('error', 'Please provide a start date');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch(`/api/transportations/`, {
|
const response = await fetch(`/api/transportations/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
|
@ -168,7 +174,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label for="start_date"
|
<label for="start_date"
|
||||||
>Date & Time <Calendar class="inline-block mb-1 w-6 h-6" /></label
|
>Start Date & Time <Calendar class="inline-block mb-1 w-6 h-6" /></label
|
||||||
><br />
|
><br />
|
||||||
<input
|
<input
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
|
@ -179,6 +185,21 @@
|
||||||
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"
|
||||||
|
>End Date & Time <Calendar class="inline-block mb-1 w-6 h-6" /></label
|
||||||
|
><br />
|
||||||
|
<input
|
||||||
|
type="datetime-local"
|
||||||
|
id="end_date"
|
||||||
|
name="end_date"
|
||||||
|
min={fullStartDate || ''}
|
||||||
|
max={fullEndDate || ''}
|
||||||
|
class="input input-bordered w-full max-w-xs mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label for="rating">Rating <Star class="inline-block mb-1 w-6 h-6" /></label><br />
|
<label for="rating">Rating <Star class="inline-block mb-1 w-6 h-6" /></label><br />
|
||||||
<input
|
<input
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
import { addToast } from '$lib/toasts';
|
import { addToast } from '$lib/toasts';
|
||||||
|
|
||||||
import Plus from '~icons/mdi/plus';
|
import Plus from '~icons/mdi/plus';
|
||||||
|
import ArrowDownThick from '~icons/mdi/arrow-down-thick';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
@ -39,19 +40,30 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="card min-w-max lg:w-96 md:w-80 sm:w-60 xs:w-40 bg-primary-content shadow-xl overflow-hidden text-base-content"
|
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 text-base-content"
|
||||||
>
|
>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title overflow-ellipsis">{transportation.name}</h2>
|
<h2 class="card-title overflow-ellipsis">{transportation.name}</h2>
|
||||||
<div class="badge badge-secondary">{transportation.type}</div>
|
<div class="badge badge-secondary">{transportation.type}</div>
|
||||||
{#if transportation.from_location && transportation.to_location}
|
<div>
|
||||||
<p class="text-sm">
|
{#if transportation.from_location}
|
||||||
{transportation.from_location} to {transportation.to_location}
|
<p class="break-words text-wrap">{transportation.from_location}</p>
|
||||||
</p>
|
{/if}
|
||||||
{/if}
|
{#if transportation.to_location}
|
||||||
{#if transportation.date}
|
<ArrowDownThick class="w-6 h-6" />
|
||||||
{new Date(transportation.date).toLocaleString(undefined, { timeZone: 'UTC' })}
|
<p class="break-words text-wrap">{transportation.to_location}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{#if transportation.date}
|
||||||
|
<p>{new Date(transportation.date).toLocaleString(undefined, { timeZone: 'UTC' })}</p>
|
||||||
|
{/if}
|
||||||
|
{#if transportation.end_date}
|
||||||
|
<ArrowDownThick class="w-6 h-6" />
|
||||||
|
<p>{new Date(transportation.end_date).toLocaleString(undefined, { timeZone: 'UTC' })}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if user?.pk === transportation.user_id}
|
{#if user?.pk === transportation.user_id}
|
||||||
<div class="card-actions justify-end">
|
<div class="card-actions justify-end">
|
||||||
<button on:click={deleteTransportation} class="btn btn-secondary"
|
<button on:click={deleteTransportation} class="btn btn-secondary"
|
||||||
|
|
|
@ -37,31 +37,6 @@ export async function exportData() {
|
||||||
visitedRegions
|
visitedRegions
|
||||||
};
|
};
|
||||||
|
|
||||||
async function convertImages() {
|
|
||||||
const promises = data.adventures.map(async (adventure, i) => {
|
|
||||||
if (adventure.image) {
|
|
||||||
const res = await fetch(adventure.image);
|
|
||||||
const blob = await res.blob();
|
|
||||||
const base64 = await blobToBase64(blob);
|
|
||||||
adventure.image = base64;
|
|
||||||
data.adventures[i].image = adventure.image;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(promises);
|
|
||||||
}
|
|
||||||
|
|
||||||
function blobToBase64(blob: Blob): Promise<string> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.readAsDataURL(blob);
|
|
||||||
reader.onloadend = () => resolve(reader.result as string);
|
|
||||||
reader.onerror = (error) => reject(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await convertImages();
|
|
||||||
|
|
||||||
const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
|
const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
|
||||||
return URL.createObjectURL(blob);
|
return URL.createObjectURL(blob);
|
||||||
}
|
}
|
||||||
|
@ -92,7 +67,19 @@ export function groupAdventuresByDate(
|
||||||
adventures.forEach((adventure) => {
|
adventures.forEach((adventure) => {
|
||||||
if (adventure.date) {
|
if (adventure.date) {
|
||||||
const adventureDate = new Date(adventure.date).toISOString().split('T')[0];
|
const adventureDate = new Date(adventure.date).toISOString().split('T')[0];
|
||||||
if (groupedAdventures[adventureDate]) {
|
if (adventure.end_date) {
|
||||||
|
const endDate = new Date(adventure.end_date).toISOString().split('T')[0];
|
||||||
|
const currentDate = new Date(startDate);
|
||||||
|
for (let i = 0; i < numberOfDays; i++) {
|
||||||
|
currentDate.setDate(startDate.getDate() + i);
|
||||||
|
const dateString = currentDate.toISOString().split('T')[0];
|
||||||
|
if (dateString >= adventureDate && dateString <= endDate) {
|
||||||
|
if (groupedAdventures[dateString]) {
|
||||||
|
groupedAdventures[dateString].push(adventure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (groupedAdventures[adventureDate]) {
|
||||||
groupedAdventures[adventureDate].push(adventure);
|
groupedAdventures[adventureDate].push(adventure);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,7 +105,19 @@ export function groupTransportationsByDate(
|
||||||
transportations.forEach((transportation) => {
|
transportations.forEach((transportation) => {
|
||||||
if (transportation.date) {
|
if (transportation.date) {
|
||||||
const transportationDate = new Date(transportation.date).toISOString().split('T')[0];
|
const transportationDate = new Date(transportation.date).toISOString().split('T')[0];
|
||||||
if (groupedTransportations[transportationDate]) {
|
if (transportation.end_date) {
|
||||||
|
const endDate = new Date(transportation.end_date).toISOString().split('T')[0];
|
||||||
|
const currentDate = new Date(startDate);
|
||||||
|
for (let i = 0; i < numberOfDays; i++) {
|
||||||
|
currentDate.setDate(startDate.getDate() + i);
|
||||||
|
const dateString = currentDate.toISOString().split('T')[0];
|
||||||
|
if (dateString >= transportationDate && dateString <= endDate) {
|
||||||
|
if (groupedTransportations[dateString]) {
|
||||||
|
groupedTransportations[dateString].push(transportation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (groupedTransportations[transportationDate]) {
|
||||||
groupedTransportations[transportationDate].push(transportation);
|
groupedTransportations[transportationDate].push(transportation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,6 +105,7 @@ export type Transportation = {
|
||||||
rating: number | null;
|
rating: number | null;
|
||||||
link: string | null;
|
link: string | null;
|
||||||
date: string | null; // ISO 8601 date string
|
date: string | null; // ISO 8601 date string
|
||||||
|
end_date: string | null; // ISO 8601 date string
|
||||||
flight_number: string | null;
|
flight_number: string | null;
|
||||||
from_location: string | null;
|
from_location: string | null;
|
||||||
to_location: string | null;
|
to_location: string | null;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue