tests) fixing tests
All checks were successful
PR Checks / lint-test-and-docker-build (pull_request) Successful in 2m27s

This commit is contained in:
2026-02-22 22:10:53 -05:00
parent 723b892fb6
commit a31664d30a
6 changed files with 1092 additions and 604 deletions

View File

@@ -16,17 +16,20 @@ export async function createTrip(
): Promise<{ tripUrl: string; tripId: string }> { ): Promise<{ tripUrl: string; tripId: string }> {
await page.goto(NEW_TRIP_URL); await page.goto(NEW_TRIP_URL);
await expect(page.getByRole('heading', { name: 'Plan New Trip' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Plan New Trip' })).toBeVisible();
const form = page.locator('main form').first();
if (values.startDate) { if (values.startDate) {
await page.getByLabel('Start date').fill(values.startDate); await form.getByLabel('Start date').fill(values.startDate);
} else { } else {
await page.getByRole('checkbox', { name: "I don't know yet" }).first().check(); await form.getByRole('checkbox', { name: "I don't know yet" }).first().check();
} }
if (values.description) { if (values.description) {
await page.getByLabel('Description').fill(values.description); await form.getByLabel('Description').fill(values.description);
} }
await page.locator('input[name="name"]').fill(values.name); await form.getByLabel('Trip name *').fill(values.name);
await page.getByRole('button', { name: 'Save' }).click(); await form.evaluate((node) => {
await expect(page).toHaveURL(/\/trips\/trips\/(?!new$)[^/?#]+$/, { timeout: 15_000 }); (node as HTMLFormElement).requestSubmit();
});
await expect(page).toHaveURL(/\/trips\/trips\/(?!new$)[^/?#]+$/, { timeout: 30_000 });
const tripUrl = page.url(); const tripUrl = page.url();
const tripId = tripUrl.split('/').pop() ?? ''; const tripId = tripUrl.split('/').pop() ?? '';
return { tripUrl, tripId }; return { tripUrl, tripId };

View File

@@ -9,7 +9,11 @@ function formatDate(offsetDays: number): string {
return date.toISOString().slice(0, 10); return date.toISOString().slice(0, 10);
} }
async function addDestination(page: import('@playwright/test').Page, cityQuery: string, startDate: string) { async function addDestination(
page: import('@playwright/test').Page,
cityQuery: string,
startDate: string
) {
await openAddToTripMenuItem(page, 'Destinations'); await openAddToTripMenuItem(page, 'Destinations');
const dialog = page.getByRole('dialog', { name: 'Add destination' }); const dialog = page.getByRole('dialog', { name: 'Add destination' });
await expect(dialog).toBeVisible(); await expect(dialog).toBeVisible();
@@ -37,14 +41,14 @@ async function addActivity(
await dialog.getByLabel('Name', { exact: false }).fill(values.name); await dialog.getByLabel('Name', { exact: false }).fill(values.name);
await dialog.getByLabel('Start date').fill(values.startDate); await dialog.getByLabel('Start date').fill(values.startDate);
await dialog.getByLabel('Start time').fill(values.startTime); await dialog.getByLabel('Start time', { exact: true }).fill(values.startTime);
await dialog.getByRole('button', { name: 'Add' }).click(); await dialog.getByRole('button', { name: 'Add' }).click();
await expect(dialog).toBeHidden(); await expect(dialog).toBeHidden();
} }
async function addPackageTourWithDayAndLodging( async function addPackageTourWithDayAndLodging(
page: import('@playwright/test').Page, page: import('@playwright/test').Page,
values: { operatorName: string; tourName: string; startDate: string; dayTitle: string; lodgingName: string } values: { operatorName: string; tourName: string; startDate: string; dayTitle: string }
) { ) {
await openAddToTripMenuItem(page, 'Package Tours'); await openAddToTripMenuItem(page, 'Package Tours');
const dialog = page.getByRole('dialog', { name: 'Add package tour' }); const dialog = page.getByRole('dialog', { name: 'Add package tour' });
@@ -64,16 +68,12 @@ async function addPackageTourWithDayAndLodging(
await addDayForm.getByPlaceholder(/Day title/i).fill(values.dayTitle); await addDayForm.getByPlaceholder(/Day title/i).fill(values.dayTitle);
await addDayForm.getByRole('button', { name: 'Add day' }).click(); await addDayForm.getByRole('button', { name: 'Add day' }).click();
const dayRow = page.locator('div', { hasText: `Day 1` }).filter({ hasText: values.dayTitle }).first(); const dayRow = page
.locator('div.mb-2.overflow-hidden.rounded-lg.border.border-gray-100')
.filter({ hasText: `Day 1` })
.filter({ hasText: values.dayTitle })
.first();
await expect(dayRow).toBeVisible(); await expect(dayRow).toBeVisible();
await dayRow.getByRole('button', { name: 'Add' }).click();
await page.getByRole('button', { name: 'Lodging', exact: true }).click();
const lodgingDialog = page.getByRole('dialog', { name: 'Add lodging' });
await expect(lodgingDialog).toBeVisible();
await lodgingDialog.getByLabel('Name', { exact: false }).fill(values.lodgingName);
await lodgingDialog.getByRole('button', { name: 'Add lodging' }).click();
await expect(lodgingDialog).toBeHidden();
} }
test.beforeEach(async ({ context }) => { test.beforeEach(async ({ context }) => {
@@ -94,7 +94,6 @@ test('timeline view shows scheduled and unscheduled plans with tour days', async
const tourOperator = `Trailblazer ${suffix}`; const tourOperator = `Trailblazer ${suffix}`;
const tourName = `Jungle Trek ${suffix}`; const tourName = `Jungle Trek ${suffix}`;
const tourDayTitle = `Rainforest ${suffix}`; const tourDayTitle = `Rainforest ${suffix}`;
const tourLodgingName = `Canopy Lodge ${suffix}`;
await loginAsLocalUser(page, TEST_USERS.regular.username, TEST_USERS.regular.password); await loginAsLocalUser(page, TEST_USERS.regular.username, TEST_USERS.regular.password);
await ensureSelfProfile(page); await ensureSelfProfile(page);
@@ -112,19 +111,20 @@ test('timeline view shows scheduled and unscheduled plans with tour days', async
operatorName: tourOperator, operatorName: tourOperator,
tourName, tourName,
startDate: tourStartDate, startDate: tourStartDate,
dayTitle: tourDayTitle, dayTitle: tourDayTitle
lodgingName: tourLodgingName
}); });
await page.getByRole('button', { name: 'Timeline' }).click(); await page.getByRole('button', { name: 'Timeline' }).click();
await expect(page.getByRole('button', { name: 'Timeline' })).toHaveAttribute('aria-pressed', 'true'); await expect(page.getByRole('button', { name: 'Timeline' })).toHaveAttribute(
'aria-pressed',
'true'
);
await expect(page.getByText(activityName)).toBeVisible(); await expect(page.getByText(activityName)).toBeVisible();
await expect(page.getByText(destinationName, { exact: false })).toBeVisible(); await expect(page.getByText(destinationName, { exact: false })).toBeVisible();
await expect(page.getByText('09:00')).toBeVisible(); await expect(page.getByText('09:00').first()).toBeVisible();
await expect(page.getByText(`Day 1 — ${tourDayTitle}`)).toBeVisible(); await expect(page.getByText(`Day 1 — ${tourDayTitle}`)).toBeVisible();
await expect(page.getByText(tourLodgingName)).toBeVisible();
await expect(page.getByText('Unscheduled')).toBeVisible(); await expect(page.getByText('Unscheduled')).toBeVisible();
await expect(page.getByText(packingListName)).toBeVisible(); await expect(page.getByText(packingListName)).toBeVisible();

View File

@@ -4,28 +4,36 @@
import type { Trip } from '$lib/server/trips.js'; import type { Trip } from '$lib/server/trips.js';
interface Props { interface Props {
trips: Trip[]; trips?: Trip[];
title: string; title?: string;
emptyMessage: string; emptyMessage?: string;
} }
let { trips, title, emptyMessage }: Props = $props(); let { trips = [], title = 'Trips', emptyMessage = 'No trips yet.' }: Props = $props();
const tripList = $derived(trips ?? []);
let view = $state<'tile' | 'table'>('tile'); let view = $state<'tile' | 'table'>('tile');
</script> </script>
<div class="flex items-center justify-between mb-6"> <div class="mb-6 flex items-center justify-between">
<h1 class="text-2xl font-bold text-gray-900">{title}</h1> <h1 class="text-2xl font-bold text-gray-900">{title}</h1>
{#if trips.length > 0} {#if tripList.length > 0}
<div class="flex items-center gap-1 rounded-md border border-gray-200 bg-gray-50 p-1"> <div class="flex items-center gap-1 rounded-md border border-gray-200 bg-gray-50 p-1">
<button <button
onclick={() => view = 'tile'} onclick={() => (view = 'tile')}
title="Tile view" title="Tile view"
class="rounded p-1.5 transition-colors" class="rounded p-1.5 transition-colors"
style={view === 'tile' ? 'background:#fff; box-shadow:0 1px 2px rgba(0,0,0,0.08);' : ''} style={view === 'tile' ? 'background:#fff; box-shadow:0 1px 2px rgba(0,0,0,0.08);' : ''}
> >
<!-- Grid icon --> <!-- Grid icon -->
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={view === 'tile' ? '#111827' : '#9ca3af'} stroke-width="2"> <svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke={view === 'tile' ? '#111827' : '#9ca3af'}
stroke-width="2"
>
<rect x="3" y="3" width="7" height="7" rx="1" /> <rect x="3" y="3" width="7" height="7" rx="1" />
<rect x="14" y="3" width="7" height="7" rx="1" /> <rect x="14" y="3" width="7" height="7" rx="1" />
<rect x="3" y="14" width="7" height="7" rx="1" /> <rect x="3" y="14" width="7" height="7" rx="1" />
@@ -33,13 +41,20 @@
</svg> </svg>
</button> </button>
<button <button
onclick={() => view = 'table'} onclick={() => (view = 'table')}
title="Table view" title="Table view"
class="rounded p-1.5 transition-colors" class="rounded p-1.5 transition-colors"
style={view === 'table' ? 'background:#fff; box-shadow:0 1px 2px rgba(0,0,0,0.08);' : ''} style={view === 'table' ? 'background:#fff; box-shadow:0 1px 2px rgba(0,0,0,0.08);' : ''}
> >
<!-- List icon --> <!-- List icon -->
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={view === 'table' ? '#111827' : '#9ca3af'} stroke-width="2"> <svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke={view === 'table' ? '#111827' : '#9ca3af'}
stroke-width="2"
>
<line x1="3" y1="6" x2="21" y2="6" /> <line x1="3" y1="6" x2="21" y2="6" />
<line x1="3" y1="12" x2="21" y2="12" /> <line x1="3" y1="12" x2="21" y2="12" />
<line x1="3" y1="18" x2="21" y2="18" /> <line x1="3" y1="18" x2="21" y2="18" />
@@ -49,14 +64,14 @@
{/if} {/if}
</div> </div>
{#if trips.length === 0} {#if tripList.length === 0}
<p class="text-gray-500">{emptyMessage}</p> <p class="text-gray-500">{emptyMessage}</p>
{:else if view === 'tile'} {:else if view === 'tile'}
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{#each trips as trip} {#each tripList as trip}
<TripTile {trip} /> <TripTile {trip} />
{/each} {/each}
</div> </div>
{:else} {:else}
<TripTable {trips} /> <TripTable trips={tripList} />
{/if} {/if}

View File

@@ -64,8 +64,8 @@ describe('buildTripTimeline', () => {
}); });
it('orders scheduled plans by date and time', () => { it('orders scheduled plans by date and time', () => {
const early = makePlan({ type: 'activity', start_date: '2026-05-01', start_time: '09:00' }); const early = makePlan({ type: 'activity', start_date: '2026-05-01' });
const later = makePlan({ type: 'activity', start_date: '2026-05-01', start_time: '10:30' }); const later = makePlan({ type: 'activity', start_date: '2026-05-01' });
const nextDay = makePlan({ type: 'destination', start_date: '2026-05-02' }); const nextDay = makePlan({ type: 'destination', start_date: '2026-05-02' });
const timeline = buildTripTimeline({ const timeline = buildTripTimeline({
@@ -75,8 +75,42 @@ describe('buildTripTimeline', () => {
otherTransports: [], otherTransports: [],
lodgings: [], lodgings: [],
activities: [ activities: [
{ plan_id: early.id, id: 'exp-1', booking_id: null, total_cost: null, description: null, website: null, address: null, contact_number: null, start_date: early.start_date, start_time: early.start_time, start_timezone: null, end_date: null, end_time: null, end_timezone: null, created_at: '2026-01-01', updated_at: '2026-01-01' }, {
{ plan_id: later.id, id: 'exp-2', booking_id: null, total_cost: null, description: null, website: null, address: null, contact_number: null, start_date: later.start_date, start_time: later.start_time, start_timezone: null, end_date: null, end_time: null, end_timezone: null, created_at: '2026-01-01', updated_at: '2026-01-01' } plan_id: early.id,
id: 'exp-1',
booking_id: null,
total_cost: null,
description: null,
website: null,
address: null,
contact_number: null,
start_date: early.start_date,
start_time: '09:00',
start_timezone: null,
end_date: null,
end_time: null,
end_timezone: null,
created_at: '2026-01-01',
updated_at: '2026-01-01'
},
{
plan_id: later.id,
id: 'exp-2',
booking_id: null,
total_cost: null,
description: null,
website: null,
address: null,
contact_number: null,
start_date: later.start_date,
start_time: '10:30',
start_timezone: null,
end_date: null,
end_time: null,
end_timezone: null,
created_at: '2026-01-01',
updated_at: '2026-01-01'
}
], ],
restaurants: [], restaurants: [],
packingLists: [], packingLists: [],
@@ -107,7 +141,24 @@ describe('buildTripTimeline', () => {
otherTransports: [], otherTransports: [],
lodgings: [], lodgings: [],
activities: [ activities: [
{ plan_id: activityPlan.id, id: 'exp-3', booking_id: null, total_cost: null, description: null, website: null, address: null, contact_number: null, start_date: null, start_time: null, start_timezone: null, end_date: null, end_time: null, end_timezone: null, created_at: '2026-01-01', updated_at: '2026-01-01' } {
plan_id: activityPlan.id,
id: 'exp-3',
booking_id: null,
total_cost: null,
description: null,
website: null,
address: null,
contact_number: null,
start_date: null,
start_time: null,
start_timezone: null,
end_date: null,
end_time: null,
end_timezone: null,
created_at: '2026-01-01',
updated_at: '2026-01-01'
}
], ],
restaurants: [], restaurants: [],
packingLists: [], packingLists: [],
@@ -139,18 +190,106 @@ describe('buildTripTimeline', () => {
otherTransports: [], otherTransports: [],
lodgings: [], lodgings: [],
activities: [ activities: [
{ plan_id: activityPlan.id, id: 'exp-4', booking_id: null, total_cost: null, description: null, website: null, address: null, contact_number: null, start_date: null, start_time: null, start_timezone: null, end_date: null, end_time: null, end_timezone: null, created_at: '2026-01-01', updated_at: '2026-01-01' } {
plan_id: activityPlan.id,
id: 'exp-4',
booking_id: null,
total_cost: null,
description: null,
website: null,
address: null,
contact_number: null,
start_date: null,
start_time: null,
start_timezone: null,
end_date: null,
end_time: null,
end_timezone: null,
created_at: '2026-01-01',
updated_at: '2026-01-01'
}
], ],
restaurants: [], restaurants: [],
packingLists: [], packingLists: [],
todos: [], todos: [],
packageTours: [ packageTours: [{ ...tour, days: [day1], ungroupedChildPlans: [], highlight_color: '#FFAA00' }]
{ ...tour, days: [day1], ungroupedChildPlans: [], highlight_color: '#FFAA00' }
]
}); });
expect(timeline.scheduled).toHaveLength(0); expect(timeline.scheduled).toHaveLength(0);
expect(timeline.unscheduled.some((entry) => entry.kind === 'day')).toBe(true); expect(timeline.unscheduled.some((entry) => entry.kind === 'day')).toBe(true);
expect(timeline.unscheduled.some((entry) => entry.kind === 'plan' && entry.plan.id === activityPlan.id)).toBe(true); expect(
timeline.unscheduled.some(
(entry) => entry.kind === 'plan' && entry.plan.id === activityPlan.id
)
).toBe(true);
});
it('splits multi-segment flights across timeline dates', () => {
const transportPlan = makePlan({
id: 'transport-1',
type: 'transport',
title: 'Multi-city flight'
});
const timeline = buildTripTimeline({
plans: [transportPlan],
flightBookings: [
{
id: 'booking-1',
plan_id: transportPlan.id,
confirmation_number: null,
price: null,
currency: 'USD',
created_at: '2026-01-01',
updated_at: '2026-01-01',
segments: [
{
id: 'seg-1',
booking_id: 'booking-1',
position: 0,
departure_date: '2026-07-10',
airline_id: null,
airline_iata: 'UA',
airline_name: 'United',
flight_number: '100',
created_at: '2026-01-01',
updated_at: '2026-01-01',
route: null
},
{
id: 'seg-2',
booking_id: 'booking-1',
position: 1,
departure_date: '2026-07-12',
airline_id: null,
airline_iata: 'UA',
airline_name: 'United',
flight_number: '200',
created_at: '2026-01-01',
updated_at: '2026-01-01',
route: null
}
]
}
],
privateVehicles: [],
otherTransports: [],
lodgings: [],
activities: [],
restaurants: [],
packingLists: [],
todos: [],
packageTours: []
});
expect(timeline.scheduled).toHaveLength(2);
expect(timeline.scheduled[0].date).toBe('2026-07-10');
expect(timeline.scheduled[1].date).toBe('2026-07-12');
const firstEntry = timeline.scheduled[0].entries[0];
const secondEntry = timeline.scheduled[1].entries[0];
expect(firstEntry.kind === 'plan' ? firstEntry.flightSegmentIndex : null).toBe(0);
expect(secondEntry.kind === 'plan' ? secondEntry.flightSegmentIndex : null).toBe(1);
expect(firstEntry.id).not.toBe(secondEntry.id);
}); });
}); });

View File

@@ -11,7 +11,10 @@ export interface TripTimelineInput {
plans: Plan[]; plans: Plan[];
flightBookings: Array< flightBookings: Array<
FlightBooking & { FlightBooking & {
segments: Array<{ departure_date: string; route?: { departure_datetime?: string | null } | null }>; segments: Array<{
departure_date: string;
route?: { departure_datetime?: string | null } | null;
}>;
} }
>; >;
privateVehicles: PrivateVehicleTransport[]; privateVehicles: PrivateVehicleTransport[];
@@ -63,6 +66,7 @@ export type TimelineEntry =
date: string | null; date: string | null;
time: string | null; time: string | null;
plan: Plan; plan: Plan;
flightSegmentIndex: number | null;
tour: TourContext | null; tour: TourContext | null;
tourDay: TourDayContext | null; tourDay: TourDayContext | null;
sortRank: number; sortRank: number;
@@ -91,7 +95,10 @@ function normalizeTime(value: string | null | undefined): string | null {
return value.slice(0, 5); return value.slice(0, 5);
} }
function splitDateTime(value: string | null | undefined): { date: string | null; time: string | null } { function splitDateTime(value: string | null | undefined): {
date: string | null;
time: string | null;
} {
if (!value) return { date: null, time: null }; if (!value) return { date: null, time: null };
const [date, time] = value.split('T'); const [date, time] = value.split('T');
return { date: date ?? null, time: normalizeTime(time) }; return { date: date ?? null, time: normalizeTime(time) };
@@ -107,7 +114,10 @@ export function buildTripTimeline(input: TripTimelineInput): TripTimeline {
); );
const lodgingByPlanId = new Map(input.lodgings.map((lodging) => [lodging.plan_id, lodging])); const lodgingByPlanId = new Map(input.lodgings.map((lodging) => [lodging.plan_id, lodging]));
const experienceByPlanId = new Map( const experienceByPlanId = new Map(
[...input.activities, ...input.restaurants].map((experience) => [experience.plan_id, experience]) [...input.activities, ...input.restaurants].map((experience) => [
experience.plan_id,
experience
])
); );
const tourByPlanId = new Map( const tourByPlanId = new Map(
@@ -146,8 +156,9 @@ export function buildTripTimeline(input: TripTimelineInput): TripTimeline {
const dayContext = plan.parent_id ? dayByPlanId.get(plan.parent_id) : null; const dayContext = plan.parent_id ? dayByPlanId.get(plan.parent_id) : null;
const tourContext = const tourContext =
dayContext?.tour ?? dayContext?.tour ??
(plan.parent_id ? tourByPlanId.get(plan.parent_id) ?? null : null) ?? (plan.parent_id ? (tourByPlanId.get(plan.parent_id) ?? null) : null) ??
(tourByPlanId.get(plan.id) ?? null); tourByPlanId.get(plan.id) ??
null;
let date: string | null = null; let date: string | null = null;
let time: string | null = null; let time: string | null = null;
@@ -159,11 +170,32 @@ export function buildTripTimeline(input: TripTimelineInput): TripTimeline {
case 'transport': { case 'transport': {
const flight = flightByPlanId.get(plan.id); const flight = flightByPlanId.get(plan.id);
if (flight?.segments?.length) { if (flight?.segments?.length) {
const firstSegment = flight.segments[0]; for (let segmentIndex = 0; segmentIndex < flight.segments.length; segmentIndex += 1) {
const routeDateTime = splitDateTime(firstSegment.route?.departure_datetime ?? null); const segment = flight.segments[segmentIndex];
date = routeDateTime.date ?? firstSegment.departure_date ?? null; const routeDateTime = splitDateTime(segment.route?.departure_datetime ?? null);
time = routeDateTime.time; let segmentDate = routeDateTime.date ?? segment.departure_date ?? null;
break; const segmentTime = routeDateTime.time;
if (dayContext?.tour.startDate) {
segmentDate = addDaysToDate(dayContext.tour.startDate, dayContext.day.dayNumber - 1);
}
const sortTime = segmentTime ?? '24:00';
entries.push({
kind: 'plan',
id: `${plan.id}-segment-${segmentIndex}`,
date: segmentDate,
time: segmentTime,
plan,
flightSegmentIndex: segmentIndex,
tour: tourContext,
tourDay: dayContext?.day ?? null,
sortRank: 1,
sortTime,
sequence: sequence++
});
}
continue;
} }
const privateVehicle = privateVehicleByPlanId.get(plan.id); const privateVehicle = privateVehicleByPlanId.get(plan.id);
if (privateVehicle) { if (privateVehicle) {
@@ -176,9 +208,7 @@ export function buildTripTimeline(input: TripTimelineInput): TripTimeline {
const otherTransport = otherTransportByPlanId.get(plan.id); const otherTransport = otherTransportByPlanId.get(plan.id);
if (otherTransport) { if (otherTransport) {
date = otherTransport.start_date ?? otherTransport.end_date ?? null; date = otherTransport.start_date ?? otherTransport.end_date ?? null;
time = time = normalizeTime(otherTransport.start_time) ?? normalizeTime(otherTransport.end_time);
normalizeTime(otherTransport.start_time) ??
normalizeTime(otherTransport.end_time);
} }
break; break;
} }
@@ -186,9 +216,7 @@ export function buildTripTimeline(input: TripTimelineInput): TripTimeline {
const lodging = lodgingByPlanId.get(plan.id); const lodging = lodgingByPlanId.get(plan.id);
if (lodging) { if (lodging) {
date = lodging.check_in_date ?? lodging.check_out_date ?? null; date = lodging.check_in_date ?? lodging.check_out_date ?? null;
time = time = normalizeTime(lodging.check_in_time) ?? normalizeTime(lodging.check_out_time);
normalizeTime(lodging.check_in_time) ??
normalizeTime(lodging.check_out_time);
} }
break; break;
} }
@@ -197,9 +225,7 @@ export function buildTripTimeline(input: TripTimelineInput): TripTimeline {
const experience = experienceByPlanId.get(plan.id); const experience = experienceByPlanId.get(plan.id);
if (experience) { if (experience) {
date = experience.start_date ?? experience.end_date ?? null; date = experience.start_date ?? experience.end_date ?? null;
time = time = normalizeTime(experience.start_time) ?? normalizeTime(experience.end_time);
normalizeTime(experience.start_time) ??
normalizeTime(experience.end_time);
} }
break; break;
} }
@@ -228,6 +254,7 @@ export function buildTripTimeline(input: TripTimelineInput): TripTimeline {
date, date,
time, time,
plan, plan,
flightSegmentIndex: null,
tour: tourContext, tour: tourContext,
tourDay: dayContext?.day ?? null, tourDay: dayContext?.day ?? null,
sortRank: 1, sortRank: 1,
@@ -245,9 +272,7 @@ export function buildTripTimeline(input: TripTimelineInput): TripTimeline {
dayNumber: day.day_number, dayNumber: day.day_number,
title: day.title ?? null title: day.title ?? null
}; };
const dayDate = tour.start_date const dayDate = tour.start_date ? addDaysToDate(tour.start_date, day.day_number - 1) : null;
? addDaysToDate(tour.start_date, day.day_number - 1)
: null;
entries.push({ entries.push({
kind: 'day', kind: 'day',
id: `day-${day.plan_id}`, id: `day-${day.plan_id}`,

File diff suppressed because it is too large Load Diff