Update frontend to match new tested backend
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
import { toast } from 'svelte-sonner';
|
||||
import * as Modal from '$lib/components/ui/dialog';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import type { Booking } from '$lib/types/booking';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
@@ -12,38 +13,6 @@
|
||||
|
||||
let { open = $bindable(), bookingId }: Props = $props();
|
||||
|
||||
type Booking = {
|
||||
id: string;
|
||||
start_time: string;
|
||||
status: string;
|
||||
notes?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
services: Array<{
|
||||
service_name?: string;
|
||||
service_description?: string;
|
||||
price?: number;
|
||||
duration_minutes?: number;
|
||||
}>;
|
||||
payments: Array<{
|
||||
id: string;
|
||||
payment_type: string;
|
||||
payment_method: string;
|
||||
status: string;
|
||||
amount: number;
|
||||
created_at: string;
|
||||
invoice_number?: number;
|
||||
is_vat_applicable: boolean;
|
||||
vat_amount?: number;
|
||||
net_amount?: number;
|
||||
vat_rate?: number;
|
||||
}>;
|
||||
total_amount: number;
|
||||
amount_paid: number;
|
||||
amount_due: number;
|
||||
duration_minutes: number;
|
||||
};
|
||||
|
||||
let selectedBooking = $state<Booking | null>(null);
|
||||
let loading = $state(false);
|
||||
|
||||
@@ -66,7 +35,7 @@
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
selectedBooking = data;
|
||||
selectedBooking = data as Booking;
|
||||
} else {
|
||||
const text = await response.text();
|
||||
toast.error('Failed to load booking: ' + text);
|
||||
@@ -169,6 +138,48 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Deposit Info -->
|
||||
{#if selectedBooking.deposit_required}
|
||||
<div class="rounded-lg border border-blue-200 bg-blue-50 p-4">
|
||||
<h3 class="mb-3 text-sm font-semibold tracking-wide text-blue-800 uppercase">
|
||||
Deposit Information
|
||||
</h3>
|
||||
<div class="grid gap-3 md:grid-cols-2">
|
||||
<div>
|
||||
<div class="text-xs text-blue-600">Deposit Amount</div>
|
||||
<div class="font-semibold text-blue-900">
|
||||
£{selectedBooking.deposit_amount?.toFixed(2) || '0.00'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-blue-600">Deposit Status</div>
|
||||
<div class="font-semibold">
|
||||
<span
|
||||
class="{selectedBooking.deposit_paid
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-red-100 text-red-800'} inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium"
|
||||
>
|
||||
{selectedBooking.deposit_paid ? 'Paid' : 'Outstanding'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{#if selectedBooking.deposit_deadline && !selectedBooking.deposit_paid}
|
||||
<div class="md:col-span-2">
|
||||
<div class="text-xs text-blue-600">Deadline</div>
|
||||
<div class="font-semibold text-blue-900">
|
||||
{new SvelteDate(selectedBooking.deposit_deadline).toLocaleDateString('en-GB', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Services -->
|
||||
{#if selectedBooking.services && selectedBooking.services.length > 0}
|
||||
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
|
||||
@@ -281,11 +292,14 @@
|
||||
|
||||
<Modal.Footer class="flex items-center justify-end gap-2">
|
||||
{#if selectedBooking}
|
||||
<Button variant="outline" onclick={() => {
|
||||
<Button
|
||||
variant="outline"
|
||||
onclick={() => {
|
||||
if (selectedBooking) {
|
||||
window.open(`/api/bookings/${selectedBooking.id}/calendar`, '_blank');
|
||||
}
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
Add to Calendar
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import * as Modal from '$lib/components/ui/dialog';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import ApprovalModal from '$lib/components/admin/ApprovalModal.svelte';
|
||||
import type { Booking } from '$lib/types/booking';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
@@ -13,75 +14,6 @@
|
||||
|
||||
let { open = $bindable(), bookingId }: Props = $props();
|
||||
|
||||
type Booking = {
|
||||
id: string;
|
||||
start_time: string;
|
||||
status:
|
||||
| 'pending'
|
||||
| 'confirmed'
|
||||
| 'in_progress'
|
||||
| 'completed'
|
||||
| 'client_cancelled'
|
||||
| 'we_cancelled'
|
||||
| 're-schedule'
|
||||
| 'no_show';
|
||||
notes?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
created_by?: string;
|
||||
|
||||
user?: {
|
||||
id: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
full_name: string;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
profile_pic_url?: string;
|
||||
date_of_birth?: string;
|
||||
account_role: string;
|
||||
loyalty_stamps?: number;
|
||||
referral_code?: string;
|
||||
referral_code_uses?: number;
|
||||
created_at: string;
|
||||
notes?: string;
|
||||
};
|
||||
|
||||
services: Array<{
|
||||
booking_id: string;
|
||||
service_id: string;
|
||||
override_price?: number;
|
||||
override_duration_minutes?: number;
|
||||
service_name?: string;
|
||||
service_description?: string;
|
||||
price?: number;
|
||||
duration_minutes?: number;
|
||||
}>;
|
||||
|
||||
payments: Array<{
|
||||
id: string;
|
||||
booking_id: string;
|
||||
payment_type: 'deposit' | 'full' | 'tip' | 'balance' | 'partial';
|
||||
payment_method: 'online_square' | 'in_person_card' | 'cash' | 'giftcard' | 'discount';
|
||||
vendor_code?: string;
|
||||
invoice_number?: number;
|
||||
status: 'pending' | 'completed' | 'failed' | 'refunded';
|
||||
amount: number;
|
||||
is_vat_applicable: boolean;
|
||||
vat_rate?: number;
|
||||
vat_amount?: number;
|
||||
net_amount?: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
created_by?: string;
|
||||
}>;
|
||||
|
||||
total_amount: number;
|
||||
amount_paid: number;
|
||||
amount_due: number;
|
||||
duration_minutes: number;
|
||||
};
|
||||
|
||||
let selectedBooking = $state<Booking | null>(null);
|
||||
let showApprovalModal = $state(false);
|
||||
|
||||
@@ -111,6 +43,17 @@
|
||||
start_time: data.start_time,
|
||||
status: data.status,
|
||||
notes: data.notes,
|
||||
created_at: data.created_at,
|
||||
updated_at: data.updated_at,
|
||||
created_by: data.created_by,
|
||||
|
||||
// Deposit fields
|
||||
deposit_required: data.deposit_required ?? false,
|
||||
deposit_amount: data.deposit_amount,
|
||||
deposit_paid: data.deposit_paid ?? false,
|
||||
deposit_deadline: data.deposit_deadline,
|
||||
|
||||
// User
|
||||
user: data.user
|
||||
? {
|
||||
id: data.user.id,
|
||||
@@ -129,14 +72,20 @@
|
||||
notes: data.user.notes
|
||||
}
|
||||
: undefined,
|
||||
|
||||
// Services
|
||||
services: (data.services || []).map((s) => ({
|
||||
booking_id: s.booking_id,
|
||||
service_id: s.service_id,
|
||||
service_name: s.service_name,
|
||||
service_description: s.service_description,
|
||||
price: s.price,
|
||||
duration_minutes: s.duration_minutes
|
||||
duration_minutes: s.duration_minutes,
|
||||
override_price: s.override_price,
|
||||
override_duration_minutes: s.override_duration_minutes
|
||||
})),
|
||||
|
||||
// Payments
|
||||
payments: (data.payments || []).map((p) => ({
|
||||
id: p.id,
|
||||
booking_id: p.booking_id,
|
||||
@@ -154,13 +103,12 @@
|
||||
updated_at: p.updated_at,
|
||||
created_by: p.created_by
|
||||
})),
|
||||
|
||||
// Financials
|
||||
total_amount: data.total_amount || 0,
|
||||
amount_paid: data.amount_paid || 0,
|
||||
amount_due: data.amount_due || 0,
|
||||
duration_minutes: data.duration_minutes || 0,
|
||||
created_at: data.created_at,
|
||||
updated_at: data.updated_at,
|
||||
created_by: data.created_by
|
||||
duration_minutes: data.duration_minutes || 0
|
||||
};
|
||||
} else {
|
||||
const text = await response.text();
|
||||
@@ -281,6 +229,48 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Deposit Info -->
|
||||
{#if selectedBooking.deposit_required}
|
||||
<div class="rounded-lg border border-blue-200 bg-blue-50 p-4">
|
||||
<h3 class="mb-3 text-sm font-semibold tracking-wide text-blue-800 uppercase">
|
||||
Deposit Information
|
||||
</h3>
|
||||
<div class="grid gap-3 md:grid-cols-2">
|
||||
<div>
|
||||
<div class="text-xs text-blue-600">Deposit Amount</div>
|
||||
<div class="font-semibold text-blue-900">
|
||||
£{selectedBooking.deposit_amount?.toFixed(2) || '0.00'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-blue-600">Deposit Status</div>
|
||||
<div class="font-semibold">
|
||||
<span
|
||||
class="{selectedBooking.deposit_paid
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-red-100 text-red-800'} inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium"
|
||||
>
|
||||
{selectedBooking.deposit_paid ? 'Paid' : 'Outstanding'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{#if selectedBooking.deposit_deadline && !selectedBooking.deposit_paid}
|
||||
<div class="md:col-span-2">
|
||||
<div class="text-xs text-blue-600">Deadline</div>
|
||||
<div class="font-semibold text-blue-900">
|
||||
{new SvelteDate(selectedBooking.deposit_deadline).toLocaleDateString('en-GB', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Services -->
|
||||
{#if selectedBooking.services && selectedBooking.services.length > 0}
|
||||
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
|
||||
|
||||
@@ -8,64 +8,11 @@
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Skeleton } from '$lib/components/ui/skeleton';
|
||||
import type { Booking } from '$lib/types/booking';
|
||||
|
||||
// Props
|
||||
let { openBookingModal }: { openBookingModal: (bookingId: string) => void } = $props();
|
||||
|
||||
// Types
|
||||
type Booking = {
|
||||
id: string;
|
||||
start_time: string;
|
||||
status:
|
||||
| 'pending'
|
||||
| 'confirmed'
|
||||
| 'in_progress'
|
||||
| 'completed'
|
||||
| 'client_cancelled'
|
||||
| 'we_cancelled'
|
||||
| 're-schedule'
|
||||
| 'no_show';
|
||||
notes?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
created_by?: string;
|
||||
user?: {
|
||||
id: string;
|
||||
full_name: string;
|
||||
};
|
||||
services: Array<{
|
||||
booking_id: string;
|
||||
service_id: string;
|
||||
override_price?: number;
|
||||
override_duration_minutes?: number;
|
||||
service_name?: string;
|
||||
service_description?: string;
|
||||
price?: number;
|
||||
duration_minutes?: number;
|
||||
}>;
|
||||
payments: Array<{
|
||||
id: string;
|
||||
booking_id: string;
|
||||
payment_type: 'deposit' | 'full' | 'tip' | 'balance' | 'partial';
|
||||
payment_method: 'online_square' | 'in_person_card' | 'cash' | 'giftcard' | 'discount';
|
||||
vendor_code?: string;
|
||||
invoice_number?: number;
|
||||
status: 'pending' | 'completed' | 'failed' | 'refunded';
|
||||
amount: number;
|
||||
is_vat_applicable: boolean;
|
||||
vat_rate?: number;
|
||||
vat_amount?: number;
|
||||
net_amount?: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
created_by?: string;
|
||||
}>;
|
||||
total_amount: number;
|
||||
amount_paid: number;
|
||||
amount_due: number;
|
||||
duration_minutes: number;
|
||||
};
|
||||
|
||||
// State
|
||||
let bookings = $state<Booking[]>([]);
|
||||
let totalBookings = $state(0);
|
||||
@@ -110,28 +57,9 @@
|
||||
return;
|
||||
}
|
||||
|
||||
bookings = data.bookings.map((b: any) => ({
|
||||
id: b.id,
|
||||
start_time: b.start_time,
|
||||
status: b.status,
|
||||
notes: b.notes,
|
||||
created_at: b.created_at,
|
||||
updated_at: b.updated_at,
|
||||
created_by: b.created_by,
|
||||
user: b.user
|
||||
? {
|
||||
id: b.user.id,
|
||||
full_name: b.user.full_name
|
||||
}
|
||||
: undefined,
|
||||
services: b.services || [],
|
||||
total_amount: b.total_amount || 0,
|
||||
amount_paid: b.amount_paid || 0,
|
||||
amount_due: b.amount_due || 0,
|
||||
duration_minutes: b.duration_minutes || 0
|
||||
}));
|
||||
bookings = data.bookings as Booking[];
|
||||
totalBookings = data.total || 0;
|
||||
totalPages = data.totalPages || 1;
|
||||
totalPages = data.total_pages || 1;
|
||||
currentPage = data.page || 1;
|
||||
} else {
|
||||
const text = await response.text();
|
||||
@@ -222,7 +150,7 @@
|
||||
|
||||
// Format services list
|
||||
function formatServices(services: Booking['services']): string {
|
||||
const serviceNames = services.map((s) => s.service_name || 'Unknown Service');
|
||||
const serviceNames = services?.map((s) => s.service_name || 'Unknown Service') || [];
|
||||
if (serviceNames.length === 0) return 'No services';
|
||||
if (serviceNames.length === 1) return serviceNames[0];
|
||||
if (serviceNames.length === 2) return serviceNames.join(' and ');
|
||||
@@ -232,7 +160,7 @@
|
||||
// Get status badge classes
|
||||
function getStatusClasses(status: Booking['status']): string {
|
||||
const baseClasses = 'inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium';
|
||||
const statusMap = {
|
||||
const statusMap: Record<string, string> = {
|
||||
confirmed: 'bg-emerald-100 text-emerald-800',
|
||||
pending: 'bg-amber-100 text-amber-800',
|
||||
in_progress: 'bg-blue-100 text-blue-800',
|
||||
@@ -240,13 +168,14 @@
|
||||
client_cancelled: 'bg-red-100 text-red-800',
|
||||
we_cancelled: 'bg-rose-100 text-rose-800',
|
||||
're-schedule': 'bg-purple-100 text-purple-800',
|
||||
no_show: 'bg-gray-100 text-gray-800'
|
||||
no_show: 'bg-gray-100 text-gray-800',
|
||||
no_deposit: 'bg-orange-100 text-orange-800'
|
||||
};
|
||||
return `${baseClasses} ${statusMap[status] || 'bg-gray-100 text-gray-800'}`;
|
||||
}
|
||||
|
||||
function getStatusDotClasses(status: Booking['status']): string {
|
||||
const statusMap = {
|
||||
const statusMap: Record<string, string> = {
|
||||
confirmed: 'bg-emerald-600',
|
||||
pending: 'bg-amber-600',
|
||||
in_progress: 'bg-blue-600',
|
||||
@@ -254,7 +183,8 @@
|
||||
client_cancelled: 'bg-red-600',
|
||||
we_cancelled: 'bg-rose-600',
|
||||
're-schedule': 'bg-purple-600',
|
||||
no_show: 'bg-gray-600'
|
||||
no_show: 'bg-gray-600',
|
||||
no_deposit: 'bg-orange-600'
|
||||
};
|
||||
return `mr-1 h-1.5 w-1.5 rounded-full ${statusMap[status] || 'bg-gray-600'}`;
|
||||
}
|
||||
|
||||
@@ -651,17 +651,99 @@
|
||||
async function submitBooking() {
|
||||
isSubmitting = true;
|
||||
try {
|
||||
console.log('Submitting booking:', {
|
||||
services: selectedServices,
|
||||
date: selectedDate,
|
||||
time: selectedTime,
|
||||
customer: authStore.isAuthenticated ? authStore.currentUser : customerInfo
|
||||
// Build the start_time in ISO format
|
||||
if (!selectedDate || !selectedTime) {
|
||||
toast.error('Please select a date and time');
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert CalendarDate to JavaScript Date, then to ISO string
|
||||
const [hours, minutes] = selectedTime.split(':').map(Number);
|
||||
const bookingDate = selectedDate.toDate(getLocalTimeZone());
|
||||
bookingDate.setHours(hours, minutes, 0, 0);
|
||||
const startTimeISO = bookingDate.toISOString();
|
||||
|
||||
// Extract service IDs
|
||||
const serviceIds = selectedServices.map((s) => s.id);
|
||||
|
||||
// Build request body
|
||||
const requestBody = {
|
||||
service_ids: serviceIds,
|
||||
start_time: startTimeISO,
|
||||
notes: customerInfo.specialRequests || null
|
||||
};
|
||||
|
||||
console.log('Submitting booking:', requestBody);
|
||||
|
||||
const response = await fetch('/api/bookings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
},
|
||||
body: JSON.stringify(requestBody)
|
||||
});
|
||||
|
||||
toast.success('Booking submitted successfully!');
|
||||
if (response.ok) {
|
||||
const booking = await response.json();
|
||||
console.log('Booking created:', booking);
|
||||
|
||||
// Show success message with booking details
|
||||
const bookingDateStr = new Date(booking.start_time).toLocaleDateString('en-GB', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'long'
|
||||
});
|
||||
const bookingTimeStr = new Date(booking.start_time).toLocaleTimeString('en-GB', {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit'
|
||||
});
|
||||
|
||||
toast.success(`Booking confirmed for ${bookingDateStr} at ${bookingTimeStr}!`);
|
||||
|
||||
// Reset form and redirect to account page
|
||||
selectedServices = [];
|
||||
selectedDate = undefined;
|
||||
selectedTime = null;
|
||||
currentStep = 1;
|
||||
|
||||
// Navigate to account page to see bookings
|
||||
window.location.href = '/account';
|
||||
} else {
|
||||
// Handle error response
|
||||
const errorText = await response.text();
|
||||
let errorMessage = 'Failed to submit booking. Please try again.';
|
||||
|
||||
// Try to parse error message from backend
|
||||
try {
|
||||
const errorData = JSON.parse(errorText);
|
||||
if (errorData.error) {
|
||||
errorMessage = errorData.error;
|
||||
} else if (typeof errorData === 'string') {
|
||||
errorMessage = errorData;
|
||||
}
|
||||
} catch {
|
||||
// Use default message if parsing fails
|
||||
}
|
||||
|
||||
// Handle specific error cases
|
||||
if (response.status === 409) {
|
||||
toast.error('This time slot is no longer available. Please choose a different time.');
|
||||
} else if (errorMessage.includes('patch test') || errorMessage.includes('Patch test')) {
|
||||
toast.error(errorMessage + ' Please complete a patch test first.');
|
||||
} else if (errorMessage.includes('48 hours') || errorMessage.includes('48h')) {
|
||||
toast.error(errorMessage);
|
||||
} else if (response.status === 400) {
|
||||
toast.error(errorMessage);
|
||||
} else {
|
||||
toast.error('Failed to submit booking: ' + errorMessage);
|
||||
}
|
||||
|
||||
console.error('Booking submission failed:', response.status, errorText);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Booking submission failed:', error);
|
||||
toast.error('Failed to submit booking. Please try again.');
|
||||
console.error('Booking submission error:', error);
|
||||
toast.error('Network error. Please check your connection and try again.');
|
||||
} finally {
|
||||
isSubmitting = false;
|
||||
}
|
||||
|
||||
@@ -39,3 +39,94 @@ export interface AvailableHoursDay {
|
||||
slots: Array<{ startTime: string; endTime: string }>;
|
||||
source: string;
|
||||
}
|
||||
|
||||
export type BookingStatus =
|
||||
| 'pending'
|
||||
| 'confirmed'
|
||||
| 'in_progress'
|
||||
| 'completed'
|
||||
| 'client_cancelled'
|
||||
| 'we_cancelled'
|
||||
| 're-schedule'
|
||||
| 'no_show'
|
||||
| 'no_deposit';
|
||||
|
||||
export interface BookingService {
|
||||
booking_id: string;
|
||||
service_id: string;
|
||||
override_price?: number;
|
||||
override_duration_minutes?: number;
|
||||
service_name?: string;
|
||||
service_description?: string;
|
||||
price?: number;
|
||||
duration_minutes?: number;
|
||||
}
|
||||
|
||||
export interface BookingUser {
|
||||
id: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
full_name: string;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
profile_pic_url?: string;
|
||||
date_of_birth?: string;
|
||||
account_role: string;
|
||||
loyalty_stamps?: number;
|
||||
referral_code?: string;
|
||||
referral_code_uses?: number;
|
||||
created_at: string;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface Payment {
|
||||
id: string;
|
||||
booking_id: string;
|
||||
payment_type: 'deposit' | 'full' | 'tip' | 'balance' | 'partial';
|
||||
payment_method: 'online_square' | 'in_person_card' | 'cash' | 'giftcard' | 'discount';
|
||||
vendor_code?: string;
|
||||
invoice_number?: number;
|
||||
status: 'pending' | 'completed' | 'failed' | 'refunded';
|
||||
amount: number;
|
||||
is_vat_applicable: boolean;
|
||||
vat_rate?: number;
|
||||
vat_amount?: number;
|
||||
net_amount?: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
created_by?: string;
|
||||
}
|
||||
|
||||
export interface Booking {
|
||||
id: string;
|
||||
start_time: string;
|
||||
status: BookingStatus;
|
||||
notes?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
created_by?: string;
|
||||
|
||||
// Deposit fields
|
||||
deposit_required: boolean;
|
||||
deposit_amount?: number;
|
||||
deposit_paid: boolean;
|
||||
deposit_deadline?: string;
|
||||
|
||||
// Joined fields
|
||||
user?: BookingUser;
|
||||
services?: BookingService[];
|
||||
payments?: Payment[];
|
||||
total_amount: number;
|
||||
amount_paid: number;
|
||||
amount_due: number;
|
||||
duration_minutes: number;
|
||||
}
|
||||
|
||||
export interface BookingListResponse {
|
||||
bookings: Booking[];
|
||||
page: number;
|
||||
per_page: number;
|
||||
total: number;
|
||||
total_pages: number;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user