"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:
@@ -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">
|
||||
Gå 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>
|
||||
|
||||
Reference in New Issue
Block a user