Files
bab-app/app/components/BoatReservationComponent.vue

228 lines
7.4 KiB
Vue

<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { useAuthStore } from '~/stores/auth';
import { useBoatStore } from '~/stores/boat';
import type { Boat } from '~/utils/boat.types';
import type { Interval, Reservation } from '~/utils/schedule.types';
import BoatScheduleTableComponent from '~/components/scheduling/boat/BoatScheduleTableComponent.vue';
import { formatDate } from '~/utils/schedule';
import { useReservationStore } from '~/stores/reservation';
import { useQuasar } from 'quasar';
interface BookingForm {
$id?: string;
user?: string;
interval?: Interval | null;
reason?: string;
members?: string[];
guests?: string[];
comment?: string;
}
const reason_options = ['Open Sail', 'Private Sail', 'Racing', 'Other'];
const boatStore = useBoatStore();
const auth = useAuthStore();
const newForm = {
user: auth.currentUser?.$id,
interval: {} as Interval,
reason: 'Open Sail',
members: [],
guests: [],
comment: '',
};
const reservation = defineModel<Reservation>();
const reservationStore = useReservationStore();
const boatSelect = ref(false);
const bookingForm = ref<BookingForm>({ ...newForm });
const $q = useQuasar();
const $router = useRouter();
watch(reservation, (newReservation) => {
if (!newReservation) {
bookingForm.value = newForm;
} else {
bookingForm.value = {
...newReservation,
user: auth.currentUser?.$id,
interval: {
start: newReservation.start,
end: newReservation.end,
resource: newReservation.resource,
},
};
}
});
const updateInterval = (interval: Interval | null | undefined) => {
bookingForm.value.interval = interval;
boatSelect.value = false;
};
const bookingDuration = computed((): { hours: number; minutes: number } => {
if (bookingForm.value.interval?.start && bookingForm.value.interval?.end) {
const start = new Date(bookingForm.value.interval.start).getTime();
const end = new Date(bookingForm.value.interval.end).getTime();
const delta = Math.abs(end - start) / 1000;
const hours = Math.floor(delta / 3600) % 24;
const minutes = Math.floor(delta - hours * 3600) % 60;
return { hours, minutes };
}
return { hours: 0, minutes: 0 };
});
const bookingName = computed(() => auth.getUserNameById(bookingForm.value?.user));
const boat = computed((): Boat | null => {
const boatId = bookingForm.value.interval?.resource;
return boatStore.getBoatById(boatId);
});
const onDelete = () => {
reservationStore.deleteReservation(reservation.value?.$id);
$router.go(-1);
};
const onReset = () => {
bookingForm.value.interval = null;
bookingForm.value = reservation.value
? {
...reservation.value,
interval: {
start: reservation.value.start,
end: reservation.value.end,
resource: reservation.value.resource,
},
}
: { ...newForm };
};
const onSubmit = async () => {
const booking = bookingForm.value;
if (
!(
booking.interval &&
booking.interval.resource &&
booking.interval.start &&
booking.interval.end &&
auth.currentUser
)
) {
return false;
}
const newReservation = <Reservation>{
resource: booking.interval.resource,
start: booking.interval.start,
end: booking.interval.end,
user: auth.currentUser.$id,
status: 'confirmed',
reason: booking.reason,
comment: booking.comment,
$id: reservation.value?.$id,
};
const status = $q.notify({
color: 'secondary',
textColor: 'white',
message: 'Submitting Reservation',
spinner: true,
closeBtn: 'Dismiss',
position: 'top',
timeout: 0,
group: false,
});
try {
const r = await reservationStore.createOrUpdateReservation(newReservation);
status({
color: 'positive',
icon: 'cloud_done',
message: `Booking ${newReservation.$id ? 'updated' : 'created'}: ${
boatStore.getBoatById(r.resource)?.name
} at ${formatDate(r.start)}`,
spinner: false,
});
} catch (e) {
status({ color: 'negative', icon: 'error', spinner: false, message: 'Failed to book!' + e });
}
$router.go(-1);
};
</script>
<template>
<div class="q-pa-xs row q-gutter-xs">
<q-card flat class="col-lg-4 col-md-6 col-sm-8 col-xs-12">
<q-card-section>
<div class="text-h5 q-mt-none q-mb-xs">
{{ reservation ? 'Modify Booking' : 'New Booking' }}
</div>
<div class="text-caption text-grey-8">for: {{ bookingName }}</div>
</q-card-section>
<q-list class="q-px-xs">
<q-item class="q-pa-none" clickable @click="boatSelect = true">
<q-card v-if="boat" class="col-12">
<q-card-section>
<q-img :src="boat.imgSrc" :fit="'scale-down'">
<div class="row absolute-top">
<div class="col text-h7 text-left">{{ boat.name }}</div>
<div class="col text-right text-caption">{{ boat.class }}</div>
</div>
</q-img>
</q-card-section>
<q-separator />
<q-card-section horizontal>
<q-card-section class="col-9">
<q-list dense class="row">
<q-item class="q-ma-none col-12">
<q-item-section avatar>
<q-badge color="primary" label="Start" />
</q-item-section>
<q-item-section class="text-body2">
{{ formatDate(bookingForm.interval?.start) }}
</q-item-section>
</q-item>
<q-item class="q-ma-none col-12">
<q-item-section avatar>
<q-badge color="primary" label="End" />
</q-item-section>
<q-item-section class="text-body2" style="min-width: 150px">
{{ formatDate(bookingForm.interval?.end) }}
</q-item-section>
</q-item>
</q-list>
</q-card-section>
<q-separator vertical />
<q-card-section class="col-3 flex flex-center bg-grey-4">
{{ bookingDuration.hours }} hours
<div v-if="bookingDuration.minutes">
<q-separator />
{{ bookingDuration.minutes }} mins
</div>
</q-card-section>
</q-card-section>
</q-card>
<div v-else class="col-12">
<q-field filled>Tap to Select a Boat / Time</q-field>
</div>
</q-item>
<q-item class="q-px-none">
<q-item-section>
<q-select filled v-model="bookingForm.reason" :options="reason_options" label="Reason for sail" />
</q-item-section>
</q-item>
<q-item class="q-px-none">
<q-item-section>
<q-input v-model="bookingForm.comment" clearable autogrow filled label="Additional Comments (optional)" />
</q-item-section>
</q-item>
</q-list>
<q-card-actions align="right">
<q-btn label="Delete" color="negative" size="lg" v-if="reservation?.$id" @click="onDelete" />
<q-btn label="Reset" @click="onReset" size="lg" color="secondary" />
<q-btn label="Submit" @click="onSubmit" size="lg" color="primary" />
</q-card-actions>
</q-card>
<q-dialog v-model="boatSelect" full-width>
<BoatScheduleTableComponent :model-value="bookingForm.interval" @update:model-value="updateInterval" />
</q-dialog>
</div>
</template>