import { formatDateTime, formatRange } from './dateTime'; import { BankInstructions } from './BankInstructions'; import * as React from 'react'; import { FinancialSummary, CancellationForm, RefundForm } from './settlement'; import { __, _n, sprintf } from '@wordpress/i18n'; import { methodLabel, paymentLabel } from './credit'; import { BookingApi } from './api'; import { money, PriceSummary, type Price } from './pricing'; import type { Booking, BookingList } from './types'; export function statusLabel(status: string): string { const labels: Record = { pending: __('Pending', 'abece-booking'), confirmed: __('Confirmed', 'abece-booking'), cancelled: __('Cancelled', 'abece-booking'), completed: __('Completed', 'abece-booking'), no_show: __('No-show', 'abece-booking'), voided: __('Removed', 'abece-booking'), sent: __('Sent', 'abece-booking'), failed: __('Failed', 'abece-booking') }; return labels[status] ?? status; } export function bookingTime(b: Booking): string { return formatRange(b.starts_at_local, b.ends_at_local, b.timezone); } export function BookingDetails({ booking: b, api, onChanged, onClose, onEdit }: { onEdit?: (b: Booking) => void; booking: Booking; api: BookingApi; onChanged: (b: Booking) => void; onClose: () => void; }) { const [paymentMethod, setPaymentMethod] = React.useState('cash'); const [removalScope, setRemovalScope] = React.useState(false); const scopedRemoval = false; const [reason, setReason] = React.useState(''); const [busy, setBusy] = React.useState(false); const [error, setError] = React.useState(''); async function action(name: string) { setBusy(true); setError(''); try { onChanged(await api.request(`bookings/${b.id}/${name}`, 'POST', name === 'mark-paid' ? { payment_method: b.payment_method, payment_medium: paymentMethod, reason } : ['cancel', 'void', 'refund'].includes(name) ? { reason } : {})); } catch (e) { setError(e instanceof Error ? e.message : __('The request failed. Please try again.', 'abece-booking')); } finally { setBusy(false); } } let external: { phone?: string; email?: string; } | null = null; try { external = b.external_contact_snapshot ? JSON.parse(b.external_contact_snapshot) : null; } catch { /* Legacy. */ } let snapshot: Price | null = null; try { snapshot = b.pricing_snapshot ? JSON.parse(b.pricing_snapshot) : null; } catch { /* A legacy booking may not have a snapshot. */ } return

{b.series_id && ↻ }{b.reference}

{b.series_id &&

{__('This booking belongs to a recurring series.', 'abece-booking')} {b.can_edit && `#${b.series_id}`}

} {b.cancelled_at && }
{__('Resource', 'abece-booking')}
{b.resource_name}
{__('Date and time', 'abece-booking')}
{bookingTime(b)}
{__('Duration', 'abece-booking')}
{sprintf(__('%d minutes', 'abece-booking'), b.duration_minutes)}
{__('Customer', 'abece-booking')}
{b.booker_name}
{b.external_customer_label !== undefined && b.customer_type === 'external' && <>
{__('External customer', 'abece-booking')}
{b.external_customer_label || '—'}
{external?.phone && <>
{__('Phone', 'abece-booking')}
{external.phone}
}{external?.email && <>
{__('Email', 'abece-booking')}
{external.email}
}}
{__('Customer type', 'abece-booking')}
{b.customer_type === 'member' ? __('Club member', 'abece-booking') : b.customer_type === 'external' ? __('External / phone booking', 'abece-booking') : __('Registered non-member', 'abece-booking')}
{__('Booking source', 'abece-booking')}
{b.booking_source === 'phone' ? __('Phone', 'abece-booking') : b.booking_source === 'admin' ? __('Admin', 'abece-booking') : __('Web', 'abece-booking')}
{__('Total players', 'abece-booking')}
{b.participant_count}
{__('Eligible members', 'abece-booking')}
{b.eligible_member_count}
{__('Non-member players', 'abece-booking')}
{b.non_member_count}
{b.admin_note !== undefined && <>
{__('Internal note', 'abece-booking')}
{b.admin_note || '—'}
{__('Created by', 'abece-booking')}
{b.created_by_name}
}
{__('Status', 'abece-booking')}
{statusLabel(b.status)}
{__('Created at', 'abece-booking')}
{formatDateTime(b.created_at, b.timezone)}
{__('Price', 'abece-booking')}
{money(b.final_amount, b.currency)}
{__('Note', 'abece-booking')}
{b.customer_note || '—'}

{__('Payment method', 'abece-booking')}: {methodLabel(b.payment_method)} · {__('Payment status', 'abece-booking')}: {paymentLabel(b.payment_status)}

{__('Payment history', 'abece-booking')}

    {b.payment_transactions.map(t =>
  • {formatDateTime(t.created_at, b.timezone)} · {t.transaction_type === 'refund' ? __('Refund', 'abece-booking') : __('Payment', 'abece-booking')} · {methodLabel(t.provider)}{t.payment_medium && ` · ${mediumLabel(t.payment_medium)}`} · {money(t.amount, t.currency)} · {statusLabel(t.status)}
  • )}
{b.admin_note !== undefined && snapshot &&
{__('Pricing breakdown', 'abece-booking')}
    {(snapshot.segments ?? []).map((s, i) =>
  • {formatRange(s.start, s.end, b.timezone)} · {money(s.base_amount, b.currency)} · {__('Member benefit', 'abece-booking')}: {money(s.discount_amount, b.currency)} · {__('Amount due', 'abece-booking')}: {money(s.final_amount, b.currency)}
  • )}
}

{__('Participants', 'abece-booking')}

    {b.participants.map(p =>
  • {p.name || __('Guest', 'abece-booking')} ({p.participant_type === 'member' ? __('Member', 'abece-booking') : __('Guest', 'abece-booking')})
  • )}
{b.participants.length === 0 &&

{__('None', 'abece-booking')}

} {b.customer_restriction_warnings && b.customer_restriction_warnings.length > 0 && }

{__('History', 'abece-booking')}

    {b.history.map(h =>
  • {formatDateTime(h.created_at, b.timezone)} · {historyLabel(h.event_type)} · {statusLabel(h.new_status)}{(h.performed_by_name || h.performed_by_user_id) && <> · {__('Actor', 'abece-booking')} {h.performed_by_name || h.performed_by_user_id}}{h.description && ` · ${h.description}`}{h.metadata && b.admin_note !== undefined && }
  • )}
{b.admin_note !== undefined &&

{__('Notifications', 'abece-booking')}

{b.notifications.some(n => n.status === 'failed') &&

{__('The confirmation email could not be sent. The booking remains valid.', 'abece-booking')}

}
{__('Delivery diagnostics', 'abece-booking')}
    {b.notifications.map(n =>
  • {statusLabel(n.status)} · {__('Attempts', 'abece-booking')}: {n.attempts}{n.sent_at && ` · ${formatDateTime(n.sent_at, b.timezone)}`}{n.last_error &&

    {n.last_error}

    }
  • )}
} {error &&

{error}

}
{b.can_edit && onEdit &&

{__('Booking', 'abece-booking')}

} {(b.can_mark_paid || b.can_refund) &&

{__('Payment', 'abece-booking')}

{b.can_mark_paid &&
{ e.preventDefault(); void action('mark-paid'); }}>
} {b.can_refund && }
} {(b.can_cancel || b.can_mark_no_show || b.can_void) &&

{__('Status', 'abece-booking')}

{b.can_mark_no_show &&
} {b.can_cancel && } {b.can_void &&
{__('Remove an erroneous booking', 'abece-booking')}
{ e.preventDefault(); if (scopedRemoval) setRemovalScope(true); else if (window.confirm(__('Remove this erroneous booking? The audit history will be retained.', 'abece-booking'))) void action('void'); }}>