Files
webMontime/src/views/app/AppBookingsView.vue
T

186 lines
5.6 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<WebAppLayout>
<h1 class="mb-1 text-xl font-bold text-text">
{{ auth.isBusinessOwner ? 'رزروهای کسب‌وکار' : 'رزروهای من' }}
</h1>
<p class="mb-4 text-sm text-muted">
{{
auth.isBusinessOwner
? 'درخواست‌های مشتریان برای خدمات شما'
: 'وضعیت رزروهای ثبت‌شده'
}}
</p>
<p
v-if="error"
class="mb-4 rounded-xl border border-red-500/30 bg-red-500/10 px-4 py-3 text-sm text-red-600 dark:text-red-400"
>
{{ error }}
</p>
<div v-if="loading" class="py-8 text-center text-sm text-muted">در حال بارگذاری…</div>
<div
v-else-if="!bookings.length"
class="rounded-2xl border border-dashed border-border bg-surface-2 p-6 text-center text-sm text-muted"
>
{{
auth.isBusinessOwner
? 'هنوز رزروی برای کسب‌وکار شما ثبت نشده است.'
: 'هنوز رزروی ندارید. از صفحه کسب‌وکارها یک خدمت را رزرو کنید.'
}}
<RouterLink
v-if="auth.isCustomer"
to="/app"
class="mt-3 inline-block text-brand-600 dark:text-brand-500"
>
مشاهده کسب‌وکارها
</RouterLink>
</div>
<ul v-else class="space-y-3">
<li
v-for="b in bookings"
:key="b.id"
class="rounded-2xl border border-border bg-surface-2 p-4"
>
<div class="flex items-start justify-between gap-2">
<div class="min-w-0 flex-1">
<p class="font-semibold text-text">
{{ b.business?.title ?? 'کسب‌وکار' }}
</p>
<p class="mt-0.5 text-sm text-muted">
{{ serviceTitle(b) }}
</p>
<p class="mt-1 text-xs text-muted">
{{ formatDate(b.scheduledAt) }}
</p>
<p
v-if="auth.isBusinessOwner && b.customer?.mobile"
class="mt-1 text-xs text-muted"
dir="ltr"
>
{{ b.customer.fullName ? `${b.customer.fullName} · ` : '' }}{{ b.customer.mobile }}
</p>
<p v-if="b.note" class="mt-2 text-xs text-muted">{{ b.note }}</p>
<span
class="mt-2 inline-block rounded-lg px-2 py-0.5 text-xs"
:class="statusClass(b.status)"
>
{{ bookingStatusLabel(b.status) }}
</span>
</div>
</div>
<div
v-if="canActOn(b)"
class="mt-3 flex flex-wrap gap-2 border-t border-border pt-3"
>
<AppButton
v-if="auth.isBusinessOwner && b.status === 'pending'"
class="!w-auto flex-1 px-3"
:loading="actingId === b.id"
@click="onConfirm(b)"
>
تأیید
</AppButton>
<AppButton
v-if="canCancel(b)"
variant="danger"
class="!w-auto flex-1 px-3"
:loading="actingId === b.id"
@click="onCancel(b)"
>
لغو
</AppButton>
</div>
</li>
</ul>
</WebAppLayout>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { cancelBooking, confirmBooking, fetchBookings } from '@/api/bookings';
import { ApiError } from '@/api/client';
import type { Booking, BookingStatus } from '@/api/types';
import AppButton from '@/components/ui/AppButton.vue';
import WebAppLayout from '@/layouts/WebAppLayout.vue';
import { useAuthStore } from '@/stores/auth';
import { bookingStatusLabel, formatDate } from '@/utils/format';
const auth = useAuthStore();
const bookings = ref<Booking[]>([]);
const loading = ref(true);
const error = ref('');
const actingId = ref<string | null>(null);
function serviceTitle(b: Booking): string {
return b.businessService?.serviceTemplate?.title ?? 'خدمت';
}
function statusClass(status: BookingStatus): string {
if (status === 'confirmed' || status === 'completed') {
return 'bg-emerald-500/15 text-emerald-700 dark:text-emerald-400';
}
if (status === 'pending') {
return 'bg-amber-500/15 text-amber-700 dark:text-amber-400';
}
if (status === 'cancelled') {
return 'bg-red-500/15 text-red-600 dark:text-red-400';
}
return 'bg-surface-3 text-muted';
}
function canCancel(b: Booking): boolean {
return b.status === 'pending' || b.status === 'confirmed';
}
function canActOn(b: Booking): boolean {
if (b.status === 'cancelled' || b.status === 'completed') return false;
if (auth.isBusinessOwner && b.status === 'pending') return true;
return canCancel(b);
}
async function load() {
loading.value = true;
error.value = '';
try {
bookings.value = await fetchBookings();
} catch (e) {
error.value = e instanceof ApiError ? e.message : 'بارگذاری رزروها ناموفق بود.';
} finally {
loading.value = false;
}
}
async function onConfirm(b: Booking) {
actingId.value = b.id;
error.value = '';
try {
await confirmBooking(b.id);
await load();
} catch (e) {
error.value = e instanceof ApiError ? e.message : 'تأیید رزرو ناموفق بود.';
} finally {
actingId.value = null;
}
}
async function onCancel(b: Booking) {
if (!confirm('این رزرو لغو شود؟')) return;
actingId.value = b.id;
error.value = '';
try {
await cancelBooking(b.id);
await load();
} catch (e) {
error.value = e instanceof ApiError ? e.message : 'لغو رزرو ناموفق بود.';
} finally {
actingId.value = null;
}
}
onMounted(load);
</script>