awaiting_payment set

This commit is contained in:
2026-06-29 17:12:25 +03:30
parent 54e2d31a51
commit 11894cea4e
3 changed files with 86 additions and 11 deletions
@@ -5,8 +5,11 @@
<h2 class="mb-3 text-sm font-bold text-text sm:mb-4 sm:text-base">خلاصه سفارش</h2> <h2 class="mb-3 text-sm font-bold text-text sm:mb-4 sm:text-base">خلاصه سفارش</h2>
<div v-if="items.length" class="space-y-3 sm:space-y-4"> <div v-if="items.length" class="space-y-3 sm:space-y-4">
<p v-if="classTitle" class="text-xs font-medium text-brand-600"> <p
کلاس {{ classTitle }} v-if="classTitle"
class="rounded-lg bg-brand-50 px-3 py-2 text-xs font-medium text-brand-700 dark:bg-brand-500/10 dark:text-brand-500"
>
کلاس خدمات: {{ classTitle }}
</p> </p>
<div <div
+56 -5
View File
@@ -22,12 +22,43 @@
<template v-else> <template v-else>
<BookingStepper :current="currentStep" /> <BookingStepper :current="currentStep" />
<div
v-if="showSelectionRecap"
class="rounded-xl border border-brand-500/25 bg-brand-50 px-4 py-3 text-sm dark:bg-brand-500/10"
>
<p class="mb-2 text-xs font-semibold text-brand-700 dark:text-brand-500">انتخاب‌های شما</p>
<dl class="space-y-1.5">
<div v-if="selectedClassTitle" class="flex justify-between gap-3">
<dt class="text-muted">کلاس خدمات</dt>
<dd class="font-medium text-text">{{ selectedClassTitle }}</dd>
</div>
<div v-if="selectedServices.length" class="flex justify-between gap-3">
<dt class="shrink-0 text-muted">خدمات</dt>
<dd class="max-w-[65%] text-left font-medium text-text">{{ selectedServiceTitle }}</dd>
</div>
<div v-if="scheduledAt && currentStep >= 3" class="flex justify-between gap-3">
<dt class="text-muted">زمان</dt>
<dd class="font-medium text-text">{{ scheduledLabel }}</dd>
</div>
<div
v-if="selectedServices.length"
class="flex justify-between gap-3 border-t border-brand-500/20 pt-1.5"
>
<dt class="text-muted">مبلغ</dt>
<dd class="font-semibold text-brand-600">
{{ formatPriceToman(selectedPrice) }} تومان
</dd>
</div>
</dl>
</div>
<p class="text-center text-sm font-semibold text-brand-600 lg:hidden"> <p class="text-center text-sm font-semibold text-brand-600 lg:hidden">
{{ currentStepLabel }} {{ currentStepLabel }}
</p> </p>
<BookingServiceClassStep <BookingServiceClassStep
v-if="currentStep === 1" v-if="currentStep === 1 && classStepRequired"
v-model="selectedClassId" v-model="selectedClassId"
:classes="serviceClasses" :classes="serviceClasses"
:loading="loadingClasses" :loading="loadingClasses"
@@ -134,7 +165,10 @@
> >
<h3 class="mb-4 font-semibold text-text">بررسی نهایی</h3> <h3 class="mb-4 font-semibold text-text">بررسی نهایی</h3>
<dl class="space-y-3 text-sm"> <dl class="space-y-3 text-sm">
<div v-if="selectedClassTitle" class="flex justify-between gap-4"> <div
v-if="classesEnabled && selectedClassTitle"
class="flex justify-between gap-4"
>
<dt class="text-muted">کلاس</dt> <dt class="text-muted">کلاس</dt>
<dd class="font-medium text-text">{{ selectedClassTitle }}</dd> <dd class="font-medium text-text">{{ selectedClassTitle }}</dd>
</div> </div>
@@ -169,7 +203,7 @@
class="hidden flex-wrap items-center justify-between gap-3 lg:flex" class="hidden flex-wrap items-center justify-between gap-3 lg:flex"
> >
<AppButton <AppButton
v-if="currentStep > 1" v-if="currentStep > firstBookingStep"
variant="ghost" variant="ghost"
class="!w-auto" class="!w-auto"
@click="goBack" @click="goBack"
@@ -193,7 +227,7 @@
> >
<div class="mx-auto flex w-full max-w-app gap-2 px-4 py-3"> <div class="mx-auto flex w-full max-w-app gap-2 px-4 py-3">
<AppButton <AppButton
v-if="currentStep > 1" v-if="currentStep > firstBookingStep"
variant="outline" variant="outline"
class="!w-auto shrink-0 px-4" class="!w-auto shrink-0 px-4"
@click="goBack" @click="goBack"
@@ -303,6 +337,20 @@ const classStepRequired = computed(
() => Boolean(props.business.serviceClassesEnabled) && serviceClasses.value.length > 0, () => Boolean(props.business.serviceClassesEnabled) && serviceClasses.value.length > 0,
); );
const firstBookingStep = computed(() => {
if (!classStepRequired.value) return 2;
if (serviceClasses.value.length <= 1) return 2;
return 1;
});
const showSelectionRecap = computed(
() =>
currentStep.value >= 2 &&
(Boolean(selectedClassTitle.value) ||
selectedServices.value.length > 0 ||
Boolean(scheduledAt.value)),
);
const classesEnabled = computed(() => Boolean(props.business.serviceClassesEnabled)); const classesEnabled = computed(() => Boolean(props.business.serviceClassesEnabled));
const perClass = computed( const perClass = computed(
@@ -503,6 +551,9 @@ async function loadClasses() {
if (serviceClasses.value.length === 1) { if (serviceClasses.value.length === 1) {
selectedClassId.value = serviceClasses.value[0].id; selectedClassId.value = serviceClasses.value[0].id;
} }
if (currentStep.value < firstBookingStep.value) {
currentStep.value = firstBookingStep.value;
}
} catch (e) { } catch (e) {
classesError.value = classesError.value =
e instanceof ApiError ? e.message : 'بارگذاری کلاس‌های خدمات ناموفق بود'; e instanceof ApiError ? e.message : 'بارگذاری کلاس‌های خدمات ناموفق بود';
@@ -542,7 +593,7 @@ function applyEarliestSlot() {
} }
function goBack() { function goBack() {
if (currentStep.value > 1) { if (currentStep.value > firstBookingStep.value) {
currentStep.value -= 1; currentStep.value -= 1;
bookingError.value = ''; bookingError.value = '';
} }
+25 -4
View File
@@ -90,7 +90,7 @@
type="button" type="button"
class="flex w-full items-center justify-between gap-3 rounded-2xl border border-border bg-surface-2 px-4 py-3 text-start transition hover:border-brand-500/30" class="flex w-full items-center justify-between gap-3 rounded-2xl border border-border bg-surface-2 px-4 py-3 text-start transition hover:border-brand-500/30"
:aria-expanded="bookingExpanded" :aria-expanded="bookingExpanded"
@click="bookingExpanded = !bookingExpanded" @click="bookingExpanded = !bookingExpanded; bookingMessage = ''; bookingError = ''"
> >
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@@ -108,6 +108,19 @@
/> />
</button> </button>
<p
v-if="bookingMessage"
class="mt-3 rounded-xl border border-brand-500/40 bg-brand-500/10 px-4 py-3 text-sm text-brand-700 dark:text-brand-500"
>
{{ bookingMessage }}
</p>
<p
v-if="bookingError"
class="mt-3 rounded-xl border border-red-500/30 bg-red-500/10 px-4 py-3 text-sm text-red-600"
>
{{ bookingError }}
</p>
<div v-show="bookingExpanded" class="mt-3 space-y-4"> <div v-show="bookingExpanded" class="mt-3 space-y-4">
<AppInput <AppInput
id="ownerConcurrentCapacity" id="ownerConcurrentCapacity"
@@ -533,6 +546,8 @@ const bookingForm = ref({
}); });
const bookingExpanded = ref(false); const bookingExpanded = ref(false);
const bookingMessage = ref('');
const bookingError = ref('');
const serviceForm = ref({ const serviceForm = ref({
serviceTemplateId: '', serviceTemplateId: '',
@@ -778,15 +793,19 @@ async function onSaveBookingSettings() {
if (!business.value) return; if (!business.value) return;
const concurrentCapacity = Number.parseInt(bookingForm.value.concurrentCapacity, 10); const concurrentCapacity = Number.parseInt(bookingForm.value.concurrentCapacity, 10);
if (!Number.isFinite(concurrentCapacity) || concurrentCapacity < 1 || concurrentCapacity > 20) { if (!Number.isFinite(concurrentCapacity) || concurrentCapacity < 1 || concurrentCapacity > 20) {
error.value = 'پذیرش همزمان باید عددی بین ۱ تا ۲۰ باشد.'; bookingError.value = 'پذیرش همزمان باید عددی بین ۱ تا ۲۰ باشد.';
bookingMessage.value = '';
return; return;
} }
const slotStepMinutes = Number.parseInt(bookingForm.value.slotStepMinutes, 10); const slotStepMinutes = Number.parseInt(bookingForm.value.slotStepMinutes, 10);
if (!Number.isFinite(slotStepMinutes) || slotStepMinutes < 5 || slotStepMinutes > 120) { if (!Number.isFinite(slotStepMinutes) || slotStepMinutes < 5 || slotStepMinutes > 120) {
error.value = 'گام نوبت‌دهی باید عددی بین ۵ تا ۱۲۰ دقیقه باشد.'; bookingError.value = 'گام نوبت‌دهی باید عددی بین ۵ تا ۱۲۰ دقیقه باشد.';
bookingMessage.value = '';
return; return;
} }
savingBooking.value = true; savingBooking.value = true;
bookingError.value = '';
bookingMessage.value = '';
error.value = ''; error.value = '';
message.value = ''; message.value = '';
try { try {
@@ -796,9 +815,11 @@ async function onSaveBookingSettings() {
autoApproveBookings: bookingForm.value.autoApproveBookings, autoApproveBookings: bookingForm.value.autoApproveBookings,
}); });
tenant.syncSingleBusiness(business.value); tenant.syncSingleBusiness(business.value);
bookingMessage.value = 'تنظیمات نوبت‌دهی ذخیره شد.';
bookingExpanded.value = false;
message.value = 'تنظیمات نوبت‌دهی ذخیره شد.'; message.value = 'تنظیمات نوبت‌دهی ذخیره شد.';
} catch (e) { } catch (e) {
error.value = e instanceof ApiError ? e.message : 'خطا در ذخیره'; bookingError.value = e instanceof ApiError ? e.message : 'خطا در ذخیره';
} finally { } finally {
savingBooking.value = false; savingBooking.value = false;
} }