connect to database
This commit is contained in:
parent
143ac1d2ea
commit
9def1973a7
|
@ -63,15 +63,19 @@
|
||||||
if (place && place.name) {
|
if (place && place.name) {
|
||||||
// If the place has photos, get the URL for the first photo
|
// If the place has photos, get the URL for the first photo
|
||||||
if (place.photos && place.photos.length > 0) {
|
if (place.photos && place.photos.length > 0) {
|
||||||
|
try {
|
||||||
const photoOptions = {
|
const photoOptions = {
|
||||||
maxWidth: 400,
|
maxWidth: 400,
|
||||||
maxHeight: 300
|
maxHeight: 300
|
||||||
};
|
};
|
||||||
// Get the photo URL
|
place.photoUrl = place.photos[0].getUrl(photoOptions);
|
||||||
console.log(place.photos[0]);
|
} catch (error) {
|
||||||
const photoUrl = place.photos[0].getUrl(photoOptions);
|
console.error('Error getting photo URL:', error);
|
||||||
place.photoUrl = photoUrl;
|
place.photoUrl = '/placeholder.jpeg';
|
||||||
console.log(place.photoUrl);
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
place.photoUrl = '/placeholder.jpeg';
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedPlace = place;
|
selectedPlace = place;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
const newPlace = {
|
const newPlace = {
|
||||||
name: place.name || 'Unknown Place',
|
name: place.name || 'Unknown Place',
|
||||||
desc: place.formatted_address || '',
|
desc: place.formatted_address || '',
|
||||||
image: (place as any).photoUrl || 'placeholder.jpeg',
|
image: (place as any).photoUrl || '/placeholder.jpeg',
|
||||||
time: 'Add Time'
|
time: 'Add Time'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,16 +3,18 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { Loader } from '@googlemaps/js-api-loader';
|
import { Loader } from '@googlemaps/js-api-loader';
|
||||||
|
import { ref, child, get, set, onValue, push } from 'firebase/database';
|
||||||
|
import { db } from '../../firebase';
|
||||||
import Button from './Button.svelte';
|
import Button from './Button.svelte';
|
||||||
|
|
||||||
export let showPopup = false;
|
export let showPopup = false;
|
||||||
export let fromPage = 'home';
|
|
||||||
|
|
||||||
let destination = "";
|
let destination = "";
|
||||||
|
let selectedPlace: any;
|
||||||
let lastSelectedPlaceName = "";
|
let lastSelectedPlaceName = "";
|
||||||
let startDate = "";
|
let startDate = "";
|
||||||
let endDate = "";
|
let endDate = "";
|
||||||
let friends: string[] = [];
|
let tripmates: string[] = [];
|
||||||
let currentEmail = "";
|
let currentEmail = "";
|
||||||
let destinationError = false;
|
let destinationError = false;
|
||||||
let startDateError = false;
|
let startDateError = false;
|
||||||
|
@ -74,13 +76,14 @@
|
||||||
autocomplete = new google.maps.places.Autocomplete(input, {
|
autocomplete = new google.maps.places.Autocomplete(input, {
|
||||||
types: ['(regions)']
|
types: ['(regions)']
|
||||||
});
|
});
|
||||||
autocomplete.setFields(['name', 'formatted_address']);
|
autocomplete.setFields(['name', 'formatted_address', 'photos', 'place_id', 'geometry']);
|
||||||
|
|
||||||
autocomplete.addListener('place_changed', () => {
|
autocomplete.addListener('place_changed', () => {
|
||||||
if (!autocomplete) return;
|
if (!autocomplete) return;
|
||||||
const place = autocomplete.getPlace();
|
const place = autocomplete.getPlace();
|
||||||
if (place.name) {
|
if (place.name) {
|
||||||
destination = place.name;
|
destination = place.name;
|
||||||
|
selectedPlace = place;
|
||||||
lastSelectedPlaceName = input.value.trim();
|
lastSelectedPlaceName = input.value.trim();
|
||||||
destinationError = false;
|
destinationError = false;
|
||||||
}
|
}
|
||||||
|
@ -100,7 +103,7 @@
|
||||||
destinationError = false;
|
destinationError = false;
|
||||||
destination = "";
|
destination = "";
|
||||||
}
|
}
|
||||||
}, 200);
|
}, 400);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,15 +117,15 @@
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const email = currentEmail.trim();
|
const email = currentEmail.trim();
|
||||||
|
|
||||||
if (email && isValidEmail(email) && !friends.includes(email)) {
|
if (email && isValidEmail(email) && !tripmates.includes(email)) {
|
||||||
friends = [...friends, email];
|
tripmates = [...tripmates, email];
|
||||||
currentEmail = "";
|
currentEmail = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeEmail(emailToRemove: string) {
|
function removeEmail(emailToRemove: string) {
|
||||||
friends = friends.filter(email => email !== emailToRemove);
|
tripmates = tripmates.filter(email => email !== emailToRemove);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCancel() {
|
function handleCancel() {
|
||||||
|
@ -130,7 +133,7 @@
|
||||||
destination = "";
|
destination = "";
|
||||||
startDate = "";
|
startDate = "";
|
||||||
endDate = "";
|
endDate = "";
|
||||||
friends = [];
|
tripmates = [];
|
||||||
currentEmail = "";
|
currentEmail = "";
|
||||||
destinationError = false;
|
destinationError = false;
|
||||||
startDateError = false;
|
startDateError = false;
|
||||||
|
@ -142,7 +145,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleStart() {
|
async function handleStart() {
|
||||||
destinationError = !destination;
|
destinationError = !destination;
|
||||||
startDateError = !startDate;
|
startDateError = !startDate;
|
||||||
endDateError = !endDate;
|
endDateError = !endDate;
|
||||||
|
@ -162,12 +165,41 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (destinationError || startDateError || endDateError) {
|
if (destinationError || startDateError || endDateError) {
|
||||||
// alert('Please fill in all required fields: Destination, Start Date, End Date');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
goto(`/itinerary?from=${fromPage}`);
|
const tid = crypto.randomUUID();
|
||||||
|
|
||||||
|
// Extract required place details
|
||||||
|
const placeDetails = {
|
||||||
|
name: selectedPlace.name,
|
||||||
|
formatted_address: selectedPlace.formatted_address,
|
||||||
|
photo: selectedPlace.photos?.[0]?.getUrl(),
|
||||||
|
location: {
|
||||||
|
lat: selectedPlace.geometry.location.lat(),
|
||||||
|
lng: selectedPlace.geometry.location.lng()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const tripData = {
|
||||||
|
tid,
|
||||||
|
destination: placeDetails,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
tripmates,
|
||||||
|
created_at: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create a new reference for this specific trip using its ID
|
||||||
|
const tripRef = ref(db, `trips/${tid}`);
|
||||||
|
await set(tripRef, tripData);
|
||||||
|
console.log(`Trip saved to db with ID: ${tid}`);
|
||||||
|
goto(`/itinerary/${tid}`);
|
||||||
handleCancel();
|
handleCancel();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving trip:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,7 +214,7 @@
|
||||||
<h1>Start a New Plan</h1>
|
<h1>Start a New Plan</h1>
|
||||||
|
|
||||||
<div class="input-form">
|
<div class="input-form">
|
||||||
<label for="destination-input">Destination</label>
|
<label for="destination">Destination</label>
|
||||||
<div bind:this={destinationInput} class="destination-wrapper" id="destination"></div>
|
<div bind:this={destinationInput} class="destination-wrapper" id="destination"></div>
|
||||||
{#if destinationError}
|
{#if destinationError}
|
||||||
<p class="error-message">Please enter your destination</p>
|
<p class="error-message">Please enter your destination</p>
|
||||||
|
@ -229,7 +261,7 @@
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="email-input-container">
|
<div class="email-input-container">
|
||||||
{#each friends as email}
|
{#each tripmates as email}
|
||||||
<div class="email-tag">
|
<div class="email-tag">
|
||||||
<span>{email}</span>
|
<span>{email}</span>
|
||||||
<button class="remove-email" onclick={() => removeEmail(email)}>×</button>
|
<button class="remove-email" onclick={() => removeEmail(email)}>×</button>
|
||||||
|
@ -240,7 +272,7 @@
|
||||||
id="trip-friends"
|
id="trip-friends"
|
||||||
bind:value={currentEmail}
|
bind:value={currentEmail}
|
||||||
onkeydown={handleEmailInput}
|
onkeydown={handleEmailInput}
|
||||||
placeholder={friends.length ? "" : "Enter email addresses"}
|
placeholder={tripmates.length ? "" : "Enter email addresses"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
const defaultPlace: Omit<Place, 'desc'> = {
|
const defaultPlace: Omit<Place, 'desc'> = {
|
||||||
name: 'PlaceName',
|
name: 'PlaceName',
|
||||||
image: 'placeholder.jpeg',
|
image: '/placeholder.jpeg',
|
||||||
time: 'Add Time'
|
time: 'Add Time'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,39 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
export let image = '';
|
export let friends = 1;
|
||||||
export let marginLeft = '0px';
|
export let images: string[] = [];
|
||||||
export let zIndex = '0';
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="profile-pictures">
|
||||||
|
{#each Array(friends) as _, i}
|
||||||
<div
|
<div
|
||||||
class="profile-picture"
|
class="profile-picture"
|
||||||
style="margin-left: {marginLeft}; z-index: {zIndex}"
|
style="z-index: {friends - i}; margin-left: {i === 0 ? '0' : '-20px'}"
|
||||||
>
|
>
|
||||||
{#if image}
|
{#if images[i]}
|
||||||
<img class="profile-img" src={image} alt="" />
|
<img class="profile-img" src={images[i]} alt="Profile" />
|
||||||
{:else}
|
{:else}
|
||||||
<img class="profile-img" src='profile-pic.png' alt="" />
|
<div class="default-avatar">
|
||||||
|
<i class="fa-solid fa-user"></i>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.profile-pictures {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.profile-picture {
|
.profile-picture {
|
||||||
width: 2.5rem;
|
width: 2.5rem;
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: 2px solid white;
|
border: 2px solid white;
|
||||||
|
background-color: var(--gray-100);
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-img {
|
.profile-img {
|
||||||
|
@ -29,4 +42,14 @@
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.default-avatar {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--gray-400);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -6,7 +6,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="trip-card">
|
<div class="trip-card">
|
||||||
<div class="image" style="background-image: url({image})">
|
<div class="image" style="background-image: url({image || ''})">
|
||||||
<!-- Image placeholder if no image provided -->
|
<!-- Image placeholder if no image provided -->
|
||||||
{#if !image}
|
{#if !image}
|
||||||
<div class="placeholder">
|
<div class="placeholder">
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
<BottomBar onClick={() => goto('/trips')} />
|
<BottomBar onClick={() => goto('/trips')} />
|
||||||
|
|
||||||
<NewTripPopup bind:showPopup={showNewTripPopup} fromPage="home" />
|
<NewTripPopup bind:showPopup={showNewTripPopup} />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -147,8 +147,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tripmates">
|
<div class="tripmates">
|
||||||
<ProfilePicture zIndex=1/>
|
<ProfilePicture friends={2} />
|
||||||
<ProfilePicture marginLeft=-15px zIndex=0/>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|
420
src/routes/itinerary/[tid]/+page.svelte
Normal file
420
src/routes/itinerary/[tid]/+page.svelte
Normal file
|
@ -0,0 +1,420 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import '../../../app.css';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { slide } from 'svelte/transition';
|
||||||
|
import { quintOut } from 'svelte/easing';
|
||||||
|
import { page } from '$app/state';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { Loader } from '@googlemaps/js-api-loader';
|
||||||
|
import { browser } from '$app/environment';
|
||||||
|
import { ref, onValue, update } from 'firebase/database';
|
||||||
|
import { db } from '../../../firebase';
|
||||||
|
import ProfilePicture from '$lib/components/ProfilePicture.svelte';
|
||||||
|
import BottomBar from '$lib/components/BottomBar.svelte';
|
||||||
|
import Button from '$lib/components/Button.svelte';
|
||||||
|
import ItineraryDate from '$lib/components/ItineraryDate.svelte';
|
||||||
|
import AddPlaces from '$lib/components/AddPlaces.svelte';
|
||||||
|
import PlaceCard from '$lib/components/PlaceCard.svelte';
|
||||||
|
|
||||||
|
let tripData: any = null;
|
||||||
|
let tripDates: string[] = [];
|
||||||
|
let places: string[] = [];
|
||||||
|
let tid: string;
|
||||||
|
const place_placeholder = { name: 'Somewhere'}
|
||||||
|
const places_placeholder = Array(3).fill(place_placeholder);
|
||||||
|
|
||||||
|
const GOOGLE_PLACES_API_KEY = import.meta.env.VITE_GOOGLE_PLACES_API_KEY;
|
||||||
|
let mapContainer: HTMLDivElement;
|
||||||
|
let expandedDates: Record<string, boolean> = {};
|
||||||
|
let map: google.maps.Map | null = null;
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
if (!browser) return;
|
||||||
|
|
||||||
|
if (!GOOGLE_PLACES_API_KEY) {
|
||||||
|
console.error('Google Places API key is missing');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the trip ID from the URL
|
||||||
|
tid = page.params.tid;
|
||||||
|
|
||||||
|
const loader = new Loader({
|
||||||
|
apiKey: GOOGLE_PLACES_API_KEY,
|
||||||
|
version: "weekly",
|
||||||
|
libraries: ["places"],
|
||||||
|
language: 'en'
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { Map } = await loader.importLibrary("maps");
|
||||||
|
|
||||||
|
// Fetch trip data and initialize map when data is ready
|
||||||
|
const tripRef = ref(db, `trips/${tid}`);
|
||||||
|
|
||||||
|
onValue(tripRef, (snapshot) => {
|
||||||
|
tripData = snapshot.val();
|
||||||
|
if (tripData) {
|
||||||
|
// Generate array of dates between start and end date
|
||||||
|
const start = new Date(tripData.startDate);
|
||||||
|
const end = new Date(tripData.endDate);
|
||||||
|
const dates = [];
|
||||||
|
for (let date = new Date(start); date <= end; date.setDate(date.getDate() + 1)) {
|
||||||
|
dates.push(date.toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' }));
|
||||||
|
}
|
||||||
|
tripDates = dates;
|
||||||
|
|
||||||
|
// Initialize expanded states for dates
|
||||||
|
expandedDates = Object.fromEntries(dates.map(date => [date, false]));
|
||||||
|
|
||||||
|
// Initialize placesToVisit from database or empty array
|
||||||
|
placesToVisit = tripData.placesToVisit || [];
|
||||||
|
|
||||||
|
// Initialize or update the map
|
||||||
|
if (mapContainer && tripData.destination?.location) {
|
||||||
|
if (!map) {
|
||||||
|
map = new Map(mapContainer, {
|
||||||
|
center: tripData.destination.location,
|
||||||
|
zoom: 8,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
map.setCenter(tripData.destination.location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading Google Maps:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let expandedSections = {
|
||||||
|
explore: true,
|
||||||
|
places_to_visit: true,
|
||||||
|
itinerary: true
|
||||||
|
};
|
||||||
|
|
||||||
|
let recommendedPlaces = [
|
||||||
|
{ name: "Place name" },
|
||||||
|
{ name: "Place name" },
|
||||||
|
{ name: "Place name" }
|
||||||
|
];
|
||||||
|
|
||||||
|
let placesToVisit: any[] = [];
|
||||||
|
|
||||||
|
async function handleDeletePlace(index: number) {
|
||||||
|
const newPlacesToVisit = placesToVisit.filter((_, i) => i !== index);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Update the database
|
||||||
|
await update(ref(db, `trips/${tid}`), {
|
||||||
|
placesToVisit: newPlacesToVisit
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update local state
|
||||||
|
placesToVisit = newPlacesToVisit;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting place:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSection(section: keyof typeof expandedSections) {
|
||||||
|
expandedSections[section] = !expandedSections[section];
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBack() {
|
||||||
|
goto('/trips');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePastTrip() {
|
||||||
|
console.log(`see past trips to ${tripData?.destination?.name}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handlePlaceSelected(place: google.maps.places.PlaceResult) {
|
||||||
|
const newPlace = {
|
||||||
|
name: place.name || 'Unknown Place',
|
||||||
|
desc: place.formatted_address || '',
|
||||||
|
image: (place as any).photoUrl || '/placeholder.jpeg'
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatedPlaces = [...placesToVisit, newPlace];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Update the database
|
||||||
|
await update(ref(db, `trips/${tid}`), {
|
||||||
|
placesToVisit: updatedPlaces
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update local state
|
||||||
|
placesToVisit = updatedPlaces;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error adding place:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
console.log('cancel update');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSave() {
|
||||||
|
console.log('save update');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRecommendPlaces() {
|
||||||
|
console.log(`will give recommendation using OpenAI`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTurnIntoItinerary() {
|
||||||
|
console.log(`please turn this into itinerary`);
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div class="plan-section">
|
||||||
|
<header>
|
||||||
|
<div class="back-btn-wrapper">
|
||||||
|
<button class="back-btn" onclick={handleBack} aria-label="Back">
|
||||||
|
<i class="fa-solid fa-chevron-left"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="trip-info">
|
||||||
|
<h1>Trip to {tripData?.destination?.name || 'Loading...'}</h1>
|
||||||
|
<p class="date">{tripData?.startDate ? new Date(tripData.startDate).toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' }) : ''} - {tripData?.endDate ? new Date(tripData.endDate).toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' }) : ''}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tripmates">
|
||||||
|
<ProfilePicture friends={tripData?.tripmates?.length || 0} />
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<section class="places-section">
|
||||||
|
<button class="section-header" onclick={() => toggleSection('places_to_visit')}>
|
||||||
|
<div class="section-text">
|
||||||
|
<i class="fa-solid fa-chevron-right arrow-icon" class:rotated={expandedSections.places_to_visit}></i>
|
||||||
|
<h2>Places to Visit</h2>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if expandedSections.places_to_visit}
|
||||||
|
<div
|
||||||
|
class="section-content places"
|
||||||
|
transition:slide={{ duration: 400, easing: quintOut }}
|
||||||
|
>
|
||||||
|
<div class="added-places">
|
||||||
|
{#each placesToVisit as place, i}
|
||||||
|
<PlaceCard
|
||||||
|
variant="simple"
|
||||||
|
place={place}
|
||||||
|
onDelete={() => handleDeletePlace(i)}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AddPlaces
|
||||||
|
onPlaceSelected={handlePlaceSelected}
|
||||||
|
countryRestriction="tw"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="places-buttons">
|
||||||
|
<Button text="Recommend Places" type="blue" onClick={handleRecommendPlaces} />
|
||||||
|
<Button text="Turn into Itinerary" type="blue" onClick={handleTurnIntoItinerary} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="itinerary-section">
|
||||||
|
<button class="section-header" onclick={() => toggleSection('itinerary')}>
|
||||||
|
<div class="section-text">
|
||||||
|
<i class="fa-solid fa-chevron-right arrow-icon" class:rotated={expandedSections.itinerary}></i>
|
||||||
|
<h2>Itinerary</h2>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if expandedSections.itinerary}
|
||||||
|
<div
|
||||||
|
class="section-content"
|
||||||
|
transition:slide={{ duration: 400, easing: quintOut }}
|
||||||
|
>
|
||||||
|
{#each tripDates as date}
|
||||||
|
<ItineraryDate
|
||||||
|
{date}
|
||||||
|
isExpanded={expandedDates[date]}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<Button text="Cancel" type="gray" onClick={handleCancel}/>
|
||||||
|
<!-- later edit this so button turns blue only when there is changes to the plan -->
|
||||||
|
<Button text="Save" type="blue" onClick={handleSave} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="map-section">
|
||||||
|
<div class="map-container" bind:this={mapContainer}></div>
|
||||||
|
<BottomBar title="Past Trips" desc="Click to view all past trips to {tripData?.destination?.name}" onClick={handlePastTrip} />
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
main {
|
||||||
|
height: 100vh;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 50% 50%;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-section {
|
||||||
|
height: 100vh;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0.5rem 0rem 0rem 0rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: #84D7EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-container {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 0 2rem 1.5rem 1rem;
|
||||||
|
border-bottom: 1px solid var(--gray-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn-wrapper {
|
||||||
|
align-self: flex-start;
|
||||||
|
margin-top: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5rem;
|
||||||
|
color: var(--gray-400);
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn:hover {
|
||||||
|
background-color: var(--gray-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trip-info {
|
||||||
|
flex: 1;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trip-info h1 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date {
|
||||||
|
color: var(--gray-400);
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tripmates {
|
||||||
|
margin-top: 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 1rem 1.5rem 0 1.5rem;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
width: 100%;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid var(--gray-100);
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0.75rem 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-text {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-icon {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
transform-origin: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rotated {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-text h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-content {
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-content.places{
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.places-buttons {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
position: sticky;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-color: var(--white);
|
||||||
|
padding: 1.5rem 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -4,29 +4,65 @@
|
||||||
import Button from '$lib/components/Button.svelte';
|
import Button from '$lib/components/Button.svelte';
|
||||||
import NewTripPopup from '$lib/components/NewTripPopup.svelte';
|
import NewTripPopup from '$lib/components/NewTripPopup.svelte';
|
||||||
import Nav from '$lib/components/Nav.svelte';
|
import Nav from '$lib/components/Nav.svelte';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { ref, onValue } from 'firebase/database';
|
||||||
|
import { db } from '../../firebase';
|
||||||
|
|
||||||
interface Trip {
|
interface Trip {
|
||||||
destination: string;
|
tid: string;
|
||||||
|
destination: {
|
||||||
|
name: string;
|
||||||
|
photo: string;
|
||||||
|
formatted_address: string;
|
||||||
|
location: {
|
||||||
|
lat: number;
|
||||||
|
lng: number;
|
||||||
|
}
|
||||||
|
};
|
||||||
startDate: string;
|
startDate: string;
|
||||||
endDate: string;
|
endDate: string;
|
||||||
imageUrl: string;
|
tripmates: string[];
|
||||||
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
let activeTab = "Ongoing Trips";
|
let activeTab = "Ongoing Trips";
|
||||||
let showNewTripPopup = false;
|
let showNewTripPopup = false;
|
||||||
let contentContainer: HTMLElement;
|
let contentContainer: HTMLElement;
|
||||||
|
|
||||||
// Sample data, replace with actual data later
|
let ongoingTrips: Trip[] = [];
|
||||||
const sample_trip = {
|
let pastTrips: Trip[] = [];
|
||||||
destination: "Taiwan",
|
|
||||||
startDate: "04.27.2025",
|
|
||||||
endDate: "04.30.2025",
|
|
||||||
imageUrl: ""
|
|
||||||
}
|
|
||||||
let ongoingTrips = Array(3).fill(sample_trip);
|
|
||||||
|
|
||||||
// let pastTrips: Trip[] = [];
|
onMount(() => {
|
||||||
let pastTrips = Array(14).fill(sample_trip);
|
// Reference to the trips node
|
||||||
|
const tripsRef = ref(db, 'trips');
|
||||||
|
|
||||||
|
// Listen for changes in the trips data
|
||||||
|
onValue(tripsRef, (snapshot) => {
|
||||||
|
const trips: Trip[] = [];
|
||||||
|
snapshot.forEach((childSnapshot) => {
|
||||||
|
trips.push({
|
||||||
|
tid: childSnapshot.key,
|
||||||
|
...childSnapshot.val()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(trips);
|
||||||
|
// Get today's date at midnight for comparison
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
// Filter trips based on end date
|
||||||
|
ongoingTrips = trips.filter(trip => {
|
||||||
|
const endDate = new Date(trip.endDate);
|
||||||
|
return endDate >= today;
|
||||||
|
}).sort((a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime());
|
||||||
|
|
||||||
|
pastTrips = trips.filter(trip => {
|
||||||
|
const endDate = new Date(trip.endDate);
|
||||||
|
return endDate < today;
|
||||||
|
}).sort((a, b) => new Date(b.endDate).getTime() - new Date(a.endDate).getTime()); // Sort past trips by most recent first
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function handleNewTrip() {
|
function handleNewTrip() {
|
||||||
showNewTripPopup = true;
|
showNewTripPopup = true;
|
||||||
|
@ -71,7 +107,12 @@
|
||||||
{:else}
|
{:else}
|
||||||
<div class="trips-grid">
|
<div class="trips-grid">
|
||||||
{#each ongoingTrips as trip}
|
{#each ongoingTrips as trip}
|
||||||
<TripCard {...trip} />
|
<TripCard
|
||||||
|
destination={trip.destination.name}
|
||||||
|
startDate={new Date(trip.startDate).toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' })}
|
||||||
|
endDate={new Date(trip.endDate).toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' })}
|
||||||
|
image={trip.destination.photo}
|
||||||
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -83,7 +124,12 @@
|
||||||
{:else}
|
{:else}
|
||||||
<div class="trips-grid">
|
<div class="trips-grid">
|
||||||
{#each pastTrips as trip}
|
{#each pastTrips as trip}
|
||||||
<TripCard {...trip} />
|
<TripCard
|
||||||
|
destination={trip.destination.name}
|
||||||
|
startDate={new Date(trip.startDate).toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' })}
|
||||||
|
endDate={new Date(trip.endDate).toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' })}
|
||||||
|
image={trip.destination.photo}
|
||||||
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -95,7 +141,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NewTripPopup bind:showPopup={showNewTripPopup} fromPage="trips" />
|
<NewTripPopup bind:showPopup={showNewTripPopup} />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user