282 lines
8.1 KiB
Vue
282 lines
8.1 KiB
Vue
<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"
|
|
v-if="reservation?.$id"
|
|
@click="onDelete" />
|
|
<q-btn
|
|
label="Reset"
|
|
@click="onReset"
|
|
color="secondary" />
|
|
<q-btn
|
|
label="Submit"
|
|
@click="onSubmit"
|
|
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>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref, watch } from 'vue';
|
|
import { useAuthStore } from 'src/stores/auth';
|
|
import { Boat, useBoatStore } from 'src/stores/boat';
|
|
import { Interval, Reservation } from 'src/stores/schedule.types';
|
|
import BoatScheduleTableComponent from 'src/components/scheduling/boat/BoatScheduleTableComponent.vue';
|
|
import { formatDate } from 'src/utils/schedule';
|
|
import { useReservationStore } from 'src/stores/reservation';
|
|
import { useQuasar } from 'quasar';
|
|
import { useRouter } from 'vue-router';
|
|
|
|
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 {
|
|
const updatedReservation = {
|
|
...newReservation,
|
|
user: auth.currentUser?.$id,
|
|
interval: {
|
|
start: newReservation.start,
|
|
end: newReservation.end,
|
|
resource: newReservation.resource,
|
|
},
|
|
};
|
|
bookingForm.value = updatedReservation;
|
|
}
|
|
});
|
|
|
|
const updateInterval = (interval: Interval) => {
|
|
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: hours, minutes: 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);
|
|
};
|
|
|
|
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
|
|
)
|
|
) {
|
|
// TODO: Make a proper validator
|
|
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>
|