diff --git a/src/api/types.ts b/src/api/types.ts
index b8ba013..6d9755d 100644
--- a/src/api/types.ts
+++ b/src/api/types.ts
@@ -24,6 +24,7 @@ export type Business = {
isActive: boolean;
serviceClassesEnabled?: boolean;
concurrentCapacity?: number;
+ slotStepMinutes?: number;
categoryId: string;
ownerId?: string | null;
category?: BusinessCategory;
@@ -67,6 +68,7 @@ export type UpdateBusinessPayload = {
longitude?: number | null;
serviceClassesEnabled?: boolean;
concurrentCapacity?: number;
+ slotStepMinutes?: number;
};
export type BusinessServiceClass = {
diff --git a/src/components/brand/AppLogo.vue b/src/components/brand/AppLogo.vue
index 4f41b8d..e577274 100644
--- a/src/components/brand/AppLogo.vue
+++ b/src/components/brand/AppLogo.vue
@@ -7,12 +7,12 @@
/>
diff --git a/src/components/owner/ServiceClassManager.vue b/src/components/owner/ServiceClassManager.vue
index e7ac37e..6f509f1 100644
--- a/src/components/owner/ServiceClassManager.vue
+++ b/src/components/owner/ServiceClassManager.vue
@@ -1,106 +1,122 @@
-
-
-
-
کلاسبندی خدمات
-
- اختیاری — برای مثال کارواش میتواند بر اساس اندازه خودرو قیمتگذاری کند.
+
+
+
-
- در حالت غیرفعال، هر خدمت یک قیمت ثابت دارد. با فعالکردن، میتوانید کلاسها را تعریف کنید.
-
+
+
+ اختیاری — برای مثال کارواش میتواند بر اساس اندازه خودرو قیمتگذاری کند.
+
-
-
- {{ error }}
-
+
+ در حالت غیرفعال، هر خدمت یک قیمت ثابت دارد. با فعالکردن، میتوانید کلاسها را تعریف کنید.
+
- در حال بارگذاری…
+
+
+ {{ error }}
+
-
- -
-
-
+
در حال بارگذاری…
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
{{ item.title }}
+
{{ item.description }}
+
+ غیرفعال
+
+
+
+
+
+
+
+
هنوز کلاسی تعریف نشده است.
+
+
-
-
-
{{ item.title }}
-
{{ item.description }}
-
- غیرفعال
-
-
-
-
-
-
-
- هنوز کلاسی تعریف نشده است.
-
-
-
+ افزودن کلاس
+
+
+
diff --git a/src/components/ui/AppInput.vue b/src/components/ui/AppInput.vue
index 809bc24..45a32d5 100644
--- a/src/components/ui/AppInput.vue
+++ b/src/components/ui/AppInput.vue
@@ -3,22 +3,40 @@
-
+
diff --git a/src/layouts/WebAppLayout.vue b/src/layouts/WebAppLayout.vue
index 8466de1..f9dda70 100644
--- a/src/layouts/WebAppLayout.vue
+++ b/src/layouts/WebAppLayout.vue
@@ -4,7 +4,7 @@
class="sticky top-0 z-10 flex items-center justify-between border-b border-border bg-surface/95 px-4 py-3 backdrop-blur"
>
-
+
{{ auth.user.mobile }}
diff --git a/src/types/icons.ts b/src/types/icons.ts
index b61a361..0ec0356 100644
--- a/src/types/icons.ts
+++ b/src/types/icons.ts
@@ -18,7 +18,12 @@ export type AppIconName =
| 'search'
| 'chevron-down'
| 'chevron-up'
- | 'clock';
+ | 'clock'
+ | 'pencil'
+ | 'trash'
+ | 'user-cog'
+ | 'eye'
+ | 'eye-off';
export type NavItem = {
to: string;
diff --git a/src/views/app/AppOwnerView.vue b/src/views/app/AppOwnerView.vue
index cd5f6dc..03345a6 100644
--- a/src/views/app/AppOwnerView.vue
+++ b/src/views/app/AppOwnerView.vue
@@ -28,16 +28,23 @@
+
+
+
+
+
+
+ حداکثر تعداد مشتری که همزمان میتوانید پذیرش کنید. مثلاً ۳ یعنی هر ساعت تا ۳ رزرو همپوشان.
+
+
+
+
+ فاصله بین ساعتهای قابل رزرو. مثلاً ۳۰ یعنی ۱۰:۰۰، ۱۰:۳۰، ۱۱:۰۰ و …
+
+
+
+ ذخیره تنظیمات نوبتدهی
+
+
+
+
-
- خدمات و قیمتها
+
+
+
+
خدماتی که اینجا ثبت میکنید در صفحه کسبوکار برای مشتریان و مقایسه نمایش داده میشود.
@@ -116,14 +188,20 @@
{{ svc.serviceTemplate.description }}
-
+
@@ -212,14 +290,6 @@
)
}}
-
@@ -359,6 +429,7 @@
ارسال برای تأیید
+
@@ -391,6 +462,8 @@ import { ApiError } from '@/api/client';
import BusinessAvailabilityEditor from '@/components/owner/BusinessAvailabilityEditor.vue';
import ServiceClassManager from '@/components/owner/ServiceClassManager.vue';
import AppButton from '@/components/ui/AppButton.vue';
+import AppIcon from '@/components/ui/AppIcon.vue';
+import AppIconButton from '@/components/ui/AppIconButton.vue';
import AppInput from '@/components/ui/AppInput.vue';
import PriceInput from '@/components/ui/PriceInput.vue';
import WebAppLayout from '@/layouts/WebAppLayout.vue';
@@ -413,6 +486,7 @@ const templates = ref>>([]);
const loading = ref(true);
const loadingServices = ref(false);
const saving = ref(false);
+const savingBooking = ref(false);
const savingService = ref(false);
const addingService = ref(false);
const addingCustom = ref(false);
@@ -420,15 +494,22 @@ const serviceRequests = ref([]);
const error = ref('');
const message = ref('');
const editingId = ref(null);
+const servicesExpanded = ref(false);
const form = ref({
title: '',
description: '',
contactPhone: '',
address: '',
- concurrentCapacity: '1',
});
+const bookingForm = ref({
+ concurrentCapacity: '1',
+ slotStepMinutes: '30',
+});
+
+const bookingExpanded = ref(false);
+
const serviceForm = ref({
serviceTemplateId: '',
price: '',
@@ -460,6 +541,19 @@ const hasServiceClasses = computed(
() => business.value?.serviceClassesEnabled === true && activeClasses.value.length > 0,
);
+const servicesSummary = computed(() => {
+ if (loadingServices.value) return 'در حال بارگذاری…';
+ const count = services.value.length;
+ if (count === 0) return 'هنوز خدمتی ثبت نشده';
+ const activeCount = services.value.filter((s) => s.isActive).length;
+ return `${count} خدمت · ${activeCount} فعال`;
+});
+
+const bookingSummary = computed(
+ () =>
+ `${bookingForm.value.concurrentCapacity} همزمان · گام ${bookingForm.value.slotStepMinutes} دقیقه`,
+);
+
function emptyClassPriceForm() {
const result: Record = {};
for (const cls of activeClasses.value) {
@@ -550,7 +644,10 @@ onMounted(async () => {
description: business.value.description ?? '',
contactPhone: business.value.contactPhone ?? '',
address: business.value.address ?? '',
+ };
+ bookingForm.value = {
concurrentCapacity: String(business.value.concurrentCapacity ?? 1),
+ slotStepMinutes: String(business.value.slotStepMinutes ?? 30),
};
await loadServicesAndTemplates();
}
@@ -563,11 +660,6 @@ onMounted(async () => {
async function onSave() {
if (!business.value) return;
- const concurrentCapacity = Number.parseInt(form.value.concurrentCapacity, 10);
- if (!Number.isFinite(concurrentCapacity) || concurrentCapacity < 1 || concurrentCapacity > 20) {
- error.value = 'پذیرش همزمان باید عددی بین ۱ تا ۲۰ باشد.';
- return;
- }
saving.value = true;
error.value = '';
message.value = '';
@@ -577,7 +669,6 @@ async function onSave() {
description: form.value.description.trim() || null,
contactPhone: form.value.contactPhone.trim() || null,
address: form.value.address.trim() || null,
- concurrentCapacity,
});
message.value = 'اطلاعات ذخیره شد.';
} catch (e) {
@@ -587,6 +678,34 @@ async function onSave() {
}
}
+async function onSaveBookingSettings() {
+ if (!business.value) return;
+ const concurrentCapacity = Number.parseInt(bookingForm.value.concurrentCapacity, 10);
+ if (!Number.isFinite(concurrentCapacity) || concurrentCapacity < 1 || concurrentCapacity > 20) {
+ error.value = 'پذیرش همزمان باید عددی بین ۱ تا ۲۰ باشد.';
+ return;
+ }
+ const slotStepMinutes = Number.parseInt(bookingForm.value.slotStepMinutes, 10);
+ if (!Number.isFinite(slotStepMinutes) || slotStepMinutes < 5 || slotStepMinutes > 120) {
+ error.value = 'گام نوبتدهی باید عددی بین ۵ تا ۱۲۰ دقیقه باشد.';
+ return;
+ }
+ savingBooking.value = true;
+ error.value = '';
+ message.value = '';
+ try {
+ business.value = await updateBusiness(business.value.id, {
+ concurrentCapacity,
+ slotStepMinutes,
+ });
+ message.value = 'تنظیمات نوبتدهی ذخیره شد.';
+ } catch (e) {
+ error.value = e instanceof ApiError ? e.message : 'خطا در ذخیره';
+ } finally {
+ savingBooking.value = false;
+ }
+}
+
async function onAddService() {
if (!business.value || !serviceForm.value.serviceTemplateId) return;