2024-12-19 18:46:52 -05:00
|
|
|
<script lang="ts">
|
|
|
|
import { createEventDispatcher } from 'svelte';
|
|
|
|
import type { Collection, Transportation } from '$lib/types';
|
|
|
|
const dispatch = createEventDispatcher();
|
|
|
|
import { onMount } from 'svelte';
|
|
|
|
import { addToast } from '$lib/toasts';
|
|
|
|
let modal: HTMLDialogElement;
|
|
|
|
import { t } from 'svelte-i18n';
|
|
|
|
|
|
|
|
import MarkdownEditor from './MarkdownEditor.svelte';
|
|
|
|
import { appVersion } from '$lib/config';
|
|
|
|
import { DefaultMarker, MapLibre } from 'svelte-maplibre';
|
2025-05-09 10:24:29 -04:00
|
|
|
import DateRangeCollapse from './DateRangeCollapse.svelte';
|
2025-06-13 23:49:14 -04:00
|
|
|
import { getBasemapUrl } from '$lib';
|
2025-07-14 18:57:39 -04:00
|
|
|
import { deserialize } from '$app/forms';
|
|
|
|
|
|
|
|
import FileImage from '~icons/mdi/file-image';
|
|
|
|
import Star from '~icons/mdi/star';
|
|
|
|
import Crown from '~icons/mdi/crown';
|
2024-12-19 18:46:52 -05:00
|
|
|
|
|
|
|
export let collection: Collection;
|
|
|
|
export let transportationToEdit: Transportation | null = null;
|
|
|
|
|
2025-04-18 23:06:36 -04:00
|
|
|
// Initialize transportation object
|
2024-12-19 18:46:52 -05:00
|
|
|
let transportation: Transportation = {
|
|
|
|
id: transportationToEdit?.id || '',
|
|
|
|
type: transportationToEdit?.type || '',
|
|
|
|
name: transportationToEdit?.name || '',
|
|
|
|
description: transportationToEdit?.description || '',
|
2025-05-09 10:24:29 -04:00
|
|
|
date: transportationToEdit?.date || null,
|
|
|
|
end_date: transportationToEdit?.end_date || null,
|
2024-12-19 18:46:52 -05:00
|
|
|
rating: transportationToEdit?.rating || 0,
|
|
|
|
link: transportationToEdit?.link || '',
|
|
|
|
flight_number: transportationToEdit?.flight_number || '',
|
|
|
|
from_location: transportationToEdit?.from_location || '',
|
|
|
|
to_location: transportationToEdit?.to_location || '',
|
Rename Adventures to Locations (#696)
* Refactor user_id to user in adventures and related models, views, and components
- Updated all instances of user_id to user in the adventures app, including models, serializers, views, and frontend components.
- Adjusted queries and filters to reflect the new user field naming convention.
- Ensured consistency across the codebase for user identification in adventures, collections, notes, and transportation entities.
- Modified frontend components to align with the updated data structure, ensuring proper access control and rendering based on user ownership.
* Refactor adventure-related views and components to use "Location" terminology
- Updated GlobalSearchView to replace AdventureSerializer with LocationSerializer.
- Modified IcsCalendarGeneratorViewSet to use LocationSerializer instead of AdventureSerializer.
- Created new LocationImageViewSet for managing location images, including primary image toggling and image deletion.
- Introduced LocationViewSet for managing locations with enhanced filtering, sorting, and sharing capabilities.
- Updated ReverseGeocodeViewSet to utilize LocationSerializer.
- Added ActivityTypesView to retrieve distinct activity types from locations.
- Refactored user views to replace AdventureSerializer with LocationSerializer.
- Updated frontend components to reflect changes from "adventure" to "location", including AdventureCard, AdventureLink, AdventureModal, and others.
- Adjusted API endpoints in frontend routes to align with new location-based structure.
- Ensured all references to adventures are replaced with locations across the codebase.
* refactor: rename adventures to locations across the application
- Updated localization files to replace adventure-related terms with location-related terms.
- Refactored TypeScript types and variables from Adventure to Location in various routes and components.
- Adjusted UI elements and labels to reflect the change from adventures to locations.
- Ensured all references to adventures in the codebase are consistent with the new location terminology.
* Refactor code structure for improved readability and maintainability
* feat: Implement location details page with server-side loading and deletion functionality
- Added +page.server.ts to handle server-side loading of additional location info.
- Created +page.svelte for displaying location details, including images, visits, and maps.
- Integrated GPX file handling and rendering on the map.
- Updated map route to link to locations instead of adventures.
- Refactored profile and search routes to use LocationCard instead of AdventureCard.
* docs: Update terminology from "Adventure" to "Location" and enhance project overview
* docs: Clarify collection examples in usage documentation
* feat: Enable credentials for GPX file fetch and add CORS_ALLOW_CREDENTIALS setting
* Refactor adventure references to locations across the backend and frontend
- Updated CategoryViewSet to reflect location context instead of adventures.
- Modified ChecklistViewSet to include locations in retrieval logic.
- Changed GlobalSearchView to search for locations instead of adventures.
- Adjusted IcsCalendarGeneratorViewSet to handle locations instead of adventures.
- Refactored LocationImageViewSet to remove unused import.
- Updated LocationViewSet to clarify public access for locations.
- Changed LodgingViewSet to reference locations instead of adventures.
- Modified NoteViewSet to prevent listing all locations.
- Updated RecommendationsViewSet to handle locations in parsing and response.
- Adjusted ReverseGeocodeViewSet to search through user locations.
- Updated StatsViewSet to count locations instead of adventures.
- Changed TagsView to reflect activity types for locations.
- Updated TransportationViewSet to reference locations instead of adventures.
- Added new translations for search results related to locations in multiple languages.
- Updated dashboard and profile pages to reflect location counts instead of adventure counts.
- Adjusted search routes to handle locations instead of adventures.
* Update banner image
* style: Update stats component background and border for improved visibility
* refactor: Rename AdventureCard and AdventureModal to LocationCard and LocationModal for consistency
2025-06-25 11:49:34 -04:00
|
|
|
user: transportationToEdit?.user || '',
|
2024-12-19 18:46:52 -05:00
|
|
|
is_public: transportationToEdit?.is_public || false,
|
|
|
|
collection: transportationToEdit?.collection || collection.id,
|
|
|
|
created_at: transportationToEdit?.created_at || '',
|
|
|
|
updated_at: transportationToEdit?.updated_at || '',
|
|
|
|
origin_latitude: transportationToEdit?.origin_latitude || NaN,
|
|
|
|
origin_longitude: transportationToEdit?.origin_longitude || NaN,
|
|
|
|
destination_latitude: transportationToEdit?.destination_latitude || NaN,
|
2025-05-10 11:59:56 -04:00
|
|
|
destination_longitude: transportationToEdit?.destination_longitude || NaN,
|
|
|
|
start_timezone: transportationToEdit?.start_timezone || '',
|
2025-06-13 23:49:14 -04:00
|
|
|
end_timezone: transportationToEdit?.end_timezone || '',
|
2025-07-14 18:57:39 -04:00
|
|
|
distance: null,
|
|
|
|
images: transportationToEdit?.images || []
|
2024-12-19 18:46:52 -05:00
|
|
|
};
|
|
|
|
|
2025-05-10 22:03:31 -04:00
|
|
|
let startTimezone: string | undefined = transportation.start_timezone ?? undefined;
|
|
|
|
let endTimezone: string | undefined = transportation.end_timezone ?? undefined;
|
|
|
|
|
|
|
|
// Later, you should manually sync these back to `transportation` if needed
|
|
|
|
$: transportation.start_timezone = startTimezone ?? '';
|
|
|
|
$: transportation.end_timezone = endTimezone ?? '';
|
|
|
|
|
2024-12-19 18:46:52 -05:00
|
|
|
let starting_airport: string = '';
|
|
|
|
let ending_airport: string = '';
|
|
|
|
|
2025-07-14 18:57:39 -04:00
|
|
|
// hold image files so they can be uploaded later
|
|
|
|
let imageInput: HTMLInputElement;
|
|
|
|
let imageFiles: File[] = [];
|
|
|
|
|
2024-12-19 18:46:52 -05:00
|
|
|
$: {
|
|
|
|
if (!transportation.rating) {
|
|
|
|
transportation.rating = NaN;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-07-14 18:57:39 -04:00
|
|
|
function handleImageChange(event: Event) {
|
|
|
|
const target = event.target as HTMLInputElement;
|
|
|
|
if (target?.files) {
|
|
|
|
if (!transportation.id) {
|
|
|
|
imageFiles = Array.from(target.files);
|
|
|
|
console.log('Images ready for deferred upload:', imageFiles);
|
|
|
|
} else {
|
|
|
|
imageFiles = Array.from(target.files);
|
|
|
|
for (const file of imageFiles) {
|
|
|
|
uploadImage(file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function uploadImage(file: File) {
|
|
|
|
let formData = new FormData();
|
|
|
|
formData.append('image', file);
|
|
|
|
formData.append('object_id', transportation.id);
|
|
|
|
formData.append('content_type', 'transportation');
|
|
|
|
|
|
|
|
let res = await fetch(`/locations?/image`, {
|
|
|
|
method: 'POST',
|
|
|
|
body: formData
|
|
|
|
});
|
|
|
|
if (res.ok) {
|
|
|
|
let newData = deserialize(await res.text()) as { data: { id: string; image: string } };
|
|
|
|
let newImage = {
|
|
|
|
id: newData.data.id,
|
|
|
|
image: newData.data.image,
|
|
|
|
is_primary: false,
|
|
|
|
immich_id: null
|
|
|
|
};
|
|
|
|
transportation.images = [...(transportation.images || []), newImage];
|
|
|
|
addToast('success', $t('adventures.image_upload_success'));
|
|
|
|
} else {
|
|
|
|
addToast('error', $t('adventures.image_upload_error'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-19 18:46:52 -05:00
|
|
|
onMount(async () => {
|
|
|
|
modal = document.getElementById('my_modal_1') as HTMLDialogElement;
|
|
|
|
if (modal) {
|
|
|
|
modal.showModal();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
function close() {
|
|
|
|
dispatch('close');
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleKeydown(event: KeyboardEvent) {
|
|
|
|
if (event.key === 'Escape') {
|
|
|
|
close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-07-14 18:57:39 -04:00
|
|
|
async function removeImage(id: string) {
|
|
|
|
let res = await fetch(`/api/images/${id}/image_delete`, {
|
|
|
|
method: 'POST'
|
|
|
|
});
|
|
|
|
if (res.status === 204) {
|
|
|
|
transportation.images = transportation.images.filter((image) => image.id !== id);
|
|
|
|
addToast('success', $t('adventures.image_removed_success'));
|
|
|
|
} else {
|
|
|
|
addToast('error', $t('adventures.image_removed_error'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function makePrimaryImage(image_id: string) {
|
|
|
|
let res = await fetch(`/api/images/${image_id}/toggle_primary`, {
|
|
|
|
method: 'POST'
|
|
|
|
});
|
|
|
|
if (res.ok) {
|
|
|
|
transportation.images = transportation.images.map((image) => {
|
|
|
|
if (image.id === image_id) {
|
|
|
|
image.is_primary = true;
|
|
|
|
} else {
|
|
|
|
image.is_primary = false;
|
|
|
|
}
|
|
|
|
return image;
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
console.error('Error in makePrimaryImage:', res);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-19 18:46:52 -05:00
|
|
|
async function geocode(e: Event | null) {
|
2025-04-19 21:44:40 -04:00
|
|
|
// Geocoding logic unchanged
|
2024-12-19 18:46:52 -05:00
|
|
|
if (e) {
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
|
|
|
|
const fetchLocation = async (query: string) => {
|
2025-05-24 14:12:06 -04:00
|
|
|
let res = await fetch(`/api/reverse-geocode/search/?query=${query}`, {
|
2024-12-19 18:46:52 -05:00
|
|
|
headers: {
|
|
|
|
'User-Agent': `AdventureLog / ${appVersion} `
|
|
|
|
}
|
|
|
|
});
|
2025-02-22 10:37:22 -05:00
|
|
|
console.log(query);
|
2024-12-19 18:46:52 -05:00
|
|
|
let data = await res.json();
|
|
|
|
return data;
|
|
|
|
};
|
|
|
|
|
|
|
|
let startingData = null;
|
|
|
|
let endingData = null;
|
|
|
|
|
|
|
|
if (transportation.type == 'plane') {
|
|
|
|
if (!starting_airport || !ending_airport) {
|
|
|
|
alert($t('adventures.no_location'));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
startingData = await fetchLocation(starting_airport + ' Airport');
|
|
|
|
endingData = await fetchLocation(ending_airport + ' Airport');
|
|
|
|
} else {
|
|
|
|
if (!transportation.from_location || !transportation.to_location) {
|
|
|
|
alert($t('adventures.no_location'));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
startingData = await fetchLocation(transportation?.from_location || '');
|
|
|
|
endingData = await fetchLocation(transportation?.to_location || '');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (startingData.length === 0 || endingData.length === 0) {
|
|
|
|
alert($t('adventures.no_location_found'));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (transportation.type == 'plane') {
|
2024-12-19 21:44:31 -05:00
|
|
|
transportation.from_location =
|
|
|
|
startingData[0].name + ' (' + starting_airport.toUpperCase() + ')';
|
|
|
|
transportation.to_location = endingData[0].name + ' (' + ending_airport.toUpperCase() + ')';
|
2024-12-19 18:46:52 -05:00
|
|
|
} else {
|
|
|
|
transportation.from_location = startingData[0].display_name;
|
|
|
|
transportation.to_location = endingData[0].display_name;
|
|
|
|
}
|
|
|
|
transportation.origin_latitude = startingData[0].lat;
|
|
|
|
transportation.origin_longitude = startingData[0].lon;
|
|
|
|
transportation.destination_latitude = endingData[0].lat;
|
|
|
|
transportation.destination_longitude = endingData[0].lon;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function handleSubmit(event: Event) {
|
|
|
|
event.preventDefault();
|
|
|
|
console.log(transportation);
|
|
|
|
|
|
|
|
// If the user has entered airport codes, but not location names, fetch the location names
|
|
|
|
if (
|
|
|
|
starting_airport &&
|
|
|
|
ending_airport &&
|
|
|
|
(!transportation.from_location || !transportation.to_location)
|
|
|
|
) {
|
|
|
|
transportation.from_location = starting_airport;
|
|
|
|
transportation.to_location = ending_airport;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Round coordinates to 6 decimal places
|
|
|
|
if (transportation.origin_latitude) {
|
|
|
|
transportation.origin_latitude = Math.round(transportation.origin_latitude * 1e6) / 1e6;
|
|
|
|
}
|
|
|
|
if (transportation.origin_longitude) {
|
|
|
|
transportation.origin_longitude = Math.round(transportation.origin_longitude * 1e6) / 1e6;
|
|
|
|
}
|
|
|
|
if (transportation.destination_latitude) {
|
|
|
|
transportation.destination_latitude =
|
|
|
|
Math.round(transportation.destination_latitude * 1e6) / 1e6;
|
|
|
|
}
|
|
|
|
if (transportation.destination_longitude) {
|
|
|
|
transportation.destination_longitude =
|
|
|
|
Math.round(transportation.destination_longitude * 1e6) / 1e6;
|
|
|
|
}
|
|
|
|
|
2025-04-18 23:06:36 -04:00
|
|
|
// Use the stored UTC dates for submission
|
|
|
|
const submissionData = {
|
2025-05-09 10:24:29 -04:00
|
|
|
...transportation
|
2025-04-18 23:06:36 -04:00
|
|
|
};
|
2025-03-18 17:40:32 -04:00
|
|
|
|
2024-12-20 15:55:30 -05:00
|
|
|
if (transportation.type != 'plane') {
|
2025-04-18 23:06:36 -04:00
|
|
|
submissionData.flight_number = '';
|
2024-12-20 15:55:30 -05:00
|
|
|
}
|
|
|
|
|
2025-04-18 23:06:36 -04:00
|
|
|
if (submissionData.id === '') {
|
2024-12-19 18:46:52 -05:00
|
|
|
let res = await fetch('/api/transportations', {
|
|
|
|
method: 'POST',
|
|
|
|
headers: {
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
},
|
2025-04-18 23:06:36 -04:00
|
|
|
body: JSON.stringify(submissionData)
|
2024-12-19 18:46:52 -05:00
|
|
|
});
|
|
|
|
let data = await res.json();
|
|
|
|
if (data.id) {
|
|
|
|
transportation = data as Transportation;
|
2025-04-18 23:06:36 -04:00
|
|
|
|
Rename Adventures to Locations (#696)
* Refactor user_id to user in adventures and related models, views, and components
- Updated all instances of user_id to user in the adventures app, including models, serializers, views, and frontend components.
- Adjusted queries and filters to reflect the new user field naming convention.
- Ensured consistency across the codebase for user identification in adventures, collections, notes, and transportation entities.
- Modified frontend components to align with the updated data structure, ensuring proper access control and rendering based on user ownership.
* Refactor adventure-related views and components to use "Location" terminology
- Updated GlobalSearchView to replace AdventureSerializer with LocationSerializer.
- Modified IcsCalendarGeneratorViewSet to use LocationSerializer instead of AdventureSerializer.
- Created new LocationImageViewSet for managing location images, including primary image toggling and image deletion.
- Introduced LocationViewSet for managing locations with enhanced filtering, sorting, and sharing capabilities.
- Updated ReverseGeocodeViewSet to utilize LocationSerializer.
- Added ActivityTypesView to retrieve distinct activity types from locations.
- Refactored user views to replace AdventureSerializer with LocationSerializer.
- Updated frontend components to reflect changes from "adventure" to "location", including AdventureCard, AdventureLink, AdventureModal, and others.
- Adjusted API endpoints in frontend routes to align with new location-based structure.
- Ensured all references to adventures are replaced with locations across the codebase.
* refactor: rename adventures to locations across the application
- Updated localization files to replace adventure-related terms with location-related terms.
- Refactored TypeScript types and variables from Adventure to Location in various routes and components.
- Adjusted UI elements and labels to reflect the change from adventures to locations.
- Ensured all references to adventures in the codebase are consistent with the new location terminology.
* Refactor code structure for improved readability and maintainability
* feat: Implement location details page with server-side loading and deletion functionality
- Added +page.server.ts to handle server-side loading of additional location info.
- Created +page.svelte for displaying location details, including images, visits, and maps.
- Integrated GPX file handling and rendering on the map.
- Updated map route to link to locations instead of adventures.
- Refactored profile and search routes to use LocationCard instead of AdventureCard.
* docs: Update terminology from "Adventure" to "Location" and enhance project overview
* docs: Clarify collection examples in usage documentation
* feat: Enable credentials for GPX file fetch and add CORS_ALLOW_CREDENTIALS setting
* Refactor adventure references to locations across the backend and frontend
- Updated CategoryViewSet to reflect location context instead of adventures.
- Modified ChecklistViewSet to include locations in retrieval logic.
- Changed GlobalSearchView to search for locations instead of adventures.
- Adjusted IcsCalendarGeneratorViewSet to handle locations instead of adventures.
- Refactored LocationImageViewSet to remove unused import.
- Updated LocationViewSet to clarify public access for locations.
- Changed LodgingViewSet to reference locations instead of adventures.
- Modified NoteViewSet to prevent listing all locations.
- Updated RecommendationsViewSet to handle locations in parsing and response.
- Adjusted ReverseGeocodeViewSet to search through user locations.
- Updated StatsViewSet to count locations instead of adventures.
- Changed TagsView to reflect activity types for locations.
- Updated TransportationViewSet to reference locations instead of adventures.
- Added new translations for search results related to locations in multiple languages.
- Updated dashboard and profile pages to reflect location counts instead of adventure counts.
- Adjusted search routes to handle locations instead of adventures.
* Update banner image
* style: Update stats component background and border for improved visibility
* refactor: Rename AdventureCard and AdventureModal to LocationCard and LocationModal for consistency
2025-06-25 11:49:34 -04:00
|
|
|
addToast('success', $t('adventures.location_created'));
|
2025-07-14 18:57:39 -04:00
|
|
|
// Handle image uploads after transportation is created
|
|
|
|
for (const file of imageFiles) {
|
|
|
|
await uploadImage(file);
|
|
|
|
}
|
2024-12-19 18:46:52 -05:00
|
|
|
dispatch('save', transportation);
|
|
|
|
} else {
|
|
|
|
console.error(data);
|
Rename Adventures to Locations (#696)
* Refactor user_id to user in adventures and related models, views, and components
- Updated all instances of user_id to user in the adventures app, including models, serializers, views, and frontend components.
- Adjusted queries and filters to reflect the new user field naming convention.
- Ensured consistency across the codebase for user identification in adventures, collections, notes, and transportation entities.
- Modified frontend components to align with the updated data structure, ensuring proper access control and rendering based on user ownership.
* Refactor adventure-related views and components to use "Location" terminology
- Updated GlobalSearchView to replace AdventureSerializer with LocationSerializer.
- Modified IcsCalendarGeneratorViewSet to use LocationSerializer instead of AdventureSerializer.
- Created new LocationImageViewSet for managing location images, including primary image toggling and image deletion.
- Introduced LocationViewSet for managing locations with enhanced filtering, sorting, and sharing capabilities.
- Updated ReverseGeocodeViewSet to utilize LocationSerializer.
- Added ActivityTypesView to retrieve distinct activity types from locations.
- Refactored user views to replace AdventureSerializer with LocationSerializer.
- Updated frontend components to reflect changes from "adventure" to "location", including AdventureCard, AdventureLink, AdventureModal, and others.
- Adjusted API endpoints in frontend routes to align with new location-based structure.
- Ensured all references to adventures are replaced with locations across the codebase.
* refactor: rename adventures to locations across the application
- Updated localization files to replace adventure-related terms with location-related terms.
- Refactored TypeScript types and variables from Adventure to Location in various routes and components.
- Adjusted UI elements and labels to reflect the change from adventures to locations.
- Ensured all references to adventures in the codebase are consistent with the new location terminology.
* Refactor code structure for improved readability and maintainability
* feat: Implement location details page with server-side loading and deletion functionality
- Added +page.server.ts to handle server-side loading of additional location info.
- Created +page.svelte for displaying location details, including images, visits, and maps.
- Integrated GPX file handling and rendering on the map.
- Updated map route to link to locations instead of adventures.
- Refactored profile and search routes to use LocationCard instead of AdventureCard.
* docs: Update terminology from "Adventure" to "Location" and enhance project overview
* docs: Clarify collection examples in usage documentation
* feat: Enable credentials for GPX file fetch and add CORS_ALLOW_CREDENTIALS setting
* Refactor adventure references to locations across the backend and frontend
- Updated CategoryViewSet to reflect location context instead of adventures.
- Modified ChecklistViewSet to include locations in retrieval logic.
- Changed GlobalSearchView to search for locations instead of adventures.
- Adjusted IcsCalendarGeneratorViewSet to handle locations instead of adventures.
- Refactored LocationImageViewSet to remove unused import.
- Updated LocationViewSet to clarify public access for locations.
- Changed LodgingViewSet to reference locations instead of adventures.
- Modified NoteViewSet to prevent listing all locations.
- Updated RecommendationsViewSet to handle locations in parsing and response.
- Adjusted ReverseGeocodeViewSet to search through user locations.
- Updated StatsViewSet to count locations instead of adventures.
- Changed TagsView to reflect activity types for locations.
- Updated TransportationViewSet to reference locations instead of adventures.
- Added new translations for search results related to locations in multiple languages.
- Updated dashboard and profile pages to reflect location counts instead of adventure counts.
- Adjusted search routes to handle locations instead of adventures.
* Update banner image
* style: Update stats component background and border for improved visibility
* refactor: Rename AdventureCard and AdventureModal to LocationCard and LocationModal for consistency
2025-06-25 11:49:34 -04:00
|
|
|
addToast('error', $t('adventures.location_create_error'));
|
2024-12-19 18:46:52 -05:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let res = await fetch(`/api/transportations/${transportation.id}`, {
|
|
|
|
method: 'PATCH',
|
|
|
|
headers: {
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
},
|
2025-04-18 23:06:36 -04:00
|
|
|
body: JSON.stringify(submissionData)
|
2024-12-19 18:46:52 -05:00
|
|
|
});
|
|
|
|
let data = await res.json();
|
|
|
|
if (data.id) {
|
|
|
|
transportation = data as Transportation;
|
2025-04-18 23:06:36 -04:00
|
|
|
|
Rename Adventures to Locations (#696)
* Refactor user_id to user in adventures and related models, views, and components
- Updated all instances of user_id to user in the adventures app, including models, serializers, views, and frontend components.
- Adjusted queries and filters to reflect the new user field naming convention.
- Ensured consistency across the codebase for user identification in adventures, collections, notes, and transportation entities.
- Modified frontend components to align with the updated data structure, ensuring proper access control and rendering based on user ownership.
* Refactor adventure-related views and components to use "Location" terminology
- Updated GlobalSearchView to replace AdventureSerializer with LocationSerializer.
- Modified IcsCalendarGeneratorViewSet to use LocationSerializer instead of AdventureSerializer.
- Created new LocationImageViewSet for managing location images, including primary image toggling and image deletion.
- Introduced LocationViewSet for managing locations with enhanced filtering, sorting, and sharing capabilities.
- Updated ReverseGeocodeViewSet to utilize LocationSerializer.
- Added ActivityTypesView to retrieve distinct activity types from locations.
- Refactored user views to replace AdventureSerializer with LocationSerializer.
- Updated frontend components to reflect changes from "adventure" to "location", including AdventureCard, AdventureLink, AdventureModal, and others.
- Adjusted API endpoints in frontend routes to align with new location-based structure.
- Ensured all references to adventures are replaced with locations across the codebase.
* refactor: rename adventures to locations across the application
- Updated localization files to replace adventure-related terms with location-related terms.
- Refactored TypeScript types and variables from Adventure to Location in various routes and components.
- Adjusted UI elements and labels to reflect the change from adventures to locations.
- Ensured all references to adventures in the codebase are consistent with the new location terminology.
* Refactor code structure for improved readability and maintainability
* feat: Implement location details page with server-side loading and deletion functionality
- Added +page.server.ts to handle server-side loading of additional location info.
- Created +page.svelte for displaying location details, including images, visits, and maps.
- Integrated GPX file handling and rendering on the map.
- Updated map route to link to locations instead of adventures.
- Refactored profile and search routes to use LocationCard instead of AdventureCard.
* docs: Update terminology from "Adventure" to "Location" and enhance project overview
* docs: Clarify collection examples in usage documentation
* feat: Enable credentials for GPX file fetch and add CORS_ALLOW_CREDENTIALS setting
* Refactor adventure references to locations across the backend and frontend
- Updated CategoryViewSet to reflect location context instead of adventures.
- Modified ChecklistViewSet to include locations in retrieval logic.
- Changed GlobalSearchView to search for locations instead of adventures.
- Adjusted IcsCalendarGeneratorViewSet to handle locations instead of adventures.
- Refactored LocationImageViewSet to remove unused import.
- Updated LocationViewSet to clarify public access for locations.
- Changed LodgingViewSet to reference locations instead of adventures.
- Modified NoteViewSet to prevent listing all locations.
- Updated RecommendationsViewSet to handle locations in parsing and response.
- Adjusted ReverseGeocodeViewSet to search through user locations.
- Updated StatsViewSet to count locations instead of adventures.
- Changed TagsView to reflect activity types for locations.
- Updated TransportationViewSet to reference locations instead of adventures.
- Added new translations for search results related to locations in multiple languages.
- Updated dashboard and profile pages to reflect location counts instead of adventure counts.
- Adjusted search routes to handle locations instead of adventures.
* Update banner image
* style: Update stats component background and border for improved visibility
* refactor: Rename AdventureCard and AdventureModal to LocationCard and LocationModal for consistency
2025-06-25 11:49:34 -04:00
|
|
|
addToast('success', $t('adventures.location_updated'));
|
2024-12-19 18:46:52 -05:00
|
|
|
dispatch('save', transportation);
|
|
|
|
} else {
|
Rename Adventures to Locations (#696)
* Refactor user_id to user in adventures and related models, views, and components
- Updated all instances of user_id to user in the adventures app, including models, serializers, views, and frontend components.
- Adjusted queries and filters to reflect the new user field naming convention.
- Ensured consistency across the codebase for user identification in adventures, collections, notes, and transportation entities.
- Modified frontend components to align with the updated data structure, ensuring proper access control and rendering based on user ownership.
* Refactor adventure-related views and components to use "Location" terminology
- Updated GlobalSearchView to replace AdventureSerializer with LocationSerializer.
- Modified IcsCalendarGeneratorViewSet to use LocationSerializer instead of AdventureSerializer.
- Created new LocationImageViewSet for managing location images, including primary image toggling and image deletion.
- Introduced LocationViewSet for managing locations with enhanced filtering, sorting, and sharing capabilities.
- Updated ReverseGeocodeViewSet to utilize LocationSerializer.
- Added ActivityTypesView to retrieve distinct activity types from locations.
- Refactored user views to replace AdventureSerializer with LocationSerializer.
- Updated frontend components to reflect changes from "adventure" to "location", including AdventureCard, AdventureLink, AdventureModal, and others.
- Adjusted API endpoints in frontend routes to align with new location-based structure.
- Ensured all references to adventures are replaced with locations across the codebase.
* refactor: rename adventures to locations across the application
- Updated localization files to replace adventure-related terms with location-related terms.
- Refactored TypeScript types and variables from Adventure to Location in various routes and components.
- Adjusted UI elements and labels to reflect the change from adventures to locations.
- Ensured all references to adventures in the codebase are consistent with the new location terminology.
* Refactor code structure for improved readability and maintainability
* feat: Implement location details page with server-side loading and deletion functionality
- Added +page.server.ts to handle server-side loading of additional location info.
- Created +page.svelte for displaying location details, including images, visits, and maps.
- Integrated GPX file handling and rendering on the map.
- Updated map route to link to locations instead of adventures.
- Refactored profile and search routes to use LocationCard instead of AdventureCard.
* docs: Update terminology from "Adventure" to "Location" and enhance project overview
* docs: Clarify collection examples in usage documentation
* feat: Enable credentials for GPX file fetch and add CORS_ALLOW_CREDENTIALS setting
* Refactor adventure references to locations across the backend and frontend
- Updated CategoryViewSet to reflect location context instead of adventures.
- Modified ChecklistViewSet to include locations in retrieval logic.
- Changed GlobalSearchView to search for locations instead of adventures.
- Adjusted IcsCalendarGeneratorViewSet to handle locations instead of adventures.
- Refactored LocationImageViewSet to remove unused import.
- Updated LocationViewSet to clarify public access for locations.
- Changed LodgingViewSet to reference locations instead of adventures.
- Modified NoteViewSet to prevent listing all locations.
- Updated RecommendationsViewSet to handle locations in parsing and response.
- Adjusted ReverseGeocodeViewSet to search through user locations.
- Updated StatsViewSet to count locations instead of adventures.
- Changed TagsView to reflect activity types for locations.
- Updated TransportationViewSet to reference locations instead of adventures.
- Added new translations for search results related to locations in multiple languages.
- Updated dashboard and profile pages to reflect location counts instead of adventure counts.
- Adjusted search routes to handle locations instead of adventures.
* Update banner image
* style: Update stats component background and border for improved visibility
* refactor: Rename AdventureCard and AdventureModal to LocationCard and LocationModal for consistency
2025-06-25 11:49:34 -04:00
|
|
|
addToast('error', $t('adventures.location_update_error'));
|
2024-12-19 18:46:52 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
2025-07-14 18:57:39 -04:00
|
|
|
<dialog id="my_modal_1" class="modal backdrop-blur-sm">
|
2024-12-19 18:46:52 -05:00
|
|
|
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
|
|
|
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
2025-07-14 18:57:39 -04:00
|
|
|
<div
|
|
|
|
class="modal-box w-11/12 max-w-6xl bg-gradient-to-br from-base-100 via-base-100 to-base-200 border border-base-300 shadow-2xl"
|
|
|
|
role="dialog"
|
|
|
|
on:keydown={handleKeydown}
|
|
|
|
tabindex="0"
|
|
|
|
>
|
|
|
|
<!-- Header Section - Following adventurelog pattern -->
|
|
|
|
<div
|
|
|
|
class="top-0 z-10 bg-base-100/90 backdrop-blur-lg border-b border-base-300 -mx-6 -mt-6 px-6 py-4 mb-6"
|
|
|
|
>
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
<div class="flex items-center gap-3">
|
|
|
|
<div class="p-2 bg-primary/10 rounded-xl">
|
|
|
|
<svg class="w-8 h-8 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
<path
|
|
|
|
stroke-linecap="round"
|
|
|
|
stroke-linejoin="round"
|
|
|
|
stroke-width="2"
|
|
|
|
d="M13 9l3 3m0 0l-3 3m3-3H8m13 0a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
|
|
/>
|
|
|
|
</svg>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<h1 class="text-3xl font-bold text-primary bg-clip-text">
|
|
|
|
{transportationToEdit
|
|
|
|
? $t('transportation.edit_transportation')
|
|
|
|
: $t('transportation.new_transportation')}
|
|
|
|
</h1>
|
|
|
|
<p class="text-sm text-base-content/60">
|
|
|
|
{transportationToEdit
|
|
|
|
? $t('transportation.update_transportation_details')
|
|
|
|
: $t('transportation.create_new_transportation')}
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Close Button -->
|
|
|
|
<button class="btn btn-ghost btn-square" on:click={close}>
|
|
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
<path
|
|
|
|
stroke-linecap="round"
|
|
|
|
stroke-linejoin="round"
|
|
|
|
stroke-width="2"
|
|
|
|
d="M6 18L18 6M6 6l12 12"
|
|
|
|
/>
|
|
|
|
</svg>
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Main Content -->
|
|
|
|
<div class="px-2">
|
2024-12-19 18:46:52 -05:00
|
|
|
<form method="post" style="width: 100%;" on:submit={handleSubmit}>
|
|
|
|
<!-- Basic Information Section -->
|
2025-07-14 18:57:39 -04:00
|
|
|
<div
|
|
|
|
class="collapse collapse-plus bg-base-200/50 border border-base-300/50 mb-6 rounded-2xl overflow-hidden"
|
|
|
|
>
|
2024-12-19 18:46:52 -05:00
|
|
|
<input type="checkbox" checked />
|
2025-07-14 18:57:39 -04:00
|
|
|
<div
|
|
|
|
class="collapse-title text-xl font-semibold bg-gradient-to-r from-primary/10 to-primary/5"
|
|
|
|
>
|
|
|
|
<div class="flex items-center gap-3">
|
|
|
|
<div class="p-2 bg-primary/10 rounded-lg">
|
|
|
|
<svg
|
|
|
|
class="w-5 h-5 text-primary"
|
|
|
|
fill="none"
|
|
|
|
stroke="currentColor"
|
|
|
|
viewBox="0 0 24 24"
|
2024-12-19 18:46:52 -05:00
|
|
|
>
|
2025-07-14 18:57:39 -04:00
|
|
|
<path
|
|
|
|
stroke-linecap="round"
|
|
|
|
stroke-linejoin="round"
|
|
|
|
stroke-width="2"
|
|
|
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
|
|
/>
|
|
|
|
</svg>
|
2024-12-19 18:46:52 -05:00
|
|
|
</div>
|
2025-07-14 18:57:39 -04:00
|
|
|
{$t('adventures.basic_information')}
|
2024-12-19 18:46:52 -05:00
|
|
|
</div>
|
2025-07-14 18:57:39 -04:00
|
|
|
</div>
|
|
|
|
<div class="collapse-content bg-base-100/50 pt-4 p-6 space-y-3">
|
|
|
|
<!-- Dual Column Layout for Large Screens -->
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
|
|
<!-- Left Column -->
|
|
|
|
<div class="space-y-4">
|
|
|
|
<!-- Name Field -->
|
|
|
|
<div class="form-control">
|
|
|
|
<label class="label" for="name">
|
|
|
|
<span class="label-text font-medium"
|
|
|
|
>{$t('adventures.name')}<span class="text-error ml-1">*</span></span
|
|
|
|
>
|
|
|
|
</label>
|
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
id="name"
|
|
|
|
name="name"
|
|
|
|
bind:value={transportation.name}
|
|
|
|
class="input input-bordered w-full bg-base-100/80 focus:bg-base-100"
|
|
|
|
placeholder={$t('transportation.enter_transportation_name')}
|
|
|
|
required
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Type Selection -->
|
|
|
|
<div class="form-control">
|
|
|
|
<label class="label" for="type">
|
|
|
|
<span class="label-text font-medium"
|
|
|
|
>{$t('transportation.type')}<span class="text-error ml-1">*</span></span
|
|
|
|
>
|
|
|
|
</label>
|
|
|
|
<select
|
|
|
|
class="select select-bordered w-full bg-base-100/80 focus:bg-base-100"
|
|
|
|
name="type"
|
|
|
|
id="type"
|
|
|
|
bind:value={transportation.type}
|
2024-12-19 18:46:52 -05:00
|
|
|
>
|
2025-07-14 18:57:39 -04:00
|
|
|
<option disabled selected>{$t('transportation.select_type')}</option>
|
|
|
|
<option value="car">{$t('transportation.modes.car')}</option>
|
|
|
|
<option value="plane">{$t('transportation.modes.plane')}</option>
|
|
|
|
<option value="train">{$t('transportation.modes.train')}</option>
|
|
|
|
<option value="bus">{$t('transportation.modes.bus')}</option>
|
|
|
|
<option value="boat">{$t('transportation.modes.boat')}</option>
|
|
|
|
<option value="bike">{$t('transportation.modes.bike')}</option>
|
|
|
|
<option value="walking">{$t('transportation.modes.walking')}</option>
|
|
|
|
<option value="other">{$t('transportation.modes.other')}</option>
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Rating Field -->
|
|
|
|
<div class="form-control">
|
|
|
|
<label class="label" for="rating">
|
|
|
|
<span class="label-text font-medium">{$t('adventures.rating')}</span>
|
|
|
|
</label>
|
|
|
|
<input
|
|
|
|
type="number"
|
|
|
|
min="0"
|
|
|
|
max="5"
|
|
|
|
hidden
|
|
|
|
bind:value={transportation.rating}
|
|
|
|
id="rating"
|
|
|
|
name="rating"
|
|
|
|
class="input input-bordered w-full max-w-xs"
|
|
|
|
/>
|
|
|
|
<div
|
|
|
|
class="flex items-center gap-4 p-4 bg-base-100/80 border border-base-300 rounded-xl"
|
|
|
|
>
|
|
|
|
<div class="rating">
|
|
|
|
<input
|
|
|
|
type="radio"
|
|
|
|
name="rating-2"
|
|
|
|
class="rating-hidden"
|
|
|
|
checked={Number.isNaN(transportation.rating)}
|
|
|
|
/>
|
|
|
|
<input
|
|
|
|
type="radio"
|
|
|
|
name="rating-2"
|
|
|
|
class="mask mask-star-2 bg-warning"
|
|
|
|
on:click={() => (transportation.rating = 1)}
|
|
|
|
checked={transportation.rating === 1}
|
|
|
|
/>
|
|
|
|
<input
|
|
|
|
type="radio"
|
|
|
|
name="rating-2"
|
|
|
|
class="mask mask-star-2 bg-warning"
|
|
|
|
on:click={() => (transportation.rating = 2)}
|
|
|
|
checked={transportation.rating === 2}
|
|
|
|
/>
|
|
|
|
<input
|
|
|
|
type="radio"
|
|
|
|
name="rating-2"
|
|
|
|
class="mask mask-star-2 bg-warning"
|
|
|
|
on:click={() => (transportation.rating = 3)}
|
|
|
|
checked={transportation.rating === 3}
|
|
|
|
/>
|
|
|
|
<input
|
|
|
|
type="radio"
|
|
|
|
name="rating-2"
|
|
|
|
class="mask mask-star-2 bg-warning"
|
|
|
|
on:click={() => (transportation.rating = 4)}
|
|
|
|
checked={transportation.rating === 4}
|
|
|
|
/>
|
|
|
|
<input
|
|
|
|
type="radio"
|
|
|
|
name="rating-2"
|
|
|
|
class="mask mask-star-2 bg-warning"
|
|
|
|
on:click={() => (transportation.rating = 5)}
|
|
|
|
checked={transportation.rating === 5}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
{#if transportation.rating}
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
class="btn btn-error btn-sm"
|
|
|
|
on:click={() => (transportation.rating = NaN)}
|
|
|
|
>
|
|
|
|
{$t('adventures.remove')}
|
|
|
|
</button>
|
|
|
|
{/if}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Right Column -->
|
|
|
|
<div class="space-y-4">
|
|
|
|
<!-- Link Field -->
|
|
|
|
<div class="form-control">
|
|
|
|
<label class="label" for="link">
|
|
|
|
<span class="label-text font-medium">{$t('adventures.link')}</span>
|
|
|
|
</label>
|
|
|
|
<input
|
|
|
|
type="url"
|
|
|
|
id="link"
|
|
|
|
name="link"
|
|
|
|
bind:value={transportation.link}
|
|
|
|
class="input input-bordered w-full bg-base-100/80 focus:bg-base-100"
|
|
|
|
placeholder={$t('transportation.enter_link')}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Description Field -->
|
|
|
|
<div class="form-control">
|
|
|
|
<label class="label" for="description">
|
|
|
|
<span class="label-text font-medium">{$t('adventures.description')}</span>
|
|
|
|
</label>
|
|
|
|
<div class="bg-base-100/80 border border-base-300 rounded-xl p-2">
|
|
|
|
<MarkdownEditor bind:text={transportation.description} editor_height={'h-32'} />
|
|
|
|
</div>
|
|
|
|
</div>
|
2024-12-19 18:46:52 -05:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2024-12-19 19:07:23 -05:00
|
|
|
|
2025-07-14 18:57:39 -04:00
|
|
|
<!-- Date Range Section -->
|
|
|
|
|
2025-05-09 10:24:29 -04:00
|
|
|
<DateRangeCollapse
|
|
|
|
type="transportation"
|
|
|
|
bind:utcStartDate={transportation.date}
|
|
|
|
bind:utcEndDate={transportation.end_date}
|
2025-05-10 22:03:31 -04:00
|
|
|
bind:selectedStartTimezone={startTimezone}
|
|
|
|
bind:selectedEndTimezone={endTimezone}
|
2025-05-10 11:59:56 -04:00
|
|
|
{collection}
|
2025-05-09 10:24:29 -04:00
|
|
|
/>
|
2024-12-19 18:46:52 -05:00
|
|
|
|
2025-07-14 18:57:39 -04:00
|
|
|
<!-- Location/Flight Information Section -->
|
|
|
|
<div
|
|
|
|
class="collapse collapse-plus bg-base-200/50 border border-base-300/50 mb-6 rounded-2xl overflow-hidden"
|
|
|
|
>
|
2024-12-19 18:46:52 -05:00
|
|
|
<input type="checkbox" checked />
|
2025-07-14 18:57:39 -04:00
|
|
|
<div
|
|
|
|
class="collapse-title text-xl font-semibold bg-gradient-to-r from-primary/10 to-primary/5"
|
|
|
|
>
|
|
|
|
<div class="flex items-center gap-3">
|
|
|
|
<div class="p-2 bg-primary/10 rounded-lg">
|
|
|
|
<svg
|
|
|
|
class="w-5 h-5 text-primary"
|
|
|
|
fill="none"
|
|
|
|
stroke="currentColor"
|
|
|
|
viewBox="0 0 24 24"
|
|
|
|
>
|
|
|
|
{#if transportation?.type == 'plane'}
|
|
|
|
<path
|
|
|
|
stroke-linecap="round"
|
|
|
|
stroke-linejoin="round"
|
|
|
|
stroke-width="2"
|
|
|
|
d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"
|
|
|
|
/>
|
|
|
|
{:else}
|
|
|
|
<path
|
|
|
|
stroke-linecap="round"
|
|
|
|
stroke-linejoin="round"
|
|
|
|
stroke-width="2"
|
|
|
|
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
|
|
|
|
/>
|
|
|
|
<path
|
|
|
|
stroke-linecap="round"
|
|
|
|
stroke-linejoin="round"
|
|
|
|
stroke-width="2"
|
|
|
|
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
|
|
|
|
/>
|
|
|
|
{/if}
|
|
|
|
</svg>
|
|
|
|
</div>
|
|
|
|
{#if transportation?.type == 'plane'}
|
|
|
|
{$t('adventures.flight_information')}
|
|
|
|
{:else}
|
|
|
|
{$t('adventures.location_information')}
|
|
|
|
{/if}
|
|
|
|
</div>
|
2024-12-19 18:46:52 -05:00
|
|
|
</div>
|
|
|
|
|
2025-07-14 18:57:39 -04:00
|
|
|
<div class="collapse-content bg-base-100/50 pt-4 p-6">
|
2024-12-19 18:46:52 -05:00
|
|
|
{#if transportation?.type == 'plane'}
|
2025-07-14 18:57:39 -04:00
|
|
|
<!-- Flight-specific fields -->
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
|
|
|
<!-- Flight Number -->
|
|
|
|
<div class="form-control">
|
|
|
|
<label class="label" for="flight_number">
|
|
|
|
<span class="label-text font-medium">{$t('transportation.flight_number')}</span>
|
2024-12-19 18:46:52 -05:00
|
|
|
</label>
|
|
|
|
<input
|
|
|
|
type="text"
|
2025-07-14 18:57:39 -04:00
|
|
|
id="flight_number"
|
|
|
|
name="flight_number"
|
|
|
|
bind:value={transportation.flight_number}
|
|
|
|
class="input input-bordered w-full bg-base-100/80 focus:bg-base-100"
|
|
|
|
placeholder={$t('transportation.enter_flight_number')}
|
2024-12-19 18:46:52 -05:00
|
|
|
/>
|
2025-07-14 18:57:39 -04:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Airport Fields (if locations not set) -->
|
|
|
|
{#if !transportation.from_location || !transportation.to_location}
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
|
|
|
<div class="form-control">
|
|
|
|
<label class="label" for="starting_airport">
|
|
|
|
<span class="label-text font-medium">{$t('adventures.starting_airport')}</span
|
|
|
|
>
|
|
|
|
</label>
|
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
id="starting_airport"
|
|
|
|
bind:value={starting_airport}
|
|
|
|
name="starting_airport"
|
|
|
|
class="input input-bordered w-full bg-base-100/80 focus:bg-base-100"
|
|
|
|
placeholder={$t('transportation.starting_airport_desc')}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="form-control">
|
|
|
|
<label class="label" for="ending_airport">
|
|
|
|
<span class="label-text font-medium">{$t('adventures.ending_airport')}</span>
|
|
|
|
</label>
|
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
id="ending_airport"
|
|
|
|
bind:value={ending_airport}
|
|
|
|
name="ending_airport"
|
|
|
|
class="input input-bordered w-full bg-base-100/80 focus:bg-base-100"
|
|
|
|
placeholder={$t('transportation.ending_airport_desc')}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="flex justify-start mb-6">
|
|
|
|
<button type="button" class="btn btn-primary gap-2" on:click={geocode}>
|
|
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
<path
|
|
|
|
stroke-linecap="round"
|
|
|
|
stroke-linejoin="round"
|
|
|
|
stroke-width="2"
|
|
|
|
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
|
|
|
/>
|
|
|
|
</svg>
|
2025-02-22 10:37:22 -05:00
|
|
|
{$t('transportation.fetch_location_information')}
|
2024-12-19 18:46:52 -05:00
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
{/if}
|
2025-07-14 18:57:39 -04:00
|
|
|
{/if}
|
2024-12-19 18:46:52 -05:00
|
|
|
|
2025-07-14 18:57:39 -04:00
|
|
|
<!-- Location Fields (for all types or when flight locations are set) -->
|
|
|
|
{#if transportation?.type != 'plane' || (transportation.from_location && transportation.to_location)}
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
2024-12-19 18:46:52 -05:00
|
|
|
<!-- From Location -->
|
2025-07-14 18:57:39 -04:00
|
|
|
<div class="form-control">
|
|
|
|
<label class="label" for="from_location">
|
|
|
|
<span class="label-text font-medium">{$t('transportation.from_location')}</span>
|
2024-12-19 18:46:52 -05:00
|
|
|
</label>
|
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
id="from_location"
|
|
|
|
name="from_location"
|
|
|
|
bind:value={transportation.from_location}
|
2025-07-14 18:57:39 -04:00
|
|
|
class="input input-bordered w-full bg-base-100/80 focus:bg-base-100"
|
|
|
|
placeholder={$t('transportation.enter_from_location')}
|
2024-12-19 18:46:52 -05:00
|
|
|
/>
|
|
|
|
</div>
|
2025-07-14 18:57:39 -04:00
|
|
|
|
2024-12-19 18:46:52 -05:00
|
|
|
<!-- To Location -->
|
2025-07-14 18:57:39 -04:00
|
|
|
<div class="form-control">
|
|
|
|
<label class="label" for="to_location">
|
|
|
|
<span class="label-text font-medium">{$t('transportation.to_location')}</span>
|
2024-12-19 18:46:52 -05:00
|
|
|
</label>
|
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
id="to_location"
|
|
|
|
name="to_location"
|
|
|
|
bind:value={transportation.to_location}
|
2025-07-14 18:57:39 -04:00
|
|
|
class="input input-bordered w-full bg-base-100/80 focus:bg-base-100"
|
|
|
|
placeholder={$t('transportation.enter_to_location')}
|
2024-12-19 18:46:52 -05:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
2025-07-14 18:57:39 -04:00
|
|
|
|
|
|
|
{#if transportation?.type != 'plane'}
|
|
|
|
<div class="flex justify-start mb-6">
|
|
|
|
<button type="button" class="btn btn-primary gap-2" on:click={geocode}>
|
|
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
<path
|
|
|
|
stroke-linecap="round"
|
|
|
|
stroke-linejoin="round"
|
|
|
|
stroke-width="2"
|
|
|
|
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
|
|
|
/>
|
|
|
|
</svg>
|
|
|
|
{$t('transportation.fetch_location_information')}
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
{/if}
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
<!-- Map Section -->
|
|
|
|
<div class="bg-base-100/80 border border-base-300 rounded-xl p-4 mb-6">
|
2024-12-19 18:46:52 -05:00
|
|
|
<div class="mb-4">
|
2025-07-14 18:57:39 -04:00
|
|
|
<h4 class="font-semibold text-base-content flex items-center gap-2">
|
|
|
|
<svg
|
|
|
|
class="w-5 h-5 text-primary"
|
|
|
|
fill="none"
|
|
|
|
stroke="currentColor"
|
|
|
|
viewBox="0 0 24 24"
|
|
|
|
>
|
|
|
|
<path
|
|
|
|
stroke-linecap="round"
|
|
|
|
stroke-linejoin="round"
|
|
|
|
stroke-width="2"
|
|
|
|
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-1.447-.894L15 4m0 13V4m0 0L9 7"
|
|
|
|
/>
|
|
|
|
</svg>
|
|
|
|
{$t('adventures.route_map')}
|
|
|
|
</h4>
|
2024-12-19 18:46:52 -05:00
|
|
|
</div>
|
|
|
|
<MapLibre
|
2025-06-13 23:49:14 -04:00
|
|
|
style={getBasemapUrl()}
|
2024-12-19 21:44:31 -05:00
|
|
|
class="relative aspect-[9/16] max-h-[70vh] w-full sm:aspect-video sm:max-h-full rounded-lg"
|
2024-12-19 18:46:52 -05:00
|
|
|
standardControls
|
|
|
|
>
|
|
|
|
{#if transportation.origin_latitude && transportation.origin_longitude}
|
|
|
|
<DefaultMarker
|
|
|
|
lngLat={[transportation.origin_longitude, transportation.origin_latitude]}
|
|
|
|
/>
|
|
|
|
{/if}
|
|
|
|
{#if transportation.destination_latitude && transportation.destination_longitude}
|
|
|
|
<DefaultMarker
|
|
|
|
lngLat={[
|
|
|
|
transportation.destination_longitude,
|
|
|
|
transportation.destination_latitude
|
|
|
|
]}
|
|
|
|
/>
|
|
|
|
{/if}
|
|
|
|
</MapLibre>
|
|
|
|
</div>
|
2025-07-14 18:57:39 -04:00
|
|
|
|
|
|
|
<!-- Clear Location Button -->
|
2024-12-19 21:44:31 -05:00
|
|
|
{#if transportation.from_location || transportation.to_location}
|
2025-07-14 18:57:39 -04:00
|
|
|
<div class="flex justify-start">
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
class="btn btn-error btn-sm gap-2"
|
|
|
|
on:click={() => {
|
|
|
|
transportation.from_location = '';
|
|
|
|
transportation.to_location = '';
|
|
|
|
starting_airport = '';
|
|
|
|
ending_airport = '';
|
|
|
|
transportation.origin_latitude = NaN;
|
|
|
|
transportation.origin_longitude = NaN;
|
|
|
|
transportation.destination_latitude = NaN;
|
|
|
|
transportation.destination_longitude = NaN;
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
<path
|
|
|
|
stroke-linecap="round"
|
|
|
|
stroke-linejoin="round"
|
|
|
|
stroke-width="2"
|
|
|
|
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
|
|
|
/>
|
|
|
|
</svg>
|
|
|
|
{$t('adventures.clear_location')}
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
{/if}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- images section -->
|
|
|
|
<div
|
|
|
|
class="collapse collapse-plus bg-base-200/50 border border-base-300/50 mb-6 rounded-2xl overflow-hidden"
|
|
|
|
>
|
|
|
|
<input type="checkbox" checked />
|
|
|
|
<div
|
|
|
|
class="collapse-title text-xl font-semibold bg-gradient-to-r from-primary/10 to-primary/5"
|
|
|
|
>
|
|
|
|
<div class="flex items-center gap-3">
|
|
|
|
<div class="p-2 bg-primary/10 rounded-lg">
|
|
|
|
<FileImage class="w-5 h-5 text-primary" />
|
|
|
|
</div>
|
|
|
|
{$t('adventures.images')}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="collapse-content bg-base-100/50 pt-4 p-6">
|
|
|
|
<div class="form-control">
|
|
|
|
<label class="label" for="image">
|
|
|
|
<span class="label-text font-medium">{$t('adventures.upload_image')}</span>
|
|
|
|
</label>
|
|
|
|
<input
|
|
|
|
type="file"
|
|
|
|
id="image"
|
|
|
|
name="image"
|
|
|
|
accept="image/*"
|
|
|
|
multiple
|
|
|
|
bind:this={imageInput}
|
|
|
|
on:change={handleImageChange}
|
|
|
|
class="file-input file-input-bordered file-input-primary w-full bg-base-100/80 focus:bg-base-100"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<p class="text-sm text-base-content/60 mt-2">
|
|
|
|
{$t('adventures.image_upload_desc')}
|
|
|
|
</p>
|
|
|
|
{#if imageFiles.length > 0 && !transportation.id}
|
|
|
|
<div class="mt-4">
|
|
|
|
<h4 class="font-semibold text-base-content mb-2">
|
|
|
|
{$t('adventures.selected_images')}
|
|
|
|
</h4>
|
|
|
|
<ul class="list-disc pl-5 space-y-1">
|
|
|
|
{#each imageFiles as file}
|
|
|
|
<li>{file.name} ({Math.round(file.size / 1024)} KB)</li>
|
|
|
|
{/each}
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
{/if}
|
|
|
|
{#if transportation.id}
|
|
|
|
<div class="divider my-6"></div>
|
|
|
|
|
|
|
|
<!-- Current Images -->
|
|
|
|
<div class="space-y-4">
|
|
|
|
<h4 class="font-semibold text-lg">{$t('adventures.my_images')}</h4>
|
|
|
|
|
|
|
|
{#if transportation.images.length > 0}
|
|
|
|
<div class="grid gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
|
|
|
|
{#each transportation.images as image}
|
|
|
|
<div class="relative group">
|
|
|
|
<div class="aspect-square overflow-hidden rounded-lg bg-base-300">
|
|
|
|
<img
|
|
|
|
src={image.image}
|
|
|
|
alt={image.id}
|
|
|
|
class="w-full h-full object-cover transition-transform group-hover:scale-105"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Image Controls -->
|
|
|
|
<div
|
|
|
|
class="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity rounded-lg flex items-center justify-center gap-2"
|
|
|
|
>
|
|
|
|
{#if !image.is_primary}
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
class="btn btn-success btn-sm"
|
|
|
|
on:click={() => makePrimaryImage(image.id)}
|
|
|
|
title="Make Primary"
|
|
|
|
>
|
|
|
|
<Star class="h-4 w-4" />
|
|
|
|
</button>
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
class="btn btn-error btn-sm"
|
|
|
|
on:click={() => removeImage(image.id)}
|
|
|
|
title="Remove"
|
|
|
|
>
|
|
|
|
✕
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Primary Badge -->
|
|
|
|
{#if image.is_primary}
|
|
|
|
<div
|
|
|
|
class="absolute top-2 left-2 bg-warning text-warning-content rounded-full p-1"
|
|
|
|
>
|
|
|
|
<Crown class="h-4 w-4" />
|
|
|
|
</div>
|
|
|
|
{/if}
|
|
|
|
</div>
|
|
|
|
{/each}
|
|
|
|
</div>
|
|
|
|
{:else}
|
|
|
|
<div class="text-center py-8">
|
|
|
|
<div class="text-base-content/60 text-lg mb-2">
|
|
|
|
{$t('adventures.no_images')}
|
|
|
|
</div>
|
|
|
|
<p class="text-sm text-base-content/40">Upload images to get started</p>
|
|
|
|
</div>
|
|
|
|
{/if}
|
|
|
|
</div>
|
2024-12-19 18:46:52 -05:00
|
|
|
{/if}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Form Actions -->
|
2025-07-14 18:57:39 -04:00
|
|
|
<div class="flex justify-end gap-3 mt-8 pt-6 border-t border-base-300">
|
|
|
|
<button type="button" class="btn btn-ghost" on:click={close}>
|
2024-12-19 18:46:52 -05:00
|
|
|
{$t('about.close')}
|
|
|
|
</button>
|
2025-07-14 18:57:39 -04:00
|
|
|
<button type="submit" class="btn btn-primary gap-2">
|
|
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
<path
|
|
|
|
stroke-linecap="round"
|
|
|
|
stroke-linejoin="round"
|
|
|
|
stroke-width="2"
|
|
|
|
d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"
|
|
|
|
/>
|
|
|
|
</svg>
|
|
|
|
{$t('notes.save')}
|
|
|
|
</button>
|
2024-12-19 18:46:52 -05:00
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</dialog>
|