"Add step 5 to NewTimeBookingFlow, enhance category/product watches, and implement fullscreen summary in MyBookingsBook.vue for improved booking flow UX"

This commit is contained in:
Jeppe Bundgaard
2025-11-04 16:40:09 +01:00
parent 05791bb54a
commit a9369e9d22
3 changed files with 162 additions and 9 deletions
@@ -7,6 +7,7 @@ import NewBookingStep1 from "@/views/dashboards/userDashboard/bookings/displays/
import NewBookingStep2 from "@/views/dashboards/userDashboard/bookings/displays/steps/NewBookingStep2.vue";
import NewBookingStep3 from "@/views/dashboards/userDashboard/bookings/displays/steps/NewBookingStep3.vue";
import NewBookingStep4 from "@/views/dashboards/userDashboard/bookings/displays/steps/NewBookingStep4.vue";
import NewBookingStep5 from "@/views/dashboards/userDashboard/bookings/displays/steps/NewBookingStep5.vue";
/**
* This is the new time booking flow.
* It contains the functions used to create a new time booking.
@@ -31,7 +32,7 @@ const stepComponents = {
2: NewBookingStep2,
3: NewBookingStep3,
4: NewBookingStep4,
5: null,
5: NewBookingStep5,
}
const steps = ref<stepType[]>([
{
@@ -71,6 +71,34 @@ onMounted(() => {
}
});
// React to department or category changes
watch(() => department_id.value, (newDeptId) => {
// Reload categories for the new department
loadCategories();
// Refetch products for the current category scope
if (getProductsCategory() === null) {
getProducts(newDeptId).then((response) => {
products.value = response.data.data;
});
} else {
getProductCategory(getProductsCategory(), newDeptId).then((response) => {
products.value = response.data.data;
});
}
});
watch(() => getProductsCategory(), (newCategory) => {
if (newCategory === null) {
getProducts(department_id.value).then((response) => {
products.value = response.data.data;
});
} else {
getProductCategory(newCategory, department_id.value).then((response) => {
products.value = response.data.data;
});
}
});
@@ -129,9 +157,10 @@ const loadCategories = async () => {
categories.value.push(...categories_static.value);
// Get the department id from the route
const route = useRoute();
const departmentId = route.params.departmentId;
// Prefer route param, otherwise fallback to global POS department_id
const departmentId = route.params.departmentId ?? department_id.value;
// If the department id is not set, return
if (departmentId === undefined) {
if (departmentId === undefined || departmentId === null) {
return;
}
// Get the categories
@@ -1,7 +1,7 @@
<script setup lang="ts">
import UserDashboardPageWrapper from "@/views/dashboards/userDashboard/UserDashboardPageWrapper.vue";
import {manageSteps} from "@/components/displays/user/bookings/NewTimeBookingFlow.vue";
import {computed, ref, watch, nextTick} from "vue";
import {computed, ref, watch, nextTick, onUnmounted} from "vue";
import NewBookingControlNavigation
from "@/views/dashboards/userDashboard/bookings/displays/controls/NewBookingControlNavigation.vue";
import NewBookingStep1 from "@/views/dashboards/userDashboard/bookings/displays/steps/NewBookingStep1.vue";
@@ -12,6 +12,7 @@ import PosDepartmentStepMobileFixedBottomControl
import NewBookingElementVehicles
from "@/views/dashboards/userDashboard/bookings/displays/elements/NewBookingElementVehicles.vue";
import NewBookingStep4 from "@/views/dashboards/userDashboard/bookings/displays/steps/NewBookingStep4.vue";
import NewBookingStep5 from "@/views/dashboards/userDashboard/bookings/displays/steps/NewBookingStep5.vue";
import {departments} from "@/components/pagination/departmentTabs.vue";
import type {PosProduct} from "@/components/displays/department/pos/steps/mobile/objects/PosProduct.vue";
import type {PosAddon} from "@/components/displays/department/pos/steps/mobile/objects/PosAddon.vue";
@@ -52,6 +53,7 @@ import PosNotes from "@/components/displays/department/pos/PosNotes.vue";
import products from "@/components/displays/Products.vue";
import PosDepartmentStepMobile2Addons
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2Addons.vue";
import { setDepartment, getProductsCategory, setProductsCategory, nextStep } from "@/components/shop/POSDepartmentProcess.vue";
const department = ref(departments[0]);
const isDepartmentInput = ref(false);
@@ -90,6 +92,29 @@ const onClickSelectDepartment = () => {
isDepartmentInput.value = true;
setInputFocus('department');
};
// Keep POS context department_id in sync with selected department for proper product filtering
watch(() => department.value, (newDept) => {
try {
if (newDept && newDept.id) {
setDepartment(newDept.id);
}
} catch (e) {
console.warn('Could not set POS department from booking view', e);
}
});
// Auto-select a sensible category when a vehicle type is identified (user can still override later)
watch([() => reg1Type.value, () => reg2Type.value], ([newReg1Type, newReg2Type]) => {
try {
// If no category chosen yet, prefer exterior category id 6 by default when a vehicle is known
if (getProductsCategory() === null && (newReg1Type || newReg2Type)) {
setProductsCategory(6);
}
} catch (e) {
console.warn('Could not auto-select category based on vehicle type', e);
}
});
const onClickSelectReg1 = () => {
// Set the input to true
isReg1Input.value = true;
@@ -576,6 +601,34 @@ const completeBasket = computed({
set: () => {}
})
// Confirmation / summary (fullscreen)
const showSummary = ref(false);
const summaryOverlay = ref<HTMLElement | null>(null);
const openSummary = () => { showSummary.value = true; nextTick(() => summaryOverlay.value?.focus()); };
const onSummaryBack = () => { showSummary.value = false; };
const onSummaryConfirm = () => {
showSummary.value = false;
try {
nextStep({ isMobile: false, orderCreation: true });
} catch (e) {
console.warn('Proceeding to next step failed', e);
}
};
// Lock background scroll when fullscreen summary is open
watch(showSummary, (val) => {
const html = document.documentElement;
if (val) {
html.classList.add('is-clipped');
} else {
html.classList.remove('is-clipped');
}
});
// Cleanup on unmount
onUnmounted(() => {
document.documentElement.classList.remove('is-clipped');
});
</script>
<template>
@@ -652,10 +705,9 @@ const completeBasket = computed({
<!--<PosSelectedCustomer class="mt-3"/> -->
<NextStepError class="is-fullwidth" />
<div class="buttons">
<NextStep
class="is-fullwidth"
label="Næste"
/>
<button class="button is-fullwidth is-primary" @click="openSummary">
til bekræftelse
</button>
<Cancel class="is-fullwidth" />
</div>
</div>
@@ -1105,6 +1157,42 @@ const completeBasket = computed({
</PosDepartmentStepMobileFixedBottomControl>
</template>
</UserDashboardPageWrapper>
<!-- Summary / confirmation fullscreen overlay -->
<div
v-if="showSummary"
class="booking-summary-overlay"
role="dialog"
aria-modal="true"
aria-labelledby="booking-summary-title"
ref="summaryOverlay"
tabindex="-1"
@keydown.esc="onSummaryBack"
>
<div class="booking-summary-header">
<p id="booking-summary-title" class="title is-5 mb-0">Bekræft booking</p>
<div class="buttons">
<button class="button" @click="onSummaryBack">Tilbage</button>
<button class="button is-primary" @click="onSummaryConfirm">Bekræft booking</button>
</div>
</div>
<div class="booking-summary-body">
<NewBookingStep5
:department="department"
:reg1="reg1"
:reg1Type="reg1Type"
:reg2="reg2"
:reg2Type="reg2Type"
:dateTime="dateTime"
:basket="completeBasket"
:notes="notes"
:reference="reference"
:poNumber="poNumber"
@back="onSummaryBack"
@confirm="onSummaryConfirm"
/>
</div>
</div>
</div>
</template>
@@ -1199,4 +1287,39 @@ const completeBasket = computed({
.custom-date-picker-time.is-selected:hover {
background-color: #d0dff5;
}
</style>
</style>
<style scoped>
.booking-summary-overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 50; /* Above most UI; Bulma modal uses 40 */
background: #ffffff;
display: flex;
flex-direction: column;
}
.booking-summary-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1rem;
background: #ffffff;
border-bottom: 1px solid rgba(10,10,10,0.1);
position: sticky;
top: 0;
z-index: 1;
}
.booking-summary-body {
flex: 1;
overflow: auto;
-webkit-overflow-scrolling: touch;
padding: 1rem;
}
@media screen and (min-width: 1024px) {
.booking-summary-body {
padding: 1.5rem 2rem;
}
}
</style>