diff --git a/package.json b/package.json index e9f37683..6a25f97e 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,14 @@ "build": "vite build", "build:dev": "vite build --mode development", "preview": "vite preview", + "preview:prod": "npm run build && npm run preview -- --host 127.0.0.1 --port 4173", "test:unit": "vitest run", "test:e2e": "playwright test", "test:e2e:ci": "playwright test --reporter=line,html", "test:e2e:smoke": "playwright test --grep @smoke --project=chromium-desktop --project=chromium-mobile", + "test:e2e:prod": "playwright test --config=playwright.prod.config.ts", + "test:e2e:live": "playwright test --config=playwright.live.config.ts", + "test:e2e:release": "npm run test:e2e:prod && npm run test:e2e:live", "test:e2e:pos-mobile-live": "node -e \"const { spawnSync } = require('child_process'); const result = spawnSync('npx', ['playwright', 'test', 'tests/e2e/adminModulePosMobileOrderFlow.spec.ts', '--project=chromium-mobile'], { stdio: 'inherit', shell: true, env: { ...process.env, PLAYWRIGHT_LIVE: '1' } }); process.exit(result.status ?? 1);\"", "test:all": "npm run test:unit && npm run test:e2e:smoke", "twa:build": "bubblewrap build", diff --git a/playwright.live.config.ts b/playwright.live.config.ts new file mode 100644 index 00000000..8baea967 --- /dev/null +++ b/playwright.live.config.ts @@ -0,0 +1,37 @@ +import { defineConfig, devices } from "@playwright/test"; + +const baseURL = process.env.PLAYWRIGHT_BASE_URL; +const isCI = !!process.env.CI; + +if (!baseURL) { + throw new Error("PLAYWRIGHT_BASE_URL is required for the live smoke gate."); +} + +export default defineConfig({ + testDir: "./tests/e2e/release", + testMatch: /.*\.live-smoke\.spec\.ts/, + timeout: 60_000, + fullyParallel: false, + forbidOnly: isCI, + retries: isCI ? 1 : 0, + workers: 1, + reporter: [ + ["list"], + ["html", { open: "never", outputFolder: "output/playwright/live/report" }], + ], + outputDir: "output/playwright/live/test-results", + use: { + baseURL, + trace: "retain-on-failure", + screenshot: "only-on-failure", + video: "retain-on-failure", + }, + projects: [ + { + name: "chromium-desktop", + use: { + ...devices["Desktop Chrome"], + }, + }, + ], +}); diff --git a/playwright.prod.config.ts b/playwright.prod.config.ts new file mode 100644 index 00000000..be81efed --- /dev/null +++ b/playwright.prod.config.ts @@ -0,0 +1,54 @@ +import { defineConfig, devices } from "@playwright/test"; + +const baseURL = "http://127.0.0.1:4173"; +const isCI = !!process.env.CI; + +process.env.PLAYWRIGHT_BASE_URL = baseURL; + +export default defineConfig({ + testDir: "./tests/e2e/release", + testMatch: /.*\.local-prod\.spec\.ts/, + timeout: 60_000, + fullyParallel: true, + forbidOnly: isCI, + retries: isCI ? 2 : 0, + workers: isCI ? 2 : 3, + reporter: [ + ["list"], + ["html", { open: "never", outputFolder: "output/playwright/prod/report" }], + ], + outputDir: "output/playwright/prod/test-results", + use: { + baseURL, + serviceWorkers: "block", + trace: "retain-on-failure", + screenshot: "only-on-failure", + video: "retain-on-failure", + }, + webServer: { + command: "npm run preview:prod", + url: baseURL, + timeout: 240_000, + reuseExistingServer: !isCI, + }, + projects: [ + { + name: "chromium-desktop", + use: { + ...devices["Desktop Chrome"], + }, + }, + { + name: "chromium-mobile", + use: { + ...devices["Pixel 5"], + }, + }, + { + name: "webkit-desktop", + use: { + ...devices["Desktop Safari"], + }, + }, + ], +}); diff --git a/src/components/displays/buttons/DatePeriodSelector.vue b/src/components/displays/buttons/DatePeriodSelector.vue index 9c903147..156ac78c 100644 --- a/src/components/displays/buttons/DatePeriodSelector.vue +++ b/src/components/displays/buttons/DatePeriodSelector.vue @@ -1,19 +1,28 @@ + +
+ - Invalid selection! Start date must be before end date. Please adjust your selection to ensure the start date is not after the end date, and that both dates are within any specified restrictions. - + > + Invalid selection! Start date must be before end date. Please adjust your selection to ensure the start date is not after the end date, and that both dates are within any specified restrictions. + - - - Selection spans multiple months! This may lead to unexpected results. Consider selecting an entire month or a shorter period for more accurate insights. - - - + Selection spans multiple months! This may lead to unexpected results. Consider selecting an entire month or a shorter period for more accurate insights. + + + - Selection is not an entire month Click to set entire month. - When a partial month selection is made, it may not accurately represent the intended time period. Consider selecting an entire month for more precise results. - + > + Selection is not an entire month Click to set entire month. + When a partial month selection is made, it may not accurately represent the intended time period. Consider selecting an entire month for more precise results. + +
diff --git a/src/components/displays/department/moduleNavigation/DepartmentModulesDisplay.vue b/src/components/displays/department/moduleNavigation/DepartmentModulesDisplay.vue index 80452e50..bc252c6d 100644 --- a/src/components/displays/department/moduleNavigation/DepartmentModulesDisplay.vue +++ b/src/components/displays/department/moduleNavigation/DepartmentModulesDisplay.vue @@ -19,6 +19,15 @@ const isLoadingSelfServeEnabled = ref(false); const isSavingSelfServeEnabled = ref(false); const bookingsCount = ref(0); +const getDepartmentId = () => { + return parseInt(router.currentRoute.value.params.departmentId); +}; + +const buildDepartmentModulePath = (modulePath = "") => { + const departmentId = getDepartmentId(); + return `/admin/${departmentId}${modulePath}`; +}; + const modules = computed(() => [ { id: 1, @@ -26,13 +35,10 @@ const modules = computed(() => [ description: t('admin.department_modules.pos.description'), icon: "fas fa-book", onClick: () => { - // Get the departmentId from the URL - const departmentId = router.currentRoute.value.params.departmentId; - // Redirect to the POS system - router.push(`/admin/${departmentId}/modules/pos`); + router.push(buildDepartmentModulePath('/modules/pos')); }, onClickCount: () => { - this.onClick(); + router.push(buildDepartmentModulePath('/modules/pos')); }, count: 0 }, @@ -42,16 +48,10 @@ const modules = computed(() => [ description: t('admin.department_modules.bookings.description'), icon: "fas fa-calendar-alt", onClick: () => { - // Get the departmentId from the URL - const departmentId = router.currentRoute.value.params.departmentId; - // Redirect to the bookings - router.push(`/admin/${departmentId}/modules/bookings`); + router.push(buildDepartmentModulePath('/modules/bookings')); }, onClickCount: () => { - // Get the departmentId from the URL - const departmentId = router.currentRoute.value.params.departmentId; - // Redirect to the bookings - router.push(`/admin/${departmentId}/modules/bookings?search=pending`); + router.push(buildDepartmentModulePath('/modules/bookings?search=pending')); }, count: bookingsCount.value }, @@ -61,13 +61,10 @@ const modules = computed(() => [ description: t('admin.department_modules.daily_report.description'), icon: "fas fa-book", onClick: () => { - // Get the departmentId from the URL - const departmentId = router.currentRoute.value.params.departmentId; - // Redirect to the POS system - router.push(`/admin/${departmentId}/modules/daily-report`); + router.push(buildDepartmentModulePath('/modules/daily-report')); }, onClickCount: () => { - this.onClick(); + router.push(buildDepartmentModulePath('/modules/daily-report')); }, count: 0 }, @@ -77,13 +74,10 @@ const modules = computed(() => [ description: t('admin.department_modules.goals.description'), icon: "fas fa-bullseye", onClick: () => { - // Get the departmentId from the URL - const departmentId = router.currentRoute.value.params.departmentId; - // Redirect to the goals - router.push(`/admin/${departmentId}/modules/goals`); + router.push(buildDepartmentModulePath('/modules/goals')); }, onClickCount: () => { - this.onClick(); + router.push(buildDepartmentModulePath('/modules/goals')); }, count: 0 }, @@ -93,13 +87,10 @@ const modules = computed(() => [ description: t('admin.department_modules.self_wash.description'), icon: "fas fa-tint", onClick: () => { - // Get the departmentId from the URL - const departmentId = router.currentRoute.value.params.departmentId; - // Redirect to the wash lanes - router.push(`/admin/${departmentId}/modules/wash-lanes`); + router.push(buildDepartmentModulePath('/modules/wash-lanes')); }, onClickCount: () => { - this.onClick(); + router.push(buildDepartmentModulePath('/modules/wash-lanes')); }, count: 0, hasSwitch: true, @@ -131,11 +122,10 @@ const modules = computed(() => [ description: 'Configure questions, conditions, rules and tasks', icon: "fas fa-project-diagram", onClick: () => { - const departmentId = router.currentRoute.value.params.departmentId; - router.push(`/admin/${departmentId}/modules/self-serve/studio`); + router.push(buildDepartmentModulePath('/modules/self-serve/studio')); }, onClickCount: () => { - this.onClick(); + router.push(buildDepartmentModulePath('/modules/self-serve/studio')); }, count: 0 } @@ -177,11 +167,6 @@ const modules = computed(() => [ */ ]); -// Get the departmentId from the URL -const getDepartmentId = () => { - return parseInt(router.currentRoute.value.params.departmentId); -}; - // Get today's bookings const getTodaysBookings = async () => { // Return, if the departmentId is not set @@ -231,9 +216,9 @@ setInterval(() => { diff --git a/src/components/displays/department/pos/steps/mobile/PosDepartmentStepMobile1.vue b/src/components/displays/department/pos/steps/mobile/PosDepartmentStepMobile1.vue index d48febbc..cdb5a874 100644 --- a/src/components/displays/department/pos/steps/mobile/PosDepartmentStepMobile1.vue +++ b/src/components/displays/department/pos/steps/mobile/PosDepartmentStepMobile1.vue @@ -310,9 +310,9 @@ onUnmounted(() => {
- - {{ SessionUser.objects.global.language.take_picture }} - + + {{ SessionUser.objects.global.language.take_picture }} + @@ -325,9 +325,9 @@ onUnmounted(() => { - - {{ SessionUser.objects.global.language.close }} - + + {{ SessionUser.objects.global.language.close }} + diff --git a/src/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile1RegistrationNumbers.vue b/src/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile1RegistrationNumbers.vue index 7051f0d9..209bb640 100644 --- a/src/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile1RegistrationNumbers.vue +++ b/src/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile1RegistrationNumbers.vue @@ -8,10 +8,8 @@ import PosDepartmentStepMobile1RegistrationNumber3 import { activeVehicleIndex, setActiveVehicleIndex, vehicles, popups, metadata, reference } from "../objects/PosDepartmentStepMobileFlow.vue"; import PosDepartmentStepMobileButtonClearAll from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobileButtonClearAll.vue"; -import PosDepartmentStepMobileReference - from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobileReference.vue"; import LongPressListener from "@/components/viewport/elements/wrappers/LongPressListener.vue"; -import {ref, watch} from "vue"; +import { nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue"; import { getNotes } from "@/components/shop/CustomerNotes.vue"; import SessionUser from "@/components/session/token/SessionUser.vue"; const isActive = (number: number): boolean => { @@ -35,6 +33,9 @@ const onLongPress = (number: number) => { } const showNewDemo = ref(true); +const registrationRowElement = ref(null); +const referenceWidth = ref(174); +let registrationRowResizeObserver: ResizeObserver | null = null; // Get the customers notes (if any) const customerNotes = ref([]); // TODO: define type @@ -57,10 +58,54 @@ watch(() => metadata.getCustomerId(), (newCustomerId) => { } }, { immediate: true }); +const updateReferenceWidth = () => { + const rowWidth = registrationRowElement.value?.getBoundingClientRect().width ?? 0; + referenceWidth.value = Math.max(174, Math.round(rowWidth)); +}; + +const observeRegistrationRow = () => { + registrationRowResizeObserver?.disconnect(); + registrationRowResizeObserver = null; + + if (!registrationRowElement.value || typeof ResizeObserver === "undefined") { + updateReferenceWidth(); + return; + } + + registrationRowResizeObserver = new ResizeObserver(() => { + updateReferenceWidth(); + }); + registrationRowResizeObserver.observe(registrationRowElement.value); + updateReferenceWidth(); +}; + +onMounted(async () => { + await nextTick(); + observeRegistrationRow(); +}); + +onBeforeUnmount(() => { + registrationRowResizeObserver?.disconnect(); +}); + +watch(registrationRowElement, async () => { + await nextTick(); + observeRegistrationRow(); +}); + +watch(() => [ + vehicles.get(1)?.reg?.length ?? 0, + vehicles.get(2)?.reg?.length ?? 0, + vehicles.get(3)?.reg?.length ?? 0, +], async () => { + await nextTick(); + updateReferenceWidth(); +}, { flush: "post" }); + - - - - {{ SessionUser.objects.global.language.close }} - - + + + + + {{ SessionUser.objects.global.language.close }} + + + - - + +
diff --git a/src/components/displays/department/pos/steps/mobile/views/PosDepartmentStep1MobileTransactionHistory.vue b/src/components/displays/department/pos/steps/mobile/views/PosDepartmentStep1MobileTransactionHistory.vue index d3b497a6..834137f5 100644 --- a/src/components/displays/department/pos/steps/mobile/views/PosDepartmentStep1MobileTransactionHistory.vue +++ b/src/components/displays/department/pos/steps/mobile/views/PosDepartmentStep1MobileTransactionHistory.vue @@ -156,23 +156,23 @@ const isVisible = (transaction: PosOrder, showPending: boolean, showCompleted: b diff --git a/src/views/dashboards/superUserDashboard/invoicing/SuperuserInvoicingLocalStore.vue b/src/views/dashboards/superUserDashboard/invoicing/SuperuserInvoicingLocalStore.ts similarity index 66% rename from src/views/dashboards/superUserDashboard/invoicing/SuperuserInvoicingLocalStore.vue rename to src/views/dashboards/superUserDashboard/invoicing/SuperuserInvoicingLocalStore.ts index 3b254afa..e18df8f2 100644 --- a/src/views/dashboards/superUserDashboard/invoicing/SuperuserInvoicingLocalStore.vue +++ b/src/views/dashboards/superUserDashboard/invoicing/SuperuserInvoicingLocalStore.ts @@ -1,6 +1,3 @@ - \ No newline at end of file +}; diff --git a/src/views/dashboards/userDashboard/bookings/displays/tables/OrderBookingsTable.vue b/src/views/dashboards/userDashboard/bookings/displays/tables/OrderBookingsTable.vue index bbaa12c0..db2487cc 100644 --- a/src/views/dashboards/userDashboard/bookings/displays/tables/OrderBookingsTable.vue +++ b/src/views/dashboards/userDashboard/bookings/displays/tables/OrderBookingsTable.vue @@ -441,7 +441,7 @@ const getColspan = () => {
- +