From 08da8c96c2e26c41032fa1af64878b4753787645 Mon Sep 17 00:00:00 2001 From: nils Date: Mon, 7 Apr 2025 11:25:38 +0200 Subject: [PATCH 01/18] Allow users to store favourite routes --- migrations/2025-03-21-favourites.js | 28 ++++ src/lib/i18n/de.ts | 10 +- src/lib/i18n/en.ts | 10 +- src/lib/i18n/translation.ts | 6 + src/lib/map/Location.ts | 4 +- src/lib/server/booking/getEventGroupInfo.ts | 2 +- src/lib/server/booking/routing.ts | 2 +- src/lib/server/db/index.ts | 15 ++ src/lib/ui/AccountingView.svelte | 5 +- src/lib/ui/AddressTypeahead.svelte | 40 +++++- src/lib/ui/SortableTable.svelte | 1 - .../{server => util}/booking/isSamePlace.ts | 0 src/routes/(customer)/routing/+page.server.ts | 135 +++++++++++++++++- src/routes/(customer)/routing/+page.svelte | 99 ++++++++++++- 14 files changed, 339 insertions(+), 18 deletions(-) create mode 100644 migrations/2025-03-21-favourites.js rename src/lib/{server => util}/booking/isSamePlace.ts (100%) diff --git a/migrations/2025-03-21-favourites.js b/migrations/2025-03-21-favourites.js new file mode 100644 index 000000000..9ccf42fa3 --- /dev/null +++ b/migrations/2025-03-21-favourites.js @@ -0,0 +1,28 @@ + + +export async function up(db) { + await db.schema + .createTable('favourite_locations') + .addColumn('id', 'serial', (col) => col.primaryKey()) + .addColumn('user', 'integer', (col) => col.references('user.id').notNull()) + .addColumn('address', 'varchar', (col) => col.notNull()) + .addColumn('lat', 'real', (col) => col.notNull()) + .addColumn('lng', 'real', (col) => col.notNull()) + .addColumn('count', 'integer', (col) => col.notNull().defaultTo(0)) + .execute(); + + await db.schema + .createTable('favourite_routes') + .addColumn('id', 'serial', (col) => col.primaryKey()) + .addColumn('user', 'integer', (col) => col.references('user.id').notNull()) + .addColumn('from_id', 'integer', (col) => col.references('favourite_locations.id').notNull()) + .addColumn('to_id', 'integer', (col) => col.references('favourite_locations.id').notNull()) + .addColumn('count', 'integer', (col) => col.notNull().defaultTo(0)) + .execute(); +} + + +export async function down(db) { + await db.dropTable('favourite_routes').execute(); + await db.dropTable('favourite_locations').execute(); +} diff --git a/src/lib/i18n/de.ts b/src/lib/i18n/de.ts index c0776817b..d8d836642 100644 --- a/src/lib/i18n/de.ts +++ b/src/lib/i18n/de.ts @@ -95,7 +95,10 @@ const translations: Translations = { // Feedback feedbackThank: 'Vielen Dank für Ihr Feedback!', - feedbackMissing: 'Kein Feedback gegeben' + feedbackMissing: 'Kein Feedback gegeben', + + invalidFrom: 'Invalid start address.', + invalidTo: 'Invalid destination address.' }, admin: { completedToursSubtitle: 'Abgeschlossene Fahrten', @@ -175,6 +178,7 @@ const translations: Translations = { odm: 'ÖPNV-Taxi - Buchung erforderlich!', from: 'Von', to: 'Nach', + favourites: 'Favoriten', arrival: 'Ankunft', departure: 'Abfahrt', duration: 'Dauer', @@ -235,7 +239,9 @@ const translations: Translations = { noCancel: 'Nein, Fahrt nicht stornieren.', cancelDescription: 'Die Stornierung der Fahrt kann nicht rückgängig gemacht werden. Eine Stornierung weniger als 24 Stunden vor der Fahrt ist mit Kosten verbunden.' - } + }, + addToFavs: 'zu Favoriten hinzufügen', + displayFavsList: 'Favoriten anzeigen' }; export default translations; diff --git a/src/lib/i18n/en.ts b/src/lib/i18n/en.ts index 887869bc5..a60a47704 100644 --- a/src/lib/i18n/en.ts +++ b/src/lib/i18n/en.ts @@ -92,7 +92,10 @@ const translations: Translations = { // Feedback feedbackThank: 'Thank you very much for your feedback!', - feedbackMissing: 'No feedback given' + feedbackMissing: 'No feedback given', + + invalidFrom: 'Invalid start address.', + invalidTo: 'Invalid destination address.' }, admin: { completedToursSubtitle: 'Completed Tours', @@ -170,6 +173,7 @@ const translations: Translations = { odm: 'Public Transport Taxi, booking required!', from: 'From', to: 'To', + favourites: 'Favourites', arrival: 'Arrival', departure: 'Departure', duration: 'Duration', @@ -229,7 +233,9 @@ const translations: Translations = { 'Cancellation cannot be undone. Cancellation less than 24 hours before the trip will incur costs.', cancelTrip: 'Cancel Trip', noCancel: 'No, I do not want to cancel.' - } + }, + addToFavs: 'add to favourites', + displayFavsList: 'display favourites' }; export default translations; diff --git a/src/lib/i18n/translation.ts b/src/lib/i18n/translation.ts index 05f929c2a..b3384b253 100644 --- a/src/lib/i18n/translation.ts +++ b/src/lib/i18n/translation.ts @@ -94,6 +94,9 @@ export type Translations = { // Feedback feedbackThank: string; feedbackMissing: string; + + invalidFrom: string; + invalidTo: string; }; admin: { completedToursSubtitle: string; @@ -160,6 +163,7 @@ export type Translations = { odm: string; from: string; to: string; + favourites: string; arrival: string; departure: string; duration: string; @@ -200,6 +204,8 @@ export type Translations = { cancelTrip: string; noCancel: string; }; + addToFavs: string; + displayFavsList: string; }; const translations: Map = new Map(Object.entries({ en, de })); diff --git a/src/lib/map/Location.ts b/src/lib/map/Location.ts index 4906aa97d..e484e52a7 100644 --- a/src/lib/map/Location.ts +++ b/src/lib/map/Location.ts @@ -9,9 +9,9 @@ export type Location = { }; }; -export function posToLocation(pos: maplibregl.LngLatLike, level: number): Location { +export function posToLocation(pos: maplibregl.LngLatLike, level: number, l?: string): Location { const { lat, lng } = maplibregl.LngLat.convert(pos); - const label = `${lat},${lng},${level}`; + const label = l ? l : `${lat},${lng},${level}`; return { label, value: { diff --git a/src/lib/server/booking/getEventGroupInfo.ts b/src/lib/server/booking/getEventGroupInfo.ts index b4d6f9972..7b365a251 100644 --- a/src/lib/server/booking/getEventGroupInfo.ts +++ b/src/lib/server/booking/getEventGroupInfo.ts @@ -1,7 +1,7 @@ import type { Coordinates } from '$lib/util/Coordinates'; import { InsertHow } from './insertionTypes'; import { v4 as uuidv4 } from 'uuid'; -import { isSamePlace } from './isSamePlace'; +import { isSamePlace } from '$lib/util/booking/isSamePlace'; import { type Event } from '$lib/server/booking/getBookingAvailability'; export type EventGroupUpdate = { diff --git a/src/lib/server/booking/routing.ts b/src/lib/server/booking/routing.ts index d758342c0..7b40cfd50 100644 --- a/src/lib/server/booking/routing.ts +++ b/src/lib/server/booking/routing.ts @@ -5,7 +5,7 @@ import type { InsertionInfo } from './insertionTypes'; import { iterateAllInsertions } from './iterateAllInsertions'; import type { VehicleId } from './VehicleId'; import type { Range } from '$lib/util/booking/getPossibleInsertions'; -import { isSamePlace } from './isSamePlace'; +import { isSamePlace } from '$lib/util/booking/isSamePlace'; import { batchOneToManyCarRouting } from '$lib/server/util/batchOneToManyCarRouting'; export type InsertionRoutingResult = { diff --git a/src/lib/server/db/index.ts b/src/lib/server/db/index.ts index d859aa4d5..a0997affd 100644 --- a/src/lib/server/db/index.ts +++ b/src/lib/server/db/index.ts @@ -98,6 +98,21 @@ export interface Database { rating: number | null; comment: string | null; }; + favouriteLocations: { + id: Generated; + user: number; + address: string; + lat: number; + lng: number; + count: number; + }; + favouriteRoutes: { + id: Generated; + user: number; + fromId: number; + toId: number; + count: number; + }; } export const pool = new pg.Pool({ connectionString: env.DATABASE_URL }); diff --git a/src/lib/ui/AccountingView.svelte b/src/lib/ui/AccountingView.svelte index 91c53a76b..a70b463e9 100644 --- a/src/lib/ui/AccountingView.svelte +++ b/src/lib/ui/AccountingView.svelte @@ -305,8 +305,7 @@ 'cursor-pointer '} + getRowStyle={(_: TourWithRequests) => 'cursor-pointer '} bind:selectedRow={selectedToursTableRow} bindSelectedRow={true} /> @@ -318,7 +317,6 @@ {/snippet} @@ -326,7 +324,6 @@ {/snippet} diff --git a/src/lib/ui/AddressTypeahead.svelte b/src/lib/ui/AddressTypeahead.svelte index 63698e6c7..4b4f59afa 100644 --- a/src/lib/ui/AddressTypeahead.svelte +++ b/src/lib/ui/AddressTypeahead.svelte @@ -8,6 +8,10 @@ import { language } from '$lib/i18n/translation'; import maplibregl from 'maplibre-gl'; import { onMount } from 'svelte'; + import { t } from '$lib/i18n/translation'; + import type { Column } from './tableData'; + import SortableTable from './SortableTable.svelte'; + import * as Card from '$lib/shadcn/card'; export type Location = { label?: string; @@ -47,15 +51,29 @@ selected = $bindable(), onValueChange, placeholder, - name + name, + favs, + selectedFav = $bindable() }: { items?: Array; selected?: Location; onValueChange?: (m: Location) => void; placeholder?: string; name?: string; + favs?: { address: string; lat: number; lng: number }[]; + selectedFav?: { address: string; lat: number; lng: number }[]; } = $props(); + let f = $derived(favs); + + const favsCols: Column<{ address: string; lat: number; lng: number }>[] = [ + { + text: [t.favourites], + sort: undefined, + toTableEntry: (r: { address: string }) => r.address + } + ]; + let inputValue = $state(''); let value = $state(''); @@ -157,6 +175,25 @@ onMount(() => ref?.focus()); +{#snippet favourites()} + {#if f != undefined} + + + {t.favourites} + + + 'cursor-pointer '} + rows={f} + cols={favsCols} + bind:selectedRow={selectedFav} + bindSelectedRow={true} + /> + + {/if} +{/snippet} + {/if} +{@render favourites()} diff --git a/src/lib/ui/SortableTable.svelte b/src/lib/ui/SortableTable.svelte index a4f154787..f511790b8 100644 --- a/src/lib/ui/SortableTable.svelte +++ b/src/lib/ui/SortableTable.svelte @@ -19,7 +19,6 @@ toColumnStyle?: (r: T) => string; hidden?: boolean; }[]; - isAdmin: boolean; getRowStyle?: (row: T) => string; selectedRow?: undefined | T[]; bindSelectedRow?: boolean; diff --git a/src/lib/server/booking/isSamePlace.ts b/src/lib/util/booking/isSamePlace.ts similarity index 100% rename from src/lib/server/booking/isSamePlace.ts rename to src/lib/util/booking/isSamePlace.ts diff --git a/src/routes/(customer)/routing/+page.server.ts b/src/routes/(customer)/routing/+page.server.ts index 64b0d0440..a6ecf965a 100644 --- a/src/routes/(customer)/routing/+page.server.ts +++ b/src/routes/(customer)/routing/+page.server.ts @@ -8,6 +8,46 @@ import { msg, type Msg } from '$lib/msg'; import { redirect } from '@sveltejs/kit'; import { sendMail } from '$lib/server/sendMail'; import NewRide from '$lib/server/email/NewRide.svelte'; +import type { PageServerLoadEvent } from './$types'; +import { isSamePlace } from '$lib/util/booking/isSamePlace'; + +export async function load(event: PageServerLoadEvent) { + const userId = event.locals.session?.userId; + if (!userId) { + return { + favs: [] + }; + } + return { + favs: await db + .selectFrom('favouriteLocations') + .where('favouriteLocations.user', '=', userId) + .orderBy('favouriteLocations.count', 'desc') + .select(['favouriteLocations.address', 'favouriteLocations.lat', 'favouriteLocations.lng']) + .limit(5) + .execute(), + favouriteRoutes: await db + .selectFrom('favouriteRoutes') + .where('favouriteRoutes.user', '=', userId) + .orderBy('favouriteRoutes.count desc') + .innerJoin( + 'favouriteLocations as fromLocations', + 'fromLocations.id', + 'favouriteRoutes.fromId' + ) + .innerJoin('favouriteLocations as toLocations', 'toLocations.id', 'favouriteRoutes.toId') + .limit(10) + .select([ + 'toLocations.address as toAddress', + 'toLocations.lat as toLat', + 'toLocations.lng as toLng', + 'fromLocations.address as fromAddress', + 'fromLocations.lat as fromLat', + 'fromLocations.lng as fromLng' + ]) + .execute() + }; +} const getCommonTour = (l1: Set, l2: Set) => { for (const e of l1) { @@ -19,7 +59,7 @@ const getCommonTour = (l1: Set, l2: Set) => { }; export const actions = { - default: async ({ request, locals }): Promise<{ msg: Msg }> => { + routing: async ({ request, locals }): Promise<{ msg: Msg }> => { const user = locals.session?.userId; if (!user) { return { msg: msg('accountDoesNotExist') }; @@ -277,5 +317,98 @@ export const actions = { } return { msg: message! }; + }, + fav: async ({ request, locals }) => { + const user = locals.session?.userId; + if (!user || typeof user != 'number') { + return { msg: msg('accountDoesNotExist') }; + } + const formData = await request.formData(); + const fromAddress = formData.get('fromAddress'); + const fromLat = formData.get('fromLat'); + const fromLon = formData.get('fromLon'); + const toAddress = formData.get('toAddress'); + const toLat = formData.get('toLat'); + const toLon = formData.get('toLon'); + if ( + typeof fromAddress !== 'string' || + typeof fromLat !== 'string' || + typeof fromLon !== 'string' + ) { + return { msg: msg('invalidFrom') }; + } + let lat = parseFloat(fromLat); + let lng = parseFloat(fromLon); + if (typeof lat !== 'number' || typeof lng !== 'number') { + return { msg: msg('invalidFrom') }; + } + let currentFavs = await db + .selectFrom('favouriteLocations') + .where('user', '=', user) + .selectAll() + .execute(); + const fromMatch = currentFavs.find((fav) => isSamePlace(fav, { lat, lng })); + let fromId = undefined; + if (fromMatch) { + await db + .updateTable('favouriteLocations') + .where('user', '=', user) + .where('favouriteLocations.id', '=', fromMatch.id) + .set({ count: fromMatch.count + 1 }) + .execute(); + } else { + fromId = (await db + .insertInto('favouriteLocations') + .values({ lat, lng, address: fromAddress, user, count: 0 }) + .returning('id') + .executeTakeFirst())!.id; + } + + if (typeof toAddress !== 'string' || typeof toLat !== 'string' || typeof toLon !== 'string') { + return { msg: msg('invalidFrom') }; + } + lat = parseFloat(toLat); + lng = parseFloat(toLon); + if (isNaN(lat) || isNaN(lng)) { + return { msg: msg('invalidFrom') }; + } + currentFavs = await db + .selectFrom('favouriteLocations') + .where('user', '=', user) + .selectAll() + .execute(); + const toMatch = currentFavs.find((fav) => isSamePlace(fav, { lat, lng })); + let toId = undefined; + if (toMatch) { + await db + .updateTable('favouriteLocations') + .where('user', '=', user) + .where('favouriteLocations.id', '=', toMatch.id) + .set({ count: toMatch.count + 1 }) + .execute(); + } else { + toId = (await db + .insertInto('favouriteLocations') + .values({ lat, lng, address: toAddress, user, count: 0 }) + .returning(['favouriteLocations.id']) + .executeTakeFirst())!.id; + } + toId = toId ?? toMatch?.id; + fromId = fromId ?? fromMatch?.id; + if (toId == undefined || fromId == undefined) { + return {}; + } + if (fromMatch && toMatch) { + await db + .updateTable('favouriteRoutes') + .where('user', '=', user) + .where('fromId', '=', fromMatch.id) + .where('toId', '=', toMatch.id) + .set((eb) => ({ count: eb('count', '+', 1) })) + .execute(); + } else { + await db.insertInto('favouriteRoutes').values({ user, toId, fromId, count: 0 }).execute(); + } + return {}; } }; diff --git a/src/routes/(customer)/routing/+page.svelte b/src/routes/(customer)/routing/+page.svelte index 10a4f8715..4b702e702 100644 --- a/src/routes/(customer)/routing/+page.svelte +++ b/src/routes/(customer)/routing/+page.svelte @@ -1,6 +1,6 @@
@@ -166,6 +239,8 @@ bind:selected={from} items={fromItems} onValueChange={() => history.back()} + favs={data.favs} + bind:selectedFav={selectedFromFav} /> {:else if page.state.selectTo} history.back()} + favs={data.favs} + bind:selectedFav={selectedToFav} /> {:else if page.state.showMap} @@ -219,7 +296,7 @@ page.state.selectedItinerary.legs.length === 1 && page.state.selectedItinerary.legs[0].mode === 'ODM'} -
+
+ {#if baseQuery == undefined && data.favouriteRoutes && data.favouriteRoutes.length != 0} + + + {t.favourites} + + + 'cursor-pointer '} + rows={data.favouriteRoutes} + cols={favsCols} + bind:selectedRow={selectedFav} + bindSelectedRow={true} + /> + + + {/if}
Date: Mon, 7 Apr 2025 13:59:48 +0200 Subject: [PATCH 02/18] wip --- e2e/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/utils.ts b/e2e/utils.ts index 8865b79ae..5559a2313 100644 --- a/e2e/utils.ts +++ b/e2e/utils.ts @@ -93,7 +93,7 @@ export async function setCompanyData(page: Page, user: UserCredentials, company: await expect(page.getByRole('heading', { name: 'Stammdaten Ihres Unternehmens' })).toBeVisible(); await page.getByLabel('Name').fill(company.name); - await page.getByLabel('Unternehmenssitz').pressSequentially(company.address, { delay: 10 }); + await page.getByLabel('Unternehmenssitz').pressSequentially(company.address, { delay: 15 }); await page.getByText('Werner-Seelenbinder-Straße 70a').click(); await page.getByLabel('Pflichtfahrgebiet').selectOption({ label: company.zone }); From 995637e0203528ca0701b3ba33f9240f2590c61f Mon Sep 17 00:00:00 2001 From: nils penzel Date: Mon, 7 Apr 2025 14:22:55 +0200 Subject: [PATCH 03/18] wip --- src/lib/ui/AddressTypeahead.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/ui/AddressTypeahead.svelte b/src/lib/ui/AddressTypeahead.svelte index 4b4f59afa..9548ef331 100644 --- a/src/lib/ui/AddressTypeahead.svelte +++ b/src/lib/ui/AddressTypeahead.svelte @@ -64,7 +64,7 @@ selectedFav?: { address: string; lat: number; lng: number }[]; } = $props(); - let f = $derived(favs); + let favRows = $derived(favs); const favsCols: Column<{ address: string; lat: number; lng: number }>[] = [ { @@ -176,7 +176,7 @@ {#snippet favourites()} - {#if f != undefined} + {#if favRows != undefined && favRows.length != 0} {t.favourites} @@ -184,7 +184,7 @@ 'cursor-pointer '} - rows={f} + rows={favRows} cols={favsCols} bind:selectedRow={selectedFav} bindSelectedRow={true} From 74a1f9bbc71f71286abda9f0b87f68abc4650483 Mon Sep 17 00:00:00 2001 From: nils penzel Date: Mon, 7 Apr 2025 15:23:40 +0200 Subject: [PATCH 04/18] wip --- src/lib/ui/AccountingView.svelte | 2 +- src/routes/(customer)/routing/+page.server.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/ui/AccountingView.svelte b/src/lib/ui/AccountingView.svelte index a70b463e9..4df815ac3 100644 --- a/src/lib/ui/AccountingView.svelte +++ b/src/lib/ui/AccountingView.svelte @@ -305,7 +305,7 @@ 'cursor-pointer '} + getRowStyle={(_) => 'cursor-pointer '} bind:selectedRow={selectedToursTableRow} bindSelectedRow={true} /> diff --git a/src/routes/(customer)/routing/+page.server.ts b/src/routes/(customer)/routing/+page.server.ts index a6ecf965a..dcbed3f2c 100644 --- a/src/routes/(customer)/routing/+page.server.ts +++ b/src/routes/(customer)/routing/+page.server.ts @@ -36,7 +36,7 @@ export async function load(event: PageServerLoadEvent) { 'favouriteRoutes.fromId' ) .innerJoin('favouriteLocations as toLocations', 'toLocations.id', 'favouriteRoutes.toId') - .limit(10) + .limit(5) .select([ 'toLocations.address as toAddress', 'toLocations.lat as toLat', From 834ab3b92af6c90268d5b7e495bf79db3bd2dd63 Mon Sep 17 00:00:00 2001 From: nils penzel Date: Mon, 7 Apr 2025 15:30:28 +0200 Subject: [PATCH 05/18] wip --- e2e/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/utils.ts b/e2e/utils.ts index 5559a2313..a274e3bae 100644 --- a/e2e/utils.ts +++ b/e2e/utils.ts @@ -93,7 +93,7 @@ export async function setCompanyData(page: Page, user: UserCredentials, company: await expect(page.getByRole('heading', { name: 'Stammdaten Ihres Unternehmens' })).toBeVisible(); await page.getByLabel('Name').fill(company.name); - await page.getByLabel('Unternehmenssitz').pressSequentially(company.address, { delay: 15 }); + await page.getByLabel('Unternehmenssitz').fill(company.address); await page.getByText('Werner-Seelenbinder-Straße 70a').click(); await page.getByLabel('Pflichtfahrgebiet').selectOption({ label: company.zone }); From 4a1391c430658a5061407aadfe2a38303fda77bf Mon Sep 17 00:00:00 2001 From: nils penzel Date: Mon, 7 Apr 2025 15:34:52 +0200 Subject: [PATCH 06/18] wip --- e2e/utils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/e2e/utils.ts b/e2e/utils.ts index a274e3bae..050107855 100644 --- a/e2e/utils.ts +++ b/e2e/utils.ts @@ -93,7 +93,8 @@ export async function setCompanyData(page: Page, user: UserCredentials, company: await expect(page.getByRole('heading', { name: 'Stammdaten Ihres Unternehmens' })).toBeVisible(); await page.getByLabel('Name').fill(company.name); - await page.getByLabel('Unternehmenssitz').fill(company.address); + await page.getByLabel('Unternehmenssitz').pressSequentially(company.address, { delay: 15 }); + await page.screenshot({ path: 'screenshots/afterEnteringCompanyAddress.png', fullPage: true }); await page.getByText('Werner-Seelenbinder-Straße 70a').click(); await page.getByLabel('Pflichtfahrgebiet').selectOption({ label: company.zone }); From 498926d24173c134d5ca0f3bd1993fa63d6eaf74 Mon Sep 17 00:00:00 2001 From: nils penzel Date: Mon, 7 Apr 2025 16:00:02 +0200 Subject: [PATCH 07/18] wip --- e2e/utils.ts | 3 +-- src/lib/i18n/de.ts | 4 +--- src/lib/i18n/en.ts | 4 +--- src/lib/i18n/translation.ts | 2 -- src/routes/taxi/company/+page.svelte | 1 + 5 files changed, 4 insertions(+), 10 deletions(-) diff --git a/e2e/utils.ts b/e2e/utils.ts index 050107855..8865b79ae 100644 --- a/e2e/utils.ts +++ b/e2e/utils.ts @@ -93,8 +93,7 @@ export async function setCompanyData(page: Page, user: UserCredentials, company: await expect(page.getByRole('heading', { name: 'Stammdaten Ihres Unternehmens' })).toBeVisible(); await page.getByLabel('Name').fill(company.name); - await page.getByLabel('Unternehmenssitz').pressSequentially(company.address, { delay: 15 }); - await page.screenshot({ path: 'screenshots/afterEnteringCompanyAddress.png', fullPage: true }); + await page.getByLabel('Unternehmenssitz').pressSequentially(company.address, { delay: 10 }); await page.getByText('Werner-Seelenbinder-Straße 70a').click(); await page.getByLabel('Pflichtfahrgebiet').selectOption({ label: company.zone }); diff --git a/src/lib/i18n/de.ts b/src/lib/i18n/de.ts index d8d836642..7ac3f1adf 100644 --- a/src/lib/i18n/de.ts +++ b/src/lib/i18n/de.ts @@ -239,9 +239,7 @@ const translations: Translations = { noCancel: 'Nein, Fahrt nicht stornieren.', cancelDescription: 'Die Stornierung der Fahrt kann nicht rückgängig gemacht werden. Eine Stornierung weniger als 24 Stunden vor der Fahrt ist mit Kosten verbunden.' - }, - addToFavs: 'zu Favoriten hinzufügen', - displayFavsList: 'Favoriten anzeigen' + } }; export default translations; diff --git a/src/lib/i18n/en.ts b/src/lib/i18n/en.ts index a60a47704..b521b8fca 100644 --- a/src/lib/i18n/en.ts +++ b/src/lib/i18n/en.ts @@ -233,9 +233,7 @@ const translations: Translations = { 'Cancellation cannot be undone. Cancellation less than 24 hours before the trip will incur costs.', cancelTrip: 'Cancel Trip', noCancel: 'No, I do not want to cancel.' - }, - addToFavs: 'add to favourites', - displayFavsList: 'display favourites' + } }; export default translations; diff --git a/src/lib/i18n/translation.ts b/src/lib/i18n/translation.ts index b3384b253..2909e3516 100644 --- a/src/lib/i18n/translation.ts +++ b/src/lib/i18n/translation.ts @@ -204,8 +204,6 @@ export type Translations = { cancelTrip: string; noCancel: string; }; - addToFavs: string; - displayFavsList: string; }; const translations: Map = new Map(Object.entries({ en, de })); diff --git a/src/routes/taxi/company/+page.svelte b/src/routes/taxi/company/+page.svelte index 2572ab304..e2729aaf4 100644 --- a/src/routes/taxi/company/+page.svelte +++ b/src/routes/taxi/company/+page.svelte @@ -90,6 +90,7 @@ (companyAddress = l)} placeholder={'Unternehmenssitz'} bind:selected={companyAddress} From aa68e623fd267bf2e8f0d5cd7054e4dcbaa78cdf Mon Sep 17 00:00:00 2001 From: nils penzel Date: Mon, 7 Apr 2025 16:06:41 +0200 Subject: [PATCH 08/18] wip --- e2e/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/utils.ts b/e2e/utils.ts index 8865b79ae..17dd123a0 100644 --- a/e2e/utils.ts +++ b/e2e/utils.ts @@ -93,7 +93,7 @@ export async function setCompanyData(page: Page, user: UserCredentials, company: await expect(page.getByRole('heading', { name: 'Stammdaten Ihres Unternehmens' })).toBeVisible(); await page.getByLabel('Name').fill(company.name); - await page.getByLabel('Unternehmenssitz').pressSequentially(company.address, { delay: 10 }); + await page.getByLabel('Unternehmenssitz').pressSequentially(company.address, { delay: 50 }); await page.getByText('Werner-Seelenbinder-Straße 70a').click(); await page.getByLabel('Pflichtfahrgebiet').selectOption({ label: company.zone }); From e203dc1a0699db432d344d117fce32327cac5d8c Mon Sep 17 00:00:00 2001 From: nils penzel Date: Mon, 7 Apr 2025 16:12:31 +0200 Subject: [PATCH 09/18] wip --- e2e/utils.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/e2e/utils.ts b/e2e/utils.ts index 17dd123a0..1a1de57aa 100644 --- a/e2e/utils.ts +++ b/e2e/utils.ts @@ -93,8 +93,10 @@ export async function setCompanyData(page: Page, user: UserCredentials, company: await expect(page.getByRole('heading', { name: 'Stammdaten Ihres Unternehmens' })).toBeVisible(); await page.getByLabel('Name').fill(company.name); - await page.getByLabel('Unternehmenssitz').pressSequentially(company.address, { delay: 50 }); - await page.getByText('Werner-Seelenbinder-Straße 70a').click(); + await page.getByLabel('Unternehmenssitz').pressSequentially(company.address, { delay: 10 }); + const addressSuggestion = page.getByText('Werner-Seelenbinder-Straße 70a'); + await expect(addressSuggestion).toBeVisible({ timeout: 10000 }); + await addressSuggestion.click(); await page.getByLabel('Pflichtfahrgebiet').selectOption({ label: company.zone }); await page.getByRole('button', { name: 'Übernehmen' }).click(); From e7b5f1ddc3d0ce32e03b9af065268aa776431ac7 Mon Sep 17 00:00:00 2001 From: nils penzel Date: Mon, 7 Apr 2025 16:15:20 +0200 Subject: [PATCH 10/18] wip --- e2e/utils.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/e2e/utils.ts b/e2e/utils.ts index 1a1de57aa..17dd123a0 100644 --- a/e2e/utils.ts +++ b/e2e/utils.ts @@ -93,10 +93,8 @@ export async function setCompanyData(page: Page, user: UserCredentials, company: await expect(page.getByRole('heading', { name: 'Stammdaten Ihres Unternehmens' })).toBeVisible(); await page.getByLabel('Name').fill(company.name); - await page.getByLabel('Unternehmenssitz').pressSequentially(company.address, { delay: 10 }); - const addressSuggestion = page.getByText('Werner-Seelenbinder-Straße 70a'); - await expect(addressSuggestion).toBeVisible({ timeout: 10000 }); - await addressSuggestion.click(); + await page.getByLabel('Unternehmenssitz').pressSequentially(company.address, { delay: 50 }); + await page.getByText('Werner-Seelenbinder-Straße 70a').click(); await page.getByLabel('Pflichtfahrgebiet').selectOption({ label: company.zone }); await page.getByRole('button', { name: 'Übernehmen' }).click(); From 7bb1a35fa1fb77a6b5ac07f8ff1c40bdec9c111f Mon Sep 17 00:00:00 2001 From: nils penzel Date: Mon, 14 Apr 2025 12:14:34 +0200 Subject: [PATCH 11/18] wip --- src/routes/(customer)/routing/+page.svelte | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/(customer)/routing/+page.svelte b/src/routes/(customer)/routing/+page.svelte index 4b702e702..d24e4fd75 100644 --- a/src/routes/(customer)/routing/+page.svelte +++ b/src/routes/(customer)/routing/+page.svelte @@ -211,6 +211,7 @@ if (selectedFromFav && selectedFromFav.length != 0) { const fav = selectedFromFav[0]; from = posToLocation({ lat: fav.lat, lng: fav.lng }, 0, fav.address); + history.back(); } }); @@ -218,6 +219,7 @@ if (selectedToFav && selectedToFav.length != 0) { const fav = selectedToFav[0]; to = posToLocation({ lat: fav.lat, lng: fav.lng }, 0, fav.address); + history.back(); } }); From 7b6dd943d6b2f36a2afb075b94c5ffbe7e236791 Mon Sep 17 00:00:00 2001 From: nils Date: Wed, 16 Apr 2025 10:57:41 +0200 Subject: [PATCH 12/18] wip --- migrations/2025-03-21-favourites.js | 8 +- src/lib/i18n/de.ts | 4 +- src/lib/server/db/index.ts | 3 + src/lib/ui/AddressTypeahead.svelte | 40 +-- src/lib/ui/FavouriteLocations.svelte | 38 +++ src/lib/ui/FavouriteRoutes.svelte | 66 +++++ src/routes/(customer)/routing/+page.server.ts | 236 ++++++++++++++---- src/routes/(customer)/routing/+page.svelte | 84 +++---- src/routes/taxi/company/+page.svelte | 1 - 9 files changed, 341 insertions(+), 139 deletions(-) create mode 100644 src/lib/ui/FavouriteLocations.svelte create mode 100644 src/lib/ui/FavouriteRoutes.svelte diff --git a/migrations/2025-03-21-favourites.js b/migrations/2025-03-21-favourites.js index 9ccf42fa3..9df9eb497 100644 --- a/migrations/2025-03-21-favourites.js +++ b/migrations/2025-03-21-favourites.js @@ -8,7 +8,9 @@ export async function up(db) { .addColumn('address', 'varchar', (col) => col.notNull()) .addColumn('lat', 'real', (col) => col.notNull()) .addColumn('lng', 'real', (col) => col.notNull()) - .addColumn('count', 'integer', (col) => col.notNull().defaultTo(0)) + .addColumn('level', 'integer', (col) => col.notNull()) + .addColumn('last_timestamp', 'bigint', (col) => col.notNull()) + .addColumn('count', 'integer', (col) => col.notNull().defaultTo(1)) .execute(); await db.schema @@ -17,11 +19,11 @@ export async function up(db) { .addColumn('user', 'integer', (col) => col.references('user.id').notNull()) .addColumn('from_id', 'integer', (col) => col.references('favourite_locations.id').notNull()) .addColumn('to_id', 'integer', (col) => col.references('favourite_locations.id').notNull()) - .addColumn('count', 'integer', (col) => col.notNull().defaultTo(0)) + .addColumn('last_timestamp', 'bigint', (col) => col.notNull()) + .addColumn('count', 'integer', (col) => col.notNull().defaultTo(1)) .execute(); } - export async function down(db) { await db.dropTable('favourite_routes').execute(); await db.dropTable('favourite_locations').execute(); diff --git a/src/lib/i18n/de.ts b/src/lib/i18n/de.ts index 7ac3f1adf..8f4dd1f24 100644 --- a/src/lib/i18n/de.ts +++ b/src/lib/i18n/de.ts @@ -97,8 +97,8 @@ const translations: Translations = { feedbackThank: 'Vielen Dank für Ihr Feedback!', feedbackMissing: 'Kein Feedback gegeben', - invalidFrom: 'Invalid start address.', - invalidTo: 'Invalid destination address.' + invalidFrom: 'Invalide Start Addresse.', + invalidTo: 'Invalide Ziel Addresse.' }, admin: { completedToursSubtitle: 'Abgeschlossene Fahrten', diff --git a/src/lib/server/db/index.ts b/src/lib/server/db/index.ts index a0997affd..e3c6e48ce 100644 --- a/src/lib/server/db/index.ts +++ b/src/lib/server/db/index.ts @@ -104,6 +104,8 @@ export interface Database { address: string; lat: number; lng: number; + level: number; + lastTimestamp: number; count: number; }; favouriteRoutes: { @@ -111,6 +113,7 @@ export interface Database { user: number; fromId: number; toId: number; + lastTimestamp: number; count: number; }; } diff --git a/src/lib/ui/AddressTypeahead.svelte b/src/lib/ui/AddressTypeahead.svelte index 9548ef331..63698e6c7 100644 --- a/src/lib/ui/AddressTypeahead.svelte +++ b/src/lib/ui/AddressTypeahead.svelte @@ -8,10 +8,6 @@ import { language } from '$lib/i18n/translation'; import maplibregl from 'maplibre-gl'; import { onMount } from 'svelte'; - import { t } from '$lib/i18n/translation'; - import type { Column } from './tableData'; - import SortableTable from './SortableTable.svelte'; - import * as Card from '$lib/shadcn/card'; export type Location = { label?: string; @@ -51,29 +47,15 @@ selected = $bindable(), onValueChange, placeholder, - name, - favs, - selectedFav = $bindable() + name }: { items?: Array; selected?: Location; onValueChange?: (m: Location) => void; placeholder?: string; name?: string; - favs?: { address: string; lat: number; lng: number }[]; - selectedFav?: { address: string; lat: number; lng: number }[]; } = $props(); - let favRows = $derived(favs); - - const favsCols: Column<{ address: string; lat: number; lng: number }>[] = [ - { - text: [t.favourites], - sort: undefined, - toTableEntry: (r: { address: string }) => r.address - } - ]; - let inputValue = $state(''); let value = $state(''); @@ -175,25 +157,6 @@ onMount(() => ref?.focus()); -{#snippet favourites()} - {#if favRows != undefined && favRows.length != 0} - - - {t.favourites} - - - 'cursor-pointer '} - rows={favRows} - cols={favsCols} - bind:selectedRow={selectedFav} - bindSelectedRow={true} - /> - - {/if} -{/snippet} - {/if} -{@render favourites()} diff --git a/src/lib/ui/FavouriteLocations.svelte b/src/lib/ui/FavouriteLocations.svelte new file mode 100644 index 000000000..b1c7b5034 --- /dev/null +++ b/src/lib/ui/FavouriteLocations.svelte @@ -0,0 +1,38 @@ + + +{#if favRows.length != 0} + + + 'cursor-pointer '} + rows={favRows} + cols={favouritesCols} + bind:selectedRow={selectedFav} + bindSelectedRow={true} + /> + +{/if} diff --git a/src/lib/ui/FavouriteRoutes.svelte b/src/lib/ui/FavouriteRoutes.svelte new file mode 100644 index 000000000..aee0f8097 --- /dev/null +++ b/src/lib/ui/FavouriteRoutes.svelte @@ -0,0 +1,66 @@ + + +
+ + + + + {t.favourites} + + + + + {#each favRows as row} + { + selectedFav = [row]; + }} + > + +
+
+ +
+
{row.fromAddress}
+
+ +
+
+
+ +
+
{row.toAddress}
+
+
+
+ {/each} +
+
+
diff --git a/src/routes/(customer)/routing/+page.server.ts b/src/routes/(customer)/routing/+page.server.ts index dcbed3f2c..e395d490e 100644 --- a/src/routes/(customer)/routing/+page.server.ts +++ b/src/routes/(customer)/routing/+page.server.ts @@ -10,41 +10,133 @@ import { sendMail } from '$lib/server/sendMail'; import NewRide from '$lib/server/email/NewRide.svelte'; import type { PageServerLoadEvent } from './$types'; import { isSamePlace } from '$lib/util/booking/isSamePlace'; +import { DAY } from '$lib/util/time'; export async function load(event: PageServerLoadEvent) { const userId = event.locals.session?.userId; if (!userId) { return { - favs: [] + favouriteLocations: [], + favouriteRoutes: [] }; } return { - favs: await db - .selectFrom('favouriteLocations') - .where('favouriteLocations.user', '=', userId) - .orderBy('favouriteLocations.count', 'desc') - .select(['favouriteLocations.address', 'favouriteLocations.lat', 'favouriteLocations.lng']) - .limit(5) + favouriteLocations: await db + .with('top_favourites', (qb) => + qb + .selectFrom('favouriteLocations') + .select((eb) => [ + eb.lit(1).$castTo().as('sort_order'), + 'address', + 'lat', + 'lng', + 'level', + 'count', + 'id' + ]) + .where('user', '=', userId) + .orderBy('count', 'desc') + .limit(5) + ) + .with('latest', (qb) => + qb + .selectFrom('favouriteLocations') + .select((eb) => [ + eb.lit(2).$castTo().as('sort_order'), + 'address', + 'lat', + 'lng', + 'level', + 'count', + 'id' + ]) + .where('user', '=', userId) + .where('lastTimestamp', '>=', Date.now() - DAY) + .orderBy('lastTimestamp', 'desc') + .limit(1) + ) + .with('combined', (qb) => + qb.selectFrom('latest').selectAll().unionAll(qb.selectFrom('top_favourites').selectAll()) + ) + .with('ranked', (qb) => + qb + .selectFrom('combined') + .selectAll() + .select(sql`ROW_NUMBER() OVER (PARTITION BY id)`.as('rn')) + ) + .selectFrom('ranked') + .orderBy('ranked.sort_order', 'desc') + .orderBy('ranked.count', 'desc') + .select(['ranked.address', 'ranked.lat', 'ranked.lng', 'ranked.level']) + .where('ranked.rn', '=', 1) .execute(), favouriteRoutes: await db - .selectFrom('favouriteRoutes') - .where('favouriteRoutes.user', '=', userId) - .orderBy('favouriteRoutes.count desc') - .innerJoin( - 'favouriteLocations as fromLocations', - 'fromLocations.id', - 'favouriteRoutes.fromId' + .with('top_favourites', (qb) => + qb + .selectFrom('favouriteRoutes') + .innerJoin( + 'favouriteLocations as fromLocations', + 'fromLocations.id', + 'favouriteRoutes.fromId' + ) + .innerJoin('favouriteLocations as toLocations', 'toLocations.id', 'favouriteRoutes.toId') + .select((eb) => [ + eb.lit(1).$castTo().as('sort_order'), + 'toLocations.address as toAddress', + 'toLocations.lat as toLat', + 'toLocations.lng as toLng', + 'toLocations.level as toLevel', + 'fromLocations.address as fromAddress', + 'fromLocations.lat as fromLat', + 'fromLocations.lng as fromLng', + 'fromLocations.level as fromLevel', + 'favouriteRoutes.count', + 'favouriteRoutes.id' + ]) + .where('favouriteRoutes.user', '=', userId) + .orderBy('favouriteRoutes.count', 'desc') + ) + .with('latest', (qb) => + qb + .selectFrom('favouriteRoutes') + .innerJoin( + 'favouriteLocations as fromLocations', + 'fromLocations.id', + 'favouriteRoutes.fromId' + ) + .innerJoin('favouriteLocations as toLocations', 'toLocations.id', 'favouriteRoutes.toId') + .select((eb) => [ + eb.lit(2).$castTo().as('sort_order'), + 'toLocations.address as toAddress', + 'toLocations.lat as toLat', + 'toLocations.lng as toLng', + 'toLocations.level as toLevel', + 'fromLocations.address as fromAddress', + 'fromLocations.lat as fromLat', + 'fromLocations.lng as fromLng', + 'fromLocations.level as fromLevel', + 'favouriteRoutes.count', + 'favouriteRoutes.id' + ]) + .where('favouriteRoutes.user', '=', userId) + .where('favouriteRoutes.lastTimestamp', '>=', Date.now() - DAY) + .orderBy('favouriteRoutes.lastTimestamp', 'desc') + .limit(1) + ) + .with('combined', (qb) => + qb.selectFrom('latest').selectAll().unionAll(qb.selectFrom('top_favourites').selectAll()) ) - .innerJoin('favouriteLocations as toLocations', 'toLocations.id', 'favouriteRoutes.toId') - .limit(5) - .select([ - 'toLocations.address as toAddress', - 'toLocations.lat as toLat', - 'toLocations.lng as toLng', - 'fromLocations.address as fromAddress', - 'fromLocations.lat as fromLat', - 'fromLocations.lng as fromLng' - ]) + .with('ranked', (qb) => + qb + .selectFrom('combined') + .selectAll() + .select(sql`ROW_NUMBER() OVER (PARTITION BY id)`.as('rn')) + ) + .selectFrom('ranked') + .orderBy('ranked.sort_order', 'desc') + .orderBy('ranked.count', 'desc') + .select(['ranked.fromAddress', 'ranked.toAddress', 'ranked.fromLat', 'ranked.toLat', 'ranked.fromLng', 'ranked.toLng', 'ranked.fromLevel', 'ranked.toLevel']) + .where('ranked.rn', '=', 1) .execute() }; } @@ -59,7 +151,7 @@ const getCommonTour = (l1: Set, l2: Set) => { }; export const actions = { - routing: async ({ request, locals }): Promise<{ msg: Msg }> => { + booking: async ({ request, locals }): Promise<{ msg: Msg }> => { const user = locals.session?.userId; if (!user) { return { msg: msg('accountDoesNotExist') }; @@ -318,7 +410,7 @@ export const actions = { return { msg: message! }; }, - fav: async ({ request, locals }) => { + updateFavourites: async ({ request, locals }) => { const user = locals.session?.userId; if (!user || typeof user != 'number') { return { msg: msg('accountDoesNotExist') }; @@ -327,87 +419,137 @@ export const actions = { const fromAddress = formData.get('fromAddress'); const fromLat = formData.get('fromLat'); const fromLon = formData.get('fromLon'); + const fromLevel = formData.get('fromLevel'); const toAddress = formData.get('toAddress'); const toLat = formData.get('toLat'); const toLon = formData.get('toLon'); + const toLevel = formData.get('toLevel'); if ( typeof fromAddress !== 'string' || typeof fromLat !== 'string' || + typeof fromLevel !== 'string' || typeof fromLon !== 'string' ) { return { msg: msg('invalidFrom') }; } - let lat = parseFloat(fromLat); - let lng = parseFloat(fromLon); - if (typeof lat !== 'number' || typeof lng !== 'number') { + const fromLatitude = parseFloat(fromLat); + const fromLongtitude = parseFloat(fromLon); + const fromLvl = parseInt(fromLevel); + if (isNaN(fromLatitude) || isNaN(fromLongtitude) || isNaN(fromLvl)) { return { msg: msg('invalidFrom') }; } - let currentFavs = await db + let currentFavourites = await db .selectFrom('favouriteLocations') .where('user', '=', user) .selectAll() .execute(); - const fromMatch = currentFavs.find((fav) => isSamePlace(fav, { lat, lng })); + const fromMatch = currentFavourites.find( + (fav) => isSamePlace(fav, { lat: fromLatitude, lng: fromLongtitude }) && fav.level === fromLvl + ); let fromId = undefined; if (fromMatch) { await db .updateTable('favouriteLocations') .where('user', '=', user) .where('favouriteLocations.id', '=', fromMatch.id) - .set({ count: fromMatch.count + 1 }) + .set({ count: fromMatch.count + 1, lastTimestamp: Date.now() }) .execute(); } else { fromId = (await db .insertInto('favouriteLocations') - .values({ lat, lng, address: fromAddress, user, count: 0 }) + .values({ + lat: fromLatitude, + lng: fromLongtitude, + level: fromLvl, + address: fromAddress, + user, + count: 1, + lastTimestamp: Date.now() + }) .returning('id') .executeTakeFirst())!.id; } - if (typeof toAddress !== 'string' || typeof toLat !== 'string' || typeof toLon !== 'string') { + if ( + typeof toAddress !== 'string' || + typeof toLat !== 'string' || + typeof toLon !== 'string' || + typeof toLevel !== 'string' + ) { return { msg: msg('invalidFrom') }; } - lat = parseFloat(toLat); - lng = parseFloat(toLon); - if (isNaN(lat) || isNaN(lng)) { + const toLatitude = parseFloat(toLat); + const toLongitude = parseFloat(toLon); + const toLvl = parseInt(toLevel); + if (isNaN(toLatitude) || isNaN(toLongitude) || isNaN(toLvl)) { return { msg: msg('invalidFrom') }; } - currentFavs = await db + currentFavourites = await db .selectFrom('favouriteLocations') .where('user', '=', user) .selectAll() .execute(); - const toMatch = currentFavs.find((fav) => isSamePlace(fav, { lat, lng })); + const toMatch = currentFavourites.find( + (fav) => isSamePlace(fav, { lat: toLatitude, lng: toLongitude }) && fav.level === toLvl + ); let toId = undefined; if (toMatch) { await db .updateTable('favouriteLocations') .where('user', '=', user) .where('favouriteLocations.id', '=', toMatch.id) - .set({ count: toMatch.count + 1 }) + .set({ count: toMatch.count + 1, lastTimestamp: Date.now() }) .execute(); } else { toId = (await db .insertInto('favouriteLocations') - .values({ lat, lng, address: toAddress, user, count: 0 }) + .values({ + lat: toLatitude, + lng: toLongitude, + level: toLvl, + address: toAddress, + user, + count: 1, + lastTimestamp: Date.now() + }) .returning(['favouriteLocations.id']) .executeTakeFirst())!.id; } toId = toId ?? toMatch?.id; fromId = fromId ?? fromMatch?.id; - if (toId == undefined || fromId == undefined) { + if ( + toId == undefined || + fromId == undefined || + isSamePlace({ lat: fromLatitude, lng: fromLongtitude }, { lat: toLatitude, lng: toLongitude }) + ) { return {}; } if (fromMatch && toMatch) { - await db - .updateTable('favouriteRoutes') + const currentFavouriteRoutes = await db + .selectFrom('favouriteRoutes') .where('user', '=', user) .where('fromId', '=', fromMatch.id) .where('toId', '=', toMatch.id) - .set((eb) => ({ count: eb('count', '+', 1) })) - .execute(); + .select(['favouriteRoutes.id']) + .executeTakeFirst(); + if (currentFavouriteRoutes) { + await db + .updateTable('favouriteRoutes') + .where('favouriteRoutes.id', '=', currentFavouriteRoutes.id) + .set((eb) => ({ count: eb('count', '+', 1), lastTimestamp: Date.now() })) + .returning('favouriteRoutes.id') + .execute(); + } else { + await db + .insertInto('favouriteRoutes') + .values({ user, toId, fromId, count: 1, lastTimestamp: Date.now() }) + .execute(); + } } else { - await db.insertInto('favouriteRoutes').values({ user, toId, fromId, count: 0 }).execute(); + await db + .insertInto('favouriteRoutes') + .values({ user, toId, fromId, count: 1, lastTimestamp: Date.now() }) + .execute(); } return {}; } diff --git a/src/routes/(customer)/routing/+page.svelte b/src/routes/(customer)/routing/+page.svelte index d24e4fd75..fedc853c3 100644 --- a/src/routes/(customer)/routing/+page.svelte +++ b/src/routes/(customer)/routing/+page.svelte @@ -44,9 +44,9 @@ import { posToLocation } from '$lib/map/Location'; import { MAX_MATCHING_DISTANCE } from '$lib/constants'; import PopupMap from '$lib/ui/PopupMap.svelte'; - import SortableTable from '$lib/ui/SortableTable.svelte'; - import type { Column } from '$lib/ui/tableData'; import * as Card from '$lib/shadcn/card'; + import FavouritesList from '$lib/ui/FavouriteLocations.svelte'; + import FavouriteRoutes from '$lib/ui/FavouriteRoutes.svelte'; type LuggageType = 'none' | 'light' | 'heavy'; @@ -125,14 +125,17 @@ clearTimeout(searchDebounceTimer); searchDebounceTimer = setTimeout(async () => { if (from.label && from.value.match && to.label && to.value.match) { + console.log('upd favs'); const formData = new FormData(); formData.append('fromAddress', from.label); formData.append('fromLat', from.value.match.lat.toString()); formData.append('fromLon', from.value.match.lon.toString()); + formData.append('fromLevel', from.value.match.level?.toString() ?? '0'); formData.append('toAddress', to.label); formData.append('toLat', to.value.match.lat.toString()); formData.append('toLon', to.value.match.lon.toString()); - await fetch('?/fav', { + formData.append('toLevel', to.value.match.level?.toString() ?? '0'); + await fetch('?/updateFavourites', { method: 'POST', body: formData }); @@ -173,86 +176,82 @@ from = posToLocation({ lat: position.coords.latitude, lon: position.coords.longitude }, 0); }; - let selectedToFav: { address: string; lat: number; lng: number }[] | undefined = + let selectedToFav: { address: string; lat: number; lng: number; level: number }[] | undefined = $state(undefined); - let selectedFromFav: { address: string; lat: number; lng: number }[] | undefined = + let selectedFromFav: { address: string; lat: number; lng: number; level: number }[] | undefined = $state(undefined); let selectedFav: | { fromAddress: string; fromLat: number; fromLng: number; + fromLevel: number; toAddress: string; toLat: number; toLng: number; + toLevel: number; }[] | undefined = $state(undefined); - const favsCols: Column<{ - fromAddress: string; - fromLat: number; - fromLng: number; - toAddress: string; - toLat: number; - toLng: number; - }>[] = [ - { - text: [t.from], - sort: undefined, - toTableEntry: (r: { fromAddress: string }) => r.fromAddress - }, - { - text: [t.to], - sort: undefined, - toTableEntry: (r: { toAddress: string }) => r.toAddress - } - ]; $effect(() => { if (selectedFromFav && selectedFromFav.length != 0) { - const fav = selectedFromFav[0]; - from = posToLocation({ lat: fav.lat, lng: fav.lng }, 0, fav.address); + const favourite = selectedFromFav[0]; + from = posToLocation( + { lat: favourite.lat, lng: favourite.lng }, + favourite.level, + favourite.address + ); history.back(); } }); $effect(() => { if (selectedToFav && selectedToFav.length != 0) { - const fav = selectedToFav[0]; - to = posToLocation({ lat: fav.lat, lng: fav.lng }, 0, fav.address); + const favourite = selectedToFav[0]; + to = posToLocation( + { lat: favourite.lat, lng: favourite.lng }, + favourite.level, + favourite.address + ); history.back(); } }); $effect(() => { if (selectedFav && selectedFav.length != 0) { - const fav = selectedFav[0]; - from = posToLocation({ lat: fav.fromLat, lng: fav.fromLng }, 0, fav.fromAddress); - to = posToLocation({ lat: fav.toLat, lng: fav.toLng }, 0, fav.toAddress); + const favourite = selectedFav[0]; + from = posToLocation( + { lat: favourite.fromLat, lng: favourite.fromLng }, + favourite.fromLevel, + favourite.fromAddress + ); + to = posToLocation( + { lat: favourite.toLat, lng: favourite.toLng }, + favourite.toLevel, + favourite.toAddress + ); } });
- {#if page.state.selectFrom} history.back()} - favs={data.favs} - bind:selectedFav={selectedFromFav} /> + {:else if page.state.selectTo} history.back()} - favs={data.favs} - bind:selectedFav={selectedToFav} /> + {:else if page.state.showMap} {:else if page.state.selectedItinerary} @@ -298,7 +297,7 @@ page.state.selectedItinerary.legs.length === 1 && page.state.selectedItinerary.legs[0].mode === 'ODM'} - + {#if baseQuery == undefined && data.favouriteRoutes && data.favouriteRoutes.length != 0} - - {t.favourites} - - 'cursor-pointer '} - rows={data.favouriteRoutes} - cols={favsCols} - bind:selectedRow={selectedFav} - bindSelectedRow={true} - /> + {/if} diff --git a/src/routes/taxi/company/+page.svelte b/src/routes/taxi/company/+page.svelte index e2729aaf4..2572ab304 100644 --- a/src/routes/taxi/company/+page.svelte +++ b/src/routes/taxi/company/+page.svelte @@ -90,7 +90,6 @@ (companyAddress = l)} placeholder={'Unternehmenssitz'} bind:selected={companyAddress} From b1f90a1485584f3ecac8236a79a4673457460f20 Mon Sep 17 00:00:00 2001 From: nils penzel Date: Wed, 16 Apr 2025 12:05:55 +0200 Subject: [PATCH 13/18] wip --- src/routes/(customer)/routing/+page.server.ts | 122 +++++++++++++++--- 1 file changed, 107 insertions(+), 15 deletions(-) diff --git a/src/routes/(customer)/routing/+page.server.ts b/src/routes/(customer)/routing/+page.server.ts index e395d490e..28ded5dec 100644 --- a/src/routes/(customer)/routing/+page.server.ts +++ b/src/routes/(customer)/routing/+page.server.ts @@ -20,18 +20,100 @@ export async function load(event: PageServerLoadEvent) { favouriteRoutes: [] }; } + console.log( + 'test: ', + await db + .with('top_favourites', (qb) => + qb + .selectFrom('favouriteRoutes') + .innerJoin( + 'favouriteLocations as fromLocations', + 'fromLocations.id', + 'favouriteRoutes.fromId' + ) + .innerJoin('favouriteLocations as toLocations', 'toLocations.id', 'favouriteRoutes.toId') + .select((eb) => [ + eb.lit(1).$castTo().as('sort_order_1'), + 'favouriteRoutes.count as sort_order_2', + 'toLocations.address as toAddress', + 'toLocations.lat as toLat', + 'toLocations.lng as toLng', + 'toLocations.level as toLevel', + 'fromLocations.address as fromAddress', + 'fromLocations.lat as fromLat', + 'fromLocations.lng as fromLng', + 'fromLocations.level as fromLevel', + 'favouriteRoutes.id' + ]) + .where('favouriteRoutes.user', '=', userId) + .orderBy('favouriteRoutes.count', 'desc') + ) + .with('latest', (qb) => + qb + .selectFrom('favouriteRoutes') + .innerJoin( + 'favouriteLocations as fromLocations', + 'fromLocations.id', + 'favouriteRoutes.fromId' + ) + .innerJoin('favouriteLocations as toLocations', 'toLocations.id', 'favouriteRoutes.toId') + .select((eb) => [ + eb.lit(2).$castTo().as('sort_order_1'), + eb.ref('favouriteRoutes.lastTimestamp').$castTo().as('sort_order_2'), + 'toLocations.address as toAddress', + 'toLocations.lat as toLat', + 'toLocations.lng as toLng', + 'toLocations.level as toLevel', + 'fromLocations.address as fromAddress', + 'fromLocations.lat as fromLat', + 'fromLocations.lng as fromLng', + 'fromLocations.level as fromLevel', + 'favouriteRoutes.id' + ]) + .where('favouriteRoutes.user', '=', userId) + .where('favouriteRoutes.lastTimestamp', '>=', Date.now() - DAY) + .orderBy('favouriteRoutes.lastTimestamp', 'desc') + .limit(2) + ) + .with('combined', (qb) => + qb.selectFrom('latest').selectAll().unionAll(qb.selectFrom('top_favourites').selectAll()) + ) + .with('ranked', (qb) => + qb + .selectFrom('combined') + .selectAll() + .select(sql`ROW_NUMBER() OVER (PARTITION BY id)`.as('rn')) + ) + .selectFrom('ranked') + .orderBy('ranked.sort_order_2', 'desc') + .select([ + 'ranked.fromAddress', + 'ranked.toAddress', + 'ranked.fromLat', + 'ranked.toLat', + 'ranked.fromLng', + 'ranked.toLng', + 'ranked.fromLevel', + 'ranked.toLevel', + 'sort_order_2' + ]) + .where('ranked.rn', '=', 1) + .where('sort_order_1', '=', 2) + .execute() + ); return { favouriteLocations: await db .with('top_favourites', (qb) => qb .selectFrom('favouriteLocations') .select((eb) => [ - eb.lit(1).$castTo().as('sort_order'), + eb.lit(1).$castTo().as('sort_order_1'), 'address', 'lat', 'lng', 'level', - 'count', + 'count as sort_order_2', + 'lastTimestamp', 'id' ]) .where('user', '=', userId) @@ -42,18 +124,19 @@ export async function load(event: PageServerLoadEvent) { qb .selectFrom('favouriteLocations') .select((eb) => [ - eb.lit(2).$castTo().as('sort_order'), + eb.lit(2).$castTo().as('sort_order_1'), 'address', 'lat', 'lng', 'level', - 'count', + 'count as sort_order_2', + 'lastTimestamp', 'id' ]) .where('user', '=', userId) .where('lastTimestamp', '>=', Date.now() - DAY) .orderBy('lastTimestamp', 'desc') - .limit(1) + .limit(2) ) .with('combined', (qb) => qb.selectFrom('latest').selectAll().unionAll(qb.selectFrom('top_favourites').selectAll()) @@ -65,8 +148,8 @@ export async function load(event: PageServerLoadEvent) { .select(sql`ROW_NUMBER() OVER (PARTITION BY id)`.as('rn')) ) .selectFrom('ranked') - .orderBy('ranked.sort_order', 'desc') - .orderBy('ranked.count', 'desc') + .orderBy('ranked.sort_order_1', 'desc') + .orderBy('ranked.sort_order_2', 'desc') .select(['ranked.address', 'ranked.lat', 'ranked.lng', 'ranked.level']) .where('ranked.rn', '=', 1) .execute(), @@ -81,7 +164,8 @@ export async function load(event: PageServerLoadEvent) { ) .innerJoin('favouriteLocations as toLocations', 'toLocations.id', 'favouriteRoutes.toId') .select((eb) => [ - eb.lit(1).$castTo().as('sort_order'), + eb.lit(1).$castTo().as('sort_order_1'), + 'favouriteRoutes.count as sort_order_2', 'toLocations.address as toAddress', 'toLocations.lat as toLat', 'toLocations.lng as toLng', @@ -90,7 +174,6 @@ export async function load(event: PageServerLoadEvent) { 'fromLocations.lat as fromLat', 'fromLocations.lng as fromLng', 'fromLocations.level as fromLevel', - 'favouriteRoutes.count', 'favouriteRoutes.id' ]) .where('favouriteRoutes.user', '=', userId) @@ -106,7 +189,8 @@ export async function load(event: PageServerLoadEvent) { ) .innerJoin('favouriteLocations as toLocations', 'toLocations.id', 'favouriteRoutes.toId') .select((eb) => [ - eb.lit(2).$castTo().as('sort_order'), + eb.lit(2).$castTo().as('sort_order_1'), + eb.ref('favouriteRoutes.lastTimestamp').$castTo().as('sort_order_2'), 'toLocations.address as toAddress', 'toLocations.lat as toLat', 'toLocations.lng as toLng', @@ -115,13 +199,12 @@ export async function load(event: PageServerLoadEvent) { 'fromLocations.lat as fromLat', 'fromLocations.lng as fromLng', 'fromLocations.level as fromLevel', - 'favouriteRoutes.count', 'favouriteRoutes.id' ]) .where('favouriteRoutes.user', '=', userId) .where('favouriteRoutes.lastTimestamp', '>=', Date.now() - DAY) .orderBy('favouriteRoutes.lastTimestamp', 'desc') - .limit(1) + .limit(2) ) .with('combined', (qb) => qb.selectFrom('latest').selectAll().unionAll(qb.selectFrom('top_favourites').selectAll()) @@ -133,9 +216,18 @@ export async function load(event: PageServerLoadEvent) { .select(sql`ROW_NUMBER() OVER (PARTITION BY id)`.as('rn')) ) .selectFrom('ranked') - .orderBy('ranked.sort_order', 'desc') - .orderBy('ranked.count', 'desc') - .select(['ranked.fromAddress', 'ranked.toAddress', 'ranked.fromLat', 'ranked.toLat', 'ranked.fromLng', 'ranked.toLng', 'ranked.fromLevel', 'ranked.toLevel']) + .orderBy('ranked.sort_order_1', 'desc') + .orderBy('ranked.sort_order_2', 'desc') + .select([ + 'ranked.fromAddress', + 'ranked.toAddress', + 'ranked.fromLat', + 'ranked.toLat', + 'ranked.fromLng', + 'ranked.toLng', + 'ranked.fromLevel', + 'ranked.toLevel' + ]) .where('ranked.rn', '=', 1) .execute() }; From f318417c9f8776d0517f60b6d9aaca3a7cba6ef5 Mon Sep 17 00:00:00 2001 From: nils penzel Date: Wed, 16 Apr 2025 13:12:17 +0200 Subject: [PATCH 14/18] wip --- migrations/2025-03-21-favourites.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migrations/2025-03-21-favourites.js b/migrations/2025-03-21-favourites.js index 9df9eb497..63053a4d4 100644 --- a/migrations/2025-03-21-favourites.js +++ b/migrations/2025-03-21-favourites.js @@ -17,8 +17,8 @@ export async function up(db) { .createTable('favourite_routes') .addColumn('id', 'serial', (col) => col.primaryKey()) .addColumn('user', 'integer', (col) => col.references('user.id').notNull()) - .addColumn('from_id', 'integer', (col) => col.references('favourite_locations.id').notNull()) - .addColumn('to_id', 'integer', (col) => col.references('favourite_locations.id').notNull()) + .addColumn('from_id', 'integer', (col) => col.references('favourite_locations.id').notNull()) + .addColumn('to_id', 'integer', (col) => col.references('favourite_locations.id').notNull()) .addColumn('last_timestamp', 'bigint', (col) => col.notNull()) .addColumn('count', 'integer', (col) => col.notNull().defaultTo(1)) .execute(); From 187004a358fdfefaa51062b0758af3aaf21c40c2 Mon Sep 17 00:00:00 2001 From: nils penzel Date: Thu, 17 Apr 2025 11:31:21 +0200 Subject: [PATCH 15/18] wip --- src/lib/ui/FavouriteLocations.svelte | 16 ++++---- src/lib/ui/FavouriteRoutes.svelte | 10 ++--- src/routes/(customer)/routing/+page.server.ts | 7 +++- src/routes/(customer)/routing/+page.svelte | 40 ++++++++++++------- 4 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/lib/ui/FavouriteLocations.svelte b/src/lib/ui/FavouriteLocations.svelte index b1c7b5034..f28e58d27 100644 --- a/src/lib/ui/FavouriteLocations.svelte +++ b/src/lib/ui/FavouriteLocations.svelte @@ -6,15 +6,15 @@ let { favourites, - selectedFav = $bindable() + selectedFavourite = $bindable() }: { favourites: { address: string; lat: number; lng: number; level: number }[]; - selectedFav?: { address: string; lat: number; lng: number; level: number }[]; + selectedFavourite?: { address: string; lat: number; lng: number; level: number }[]; } = $props(); - let favRows = $derived(favourites); + let favouriteRows = $derived(favourites); - const favouritesCols: Column<{ address: string; lat: number; lng: number }>[] = [ + const favouriteCols: Column<{ address: string; lat: number; lng: number }>[] = [ { text: [t.favourites], sort: undefined, @@ -23,14 +23,14 @@ ]; -{#if favRows.length != 0} +{#if favouriteRows.length != 0} 'cursor-pointer '} - rows={favRows} - cols={favouritesCols} - bind:selectedRow={selectedFav} + rows={favouriteRows} + cols={favouriteCols} + bind:selectedRow={selectedFavourite} bindSelectedRow={true} />
@@ -36,11 +36,11 @@ - {#each favRows as row} + {#each favouriteRows as row} { - selectedFav = [row]; + selectedFavourite = [row]; }} > diff --git a/src/routes/(customer)/routing/+page.server.ts b/src/routes/(customer)/routing/+page.server.ts index 28ded5dec..d5f81a58f 100644 --- a/src/routes/(customer)/routing/+page.server.ts +++ b/src/routes/(customer)/routing/+page.server.ts @@ -536,7 +536,9 @@ export const actions = { .selectAll() .execute(); const fromMatch = currentFavourites.find( - (fav) => isSamePlace(fav, { lat: fromLatitude, lng: fromLongtitude }) && fav.level === fromLvl + (favourite) => + isSamePlace(favourite, { lat: fromLatitude, lng: fromLongtitude }) && + favourite.level === fromLvl ); let fromId = undefined; if (fromMatch) { @@ -582,7 +584,8 @@ export const actions = { .selectAll() .execute(); const toMatch = currentFavourites.find( - (fav) => isSamePlace(fav, { lat: toLatitude, lng: toLongitude }) && fav.level === toLvl + (favourite) => + isSamePlace(favourite, { lat: toLatitude, lng: toLongitude }) && favourite.level === toLvl ); let toId = undefined; if (toMatch) { diff --git a/src/routes/(customer)/routing/+page.svelte b/src/routes/(customer)/routing/+page.svelte index fedc853c3..c0892d72f 100644 --- a/src/routes/(customer)/routing/+page.svelte +++ b/src/routes/(customer)/routing/+page.svelte @@ -125,7 +125,6 @@ clearTimeout(searchDebounceTimer); searchDebounceTimer = setTimeout(async () => { if (from.label && from.value.match && to.label && to.value.match) { - console.log('upd favs'); const formData = new FormData(); formData.append('fromAddress', from.label); formData.append('fromLat', from.value.match.lat.toString()); @@ -176,11 +175,13 @@ from = posToLocation({ lat: position.coords.latitude, lon: position.coords.longitude }, 0); }; - let selectedToFav: { address: string; lat: number; lng: number; level: number }[] | undefined = - $state(undefined); - let selectedFromFav: { address: string; lat: number; lng: number; level: number }[] | undefined = - $state(undefined); - let selectedFav: + let selectedToFavourite: + | { address: string; lat: number; lng: number; level: number }[] + | undefined = $state(undefined); + let selectedFromFavourite: + | { address: string; lat: number; lng: number; level: number }[] + | undefined = $state(undefined); + let selectedRouteFavourite: | { fromAddress: string; fromLat: number; @@ -194,8 +195,8 @@ | undefined = $state(undefined); $effect(() => { - if (selectedFromFav && selectedFromFav.length != 0) { - const favourite = selectedFromFav[0]; + if (selectedFromFavourite && selectedFromFavourite.length != 0) { + const favourite = selectedFromFavourite[0]; from = posToLocation( { lat: favourite.lat, lng: favourite.lng }, favourite.level, @@ -206,8 +207,8 @@ }); $effect(() => { - if (selectedToFav && selectedToFav.length != 0) { - const favourite = selectedToFav[0]; + if (selectedToFavourite && selectedToFavourite.length != 0) { + const favourite = selectedToFavourite[0]; to = posToLocation( { lat: favourite.lat, lng: favourite.lng }, favourite.level, @@ -218,8 +219,8 @@ }); $effect(() => { - if (selectedFav && selectedFav.length != 0) { - const favourite = selectedFav[0]; + if (selectedRouteFavourite && selectedRouteFavourite.length != 0) { + const favourite = selectedRouteFavourite[0]; from = posToLocation( { lat: favourite.fromLat, lng: favourite.fromLng }, favourite.fromLevel, @@ -243,7 +244,10 @@ items={fromItems} onValueChange={() => history.back()} /> - + {:else if page.state.selectTo} history.back()} /> - + {:else if page.state.showMap} {:else if page.state.selectedItinerary} @@ -533,7 +540,10 @@ {#if baseQuery == undefined && data.favouriteRoutes && data.favouriteRoutes.length != 0} - + {/if} From 1edb83ed3bbfec28732d460dcbc3948d4d7a49d4 Mon Sep 17 00:00:00 2001 From: nils Date: Sun, 20 Apr 2025 11:13:38 +0200 Subject: [PATCH 16/18] wip --- src/lib/ui/DisplayAddresses.svelte | 22 +++++++++++++++++++ src/lib/ui/FavouriteRoutes.svelte | 19 ++-------------- .../routing/ItinerarySummary.svelte | 3 ++- 3 files changed, 26 insertions(+), 18 deletions(-) create mode 100644 src/lib/ui/DisplayAddresses.svelte diff --git a/src/lib/ui/DisplayAddresses.svelte b/src/lib/ui/DisplayAddresses.svelte new file mode 100644 index 000000000..61fef8f4d --- /dev/null +++ b/src/lib/ui/DisplayAddresses.svelte @@ -0,0 +1,22 @@ + + +
+
+ +
+
{fromAddress}
+
+ +
+
+
+ +
+
{toAddress}
+
diff --git a/src/lib/ui/FavouriteRoutes.svelte b/src/lib/ui/FavouriteRoutes.svelte index f001dfae7..683037445 100644 --- a/src/lib/ui/FavouriteRoutes.svelte +++ b/src/lib/ui/FavouriteRoutes.svelte @@ -1,9 +1,7 @@ -
-
- -
-
{fromAddress}
-
- +
+
+ +
+
-
-
- +
+
{fromAddress}
+
+
{toAddress}
-
{toAddress}
From 367d7213157a7853d12e04528322645b09ec2716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCndling?= Date: Wed, 23 Apr 2025 14:42:31 +0200 Subject: [PATCH 18/18] wip --- src/lib/ui/FavouriteLocations.svelte | 15 +++++++--- src/routes/(customer)/routing/+page.svelte | 34 ++++++++++------------ 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/lib/ui/FavouriteLocations.svelte b/src/lib/ui/FavouriteLocations.svelte index f28e58d27..302b41557 100644 --- a/src/lib/ui/FavouriteLocations.svelte +++ b/src/lib/ui/FavouriteLocations.svelte @@ -4,12 +4,19 @@ import SortableTable from './SortableTable.svelte'; import * as Card from '$lib/shadcn/card'; + export type Favourite = { + address: string; + lat: number; + lng: number; + level: number; + }; + let { favourites, selectedFavourite = $bindable() }: { - favourites: { address: string; lat: number; lng: number; level: number }[]; - selectedFavourite?: { address: string; lat: number; lng: number; level: number }[]; + favourites: Favourite[]; + selectedFavourite?: Favourite[]; } = $props(); let favouriteRows = $derived(favourites); @@ -33,6 +40,6 @@ bind:selectedRow={selectedFavourite} bindSelectedRow={true} /> - + + {/if} diff --git a/src/routes/(customer)/routing/+page.svelte b/src/routes/(customer)/routing/+page.svelte index c0892d72f..e1cbeeaa9 100644 --- a/src/routes/(customer)/routing/+page.svelte +++ b/src/routes/(customer)/routing/+page.svelte @@ -45,7 +45,7 @@ import { MAX_MATCHING_DISTANCE } from '$lib/constants'; import PopupMap from '$lib/ui/PopupMap.svelte'; import * as Card from '$lib/shadcn/card'; - import FavouritesList from '$lib/ui/FavouriteLocations.svelte'; + import FavouritesList, { type Favourite } from '$lib/ui/FavouriteLocations.svelte'; import FavouriteRoutes from '$lib/ui/FavouriteRoutes.svelte'; type LuggageType = 'none' | 'light' | 'heavy'; @@ -175,24 +175,20 @@ from = posToLocation({ lat: position.coords.latitude, lon: position.coords.longitude }, 0); }; - let selectedToFavourite: - | { address: string; lat: number; lng: number; level: number }[] - | undefined = $state(undefined); - let selectedFromFavourite: - | { address: string; lat: number; lng: number; level: number }[] - | undefined = $state(undefined); - let selectedRouteFavourite: - | { - fromAddress: string; - fromLat: number; - fromLng: number; - fromLevel: number; - toAddress: string; - toLat: number; - toLng: number; - toLevel: number; - }[] - | undefined = $state(undefined); + let selectedToFavourite = $state(); + let selectedFromFavourite = $state(); + let selectedRouteFavourite = $state< + { + fromAddress: string; + fromLat: number; + fromLng: number; + fromLevel: number; + toAddress: string; + toLat: number; + toLng: number; + toLevel: number; + }[] + >(); $effect(() => { if (selectedFromFavourite && selectedFromFavourite.length != 0) {