From 3c2db0944707c5e14beb252f80361f24646ffa09 Mon Sep 17 00:00:00 2001 From: Mahdi Date: Sat, 13 Jun 2026 13:43:40 +0330 Subject: [PATCH] add cancellationReason --- src/api/bookings.ts | 10 +- src/api/types.ts | 1 + src/components/booking/BookingItem.vue | 12 + .../business/BusinessImageGallery.vue | 220 +++++++++++++++--- src/components/home/HomeHeroSection.vue | 42 +--- .../landing/MobileBusinessLanding.vue | 30 +-- .../owner/BusinessAvailabilityEditor.vue | 2 +- src/components/ui/AppIcon.vue | 4 + src/styles/main.css | 24 ++ src/types/icons.ts | 2 + src/utils/booking-groups.ts | 22 ++ src/views/app/AppBookingsView.vue | 180 +++++++++++++- 12 files changed, 447 insertions(+), 102 deletions(-) diff --git a/src/api/bookings.ts b/src/api/bookings.ts index b125239..4ccc192 100644 --- a/src/api/bookings.ts +++ b/src/api/bookings.ts @@ -46,8 +46,14 @@ export function createBookingBatch(payload: CreateBookingBatchPayload) { }); } -export function cancelBooking(id: string) { - return api(`/bookings/${id}/cancel`, { method: 'POST' }); +export function cancelBooking( + id: string, + payload?: { cancellationReason?: string }, +) { + return api(`/bookings/${id}/cancel`, { + method: 'POST', + body: JSON.stringify(payload ?? {}), + }); } export function confirmBooking(id: string) { diff --git a/src/api/types.ts b/src/api/types.ts index e3852f6..683b590 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -97,6 +97,7 @@ export type Booking = { scheduledAt: string; status: BookingStatus; note?: string | null; + cancellationReason?: string | null; business?: Business; businessService?: BusinessService; serviceClass?: BusinessServiceClass; diff --git a/src/components/booking/BookingItem.vue b/src/components/booking/BookingItem.vue index cf6e4fb..066bfa5 100644 --- a/src/components/booking/BookingItem.vue +++ b/src/components/booking/BookingItem.vue @@ -55,6 +55,13 @@

{{ primary.note }}

+

+ علت لغو: + {{ cancellationReason }} +

{ return bookingStatusLabel(groupStatus.value); }); +const cancellationReason = computed(() => { + const booking = props.bookings.find((item) => item.cancellationReason?.trim()); + return booking?.cancellationReason?.trim() ?? null; +}); + function statusClassFor(status: BookingStatus | 'mixed') { if (status === 'confirmed' || status === 'completed') { return 'bg-emerald-500/15 text-emerald-700 dark:text-emerald-400'; diff --git a/src/components/business/BusinessImageGallery.vue b/src/components/business/BusinessImageGallery.vue index 7461afe..534cf7b 100644 --- a/src/components/business/BusinessImageGallery.vue +++ b/src/components/business/BusinessImageGallery.vue @@ -1,44 +1,94 @@ @@ -49,37 +99,131 @@ import AppIcon from '@/components/ui/AppIcon.vue'; import { businessGalleryUrls } from '@/utils/media'; import type { BusinessImage } from '@/api/types'; -const props = defineProps<{ - images?: BusinessImage[] | null; - title?: string; -}>(); +const SWIPE_THRESHOLD_PX = 48; +const WHEEL_COOLDOWN_MS = 380; + +const props = withDefaults( + defineProps<{ + images?: BusinessImage[] | null; + title?: string; + variant?: 'default' | 'card' | 'hero'; + frameExtraClass?: string; + }>(), + { variant: 'default', title: '', frameExtraClass: '' }, +); -const trackRef = ref(null); const activeIndex = ref(0); +const isDragging = ref(false); +const touchStartX = ref(0); +const touchDeltaX = ref(0); +const wheelLocked = ref(false); const images = computed(() => businessGalleryUrls(props.images)); -function updateActiveIndex() { - const track = trackRef.value; - if (!track || !images.value.length) { - return; +const frameClass = computed(() => { + if (props.variant === 'card') { + return 'h-52 bg-gradient-to-br from-brand-50 via-slate-100 to-brand-100 dark:from-brand-500/10 dark:via-surface-3 dark:to-surface-2'; } - - const width = track.clientWidth; - if (!width) { - return; + if (props.variant === 'hero') { + return 'h-[320px] bg-slate-200 lg:h-[390px]'; } + return 'aspect-[4/3]'; +}); +const imageClass = computed(() => { + if (props.variant === 'card') { + return 'h-52'; + } + if (props.variant === 'hero') { + return 'h-[320px] lg:h-[390px]'; + } + return 'aspect-[4/3]'; +}); + +const trackStyle = computed(() => { + const base = `translateX(calc(-${activeIndex.value * 100}% + ${isDragging.value ? touchDeltaX.value : 0}px))`; + return { transform: base }; +}); + +function goTo(index: number) { activeIndex.value = Math.min( images.value.length - 1, - Math.max(0, Math.round(track.scrollLeft / width)), + Math.max(0, index), ); } +function goPrev() { + goTo(activeIndex.value - 1); +} + +function goNext() { + goTo(activeIndex.value + 1); +} + +function onWheel(event: WheelEvent) { + if (images.value.length <= 1) return; + + const delta = + Math.abs(event.deltaX) > Math.abs(event.deltaY) + ? event.deltaX + : event.deltaY; + + if (Math.abs(delta) < 10) return; + + event.preventDefault(); + + if (wheelLocked.value) return; + wheelLocked.value = true; + + if (delta > 0) { + goNext(); + } else { + goPrev(); + } + + window.setTimeout(() => { + wheelLocked.value = false; + }, WHEEL_COOLDOWN_MS); +} + +function onTouchStart(event: TouchEvent) { + if (images.value.length <= 1) return; + touchStartX.value = event.touches[0]?.clientX ?? 0; + touchDeltaX.value = 0; + isDragging.value = true; +} + +function onTouchMove(event: TouchEvent) { + if (!isDragging.value || images.value.length <= 1) return; + + const currentX = event.touches[0]?.clientX ?? touchStartX.value; + let delta = currentX - touchStartX.value; + + if (activeIndex.value === 0 && delta > 0) { + delta *= 0.35; + } else if (activeIndex.value === images.value.length - 1 && delta < 0) { + delta *= 0.35; + } + + touchDeltaX.value = delta; +} + +function onTouchEnd() { + if (!isDragging.value) return; + + if (touchDeltaX.value <= -SWIPE_THRESHOLD_PX) { + goNext(); + } else if (touchDeltaX.value >= SWIPE_THRESHOLD_PX) { + goPrev(); + } + + isDragging.value = false; + touchDeltaX.value = 0; +} + watch(images, () => { activeIndex.value = 0; - if (trackRef.value) { - trackRef.value.scrollLeft = 0; - } + isDragging.value = false; + touchDeltaX.value = 0; }); diff --git a/src/components/home/HomeHeroSection.vue b/src/components/home/HomeHeroSection.vue index 8095cf2..0f36615 100644 --- a/src/components/home/HomeHeroSection.vue +++ b/src/components/home/HomeHeroSection.vue @@ -70,35 +70,25 @@
-
-
- -
- -
-
+
+
diff --git a/src/components/landing/MobileBusinessLanding.vue b/src/components/landing/MobileBusinessLanding.vue index 550b92a..6c78197 100644 --- a/src/components/landing/MobileBusinessLanding.vue +++ b/src/components/landing/MobileBusinessLanding.vue @@ -17,19 +17,11 @@
- -
- -
@@ -119,14 +111,14 @@