Add time rezerv

This commit is contained in:
2026-05-25 19:13:28 +03:30
parent 0b2defd50c
commit 27b73c981a
12 changed files with 803 additions and 45 deletions
@@ -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>