style: fix prefer-const and prettier formatting issues
This commit is contained in:
@@ -89,57 +89,57 @@
|
||||
);
|
||||
let placeholderDate = $state<CalendarDate>(minDate);
|
||||
|
||||
let hoursUntilAppointment = $derived(
|
||||
const hoursUntilAppointment = $derived(
|
||||
(new SvelteDate(booking.start_time).getTime() - new SvelteDate().getTime()) / (1000 * 60 * 60)
|
||||
);
|
||||
let hasPayments = $derived((booking.amount_paid ?? 0) > 0);
|
||||
let noticePeriodBlocked = $derived(
|
||||
const hasPayments = $derived((booking.amount_paid ?? 0) > 0);
|
||||
const noticePeriodBlocked = $derived(
|
||||
hasPayments ? hoursUntilAppointment < 72 : hoursUntilAppointment < 24
|
||||
);
|
||||
let noticePeriodWarning = $derived(
|
||||
const noticePeriodWarning = $derived(
|
||||
!hasPayments && hoursUntilAppointment < 24 && hoursUntilAppointment >= 0
|
||||
);
|
||||
let noticeBlockedMessage = $derived(
|
||||
const noticeBlockedMessage = $derived(
|
||||
hasPayments
|
||||
? 'This booking has payments and is too close to the original appointment to reschedule online.'
|
||||
: 'This booking is too close to the original appointment time to reschedule online.'
|
||||
);
|
||||
|
||||
let discountTotal = $derived(
|
||||
const discountTotal = $derived(
|
||||
booking.discounts?.reduce((sum, d) => sum + d.discount_amount, 0) ?? 0
|
||||
);
|
||||
|
||||
let bookingTotalDuration = $derived(
|
||||
const bookingTotalDuration = $derived(
|
||||
booking.services?.reduce(
|
||||
(sum, s) => sum + (s.override_duration_minutes ?? s.duration_minutes ?? 0),
|
||||
0
|
||||
) || 0
|
||||
);
|
||||
|
||||
let selectedServicesDuration = $derived(
|
||||
const selectedServicesDuration = $derived(
|
||||
selectedServices.reduce((sum, s) => sum + (s.duration_minutes ?? 0), 0)
|
||||
);
|
||||
|
||||
let hasOverrides = $derived(
|
||||
const hasOverrides = $derived(
|
||||
booking?.services?.some(
|
||||
(s) => s.override_price != null || s.override_duration_minutes != null
|
||||
) ?? false
|
||||
);
|
||||
|
||||
let slotDuration = $derived(
|
||||
const slotDuration = $derived(
|
||||
editMode === 'time' ? bookingTotalDuration : selectedServicesDuration
|
||||
);
|
||||
|
||||
let canSubmit = $derived(
|
||||
const canSubmit = $derived(
|
||||
submitting === false &&
|
||||
((editMode === 'time' && !!newDate && newTime.length >= 4) ||
|
||||
(editMode === 'services' && selectedServices.length > 0) ||
|
||||
(editMode === 'both-time' && !!newDate && newTime.length >= 4))
|
||||
);
|
||||
|
||||
let notesChanged = $derived(notes.trim() !== originalNotes.trim());
|
||||
const notesChanged = $derived(notes.trim() !== originalNotes.trim());
|
||||
|
||||
let modalTitle = $derived(() => {
|
||||
const modalTitle = $derived(() => {
|
||||
switch (editMode) {
|
||||
case 'select':
|
||||
return 'Edit/Reschedule';
|
||||
@@ -193,7 +193,7 @@
|
||||
|
||||
function calculateEndTime(startTime: string, durationMinutes: number): string {
|
||||
const [hours, minutes] = startTime.split(':').map(Number);
|
||||
let total = hours * 60 + minutes + durationMinutes;
|
||||
const total = hours * 60 + minutes + durationMinutes;
|
||||
const h = Math.floor(total / 60);
|
||||
const m = total % 60;
|
||||
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
|
||||
@@ -201,7 +201,7 @@
|
||||
|
||||
function calculatePreviousTime(time: string): string {
|
||||
const [h, m] = time.split(':').map(Number);
|
||||
let total = h * 60 + m - 15;
|
||||
const total = h * 60 + m - 15;
|
||||
return `${String(Math.floor(total / 60)).padStart(2, '0')}:${String(total % 60).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
@@ -654,7 +654,7 @@
|
||||
return Math.max(0, Math.min(nextBookingStart, workingEndMinutes) - bookingEndMinutes);
|
||||
}
|
||||
|
||||
let availableAdditionalServices = $derived(() => {
|
||||
const availableAdditionalServices = $derived(() => {
|
||||
if (editMode !== 'services') return [];
|
||||
const remaining = calculateRemainingTime();
|
||||
if (remaining <= 0) return [];
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
let { open = $bindable(), bookingId }: Props = $props();
|
||||
|
||||
let selectedBooking = $state<Booking | null>(null);
|
||||
let businessSettings = $derived(getBusinessInfo());
|
||||
const businessSettings = $derived(getBusinessInfo());
|
||||
let loading = $state(false);
|
||||
let hasPendingEditRequest = $state(false);
|
||||
let pendingEditRequest = $state<{
|
||||
@@ -44,80 +44,80 @@
|
||||
|
||||
// IMPORTANT: Use override_duration_minutes when present — services may have been
|
||||
// customised at booking time. Showing base values misleads users about what was booked.
|
||||
let totalDuration = $derived(
|
||||
const totalDuration = $derived(
|
||||
selectedBooking?.services?.reduce(
|
||||
(sum, service) => sum + (service.override_duration_minutes ?? service.duration_minutes ?? 0),
|
||||
0
|
||||
) || 0
|
||||
);
|
||||
|
||||
let isFutureBooking = $derived(
|
||||
const isFutureBooking = $derived(
|
||||
selectedBooking ? new SvelteDate(selectedBooking.start_time) > new SvelteDate() : false
|
||||
);
|
||||
|
||||
let hasPayments = $derived(
|
||||
const hasPayments = $derived(
|
||||
selectedBooking && selectedBooking.payments && selectedBooking.payments.length > 0
|
||||
);
|
||||
|
||||
let isCancellable = $derived(
|
||||
const isCancellable = $derived(
|
||||
selectedBooking && isFutureBooking && ['pending', 'confirmed'].includes(selectedBooking.status)
|
||||
);
|
||||
|
||||
let canEditBooking = $derived(isCancellable);
|
||||
const canEditBooking = $derived(isCancellable);
|
||||
|
||||
let totalPaid = $derived(
|
||||
const totalPaid = $derived(
|
||||
selectedBooking?.payments
|
||||
?.filter((p) => p.status === 'completed')
|
||||
.reduce((sum, p) => sum + p.amount, 0) || 0
|
||||
);
|
||||
|
||||
let totalRefunds = $derived(
|
||||
const totalRefunds = $derived(
|
||||
(selectedBooking?.refunds ?? [])
|
||||
.filter((r) => r.status === 'completed')
|
||||
.reduce((sum, r) => sum + r.amount, 0)
|
||||
);
|
||||
|
||||
let balanceDue = $derived(selectedBooking ? computeBalanceDue(selectedBooking) : 0);
|
||||
const balanceDue = $derived(selectedBooking ? computeBalanceDue(selectedBooking) : 0);
|
||||
|
||||
let totalVAT = $derived(
|
||||
const totalVAT = $derived(
|
||||
selectedBooking?.payments
|
||||
?.filter((p) => p.status === 'completed' && p.vat_amount)
|
||||
.reduce((sum, p) => sum + (p.vat_amount ?? 0), 0) || 0
|
||||
);
|
||||
|
||||
let totalNet = $derived(
|
||||
const totalNet = $derived(
|
||||
selectedBooking?.payments
|
||||
?.filter((p) => p.status === 'completed' && p.net_amount)
|
||||
.reduce((sum, p) => sum + (p.net_amount ?? 0), 0) || 0
|
||||
);
|
||||
|
||||
let hasVAT = $derived(totalVAT > 0);
|
||||
const hasVAT = $derived(totalVAT > 0);
|
||||
|
||||
let depositOutstanding = $derived(
|
||||
const depositOutstanding = $derived(
|
||||
selectedBooking?.deposit_required && !selectedBooking?.deposit_paid
|
||||
);
|
||||
|
||||
let canPayEarly = $derived(
|
||||
const canPayEarly = $derived(
|
||||
selectedBooking &&
|
||||
!depositOutstanding &&
|
||||
totalPaid < selectedBooking.total_amount &&
|
||||
['confirmed', 'in_progress'].includes(selectedBooking.status)
|
||||
);
|
||||
|
||||
let isCompleted = $derived(selectedBooking?.status === 'completed');
|
||||
const isCompleted = $derived(selectedBooking?.status === 'completed');
|
||||
|
||||
let hoursUntilAppointment = $derived(
|
||||
const hoursUntilAppointment = $derived(
|
||||
selectedBooking
|
||||
? (new SvelteDate(selectedBooking.start_time).getTime() - new SvelteDate().getTime()) /
|
||||
(1000 * 60 * 60)
|
||||
: Infinity
|
||||
);
|
||||
let protectedDeposit = $derived(
|
||||
const protectedDeposit = $derived(
|
||||
selectedBooking
|
||||
? Math.min(totalPaid, selectedBooking.total_amount * POLICY.PROTECTED_DEPOSIT_MAX_PCT)
|
||||
: 0
|
||||
);
|
||||
let estimatedRefund = $derived(
|
||||
const estimatedRefund = $derived(
|
||||
hoursUntilAppointment > POLICY.FULL_REFUND_THRESHOLD_HOURS
|
||||
? totalPaid
|
||||
: hoursUntilAppointment >= POLICY.PARTIAL_REFUND_THRESHOLD_HOURS
|
||||
@@ -133,11 +133,11 @@
|
||||
let customTipInput = $state('');
|
||||
let tipProcessing = $state(false);
|
||||
|
||||
let canSaveCards = $derived(
|
||||
const canSaveCards = $derived(
|
||||
authStore.currentUser?.role === 'verified_email' || authStore.currentUser?.role === 'affiliate'
|
||||
);
|
||||
|
||||
let tipPresets = $derived(
|
||||
const tipPresets = $derived(
|
||||
selectedBooking
|
||||
? [
|
||||
{ pct: 10, amount: Math.round(selectedBooking.total_amount * 0.1 * 100) / 100 },
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
minimum_age_required: validateCsMinimumAge(newCustomService.minimum_age_required)
|
||||
};
|
||||
}
|
||||
let isCustomFormValid = $derived(
|
||||
const isCustomFormValid = $derived(
|
||||
(newCustomService.name ?? '').trim() !== '' &&
|
||||
!customServiceErrors.name &&
|
||||
!customServiceErrors.price &&
|
||||
|
||||
@@ -32,29 +32,29 @@
|
||||
let cancelling = $state(false);
|
||||
|
||||
// Derived values for cancel confirmation
|
||||
let totalPaid = $derived(
|
||||
const totalPaid = $derived(
|
||||
(selectedBooking?.payments ?? [])
|
||||
.filter((p) => p.payment_method !== 'discount' && p.status === 'completed')
|
||||
.reduce((sum, p) => sum + p.amount, 0)
|
||||
);
|
||||
let totalRefunds = $derived(
|
||||
const totalRefunds = $derived(
|
||||
(selectedBooking?.refunds ?? [])
|
||||
.filter((r) => r.status === 'completed')
|
||||
.reduce((sum, r) => sum + r.amount, 0)
|
||||
);
|
||||
let balanceDue = $derived(selectedBooking ? computeBalanceDue(selectedBooking) : 0);
|
||||
let hoursUntilAppt = $derived(
|
||||
const balanceDue = $derived(selectedBooking ? computeBalanceDue(selectedBooking) : 0);
|
||||
const hoursUntilAppt = $derived(
|
||||
selectedBooking
|
||||
? (new SvelteDate(selectedBooking.start_time).getTime() - new SvelteDate().getTime()) /
|
||||
(1000 * 60 * 60)
|
||||
: Infinity
|
||||
);
|
||||
let protectedDeposit = $derived(
|
||||
const protectedDeposit = $derived(
|
||||
selectedBooking
|
||||
? Math.min(totalPaid, selectedBooking.total_amount * POLICY.PROTECTED_DEPOSIT_MAX_PCT)
|
||||
: 0
|
||||
);
|
||||
let estimatedRefund = $derived(
|
||||
const estimatedRefund = $derived(
|
||||
forgiveFeesCancel
|
||||
? totalPaid
|
||||
: hoursUntilAppt > POLICY.FULL_REFUND_THRESHOLD_HOURS
|
||||
@@ -97,7 +97,7 @@
|
||||
// IMPORTANT: Use override_duration_minutes when present — services may have been
|
||||
// customised at booking time (discounts, extended sessions). Showing base values
|
||||
// misleads admins about what was actually booked.
|
||||
let totalDuration = $derived(
|
||||
const totalDuration = $derived(
|
||||
selectedBooking?.services?.reduce(
|
||||
(sum, service) => sum + (service.override_duration_minutes ?? service.duration_minutes ?? 0),
|
||||
0
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import { formatUserName } from '$lib/utils/nameDisplay';
|
||||
|
||||
// Props
|
||||
let { openBookingModal }: { openBookingModal: (bookingId: string) => void } = $props();
|
||||
const { openBookingModal }: { openBookingModal: (bookingId: string) => void } = $props();
|
||||
|
||||
// State
|
||||
let bookings = $state<Booking[]>([]);
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
let services = $state<CustomService[]>([]);
|
||||
let total = $state(0);
|
||||
let page = $state(1);
|
||||
let perPage = $state(10);
|
||||
const perPage = $state(10);
|
||||
let searchQuery = $state('');
|
||||
let loading = $state(true);
|
||||
let creating = $state(false);
|
||||
@@ -95,7 +95,7 @@
|
||||
serviceErrors.minimum_age_required = validateMinimumAge(newService.minimum_age_required);
|
||||
}
|
||||
|
||||
let isFormValid = $derived(
|
||||
const isFormValid = $derived(
|
||||
(newService.name ?? '').trim() !== '' &&
|
||||
!serviceErrors.name &&
|
||||
!serviceErrors.price &&
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
let isFormValid = $derived.by(() => {
|
||||
const isFormValid = $derived.by(() => {
|
||||
if (!form.name.trim()) return false;
|
||||
if (form.discount_percent <= 0 || form.discount_percent > 100) return false;
|
||||
if (form.campaign_type === 'time_based') {
|
||||
|
||||
@@ -149,11 +149,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
let totalDuration = $derived(
|
||||
const totalDuration = $derived(
|
||||
services.reduce((sum, s) => sum + (s.override_duration_minutes ?? s.duration_minutes ?? 0), 0)
|
||||
);
|
||||
|
||||
let totalPrice = $derived(
|
||||
const totalPrice = $derived(
|
||||
services.reduce((sum, s) => sum + (s.override_price ?? s.price ?? 0), 0)
|
||||
);
|
||||
|
||||
@@ -280,7 +280,7 @@
|
||||
return services.some((s) => s.service_id === serviceId);
|
||||
}
|
||||
|
||||
let maxAvailableDuration = $derived.by(() => {
|
||||
const maxAvailableDuration = $derived.by(() => {
|
||||
if (!booking?.start_time || !nextAppointmentStart) return null;
|
||||
|
||||
const bookingStart = new SvelteDate(booking.start_time).getTime();
|
||||
@@ -292,7 +292,7 @@
|
||||
return Math.max(0, Math.floor(availableMs / (60 * 1000)));
|
||||
});
|
||||
|
||||
let filteredServices = $derived.by(() => {
|
||||
const filteredServices = $derived.by(() => {
|
||||
return availableServices.filter((s) => {
|
||||
if (isServiceAdded(s.id)) return false;
|
||||
if (maxAvailableDuration !== null && s.duration_minutes > maxAvailableDuration) return false;
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
let serviceDiff = $derived.by(() => {
|
||||
const serviceDiff = $derived.by(() => {
|
||||
const orig = editRequest.original.services;
|
||||
const prop = editRequest.proposed.services;
|
||||
const origIds = new Set(orig.map((s) => s.id));
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
user_balances: UserBalance[];
|
||||
}
|
||||
|
||||
let summary = $state<GiftCardSummary>({
|
||||
const summary = $state<GiftCardSummary>({
|
||||
total_unclaimed: 0,
|
||||
total_user_balances: 0,
|
||||
gift_cards: [],
|
||||
@@ -196,32 +196,32 @@
|
||||
let topUpMode = $state<'giveaway' | 'purchase'>('giveaway');
|
||||
|
||||
// Validation
|
||||
let generateError = $derived(
|
||||
const generateError = $derived(
|
||||
generateAmount && (isNaN(Number(generateAmount)) || Number(generateAmount) <= 0)
|
||||
? 'Must be a valid positive number'
|
||||
: ''
|
||||
);
|
||||
let topUpError = $derived(
|
||||
const topUpError = $derived(
|
||||
topUpAmount && (isNaN(Number(topUpAmount)) || Number(topUpAmount) <= 0)
|
||||
? 'Must be a valid positive number'
|
||||
: ''
|
||||
);
|
||||
let transferAmountError = $derived(
|
||||
const transferAmountError = $derived(
|
||||
transferAmount && (isNaN(Number(transferAmount)) || Number(transferAmount) <= 0)
|
||||
? 'Must be a valid positive number'
|
||||
: ''
|
||||
);
|
||||
let transferCodeError = $derived(
|
||||
const transferCodeError = $derived(
|
||||
transferToCode && transferToCode.replace(/[^a-zA-Z0-9]/g, '').length !== 12
|
||||
? 'Code must be exactly 12 characters'
|
||||
: ''
|
||||
);
|
||||
|
||||
let isAmountValid = $derived(
|
||||
const isAmountValid = $derived(
|
||||
generateType === 'stock' ? true : !!(generateAmount && !generateError)
|
||||
);
|
||||
|
||||
let isEmailValid = $derived.by(() => {
|
||||
const isEmailValid = $derived.by(() => {
|
||||
if (generateType !== 'code') return true;
|
||||
const trimmed = generateEmail.trim();
|
||||
if (isGuestSelected) {
|
||||
@@ -233,20 +233,20 @@
|
||||
return trimmed.includes('@') && trimmed.includes('.');
|
||||
});
|
||||
|
||||
let isGenerateValid = $derived(generateType === 'stock' ? true : isAmountValid && isEmailValid);
|
||||
const isGenerateValid = $derived(generateType === 'stock' ? true : isAmountValid && isEmailValid);
|
||||
|
||||
let isTopUpValid = $derived(topUpAmount && !topUpError);
|
||||
let isTransferValid = $derived(
|
||||
const isTopUpValid = $derived(topUpAmount && !topUpError);
|
||||
const isTransferValid = $derived(
|
||||
transferAmount && !transferAmountError && transferToCode && !transferCodeError
|
||||
);
|
||||
|
||||
// TillPayment state
|
||||
let showTillPayment = $state(false);
|
||||
let tillAmount = $state(0);
|
||||
let tillAction = $state<'create' | 'topup'>('create');
|
||||
let tillGiftCardId = $state<string | undefined>(undefined);
|
||||
let tillUserId = $state<string | undefined>(undefined);
|
||||
let tillDelivery = $state<'account' | 'code'>('code');
|
||||
const showTillPayment = $state(false);
|
||||
const tillAmount = $state(0);
|
||||
const tillAction = $state<'create' | 'topup'>('create');
|
||||
const tillGiftCardId = $state<string | undefined>(undefined);
|
||||
const tillUserId = $state<string | undefined>(undefined);
|
||||
const tillDelivery = $state<'account' | 'code'>('code');
|
||||
|
||||
async function fetchGiftCards(page: number = 1, search: string = '') {
|
||||
loading = true;
|
||||
@@ -512,7 +512,7 @@
|
||||
|
||||
// =============== Embedded Payment Handlers ===============
|
||||
|
||||
let isEphemeralCardValid = $derived(
|
||||
const isEphemeralCardValid = $derived(
|
||||
ephemeralCardNumber.replace(/\s/g, '').length >= 13 &&
|
||||
ephemeralCardExpiry.includes('/') &&
|
||||
ephemeralCardExpiry.length === 5 &&
|
||||
@@ -811,8 +811,11 @@
|
||||
| 'balance'
|
||||
| 'updated';
|
||||
|
||||
let cardSort = $state<{ key: SortKey; dir: 'asc' | 'desc' }>({ key: 'created', dir: 'desc' });
|
||||
let balanceSort = $state<{ key: SortKey; dir: 'asc' | 'desc' }>({ key: 'updated', dir: 'desc' });
|
||||
const cardSort = $state<{ key: SortKey; dir: 'asc' | 'desc' }>({ key: 'created', dir: 'desc' });
|
||||
const balanceSort = $state<{ key: SortKey; dir: 'asc' | 'desc' }>({
|
||||
key: 'updated',
|
||||
dir: 'desc'
|
||||
});
|
||||
|
||||
function toggleCardSort(key: SortKey) {
|
||||
if (cardSort.key === key) {
|
||||
@@ -832,7 +835,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
let sortedCards = $derived.by(() => {
|
||||
const sortedCards = $derived.by(() => {
|
||||
let filtered = [...cards];
|
||||
if (activeSection === 'cards') {
|
||||
filtered = cards.filter((gc) => !isExpired(gc.last_used_at));
|
||||
@@ -864,10 +867,10 @@
|
||||
return sorted;
|
||||
});
|
||||
|
||||
let activeCardsCount = $derived(cards.filter((gc) => !isExpired(gc.last_used_at)).length);
|
||||
let expiredCardsCount = $derived(cards.filter((gc) => isExpired(gc.last_used_at)).length);
|
||||
const activeCardsCount = $derived(cards.filter((gc) => !isExpired(gc.last_used_at)).length);
|
||||
const expiredCardsCount = $derived(cards.filter((gc) => isExpired(gc.last_used_at)).length);
|
||||
|
||||
let sortedBalances = $derived.by(() => {
|
||||
const sortedBalances = $derived.by(() => {
|
||||
const bals = [...balances];
|
||||
const { key, dir } = balanceSort;
|
||||
const mul = dir === 'asc' ? 1 : -1;
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
let exceptionGroupsLoading = $state(true);
|
||||
let savingHours = $state(false);
|
||||
|
||||
let formErrors = $state({
|
||||
const formErrors = $state({
|
||||
name: '',
|
||||
weeks: ''
|
||||
});
|
||||
@@ -59,7 +59,7 @@
|
||||
]
|
||||
});
|
||||
|
||||
let isFormValid = $derived(
|
||||
const isFormValid = $derived(
|
||||
exceptionDraft.name.trim() !== '' && exceptionDraft.weekStarts.length > 0
|
||||
);
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
return file.size > MAX_FILE_SIZE;
|
||||
}
|
||||
|
||||
let hasOversizedFiles = $derived(uploadFiles.some(isFileTooBig));
|
||||
const hasOversizedFiles = $derived(uploadFiles.some(isFileTooBig));
|
||||
|
||||
function isHeicFile(file: File): boolean {
|
||||
const name = file.name.toLowerCase();
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
validateExpiryField();
|
||||
}
|
||||
|
||||
let isFormValid = $derived(
|
||||
const isFormValid = $derived(
|
||||
formData.name.trim() !== '' &&
|
||||
!formErrors.name &&
|
||||
!formErrors.notice_duration &&
|
||||
|
||||
@@ -65,20 +65,20 @@
|
||||
maxDate.getDate()
|
||||
);
|
||||
|
||||
let hoursUntilAppointment = $derived(
|
||||
const hoursUntilAppointment = $derived(
|
||||
(new SvelteDate(booking.start_time).getTime() - new SvelteDate().getTime()) / (1000 * 60 * 60)
|
||||
);
|
||||
let hasPayments = $derived((booking.amount_paid ?? 0) > 0);
|
||||
let showNoticeWarning = $derived(
|
||||
const hasPayments = $derived((booking.amount_paid ?? 0) > 0);
|
||||
const showNoticeWarning = $derived(
|
||||
hasPayments ? hoursUntilAppointment < 72 : hoursUntilAppointment < 24
|
||||
);
|
||||
let showNoShowWarning = $derived(
|
||||
const showNoShowWarning = $derived(
|
||||
!hasPayments && hoursUntilAppointment < 24 && hoursUntilAppointment >= 0
|
||||
);
|
||||
let forgiveFees = $state(false);
|
||||
let forgiveNoShow = $state(false);
|
||||
|
||||
let bookingDuration = $derived(
|
||||
const bookingDuration = $derived(
|
||||
booking.services?.reduce(
|
||||
(sum, s) => sum + (s.override_duration_minutes ?? s.duration_minutes ?? 0),
|
||||
0
|
||||
|
||||
@@ -36,9 +36,9 @@
|
||||
is_active: true,
|
||||
minimum_age_required: 0
|
||||
});
|
||||
let editingService = $state<Service | null>(null);
|
||||
const editingService = $state<Service | null>(null);
|
||||
let servicesLoading = $state(true);
|
||||
let servicesUpdating = $state<Record<string, boolean>>({});
|
||||
const servicesUpdating = $state<Record<string, boolean>>({});
|
||||
let showServiceModal = $state(false);
|
||||
let creatingService = $state(false);
|
||||
let serviceErrors = $state<Record<string, string>>({});
|
||||
@@ -129,7 +129,7 @@
|
||||
);
|
||||
}
|
||||
|
||||
let isFormValid = $derived(
|
||||
const isFormValid = $derived(
|
||||
newService.name !== '' &&
|
||||
!serviceErrors.name &&
|
||||
!serviceErrors.price &&
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
let giftCardAmount = $state('25');
|
||||
let showGiftCardInput = $state(false);
|
||||
|
||||
let subtotal = $derived(cart.reduce((sum, item) => sum + item.price * item.qty, 0));
|
||||
let itemCount = $derived(cart.reduce((sum, item) => sum + item.qty, 0));
|
||||
const subtotal = $derived(cart.reduce((sum, item) => sum + item.price * item.qty, 0));
|
||||
const itemCount = $derived(cart.reduce((sum, item) => sum + item.qty, 0));
|
||||
|
||||
function formatCurrency(n: number): string {
|
||||
return new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP' }).format(n);
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
rescheduleVersion?: number;
|
||||
}
|
||||
|
||||
let { openUserModal, openBookingModal, rescheduleVersion = 0 }: Props = $props();
|
||||
const { openUserModal, openBookingModal, rescheduleVersion = 0 }: Props = $props();
|
||||
|
||||
const PAGE_SIZE = 5;
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
|
||||
let overlappingBookings = $state<OverlappingBooking[]>([]);
|
||||
let hasOverlap = $state(false);
|
||||
let hasDayConflicts = $derived(overlappingBookings.length > 0);
|
||||
const hasDayConflicts = $derived(overlappingBookings.length > 0);
|
||||
|
||||
let showDeleteAlert = $state(false);
|
||||
let blockerToDelete = $state<TimeBlocker | null>(null);
|
||||
@@ -128,9 +128,9 @@
|
||||
return `${row.startTime}-${row.endTime}-${row.isOpen}`;
|
||||
}
|
||||
|
||||
let selectedWorkingHours = $derived.by(() => getWorkingHoursForDate(newStartDate));
|
||||
const selectedWorkingHours = $derived.by(() => getWorkingHoursForDate(newStartDate));
|
||||
|
||||
let availableStartOptions = $derived.by(() => {
|
||||
const availableStartOptions = $derived.by(() => {
|
||||
const wh = selectedWorkingHours;
|
||||
if (!wh || !wh.isOpen) return [];
|
||||
const startMin = timeToMinutes(wh.startTime);
|
||||
@@ -154,7 +154,7 @@
|
||||
return options;
|
||||
});
|
||||
|
||||
let availableEndOptions = $derived.by(() => {
|
||||
const availableEndOptions = $derived.by(() => {
|
||||
const wh = selectedWorkingHours;
|
||||
if (!wh || !wh.isOpen) return [];
|
||||
const startMin = timeToMinutes(wh.startTime);
|
||||
@@ -197,14 +197,14 @@
|
||||
return `in ${Math.floor(hr / 24)}d`;
|
||||
}
|
||||
|
||||
let sortedBlockers = $derived.by(() => {
|
||||
const sortedBlockers = $derived.by(() => {
|
||||
return [...blockers].sort(
|
||||
(a, b) => new SvelteDate(a.start_time).getTime() - new SvelteDate(b.start_time).getTime()
|
||||
);
|
||||
});
|
||||
|
||||
let totalPages = $derived.by(() => Math.max(1, Math.ceil(sortedBlockers.length / PAGE_SIZE)));
|
||||
let pagedBlockers = $derived.by(() => {
|
||||
const totalPages = $derived.by(() => Math.max(1, Math.ceil(sortedBlockers.length / PAGE_SIZE)));
|
||||
const pagedBlockers = $derived.by(() => {
|
||||
const start = (currentPage - 1) * PAGE_SIZE;
|
||||
return sortedBlockers.slice(start, start + PAGE_SIZE);
|
||||
});
|
||||
@@ -275,13 +275,13 @@
|
||||
return formatLocalDateTime(dt);
|
||||
}
|
||||
|
||||
let formComplete = $derived.by(() => {
|
||||
const formComplete = $derived.by(() => {
|
||||
return (
|
||||
newDescription.trim() !== '' && newStartDate !== '' && startHour !== '' && endHour !== ''
|
||||
);
|
||||
});
|
||||
|
||||
let canCreate = $derived.by(() => {
|
||||
const canCreate = $derived.by(() => {
|
||||
return formComplete && !hasOverlap && !hasDayConflicts && !checkingOverlap;
|
||||
});
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
openUserModal: (userId: string) => void;
|
||||
}
|
||||
|
||||
let { openUserModal }: Props = $props();
|
||||
const { openUserModal }: Props = $props();
|
||||
|
||||
type UserListItem = {
|
||||
id: string;
|
||||
|
||||
@@ -322,13 +322,13 @@
|
||||
window.__walkInCountdownInterval = setInterval(updateCountdown, 1000);
|
||||
}
|
||||
|
||||
let availableMinutes = $derived.by(() => {
|
||||
const availableMinutes = $derived.by(() => {
|
||||
if (!slotInfo) return null;
|
||||
if (slotInfo.isAvailableNow) return getLiveRemainingMinutes();
|
||||
return slotInfo.durationMinutes;
|
||||
});
|
||||
|
||||
let tooShortForService = $derived.by(() => {
|
||||
const tooShortForService = $derived.by(() => {
|
||||
if (shortestServiceMinutes === null || availableMinutes === null) return false;
|
||||
return availableMinutes < shortestServiceMinutes;
|
||||
});
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
minimum_age_required: validateCsMinimumAge(newCustomService.minimum_age_required)
|
||||
};
|
||||
}
|
||||
let isCustomFormValid = $derived(
|
||||
const isCustomFormValid = $derived(
|
||||
(newCustomService.name ?? '').trim() !== '' &&
|
||||
!customServiceErrors.name &&
|
||||
!customServiceErrors.price &&
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
let selectedServices = $state<Service[]>([]);
|
||||
let selectedDate = $state<CalendarDate | undefined>(undefined);
|
||||
let selectedTime = $state<string | null>(null);
|
||||
let customerInfo = $state<CustomerInfo>({
|
||||
const customerInfo = $state<CustomerInfo>({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
@@ -98,13 +98,13 @@
|
||||
let newCardNumber = $state('');
|
||||
let newCardExpiry = $state('');
|
||||
let newCardCVC = $state('');
|
||||
let saveCardForFuture = $state(false);
|
||||
const saveCardForFuture = $state(false);
|
||||
|
||||
// Payment flow state
|
||||
let depositPaid = $state(false);
|
||||
let showPaymentForm = $state(false);
|
||||
|
||||
let depositCardFormValid = $derived(
|
||||
const depositCardFormValid = $derived(
|
||||
selectedPaymentMethod !== null ||
|
||||
(showNewCardForm &&
|
||||
newCardNumber.replace(/\s/g, '').length >= 13 &&
|
||||
@@ -113,17 +113,17 @@
|
||||
);
|
||||
|
||||
// VAT registration status from public business info (via shared store)
|
||||
let vatRegistered = $derived(getBusinessInfo()?.is_vat_registered ?? false);
|
||||
const vatRegistered = $derived(getBusinessInfo()?.is_vat_registered ?? false);
|
||||
|
||||
// Email existence check (guest flow only)
|
||||
let emailChecking = $state(false);
|
||||
let emailSuggestion = $state<string | null>(null);
|
||||
let emailError = $state('');
|
||||
let emailFormatValid = $derived(
|
||||
const emailFormatValid = $derived(
|
||||
!customerInfo.email ||
|
||||
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(customerInfo.email)
|
||||
);
|
||||
let allGuestFieldsValid = $derived(
|
||||
const allGuestFieldsValid = $derived(
|
||||
customerInfo.firstName &&
|
||||
customerInfo.lastName &&
|
||||
customerInfo.email &&
|
||||
@@ -571,12 +571,12 @@
|
||||
let loadingWorkingHours = $state<boolean>(false);
|
||||
let loadingAvailableHours = $state<boolean>(false);
|
||||
|
||||
let workingHoursCache: Record<
|
||||
const workingHoursCache: Record<
|
||||
string,
|
||||
Record<string, { isOpen: boolean; startTime: string; endTime: string }>
|
||||
> = {};
|
||||
|
||||
let availableHoursCache: Record<
|
||||
const availableHoursCache: Record<
|
||||
string,
|
||||
Record<string, { isOpen: boolean; slots: Array<{ startTime: string; endTime: string }> }>
|
||||
> = {};
|
||||
@@ -602,7 +602,7 @@
|
||||
});
|
||||
|
||||
// Track which months are currently being fetched (prevents duplicate requests)
|
||||
let loadingMonths: Record<string, boolean> = {};
|
||||
const loadingMonths: Record<string, boolean> = {};
|
||||
|
||||
// Preload current + next month on first render; subsequent months fetched individually
|
||||
let initialLoadDone = $state(false);
|
||||
@@ -1304,13 +1304,13 @@
|
||||
selectedDate ? getDayWithOrdinal(selectedDate) : undefined
|
||||
);
|
||||
|
||||
let depositRequired = $derived(calculateDepositRequired());
|
||||
let totalSteps = $derived(authStore.isAuthenticated ? 4 : 5);
|
||||
const depositRequired = $derived(calculateDepositRequired());
|
||||
const totalSteps = $derived(authStore.isAuthenticated ? 4 : 5);
|
||||
// StepIndicator uses displayNumber = startAt + index. currentStep aligns with displayNumber,
|
||||
// not the array index. For auth: startAt=1, totalSteps=4 → last displayNumber=4.
|
||||
// For guest: startAt=0, totalSteps=5 → last displayNumber=4. Always evaluates to 4.
|
||||
let finalStep = $derived(totalSteps - 1 + (authStore.isAuthenticated ? 1 : 0));
|
||||
let stepLabels = $derived(
|
||||
const finalStep = $derived(totalSteps - 1 + (authStore.isAuthenticated ? 1 : 0));
|
||||
const stepLabels = $derived(
|
||||
authStore.isAuthenticated
|
||||
? ['Service', 'Date & Time', 'Details', 'Payment']
|
||||
: userDepositsRequired > 0
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { getLocalTimeZone } from '@internationalized/date';
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
|
||||
let {
|
||||
const {
|
||||
services = [],
|
||||
date = undefined,
|
||||
time = null,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { CalendarDate, DateValue } from '@internationalized/date';
|
||||
import Calendar from '$lib/components/ui/calendar/calendar.svelte';
|
||||
|
||||
let {
|
||||
const {
|
||||
date,
|
||||
placeholder,
|
||||
minValue,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
outOfHours?: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
const {
|
||||
selectedDate,
|
||||
selectedTime,
|
||||
endTime,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { resolve } from '$app/paths';
|
||||
import type { Service } from '$lib/types/booking';
|
||||
|
||||
let {
|
||||
const {
|
||||
service,
|
||||
selected = false,
|
||||
onclick,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import ServiceCard from './ServiceCard.svelte';
|
||||
import type { Service } from '$lib/types/booking';
|
||||
|
||||
let {
|
||||
const {
|
||||
services = [],
|
||||
selected = [],
|
||||
loading = true,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
onSelect: (time: string) => void;
|
||||
}
|
||||
|
||||
let { slots, selectedTime, duration, protection, onSelect }: Props = $props();
|
||||
const { slots, selectedTime, duration, protection, onSelect }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if slots.length > 0}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { CalendarDate } from '@internationalized/date';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
|
||||
let {
|
||||
const {
|
||||
date,
|
||||
groupedTimeSlots = [],
|
||||
selectedTime = null,
|
||||
|
||||
@@ -52,8 +52,8 @@
|
||||
};
|
||||
|
||||
// Derived values for auth state - ensures reactivity
|
||||
let isAuthenticated = $derived(authStore.isAuthenticated);
|
||||
let isLoading = $derived(authStore.isLoading);
|
||||
const isAuthenticated = $derived(authStore.isAuthenticated);
|
||||
const isLoading = $derived(authStore.isLoading);
|
||||
|
||||
// Notification bell state
|
||||
let unreadCount = $state(0);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
onComplete: (payment: PaymentResult) => void;
|
||||
}
|
||||
|
||||
let { booking, onClose, onComplete }: Props = $props();
|
||||
const { booking, onClose, onComplete }: Props = $props();
|
||||
|
||||
type PaymentStatus =
|
||||
| 'idle'
|
||||
@@ -47,17 +47,17 @@
|
||||
let paymentResult = $state<PaymentResult | null>(null);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
let stamps = $derived(booking.user?.loyalty_stamps ?? 0);
|
||||
const stamps = $derived(booking.user?.loyalty_stamps ?? 0);
|
||||
let useLoyalty = $state(false);
|
||||
|
||||
let loyaltyEligible = $derived(
|
||||
const loyaltyEligible = $derived(
|
||||
stamps >= 10 &&
|
||||
!(booking.discounts ?? []).some((d: BookingDiscount) => d.discount_source === 'loyalty') &&
|
||||
booking.total_amount > 0 &&
|
||||
booking.amount_paid === 0
|
||||
);
|
||||
|
||||
let loyaltyDiscount = $derived(
|
||||
const loyaltyDiscount = $derived(
|
||||
useLoyalty ? Math.round(booking.total_amount * 100 * LOYALTY_DISCOUNT_RATE) : 0
|
||||
);
|
||||
|
||||
@@ -203,13 +203,15 @@
|
||||
return service.override_price ?? service.price ?? 0;
|
||||
}
|
||||
|
||||
let subtotal = $derived((booking.services ?? []).reduce((sum, s) => sum + getServicePrice(s), 0));
|
||||
let discountSum = $derived(
|
||||
const subtotal = $derived(
|
||||
(booking.services ?? []).reduce((sum, s) => sum + getServicePrice(s), 0)
|
||||
);
|
||||
const discountSum = $derived(
|
||||
(booking.discounts ?? []).reduce((sum, d) => sum + d.discount_amount, 0)
|
||||
);
|
||||
let netTotal = $derived(Math.max(0, subtotal - discountSum));
|
||||
const netTotal = $derived(Math.max(0, subtotal - discountSum));
|
||||
|
||||
let tipPercentages = $derived.by(() => {
|
||||
const tipPercentages = $derived.by(() => {
|
||||
if (netTotal <= 0) return [];
|
||||
return [
|
||||
{ pct: 10, amount: Math.round(netTotal * 0.1 * 100) / 100 },
|
||||
@@ -218,7 +220,7 @@
|
||||
];
|
||||
});
|
||||
|
||||
let tipMultiplier = $derived(
|
||||
const tipMultiplier = $derived(
|
||||
selectedTipPercent !== null
|
||||
? 1 + selectedTipPercent / 100
|
||||
: customTipAmount && parseFloat(customTipAmount) > 0
|
||||
@@ -226,8 +228,8 @@
|
||||
: 1
|
||||
);
|
||||
|
||||
let totalWithTip = $derived(tipEnabled ? netTotal * tipMultiplier : netTotal);
|
||||
let tipDisplay = $derived(
|
||||
const totalWithTip = $derived(tipEnabled ? netTotal * tipMultiplier : netTotal);
|
||||
const tipDisplay = $derived(
|
||||
selectedTipPercent !== null
|
||||
? `${selectedTipPercent}%`
|
||||
: customTipAmount && parseFloat(customTipAmount) > 0
|
||||
@@ -235,7 +237,7 @@
|
||||
: ''
|
||||
);
|
||||
|
||||
let totalDue = $derived(tipEnabled ? totalWithTip : netTotal);
|
||||
const totalDue = $derived(tipEnabled ? totalWithTip : netTotal);
|
||||
|
||||
function formatCurrency(value: number): string {
|
||||
return new Intl.NumberFormat('en-GB', {
|
||||
@@ -378,8 +380,8 @@
|
||||
});
|
||||
|
||||
let cashAmount = $state<string>('');
|
||||
let cashAmountNum = $derived(cashAmount === '' ? 0 : parseFloat(cashAmount));
|
||||
let changeDue = $derived(cashAmountNum > totalDue ? cashAmountNum - totalDue : 0);
|
||||
const cashAmountNum = $derived(cashAmount === '' ? 0 : parseFloat(cashAmount));
|
||||
const changeDue = $derived(cashAmountNum > totalDue ? cashAmountNum - totalDue : 0);
|
||||
let extraAsTip = $state(false);
|
||||
|
||||
function handleCashInput(e: Event) {
|
||||
@@ -511,7 +513,7 @@
|
||||
giftCardId = formatted;
|
||||
}
|
||||
|
||||
let giftCardValid = $derived(useAccountBalance || giftCardId.replace(/-/g, '').length === 12);
|
||||
const giftCardValid = $derived(useAccountBalance || giftCardId.replace(/-/g, '').length === 12);
|
||||
|
||||
async function handleGiftCardPayment() {
|
||||
if (!giftCardValid) {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
onComplete: (result: TillSaleResult) => void;
|
||||
}
|
||||
|
||||
let {
|
||||
const {
|
||||
amount,
|
||||
itemType,
|
||||
action,
|
||||
@@ -100,9 +100,9 @@
|
||||
|
||||
// Cash
|
||||
let cashAmount = $state('');
|
||||
let cashAmountNum = $derived(Number(cashAmount) || 0);
|
||||
let changeDue = $derived(Math.max(0, cashAmountNum - amount));
|
||||
let cashEntryComplete = $derived(cashAmountNum >= amount);
|
||||
const cashAmountNum = $derived(Number(cashAmount) || 0);
|
||||
const changeDue = $derived(Math.max(0, cashAmountNum - amount));
|
||||
const cashEntryComplete = $derived(cashAmountNum >= amount);
|
||||
// Remember cash received for success screen display
|
||||
let cashTendered = $state(0);
|
||||
|
||||
@@ -224,13 +224,13 @@
|
||||
ephemeralCardCVC = input.value.replace(/\D/g, '').substring(0, 4);
|
||||
}
|
||||
|
||||
let isEphemeralCardValid = $derived(
|
||||
const isEphemeralCardValid = $derived(
|
||||
isValidLuhn(ephemeralCardNumber) &&
|
||||
/^\d{2}\/\d{2}$/.test(ephemeralCardExpiry) &&
|
||||
ephemeralCardCVC.length >= 3
|
||||
);
|
||||
|
||||
let ephemeralCardError = $derived(
|
||||
const ephemeralCardError = $derived(
|
||||
ephemeralCardNumber.length > 0 && !isValidLuhn(ephemeralCardNumber)
|
||||
? 'Invalid card number'
|
||||
: /^\d{2}\/\d{2}$/.test(ephemeralCardExpiry) &&
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
defaultPaymentType?: 'full' | 'partial' | 'deposit';
|
||||
}
|
||||
|
||||
let { booking, onClose, onComplete, canSaveCards = true, defaultPaymentType }: Props = $props();
|
||||
const { booking, onClose, onComplete, canSaveCards = true, defaultPaymentType }: Props = $props();
|
||||
|
||||
type PaymentStatus = 'idle' | 'processing' | 'polling' | 'success' | 'error';
|
||||
|
||||
@@ -140,9 +140,9 @@
|
||||
return { month, year };
|
||||
}
|
||||
|
||||
let expiryParts = $derived(parseExpiryParts(newCardExpiry));
|
||||
const expiryParts = $derived(parseExpiryParts(newCardExpiry));
|
||||
|
||||
let isExpiryInPast = $derived(
|
||||
const isExpiryInPast = $derived(
|
||||
expiryParts !== null &&
|
||||
(() => {
|
||||
const expiryDate = new SvelteDate(expiryParts.year, expiryParts.month);
|
||||
@@ -150,7 +150,7 @@
|
||||
})()
|
||||
);
|
||||
|
||||
let hasInvalidMonth = $derived(/^\d{2}\/\d{2}$/.test(newCardExpiry) && expiryParts === null);
|
||||
const hasInvalidMonth = $derived(/^\d{2}\/\d{2}$/.test(newCardExpiry) && expiryParts === null);
|
||||
|
||||
function isValidLuhn(cardNumber: string): boolean {
|
||||
const s = cardNumber.replace(/\D/g, '');
|
||||
@@ -168,17 +168,17 @@
|
||||
return sum % 10 === 0 && s.length >= 13 && s.length <= 19;
|
||||
}
|
||||
|
||||
let cardFormValid = $derived(
|
||||
const cardFormValid = $derived(
|
||||
isValidLuhn(newCardNumber) && expiryParts !== null && newCardCVC.length >= 3 && !isExpiryInPast
|
||||
);
|
||||
|
||||
let cardSelected = $derived(
|
||||
const cardSelected = $derived(
|
||||
(selectedPaymentMethod !== null && paymentMethods.length > 0) ||
|
||||
(showNewCardForm && cardFormValid) ||
|
||||
(paymentMethods.length === 0 && cardFormValid)
|
||||
);
|
||||
|
||||
let cardValidationError = $derived(
|
||||
const cardValidationError = $derived(
|
||||
!cardSelected
|
||||
? selectedPaymentMethod === null && paymentMethods.length > 0 && !showNewCardForm
|
||||
? 'Please select a card'
|
||||
@@ -211,12 +211,12 @@
|
||||
} | null>(null);
|
||||
|
||||
// Derived values
|
||||
let depositOutstanding = $derived(booking.deposit_required && !booking.deposit_paid);
|
||||
const depositOutstanding = $derived(booking.deposit_required && !booking.deposit_paid);
|
||||
|
||||
// Auto-select a sensible default payment type based on the booking's deposit
|
||||
// state. The backend will split the charge into deposit + non-deposit records
|
||||
// when appropriate, so this choice mainly controls the button label and amount.
|
||||
let defaultType = $derived(defaultPaymentType ?? (depositOutstanding ? 'deposit' : 'full'));
|
||||
const defaultType = $derived(defaultPaymentType ?? (depositOutstanding ? 'deposit' : 'full'));
|
||||
let paymentType = $state<'full' | 'partial' | 'deposit'>('full');
|
||||
$effect(() => {
|
||||
paymentType = defaultType as 'full' | 'partial' | 'deposit';
|
||||
@@ -230,35 +230,35 @@
|
||||
let lockInterval: ReturnType<typeof setInterval> | null = null;
|
||||
let countdownInterval: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
let servicesSubtotal = $derived(
|
||||
const servicesSubtotal = $derived(
|
||||
(booking.services ?? []).reduce((sum, s) => sum + (s.price || 0), 0)
|
||||
);
|
||||
let discountSum = $derived(
|
||||
const discountSum = $derived(
|
||||
(booking.discounts ?? []).reduce((sum, d) => sum + d.discount_amount, 0)
|
||||
);
|
||||
|
||||
let totalPaid = $derived(
|
||||
const totalPaid = $derived(
|
||||
booking.payments
|
||||
?.filter((p) => p.status === 'completed')
|
||||
.reduce((sum, p) => sum + p.amount, 0) || 0
|
||||
);
|
||||
|
||||
let amountRemaining = $derived(booking.total_amount - totalPaid);
|
||||
const amountRemaining = $derived(booking.total_amount - totalPaid);
|
||||
|
||||
let loyaltyEligible = $derived(
|
||||
const loyaltyEligible = $derived(
|
||||
stamps >= 10 &&
|
||||
!(booking.discounts ?? []).some((d) => d.discount_source === 'loyalty') &&
|
||||
booking.total_amount > 0 &&
|
||||
booking.amount_paid === 0
|
||||
);
|
||||
|
||||
let loyaltyDiscount = $derived(
|
||||
const loyaltyDiscount = $derived(
|
||||
useLoyalty ? Math.round(booking.total_amount * 100 * LOYALTY_DISCOUNT_RATE) : 0
|
||||
);
|
||||
|
||||
// Deposit policy warning text — dynamic based on booking state
|
||||
let expectedDepositPercent = $derived(booking.deposit_required ? 20 : 0);
|
||||
let depositPolicyWarning = $derived<string | null>(
|
||||
const expectedDepositPercent = $derived(booking.deposit_required ? 20 : 0);
|
||||
const depositPolicyWarning = $derived<string | null>(
|
||||
{
|
||||
get text(): string | null {
|
||||
if (!booking.deposit_required && totalPaid === 0 && booking.amount_due <= 0) return null;
|
||||
@@ -273,12 +273,12 @@
|
||||
}.text
|
||||
);
|
||||
|
||||
let isScenarioA = $derived(depositOutstanding);
|
||||
const isScenarioA = $derived(depositOutstanding);
|
||||
|
||||
let isScenarioB = $derived(!depositOutstanding && totalPaid < booking.total_amount);
|
||||
const isScenarioB = $derived(!depositOutstanding && totalPaid < booking.total_amount);
|
||||
|
||||
let partialAmountNum = $derived(partialAmount === '' ? 0 : parseFloat(partialAmount));
|
||||
let partialAmountValid = $derived(
|
||||
const partialAmountNum = $derived(partialAmount === '' ? 0 : parseFloat(partialAmount));
|
||||
const partialAmountValid = $derived(
|
||||
paymentType === 'partial' &&
|
||||
partialAmount !== '' &&
|
||||
!isNaN(partialAmountNum) &&
|
||||
@@ -287,7 +287,7 @@
|
||||
/^\d+(\.\d{0,2})?$/.test(partialAmount)
|
||||
);
|
||||
|
||||
let partialValidationError = $derived(
|
||||
const partialValidationError = $derived(
|
||||
paymentType === 'partial' && !partialAmountValid
|
||||
? partialAmount === ''
|
||||
? 'Enter an amount'
|
||||
@@ -301,14 +301,14 @@
|
||||
: null
|
||||
);
|
||||
|
||||
let payButtonDisabled = $derived(
|
||||
const payButtonDisabled = $derived(
|
||||
status === 'processing' ||
|
||||
!cardSelected ||
|
||||
(paymentType === 'partial' && !partialAmountValid) ||
|
||||
(booking.status === 'pending_release' && (lockTimer <= 0 || !lockAcquired))
|
||||
);
|
||||
|
||||
let payButtonError = $derived(
|
||||
const payButtonError = $derived(
|
||||
status === 'processing'
|
||||
? null
|
||||
: !cardSelected
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
openUserModal: (userId: string) => void;
|
||||
}
|
||||
|
||||
let { _openBookingModal, openEditBookingModal, openUserModal }: Props = $props();
|
||||
const { _openBookingModal, openEditBookingModal, openUserModal }: Props = $props();
|
||||
|
||||
type Booking = {
|
||||
id: string;
|
||||
@@ -90,7 +90,7 @@
|
||||
let weekSummary = $state<DailySummary | null>(null);
|
||||
|
||||
// VAT registration status from public business info (via shared store)
|
||||
let vatRegistered = $derived(getBusinessInfo()?.is_vat_registered ?? false);
|
||||
const vatRegistered = $derived(getBusinessInfo()?.is_vat_registered ?? false);
|
||||
|
||||
// Data diffing — only update UI when payload actually changes
|
||||
let prevDataJson = $state('');
|
||||
|
||||
@@ -87,17 +87,17 @@
|
||||
};
|
||||
|
||||
let pendingApprovals = $state<PendingApproval[]>([]);
|
||||
let visibleApprovals = $derived(pendingApprovals.slice(0, 3));
|
||||
const visibleApprovals = $derived(pendingApprovals.slice(0, 3));
|
||||
let loading = $state(true);
|
||||
let showApprovalModal = $state(false);
|
||||
let selectedBooking = $state<PendingBooking | null>(null);
|
||||
|
||||
let pendingEditRequests = $state<EditRequest[]>([]);
|
||||
let visibleEditRequests = $derived(pendingEditRequests.slice(0, 3));
|
||||
const visibleEditRequests = $derived(pendingEditRequests.slice(0, 3));
|
||||
let showEditRequestModal = $state(false);
|
||||
let selectedEditRequest = $state<EditRequest | null>(null);
|
||||
|
||||
let _hasItems = $derived(pendingApprovals.length > 0 || pendingEditRequests.length > 0);
|
||||
const _hasItems = $derived(pendingApprovals.length > 0 || pendingEditRequests.length > 0);
|
||||
$effect(() => {
|
||||
hasItems = _hasItems;
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
openUserModal: (userId: string) => void;
|
||||
}
|
||||
|
||||
let { openBookingModal, openUserModal }: Props = $props();
|
||||
const { openBookingModal, openUserModal }: Props = $props();
|
||||
|
||||
type TodayAppointment = {
|
||||
id: string;
|
||||
@@ -155,7 +155,7 @@
|
||||
return blocker.description?.startsWith('RESERVATION:') ?? false;
|
||||
}
|
||||
|
||||
let availableStartOptions = $derived.by(() => {
|
||||
const availableStartOptions = $derived.by(() => {
|
||||
const wh = workingHours;
|
||||
if (!wh || !wh.isOpen) return [];
|
||||
const startMin = timeToMinutes(wh.startTime);
|
||||
@@ -174,7 +174,7 @@
|
||||
return options;
|
||||
});
|
||||
|
||||
let availableEndOptions = $derived.by(() => {
|
||||
const availableEndOptions = $derived.by(() => {
|
||||
const wh = workingHours;
|
||||
if (!wh || !wh.isOpen) return [];
|
||||
const endMin = timeToMinutes(wh.endTime);
|
||||
@@ -197,7 +197,7 @@
|
||||
return options;
|
||||
});
|
||||
|
||||
let suggestedLunch = $derived.by(() => {
|
||||
const suggestedLunch = $derived.by(() => {
|
||||
if (!workingHours || !availableHours || !workingHours.isOpen) return null;
|
||||
const wh = workingHours;
|
||||
const ah = availableHours;
|
||||
@@ -284,7 +284,7 @@
|
||||
| LunchTimelineItem
|
||||
| ClosingTimeTimelineItem;
|
||||
|
||||
let timeline = $derived.by(() => {
|
||||
const timeline = $derived.by(() => {
|
||||
const items: TimelineItem[] = [];
|
||||
|
||||
for (const apt of appointments) {
|
||||
@@ -512,7 +512,7 @@
|
||||
return formatLocalDateTime(dt);
|
||||
}
|
||||
|
||||
let canCreate = $derived.by(() => !hasOverlap && !checkingOverlap);
|
||||
const canCreate = $derived.by(() => !hasOverlap && !checkingOverlap);
|
||||
|
||||
async function createBlocker() {
|
||||
if (!canCreate) return;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
maxChars?: number;
|
||||
threshold?: number; // when to start showing counter
|
||||
};
|
||||
let { text, maxChars = 1000000, threshold = 750000 }: Props = $props();
|
||||
const { text, maxChars = 1000000, threshold = 750000 }: Props = $props();
|
||||
|
||||
const graphemeCount = $derived.by(() => {
|
||||
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
jxl?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
const {
|
||||
urls,
|
||||
type = 'thumb',
|
||||
alt = '',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
let { status }: { status: string } = $props();
|
||||
const { status }: { status: string } = $props();
|
||||
|
||||
const variantMap: Record<string, { label: string; variantClass: string }> = {
|
||||
draft: { label: 'Draft', variantClass: 'bg-gray-100 text-gray-800' },
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import * as Button from '$lib/components/ui/button/index.js';
|
||||
|
||||
let { ...restProps }: Record<string, unknown> = $props();
|
||||
const { ...restProps }: Record<string, unknown> = $props();
|
||||
let ref = $state<HTMLElement | null>(null);
|
||||
</script>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useImageCropperCropper } from './image-cropper.svelte.js';
|
||||
import type { ImageCropperCropperProps } from './types.js';
|
||||
|
||||
let {
|
||||
const {
|
||||
cropShape = 'round',
|
||||
aspect = 1,
|
||||
showGrid = false,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { useImageCropperDialog } from './image-cropper.svelte.js';
|
||||
import type { ImageCropperDialogProps } from './types';
|
||||
|
||||
let { children, class: className, ...rest }: ImageCropperDialogProps = $props();
|
||||
const { children, class: className, ...rest }: ImageCropperDialogProps = $props();
|
||||
|
||||
const dialogState = useImageCropperDialog();
|
||||
</script>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import UploadIcon from '@lucide/svelte/icons/upload';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
let { child, class: className }: ImageCropperPreviewProps = $props();
|
||||
const { child, class: className }: ImageCropperPreviewProps = $props();
|
||||
|
||||
const previewState = useImageCropperPreview();
|
||||
</script>
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<script lang="ts" generics="T extends MapArcDatum = MapArcDatum">
|
||||
import { useMap } from '$lib/hooks/use-map.svelte.js';
|
||||
|
||||
let {
|
||||
const {
|
||||
data,
|
||||
id: propId,
|
||||
curvature = 0.2,
|
||||
@@ -80,7 +80,7 @@
|
||||
const ARC_HIT_MIN_WIDTH = 12;
|
||||
const ARC_HIT_PADDING = 6;
|
||||
|
||||
let autoId = $state(Math.random().toString(36).slice(2));
|
||||
const autoId = $state(Math.random().toString(36).slice(2));
|
||||
const id = $derived(propId ?? autoId);
|
||||
const sourceId = $derived(`arc-source-${id}`);
|
||||
const layerId = $derived(`arc-layer-${id}`);
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
onclusterclick?: (clusterId: number, coordinates: [number, number], pointCount: number) => void;
|
||||
}
|
||||
|
||||
let {
|
||||
const {
|
||||
data,
|
||||
clusterMaxZoom = 14,
|
||||
clusterRadius = 50,
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
onlocate?: (coords: { longitude: number; latitude: number }) => void;
|
||||
}
|
||||
|
||||
let {
|
||||
const {
|
||||
position = 'bottom-right',
|
||||
showZoom = true,
|
||||
showCompass = false,
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
rotationAlignment?: MarkerOptions['rotationAlignment'];
|
||||
}
|
||||
|
||||
let {
|
||||
const {
|
||||
longitude,
|
||||
latitude,
|
||||
children,
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
maxWidth?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
const {
|
||||
longitude,
|
||||
latitude,
|
||||
children,
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
interactive?: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
const {
|
||||
coordinates,
|
||||
color = '#4285F4',
|
||||
width = 3,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let { children, class: className }: Props = $props();
|
||||
const { children, class: className }: Props = $props();
|
||||
|
||||
const markerCtx = getContext<{
|
||||
getMarker: () => MapLibreGL.Marker | null;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
position?: 'top' | 'bottom';
|
||||
}
|
||||
|
||||
let { children, class: className, position = 'top' }: Props = $props();
|
||||
const { children, class: className, position = 'top' }: Props = $props();
|
||||
|
||||
const positionClasses = {
|
||||
top: 'bottom-full mb-1',
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
maxWidth?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
const {
|
||||
children,
|
||||
class: className,
|
||||
closeButton = false,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
anchor?: PopupOptions['anchor'];
|
||||
}
|
||||
|
||||
let { children, class: className, offset = 16, anchor }: Props = $props();
|
||||
const { children, class: className, offset = 16, anchor }: Props = $props();
|
||||
|
||||
const markerCtx = getContext<{
|
||||
getMarker: () => MapLibreGL.Marker | null;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
let {
|
||||
const {
|
||||
trigger
|
||||
}: {
|
||||
trigger?: Snippet;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { Toaster as Sonner, type ToasterProps as SonnerProps } from 'svelte-sonner';
|
||||
import { mode } from 'mode-watcher';
|
||||
|
||||
let { ...restProps }: SonnerProps = $props();
|
||||
const { ...restProps }: SonnerProps = $props();
|
||||
</script>
|
||||
|
||||
<Sonner
|
||||
|
||||
@@ -120,7 +120,7 @@ export function timeToMinutes(time: string): number {
|
||||
|
||||
export function calculatePreviousTime(time: string): string {
|
||||
const [hours, minutes] = time.split(':').map(Number);
|
||||
let totalMinutes = hours * 60 + minutes - 15;
|
||||
const totalMinutes = hours * 60 + minutes - 15;
|
||||
return `${String(Math.floor(totalMinutes / 60)).padStart(2, '0')}:${String(totalMinutes % 60).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ export function generateAvailableTimeSlots(
|
||||
});
|
||||
const [londonHours, londonMinutes] = londonTimeStr.split(':').map(Number);
|
||||
const currentMinutes = londonHours * 60 + londonMinutes;
|
||||
let minimumStart = Math.ceil((currentMinutes + 60) / 15) * 15;
|
||||
const minimumStart = Math.ceil((currentMinutes + 60) / 15) * 15;
|
||||
startTotalMinutes = Math.max(startTotalMinutes, minimumStart);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user