Add time rezerv
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<section class="space-y-4 border-t border-border pt-6">
|
||||
<div>
|
||||
<h2 class="text-base font-semibold text-text">ساعات پذیرش آنلاین</h2>
|
||||
<p class="mt-1 text-xs text-muted">
|
||||
مشتری فقط در بازههای زیر میتواند رزرو کند. برای هر روز میتوانید چند بازه تعریف کنید.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p v-if="error" class="text-sm text-red-600">{{ error }}</p>
|
||||
<p v-if="message" class="text-sm text-brand-700 dark:text-brand-500">{{ message }}</p>
|
||||
|
||||
<div v-if="loading" class="text-sm text-muted">در حال بارگذاری…</div>
|
||||
|
||||
<div v-else class="space-y-3">
|
||||
<div
|
||||
v-for="day in weekDays"
|
||||
:key="day.dayOfWeek"
|
||||
class="rounded-xl border border-border bg-surface-2 p-3"
|
||||
>
|
||||
<p class="mb-2 text-sm font-medium text-text">{{ day.label }}</p>
|
||||
|
||||
<ul v-if="slotsByDay[day.dayOfWeek]?.length" class="mb-2 space-y-1">
|
||||
<li
|
||||
v-for="(slot, idx) in slotsByDay[day.dayOfWeek]"
|
||||
:key="idx"
|
||||
class="flex items-center justify-between rounded-lg bg-surface-3 px-2 py-1.5 text-sm"
|
||||
>
|
||||
<span dir="ltr">{{ slot.startTime }} – {{ slot.endTime }}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="text-xs text-red-600"
|
||||
@click="removeSlot(day.dayOfWeek, idx)"
|
||||
>
|
||||
حذف
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<p v-else class="mb-2 text-xs text-muted">بازهای ثبت نشده</p>
|
||||
|
||||
<div class="flex flex-wrap items-end gap-2">
|
||||
<div class="space-y-1">
|
||||
<span class="text-xs text-muted">از</span>
|
||||
<input
|
||||
v-model="draft[day.dayOfWeek].start"
|
||||
type="time"
|
||||
class="rounded-lg border border-border bg-surface px-2 py-1.5 text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<span class="text-xs text-muted">تا</span>
|
||||
<input
|
||||
v-model="draft[day.dayOfWeek].end"
|
||||
type="time"
|
||||
class="rounded-lg border border-border bg-surface px-2 py-1.5 text-sm"
|
||||
/>
|
||||
</div>
|
||||
<AppButton variant="ghost" class="!w-auto px-3 text-xs" @click="addSlot(day.dayOfWeek)">
|
||||
افزودن بازه
|
||||
</AppButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AppButton :loading="saving" @click="onSave">ذخیره ساعات پذیرش</AppButton>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import {
|
||||
fetchBusinessAvailability,
|
||||
setBusinessAvailability,
|
||||
type AvailabilitySlot,
|
||||
} from '@/api/availability';
|
||||
import { ApiError } from '@/api/client';
|
||||
import AppButton from '@/components/ui/AppButton.vue';
|
||||
|
||||
const props = defineProps<{ businessId: string }>();
|
||||
|
||||
const weekDays = [
|
||||
{ dayOfWeek: 0, label: 'شنبه' },
|
||||
{ dayOfWeek: 1, label: 'یکشنبه' },
|
||||
{ dayOfWeek: 2, label: 'دوشنبه' },
|
||||
{ dayOfWeek: 3, label: 'سهشنبه' },
|
||||
{ dayOfWeek: 4, label: 'چهارشنبه' },
|
||||
{ dayOfWeek: 5, label: 'پنجشنبه' },
|
||||
{ dayOfWeek: 6, label: 'جمعه' },
|
||||
];
|
||||
|
||||
const slotsByDay = ref<Record<number, AvailabilitySlot[]>>({});
|
||||
const draft = reactive<Record<number, { start: string; end: string }>>({});
|
||||
const loading = ref(true);
|
||||
const saving = ref(false);
|
||||
const error = ref('');
|
||||
const message = ref('');
|
||||
|
||||
function initDraft() {
|
||||
for (const d of weekDays) {
|
||||
draft[d.dayOfWeek] = { start: '09:00', end: '17:00' };
|
||||
if (!slotsByDay.value[d.dayOfWeek]) {
|
||||
slotsByDay.value[d.dayOfWeek] = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function groupSlots(rows: AvailabilitySlot[]) {
|
||||
const grouped: Record<number, AvailabilitySlot[]> = {};
|
||||
for (const d of weekDays) {
|
||||
grouped[d.dayOfWeek] = [];
|
||||
}
|
||||
for (const row of rows) {
|
||||
grouped[row.dayOfWeek]?.push(row);
|
||||
}
|
||||
slotsByDay.value = grouped;
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
try {
|
||||
const rows = await fetchBusinessAvailability(props.businessId);
|
||||
groupSlots(rows);
|
||||
} catch (e) {
|
||||
error.value = e instanceof ApiError ? e.message : 'خطا در بارگذاری';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function addSlot(dayOfWeek: number) {
|
||||
const start = draft[dayOfWeek].start;
|
||||
const end = draft[dayOfWeek].end;
|
||||
if (!start || !end || start >= end) {
|
||||
error.value = 'ساعت پایان باید بعد از شروع باشد.';
|
||||
return;
|
||||
}
|
||||
error.value = '';
|
||||
slotsByDay.value[dayOfWeek].push({ dayOfWeek, startTime: start, endTime: end });
|
||||
}
|
||||
|
||||
function removeSlot(dayOfWeek: number, index: number) {
|
||||
slotsByDay.value[dayOfWeek].splice(index, 1);
|
||||
}
|
||||
|
||||
async function onSave() {
|
||||
saving.value = true;
|
||||
error.value = '';
|
||||
message.value = '';
|
||||
const payload: Omit<AvailabilitySlot, 'id'>[] = [];
|
||||
for (const d of weekDays) {
|
||||
for (const slot of slotsByDay.value[d.dayOfWeek] ?? []) {
|
||||
payload.push({
|
||||
dayOfWeek: slot.dayOfWeek,
|
||||
startTime: slot.startTime,
|
||||
endTime: slot.endTime,
|
||||
});
|
||||
}
|
||||
}
|
||||
try {
|
||||
await setBusinessAvailability(props.businessId, payload);
|
||||
message.value = 'ساعات پذیرش ذخیره شد.';
|
||||
await load();
|
||||
} catch (e) {
|
||||
error.value = e instanceof ApiError ? e.message : 'خطا در ذخیره';
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initDraft();
|
||||
load();
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user