From 4aabf471082dfb8d6c026b5f1a566393858ac65a Mon Sep 17 00:00:00 2001 From: AI Agent Date: Tue, 24 Feb 2026 20:17:26 +0000 Subject: [PATCH] trip) add day/week trip views (#52) Reviewed-on: https://cloud.campbellwireless.net/git/campbellwireless/trips/pulls/52 Co-authored-by: AI Agent Co-committed-by: AI Agent --- .env.test | 2 +- e2e/helpers/trip.ts | 5 +- e2e/setup/seed-db.ts | 15 +- e2e/trip-date-view.test.ts | 70 ++++ playwright.config.ts | 8 +- src/lib/date-views.test.ts | 34 ++ src/lib/date-views.ts | 33 ++ src/lib/server/db/migrations.ts | 35 ++ .../(protected)/trips/[id]/+page.svelte | 347 +++++++++++++++--- 9 files changed, 500 insertions(+), 49 deletions(-) create mode 100644 e2e/trip-date-view.test.ts create mode 100644 src/lib/date-views.test.ts create mode 100644 src/lib/date-views.ts diff --git a/.env.test b/.env.test index 40285b6..8ed88f7 100644 --- a/.env.test +++ b/.env.test @@ -13,7 +13,7 @@ LOCAL_AUTH_ARGON2_PARALLELISM=1 ADMIN_USER_IDS=e2e_admin # Separate test database — never touches trips.db -DATABASE_URL=file:trips.test.db +DATABASE_URL=file:/tmp/trips.test.db # Synology OIDC — not exercised in E2E tests but must be present to avoid startup errors SYNOLOGY_ISSUER=https://cloud.campbellwireless.net/auth/webman/sso diff --git a/e2e/helpers/trip.ts b/e2e/helpers/trip.ts index 684b207..3ba50e4 100644 --- a/e2e/helpers/trip.ts +++ b/e2e/helpers/trip.ts @@ -12,7 +12,7 @@ export function uniqueSuffix(): string { export async function createTrip( page: Page, - values: { name: string; startDate?: string; description?: string } + values: { name: string; startDate?: string; endDate?: string; description?: string } ): Promise<{ tripUrl: string; tripId: string }> { await page.goto(NEW_TRIP_URL); await expect(page.getByRole('heading', { name: 'Plan New Trip' })).toBeVisible(); @@ -22,6 +22,9 @@ export async function createTrip( } else { await form.getByRole('checkbox', { name: "I don't know yet" }).first().check(); } + if (values.endDate) { + await form.getByLabel('End date').fill(values.endDate); + } if (values.description) { await form.getByLabel('Description').fill(values.description); } diff --git a/e2e/setup/seed-db.ts b/e2e/setup/seed-db.ts index 69244af..7dcd46a 100644 --- a/e2e/setup/seed-db.ts +++ b/e2e/setup/seed-db.ts @@ -10,7 +10,17 @@ import { TEST_USERS } from './test-users.js'; dotenvConfig({ path: resolve(process.cwd(), '.env.test'), override: true }); -const DB_PATH = resolve(process.cwd(), 'trips.test.db'); +function resolveDatabasePath(): string { + const rawUrl = process.env.DATABASE_URL; + if (rawUrl && rawUrl.startsWith('file:')) { + const rawPath = rawUrl.slice('file:'.length); + if (!rawPath) return resolve(process.cwd(), 'trips.test.db'); + return rawPath.startsWith('/') ? rawPath : resolve(process.cwd(), rawPath); + } + return resolve(process.cwd(), 'trips.test.db'); +} + +const DB_PATH = resolveDatabasePath(); // Delete any existing test DB so we start clean each run for (const ext of ['', '-shm', '-wal']) { @@ -20,6 +30,7 @@ for (const ext of ['', '-shm', '-wal']) { } } +console.log(`[e2e] Using test database at ${DB_PATH}`); const db = new BunSqlite(DB_PATH); db.exec('PRAGMA foreign_keys = ON'); @@ -40,6 +51,7 @@ const dbWrapper = { } }; +console.log('[e2e] Running migrations...'); const { runMigrations } = await import('../../src/lib/server/db/migrations.js'); runMigrations(dbWrapper); console.log('[e2e] Migrations complete.'); @@ -53,6 +65,7 @@ async function hashPassword(password: string): Promise { }); } +console.log('[e2e] Creating test users...'); const regularId = randomUUID(); dbWrapper.run( `INSERT INTO users (id, username, full_name, email, auth_source) VALUES (?, ?, ?, ?, ?)`, diff --git a/e2e/trip-date-view.test.ts b/e2e/trip-date-view.test.ts new file mode 100644 index 0000000..0c55c3a --- /dev/null +++ b/e2e/trip-date-view.test.ts @@ -0,0 +1,70 @@ +import { test, expect } from '@playwright/test'; +import { TEST_USERS } from './setup/test-users.js'; +import { loginAsLocalUser, ensureSelfProfile } from './helpers/auth.js'; +import { createTrip, openAddToTripMenuItem, uniqueSuffix } from './helpers/trip.js'; + +function formatDate(offsetDays: number): string { + const date = new Date(); + date.setDate(date.getDate() + offsetDays); + return date.toISOString().slice(0, 10); +} + +async function addActivity( + page: import('@playwright/test').Page, + values: { name: string; startDate: string; startTime: string } +) { + await openAddToTripMenuItem(page, 'Attractions & Activities'); + const dialog = page + .locator('[role="dialog"]') + .filter({ has: page.getByRole('heading', { name: /Attraction & activity/i }) }) + .first(); + await expect(dialog).toBeVisible(); + + await dialog.getByLabel('Name', { exact: false }).fill(values.name); + await dialog.getByLabel('Start date').fill(values.startDate); + await dialog.getByLabel('Start time', { exact: true }).fill(values.startTime); + await dialog.getByRole('button', { name: 'Add' }).click(); + await expect(dialog).toBeHidden(); +} + +test.beforeEach(async ({ context }) => { + await context.clearCookies(); +}); + +test('day and week views filter itinerary items', async ({ page }) => { + const suffix = uniqueSuffix(); + const startDate = formatDate(0); + const endDate = formatDate(9); + const dayOneActivity = `Museum visit ${suffix}`; + const dayEightActivity = `Harbor walk ${suffix}`; + + await loginAsLocalUser(page, TEST_USERS.regular.username, TEST_USERS.regular.password); + await ensureSelfProfile(page); + + await createTrip(page, { + name: `E2E Date Views Trip ${suffix}`, + startDate, + endDate, + description: 'Date view coverage' + }); + + await addActivity(page, { name: dayOneActivity, startDate, startTime: '09:00' }); + await addActivity(page, { name: dayEightActivity, startDate: formatDate(7), startTime: '11:00' }); + await page.reload(); + await expect(page.getByText(dayOneActivity)).toBeVisible(); + + await page.getByRole('button', { name: 'Day' }).click(); + await expect(page.getByText('Day 1')).toBeVisible(); + await expect(page.getByText(dayOneActivity)).toBeVisible(); + await expect(page.getByText(dayEightActivity)).toBeHidden(); + + await page.getByRole('button', { name: 'Week' }).click(); + await expect(page.getByText('Week 1')).toBeVisible(); + await expect(page.getByText(dayOneActivity)).toBeVisible(); + await expect(page.getByText(dayEightActivity)).toBeHidden(); + + await page.getByRole('button', { name: 'Next week' }).click(); + await expect(page.getByText('Week 2')).toBeVisible(); + await expect(page.getByText(dayEightActivity)).toBeVisible(); + await expect(page.getByText(dayOneActivity)).toBeHidden(); +}); diff --git a/playwright.config.ts b/playwright.config.ts index 1fefc0c..37421ad 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -5,6 +5,11 @@ import { resolve } from 'path'; // Load .env.test so both this process and the webServer child process see the values. dotenvConfig({ path: resolve(process.cwd(), '.env.test'), override: true }); +const chromiumExecutablePath = process.env.PLAYWRIGHT_CHROMIUM_PATH; +const launchOptions = chromiumExecutablePath + ? { executablePath: chromiumExecutablePath } + : undefined; + export default defineConfig({ testDir: './e2e', fullyParallel: false, @@ -16,7 +21,8 @@ export default defineConfig({ use: { baseURL: 'http://127.0.0.1:5173', trace: process.env.PW_TRACE_MODE ?? 'on-first-retry', - video: process.env.PW_VIDEO_MODE ?? 'off' + video: process.env.PW_VIDEO_MODE ?? 'off', + ...(launchOptions ? { launchOptions } : {}) }, projects: [ diff --git a/src/lib/date-views.test.ts b/src/lib/date-views.test.ts new file mode 100644 index 0000000..82cdb66 --- /dev/null +++ b/src/lib/date-views.test.ts @@ -0,0 +1,34 @@ +import { describe, it, expect } from 'vitest'; +import { addDaysToDate, buildDateSequence, buildWeekRanges } from './date-views.js'; + +describe('date-views', () => { + it('adds days to a date string', () => { + expect(addDaysToDate('2026-04-01', 1)).toBe('2026-04-02'); + expect(addDaysToDate('2026-04-01', 7)).toBe('2026-04-08'); + }); + + it('builds an inclusive date sequence', () => { + const dates = buildDateSequence('2026-04-01', '2026-04-03'); + expect(dates).toEqual(['2026-04-01', '2026-04-02', '2026-04-03']); + }); + + it('returns empty sequence when end is before start', () => { + expect(buildDateSequence('2026-04-03', '2026-04-01')).toEqual([]); + }); + + it('chunks weeks into seven-day ranges', () => { + const dates = buildDateSequence('2026-04-01', '2026-04-10'); + const ranges = buildWeekRanges(dates); + expect(ranges).toHaveLength(2); + expect(ranges[0]).toEqual({ + start: '2026-04-01', + end: '2026-04-07', + weekNumber: 1 + }); + expect(ranges[1]).toEqual({ + start: '2026-04-08', + end: '2026-04-10', + weekNumber: 2 + }); + }); +}); diff --git a/src/lib/date-views.ts b/src/lib/date-views.ts new file mode 100644 index 0000000..fff14c1 --- /dev/null +++ b/src/lib/date-views.ts @@ -0,0 +1,33 @@ +export function addDaysToDate(date: string, offset: number): string { + const [year, month, day] = date.split('-').map(Number); + const ts = Date.UTC(year, month - 1, day + offset); + return new Date(ts).toISOString().slice(0, 10); +} + +export function buildDateSequence(start: string, end: string): string[] { + if (!start || !end) return []; + if (start > end) return []; + const dates: string[] = []; + let current = start; + while (current <= end) { + dates.push(current); + current = addDaysToDate(current, 1); + } + return dates; +} + +export function buildWeekRanges( + dates: string[] +): Array<{ start: string; end: string; weekNumber: number }> { + const ranges: Array<{ start: string; end: string; weekNumber: number }> = []; + for (let i = 0; i < dates.length; i += 7) { + const slice = dates.slice(i, i + 7); + if (slice.length === 0) continue; + ranges.push({ + start: slice[0], + end: slice[slice.length - 1], + weekNumber: Math.floor(i / 7) + 1 + }); + } + return ranges; +} diff --git a/src/lib/server/db/migrations.ts b/src/lib/server/db/migrations.ts index cf99467..1cfb1a7 100644 --- a/src/lib/server/db/migrations.ts +++ b/src/lib/server/db/migrations.ts @@ -600,14 +600,21 @@ function seedCities(db: Database): void { const cities: { name: string; country: string; country_code: string; population: number }[] = JSON.parse(data); let id = 1; + db.run('BEGIN'); for (const city of cities) { db.run( 'INSERT OR IGNORE INTO cities (id, name, country, country_code, population) VALUES (?, ?, ?, ?, ?)', [id++, city.name, city.country, city.country_code, city.population] ); } + db.run('COMMIT'); console.log(`[db] Seeded ${cities.length} cities`); } catch (e) { + try { + db.run('ROLLBACK'); + } catch { + /* ignore rollback failure */ + } console.error('[db] Failed to seed cities:', e); } } @@ -622,6 +629,7 @@ function seedAirports(db: Database): void { const lines = data.split('\n').filter((line) => line.trim()); let imported = 0; let skipped = 0; + db.run('BEGIN'); for (const line of lines) { // Skip comments @@ -676,9 +684,15 @@ function seedAirports(db: Database): void { skipped++; } } + db.run('COMMIT'); console.log(`[db] Seeded ${imported} airports from airports.dat (${skipped} skipped)`); return; } catch (_fileError) { + try { + db.run('ROLLBACK'); + } catch { + /* ignore rollback failure */ + } // File doesn't exist, fall back to minimal seed only if table is empty const airportsRow = db.get<{ count: number }>('SELECT COUNT(*) as count FROM airports'); if ((airportsRow?.count ?? 0) === 0) { @@ -782,6 +796,7 @@ function seedAirports(db: Database): void { ]; let id = 1; + db.run('BEGIN'); for (const airport of majorAirports) { db.run( `INSERT OR IGNORE INTO airports (id, iata_code, icao_code, name, city, country, country_code, latitude, longitude, timezone) @@ -800,8 +815,14 @@ function seedAirports(db: Database): void { ] ); } + db.run('COMMIT'); console.log(`[db] Seeded ${majorAirports.length} airports`); } catch (e) { + try { + db.run('ROLLBACK'); + } catch { + /* ignore rollback failure */ + } console.error('[db] Failed to seed airports:', e); } } @@ -816,6 +837,7 @@ function seedAirlines(db: Database): void { const lines = data.split('\n').filter((line) => line.trim()); let imported = 0; let skipped = 0; + db.run('BEGIN'); for (const line of lines) { // Skip comments @@ -857,9 +879,15 @@ function seedAirlines(db: Database): void { skipped++; } } + db.run('COMMIT'); console.log(`[db] Seeded ${imported} airlines from airlines.dat (${skipped} skipped)`); return; } catch (_fileError) { + try { + db.run('ROLLBACK'); + } catch { + /* ignore rollback failure */ + } // File doesn't exist, fall back to minimal seed only if table is empty const airlinesRow = db.get<{ count: number }>('SELECT COUNT(*) as count FROM airlines'); if ((airlinesRow?.count ?? 0) === 0) { @@ -921,6 +949,7 @@ function seedAirlines(db: Database): void { ]; let id = 1; + db.run('BEGIN'); for (const airline of majorAirlines) { db.run( `INSERT OR IGNORE INTO airlines (id, iata_code, icao_code, name, country, country_code) @@ -928,8 +957,14 @@ function seedAirlines(db: Database): void { [id++, airline.iata, airline.icao, airline.name, airline.country, airline.country_code] ); } + db.run('COMMIT'); console.log(`[db] Seeded ${majorAirlines.length} airlines`); } catch (e) { + try { + db.run('ROLLBACK'); + } catch { + /* ignore rollback failure */ + } console.error('[db] Failed to seed airlines:', e); } } diff --git a/src/routes/(protected)/trips/[id]/+page.svelte b/src/routes/(protected)/trips/[id]/+page.svelte index 40389e2..64525ec 100644 --- a/src/routes/(protected)/trips/[id]/+page.svelte +++ b/src/routes/(protected)/trips/[id]/+page.svelte @@ -24,6 +24,7 @@ import PrivateVehicleCard from '$lib/components/PrivateVehicleCard.svelte'; import LodgingCard from '$lib/components/LodgingCard.svelte'; import TravellerChip from '$lib/components/TravellerChip.svelte'; + import { buildDateSequence, buildWeekRanges, addDaysToDate } from '$lib/date-views.js'; import { buildTripTimeline } from '$lib/timeline.js'; let { data, form } = $props(); @@ -93,12 +94,21 @@ let togglingChecklistItemChecked = $state<'0' | '1'>('0'); let addingChildToPlanId = $state(null); let planView = $state<'types' | 'timeline'>('types'); + let scheduleView = $state<'trip' | 'day' | 'week'>('trip'); + let dayIndex = $state(0); + let weekIndex = $state(0); const planViewOptions = [ { id: 'types', label: 'By type' }, { id: 'timeline', label: 'Timeline' } ] as const; + const scheduleViewOptions = [ + { id: 'trip', label: 'Trip' }, + { id: 'day', label: 'Day' }, + { id: 'week', label: 'Week' } + ] as const; + let transportationLocationOptions = $derived( plans .filter((plan) => plan.type !== 'transport' && plan.type !== 'day') @@ -155,10 +165,27 @@ return t.slice(0, 5); } - function addDaysToDate(date: string, offset: number): string { - const [year, month, day] = date.split('-').map(Number); - const ts = Date.UTC(year, month - 1, day + offset); - return new Date(ts).toISOString().slice(0, 10); + function toLocalDateString(date: Date): string { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; + } + + function formatLongDate(d: string) { + return new Date(d + 'T00:00:00').toLocaleDateString(undefined, { + weekday: 'long', + month: 'long', + day: 'numeric', + year: 'numeric' + }); + } + + function formatRangeDate(d: string) { + return new Date(d + 'T00:00:00').toLocaleDateString(undefined, { + month: 'long', + day: 'numeric' + }); } function toggleChecklistItem(itemId: string | number, nextChecked: boolean) { @@ -253,6 +280,132 @@ packageTours }) ); + + const tripDateRange = $derived( + trip.start_date && trip.end_date + ? { start: trip.start_date, end: trip.end_date } + : timeline.scheduled.length === 0 + ? null + : (() => { + const dates = timeline.scheduled.map((group) => group.date).sort(); + return { start: dates[0], end: dates[dates.length - 1] }; + })() + ); + + const tripDates = $derived( + tripDateRange ? buildDateSequence(tripDateRange.start, tripDateRange.end) : [] + ); + + const weekRanges = $derived(buildWeekRanges(tripDates)); + + const currentRange = $derived( + scheduleView === 'day' + ? tripDates[dayIndex] + ? { start: tripDates[dayIndex], end: tripDates[dayIndex] } + : null + : scheduleView === 'week' + ? weekRanges[weekIndex] ?? null + : null + ); + + const filteredTimeline = $derived( + currentRange + ? { + scheduled: timeline.scheduled.filter( + (group) => group.date >= currentRange.start && group.date <= currentRange.end + ), + unscheduled: [] + } + : timeline + ); + + const filteredPlanIds = $derived( + currentRange + ? (() => { + const ids = new Set(); + for (const group of timeline.scheduled) { + if (group.date < currentRange.start || group.date > currentRange.end) continue; + for (const entry of group.entries) { + if (entry.kind === 'plan') ids.add(entry.plan.id); + } + } + return ids; + })() + : null + ); + + const visiblePlans = $derived( + filteredPlanIds ? plans.filter((plan) => filteredPlanIds.has(plan.id)) : plans + ); + + const visibleFlightBookings = $derived( + filteredPlanIds + ? flightBookings.filter((booking) => filteredPlanIds.has(booking.plan_id)) + : flightBookings + ); + + const visiblePrivateVehicles = $derived( + filteredPlanIds + ? privateVehicles.filter((vehicle) => filteredPlanIds.has(vehicle.plan_id)) + : privateVehicles + ); + + const visibleOtherTransports = $derived( + filteredPlanIds + ? otherTransports.filter((transport) => filteredPlanIds.has(transport.plan_id)) + : otherTransports + ); + + const visibleLodgings = $derived( + filteredPlanIds + ? lodgings.filter((lodging) => filteredPlanIds.has(lodging.plan_id)) + : lodgings + ); + + const visibleActivities = $derived( + filteredPlanIds + ? activities.filter((activity) => filteredPlanIds.has(activity.plan_id)) + : activities + ); + + const visibleRestaurants = $derived( + filteredPlanIds + ? restaurants.filter((restaurant) => filteredPlanIds.has(restaurant.plan_id)) + : restaurants + ); + + const visiblePackingLists = $derived( + filteredPlanIds + ? packingLists.filter((list) => filteredPlanIds.has(list.plan_id)) + : packingLists + ); + + const visibleTodos = $derived( + filteredPlanIds ? todos.filter((list) => filteredPlanIds.has(list.plan_id)) : todos + ); + + const visiblePackageTours = $derived( + filteredPlanIds + ? packageTours.filter((tour) => filteredPlanIds.has(tour.plan_id)) + : packageTours + ); + + const todayDate = $derived(toLocalDateString(new Date())); + const showTodayButton = $derived( + !!trip.start_date && + !!trip.end_date && + todayDate >= trip.start_date && + todayDate <= trip.end_date + ); + const todayIndex = $derived(Array.isArray(tripDates) ? tripDates.indexOf(todayDate) : -1); + + $effect(() => { + if (dayIndex < 0 || dayIndex >= tripDates.length) dayIndex = 0; + }); + + $effect(() => { + if (weekIndex < 0 || weekIndex >= weekRanges.length) weekIndex = 0; + }); @@ -661,30 +814,130 @@ {:else}

Plans

-
- {#each planViewOptions as option} +
+
+ {#each planViewOptions as option} + + {/each} +
+
+ {#each scheduleViewOptions as option} + + {/each} +
+ {#if showTodayButton} - {/each} + {/if}
+ {#if scheduleView !== 'trip' && currentRange} +
+
+ {#if scheduleView === 'day' && tripDates.length > 1} + + {/if} + {#if scheduleView === 'week' && weekRanges.length > 1} + + {/if} + + {#if scheduleView === 'day'} +

+ {formatLongDate(currentRange.start)} +

+

Day {dayIndex + 1}

+ {:else if scheduleView === 'week'} +

+ Week {weekRanges[weekIndex]?.weekNumber ?? 1} +

+

+ {formatRangeDate(currentRange.start)} - {formatRangeDate(currentRange.end)} +

+ {/if} + + {#if scheduleView === 'day' && tripDates.length > 1} + + {/if} + {#if scheduleView === 'week' && weekRanges.length > 1} + + {/if} +
+
+ {/if} + {#if planView === 'timeline'}
- {#if timeline.scheduled.length === 0} -

No scheduled plans yet.

+ {#if filteredTimeline.scheduled.length === 0} +

+ {scheduleView === 'trip' + ? 'No scheduled plans yet.' + : 'No scheduled plans for this range.'} +

{:else} - {#each timeline.scheduled as group (group.date)} + {#each filteredTimeline.scheduled as group (group.date)}

@@ -1028,15 +1281,15 @@ {/each} {/if} - {#if timeline.unscheduled.length > 0} + {#if filteredTimeline.unscheduled.length > 0}

Unscheduled

- {#each timeline.unscheduled as entry (entry.id)} - {#if entry.kind === 'day'} + {#each filteredTimeline.unscheduled as entry (entry.id)} + {#if entry.kind === 'day'}
- {#each plans.filter((p) => p.type === 'destination') as plan (plan.id)} + {#each visiblePlans.filter((p) => p.type === 'destination') as plan (plan.id)} {@const formId = `remove-plan-${plan.id}`} {@const packageTourContext = byTypeTourContextByPlanId.get(plan.id)} {@const submitForm = () => { @@ -1421,16 +1674,20 @@
- {#if flightBookings.length > 0 || privateVehicles.length > 0 || otherTransports.length > 0} + {#if + visibleFlightBookings.length > 0 || + visiblePrivateVehicles.length > 0 || + visibleOtherTransports.length > 0 + }

Transportation

- {#each plans.filter((p) => p.type === 'transport') as plan (plan.id)} - {@const flightBooking = flightBookings.find((b) => b.plan_id === plan.id)} - {@const privateVehicle = privateVehicles.find((pv) => pv.plan_id === plan.id)} - {@const otherTransport = otherTransports.find((ot) => ot.plan_id === plan.id)} + {#each visiblePlans.filter((p) => p.type === 'transport') as plan (plan.id)} + {@const flightBooking = visibleFlightBookings.find((b) => b.plan_id === plan.id)} + {@const privateVehicle = visiblePrivateVehicles.find((pv) => pv.plan_id === plan.id)} + {@const otherTransport = visibleOtherTransports.find((ot) => ot.plan_id === plan.id)} {@const packageTourContext = byTypeTourContextByPlanId.get(plan.id)} {#if plan} {@const formId = `remove-plan-${plan.id}`} @@ -1532,14 +1789,14 @@ {/if} - {#if activities.length > 0} + {#if visibleActivities.length > 0}

Attractions & Activities

- {#each activities as activity (activity.id)} - {@const plan = plans.find((p) => p.id === activity.plan_id)} + {#each visibleActivities as activity (activity.id)} + {@const plan = visiblePlans.find((p) => p.id === activity.plan_id)} {#if plan} {@const formId = `remove-plan-${plan.id}`} {@const packageTourContext = byTypeTourContextByPlanId.get(plan.id)} @@ -1608,14 +1865,14 @@ {/if} - {#if restaurants.length > 0} + {#if visibleRestaurants.length > 0}

Restaurants

- {#each restaurants as restaurant (restaurant.id)} - {@const plan = plans.find((p) => p.id === restaurant.plan_id)} + {#each visibleRestaurants as restaurant (restaurant.id)} + {@const plan = visiblePlans.find((p) => p.id === restaurant.plan_id)} {#if plan} {@const formId = `remove-plan-${plan.id}`} {@const packageTourContext = byTypeTourContextByPlanId.get(plan.id)} @@ -1684,14 +1941,14 @@ {/if} - {#if packingLists.length > 0} + {#if visiblePackingLists.length > 0}

Packing Lists

- {#each packingLists as list (list.id)} - {@const plan = plans.find((p) => p.id === list.plan_id)} + {#each visiblePackingLists as list (list.id)} + {@const plan = visiblePlans.find((p) => p.id === list.plan_id)} {#if plan} {@const formId = `remove-plan-${plan.id}`} {@const submitForm = () => { @@ -1725,14 +1982,14 @@ {/if} - {#if todos.length > 0} + {#if visibleTodos.length > 0}

To-dos

- {#each todos as list (list.id)} - {@const plan = plans.find((p) => p.id === list.plan_id)} + {#each visibleTodos as list (list.id)} + {@const plan = visiblePlans.find((p) => p.id === list.plan_id)} {#if plan} {@const formId = `remove-plan-${plan.id}`} {@const submitForm = () => { @@ -1766,14 +2023,14 @@ {/if} - {#if lodgings.length > 0} + {#if visibleLodgings.length > 0}

Lodgings

- {#each lodgings as lodging (lodging.id)} - {@const plan = plans.find((p) => p.id === lodging.plan_id)} + {#each visibleLodgings as lodging (lodging.id)} + {@const plan = visiblePlans.find((p) => p.id === lodging.plan_id)} {#if plan} {@const formId = `remove-plan-${plan.id}`} {@const packageTourContext = byTypeTourContextByPlanId.get(plan.id)} @@ -1842,14 +2099,14 @@ {/if} - {#if packageTours.length > 0} + {#if visiblePackageTours.length > 0}

Package Tours

- {#each packageTours as tour (tour.id)} - {@const plan = plans.find((p) => p.id === tour.plan_id)} + {#each visiblePackageTours as tour (tour.id)} + {@const plan = visiblePlans.find((p) => p.id === tour.plan_id)} {#if plan} {@const formId = `remove-plan-${plan.id}`} {@const submitForm = () => {