diff --git a/client/src/components/Planner/ReservationsPanel.test.tsx b/client/src/components/Planner/ReservationsPanel.test.tsx
index 054db8410..279e41183 100644
--- a/client/src/components/Planner/ReservationsPanel.test.tsx
+++ b/client/src/components/Planner/ReservationsPanel.test.tsx
@@ -66,15 +66,16 @@ describe('ReservationsPanel', () => {
it('FE-COMP-RES-005: shows Manual Booking add button', () => {
render();
- // Button text is reservations.addManual = "Manual Booking"
- expect(screen.getByText('Manual Booking')).toBeInTheDocument();
+ // Button text is reservations.addManual = "Manual Booking" — in the toolbar
+ // and, on an empty list, in the empty state's call to action too (#2007).
+ expect(screen.getAllByText('Manual Booking').length).toBeGreaterThan(0);
});
it('FE-COMP-RES-006: clicking Manual Booking button calls onAdd', async () => {
const user = userEvent.setup();
const onAdd = vi.fn();
render();
- await user.click(screen.getByText('Manual Booking'));
+ await user.click(screen.getAllByText('Manual Booking')[0]);
expect(onAdd).toHaveBeenCalled();
});
@@ -654,7 +655,7 @@ describe('ReservationsPanel', () => {
it('FE-PLANNER-RESP-058: the add button dims on hover and restores on leave', () => {
render();
- const add = screen.getByText('Manual Booking').closest('button') as HTMLButtonElement;
+ const add = screen.getAllByText('Manual Booking')[0].closest('button') as HTMLButtonElement;
act(() => { add.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); });
expect(add.style.opacity).toBe('0.88');
act(() => { add.dispatchEvent(new MouseEvent('mouseout', { bubbles: true })); });
diff --git a/client/src/components/Planner/ReservationsPanel.tsx b/client/src/components/Planner/ReservationsPanel.tsx
index fe97ad203..29f217f4d 100644
--- a/client/src/components/Planner/ReservationsPanel.tsx
+++ b/client/src/components/Planner/ReservationsPanel.tsx
@@ -1,4 +1,4 @@
-import { useState, useMemo, useEffect } from 'react'
+import { useState, useMemo, useEffect, type CSSProperties } from 'react'
import { createPortal } from 'react-dom'
import { useTripStore } from '../../store/tripStore'
import { useCanDo } from '../../store/permissionsStore'
@@ -705,6 +705,14 @@ interface ReservationsPanelProps {
tripMembers?: TripMember[]
}
+/** The empty state's call-to-action buttons — same shape as the toolbar's. */
+const CTA_STYLE: CSSProperties = {
+ appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit',
+ display: 'inline-flex', alignItems: 'center', gap: 6,
+ padding: '9px 14px', borderRadius: 10,
+ fontSize: 'calc(13px * var(--fs-scale-body, 1))', fontWeight: 500,
+}
+
export default function ReservationsPanel({ tripId, reservations, days, assignments, files = [], onAdd, onImport, bookingImportAvailable, onAirTrailImport, airTrailAvailable, onEdit, onDelete, onNavigateToFiles, titleKey = 'reservations.title', addManualKey = 'reservations.addManual', contributionView = 'reservations', tripMembers = [] }: ReservationsPanelProps) {
const { t, locale } = useTranslation()
const can = useCanDo()
@@ -928,7 +936,32 @@ export default function ReservationsPanel({ tripId, reservations, days, assignme
{/* Content */}
{total === 0 && reservations.length === 0 ? (
-
+
+
+ {onImport && bookingImportAvailable && (
+
+ )}
+ {onAirTrailImport && airTrailAvailable && (
+
+ )}
+
+ ) : undefined}
+ />
) : total === 0 ? (
{t('places.noneFound')}
diff --git a/client/src/components/shared/EmptyState.tsx b/client/src/components/shared/EmptyState.tsx
index 53d2133e6..35b838e90 100644
--- a/client/src/components/shared/EmptyState.tsx
+++ b/client/src/components/shared/EmptyState.tsx
@@ -1,4 +1,4 @@
-import type { CSSProperties } from 'react'
+import type { CSSProperties, ReactNode } from 'react'
import MDancingTrek, { type TrekScene, type TrekMood } from '../../mobile/components/MDancingTrek'
/**
@@ -18,6 +18,7 @@ export default function EmptyState({
size = 104,
surface = 'var(--bg-card)',
className = '',
+ action,
}: {
scene?: TrekScene
mood?: TrekMood
@@ -25,6 +26,8 @@ export default function EmptyState({
size?: number
surface?: string
className?: string
+ /** Optional call to action under the title, for states that have an obvious next step. */
+ action?: ReactNode
}) {
return (
)
}