feat: Schedule bookings
This commit is contained in:
@@ -7,6 +7,9 @@
|
||||
</IonButtons>
|
||||
<IonTitle>Manage Slots</IonTitle>
|
||||
<IonButtons slot="end">
|
||||
<IonButton @click="showWeekApply = true">
|
||||
<IonIcon slot="icon-only" :icon="calendarOutline" />
|
||||
</IonButton>
|
||||
<IonButton @click="showAddSlot = true">
|
||||
<IonIcon slot="icon-only" :icon="addOutline" />
|
||||
</IonButton>
|
||||
@@ -127,6 +130,52 @@
|
||||
</IonContent>
|
||||
</IonModal>
|
||||
|
||||
<!-- Apply to Week modal -->
|
||||
<IonModal :is-open="showWeekApply" @did-dismiss="showWeekApply = false">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Apply Template to Week</IonTitle>
|
||||
<IonButtons slot="end">
|
||||
<IonButton @click="showWeekApply = false">Cancel</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent class="ion-padding">
|
||||
<IonList lines="full">
|
||||
<IonItem>
|
||||
<IonLabel position="stacked">Template</IonLabel>
|
||||
<IonSelect v-model="weekApply.templateId" placeholder="Select template" interface="action-sheet">
|
||||
<IonSelectOption v-for="t in templates" :key="t.id" :value="t.id">{{ t.name }}</IonSelectOption>
|
||||
</IonSelect>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel position="stacked">Boat</IonLabel>
|
||||
<IonSelect v-model="weekApply.boatId" placeholder="Select boat" interface="action-sheet">
|
||||
<IonSelectOption v-for="b in boats" :key="b.id" :value="b.id">{{ b.display_name || b.name }}</IonSelectOption>
|
||||
</IonSelect>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
|
||||
<h4 class="section-title ion-margin-top">Days to apply</h4>
|
||||
<IonList lines="none">
|
||||
<IonItem v-for="(day, i) in weekDays" :key="day.iso">
|
||||
<IonCheckbox v-model="weekApply.days[i]" slot="start" />
|
||||
<IonLabel>{{ day.label }}</IonLabel>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
|
||||
<IonButton
|
||||
expand="block"
|
||||
class="ion-margin-top"
|
||||
:disabled="!weekApply.templateId || !weekApply.boatId || !weekApply.days.some(Boolean) || weekApply.applying"
|
||||
@click="applyTemplateToWeek"
|
||||
>
|
||||
<IonSpinner v-if="weekApply.applying" name="crescent" slot="start" />
|
||||
Apply to Selected Days
|
||||
</IonButton>
|
||||
</IonContent>
|
||||
</IonModal>
|
||||
|
||||
<IonToast
|
||||
v-model:is-open="toast.show"
|
||||
:message="toast.message"
|
||||
@@ -143,10 +192,10 @@ import {
|
||||
IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonButtons,
|
||||
IonMenuButton, IonButton, IonIcon, IonCard, IonCardHeader, IonCardTitle,
|
||||
IonCardSubtitle, IonCardContent, IonList, IonItem, IonLabel, IonToggle,
|
||||
IonSelect, IonSelectOption, IonSpinner, IonModal, IonInput, IonToast,
|
||||
IonSelect, IonSelectOption, IonSpinner, IonModal, IonInput, IonToast, IonCheckbox,
|
||||
} from '@ionic/vue'
|
||||
import {
|
||||
addOutline, timeOutline, trashOutline, constructOutline,
|
||||
addOutline, timeOutline, trashOutline, constructOutline, calendarOutline,
|
||||
} from 'ionicons/icons'
|
||||
import type { Database, TimeTuple } from '~/types/supabase'
|
||||
|
||||
@@ -162,6 +211,23 @@ const supabase = useSupabaseClient() as any
|
||||
const loading = ref(false)
|
||||
const addingSlot = ref(false)
|
||||
const showAddSlot = ref(false)
|
||||
const showWeekApply = ref(false)
|
||||
const weekApply = reactive({ templateId: '', boatId: '', applying: false, days: [] as boolean[] })
|
||||
|
||||
watch(showWeekApply, (open) => {
|
||||
if (open) weekApply.days = Array(7).fill(true)
|
||||
})
|
||||
const weekDays = computed(() => {
|
||||
const base = new Date(selectedDate.value + 'T12:00:00')
|
||||
const dow = base.getDay() // 0=Sun
|
||||
const monday = new Date(base)
|
||||
monday.setDate(base.getDate() - ((dow + 6) % 7))
|
||||
return Array.from({ length: 7 }, (_, i) => {
|
||||
const d = new Date(monday)
|
||||
d.setDate(monday.getDate() + i)
|
||||
return { iso: d.toISOString().slice(0, 10), label: d.toLocaleDateString('en-CA', { weekday: 'short', month: 'short', day: 'numeric' }) }
|
||||
})
|
||||
})
|
||||
const selectedDate = ref(todayIso())
|
||||
const boats = ref<Boat[]>([])
|
||||
const templates = ref<Template[]>([])
|
||||
@@ -280,6 +346,28 @@ async function applyTemplate(boatId: string, templateId: string) {
|
||||
showToast(`Template "${template.name}" applied`, 'success')
|
||||
}
|
||||
|
||||
async function applyTemplateToWeek() {
|
||||
const template = templates.value.find(t => t.id === weekApply.templateId)
|
||||
if (!template) return
|
||||
weekApply.applying = true
|
||||
|
||||
const selectedDays = weekDays.value.filter((_, i) => weekApply.days[i])
|
||||
const inserts = selectedDays.flatMap(day =>
|
||||
(template.time_tuples as TimeTuple[]).map(([start, end]) => ({
|
||||
boat_id: weekApply.boatId,
|
||||
start_time: new Date(`${day.iso}T${start}:00`).toISOString(),
|
||||
end_time: new Date(`${day.iso}T${end}:00`).toISOString(),
|
||||
}))
|
||||
)
|
||||
|
||||
const { error } = await supabase.from('intervals').insert(inserts)
|
||||
weekApply.applying = false
|
||||
if (error) { showToast('Failed to apply: ' + error.message, 'danger'); return }
|
||||
showWeekApply.value = false
|
||||
await fetchSlots()
|
||||
showToast(`Template applied to ${selectedDays.length} day(s)`, 'success')
|
||||
}
|
||||
|
||||
function showToast(message: string, color: string) {
|
||||
toast.message = message
|
||||
toast.color = color
|
||||
|
||||
Reference in New Issue
Block a user