Compare commits

...
Author SHA1 Message Date
Jeppe Bundgaard 7fc889dd75 Refactor LoadButtonWhileAwait to use Buefy components and enhance loading/disabled state handling 2026-02-17 16:22:43 +01:00
Jeppe Bundgaard 8aa6fcf17f Refactor DepartmentPos pagination component to use TableLabeledPagination and enhance i18n integration 2026-02-17 15:57:22 +01:00
Jeppe Bundgaard 9f5fba3400 Extend i18n integration for department SMS notifications and labels
- Replaced hardcoded text with dynamic translations using `$t` and `i18n.global.t`.
- Updated `en.json` and `da.json` with new translation keys for SMS notifications, country codes, and related labels.
- Improved multilingual consistency and maintainability across components.
2026-02-17 15:50:28 +01:00
Jeppe Bundgaard ca26751b9f Add i18n keys for department reports (revenue, washes, avg revenue per wash) in en.json 2026-02-17 15:41:17 +01:00
Jeppe Bundgaard 66298d7d45 Make DatePeriodSelector shortcuts reactive to language changes
- Replaced static shortcut arrays with computed properties for dynamic reactivity to language changes.
- Improved i18n maintainability by using `SessionUser.objects.global.language
2026-02-17 15:39:50 +01:00
Jeppe Bundgaard 76e5b03bc2 Extend i18n integration in DepartmentDailyReport components
- Added new translation keys (`quantity_sold`) in `en.json` and `da.json` for improved multilingual support.
- Replaced hardcoded labels in `DepartmentDailyReportCount.vue` with computed properties using dynamic translations from `useI18n`.
- Improved maintainability and consistency by centralizing reusable strings.
2026-02-17 15:35:52 +01:00
Jeppe Bundgaard 731ba09696 Extend i18n integration in DepartmentDailyReport components
- Replaced hardcoded strings with dynamic translations using `useI18n` and `$t`.
- Localized titles, subtitles, placeholders, labels, and error messages across daily report components for multilingual consistency.
- Added new scalable translation keys to enhance maintainability.
2026-02-17 15:26:26 +01:00
Jeppe Bundgaard fe3f4b10f0 Extend i18n integration across dashboards, reports, and components
- Updated `en.json` and `da.json` with comprehensive translation keys for department dashboards, booking reports, tables, and POS components.
- Replaced hardcoded strings with dynamic translations using `$t` in multiple components for multilingual consistency.
- Improved maintainability by organizing and centralizing reusable translation keys.
2026-02-17 15:25:32 +01:00
Jeppe Bundgaard 2266e3908f Extend i18n integration for time bookings and POS components
- Added new translation keys in `en.json` and `da.json` for time bookings (placeholders, labels, and actions) and POS component buttons.
- Improved localization for registration numbers, license plate inputs, and booking flows.
2026-02-17 15:13:21 +01:00
Jeppe Bundgaard b0a2c0216f Expand i18n integration across component actions and admin settings
- Replaced hardcoded texts in `ActionSettingsWheelButton.vue` and related components with dynamic translations using `useI18n` for improved multilingual consistency.
- Updated `en.json` with new translation keys for buttons, modals, and action labels.
- Consolidated unused translation keys and improved maintainability by centralizing reusable strings.
2026-02-17 14:57:13 +01:00
Jeppe Bundgaard 16d63a2710 Expand i18n integration across dashboards and components
- Replaced hardcoded text with dynamic translations using `useI18n` or `$t` across multiple dashboards and components.
- Localized labels, placeholders, titles, subtitles, error messages, table headers, buttons, and actions.
- Extended and centralized translation keys for improved multilingual support and maintainability.
2026-02-17 14:16:41 +01:00
Jeppe Bundgaard fe3318e6ac Extend i18n integration across components
- Replaced hardcoded text with dynamic translations using `useI18n` or global `i18n` in multiple components and dashboards.
- Localized buttons, placeholders, labels, table headers, error messages, and object metadata, enhancing multilingual support.
- Improved maintainability by introducing and centralizing new translation keys.
2026-02-17 13:39:43 +01:00
Jeppe Bundgaard d6be146d91 Refactor i18n integration in superuser navigation menu items
- Replaced `useI18n` with global `i18n` instance for consistency.
- Adjusted translation function to `i18n.global.t` for improved maintainability.
2026-02-17 13:18:05 +01:00
Jeppe Bundgaard 27f6de8b42 Localize titles, subtitles, and error messages across dashboards and tables
- Replaced hardcoded titles, subtitles, and error messages with dynamic translations using `$t` across dashboard and table components.
- Enhanced i18n support by introducing new translation keys for improved multilingual consistency.
2026-02-17 13:16:55 +01:00
Jeppe Bundgaard 6c2ca85dee Localize tables and improve i18n integration
- Replaced hardcoded labels and placeholders with dynamic translations using `i18n` across multiple table components (`PosLastScannedLicensePlates.vue`, `InvoiceCollectionsTable.vue`, `ordersSyncTable.vue`, etc.).
- Added comprehensive translation keys for table headers, actions, and states to improve multilingual support.
- Enhanced maintainability by centralizing reusable translation keys.
2026-02-17 13:06:39 +01:00
Jeppe Bundgaard fd8b86987f Localize objects and enhance i18n integration
- Replaced hardcoded labels in `Bookings.vue`, `Orders.vue`, and `Vehicles.vue` with dynamic translations using `i18n`.
- Added comprehensive translation keys for `objects` in `en.json` and `da.json`.
- Improved multilingual support for objects metadata, columns, and labels.
2026-02-17 12:56:40 +01:00
Jeppe Bundgaard 665dc61d98 Localize global objects and extend i18n support
- Replaced hardcoded text in `ObjectsGlobal.vue` with dynamic translations using `i18n`.
- Centralized global translation keys in `en.json` and `da.json` under the `global` namespace.
- Improved multilingual support and maintainability for global object labels and actions.
2026-02-17 12:52:12 +01:00
Jeppe Bundgaard 33c64659c7 Localize admin and user navigation menus
- Replaced hardcoded navigation labels in `NavigationMenuItemsUser.vue`, `NavigationMenuItemsAdmin.vue`, and `NavigationMenuItemsGlobal.vue` with dynamic translations using `i18n`.
- Added new translation keys for navigation items in `en.json` and `da.json`, improving multilingual support.
2026-02-17 12:47:29 +01:00
Jeppe Bundgaard bb8ae993eb Localize user dashboard and enhance translation support
- Replaced hardcoded dashboard text in `UserDashboard.vue` with dynamic translations using `$t`.
- Added new translation keys in `en.json` and `da.json` for user dashboard actions, descriptions, and messages.
- Improved multilingual support for subuser actions, empty state messages, and contact information.
2026-02-17 12:44:45 +01:00
Jeppe Bundgaard 3f71fb85f4 Add language selector and localize user navigation menus
- Introduced a new `LanguageSelector` component for switching between languages with persisted user preference.
- Localized navigation items in `UserLeftMenu.vue` using translation keys in `en.json` and `da.json`.
- Updated `DesktopNavigationBuefy.vue` and `UserLeftMenu.vue` to include the `LanguageSelector` component.
2026-02-17 12:43:06 +01:00
Jeppe Bundgaard 7d3630fa62 Localize QR login and headers
- Replaced hardcoded text in `LoginQR.vue`, `DesktopHeader.vue`, `TabletHeader.vue`, and `GuestBookExteriorWash.vue` with dynamic translations using `$t`.
- Added new translation keys to `en.json` and `da.json` for improved multilingual support across login, QR scanning, and navigation headers.
- Updated `config.js` to adjust the `API_URL` for local development.
2026-02-17 12:38:56 +01:00
Jeppe Bundgaard 32a2725de2 Localize login form components
- Replaced hardcoded labels and button text in `LoginForm.vue` with dynamic translations using `$t`.
- Improved maintainability and multilingual support by introducing keys for customer number, password, login, login with QR code, and forgot password links.
2026-02-17 12:34:51 +01:00
Jeppe Bundgaard f15c07e43e Localize user dashboard and password reset pages
- Replaced hardcoded text with translations using `$t` across user dashboard components (`MyInvoices.vue`, `MyVehicles.vue`, `Home.vue`, `MyWash.vue`, `MyOrders.vue`, `MyBookings.vue`) and password reset pages (`PasswordResetPage.vue`, `PasswordResetConfirmationPage.vue`).
- Added new translation keys to `en.json` and `da.json`, enhancing multilingual support for titles, subtitles, and labels.
2026-02-17 12:31:04 +01:00
Jeppe Bundgaard c4deaa8e86 Localize customer creation and about us pages
- Updated `CustomerCreationPage.vue` and `AboutUsPage.vue` to use translations from `en.json` and `da.json`.
- Added relevant keys for customer creation, about us sections, and form labels in both languages.
- Improved maintainability and consistency by replacing hardcoded text with dynamic translations using `$t`.
2026-02-17 12:28:00 +01:00
copilot-swe-agent[bot]andjepp9350 1ed8a30826 Improve error messages based on code review feedback
Co-authored-by: jepp9350 <17977216+jepp9350@users.noreply.github.com>
2026-02-17 11:04:51 +00:00
copilot-swe-agent[bot]andjepp9350 5dc35d0a1b Add i18n translations to UserProfile.vue for email management dialogs
Co-authored-by: jepp9350 <17977216+jepp9350@users.noreply.github.com>
2026-02-17 11:01:58 +00:00
copilot-swe-agent[bot]andjepp9350 63a10dd5c4 Add i18n setup with vue-i18n, locale files, and LanguageSwitcher component
Co-authored-by: jepp9350 <17977216+jepp9350@users.noreply.github.com>
2026-02-17 10:57:31 +00:00
copilot-swe-agent[bot] 19be7ef797 Initial plan 2026-02-17 10:50:22 +00:00
Jeppe Bundgaard ecf65b47a1 Remove duplicate "Operatørlogin" navigation item from LayoutV2.vue 2026-02-16 00:09:49 +01:00
Jeppe Bundgaard 3e1f8ed6d6 Merge remote-tracking branch 'origin/master' 2026-02-16 00:09:34 +01:00
Jeppe Bundgaard 5ccfb5f050 Add "Operatørlogin" navigation item to LayoutV2.vue
- Added a new link for operator login to the navigation menu.
2026-02-16 00:09:18 +01:00
gold-dev 8ae010b55c Feat: Add operator login for mobile 2026-02-13 16:41:55 +02:00
Jeppe Bundgaard 702b599490 Add empty state for subusers without shortcut permissions
- Render a message in `UserDashboard` when subusers lack access to dashboard shortcuts, including instructions to contact the administrator.
- Introduced `hasAnyShortcutPermission` computed property to check subuser permissions.
2026-02-12 18:15:34 +01:00
Jeppe Bundgaard c24eeca6b4 Add no-permissions message to SubuserGrantSelector for subusers without grants
- Display a warning message when no grants are available, with compact and expanded layouts.
- Updated styles to support new no-permissions notification design.
2026-02-12 18:14:34 +01:00
Jeppe Bundgaard 2156d55650 Update subuser terminology to "chauffør" and refine navigation item visibility logic
- Replaced "subuser" with "chauffør" in `UserProfile.vue` for improved role specificity.
- Enhanced navigation item visibility in `DesktopNavigationBuefy.vue` by adding permission-based checks and refining hidden item filtering logic.
2026-02-12 18:10:54 +01:00
Jeppe Bundgaard ba63aab53b Enhance subuser profile management with editable name and email fields
- Added support for subusers to update their name and email via modals in `UserProfile.vue`.
- Updated `SubuserGrantSelector` styles with improved layout and accessibility for compact mode.
- Refined `SessionUser` logic to conditionally render visible profile attributes and actions based on subuser status.
- Adjusted navigation permissions to remove redundant `user_invoices` permission check.
2026-02-12 18:07:02 +01:00
Jeppe Bundgaard caff98690c Enhance SubuserGrantSelector with compact dropdown mode and integrate into user profile
- Added dropdown-based compact layout for `SubuserGrantSelector` to improve subuser grant selection UX.
- Integrated `SubuserGrantSelector` into `UserProfile.vue` for subuser-specific grant management.
- Updated styles to support compact mode, including dynamic icon and text transformation based on environment.
2026-02-12 18:01:32 +01:00
Jeppe Bundgaard 22f9a486c6 Add compact prop to SubuserGrantSelector and integrate into headers
- Introduced a `compact` prop in `SubuserGrantSelector.vue` for adjustable layout styles.
- Updated `MobileHeader.vue` and `DesktopHeader.vue` to utilize `compact` mode for streamlined grant selection UI in navigation bars.
- Refined styles for `compact` mode, including reduced spacing and smaller select elements.
2026-02-12 17:59:06 +01:00
Jeppe Bundgaard 85477916d5 Integrate SubuserGrantSelector into header components and enhance navigation permissions
- Removed permissions display from `SubuserGrantSelector.vue` template.
- Added `SubuserGrantSelector` to both `MobileHeader.vue` and `DesktopHeader.vue` for subuser-specific navigation.
- Updated user menu items to include permissions for enhanced access control.
2026-02-12 17:57:12 +01:00
Jeppe Bundgaard b173b14e86 Enhance subuser grant handling and permissions
- Update `SubuserGrantSelector` to disable the default option and display customer names if available.
- Refine `UserDashboard` to conditionally render cards based on `SessionUser` permissions.
- Add `X-Customer-Number` header in authenticated requests for subuser-specific actions.
2026-02-12 17:52:59 +01:00
Jeppe Bundgaard 66c9936d32 Expand subuser grant handling and enforce selection before dashboard access
- Enhanced `SessionUser` permissions logic to validate selected grant for subusers.
- Updated `UserDashboard` to require subusers to select a grant before accessing dashboard content.
- Integrated `SubuserGrantSelector` component for dynamic grant management.
2026-02-12 17:40:30 +01:00
Jeppe Bundgaard 316cca9ca3 Expand subuser permissions and adjust navigation logic
- Updated navigation permissions to include specific grants for subusers, ensuring appropriate access to user-related pages.
- Enhanced `SessionUser.getName` to display subuser's name when applicable.
- Refined navigation logic to ensure subusers can access user menus on relevant pages.
2026-02-12 17:33:42 +01:00
Jeppe Bundgaard 393a46ae34 Add subuser role with hierarchy adjustments and redirection logic
- Introduced "subuser" role with level 2 hierarchy and access control.
- Added reactive subuser redirection logic based on status changes.
- Adjusted hierarchy levels for "superuser" (4) and "admin" (3).
2026-02-12 17:27:14 +01:00
Jeppe Bundgaard b41d839258 Introduce Subuser Grant Selector and Chauffør Login Integration
- Added `SubuserGrantSelector.vue` for subuser grant management with dynamic customer selection and associated permissions display.
- Implemented `SubuserLogin.vue` and supporting `SubuserLoginForm.vue` to enable chauffeur login functionality.
- Enhanced `SessionUser` to include grant selection logic, with methods for managing selected customer numbers, persisting selections, and header handling for authenticated requests.
- Integrated new `subuserlogin` route into `router.js`.
- Updated `authenticatedRequest.vue` to include `X-Customer-Number` header for subuser-specific requests.
2026-02-12 17:17:22 +01:00
Jeppe Bundgaard 1f27f3b069 Add subuser session management and authentication support
- Introduced subuser-specific session data handling, including `getSubuserSessionData`.
- Added `authenticateSubuser` to enable password-based authentication for subusers.
- Updated `SessionUser` to support subuser sessions with fields like `isSubuser` and `subuser` details.
- Adjusted logic for clearing and initializing sessions to differentiate subuser and classic user contexts.
- Enhanced OpenAPI spec to include subuser-specific endpoints and permissions, updating customer-targeting behavior.
2026-02-12 17:00:51 +01:00
Jeppe Bundgaard ec292bce73 Update labels and placeholders to replace "Underbruger" with "Chauffør" across Subuser Grants components. 2026-02-12 16:45:11 +01:00
Jeppe Bundgaard 48896d2b83 Update navigation to replace subuser category with direct link to 'Chauffører' grants page. 2026-02-12 16:07:14 +01:00
Jeppe Bundgaard 6829955f2b Refactor permission group display in SubuserGrantPermissionNodes.vue to wrap titles and descriptions within a styled content divider. 2026-02-12 15:56:00 +01:00
Jeppe Bundgaard ec93989002 Refactor SubuserGrantsTable.vue to use dynamic labels for column headers and parse functions, remove unused columns, and update SubuserGrants.vue labels. 2026-02-12 15:48:36 +01:00
Jeppe Bundgaard 08a6f4fefd Reintroduce editable columns in SubuserGrantsTable.vue with EditableTableColumn and implement showEditObjectFieldForm in SubuserGrants.vue 2026-02-12 14:32:06 +01:00
Jeppe Bundgaard ccf4e139f6 Remove editable column for subuser grants in SubuserGrantsTable.vue and replace with static status tag rendering. 2026-02-12 14:25:55 +01:00
Jeppe Bundgaard cb1e007fda Merge remote-tracking branch 'origin/master' 2026-02-12 14:16:57 +01:00
Jeppe Bundgaard 7c8f79f13b Implement interactive modal for editing subuser grant permissions using SubuserGrantPermissionNodes.vue and integrate SweetAlert2 with Vue app creation. 2026-02-12 14:11:15 +01:00
Jeppe Bundgaard d2e0467605 Mark permissions prop in SubuserGrantPermissionNodes.vue as required 2026-02-12 13:20:55 +01:00
Jeppe Bundgaard 8805247dbc Add support for editing subuser grant permissions, integrate permission nodes selector component, and update relevant UI and backend logic 2026-02-12 13:16:52 +01:00
Jeppe Bundgaard b79ce07cda Add editable column for subuser grants and implement permission nodes selector component 2026-02-12 12:34:26 +01:00
gold-dev 44f78e817e Feat: Layout index for header mobile menu 2026-02-12 10:43:08 +02:00
Jeppe Bundgaard 1666c3de45 Expand openapi.yaml with listSubusers and getSubuser endpoints for subuser management, including query parameters, response structure, and error handling definitions. 2026-02-11 18:00:51 +01:00
Jeppe Bundgaard da0bae5bf2 Add created/updated timestamps to SubuserGrantsTable; define new SubuserGrants object structure 2026-02-11 17:58:27 +01:00
Jeppe Bundgaard b7bc4ec907 Add subuser and grant management navigation, expand openapi.yaml with subuser-related definitions, and update permissions in SubuserGrants.vue. 2026-02-11 17:53:54 +01:00
Jeppe Bundgaard c9fee21d24 Add subuser and grant management views with pagination components and routing integration 2026-02-11 17:38:36 +01:00
Jeppe Bundgaard 40a6a4f664 Add subuser, grant, and passkey management endpoints to openapi.yaml.
Expanded the API specification with endpoints for subuser management, including setup, authentication, and permission grants, as well as account security passkey handling.
2026-02-11 17:28:10 +01:00
Jeppe Bundgaard 710c05bf52 Update phone number on AboutUsPage.vue for contact section. 2026-02-11 15:38:58 +01:00
Jeppe Bundgaard 0b7f79d59c Replace team member images and update corresponding references in AboutUsPage.vue. Add new assets mads-scaled.png and jeppe-scaled.png. 2026-02-11 15:36:29 +01:00
Jeppe Bundgaard 5606f2d5f7 Merge remote-tracking branch 'origin/master' 2026-02-03 15:08:57 +01:00
Jeppe Bundgaard 9172a95221 Refine error handling in CollectedOrderInvoicesCompareDataStructure.vue to extract and process differences from error responses. 2026-02-03 14:28:35 +01:00
gold-dev 4af3f2a997 Feat: Slides 4, 8 2026-02-03 15:15:19 +02:00
Jeppe Bundgaard 314462345d Add difference calculation and display to MassDataInsertTable.vue and refine response handling in CollectedOrderInvoicesCompareDataStructure.vue 2026-02-03 13:57:06 +01:00
Jeppe Bundgaard a08ef03bb8 Add onClose prop to MassDataInserterModal.vue for custom modal close handling and minor adjustment to CollectedOrderInvoicesCompareDataStructure.vue structure definition 2026-02-03 13:51:41 +01:00
Jeppe Bundgaard 59d1272e98 Add total_net_amount field to CollectedOrderInvoices structure and implement import/export functionality in MassDataInserterModal.vue with JSON support 2026-02-03 13:35:18 +01:00
Jeppe Bundgaard 71c549df87 Add "Remove Passing Items" functionality to MassDataInserterModal.vue with UI button, data filtering, and progress updates 2026-02-03 13:27:35 +01:00
Jeppe Bundgaard b779d9b479 Add re-run functionality to MassDataInserterModal.vue with progress reset, validation for re-run conditions, and UI button integration 2026-02-03 13:26:21 +01:00
Jeppe Bundgaard 55e85215ce Add multi-page invoice fetching to mass comparison flow in CollectedOrderInvoicesListPagination.vue with user confirmation for large datasets and improved error handling. 2026-02-03 13:16:40 +01:00
Jeppe Bundgaard 931b473efe Add CollectedOrderInvoicesCompareDataStructure.vue and refactor mass comparison flow for collected invoices with E-conomic. Expand pagination options and improve error/message handling. 2026-02-03 12:53:14 +01:00
Jeppe Bundgaard ec1114b1cd Add comparison endpoint and mass comparison feature for collected invoices with E-conomic in openapi.yaml and CollectedOrderInvoicesListPagination.vue. 2026-02-03 12:34:19 +01:00
Jeppe Bundgaard 344acf4a13 Add CollectedOrderInvoicesListPagination and CollectedOrderInvoicesDefaultTable components for handling and displaying collected order invoices with advanced filtering and pagination options 2026-02-03 12:24:13 +01:00
Jeppe Bundgaard 95470794f5 Add QR routes and components for guest actions including new customer registration, driver setup, and booking creation 2026-02-02 11:17:53 +01:00
Jeppe Bundgaard ae7c1dac5b Merge remote-tracking branch 'origin/master' 2026-02-02 10:31:13 +01:00
Jeppe Bundgaard e15e94545c Increase transaction history pagination limit to 1000 in PosDepartmentStep1MobileTransactionHistory.vue. 2026-02-02 10:30:34 +01:00
gold-dev a7d4373abc Feat: Slides 2, 3, 5, 6 2026-02-02 10:49:05 +02:00
Jeppe Bundgaard 14be8db1a6 Add departmental progress tracking in DepartmentGoals.vue with enhanced UI elements and new methods to calculate daily targets and business days. 2026-01-29 17:56:44 +01:00
Jeppe Bundgaard 194c5a8703 Merge remote-tracking branch 'origin/master' 2026-01-28 20:07:17 +01:00
Jeppe Bundgaard 083c53e75f Update goal progress display in DepartmentGoals.vue to show value/target format and adjust progress bar calculations 2026-01-28 20:07:05 +01:00
gold-dev bffde4efc7 Feat: Styleguide 2026-01-28 12:04:45 +02:00
Jeppe Bundgaard f8c0925fef Add test progress alert functionality to department goals, including UI integration and API call 2026-01-27 09:19:34 +01:00
Jeppe Bundgaard be37edaa15 Add endpoint to send test progress alerts for department goals in OpenAPI documentation 2026-01-27 09:12:58 +01:00
Jeppe Bundgaard 28357df51d Add time picker for progress_alert_time_of_day in GoalFormModal with local time handling and timezone offset adjustment 2026-01-27 09:05:40 +01:00
Jeppe Bundgaard 7775f58a29 Add enhanced alert configuration options to GoalFormModal and update display in DepartmentGoals.vue 2026-01-27 08:57:45 +01:00
Jeppe Bundgaard 1d61efbe73 Extend OpenAPI documentation with new progress_alert_* fields for enhanced alert configuration options. 2026-01-27 08:53:32 +01:00
Jeppe Bundgaard 28e7b5f23e Add progress_alert_frequency field in GoalFormModal, handle its display in DepartmentGoals.vue, and update related UI elements. 2026-01-26 20:14:04 +01:00
Jeppe Bundgaard d812e57be7 Add "label" field to goal criteria in GoalFormModal, update fallback naming in DepartmentGoals.vue, and extend OpenAPI documentation 2026-01-26 19:47:50 +01:00
Jeppe Bundgaard a68d7a0b95 Improve caching strategy across ObjectsGlobal methods and update fallback goal naming logic in DepartmentGoals.vue. 2026-01-26 18:56:16 +01:00
Jeppe Bundgaard 39b71e5f2b Refactor GoalFormModal to replace user search with customer search functionality, update related filtering logic, and improve progress calculation in DepartmentGoals.vue. 2026-01-26 18:41:02 +01:00
Jeppe Bundgaard 8257568573 Enhance GoalFormModal with debounced search for users and products, improved filtering logic, and support for fetching missing details during editing. 2026-01-26 18:28:57 +01:00
Jeppe Bundgaard 46afda9fb7 Refactor goal management modals: merge GoalCreateModal and edit functionality into reusable GoalFormModal. Update integration in DepartmentGoals.vue. 2026-01-26 18:24:58 +01:00
Jeppe Bundgaard 09b0b6c80b Add GoalCreateModal component with creation workflow and integrate it into DepartmentGoals.vue 2026-01-26 18:12:17 +01:00
Jeppe Bundgaard 159470d921 Enhance DepartmentGoals.vue with dynamic goals fetching, filtering, and improved UI/UX for goal management 2026-01-26 18:05:15 +01:00
Jeppe Bundgaard 4b158042af Add "Goals" module with navigation, routing, and basic UI structure 2026-01-26 18:02:41 +01:00
Jeppe Bundgaard bf16481b29 Add Goals module with CRUD functionality and OpenAPI documentation 2026-01-26 17:55:52 +01:00
Jeppe Bundgaard 9c236afdeb Enable drag-and-drop reordering of departments with live priority updates 2026-01-26 14:06:37 +01:00
Jeppe Bundgaard 40846dcf2a Add order priority column to DepartmentsTable for future draggable implementation 2026-01-26 13:53:04 +01:00
Jeppe Bundgaard d9516f0e17 Add order_priority field to Departments.vue with support for creation and updates 2026-01-26 13:50:40 +01:00
Jeppe Bundgaard 3e5c5b893c Redirect authenticated users on the landing page and increment TWA app version to 6 2026-01-26 12:40:00 +01:00
gold-dev 9ba2141d42 Feat: reset password + login improvements 2026-01-22 16:03:12 +02:00
gold-dev a51fb8baff Feat: slides 6, 8, 9 2026-01-22 14:22:27 +02:00
Jeppe Bundgaard 1daa7d6660 Simplify date range and department selection logic in DepartmentDailyReport. 2026-01-22 12:59:25 +01:00
Jeppe Bundgaard b99fdd620d Add query params handling for date range and department selection in DepartmentDailyReport
- Enhance department selection logic to filter accessible departments.
- Automatically update URL query parameters based on user selections.
- Add loading state to department navigation buttons.
2026-01-22 12:54:07 +01:00
Jeppe Bundgaard 777dd9ef3c Remove unused navigation menu item for self-serve questions in admin view 2026-01-22 09:00:11 +01:00
Jeppe Bundgaard ac2d71a202 Add sorting by datetime for OrderBookingsPagination and enhance filter initialization 2026-01-22 08:54:22 +01:00
Jeppe Bundgaard 70815ad401 Update DepartmentModulesDisplay to fetch self-serve status on mount and improve toggle switch binding 2026-01-21 10:39:35 +01:00
Jeppe Bundgaard a2aede936e Add self-serve status toggle to DepartmentModulesDisplay and API endpoints for enable/disable actions 2026-01-21 10:32:55 +01:00
Jeppe Bundgaard 28d01cb4dd Add toggle switch to DepartmentModulesDisplay for wash lane settings 2026-01-21 10:27:01 +01:00
Jeppe Bundgaard 1bd3feab66 Remove Privacy Policy link from LayoutV2 navigation menu 2026-01-21 09:54:08 +01:00
Jeppe Bundgaard bf573a9ca9 Add Privacy Policy page and navigation links 2026-01-21 09:49:58 +01:00
Jeppe Bundgaard 825219e264 Update TWA manifest with incremented app version and version code 2026-01-21 09:35:01 +01:00
Jeppe Bundgaard 670324c7fb Add Bubblewrap TWA build and install instructions to README.md 2026-01-21 09:09:09 +01:00
Jeppe Bundgaard 0cf1cc4498 Update TWA manifest with new configurations, asset links, and updated app version 2026-01-21 09:05:59 +01:00
Jeppe Bundgaard c3f99d15c7 Update TWA manifest with new package ID, host, app name, and asset URLs. 2026-01-20 16:20:48 +01:00
Jeppe Bundgaard 7371d235b7 Add PWA support with manifest and TWA integration. Update Vite PWA plugin configuration and scripts for building and updating TWA. 2026-01-20 16:14:50 +01:00
Jeppe Bundgaard b828fb9c77 Remove "force nearest department" superuser controls and update related logic in MyWashStart.vue. 2026-01-20 15:44:34 +01:00
Jeppe Bundgaard e29813e1a3 Add superuser controls for manually forcing nearest department in MyWashStart.vue. 2026-01-20 15:39:10 +01:00
Jeppe Bundgaard 21ae3b23b7 Display loading message in MyWashStart.vue when no vehicle types are available for selection. 2026-01-20 15:18:11 +01:00
Jeppe Bundgaard c0ac599776 Handle empty availableProductIds in MyWashStart.vue by displaying a warning message when self-serve is unavailable. 2026-01-20 15:14:14 +01:00
Jeppe Bundgaard dffd93fe14 Force nearest department to saved one if wash is in progress; add location-based department evaluation in MyWashStart.vue. 2026-01-20 15:10:31 +01:00
Jeppe Bundgaard 52ae521ad6 Comment out excessive logging in MyWashStart.vue to reduce noise during runtime and improve readability. 2026-01-20 15:08:00 +01:00
Jeppe Bundgaard 809540e0ff Merge remote-tracking branch 'origin/master' 2026-01-20 14:58:48 +01:00
Jeppe Bundgaard 781b419a28 Update step labels in MyWashStart.vue for clearer user guidance and streamline step visibility logic 2026-01-20 14:58:13 +01:00
Jeppe Bundgaard ec5ff41bfa Fix inconsistent spacing and indentation in MyWashStart.vue for improved readability and maintainability. 2026-01-20 14:35:11 +01:00
gold-dev 4c9199e66d Feat: slides 3-5 partial 6 2026-01-20 15:31:13 +02:00
gold-dev 74f35b3008 Merge remote-tracking branch 'origin/master' 2026-01-20 15:29:02 +02:00
Jeppe Bundgaard df716ccbaf Comment out unused question description in MyWashStart.vue for cleaner UI. 2026-01-20 13:26:58 +01:00
Jeppe Bundgaard e55b21acae Format self-serve object names to include department, lane, and product details for improved clarity and consistency across questions, tasks, and conditions. 2026-01-20 12:34:43 +01:00
gold-dev 00b9932c40 Feat: add dev-dist to gitignore 2026-01-20 13:28:04 +02:00
gold-dev 6e02618a38 Feat: Slides 1-2 2026-01-20 13:27:35 +02:00
Jeppe Bundgaard 943a143a19 Optimize condition and task name handling by integrating department and product names, add global cache for improved request efficiency, and streamline object fetching methods. 2026-01-20 12:24:44 +01:00
Jeppe Bundgaard d0ddf29235 Refactor self-serve condition and task handling to use formatted names, streamline setters, and optimize attachment methods 2026-01-20 12:21:01 +01:00
Jeppe Bundgaard 80c4d16eff Restrict vehicle type selection and validation in MyWashStart.vue and apply product-based filtering in VehicleTypeSelection.vue. 2026-01-20 12:03:34 +01:00
Jeppe Bundgaard fbf55c6d18 Restrict vehicle type selection in MyWashStart.vue to available product IDs 2026-01-20 11:41:48 +01:00
Jeppe Bundgaard 11dde044e0 Refactor self-serve logic to simplify filters, add task prioritization, and improve attachment handling 2026-01-20 11:12:16 +01:00
Jeppe Bundgaard 0bef66201b Add logging for task attachment fetching and enhance UI with data-fetching status indicators in self-serve modals 2026-01-20 11:04:52 +01:00
Jeppe Bundgaard c4ab40c76f Disable debug mode by default and hide debug button in MyWashStart.vue 2026-01-19 14:56:43 +01:00
Jeppe Bundgaard 5de72344f8 Ensure user ownership of the vehicle before saving conditions in MyWashStart.vue
- Added `doesUserVehicleExist` check to validate ownership before saving conditions to the server.
- Skipped server-side condition save for unowned vehicles with an appropriate log message.
2026-01-19 14:51:08 +01:00
Jeppe Bundgaard 241efe34aa Add detailed logging for restoration and saving processes in MyWashStart.vue
- Introduced `instanceId` for better logging differentiation during multiple component instances.
- Enhanced restoration logic with stricter protections against unintended state changes.
- Updated `saveProgress` to handle triggers, destruction states, and invalid step resets more robustly.
- Improved logging visibility for debugging restoration, saving, and flow issues.
- Streamlined event handling for `beforeunload` and `onUnmounted` scenarios.
2026-01-19 14:46:01 +01:00
Jeppe Bundgaard 017737df19 Enhance wash restoration logic in MyWashStart.vue
- Added a timeout-based restoration process (`isRestoring`) to handle incomplete or timed-out washes.
- Prevented unintended state changes during restoration by refining watchers and condition checks.
- Introduced a 2-hour expiration for saved progress to avoid restoring outdated data.
2026-01-19 14:19:31 +01:00
Jeppe Bundgaard 10fe180ad2 Split "Questions" and "Tasks" into separate steps in MyWashStart.vue to improve navigation and step clarity
- Introduced a dedicated `TASKS` step and adjusted `currentStep` logic accordingly.
- Updated `onStartWash` calls, validation, and clickable step logic to align with the new step structure.
- Refined UI by separating "Questions" and "Tasks" into distinct step components and improving visibility conditions.
2026-01-19 14:15:16 +01:00
Jeppe Bundgaard ac4f3912ff Streamline wash start flow and improve UI logic in MyWashStart.vue
- Simplified step logic with updated `onStartWash` calls and condition checks.
- Added watcher for `allVisibleQuestionsAnswered` to trigger wash start dynamically.
- Enhanced `Confirm and Start Wash` button visibility logic to account for ongoing washes.
- Refined answer and task reset logic to prevent unintended state changes during washes.
2026-01-19 14:09:14 +01:00
Jeppe Bundgaard 632cd83a4a Add "Confirm and Start Wash" button in MyWashStart.vue
- Introduced `onConfirmAndStartWash` method to handle wash start without leaving the questions step.
- Added a conditional button to allow users to confirm and start the wash when all visible questions are answered.
2026-01-19 13:47:56 +01:00
Jeppe Bundgaard 35ffc82aa0 Add editAnswers state management and UI updates in MyWashStart.vue
- Introduced `editAnswers` state to track and toggle edit mode for answers.
- Added watcher to reset `editAnswers` when navigating away from the `QUESTIONS` step.
- Enhanced UI with "Edit Answers" and "Finish" buttons for improved user control.
- Updated visibility and validation logic to account for `editAnswers` state.
2026-01-19 13:42:19 +01:00
Jeppe Bundgaard 989ee5acce Combine "Questions" and "Tasks" steps for streamlined navigation in MyWashStart.vue
- Merged `QUESTIONS` and `TASKS` steps into a single step for improved user flow and consistency.
- Adjusted logic to dynamically display "Questions & Tasks" based on visibility and progress.
- Refactored navigation and validation to simplify step transitions and state handling.
- Consolidated UI for tasks and questions, including conditional visibility and improved answer tracking.
2026-01-19 13:36:05 +01:00
Jeppe Bundgaard 603bef2d44 Simplify rule evaluation logic and condition filtering in useSelfServeLogic
- Removed unnecessary checks for empty sub-rules, allowing rule evaluations to fail gracefully.
- Standardized `parseInt` usage to improve consistency across condition and task filtering.
- Enhanced filtering logic to handle default values for departments, products, and lanes more flexibly.
2026-01-19 13:04:27 +01:00
Jeppe Bundgaard d770da869f Add debug UI for task visibility and improve lane filtering logic
- Added a debug section in `MyWashStart.vue` and `SelfServeTryModal.vue` to display task visibility, conditions, and rule evaluations for enhanced testing and visibility.
- Updated lane filtering logic in `useSelfServeLogic` to handle `null` and missing lane values more consistently.
2026-01-19 12:41:25 +01:00
Jeppe Bundgaard 479962e755 Refactor Self-Serve logic into composable
- Extracted reusable Self-Serve logic into `useSelfServeLogic` composable.
- Replaced redundant data fetching, rule evaluation, and attachment handling logic in `MyWashStart.vue` and `SelfServeTryModal.vue` with `useSelfServeLogic`.
- Improved maintainability and consistency by consolidating shared functionality.
- Added debug UI with conditions and rule evaluations to `MyWashStart.vue` for enhanced visibility and testing.
2026-01-19 12:37:46 +01:00
Jeppe Bundgaard e65f4b2fc6 Refactor filtering and condition rule evaluation across Self-Serve components
- Standardized `filters` usage in `get.all` methods for `MyWashStart.vue`, `SelfServeTryModal.vue`, and `ObjectsGlobal.vue`.
- Updated rule evaluation logic to return `false` for empty condition rules and simplified sub-rule handling.
- Improved filter transformation to handle arrays in the `ObjectsGlobal.vue` API for more flexible requests.
- Consolidated duplicate logic for conditions and tasks across modules.
2026-01-19 12:07:10 +01:00
Jeppe Bundgaard 85b84df7f7 Refactor MyWashStart.vue to enhance wash flow and step navigation
- Simplified `currentStep` assignment by removing redundant `washInProgress` checks.
- Updated `onStartWash` to accept a `targetStep` parameter for better step control.
- Enhanced clickable steps logic to handle ongoing washes consistently.
- Added sticky header UI for "Wash In Progress" state.
- Fixed button disabling logic in navigation for ongoing washes.
2026-01-19 11:51:48 +01:00
Jeppe Bundgaard e7ccb29b05 Add task management and improve navigation flow in MyWashStart.vue and SelfServeTryModal.vue
- Added task completion tracking with `completedTasks` state and checkbox UI.
- Refactored step navigation flow in `MyWashStart.vue` to include `QUESTIONS` and `TASKS` steps.
- Updated validation and data persistence logic for additional steps.
- Enhanced task UI in `SelfServeTryModal.vue` with completion checkboxes and dynamic styling for completed tasks.
2026-01-19 11:35:18 +01:00
Jeppe Bundgaard 3ecaee7aed Add image preview and download link support for task attachments in Self-Serve components
- Updated `MyWashStart.vue` and `SelfServeTryModal.vue` to fetch and display download links for image attachments.
- Enhanced UI to show inline image previews for attachments with valid download links.
- Introduced `isImage` utility function to identify image formats.
2026-01-19 11:23:42 +01:00
Jeppe Bundgaard 2435a85ff9 Add attachment handling and password reset endpoints to Self-Serve module
- Introduced attachment fetching and downloading logic in `MyWashStart.vue` and `SelfServeTryModal.vue`.
- Enhanced task-related UI to display and handle attachments.
- Added password reset API endpoints (`request`, `validate`, `set`) in `openapi.yaml`.
2026-01-19 11:19:52 +01:00
Jeppe Bundgaard f2e82f8812 Extend Self-Serve API with new commands, schemas, and configuration support
- Added `SelfServeLaneStatus` and `SelfServeConfig` schemas to improve data handling and API responses.
- Enhanced `/modules/self-serve/lane/command` to support `RESERVE`, `RELEASE`, and `license_plate`.
- Updated `/modules/self-serve/config` endpoints to include request/response schemas for configuration management.
2026-01-19 10:54:41 +01:00
Jeppe Bundgaard 143ca80eb4 Add license plate and customer number watchers to fetch previous answers in MyWashStart.vue
- Introduced `fetchPreviousAnswers` function to retrieve and populate previous answers based on license plate and customer number.
- Added logic to clear answers state when license plate or customer number changes.
- Updated watchers to trigger data fetching and ensure answers reflect the latest state.
2026-01-15 12:46:01 +01:00
Jeppe Bundgaard 40884fc9e6 Refactor MyWashStart.vue and SelfServeTryModal.vue to optimize rule fetching logic
- Replaced pre-fetching of all condition rules with conditional fetching based on relevant condition IDs.
- Updated question rendering in `MyWashStart.vue` to use `WhiteBoxCard` for improved UI structure.
2026-01-15 12:40:48 +01:00
Jeppe Bundgaard 52529fec3e Add filtering support to get.all methods and enhance Self-Serve data fetching logic
- Updated `get.all` methods across multiple Self-Serve components (`Questions`, `Conditions`, `Tasks`, `ConditionRules`, `Vehicles`) to accept optional filters.
- Refactored `MyWashStart.vue` and `SelfServeTryModal.vue` to leverage filters for context-specific data fetching (e.g., department, product, lane).
2026-01-15 11:42:14 +01:00
Jeppe Bundgaard 2c7d69c458 Add vehicle condition confirmation step in MyWashStart.vue
- Introduced new "Vehicle Configuration" step with dynamic question and task visibility.
- Enhanced `onStartWash` to persist user answers before starting the wash.
- Added `answers` state and related handling for vehicle condition questions.
- Updated navigation logic and UI to incorporate vehicle confirmation.
2026-01-15 11:31:09 +01:00
Jeppe Bundgaard 4bac68f098 Refactor wash commands in MyWashStart.vue to consolidate logic and improve reusability
- Introduced `executeSelfServeCommand` for shared command execution logic across wash actions.
- Replaced custom command handling in `onStartWash`, `onStopWash`, `onReserveWashLane`, and `onReleaseWashLane` with `executeSelfServeCommand`.
- Added new vehicle confirmation step and related UI updates.
- Adjusted default `forceNearestDepartmentEvaluationId` to `6`.
- Updated default behavior and validation logic in `VehicleDataStructure.vue`.
2026-01-15 11:06:28 +01:00
Jeppe Bundgaard 3cf3dc4146 Update forceNearestDepartmentEvaluationId default to 0 in MyWashStart.vue 2026-01-14 21:30:13 +01:00
Jeppe Bundgaard c1873d76c9 Merge remote-tracking branch 'origin/master' 2026-01-14 21:20:10 +01:00
Jeppe Bundgaard 3618c570b5 Update DesktopNavigationBuefy and Viewport layout logic for better responsiveness
- Removed unnecessary visibility check for `DesktopNavigationBuefy`.
- Introduced `watch` in `Viewport` to dynamically update header height on state changes.
- Adjusted grid column spans to handle hidden states and ultra-wide screen devices.
2026-01-13 12:40:02 +01:00
Jeppe Bundgaard b637b9dc5f Add SelfServeVehicleConditions feature to Self-Serve module
- Introduced `SelfServeVehicleConditions` object and related CRUD operations for managing vehicle-specific conditions.
- Added pagination, table, and detail views for managing vehicle conditions within the Department dashboard.
- Updated routing and navigation to include `selfservevehicleconditions` path and entry points.
- Extended `openapi.yaml` to document new endpoints for vehicle condition management.
2026-01-13 12:35:51 +01:00
gold-dev 21e763b25a Feat: remove unused sections from landing page 2026-01-12 18:40:25 +02:00
gold-dev b59d4cff01 Feat: Set mx auto for form 2026-01-12 18:39:17 +02:00
gold-dev 4300f5c646 Feat: Fix sticky bug for employee login 2026-01-12 18:37:10 +02:00
gold-dev 2b7f29b6ef Feat: New pages for 202512 - truckwash.io slides 1-9 2026-01-12 18:33:08 +02:00
Jeppe Bundgaard 70029d72d9 Refactor ActionSettingsWheelButton to adjust lane link behavior based on device type 2026-01-12 10:53:58 +01:00
gold-dev 1749f52f24 Feat: Change styles for 202512 - truckwash.io Set button same width 2026-01-09 16:58:31 +02:00
Jeppe Bundgaard a6867643fc Merge remote-tracking branch 'origin/master' 2026-01-09 15:41:18 +01:00
Jeppe Bundgaard 4ea93ff0c8 Extend SelfServeConditionRules with support for nested evaluation of IS_TRUE_OR_ANY_TRUE and IS_FALSE_OR_ANY_FALSE rule types 2026-01-09 15:26:38 +01:00
Jeppe Bundgaard 36b60038b9 Enhance SelfServeTryModal with tooltips and dynamic rule evaluation
- Added `BTooltip` from Buefy for displaying detailed information about conditions and rules.
- Implemented `getRuleTypeLabel` and `getConditionById` methods for improved condition and rule handling.
- Updated UI to include tooltips with rule descriptions, evaluation states, and fallback text for missing rules.
2026-01-09 15:08:33 +01:00
Jeppe Bundgaard b20953e51b Add IS_TRUE_OR_ANY_TRUE and IS_FALSE_OR_ANY_FALSE rule types to condition evaluation
- Extended `SelfServeConditionRules` and its UI to support new rule types.
- Updated `openapi.yaml` to document the newly added options.
- Refactored `evaluateCondition` logic to handle nested evaluation with added rule types.
2026-01-09 15:03:52 +01:00
Jeppe Bundgaard 0591d39a1d Add "Prøv" button and lane-specific filtering to SelfServeTryModal
- Updated `DepartmentWashLane` to include a "Prøv" button for opening `SelfServeTryModal`.
- Enhanced modal logic to support filtering by department, vehicle type, and lane.
- Improved UI with lane-specific context in the modal's title and dynamic data handling.
2026-01-09 14:33:01 +01:00
Jeppe Bundgaard 66307a9aad Add SelfServeTryModal to DepartmentSelfServeQuestions and enable vehicle-type-specific test flows
- Introduced `SelfServeTryModal` component for handling department and vehicle-type-specific test flows.
- Added a "Prøv" button in `DepartmentSelfServeQuestions` for opening the modal.
- Implemented logic for fetching and filtering questions, conditions, rules, and tasks based on department and vehicle type.
- Enhanced UI to support answering questions, evaluating conditions, and managing tasks within the modal.
2026-01-09 14:29:14 +01:00
Jeppe Bundgaard 32e557a9df Add IS_TRUE_OR_NOT_SET and IS_FALSE_OR_NOT_SET options to SelfServeConditionRules 2026-01-09 14:22:25 +01:00
Jeppe Bundgaard 585e56a87f Add IS_SET and IS_NOT_SET options to SelfServeConditionRules and update UI with new rule creation button
- Extended condition rules with `IS_SET` and `IS_NOT_SET` options.
- Added "Tilføj betingelsesregel" button in `SelfServeConditionRulesModal` for enhanced rule management.
2026-01-09 14:13:56 +01:00
Jeppe Bundgaard 1c13e2c80d Add condition_id support to SelfServeConditions, SelfServeConditionRules, and related components
- Renamed `question_condition` to `condition_id` for consistency across `SelfServeConditions`, `SelfServeTasks`, and `SelfServeQuestions`.
- Introduced CRUD operations for `SelfServeConditionRules` with logic for managing condition rules.
- Enhanced existing components to support `condition_id`, including dynamic filtering and table parsing.
- Updated UI elements to reflect changes, including new rule creation options and improved field handling for `condition_id`.
2026-01-09 14:08:53 +01:00
Jeppe Bundgaard aaa0298bae Rename question_condition to condition_id in SelfServeQuestions for consistency and update related methods, parsing, and table logic 2026-01-09 13:58:30 +01:00
Jeppe Bundgaard b1ffa45bc5 Rename question field to condition_id in SelfServeTasks for consistency and update related filtering, parsing, and table logic 2026-01-09 13:54:20 +01:00
Jeppe Bundgaard cecd7eb64a Add modal for managing condition rules in SelfServeConditionsTable
- Replaced navigation link with a modal for managing condition rules.
- Added `SelfServeConditionRulesModal` component with rule creation and pagination support.
- Improved user interaction with dynamic modal handling (`openModal`, `closeModal`).
2026-01-09 13:28:19 +01:00
Jeppe Bundgaard b2cac639ce Use loose equality (==) instead of strict equality (===) in getLockedValue comparison. 2026-01-09 13:24:07 +01:00
Jeppe Bundgaard 672e6407fd Add onchange support to SelfServeConditionRules for dynamic input handling
- Implemented asynchronous `onchange` events in `creation` config to dynamically update fields based on selected object type.
- Enhanced modal behavior in `ObjectsGlobal` to attach and trigger `onchange` events for configured inputs during initialization.
- Improved user experience with auto-filled `name` and `description` fields for questions and tasks.
2026-01-09 13:10:42 +01:00
Jeppe Bundgaard 67e00e2c99 Add condition_id support to SelfServeConditions with dynamic filtering and table integration
- Introduced `condition_id` field for managing conditional logic in `SelfServeConditions`.
- Enhanced dynamic filtering using `department_id` and `product_id` dependencies.
- Added default "Ingen" option for `condition_id`.
- Updated `SelfServeConditionsTable` with editable `condition_id` column.
- Improved pagination and object creation logic to support `condition_id`.
2026-01-09 13:02:20 +01:00
Jeppe Bundgaard f332ad1f4f Add task-specific rules and caching logic to improve SelfServeTasks and SelfServeConditionRules
- Introduced `getTaskText` function with local task caching for optimized task text retrieval.
- Enhanced `SelfServeConditionRules` options handling to support combined question and task selections.
- Updated `DepartmentSelfServeConditionRules` to include separate buttons for adding question and task rules.
- Improved table parsing logic in `SelfServeConditionRulesTable` to handle tasks alongside questions.
2026-01-09 12:51:45 +01:00
Jeppe Bundgaard 9da48a36c0 Add SelfServeConditions and SelfServeConditionRules modules with dynamic filtering and CRUD support
- Introduced `SelfServeConditions` and `SelfServeConditionRules` modules for managing conditions and rules.
- Added routing, components, and backend integration for CRUD operations.
- Implemented dynamic filtering based on `department_id`, `lane_id`, and `vehicle_type`.
- Enhanced self-serve workflows with condition and rule management for questions and tasks.
- Updated related navigation, tables, and pagination components for seamless integration.
2026-01-09 12:45:31 +01:00
Jeppe Bundgaard 2d7d3265c3 Add question_condition support to SelfServeQuestions with dynamic filtering and table integration
- Introduced `question_condition` field for conditional question handling.
- Enhanced question filtering logic using `department_id` and `product_id`.
- Added default "Ingen" option for `question_condition`.
- Updated `SelfServeQuestionsTable` to include editable `question_condition` column.
- Removed deprecated `image` and `vehicle_type` column handlers.
- Added `question_condition` handling in the backend update logic.
2026-01-09 11:38:21 +01:00
Jeppe Bundgaard e2c31b1db6 Add SelfServeTaskAttachmentsModal component to manage task attachments
- Implemented attachment management (list, upload, download, delete) within a modal interface.
- Integrated file-handling logic with `SessionUser` for authenticated requests.
- Added real-time updates for attachment actions with success/error feedback via `Swal`.
- Designed a responsive modal layout for efficient user interaction.
2026-01-09 11:13:20 +01:00
Jeppe Bundgaard 7962c805e0 Add attachment handling to SelfServeTasks with modal support in task table
- Introduced attachment management (list, upload, delete, download) via `authenticatedRequest`.
- Added `SelfServeTaskAttachmentsModal` for viewing and managing task attachments.
- Updated `SelfServeTasksTable` to include attachment actions with modal integration.
- Adjusted default nearest department evaluation ID in `MyWashStart.vue`.
2026-01-09 11:05:22 +01:00
gold-dev 467365531d Feat: Change styles for 202512 - truckwash.io edits V2 slide 10 and parts 11-14 2026-01-08 18:06:29 +02:00
gold-dev 09f0b76724 Feat: Change styles for 202512 - truckwash.io edits V2 slide 10 and parts 11-14 2026-01-08 18:01:19 +02:00
Jeppe Bundgaard e2492b63b9 Update SelfServeTasks question label from "Spørgsmål" to "Hvis" 2026-01-08 14:43:00 +01:00
Jeppe Bundgaard 3a415a8fd5 Update SelfServeTasks question handling with enhanced filtering and default option
- Made `question` selection mandatory during task creation.
- Enhanced dynamic filtering by including `department_id` and `product_id`.
- Added a default "Altid" option for question selection.
- Improved `showEditObjectFieldForm` by supporting extra `options` parameters for contextual edits.
2026-01-08 14:14:23 +01:00
Jeppe Bundgaard 626c1d56bb Add SelfServeTasksPagination to DepartmentWashLane with dynamic filtering and task creation support 2026-01-08 14:10:13 +01:00
Jeppe Bundgaard 745a70621a Add support for SelfServeTasks with dynamic filtering, drag-and-drop, and enhanced CRUD operations
- Introduced `SelfServeTasks` module with backend integration for tasks CRUD operations.
- Added `SelfServeTasksPagination` and `SelfServeTasksTable` components to handle task listing, filtering, and reordering.
- Enabled dynamic filtering with `department`, `lane`, `product`, and `question`.
- Enhanced drag-and-drop functionality for reordering tasks with immediate visual feedback.
- Added routing and navigation updates for `SelfServeTasks` under department dashboard.
- Integrated `order_priority` handling for task creation and updates.
- Improved consistency with `SelfServeQuestions` by implementing shared logic and components where applicable.
2026-01-08 14:02:25 +01:00
Jeppe Bundgaard af21e028ea Add order_priority to SelfServeQuestions creation and enhance button slot with totalItems
- Enabled dynamic `order_priority` assignment during question creation based on `totalItems`.
- Updated button slot in SelfServeQuestionsPagination and related components to expose `totalItems`.
2026-01-08 13:36:08 +01:00
Jeppe Bundgaard 549b6208ce Update SelfServeQuestionsTable to replace id column with editable order_priority column
- Replaced the `id` column with the `order_priority` column for better ordering functionality.
- Removed redundant definitions of `order_priority` column for cleaner code structure.
2026-01-08 13:31:06 +01:00
Jeppe Bundgaard 8fe1ee2189 Add sorting by order_priority to SelfServeQuestionsTable and improve drag-and-drop behavior
- Introduced computed `sortedList` for dynamic sorting by `order_priority` in `SelfServeQuestionsPagination`.
- Enhanced drag-and-drop implementation with optimistic updates and immediate feedback by using `setList`.
2026-01-08 13:29:03 +01:00
Jeppe Bundgaard 0bf96e7f21 Enable drag-and-drop functionality in SelfServeQuestionsTable and add order management in SelfServeQuestionsPagination
- Added drag-and-drop handling for row reordering in `SelfServeQuestionsTable`.
- Introduced `setOrder` for managing question order in `SelfServeQuestionsPagination`.
2026-01-08 13:23:53 +01:00
Jeppe Bundgaard 178ad88661 Update SelfServeQuestionsTable to use getProductName for product parsing
- Replaced hardcoded `productId` parsing logic with dynamic `getProductName` function for consistency in product name handling.
2026-01-08 13:19:22 +01:00
Jeppe Bundgaard 17df279797 Improve error handling and expose loadList in pagination components
- Added error handling with `Swal.showValidationMessage` across `add`, `set`, and `delete` operations in `ObjectsGlobal.vue`.
- Exposed `loadList` function to `buttons` slot in `TableLabeledPagination.vue` and `PaginationDisplayGeneralSearchReload.vue`.
2026-01-08 13:17:31 +01:00
Jeppe Bundgaard e338b3ced1 Remove vehicle_type field from SelfServeQuestions and update related references
- Replaced `vehicle_type` with `product` in forms and logic.
- Removed `vehicle_type` column and handling across components.
- Adjusted `add` method and table definitions accordingly.
2026-01-08 13:14:02 +01:00
Jeppe Bundgaard 9e3d4cfccb Add config.js for centralizing configuration constants
- Introduced `IS_DEV` flag for environment-based logic.
- Added `POS_STEP_1_VERSION` and `API_URL` configuration exports.
2026-01-08 12:55:38 +01:00
Jeppe Bundgaard 9ff0bcec17 Refactor: Move configuration exports (API_URL, IS_DEV, POS_STEP_1_VERSION) to dedicated config file and update imports across components 2026-01-08 12:55:23 +01:00
Jeppe Bundgaard 79ce34af5f Update SelfServeQuestions component with dynamic filtering and enhanced CRUD operations
- Added `SessionUser` import to expand options data handling.
- Updated `SelfServeQuestions` fields (`product`, `vehicle_type`) to use dynamic `select` types with async options and filtering.
- Enhanced `add` and `set` methods with explicit parameter handling (`department`, `lane`, etc.).
- Refined options handling using locked values and async calls.
- Adjusted `ObjectsGlobal` methods for consistent request formatting and error handling.
2026-01-08 12:40:21 +01:00
Jeppe Bundgaard 67bcbf45f5 Refactor button slots and layouts across Self-Serve and Wash Lanes modules
- Standardized `buttons` slot structure in pagination templates.
- Removed redundant button definitions in `DepartmentWashLane.vue`.
- Updated button styles and layouts for consistency across components.
2026-01-08 12:00:47 +01:00
Jeppe Bundgaard 193915e5f1 Add initial data support to SelfServeQuestions forms and update pagination refs
- Updated `showCreateObjectForm` to accept `initialData` for pre-filling forms dynamically.
- Exposed `loadList` in `SelfServeQuestionsPagination.vue` for external access.
- Integrated `selfServeQuestionsPagination` ref and dynamic filtering into `DepartmentWashLane.vue`.
2026-01-08 11:53:57 +01:00
Jeppe Bundgaard 9ac322151b Add vehicle type filter to Self-Serve Questions module
- Introduced vehicle type filter support across Self-Serve Questions components, including `SelfServeQuestionsPagination`, `SelfServeQuestionsTable`, and `DepartmentSelfServeQuestions`.
- Added `selectedVehicleType` logic and `MyWashStartVehicleTypeSelection` for enhanced filter functionality.
- Updated filtering and pagination logic to handle dynamic vehicle type selection and modifications.
2026-01-08 11:50:11 +01:00
Jeppe Bundgaard 33ee8c1803 Add SelfServeQuestions module with pagination, routing, and admin navigation integration
- Added new `SelfServeQuestionsPagination.vue` and `SelfServeQuestionsTable.vue` components for managing self-service questions with pagination support.
- Introduced routing for self-service questions in the "Self-Serve" module under admin dashboard.
- Updated `NavigationMenuItemsAdmin.vue` to include "Self-Serve Questions" navigation item.
- Integrated filtering and auto-load functionality to support department and lane-specific views.
2026-01-08 11:44:21 +01:00
Jeppe Bundgaard 9c6ffd2297 Add detailed view and routing for individual wash lanes
- Introduced `DepartmentWashLane.vue` for viewing and managing individual wash lane details.
- Added new route for accessing specific wash lanes under the "Wash Lanes" module.
- Updated `ActionSettingsWheelButton.vue` and `departmentLanesTable.vue` to include navigation to detailed wash lane view.
- Refined permissions logic for displaying lane-related actions based on user roles.
2026-01-08 11:37:35 +01:00
Jeppe Bundgaard 1751d98cd3 Add SelfServeQuestions module to SessionUser components
- Introduced `SelfServeQuestions` object for managing self-service questions with metadata, columns, and CRUD operations.
- Integrated `SelfServeQuestions` module into `SessionUser.vue` and its global objects mapping.
2026-01-08 11:19:03 +01:00
Jeppe Bundgaard a27ab7c2a1 Dynamically generate navigation labels using firstToUpperCase and integrate "Wash Lanes" module into admin menu. 2026-01-08 10:43:34 +01:00
Jeppe Bundgaard a5b7fa7cd3 Add advanced view support to DepartmentLanes components
- Introduced `advancedView` prop to enable additional data display and configuration options across `DepartmentLanes` components.
- Updated `DepartmentLanesPagination.vue` and `departmentLanesTable.vue` templates to conditionally render advanced data based on `advancedView`.
- Propagated `advancedView` prop through relevant parent components for consistent functionality.
2026-01-08 10:38:39 +01:00
Jeppe Bundgaard f45c676a14 Refactor DepartmentLanesPagination component and add "Wash Lanes" module routing
- Refactored `DepartmentLanesPagination.vue` to simplify structure using `TableLabeledPagination`.
- Introduced the "Wash Lanes" module with new routing (`DepartmentWashLanes.vue`) and navigation integration.
2026-01-08 10:25:34 +01:00
Jeppe Bundgaard b9cd4bf1fd Add margin-bottom to table section in TableLabeledPagination component 2026-01-08 10:25:21 +01:00
Jeppe Bundgaard e64f936b3b Add "Vaskebaner" module to DepartmentModulesDisplay
- Introduced a new "Vaskebaner" module with navigation and routing functionality.
- Updated layout to display modules in a 4-column grid instead of 3.
- Simplified "Dagsopgørelse" module description.
2026-01-08 10:18:39 +01:00
Jeppe Bundgaard 6f4d7f8699 Add vehicle type selection component to MyWashStart workflow
- Introduced `MyWashStartVehicleTypeSelection.vue` for vehicle type selection with enhanced UI and dynamic fetching.
- Replaced deprecated "Type of vehicle" field with the new selection component.
- Updated logic to handle vehicle type fetching, selection, and state updates in `MyWashStart.vue`.
2026-01-08 10:14:17 +01:00
Jeppe Bundgaard ff98b84acb Enable system orders inclusion in UserOrders component 2026-01-06 15:55:01 +01:00
Jeppe Bundgaard 2dad26078e Add system orders inclusion toggle to InvoiceOrdersPagination
- Introduced `includeSystemOrders` prop in `InvoiceOrdersPagination.vue` to enable inclusion of system orders in pagination.
- Updated logic to add query parameter `show_wash_subscription` when `includeSystemOrders` is true.
- Adjusted `collectedOrderInvoice.vue` to utilize the new prop for enhanced filtering options.
2026-01-06 15:44:44 +01:00
Jeppe Bundgaard c0adca9558 Add exemption option for monthly administration and environmental fee
- Added `exemptFromAdministrationFee` property to customer settings in `PosSelectedCustomer.vue`.
- Enabled toggle to exclude customers from monthly fees with descriptive tooltip and icon.
2026-01-06 15:22:34 +01:00
Jeppe Bundgaard 010b0d8af7 Add environment fee configuration to E-conomic module
- Added `ConfigurationInputNumber` for setting `feeProductId` in `ConfigurationEconomic.vue`.
- Extended `Economic/Config` with getter and setter methods for `feeProductId`.
2026-01-06 13:45:33 +01:00
Jeppe Bundgaard ee0d486101 Fix conditional rendering in EditableTableColumn and add null-check in getProductOptions
- Updated `EditableTableColumn.vue` to improve handling of `virtualColumn` for hover text rendering.
- Added safeguard in `getProductOptions` to return an empty array when the `product` argument is null or undefined.
2026-01-06 13:17:31 +01:00
Jeppe Bundgaard 0b6d2e4045 Add administration and environment fee configuration to E-conomic module
- Introduced `ConfigurationInputNumber` component for setting `adminFeeMonthly` and `adminFeeOrder` in `ConfigurationEconomic.vue`.
- Extended `Economic/Config` module to include getter and setter methods for fee management.
2026-01-06 13:06:55 +01:00
Jeppe Bundgaard 10526cfe6e Add "Stop" localization key to ObjectsGlobal.vue 2026-01-06 12:02:53 +01:00
Jeppe Bundgaard 4a0edb59db Refactor pagination logic: implement usePaginatedList composable and update dependencies
- Added `usePaginatedList` composable to centralize pagination logic, enabling cleaner and reusable code.
- Replaced direct imports of pagination logic with composable injections across multiple components for consistency.
- Updated components to support the new pagination instance-based structure for better maintainability.
- Simplified `paginatedList.vue` and introduced dependency injection using `PaginatedListKey`.
2026-01-06 11:40:58 +01:00
Jeppe Bundgaard 2f16a91007 Fix conditional rendering in ModuleActionLogsTable and remove unused lifecycle hook
- Updated `ModuleActionLogsTable.vue` to handle null or undefined `created_at` and `data` fields gracefully.
- Improved "no logs found" message with localized text using `SessionUser.objects.global.language.no_data`.
- Removed unused `onMounted` lifecycle hook from `ModulesPagination.vue` for cleanup.
2026-01-06 11:34:19 +01:00
Jeppe Bundgaard d5c3f44da7 Introduce reusable ModulesPagination component and refactor DepartmentLane.vue
- Added `ModulesPagination.vue` for handling paginated action logs with dynamic filtering, sorting, and endpoint configuration.
- Replaced `ModuleActionLogsTable` in `DepartmentLane.vue` with `ModulesPagination` for better abstraction and reuse.
- Simplified `ModuleActionLogsTable.vue` to function as a pure presentation component with external data injection.
2026-01-06 11:32:15 +01:00
Jeppe Bundgaard de25e9c5f0 Refactor DepartmentLane.vue to use reusable ModuleActionLogsTable component
- Replaced inline logs table in `DepartmentLane.vue` with reusable `ModuleActionLogsTable` for better modularity.
- Enhanced `ModuleActionLogs` to support pagination and dynamic filtering.
- Introduced new `ModuleActionLogsTable.vue` component to centralize log display logic.
2026-01-06 11:29:30 +01:00
Jeppe Bundgaard e9d72918a7 Add ModuleActionLogs integration and display logs in DepartmentLane
- Introduced `ModuleActionLogs` component under `SessionUser.Objects` for handling action logs.
- Updated `DepartmentLane.vue` to fetch and display module action logs for "selfserve" under a new "Handlingslogs" section.
- Enhanced user experience by providing detailed log data with sortable columns (e.g., action, status code, creation time).
2026-01-06 11:22:37 +01:00
Jeppe Bundgaard 5276322dcc Add detailed view for Department Lanes and introduce washing actions
- Added `DepartmentLane.vue` for viewing lane details, including configuration and status indicators.
- Extended router with dynamic `/superuser/department/lanes/:id` route for lane-specific pages.
- Updated `ActionSettingsWheelButton` to modify lane redirect paths for consistency.
- Enhanced `DepartmentLanesTable` to include washing actions (`stopWashing` and status checks) with improved UI.
- Added utility functions (`isWashing`, `stopWashing`) to `SessionUser.Objects.DepartmentLanes`.
2026-01-06 10:55:43 +01:00
Jeppe Bundgaard cb018cc2ec Add department lane actions to ActionSettingsWheelButton and improve DepartmentLanesTable status indicators
- Added `department_lane_id` prop to `ActionSettingsWheelButton` for department lane-specific actions.
- Implemented "View lane in new tab" action and conditional visibility logic.
- Enhanced `DepartmentLanesTable` with status indicators for operational, non-operational, and unconfigured states.
- Introduced status enumerations and utility methods (`isOperational`, `isConfigured`, `isFaulty`) in `SessionUser.Objects.DepartmentLanes`.
2026-01-06 09:58:38 +01:00
Jeppe Bundgaard 429709e270 Handle error in version update API call in vite.config.js
- Added `try-catch` block to gracefully handle errors when calling the server API.
- Improved logging for both success and failure cases to aid debugging.
2026-01-05 11:20:32 +01:00
Jeppe Bundgaard 7f511dd205 Add project guidelines document and adjust VITE_COMMIT_HASH handling
- Introduced `.junie/guidelines.md` to outline project setup, code conventions, and structure.
- Updated `env.d.ts` and `vite.config.js` to standardize `VITE_COMMIT_HASH` usage.
2026-01-05 11:15:48 +01:00
Jeppe B 0fd65e5eaf Merge pull request #35 from copenhagentruckwash/buefy-integration
buefy-integration
2026-01-05 09:20:36 +01:00
Jeppe Bundgaard 2dd5263087 Update UserProfile.vue: conditionally set module for mobile and add logout button
- Updated `module` bindings to account for mobile devices by displaying empty strings when accessed on mobile.
- Introduced a logout button with confirmation dialog functionality for improved user experience.
2025-12-18 11:51:52 +01:00
Jeppe Bundgaard 3330f20db4 Refactor MobileHeader.vue: replace useRouter with useRoute, update route-based rendering logic, and improve admin features visibility 2025-12-18 11:45:01 +01:00
Jeppe Bundgaard eb6863e4a8 Show wash card in UserDashboard only in development mode using IS_DEV flag 2025-12-18 11:44:54 +01:00
Jeppe Bundgaard 21b8324933 Update MobileFooter.vue: disable "Selv vask" button in production mode using IS_DEV flag 2025-12-18 11:44:47 +01:00
Jeppe Bundgaard ead0ceca39 Refactor MyWashStart.vue: integrate ScrollableSteps, refine guided wash flow, and update footer and assistance logic 2025-12-18 11:10:34 +01:00
Jeppe Bundgaard 815f47004b Add ScrollableSteps.vue and refactor phone call functionality
- Introduced `ScrollableSteps.vue` component with configurable maxHeight and slot-based structure for enhanced reusability.
- Moved phone call functionality to `SessionUser.vue` under reusable `contact` property.
- Updated `MobileHeader.vue` to utilize centralized phone call logic for consistency.
2025-12-18 11:00:51 +01:00
Jeppe Bundgaard 895ab6ffd2 Add call phone button to MobileHeader.vue with dynamic styling and visibility handling
- Implemented a phone call button for quick access with `tel` protocol.
- Added conditional rendering of the button based on `showProfile`.
- Applied dynamic icon styling for development and production modes.
2025-12-18 10:55:44 +01:00
Jeppe Bundgaard 16d8b120f7 Refactor footer visibility logic and improve route-based rendering in MobileHeader.vue
- Extracted `showFooterInContent` into a shared module for reusability.
- Updated `MobileHeader.vue` to use a computed property (`showProfile`) for improved route-based condition handling.
- Renamed `showFooterInContent` to `isFooterInContent` in `Viewport.vue` for clarity and consistency.
2025-12-18 10:12:09 +01:00
Jeppe Bundgaard aac14e474a Update Viewport.vue: add mobile-specific class to header spacer for improved layout handling on mobile devices 2025-12-18 09:44:11 +01:00
Jeppe Bundgaard fba30ba2ac Update MobileHeader.vue: conditionally show profile link and logo based on route
- Added `useRouter` to dynamically check the current route and display the profile button or logo depending on whether the path starts with `/user`.
2025-12-18 09:41:14 +01:00
Jeppe Bundgaard 7a6d2e0b8d Update Viewport.vue: refine footer visibility and enhance mobile footer display
- Adjusted `showFooterInContent` logic to conditionally render footer only on mobile devices using new device detection.
- Updated footer layout to include copyright text and refined styling for improved presentation.
2025-12-18 09:36:47 +01:00
Jeppe Bundgaard 16ccda0c33 Update Viewport.vue: adjust independentScroll logic, add spacer for header, and refine max-height calculation
- Modified `independentScrollMaxHeight` to include `footerHeight` for accurate layout adjustment.
- Added spacer element to prevent content overlap with the fixed header.
- Updated `.independent-scroll` styles for consistency in max-height calculation.
2025-12-18 09:31:16 +01:00
Jeppe Bundgaard 096b878d3d Refactor department dashboard: add "washes" tracking, update calculations for average earnings, and improve subtitle generation logic 2025-12-18 09:14:05 +01:00
Jeppe Bundgaard aab494b239 Improve department navigation: add loading state, skeleton loaders, and optimize department list rendering 2025-12-18 07:54:35 +01:00
Jeppe Bundgaard 8a2b30b117 Add descending order support to paginated list methods across components
- Updated `OrderBookings.vue`, `Orders.vue`, and `ObjectsGlobal.vue` to include `order: 'id:DESC'` in pagination options, ensuring consistent sorting by ID in descending order.
2025-12-18 07:43:38 +01:00
Jeppe Bundgaard e66b7c8853 Add profile link to MobileHeader.vue and dynamic footer visibility in Viewport.vue
- Included user profile button in `MobileHeader.vue` with authentication check and styling for development mode.
- Updated `Viewport.vue` to conditionally show footer content based on route using new computed property.
- Adjusted layout styles, added new color filter classes, and refined footer handling logic for specific paths.
2025-12-17 16:04:15 +01:00
Jeppe Bundgaard 203e39aa6c Refactor DepartmentsOverviewObject.vue: use defineComponent for clarity and export consistency 2025-12-17 15:26:19 +01:00
Jeppe Bundgaard ce2c313f63 Add guided wash flow to MyWashStart.vue with step-by-step instructions, brush details, and improved user navigation 2025-12-17 15:19:57 +01:00
Jeppe Bundgaard da45e23324 Refactor MyWashStart.vue: simplify customer number validation, improve step navigation logic, and ensure reactive updates 2025-12-17 12:20:39 +01:00
Jeppe Bundgaard 4c0cd44a82 Refactor ViewportFooter.vue: replace deprecated slot syntax with v-slot and conditionally render footer component in Viewport.vue 2025-12-17 12:14:09 +01:00
Jeppe Bundgaard 3370bf7469 Refactor department dashboard: replace date shortcuts with DatePeriodSelector, improve loading state handling, and enhance transaction fetch logic 2025-12-17 12:03:26 +01:00
Jeppe Bundgaard b860417cc4 Refactor DepartmentDashboardMyDepartmentsOverview.vue: replace unused BookingsPagination component, add tabbed display for daily reports, and improve department list rendering 2025-12-17 10:05:57 +01:00
Jeppe Bundgaard 58c6a528e3 Enable "Selv vask" button in MobileFooter.vue and remove unused v-show property in UserDashboard.vue 2025-12-16 14:06:40 +01:00
Jeppe Bundgaard 8c88052784 Update MobileFooter.vue: add disabled state to navigation items and bind to button logic 2025-12-16 13:30:41 +01:00
Jeppe Bundgaard 7b5262cebb Add MobileFooter.vue and enhance MyWashStart.vue navigation
- Added `MobileFooter.vue` for improved mobile navigation with dedicated page links.
- Updated `MyWashStart.vue` to refine customer number step validation and clickable state logic.
- Set default `forceNearestDepartmentEvaluationId` to 0 for consistency.
2025-12-16 13:25:14 +01:00
Jeppe Bundgaard 7e64e9f1aa Refactor footers: replace unused content slots with footer components and add ViewportFooter to Viewport.vue 2025-12-16 11:59:30 +01:00
Jeppe Bundgaard 32ae94069b Refactor MyWashStart.vue: set default forceNearestDepartmentEvaluationId to 0 and remove unused console warnings 2025-12-16 11:45:40 +01:00
Jeppe Bundgaard 333fecd2c4 Update MyWashStart.vue: add persistence for in-progress washes using localStorage 2025-12-16 11:44:19 +01:00
Jeppe Bundgaard 6cc425dd1e Update MyWashStart.vue: validate vehicle type selection for step navigation and button state 2025-12-16 11:15:08 +01:00
Jeppe Bundgaard 6e300cd5c3 Update MyWashStart.vue: add vehicle management, enhance autocomplete for license plates, and fetch vehicle types 2025-12-16 11:12:41 +01:00
Jeppe Bundgaard e3db468d0b Update OrderBookingsTable.vue: add margin to calendar icon for better spacing 2025-12-16 09:36:08 +01:00
Jeppe Bundgaard dc5533ccb0 Update MyWashStart.vue: fix nearestDepartment name rendering in lane availability message 2025-12-15 16:02:01 +01:00
Jeppe Bundgaard 08ad376e6d Update MyWashStart.vue: enhance lane availability message with dynamic department name rendering 2025-12-15 15:56:05 +01:00
Jeppe Bundgaard f6784bc815 "Update MyWashStart.vue: set forceNearestDepartmentEvaluationId default value to 0" 2025-12-15 15:52:39 +01:00
Jeppe Bundgaard 6e2eb81d5b "Refactor MyWashStart.vue and ordersTable.vue: enhance wash timer display, add lane labeling, and improve department access validation logic in navigation." 2025-12-15 15:49:55 +01:00
Jeppe Bundgaard 7300cb5722 "Refactor MyWashStart.vue: add onCloseCompleted action to reset flow state, update UI for Completed step, and improve navigation button logic." 2025-12-15 15:34:34 +01:00
Jeppe Bundgaard 617793830b "Refactor MyWashStart.vue: add completed wash duration tracking, reset logic, and display total time in UI after completion." 2025-12-15 15:30:56 +01:00
Jeppe Bundgaard 72edbf338d "Refactor MyWashStart.vue: add elapsed wash timer with start/stop logic, integrate onUnmounted for cleanup, and enhance UI with completed wash step." 2025-12-15 15:28:38 +01:00
Jeppe Bundgaard a9f8e59433 "Refactor MyWashStart.vue: improve wash process with machine/manual options, lane availability checks, and enhanced step-based flow using Buefy components." 2025-12-15 15:24:05 +01:00
Jeppe Bundgaard b50f07b409 "Update MyWashStart.vue: set forceNearestDepartmentEvaluationId to 0 for production" 2025-12-15 13:38:14 +01:00
Jeppe Bundgaard 35439fb963 "Update UserDashboard.vue: change wash certificate link to orders page and remove redundant disabled state" 2025-12-15 13:28:01 +01:00
Jeppe Bundgaard 16866155c1 "Refactor MyWashStart.vue: enhance wash process flow with step-based navigation, integrate Buefy components for improved UI, add forced nearest department evaluation for debugging, and streamline input validation logic." 2025-12-15 13:26:37 +01:00
Jeppe Bundgaard e94e2f0541 "Refactor OrderBookingsTable.vue: enhance editable table rows, add hover tooltips for item details, integrate customizable themes in EditableTableColumn.vue, and improve overall table usability and styling." 2025-12-15 11:46:39 +01:00
Jeppe Bundgaard a861af5a9c "Update InvoiceOrdersPagination.vue: replace hardcoded label with dynamic label from SessionUser objects for improved localization support." 2025-12-11 14:12:56 +01:00
Jeppe Bundgaard d5b83f64cc "Update InvoiceOrdersPagination.vue: adjust label text to 'Historiske vaske' for improved clarity and accuracy." 2025-12-11 14:11:06 +01:00
Jeppe Bundgaard c84d0d2851 "Refactor DatePeriodSelector.vue and InvoiceOrdersPagination.vue: integrate shortcut visibility options, replace manual date filter shortcuts with DatePeriodSelector, and improve filter structure for better maintainability and usability." 2025-12-11 14:09:15 +01:00
Jeppe Bundgaard bfad04aa85 "Add responsive mobile layout to DatePeriodSelector.vue with updated date, month, and year selectors using Buefy components." 2025-12-11 13:22:44 +01:00
Jeppe Bundgaard 97809d2e43 "Integrate DesktopNavigationBuefy into MobileNavigation.vue and update menu structure for improved component reuse and maintainability." 2025-12-11 12:49:20 +01:00
Jeppe Bundgaard 94f1528d4a "Add tablet-specific header component and responsive layout updates for improved device compatibility." 2025-12-11 12:35:19 +01:00
Jeppe Bundgaard f71451ad88 "Update TableLabeledPagination.vue: adjust label styling and class for improved readability" 2025-12-10 14:39:26 +01:00
Jeppe Bundgaard b035da7984 "Update TableLabeledPagination.vue: adjust label styling and class for improved readability" 2025-12-10 14:25:47 +01:00
Jeppe Bundgaard 3071e7f777 "Refactor MyInvoicesPagination.vue: integrate TableLabeledPagination for improved structure, update InvoiceCollectionsTable label to 'total_price_amount', and adjust locale strings for clarity." 2025-12-10 14:19:32 +01:00
Jeppe Bundgaard 824840d729 "Refactor OrdersPagination.vue: replace nested components with TableLabeledPagination, adjust filters and table integration for improved structure and maintainability." 2025-12-10 14:16:21 +01:00
Jeppe Bundgaard d17c84a42c "Update UserDashboard pagination components: adjust column classes, add spacing labels for better layout alignment, and remove redundant label element in TableLabeledPagination.vue." 2025-12-10 14:12:49 +01:00
Jeppe Bundgaard 2314f02239 "Refactor OrderBookingsTable.vue: simplify table logic by removing unnecessary filters and pagination handling, migrate pagination to OrderBookingsPagination.vue, and update components for improved code maintainability and usability." 2025-12-10 14:01:24 +01:00
Jeppe Bundgaard b34bb0b0dc "Update ordersTable.vue: rename 'Total' label to 'Beløb' for locale consistency." 2025-12-10 13:11:00 +01:00
Jeppe Bundgaard 65d6e510d1 "Refactor VehiclesPagination.vue: consolidate pagination logic into new TableLabeledPagination component, remove unused imports, and simplify VehiclesTable template structure for improved maintainability and readability." 2025-12-10 12:39:04 +01:00
Jeppe Bundgaard d63b28977f "Add minHeight to independent-scroll in Viewport for consistent scrollable area styling" 2025-12-10 12:30:41 +01:00
Jeppe Bundgaard 4c6f8df45d "Update UserDashboard labels for clarity: revise messaging in booking, vehicle management, wash certificates, and invoices cards." 2025-12-10 11:10:44 +01:00
Jeppe Bundgaard 4a76137a90 "Implement dynamic card height adjustment in UserDashboard for improved layout consistency across device types." 2025-12-10 11:04:13 +01:00
Jeppe Bundgaard 60dcf3e227 "Add optional CSS class support for navigation items, dynamic header height handling in Viewport, locale-based label updates, and device type checks." 2025-12-10 10:54:18 +01:00
Jeppe Bundgaard 27d3200c3b "Update DesktopHeader.vue to rename 'Opret konto' to 'Opret bruger' for label consistency." 2025-12-09 16:00:24 +01:00
Jeppe Bundgaard 1c67f64c7e "Add back-office, department, and return-to-account buttons in DesktopHeader.vue with session-based visibility and dynamic routing for improved navigation functionality." 2025-12-09 15:55:42 +01:00
Jeppe Bundgaard 39adc6536d "Refactor navigation components: optimize computed properties, update department_id logic, improve menu item handling, and add dynamic session-based routes in DesktopHeader for enhanced maintainability and functionality." 2025-12-09 15:34:51 +01:00
Jeppe Bundgaard f7c75f75fa "Remove unused imports and redundant components in DesktopNavigation and DesktopHeader for cleaner code and improved maintainability." 2025-12-09 14:22:23 +01:00
Jeppe Bundgaard 06d84872ff "Add 'Start Wash' feature with MyWash, MyWashStart components, dynamic route integration, and user location-based functionality in UserDashboard." 2025-12-09 14:14:47 +01:00
Jeppe Bundgaard f019c43853 "Replace session redirection with new dashboard cards for streamlined user actions and enhanced UI functionality in UserDashboard.vue." 2025-12-09 11:43:03 +01:00
Jeppe Bundgaard 75ccc4ab1e "Improve navigation menu styles, add dynamic visibility for logout link, and enhance mobile menu layout and usability." 2025-12-09 11:05:20 +01:00
Jeppe Bundgaard 03e28c6070 "Update labels for consistency in PaginationDisplay.vue and improve table styling by adding bordered class in DepartmentLanesTable.vue." 2025-12-09 11:05:03 +01:00
Jeppe Bundgaard 3831471f54 "Add Qodana configuration file (qodana.yaml) for code quality analysis and CI/CD integration." 2025-12-09 11:04:44 +01:00
Jeppe Bundgaard f808caff31 "Add relay_machine_id column to DepartmentLanes table and backend for improved data management and editability." 2025-12-09 10:27:17 +01:00
Jeppe Bundgaard 3ddb6c168e "Add SelfServe module configuration, routes, and UI integration in SuperUserDashboard for enhanced functionality and manageability." 2025-12-09 09:07:46 +01:00
Jeppe Bundgaard 40b90ff2d1 "Remove unused imports across multiple components for cleaner code and improved maintainability." 2025-12-07 11:07:15 +01:00
Jeppe Bundgaard 66f458014c "Remove TypeScript declaration from <script setup> in Viewport and related header components to simplify and align with current codebase practices." 2025-12-07 11:03:13 +01:00
Jeppe Bundgaard 45c9e87380 "Remove unused imports and redundant components across pagination and dashboard files for cleaner code and improved maintainability." 2025-12-06 20:41:30 +01:00
Jeppe Bundgaard 7945e0996b "Update Vehicles.vue and vehiclesTable.vue for label consistency, improved localization, and cleaned up empty column headers." 2025-12-06 20:39:31 +01:00
Jeppe Bundgaard a74d4ab042 "Replace search input and LoadButtonWhileAwait with PaginationDisplayGeneralSearchReload in MyInvoicesPagination.vue for cleaner design and enhanced reusability." 2025-12-06 20:34:43 +01:00
Jeppe Bundgaard 00995e38aa "Add 'create vehicle' button in VehiclesPagination.vue for user-specific routes, improve responsiveness with isSmall ref, and enhance button styling with dynamic colors." 2025-12-06 20:28:40 +01:00
Jeppe Bundgaard f2f9d24652 "Improve filter reset UI in PaginationDisplayFilters.vue, update translation for 'reload' label, and remove redundant LoadButtonWhileAwait component in VehiclesPagination.vue for cleaner code and enhanced UX." 2025-12-06 20:12:38 +01:00
Jeppe Bundgaard d2de664186 "Refactor orders table to dynamically fetch localized labels and improve column editability; enhance navigation and pagination with context-sensitive filters and dynamic menu visibility." 2025-12-06 19:59:48 +01:00
Jeppe Bundgaard 70949630fb "Enhance navigation menu with context-aware active route highlighting and dynamic visibility based on department selection status." 2025-12-06 15:38:13 +01:00
Jeppe Bundgaard bc6c382b9e "Add optional 'hidden' property to NavigationItemProps for improved flexibility in navigation item visibility." 2025-12-06 15:37:57 +01:00
Jeppe Bundgaard 5021b99ca1 "Adjust label styling in OrdersPagination.vue for smaller font size and improved UI consistency." 2025-12-05 20:30:28 +01:00
Jeppe Bundgaard dd2dcc9043 "Update field labels in Orders.vue for improved clarity and consistency in UI text." 2025-12-05 20:30:13 +01:00
Jeppe Bundgaard 5329b62bc5 "Add Buefy for navigation menu; implement DesktopNavigationBuefy.vue and integrate into viewport layout." 2025-12-05 20:29:46 +01:00
Jeppe Bundgaard 83bc1246fb "Refactor NavigationMenuDepartment button formatting for improved readability in mobile tooltip layout." 2025-12-05 20:19:45 +01:00
Jeppe Bundgaard 54cc7c298c "Add responsive department selection menu with tooltip for mobile; optimize desktop dropdown layout and styling." 2025-12-05 20:09:24 +01:00
Jeppe Bundgaard 7a43669ed5 "Fix update-version endpoint URL by removing incorrect port specification in Vite configuration." 2025-12-03 08:45:07 +01:00
Jeppe Bundgaard d1446f1435 "Fix update-version endpoint URL by removing incorrect port specification in Vite configuration." 2025-12-01 15:22:28 +01:00
Jeppe Bundgaard b548582c7c "Remove unnecessary console warning from VersionCheck.vue for cleaner debugging output." 2025-12-01 15:10:27 +01:00
Jeppe Bundgaard 933eba04aa "Introduce version check mechanism with notifications for updates; enhance Vite build process for automated server update handling." 2025-12-01 14:59:56 +01:00
Jeppe Bundgaard ac7ddc37fb "Add new piktogrammer images and update switch-case logic in Piktogrammer.vue to support additional product categories." 2025-11-28 12:35:54 +01:00
Jeppe Bundgaard eed5d6aa3b "Restrict 'Tilbage til egen konto' navigation item to superusers by updating permissions for improved access control." 2025-11-28 08:08:56 +01:00
Jeppe Bundgaard e9a83700fa "Remove redundant action label in OrdersTable for cleaner UI and simplified code." 2025-11-27 15:29:51 +01:00
Jeppe Bundgaard 61c828c051 "Enhance ActionSettingsWheelButton: add visibility condition for labels based on user permissions and session properties." 2025-11-27 15:12:55 +01:00
Jeppe Bundgaard fdc5b845be "Update redirect logic in OrdersTable to include additional parameter for enhanced navigation handling." 2025-11-27 15:05:27 +01:00
Jeppe Bundgaard aeb57e70e2 "Introduce Order Actions Modal to enhance user interaction; refactor OrdersTable for improved layout and add summary mode to OrderContentTable for concise order display." 2025-11-27 15:01:24 +01:00
Jeppe Bundgaard 9920455728 "Refactor OrdersTable for responsive design; add mobile and tablet-friendly card layouts, enhance redirection logic, and introduce relative time labels." 2025-11-27 14:11:03 +01:00
Jeppe Bundgaard e91f8f2a4b "Add filters and mobile-friendly toggles to OrderBookingsTable; enhance date labels with relative terms and provide 'Show all bookings' button when filters are applied." 2025-11-27 12:07:58 +01:00
Jeppe Bundgaard f8a57c9654 "Remove extraneous character from MyBookingsBook.vue; clean up UI display in empty state message." 2025-11-27 10:51:39 +01:00
Jeppe Bundgaard 2ce314590e "Hide system orders from non-superusers in OrdersTable based on user permissions; update visibility logic to enhance access control." 2025-11-27 10:30:56 +01:00
Jeppe Bundgaard 8110bff6db "Refactor OrdersTable and related components to enhance visibility handling based on user permissions, streamline PO display logic, and improve guest mode functionality across booking flows." 2025-11-27 10:24:12 +01:00
Jeppe Bundgaard e22092ada2 "Add validation message to MyBookingsBook.vue for displaying missing required fields; disable 'Go to confirmation' button if validation errors persist." 2025-11-26 13:12:09 +01:00
Jeppe Bundgaard 284379e534 "Refactor GuestBookExteriorWash, ActionSettingsWheelButton, and related components: streamline user session checks, improve error handling in vehicle suggestions, and enhance authenticated request logic for flexible token management." 2025-11-26 12:21:19 +01:00
Jeppe Bundgaard f8b171dc71 "Refactor DepartmentDailyReport components: add props for department-specific and all-departments handling, update navigation to support selectAllDepartments, and modify report display logic for enhanced flexibility and usability." 2025-11-26 11:15:43 +01:00
Jeppe Bundgaard ba9a5551a3 "Add version selector to OrderBookingsTable for toggling between new and legacy booking portals; replace notification with dropdown to improve UI clarity and portal access." 2025-11-26 10:02:38 +01:00
Jeppe Bundgaard 00b5a63477 "Remove wash certificate booking logic from PosDepartmentStepMobile2.vue to simplify transaction handling and reduce unnecessary promise chaining." 2025-11-25 15:13:33 +01:00
Jeppe Bundgaard a87526fa64 "Add excludedOrderIds prop to OrdersTable and related components; implement logic for filtering and handling excluded orders across invoicing and transaction views." 2025-11-25 14:40:23 +01:00
Jeppe Bundgaard 8fbbdf5fd0 "Add clickFunction prop to DepartmentDashboardDailyReportCount for handling card click events; integrate OrderBookingsTable into DepartmentDailyReport with click-to-bookings redirection." 2025-11-25 12:36:29 +01:00
Jeppe Bundgaard 1be833462a "Refactor DepartmentDashboardDailyReport components: add dynamic suffix handling for labels, enhance 'All' department selection feature in navigation, and streamline out-of values management across display and report logic." 2025-11-25 11:32:42 +01:00
Jeppe Bundgaard b4b85d6a9b "Enable additional items component display in PosDepartmentStepMobile2.vue by removing conditional v-show directive." 2025-11-25 11:17:15 +01:00
Jeppe Bundgaard 6a16c5aa7b "Add booking stats to DepartmentDailyReport: include booking counts, update UI labels, and enhance daily report display logic for more comprehensive metrics." 2025-11-25 11:17:10 +01:00
Jeppe Bundgaard 5e4713338e "Improve filter management in OrderBookingsTable: add new filter detection logic with isFilterApplied, enhance empty table message based on filters, streamline 'Show All Bookings' functionality, and update UI consistency for filter reset actions." 2025-11-25 09:28:08 +01:00
Jeppe Bundgaard ac29d026e4 "Refactor user impersonation logic: move QR code generation and display logic to ActionSettingsWheelButton, update usersTable to use enhanced settings wheel, and improve filter reset handling in OrderBookingsTable." 2025-11-25 08:32:24 +01:00
Jeppe Bundgaard b07a7febb2 "Add new booking portal links and filters: implement new portal redirection in BookingsPagination, add filters for today's and pending bookings in OrderBookingsTable, and enhance mobile responsiveness." 2025-11-25 08:14:08 +01:00
Jeppe Bundgaard eec5aca983 "Introduce legacy booking pages and routes: add MyBookingsLegacy and DepartmentBookingsLegacy components with associated routes, update OrderBookingsTable to include legacy booking portal link, and implement conditional redirection for legacy portal access." 2025-11-24 16:24:58 +01:00
Jeppe Bundgaard e7befaa981 "Refactor booking logic across multiple components: enhance booking completion flow, improve safety seal certificate handling, streamline booking ID validation, and update API filter syntax for consistency." 2025-11-24 15:47:59 +01:00
Jeppe Bundgaard 023743f80d "Enhance OrdersTable hover functionality: add hover text support, truncate long text values with a new truncate utility in SessionUser, and update EditableTableColumn with hover text handling." 2025-11-24 13:21:32 +01:00
Jeppe Bundgaard db996f093e "Refactor vehicle and booking components: streamline booking application logic, update metadata fields (e.g., booking_id, lastOrderId), enhance vehicle selection with duplicate prevention, and improve consistency in pending bookings handling." 2025-11-24 13:02:15 +01:00
Jeppe Bundgaard 85f0901cc2 "Add branding details table in DepartmentProfile.vue for improved department information display." 2025-11-24 10:58:22 +01:00
Jeppe Bundgaard 36325fb7b5 "Disable vehicle selection view in PosDepartmentStepMobile2.vue when primary product is set." 2025-11-20 16:12:49 +01:00
Jeppe Bundgaard b9e752444b "Add auto-apply logic for pending bookings in PosDepartmentStepMobile2.vue: implement booking selection, metadata updates, primary/addon mapping, and watchers for booking/vehicle changes." 2025-11-20 16:08:19 +01:00
Jeppe Bundgaard f790532eae Remove unnecessary console.log statements from POSDepartmentProcess.vue for cleaner code. 2025-11-20 15:45:13 +01:00
Jeppe Bundgaard b69e4a2c3b "Refactor booking metadata handling and vehicle selection: replace regNrTraekker/regNrTrailer with reg_1/reg_2, adjust pending bookings logic, and align filters in loadPendingBookings for consistency." 2025-11-20 15:15:30 +01:00
Jeppe Bundgaard 71c80c0c18 "Refactor booking components: update routes, adjust navigation for 'Book Vask' pages, improve conditional rendering in GuestBookExteriorWash, introduce getDepartmentsGuest for unauthenticated requests, and enhance table colspan logic in OrderBookingsTable." 2025-11-20 13:12:52 +01:00
Jeppe Bundgaard a92ee51464 "Refactor OrdersTable and related components: simplify table classes, remove unused ColorIndicator, wrap action buttons in a div, and adjust label size in UsersPagination for consistency. Rename translation key in Orders.vue." 2025-11-20 11:24:38 +01:00
Jeppe Bundgaard ed23743f06 "Refactor navigation and booking components: update routes for 'Book Vask' pages, adjust conditional rendering in booking tables, and revise modal styling for consistency." 2025-11-20 09:57:32 +01:00
Jeppe Bundgaard 6e3ea19366 "Refine PosDepartmentStepMobile2.vue and PosDepartmentStep2MobileVehicleSelection.vue: simplify button logic, update text display for the next button, and remove restrictive category filtering for improved flexibility." 2025-11-20 09:40:21 +01:00
Jeppe Bundgaard 0378f2c956 "Refactor DepartmentPosOrder.vue: replace editField function invocations with streamlined showEditObjectFieldForm method for consistency and maintainability." 2025-11-20 09:22:39 +01:00
Jeppe Bundgaard 77d1ab8a83 "Set fixed table layout in ProductsTable for improved content alignment and consistency." 2025-11-19 16:47:30 +01:00
Jeppe Bundgaard d3870b2d0d "Enhance Add Customer button logic in POS mobile popup: require specific fields to be filled and update conditions for rendering. Simplify input field classes by removing redundant validation styling." 2025-11-19 13:35:47 +01:00
Jeppe Bundgaard b139e606d1 "Normalize registration number inputs by converting to uppercase across components. Prevent duplicates in POS search results and simplify watchers in mobile manual input fields." 2025-11-18 15:49:42 +01:00
Jeppe Bundgaard 0b1b588d17 "Add watchers for reg_1, reg_2, and reg_3 in PosDepartmentStep1MobileManualInput to synchronize user input changes with setValueUserInput function." 2025-11-18 14:52:26 +01:00
Jeppe Bundgaard d3210f2a8d "Update empty state message in PosLastScannedLicensePlatesV2 to improve clarity and consistency." 2025-11-18 14:19:54 +01:00
Jeppe Bundgaard 03caca314b "Fix division by zero issue in DepartmentDailyReportThisWeek.vue by adding fallback to 0 for calculated values." 2025-11-18 14:13:08 +01:00
Jeppe Bundgaard 443774a0cb "Refactor department dashboard: centralize reports display with DepartmentDashboardReports, adjust column span in notifications table for better alignment, and comment out unused notifications module for cleaner navigation." 2025-11-18 14:03:23 +01:00
Jeppe Bundgaard 75849e3575 "Remove redundant table cell in bookings table for cleaner layout." 2025-11-18 13:34:13 +01:00
Jeppe Bundgaard 9761bde75b "Adjust permissions and enhance UI behavior across components: refine super user and permission checks in DepartmentPosOrder.vue, improve bookings table layout, conditionally render pagination in DepartmentBookings, handle function execution with promises in EditFieldForm, and comment out unused time bookings module." 2025-11-18 13:16:18 +01:00
Jeppe Bundgaard ff3cfebe3a "Remove unused Clear button in PosDepartmentStep1MobileTransactionHistory for streamlined UI." 2025-11-18 11:18:58 +01:00
Jeppe Bundgaard 84cbd432a8 "Enhance POS mobile steps: refactor button styles with buttonClasses prop, improve product/category display logic, add loading states, and prioritize category sorting by rules." 2025-11-18 11:09:07 +01:00
Jeppe Bundgaard f59568a3f9 "Add new favicon assets and rename existing files for updated branding." 2025-11-18 09:17:56 +01:00
Jeppe Bundgaard 5bdd774849 "Hide customer and additional items sections in PosDepartmentStepMobile2 using v-show directive. Update app branding: rename app to 'Truck Wash' in vite.config.js. Add and update favicon assets for new branding." 2025-11-17 17:20:05 +01:00
Jeppe Bundgaard ac1765f1c0 "Refactor mobile POS components: integrate vehicle selection reset, add long press handling with scroll detection in PosDepartmentStepMobile2, introduce minimum close delay for vehicle selection, and enhance product sorting with priority-based ordering." 2025-11-17 16:32:31 +01:00
Jeppe Bundgaard 7ea0c19caf "Update app branding in vite.config.js: change app name, short name, and description to 'Truck Wash.io'." 2025-11-17 15:26:58 +01:00
Jeppe Bundgaard 97f5338f74 "Hide global search in DesktopHeader.vue by adding v-show directive for conditional rendering." 2025-11-17 15:23:07 +01:00
Jeppe Bundgaard d25d167903 "Remove unnecessary console warning from SelectProductsFormPOS.vue to clean up mounted lifecycle hook." 2025-11-17 14:17:25 +01:00
Jeppe Bundgaard 32bf34658d "Add 'Add Customer' functionality: integrate new popup component and action flow for customer creation in PosDepartmentStepMobile. Update popup handling logic, adjust button actions, and introduce dynamic form autofill for seamless user experience." 2025-11-17 13:31:54 +01:00
Jeppe Bundgaard 3ae84cac1c "Refactor booking-related components: update terminology in OrderBookings.vue, enhance error handling in ShowErrorField.vue, and improve action handling with extended support and dynamic display in ActionSettingsWheelButton.vue." 2025-11-17 10:15:29 +01:00
Jeppe Bundgaard 9c35e34ce6 "Refactor multiple components for improved booking flow and mobile responsiveness: add 'only today' filter to OrderBookingsTable.vue for precise data selection, enable small-screen support in PaginationDisplay.vue, and refactor DepartmentBookings.vue with enhanced filters. Introduce ViewportResponsiveWrapper for adaptive layouts and improve mobile UI with WhiteBoxCard integration." 2025-11-17 08:07:56 +01:00
Jeppe Bundgaard 6cac0b205a "Refactor multiple components to enhance booking flow and usability: update EditableTableColumn logic in OrdersTable.vue, optimize hasVehicle computation in MyBookingsBook.vue, introduce mobile wizard steps with dynamic step navigation, and improve mobile UI with fixed bottom navigation and layout adjustments. Remove and comment unused intranet routes." 2025-11-13 15:43:50 +01:00
Jeppe Bundgaard 1550138a43 "Refactor multiple components for improved booking flow and usability: add validation checks to order updates in PosDepartmentStepMobileButtonNextStep.vue, optimize query parameter construction in Products.vue, adjust mobile column classes in SelectProductsFormPOS.vue, and enhance mobile booking UI in MyBookingsBook.vue with improved layout, accessibility, and date/time selection logic." 2025-11-13 15:01:03 +01:00
Jeppe Bundgaard 34cb298f24 "Enhance SelectProductsFormPOS.vue and MyBookingsBook.vue with improved category handling, compact layout adjustments, dynamic grid updates, and updated booking flow: implement default category selection, update mobile-friendly column classes, add product selection step for mobile, and refine time formatting logic." 2025-11-13 14:15:06 +01:00
Jeppe Bundgaard 017437fecf "Enhance various components with improved sorting, formatting, and layout updates: add time formatting logic for midnight in NewBookingStep5.vue, introduce compact mode for vehicle suggestions layout in TimeBookingsElementVehicleSuggestions.vue, and implement priority-based addon sorting in PosDepartmentStepMobile2Addons.vue. Remove unused customer slot logic in MyBookingsBook.vue for cleanup." 2025-11-13 13:21:10 +01:00
Jeppe Bundgaard 40127ac64a "Add new piktograms (19.png, 20.png) and update getPicture logic in Piktogrammer.vue for mapping enhancements" 2025-11-13 12:30:05 +01:00
Jeppe Bundgaard 6859bfda30 "Enhance MyBookingsBook.vue with dynamic interior-exterior wash handling: add logic for linked product toggling, caching associated washes, and improving addon visibility/updates." 2025-11-13 12:08:26 +01:00
Jeppe Bundgaard 145501b883 "Add new piktograms and mapping updates to Piktogrammer.vue: integrate 5.png, 6.png, 17.png, and 39.png assets with updated getPicture logic and deprecated older assets." 2025-11-13 10:49:55 +01:00
Jeppe Bundgaard 126179e9b4 "Add new piktograms and corresponding logic: integrate 11.png, 15.png, and 49.png assets into Piktogrammer.vue with updated getPicture mappings." 2025-11-13 10:28:49 +01:00
Jeppe Bundgaard 86174a6485 "Enhance MyBookingsBook.vue initialization: add conditional customer number check with onCustomerNumberChange; optimize loadWashCertificateProduct placement for better efficiency." 2025-11-13 10:02:23 +01:00
Jeppe Bundgaard 1f31c12810 "Add error handling and validation enhancements to MyBookingsBook.vue: improve customer number validation with dynamic feedback and validation requests; enhance booking process error handling with detailed alerts and retry options." 2025-11-13 09:48:21 +01:00
Jeppe Bundgaard c91f7acc2a "Add dynamic validation to customer number input in MyBookingsBook.vue: highlight input field with error styling when missing and required." 2025-11-12 15:47:24 +01:00
Jeppe Bundgaard f5568f849b "Enhance MyBookingsBook.vue with forced pickup product addition: auto-add pickup product when enabled; update logic for visibility, filtering, and basket handling for consistency." 2025-11-12 15:33:58 +01:00
Jeppe Bundgaard b10634e190 "Standardize pickup checkbox label in MyBookingsBook.vue to 'Inkl. Pick-Up' for consistency across views." 2025-11-12 14:47:16 +01:00
Jeppe Bundgaard d6c47ad85c "Update MyBookingsBook.vue: revise checkbox label for pickup option to 'Inkl. Pickup' for improved clarity." 2025-11-12 14:46:08 +01:00
Jeppe Bundgaard 7a3d1d1b91 "Add showPrices prop for conditional price display across components; implement handling in ProductBox.vue, NewBookingStep5.vue, and related views for improved flexibility." 2025-11-12 14:44:27 +01:00
Jeppe Bundgaard 3a859074ca "Add warning and success notifications to NewBookingStep5.vue: validate booking fields, improve user guidance with dynamic messages; refine date-time handling in MyBookingsBook.vue and TimeBookingsElementSelectDateTime.vue for consistent formatting." 2025-11-12 14:11:09 +01:00
Jeppe Bundgaard f2cc8e6a55 "Refine MyBookingsBook.vue and TimeBookingsElementSelectDateTime.vue date-time selection: add '00:00' as 'in the day', improve time formatting, conditional labels, and slot handling for enhanced user clarity." 2025-11-12 13:21:50 +01:00
Jeppe Bundgaard 4b0195d26b "Enhance OrderBookingsTable.vue with customer number visibility controlled by permissions; improve table layout with updated editable fields, ellipsis styling, and dynamic column adjustments." 2025-11-12 11:45:50 +01:00
Jeppe Bundgaard bcc9198394 "Add email and SMS notifications configuration to UserProfile.vue; implement editable fields with validation and backend integration." 2025-11-11 15:03:30 +01:00
Jeppe Bundgaard 5431da663d "Update navigation menu items to use localized labels; improve ActionSettingsWheelButton order editing with conditional label logic." 2025-11-11 13:39:47 +01:00
Jeppe Bundgaard a957919986 "Add phone number edit functionality to UserProfile.vue; implement save dialog with validation and backend integration; filter Safety Seal addon in MyBookingsBook.vue." 2025-11-11 13:25:15 +01:00
Jeppe Bundgaard 96a44b47ef "Add wash certificate product auto-attachment to MyBookingsBook.vue; ensure accurate pricing with department context and reload on department change." 2025-11-11 11:32:05 +01:00
Jeppe Bundgaard 1716e959f3 "Add attachment handling to ActionSettingsWheelButton.vue: fetch and display order attachments with download functionality; update downloadAttachment method to support auto-opening links." 2025-11-11 11:21:14 +01:00
Jeppe Bundgaard 46ff0a938b "Comment out booking edit action in OrderBookingsTable.vue; add placeholder comment for Actions header to improve clarity." 2025-11-11 10:40:00 +01:00
Jeppe Bundgaard 7fbca5de56 "Update OrderBookingsTable.vue filters to use clearer labels; set default filter for MyBookings.vue to show unfulfilled bookings." 2025-11-11 10:30:26 +01:00
Jeppe Bundgaard b7cb9ddbca "Add sorted bookings by date to OrderBookingsTable.vue; introduce 'isFirstBookingOfDay' to group entries by day and enhance table display with daily headers." 2025-11-11 10:23:09 +01:00
Jeppe Bundgaard f1233a8830 "Add canEditBooking function to enhance permission checks in OrderBookingsTable.vue; update editable fields and actions to respect booking edit permissions." 2025-11-11 10:13:29 +01:00
Jeppe Bundgaard 723a3447d2 "Add canEditBooking function to enhance permission checks in DepartmentDashboardOrderBooking.vue; update editable fields, actions, and UI elements to respect booking edit permissions." 2025-11-11 10:06:18 +01:00
Jeppe Bundgaard f68791d552 "Comment out 'Statistics' menu item in NavigationMenuItemsSuperUser.vue to hide it from the navigation menu." 2025-11-11 09:50:54 +01:00
Jeppe Bundgaard 6b25180fe3 "Enhance OrderBookings and ActionSettingsWheelButton: add safety seal validation, denial handling, and improved admin booking actions; introduce reg_1 and reg_2 handling for vehicle-related actions; refine redirection logic and UI elements for better clarity and functionality." 2025-11-11 08:56:14 +01:00
Jeppe Bundgaard 745d8d93cb "Replace BookingsPagination with OrderBookingsTable in DepartmentBookings.vue; add filter props and dynamic filter handling to OrderBookingsTable.vue for enhanced filtering and data display." 2025-11-11 08:21:03 +01:00
Jeppe Bundgaard d18c8b0ccd "Enhance user redirection logic: add new_tab parameter to SessionUser.vue and update ActionSettingsWheelButton.vue to support opening links in a new tab for improved user navigation." 2025-11-11 08:15:12 +01:00
Jeppe Bundgaard 74be373e7c "Refactor ActionSettingsWheelButton.vue: simplify label logic using dynamic properties, add conditional display for admin actions, and enhance delete buttons with danger templates for improved clarity and safety." 2025-11-11 08:10:32 +01:00
Jeppe Bundgaard 61d500e7a1 "Enhance error handling in ObjectsGlobal.vue with a new centralized handleError method; update OrderBookingsTable.vue to include department filter functionality and dynamic department loading for improved filtering options." 2025-11-11 08:03:52 +01:00
Jeppe Bundgaard facf9c001c "Add 'permissionCheckFunction' to editable columns in OrderBookingsTable.vue for improved permission handling and customization" 2025-11-10 17:11:48 +01:00
Jeppe Bundgaard 30e88c7076 "Update DesktopHeader.vue: change IS_DEV class color from 'warning' to 'red' for improved visual distinction during development." 2025-11-10 17:02:21 +01:00
Jeppe Bundgaard 7eaf14179d "Enhance product fetching in SelectProductsFormPOS.vue: add support for 'customer_number' and 'final_price' parameters; refine product category logic and dynamic prop handling for improved filtering and usability across booking components." 2025-11-10 16:59:53 +01:00
Jeppe Bundgaard 1a2754dbac "Add 'hideTypeSelect' prop to TimeBookingsElementSelectVehicles.vue for flexible UI control; simplify reg1/reg2 validation logic and improve vehicle suggestions handling in multiple booking components." 2025-11-10 16:05:28 +01:00
Jeppe Bundgaard 9fc19f9b23 "Enhance SelectProductsFormPOS.vue: add 'filterProductsVisibleOnCustomerBooking' prop and update layout logic; adjust PosDepartmentStep2.vue to pass 'isCustomerBooking' prop for improved product filtering and display behavior." 2025-11-10 15:40:19 +01:00
Jeppe Bundgaard 1da17440b4 "Add 'Mark as Completed' functionality to OrderBookings: implement safety seal confirmation modal and completion action; enhance ActionSettingsWheelButton with new booking completion option." 2025-11-10 15:32:59 +01:00
Jeppe Bundgaard b3ea80060b "Enhance OrderBookingsTable.vue with interactive item popper: add mouse hover and click functionality for detailed item previews; refine item editing with updated UI and actions. Adjust POSDepartmentProcess.vue to streamline downloadAttachment logic and improve usability." 2025-11-10 15:12:26 +01:00
Jeppe Bundgaard 19708bf20f "Update UI in PaginationDisplay.vue and OrderBookingsTable.vue: adjust label styles, reorganize layout for search and reload controls, and improve filter dropdown with dynamic labels for enhanced clarity and usability" 2025-11-10 12:31:52 +01:00
Jeppe Bundgaard 2ea876da03 "Adjust OrderBookingsTable.vue UI structure: reorganize pagination and filters into a new row for improved layout clarity" 2025-11-10 12:25:43 +01:00
Jeppe Bundgaard 624f4fb35d "Add filtering and search functionalities to OrderBookingsTable.vue with dynamic order ID filtering, search input, and updated UI structure for enhanced usability" 2025-11-10 12:24:11 +01:00
Jeppe Bundgaard 954a35db5c "Refactor OrderBookingsTable.vue to integrate shared pagination utilities, replacing custom methods and improving loading, navigation, and empty state handling; update OrderBookings.vue with new list method" 2025-11-10 12:12:36 +01:00
Jeppe Bundgaard f040534f2e "Add pagination to OrderBookingsTable.vue with next/previous controls, dynamic page limit, and loading state management; refine empty state handling and table structure for improved user experience" 2025-11-10 12:06:50 +01:00
Jeppe Bundgaard 61484f01bd "Update DepartmentDashboardOrderBooking.vue: adjust table structure and column mappings, comment out unused success notification, and refine department and vehicle field handling." 2025-11-10 11:56:54 +01:00
Jeppe Bundgaard e14456ba4c "Refactor DepartmentDashboardOrderBooking.vue to replace custom editable fields with the shared EditableTableColumn component, simplifying logic and improving consistency across booking fields." 2025-11-10 11:48:49 +01:00
Jeppe Bundgaard c33be13048 "Add editable booking fields to DepartmentDashboardOrderBooking.vue for PO, reference, note, and vehicle registrations; include save methods and UI enhancements for better form handling and input synchronization." 2025-11-10 11:45:34 +01:00
Jeppe Bundgaard 48b76e35f9 "Refine DepartmentDashboardOrderBooking.vue to handle item removal when quantity decreases to zero, ensuring accurate item management and persistence." 2025-11-10 11:29:56 +01:00
Jeppe Bundgaard 66415447c7 "Enhance DepartmentDashboardOrderBooking.vue with item management functionality, including add, update, and delete item actions, dynamic total calculation, and improved error handling; refine UI with department name display and detailed item list." 2025-11-10 11:19:21 +01:00
Jeppe Bundgaard c627d71f73 "Remove unused ViewportResponsiveWrapper from DepartmentDashboardOrderBooking.vue to simplify component structure" 2025-11-10 11:03:44 +01:00
Jeppe Bundgaard 3aed75fb3f "Add DepartmentDashboardOrderBooking.vue, update routing for order bookings, and fix ActionSettingsWheelButton redirect path" 2025-11-10 10:50:14 +01:00
Jeppe Bundgaard e3002b458e "Add delete functionality for OrderBookings, integrate delete confirmation modal, and update ActionSettingsWheelButton with delete action and refresh support" 2025-11-10 10:35:32 +01:00
Jeppe Bundgaard 99ba771d4e "Add 'is-fullwidth' class to table in OrderBookingsTable.vue for improved table styling" 2025-11-10 10:25:08 +01:00
Jeppe Bundgaard f3bf3b9279 "Add support for linking orders in OrderBookings, introduce editable order and datetime fields, implement dynamic booking actions, and refine UI with ActionSettingsWheelButton integration across booking components" 2025-11-10 10:21:34 +01:00
Jeppe Bundgaard 652cfdd4fc "Introduce OrderBookingsTable.vue to display user bookings with editable fields, replace BookingsPagination in MyBookings.vue for improved table-based interaction and dynamic fetching logic" 2025-11-06 12:57:53 +01:00
Jeppe Bundgaard 82b07defb2 "Parse customer_number and department as integers in OrderBookings API calls, add 'all' method for fetching multiple objects, and refactor EditableTableColumn.vue layout with responsive level components for better alignment." 2025-11-06 12:55:59 +01:00
Jeppe Bundgaard 9e36bda547 "Refactor DesktopHeader.vue to import IS_DEV from '@/main' instead of using import.meta.env, update logo color transformation logic to use IS_DEV for better consistency and maintainability" 2025-11-06 11:20:50 +01:00
Jeppe Bundgaard 836423cae4 "Apply dynamic logo color transformation in DesktopHeader.vue based on VITE_IS_DEV environment variable, add new CSS classes for color filters, and enhance visual distinction for dev environment" 2025-11-06 10:57:15 +01:00
Jeppe Bundgaard 75fb34afa3 "Update navigation routes for 'Book Vask', add mobile-specific message in LoginQR.vue, introduce 'build:dev' script in package.json, and enhance error message in NavigationMenuItem.vue" 2025-11-06 10:46:32 +01:00
Jeppe Bundgaard ac6b38b694 "Refactor onAddProduct method in PosDepartmentStepMobile2Addons.vue to replace verbose console warnings with inline comments, improving readability and maintaining functionality for click and double-click product interactions" 2025-11-06 09:13:51 +01:00
Jeppe Bundgaard b3e3ddce25 "Add onAddProduct method in PosDepartmentStepMobile2Addons.vue for double-click quantity increment, apply custom styles for selected products in PosDepartmentStepMobile2CategoryProduct.vue, and enhance product interaction logic with conditional classes and cursor pointers" 2025-11-06 09:06:30 +01:00
Jeppe Bundgaard 2cbe709c35 "Adjust labels in MyBookingsBook.vue for clarity, add dynamic label prop to TimeBookingsElementSelectDateTime.vue, and update required field indicators for improved UX." 2025-11-06 08:21:28 +01:00
Jeppe Bundgaard 357087a072 "Add customer number watcher in MyBookingsBook.vue to update customerNumber state dynamically and implement getCustomerNumber method in SessionUser for streamlined access" 2025-11-06 08:13:52 +01:00
Jeppe Bundgaard 9e959c4899 "Add attemptAutoSelectId prop to SelectProductsFormPOS.vue for auto-selecting products based on ID, prevent duplicate categories in product fetching, and integrate auto-selection logic in MyBookingsBook.vue" 2025-11-06 08:05:48 +01:00
Jeppe Bundgaard 2968ecc084 "Integrate SweetAlert2 in MyBookingsBook.vue to display success messages on booking creation, add auto-reload functionality post-confirmation, and enhance user feedback with timed alerts" 2025-11-05 15:44:28 +01:00
Jeppe Bundgaard b6d2acc3a2 "Update MyBookingsBook.vue with computed validation for missing mandatory fields, adjust label texts for clarity, and add button disable logic for incomplete forms" 2025-11-05 15:24:28 +01:00
Jeppe Bundgaard 1c16f47eaa "Add NewBookingStep5.vue for booking confirmation step in dashboard, implement summary display with computed totals and formatted inputs" 2025-11-05 14:22:55 +01:00
Jeppe Bundgaard bccc989157 "Add pickup checkbox to MyBookingsBook.vue for optional pickup selection and pass pickup data to summary confirmation" 2025-11-05 12:48:09 +01:00
Jeppe Bundgaard e1d362e78e "Fix date parsing in MyBookingsBook.vue by correcting ISO string split logic, and add optional customer number input field for improved booking details handling" 2025-11-05 12:43:24 +01:00
Jeppe Bundgaard 33ce23c610 "Add OrderBookings.vue for managing order bookings, integrate with SessionUser and dashboards, and enhance MyBookingsBook.vue with order booking creation logic" 2025-11-05 12:04:00 +01:00
Jeppe Bundgaard 719cb85012 "Add ReturnToOwnAccount.vue for managing superuser token redirection, enhance permissions handling with virtual permissions support, and update NavigationMenuItemsGlobal.vue with new link for account return" 2025-11-05 10:18:48 +01:00
Jeppe Bundgaard d6c5c03e85 "Enhance OrdersTable.vue with PO field visibility logic, updated invoice collection display, and column adjustments for improved UI clarity and functionality" 2025-11-05 09:17:25 +01:00
Jeppe Bundgaard c3c10736cb "Improve MyBookingsBook.vue department selection with focus auto-handling for vehicle inputs, enhanced keyboard accessibility, and streamlined UI interactions" 2025-11-04 18:46:47 +01:00
Jeppe Bundgaard e6f732c1c0 "Enhance SelectProductsFormPOS.vue with dynamic layout switching, auto-scroll for selected products, and improved grid/scroll behaviors in product selection flows" 2025-11-04 18:26:17 +01:00
Jeppe Bundgaard bf788b5fbb "Refactor MyBookingsBook.vue to improve department and vehicle selection flows, enhance UI with conditional templates, and reorganize date/time input logic for better usability" 2025-11-04 17:25:38 +01:00
Jeppe Bundgaard 7e7fbdc962 "Enhance MyBookingsBook.vue with quantity handling for addons, improved basket calculations, and updated UI display for better clarity and accuracy in booking flow" 2025-11-04 17:02:00 +01:00
Jeppe Bundgaard a9369e9d22 "Add step 5 to NewTimeBookingFlow, enhance category/product watches, and implement fullscreen summary in MyBookingsBook.vue for improved booking flow UX" 2025-11-04 16:40:09 +01:00
Jeppe Bundgaard 05791bb54a "Refine addon handling in MyBookingsBook.vue to separate interior wash logic, enhance addon filtering, and update UI with visibleAddons for better usability" 2025-11-04 15:05:53 +01:00
Jeppe Bundgaard c3d9bef414 "Enhance product selection UI in MyBookingsBook.vue with computed price labels, button state updates, and improved display logic for interior and exterior wash addons" 2025-11-04 14:59:46 +01:00
Jeppe Bundgaard 35cd9b6682 "Remove unnecessary console warnings and enhance product handling logic with immutability improvements, cleaner addon filtering, and updated basket management in POS and booking flows" 2025-11-04 14:55:15 +01:00
Jeppe Bundgaard e3b9286a5d "Refactor POS and booking flow for dynamic basket updates, improved product selection logic, and enhanced UI interactions with guarded event handling and detailed basket displays" 2025-11-04 14:30:39 +01:00
Jeppe Bundgaard 89eb3ea660 "Refactor and enhance product and basket handling in POS and booking flow with improved selection logic, dynamic basket updates, and refined UI interactions" 2025-11-04 12:39:57 +01:00
Jeppe B 3fb6ebe095 Merge pull request #33 from copenhagentruckwash/pwa
Add mobile POS functionality and refine workflow enhancements
2025-10-29 16:00:33 +01:00
Jeppe B 2e535fc00e Merge pull request #32 from copenhagentruckwash/pwa
Pwa
2025-10-28 21:41:53 +01:00
Jeppe Bundgaard f22c5204f3 Update FormDisplay to support multiple select fields with is-multiple class. 2025-08-05 11:32:41 +02:00
468 changed files with 40137 additions and 4667 deletions
+1
View File
@@ -13,6 +13,7 @@ dist
dist-ssr
coverage
*.local
dev-dist
/cypress/videos/
/cypress/screenshots/
+77
View File
@@ -0,0 +1,77 @@
# Project Guidelines
## Project Overview
This repository contains a Vue 3 single-page application built with Vite. It powers dashboard-style pages and modules such as bookings and POS flows. The UI is primarily styled with Bulma. The app leverages libraries like FullCalendar and Chart.js for scheduling and analytics.
- Tech stack: Vue 3, Vite 5, Vue Router, Vuex, Bulma, FullCalendar, Chart.js
- Notable integrations: toast notifications, QR/Barcode utilities, and PWA/devtools plugins
- Target: modern browsers and mobile WebView contexts
Local development
- Requirements: Node.js 18+ (recommended to match Vite 5), npm 9+
- Install dependencies: `npm install`
- Start dev server: `npm run dev` (default at http://localhost:5173)
Build and preview
- Production build: `npm run build` (output in `dist/`)
- Preview build locally: `npm run preview`
Testing
- Framework: WebdriverIO (wdio) with Vite service; Appium available for mobile testing
- Common commands:
- `npm run wdio` — run test suite per `wdio.conf.js`
- `npm run test:mobile` — run tests excluding Safari
- `npm run test:android` — run Android-targeted specs
- Prerequisites for mobile tests may include Android SDK/emulator or iOS tooling and Appium drivers
Environment configuration
- Place environment variables in `.env.*` files (Vite loads `VITE_`-prefixed vars into the client)
See the sections below for code style and detailed project structure.
## Code Style and Conventions
### Vue Components
- Use PascalCase for component names (e.g., `UserProfile.vue`)
- Implement Single File Components (SFC) pattern
- Keep components focused and single-responsibility
- Use composition API for new components
- Document component props and events
### JavaScript/TypeScript
- Use ES6+ features
- Maintain consistent naming conventions (camelCase for variables/methods)
- Document complex logic with clear comments
- Use TypeScript for type safety when possible
## Project Structure
### Root Directory Structure
- `/src` - Source code files
- `/assets` - Static assets (images, fonts, etc.)
- `/components` - Reusable Vue components
- `/views` - Page components
- `/router` - Vue Router configuration
- `/store` - Vuex store modules
- `/services` - API and external service integrations
- `/utils` - Utility functions and helpers
- `/styles` - Global styles and variables
### Configuration Files
- `vite.config.js` - Vite configuration
- `.env.*` - Environment variables
- `package.json` - Project dependencies and scripts
- `jsconfig.json` - JavaScript/TypeScript tooling and path aliases
### File Naming Conventions
- Components: PascalCase (e.g., `UserProfile.vue`)
- Utilities: camelCase (e.g., `formatDate.js`)
- Store modules: camelCase (e.g., `userStore.js`)
- Style files: kebab-case (e.g., `main-styles.scss`)
- Test files: `*.spec.js` or `*.test.js`
+16
View File
@@ -27,3 +27,19 @@ npm run dev
```sh
npm run build
```
## Bubblewrap (TWA) Build and Install
To build and install the Trusted Web Activity (TWA) using Bubblewrap, use the following commands:
### Build the TWA
```sh
bubblewrap build
```
### Install the TWA on a connected device
```sh
bubblewrap install
```
Vendored
+1 -1
View File
@@ -3,7 +3,7 @@
interface ImportMetaEnv {
readonly VITE_BUILD_DATE: string,
readonly VITE_APP_VERSION: string,
readonly VITE_GIT_COMMIT_HASH: string,
readonly VITE_COMMIT_HASH: string,
readonly VITE_IS_DEV: string,
}
interface ImportMeta {
+16 -10
View File
@@ -1,16 +1,22 @@
<!DOCTYPE html>
<html lang="" class="theme-light" data-theme="light">
<head>
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<link rel="stylesheet" href="bulma/css/bulma.min.css">
<link rel="icon" type="image/png" href="/favicons/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicons/favicon.svg" />
<link rel="shortcut icon" href="/favicons/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/favicons/apple-touch-icon.png" />
<link rel="manifest" href="/manifest.json" />
<meta name="apple-mobile-web-app-title" content="Truck Wash Kundeportal" />
<link rel="stylesheet" href="bulma/css/bulma.min.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.1/css/all.min.css" integrity="sha512-5Hs3dF2AEPkpNAR7UiOHba+lRSJNeM2ECkwxUIxC1Q/FLycGTbNapWXB4tP889k5T5Ju8fs4b1P5z/iB4nMfSQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<title>Truck Wash</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
<meta name="mobile-web-app-capable" content="yes">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.1/css/all.min.css" integrity="sha512-5Hs3dF2AEPkpNAR7UiOHba+lRSJNeM2ECkwxUIxC1Q/FLycGTbNapWXB4tP889k5T5Ju8fs4b1P5z/iB4nMfSQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<title>Truck Wash Kundeportal</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
+7437
View File
File diff suppressed because it is too large Load Diff
+114 -5
View File
@@ -26,6 +26,7 @@
"@vueuse/integrations": "^13.7.0",
"animate.css": "^4.1.1",
"axios": "^1.7.8",
"buefy": "^3.0.3",
"bulma": "^1.0.2",
"bulma-block-list": "^1.1.0",
"bulma-calendar": "^7.1.1",
@@ -46,6 +47,7 @@
"vue": "^3.5.12",
"vue-cal": "^5.0.1-rc.20",
"vue-chartjs": "^5.3.2",
"vue-i18n": "^9.14.5",
"vue-popperjs": "^2.3.0",
"vue-qrcode-reader": "^5.7.3",
"vue-router": "^4.5.0",
@@ -902,6 +904,7 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz",
"integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
@@ -2896,6 +2899,7 @@
"version": "6.1.17",
"resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.17.tgz",
"integrity": "sha512-0W7lnIrv18ruJ5zeWBeNZXO8qCWlzxDdp9COFEsZnyNjiEhUVnrW/dPbjRKYpL0edGG0/Lhs0ghp1z/5ekt8ZA==",
"peer": true,
"dependencies": {
"preact": "~10.12.1"
}
@@ -3776,6 +3780,50 @@
}
}
},
"node_modules/@intlify/core-base": {
"version": "9.14.5",
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.5.tgz",
"integrity": "sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==",
"license": "MIT",
"dependencies": {
"@intlify/message-compiler": "9.14.5",
"@intlify/shared": "9.14.5"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/message-compiler": {
"version": "9.14.5",
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.5.tgz",
"integrity": "sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==",
"license": "MIT",
"dependencies": {
"@intlify/shared": "9.14.5",
"source-map-js": "^1.0.2"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/shared": {
"version": "9.14.5",
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.5.tgz",
"integrity": "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==",
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@isaacs/balanced-match": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
@@ -4012,6 +4060,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.12.tgz",
"integrity": "sha512-xcmww1O/JFP2MrlGUMd3Q78S3Qu6W3mYTXYuIqFq33EorgYHV/HqymHfXy9GjiCJ7OI+7lWx6nYFOzU7M4rd1Q==",
"peer": true,
"dependencies": {
"@jimp/core": "^0.22.12"
}
@@ -4540,6 +4589,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.12.tgz",
"integrity": "sha512-xslz2ZoFZOPLY8EZ4dC29m168BtDx95D6K80TzgUi8gqT7LY6CsajWO0FAxDwHz6h0eomHMfyGX0stspBrTKnQ==",
"peer": true,
"dependencies": {
"@jimp/utils": "^0.22.12"
},
@@ -4551,6 +4601,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.22.12.tgz",
"integrity": "sha512-S0vJADTuh1Q9F+cXAwFPlrKWzDj2F9t/9JAbUvaaDuivpyWuImEKXVz5PUZw2NbpuSHjwssbTpOZ8F13iJX4uw==",
"peer": true,
"dependencies": {
"@jimp/utils": "^0.22.12"
},
@@ -4573,6 +4624,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.12.tgz",
"integrity": "sha512-xImhTE5BpS8xa+mAN6j4sMRWaUgUDLoaGHhJhpC+r7SKKErYDR0WQV4yCE4gP+N0gozD0F3Ka1LUSaMXrn7ZIA==",
"peer": true,
"dependencies": {
"@jimp/utils": "^0.22.12",
"tinycolor2": "^1.6.0"
@@ -4613,6 +4665,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.12.tgz",
"integrity": "sha512-FNuUN0OVzRCozx8XSgP9MyLGMxNHHJMFt+LJuFjn1mu3k0VQxrzqbN06yIl46TVejhyAhcq5gLzqmSCHvlcBVw==",
"peer": true,
"dependencies": {
"@jimp/utils": "^0.22.12"
},
@@ -4853,6 +4906,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.12.tgz",
"integrity": "sha512-3NyTPlPbTnGKDIbaBgQ3HbE6wXbAlFfxHVERmrbqAi8R3r6fQPxpCauA8UVDnieg5eo04D0T8nnnNIX//i/sXg==",
"peer": true,
"dependencies": {
"@jimp/utils": "^0.22.12"
},
@@ -4864,6 +4918,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.12.tgz",
"integrity": "sha512-9YNEt7BPAFfTls2FGfKBVgwwLUuKqy+E8bDGGEsOqHtbuhbshVGxN2WMZaD4gh5IDWvR+emmmPPWGgaYNYt1gA==",
"peer": true,
"dependencies": {
"@jimp/utils": "^0.22.12"
},
@@ -4878,6 +4933,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.12.tgz",
"integrity": "sha512-dghs92qM6MhHj0HrV2qAwKPMklQtjNpoYgAB94ysYpsXslhRTiPisueSIELRwZGEr0J0VUxpUY7HgJwlSIgGZw==",
"peer": true,
"dependencies": {
"@jimp/utils": "^0.22.12"
},
@@ -8249,6 +8305,7 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"devOptional": true,
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -8272,6 +8329,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -14254,6 +14312,7 @@
"url": "https://github.com/sponsors/ai"
}
],
"peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001688",
"electron-to-chromium": "^1.5.73",
@@ -14267,6 +14326,18 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/buefy": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/buefy/-/buefy-3.0.3.tgz",
"integrity": "sha512-//QfIDgbhmpn+mFszD/lEwhlphA+hQavJED2XwjlzqS/bpxYjk/Rnj36X08QVbqiH3lt4a+G357uzsZczc7JTg==",
"license": "MIT",
"dependencies": {
"bulma": "^1.0.4"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
@@ -14323,9 +14394,11 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
"node_modules/bulma": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bulma/-/bulma-1.0.2.tgz",
"integrity": "sha512-D7GnDuF6seb6HkcnRMM9E739QpEY9chDzzeFrHMyEns/EXyDJuQ0XA0KxbBl/B2NTsKSoDomW61jFGFaAxhK5A=="
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/bulma/-/bulma-1.0.4.tgz",
"integrity": "sha512-Ffb6YGXDiZYX3cqvSbHWqQ8+LkX6tVoTcZuVB3lm93sbAVXlO0D6QlOTMnV6g18gILpAXqkG2z9hf9z4hCjz2g==",
"license": "MIT",
"peer": true
},
"node_modules/bulma-block-list": {
"version": "1.1.0",
@@ -14540,7 +14613,8 @@
"resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz",
"integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==",
"devOptional": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/chardet": {
"version": "0.7.0",
@@ -14551,6 +14625,7 @@
"version": "4.4.7",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.7.tgz",
"integrity": "sha512-pwkcKfdzTMAU/+jNosKhNL2bHtJc/sSmYgVbuGTEDhzkrhmyihmP7vUc/5ZK9WopidMDHNe3Wm7jOd/WhuHWuw==",
"peer": true,
"dependencies": {
"@kurkle/color": "^0.3.0"
},
@@ -15427,6 +15502,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"peer": true,
"engines": {
"node": ">=12"
}
@@ -15592,6 +15668,7 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.16.tgz",
"integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==",
"deprecated": "Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.",
"peer": true,
"dependencies": {
"@vue/compiler-sfc": "2.7.16",
"csstype": "^3.1.0"
@@ -16561,6 +16638,7 @@
"integrity": "sha512-/XxRRR90gNSuNf++w1jOQjhC5LE9Ixf/iAQctVb/miEI3dwzPZTuG27/omoh5REfSLDoPXofM84vAH/ULtz35g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vitest/snapshot": "^3.2.4",
"deep-eql": "^5.0.2",
@@ -18055,7 +18133,8 @@
"node_modules/idb-keyval": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.2.tgz",
"integrity": "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg=="
"integrity": "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==",
"peer": true
},
"node_modules/ieee754": {
"version": "1.2.1",
@@ -18918,6 +18997,7 @@
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
@@ -19927,6 +20007,7 @@
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"peer": true,
"engines": {
"node": "*"
}
@@ -21232,6 +21313,7 @@
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
"license": "MIT",
"peer": true,
"dependencies": {
"dijkstrajs": "^1.0.1",
"pngjs": "^5.0.0",
@@ -21916,6 +21998,7 @@
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz",
"integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/estree": "1.0.8"
},
@@ -22128,6 +22211,7 @@
"version": "1.81.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.81.1.tgz",
"integrity": "sha512-VNLgf4FC5yFyKwAumAAwwNh8X4SevlVREq3Y8aDZIkm0lI/zO1feycMXQ4hn+eB6FVhRbleSQ1Yb/q8juSldTA==",
"peer": true,
"dependencies": {
"chokidar": "^4.0.0",
"immutable": "^5.0.2",
@@ -22148,6 +22232,7 @@
"resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.81.0.tgz",
"integrity": "sha512-uZQ2Faxb1oWBHpeSSzjxnhClbMb3QadN0ql0ZFNuqWOLUxwaVhrMlMhPq6TDPbbfDUjihuwrMCuy695Bgna5RA==",
"devOptional": true,
"peer": true,
"dependencies": {
"@bufbuild/protobuf": "^2.0.0",
"buffer-builder": "^0.2.0",
@@ -24847,6 +24932,7 @@
"version": "5.4.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
"integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
"peer": true,
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
@@ -25034,6 +25120,7 @@
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz",
"integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.13",
"@vue/compiler-sfc": "3.5.13",
@@ -25070,6 +25157,27 @@
"vue": "^3.0.0-0 || ^2.7.0"
}
},
"node_modules/vue-i18n": {
"version": "9.14.5",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.5.tgz",
"integrity": "sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==",
"deprecated": "v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html",
"license": "MIT",
"dependencies": {
"@intlify/core-base": "9.14.5",
"@intlify/shared": "9.14.5",
"@vue/devtools-api": "^6.5.0"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/vue-popperjs": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/vue-popperjs/-/vue-popperjs-2.3.0.tgz",
@@ -26371,6 +26479,7 @@
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz",
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
"dev": true,
"peer": true,
"bin": {
"rollup": "dist/bin/rollup"
},
+6 -1
View File
@@ -6,10 +6,13 @@
"scripts": {
"dev": "vite",
"build": "vite build",
"build:dev": "vite build --mode development",
"preview": "vite preview",
"test:mobile": "wdio run wdio.conf.js --exclude safari",
"test:android": "wdio run wdio.conf.js --spec test/specs/*.spec.js",
"wdio": "wdio run ./wdio.conf.js"
"wdio": "wdio run ./wdio.conf.js",
"twa:build": "bubblewrap build",
"twa:update": "bubblewrap update"
},
"dependencies": {
"@azure/msal-browser": "^4.12.0",
@@ -30,6 +33,7 @@
"@vueuse/integrations": "^13.7.0",
"animate.css": "^4.1.1",
"axios": "^1.7.8",
"buefy": "^3.0.3",
"bulma": "^1.0.2",
"bulma-block-list": "^1.1.0",
"bulma-calendar": "^7.1.1",
@@ -50,6 +54,7 @@
"vue": "^3.5.12",
"vue-cal": "^5.0.1-rc.20",
"vue-chartjs": "^5.3.2",
"vue-i18n": "^9.14.5",
"vue-popperjs": "^2.3.0",
"vue-qrcode-reader": "^5.7.3",
"vue-router": "^4.5.0",
+12
View File
@@ -0,0 +1,12 @@
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "io.truckwash.twa",
"sha256_cert_fingerprints": [
"29:56:F7:8D:BD:A0:2E:A9:32:82:97:28:A3:E2:65:16:23:73:DD:2C:16:F6:7A:97:AD:63:27:14:5C:8C:FB:89"
]
}
}
]
Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

+25
View File
@@ -0,0 +1,25 @@
{
"name": "Truck Wash Kundeportal",
"short_name": "Truck Wash",
"description": "Access your Truck Wash accounts and transactions from anywhere.",
"icons": [
{
"src": "/favicons/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/favicons/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
}
],
"start_url": "/",
"display": "standalone",
"background_color": "#0787bb",
"theme_color": "#063651",
"orientation": "portrait",
"scope": "/"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+46
View File
@@ -0,0 +1,46 @@
#-------------------------------------------------------------------------------#
# Qodana analysis is configured by qodana.yaml file #
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
#-------------------------------------------------------------------------------#
#################################################################################
# WARNING: Do not store sensitive information in this file, #
# as its contents will be included in the Qodana report. #
#################################################################################
version: "1.0"
#Specify inspection profile for code analysis
profile:
name: qodana.starter
#Enable inspections
#include:
# - name: <SomeEnabledInspectionId>
#Disable inspections
#exclude:
# - name: <SomeDisabledInspectionId>
# paths:
# - <path/where/not/run/inspection>
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
#bootstrap: sh ./prepare-qodana.sh
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
#plugins:
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
# Quality gate. Will fail the CI/CD pipeline if any condition is not met
# severityThresholds - configures maximum thresholds for different problem severities
# testCoverageThresholds - configures minimum code coverage on a whole project and newly added code
# Code Coverage is available in Ultimate and Ultimate Plus plans
#failureConditions:
# severityThresholds:
# any: 15
# critical: 5
# testCoverageThresholds:
# fresh: 70
# total: 50
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
linter: jetbrains/qodana-js:2025.3
+19 -6
View File
@@ -5,6 +5,11 @@ import { SessionUser } from "@/components/session/token/SessionUser.vue";
// Set the favicon to the /favicon.ico file
import { useHead } from "@vueuse/head";
import favicon from '@/assets/favicon.ico';
import VersionCheck from "@/components/global/VersionCheck.vue";
import {useRoute} from "vue-router";
import LayoutV2 from "@/components/page/wrappers/LayoutV2.vue";
const route = useRoute();
useHead({
link: [
@@ -19,13 +24,21 @@ onMounted(() => {
</script>
<template>
<header>
</header>
<main>
<DefaultPageWrapper>
<router-view />
</DefaultPageWrapper>
<LayoutV2 v-if="route.meta?.template === 'with-header'">
<router-view />
</LayoutV2>
<main v-else-if="route.meta?.template === 'clear-main'">
<router-view />
</main>
<template v-else>
<header></header>
<main>
<DefaultPageWrapper>
<VersionCheck/>
<router-view />
</DefaultPageWrapper>
</main>
</template>
</template>
<style scoped>
Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

+86 -1
View File
@@ -13,4 +13,89 @@ textarea.has-sharp-edges {
min-width: 250px;
min-height: 100px;
text-align: center;
}
}
/*body .has-text-grey {*/
/* color: #333333 !important;*/
/*}*/
:root {
/* 1. Primary Brand Colors (HSL) */
--bulma-primary-h: 197deg !important;
--bulma-primary-l: 35% !important;
--bulma-link-h: 224deg !important;
--bulma-link-l: 13% !important;
--bulma-info-h: 215deg !important;
--bulma-info-l: 56% !important;
--bulma-warning-h: 44deg !important;
--bulma-warning-l: 50% !important;
--bulma-dark-h: 0 !important;
--bulma-dark-s: 0% !important;
--bulma-dark-l: 20% !important;
--bulma-dark-dark-invert-l: 100% !important;
/* 2. Typography - Avenir */
--bulma-family-primary: "Avenir", sans-serif;
--bulma-body-size: 11pt !important;
--bulma-title-weight: 800 !important;
--bulma-body-weight: 400 !important;
/* 3. Strict Hierarchy */
--bulma-title-size: 13pt !important;
/* 4. Brand Specific Restrictions */
--bulma-shadow: none !important;
--bulma-box-shadow: none !important;
--bulma-card-shadow: none !important;
--bulma-skeleton-background: hsla(197 100% 35% / 0.70) !important;
}
/*:root {*/
/* --bulma-primary-h: 207deg !important;*/
/* --bulma-primary-s: 60% !important;*/
/* --bulma-primary-l: 19% !important;*/
/* --bulma-link-h: 200deg !important;*/
/* --bulma-link-s: 80% !important;*/
/* --bulma-link-l: 41% !important;*/
/* --bulma-info-h: 215deg !important;*/
/* --bulma-info-l: 56% !important;*/
/* --bulma-success-s: 84% !important;*/
/* --bulma-warning-h: 44deg !important;*/
/* --bulma-warning-l: 50% !important;*/
/*}*/
.button-same-width {
min-width: 124px !important;
}
/*:root {*/
/* --bulma-link-h: 200 !important;*/
/* --bulma-link-s: 80% !important;*/
/* --bulma-link-l: 41% !important;*/
/* --bulma-warning-h: 44 !important;*/
/* --bulma-warning-s: 100% !important;*/
/* --bulma-warning-l: 50% !important;*/
/*}*/
/*:root {*/
/* --bulma-menu-item-h: var(--bulma-menu-item-selected-h);*/
/* --bulma-menu-item-s: var(--bulma-menu-item-selected-s);*/
/* --bulma-menu-item-l: var(--bulma-menu-item-selected-l);*/
/* --bulma-menu-item-background-l: var(--bulma-menu-item-selected-background-l);*/
/* --bulma-menu-item-color-l: var(--bulma-menu-item-selected-color-l);*/
/* !* Set the base H, S, and L for the menu item *!*/
/* --bulma-menu-item-h: 200;*/
/* --bulma-menu-item-s: 80%;*/
/* !* Set the base lightness to 41% (from your hex) *!*/
/* --bulma-menu-item-background-l: 41%;*/
/* !* Set delta to 0% if you want exactly #1584BC *!*/
/* !* Or use a small % if you want it to brighten/darken on state changes *!*/
/* --bulma-menu-item-background-l-delta: 0%;*/
/*}*/
/*button.is-link {*/
/* background-color: #1584BC;*/
/*}*/
Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

+155
View File
@@ -0,0 +1,155 @@
<script setup>
import { useI18n } from 'vue-i18n'
import { ref, watch } from 'vue'
const { locale } = useI18n()
const isOpen = ref(false)
// Language options
const languages = [
{ code: 'da', name: 'Dansk', flag: '🇩🇰' },
{ code: 'en', name: 'English', flag: '🇬🇧' }
]
// Get current language
const currentLanguage = ref(languages.find(lang => lang.code === locale.value) || languages[0])
// Toggle dropdown
const toggleDropdown = () => {
isOpen.value = !isOpen.value
}
// Close dropdown when clicking outside
const closeDropdown = () => {
isOpen.value = false
}
// Change language
const changeLanguage = (lang) => {
locale.value = lang.code
currentLanguage.value = lang
localStorage.setItem('locale', lang.code)
isOpen.value = false
}
// Watch locale changes
watch(locale, (newLocale) => {
currentLanguage.value = languages.find(lang => lang.code === newLocale) || languages[0]
})
</script>
<template>
<div class="language-switcher" @blur="closeDropdown" tabindex="0">
<button class="language-button" @click="toggleDropdown" type="button">
<span class="flag">{{ currentLanguage.flag }}</span>
<span class="name">{{ currentLanguage.name }}</span>
<span class="arrow" :class="{ 'open': isOpen }"></span>
</button>
<div v-if="isOpen" class="language-dropdown">
<button
v-for="lang in languages"
:key="lang.code"
@click="changeLanguage(lang)"
:class="{ 'active': lang.code === locale }"
class="language-option"
type="button"
>
<span class="flag">{{ lang.flag }}</span>
<span class="name">{{ lang.name }}</span>
</button>
</div>
</div>
</template>
<style scoped>
.language-switcher {
position: relative;
display: inline-block;
}
.language-button {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background-color: transparent;
border: 1.5px solid rgba(255, 255, 255, 0.3);
border-radius: 8px;
color: #fff;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
}
.language-button:hover {
background-color: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.5);
}
.language-button .flag {
font-size: 18px;
line-height: 1;
}
.language-button .name {
line-height: 1;
}
.language-button .arrow {
font-size: 10px;
transition: transform 0.3s ease;
line-height: 1;
}
.language-button .arrow.open {
transform: rotate(180deg);
}
.language-dropdown {
position: absolute;
top: calc(100% + 8px);
right: 0;
background-color: #fff;
border: 1.5px solid #e0e0e0;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
overflow: hidden;
z-index: 1000;
min-width: 150px;
}
.language-option {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
padding: 12px 16px;
background-color: transparent;
border: none;
color: #13324C;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: background-color 0.2s ease;
text-align: left;
}
.language-option:hover {
background-color: #f5f5f5;
}
.language-option.active {
background-color: #e8f4fd;
color: #1976d2;
}
.language-option .flag {
font-size: 18px;
line-height: 1;
}
.language-option .name {
line-height: 1;
}
</style>
+3 -1
View File
@@ -3,7 +3,9 @@ import { ref } from 'vue'
import PageTitle from "@/components/global/PageTitle.vue";
import CategoriesPagination from "@/components/displays/pagination/models/SuperUserDashboard/CategoriesPagination.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const isLoading = ref(false);
/**
@@ -26,7 +28,7 @@ const refreshData = () => {
<span class="icon">
<i class="fas fa-plus"></i>
</span>
<span>Opret {{ SessionUser.objects.categories.meta.labels.single }}</span>
<span>{{ t('superuser.pages.categories.create') }} {{ SessionUser.objects.categories.meta.labels.single }}</span>
</button>
</template>
</PageTitle>
+5 -2
View File
@@ -3,17 +3,20 @@ import UsersPagination from "@/components/displays/pagination/models/SuperUserDa
import PageTitle from "@/components/global/PageTitle.vue";
import { showCreateUserForm } from "@/components/forms/superUser/createUserForm.vue";
import CustomersPagination from "@/components/displays/pagination/models/SuperUserDashboard/CustomersPagination.vue";
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>
<template>
<div>
<PageTitle title="Kunder" subtitle="Her kan du se en liste over alle kunder i systemet">
<PageTitle :title="t('superuser.pages.customers.title')" :subtitle="t('superuser.pages.customers.subtitle')">
<template #buttons>
<!--<button class="button is-dark" @click="showCreateUserForm">
<span class="icon">
<i class="fas fa-plus"></i>
</span>
<span>Opret ny bruger</span>
<span>{{ t('superuser.pages.customers.create') }}</span>
</button> -->
</template>
</PageTitle>
+15 -4
View File
@@ -1,9 +1,20 @@
<script setup>
import { ref } from 'vue'
import { defineProps } from 'vue';
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { loadList } from "@/components/pagination/paginatedList.vue";
import PageTitle from "@/components/global/PageTitle.vue";
import DepartmentLanesPagination from "@/components/displays/pagination/models/SuperUserDashboard/DepartmentLanesPagination.vue";
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
// Define props
defineProps({
advancedView: {
type: Boolean,
default: false,
},
});
const redirect = (path) => {
window.location = path;
@@ -12,17 +23,17 @@ const redirect = (path) => {
<template>
<div>
<PageTitle title="Afdelingsbaner" subtitle="Her kan du se en liste over alle afdelingsbaner i systemet">
<PageTitle :title="t('superuser.pages.department_lanes.title')" :subtitle="t('superuser.pages.department_lanes.subtitle')">
<template #buttons>
<button class="button is-dark" @click="SessionUser.objects.department_lanes.showCreateObjectForm(() => loadList())">
<span class="icon">
<i class="fas fa-plus"></i>
</span>
<span>Opret ny afdelingsbane</span>
<span>{{ t('superuser.pages.department_lanes.create') }}</span>
</button>
</template>
</PageTitle>
<DepartmentLanesPagination auto-load="true" />
<DepartmentLanesPagination :auto-load="true" :advancedView="advancedView" />
</div>
</template>
+4 -2
View File
@@ -4,7 +4,9 @@ import { getDepartmentListData } from "@/components/session/Session.vue";
import { showCreateDepartmentForm } from "@/components/forms/superUser/createDepartmentForm.vue";
import PageTitle from "@/components/global/PageTitle.vue";
import DepartmentsPagination from "@/components/displays/pagination/models/SuperUserDashboard/DepartmentsPagination.vue";
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const tableData = ref([]);
getDepartmentListData().then((response) => {
@@ -18,13 +20,13 @@ const redirect = (path) => {
<template>
<div>
<PageTitle title="Afdelinger" subtitle="Her kan du se en liste over alle afdelinger i systemet">
<PageTitle :title="t('superuser.pages.departments.title')" :subtitle="t('superuser.pages.departments.subtitle')">
<template #buttons>
<button class="button is-dark" @click="showCreateDepartmentForm">
<span class="icon">
<i class="fas fa-plus"></i>
</span>
<span>Opret ny afdeling</span>
<span>{{ t('superuser.pages.departments.create') }}</span>
</button>
</template>
</PageTitle>
+2 -2
View File
@@ -6,7 +6,7 @@ import ShowErrorField from "@/components/global/ShowErrorField.vue";
import bulmaCalendar from "bulma-calendar";
import 'bulma-calendar/src/scss/index.scss';
import Swal from "sweetalert2";
import {IS_DEV} from "@/main.js";
import {IS_DEV} from "@/config.js";
const props = defineProps({
form_identifier: {
@@ -743,4 +743,4 @@ const debugGetForm = () => {
<style scoped>
</style>
</style>
@@ -4,23 +4,26 @@ import PageTitle from "@/components/global/PageTitle.vue";
import { showCreateNumberPlateScannerForm } from "@/components/forms/superUser/createNumberPlateScannerForm.vue";
import {showCreateNumberPlateScanForm} from "@/components/forms/superUser/createNumberPlateScan.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>
<template>
<div>
<PageTitle title="Nummerpladescannere" subtitle="Her kan du se en liste over alle nummerpladescannere i systemet">
<PageTitle :title="t('superuser.pages.scanners.title')" :subtitle="t('superuser.pages.scanners.subtitle')">
<template #buttons>
<button class="button is-dark" @click="showCreateNumberPlateScannerForm">
<span class="icon">
<i class="fas fa-plus"></i>
</span>
<span>Opret ny nummerpladescanner</span>
<span>{{ t('superuser.pages.scanners.create') }}</span>
</button>
<button class="ml-2 button is-warning" @click="showCreateNumberPlateScanForm" v-if="SessionUser.canAccessDeveloper()">
<span class="icon">
<i class="fas fa-bug"></i>
</span>
<span>Simuler scan</span>
<span>{{ t('superuser.pages.scanners.simulate_scan') }}</span>
</button>
</template>
</PageTitle>
+5 -2
View File
@@ -4,17 +4,20 @@ import PageTitle from "@/components/global/PageTitle.vue";
import { showCreateProductForm } from "@/components/forms/superUser/createProductForm.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { loadList } from "@/components/pagination/paginatedList.vue";
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>
<template>
<div>
<PageTitle title="Produkter" subtitle="Her kan du se en liste over alle produkter i systemet">
<PageTitle :title="t('superuser.pages.products.title')" :subtitle="t('superuser.pages.products.subtitle')">
<template #buttons>
<button class="button is-dark" @click="SessionUser.objects.products.showCreateObjectForm(loadList)">
<span class="icon">
<i class="fas fa-plus"></i>
</span>
<span>Opret nyt produkt</span>
<span>{{ t('superuser.pages.products.create') }}</span>
</button>
</template>
</PageTitle>
+19
View File
@@ -0,0 +1,19 @@
<script setup>
import SubuserGrantsPagination from "@/components/displays/pagination/models/SuperUserDashboard/SubuserGrantsPagination.vue";
import PageTitle from "@/components/global/PageTitle.vue";
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>
<template>
<div>
<PageTitle :title="t('objects.subuser_grants.title')" :subtitle="t('objects.subuser_grants.subtitle')">
</PageTitle>
<SubuserGrantsPagination auto-load="true" />
</div>
</template>
<style scoped>
</style>
+19
View File
@@ -0,0 +1,19 @@
<script setup>
import SubusersPagination from "@/components/displays/pagination/models/SuperUserDashboard/SubusersPagination.vue";
import PageTitle from "@/components/global/PageTitle.vue";
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>
<template>
<div>
<PageTitle :title="t('superuser.pages.subusers.title')" :subtitle="t('superuser.pages.subusers.subtitle')">
</PageTitle>
<SubusersPagination auto-load="true" />
</div>
</template>
<style scoped>
</style>
+5 -2
View File
@@ -2,17 +2,20 @@
import UsersPagination from "@/components/displays/pagination/models/SuperUserDashboard/UsersPagination.vue";
import PageTitle from "@/components/global/PageTitle.vue";
import { showCreateUserForm } from "@/components/forms/superUser/createUserForm.vue";
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>
<template>
<div>
<PageTitle title="Medarbejdere" subtitle="Her kan du se en liste over alle medarbejdere i systemet">
<PageTitle :title="t('superuser.pages.employees.title')" :subtitle="t('superuser.pages.employees.subtitle')">
<template #buttons>
<button class="button is-dark" @click="showCreateUserForm">
<span class="icon">
<i class="fas fa-plus"></i>
</span>
<span>Opret ny medarbejder</span>
<span>{{ t('superuser.pages.employees.create') }}</span>
</button>
</template>
</PageTitle>
+14 -2
View File
@@ -5,7 +5,10 @@ import { showPopper, popperBoxProductAddonHtml, removePopperIfOpen } from "@/com
import Swal from "sweetalert2";
import CustomerProductDiscountDisplay
from "@/components/displays/department/pos/displays/CustomerProductDiscountDisplay.vue";
import SessionUser from "@/components/session/token/SessionUser.vue";
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const expandIcon = ref(null);
const emit = defineEmits(['add-to-cart', 'addToCartProduct']);
@@ -29,6 +32,10 @@ const props = defineProps({
customerDiscounts: Array,
customerAttributes: Array,
compact: Boolean,
showPrices: {
type: Boolean,
default: true
}
});
const isAddonSelected = (productId, addonId) => {
@@ -200,7 +207,12 @@ const orderByOrderPriority = (addons) => {
<!-- Title -->
<div class="p-2">
<p class="title is-6 mb-0 text-automatic-shrink" style="font-size: small">{{ name }}</p>
<p class="subtitle is-6 mb-0 has-text-grey-light" style="font-size: smaller"><small>579 kr</small></p>
<template v-if="showPrices">
<p class="subtitle is-6 mb-0 has-text-grey-light" style="font-size: smaller"><small>{{ price }} kr</small></p>
</template>
<template v-else>
<p class="subtitle is-6 mb-0 has-text-grey-light" style="font-size: smaller"><br/></p>
</template>
</div>
</div>
<!-- Price -->
@@ -324,7 +336,7 @@ const orderByOrderPriority = (addons) => {
<span class="icon is-small">
<i class="fas fa-cart-plus"></i>
</span>
<span>Tilføj til kurv</span>
<span>{{ t('pos.add_to_cart') }}</span>
</button>
</div>
</div>
@@ -1,5 +1,5 @@
<script setup>
import { defineProps, defineEmits } from 'vue';
import {defineProps, defineEmits, onMounted, computed} from 'vue';
import { useSlots } from "vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import ActionSettingsWheelItem from "@/components/displays/buttons/ActionSettingsWheelItem.vue";
@@ -7,6 +7,10 @@ import Swal from "sweetalert2";
import { ref } from "vue";
import CustomerModal from "@/components/displays/modals/CustomerModal.vue";
import ActionSettingsWheelItemLabel from "@/components/displays/buttons/ActionSettingsWheelItemLabel.vue";
import ShowErrorField from "@/components/global/ShowErrorField.vue";
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const emit = defineEmits(['deleted']);
const emitDeleted = () => {
emit('deleted');
@@ -36,19 +40,72 @@ const props = defineProps({
type: String,
default: null
},
reg_2: {
type: String,
default: null
},
order_booking_id: {
type: Number,
default: null
},
customer_number: {
type: Number,
default: null
},
refreshFunction: {
type: Function,
default: () => {
console.warn("Refresh function is not defined");
}
},
department_id: {
type: Number,
default: null
},
attachments: {
type: Array,
default: () => []
},
displayActionsDirectly: {
type: Boolean,
default: false
},
department_lane_id: {
type: Number,
default: null
},
subuserGrant: {
type: Object,
default: null
},
});
const customerModalVisible = ref(false);
const userIdFromCustomerNumber = ref(null);
const getUserId = () => {
if (!!props.customer_number) {
SessionUser.adminUser.customers.fromCustomerNumber.getUserId(props.customer_number).then((response) => {
userIdFromCustomerNumber.value = response.data.data.user_id;
console.warn(userIdFromCustomerNumber.value);
}).catch((error) => {
console.error(error);
});
} else if (!!props.user_id) {
userIdFromCustomerNumber.value = props.user_id;
} else {
userIdFromCustomerNumber.value = null;
}
}
const showSetCustomerPassword = (userId) => {
Swal.fire({
title: 'Skift kodeord',
html: '<input id="swal-input1" class="swal2-input" placeholder="Indtast kodeord">',
title: t('admin.pos.settings_wheel.change_password'),
html: `<input id="swal-input1" class="swal2-input" placeholder="${t('admin.pos.settings_wheel.enter_password')}">`,
focusConfirm: false,
preConfirm: () => {
const password = document.getElementById('swal-input1').value;
if (password === '') {
Swal.showValidationMessage('Indtast venligst et kodeord');
Swal.showValidationMessage(t('admin.pos.settings_wheel.please_enter_password'));
}
return password;
}
@@ -65,14 +122,14 @@ const showSetCustomerPassword = (userId) => {
}
).then(() => {
Swal.fire({
title: 'Kodeord ændret',
text: 'Kodeordet er blevet ændret.',
title: t('admin.pos.settings_wheel.password_changed'),
text: t('admin.pos.settings_wheel.password_changed_text'),
icon: 'success',
});
}).catch((error) => {
Swal.fire({
title: 'Fejl',
text: 'Der opstod en fejl under ændring af kodeordet.',
title: t('admin.pos.settings_wheel.error'),
text: t('admin.pos.settings_wheel.error_changing_password'),
icon: 'error',
});
});
@@ -85,163 +142,723 @@ const redirectSuperUserInvoiceCollectionPage = (invoiceCollectionId) => {
window.open(`/superuser/invoices/${invoiceCollectionId}`, '_blank');
};
const redirectDepartmentOrderPage = async (orderId) => {
const redirectDepartmentOrderPage = async (orderId, newTab = false) => {
// Send the user to the order page (In a new tab)
// Get the department id from the order
await SessionUser.objects.orders.functions.get_department_id(props.order_id).then((response) => {
console.warn(response);
// Get the department id from the response
// Send the user to the order page (In a new tab)
window.open(`/admin/${response}/modules/pos/orders/${orderId}`, '_blank');
SessionUser.functions.redirectTo.department(response, 'modules/pos/orders/' + orderId, !!newTab);
}).catch((error) => {
console.error(error);
});
};
const hasUser = () => {
if (userIdFromCustomerNumber.value) {
return true;
}
let hasUser = false;
if (!!props.user_id) {
hasUser = true;
userIdFromCustomerNumber.value = props.user_id;
}
if (!!props.customer_number) {
hasUser = true;
getUserId();
}
return hasUser;
}
const attachmentsFromOrder = ref([]);
const attachmentsFromOrderError = ref(null);
onMounted(() => {
if (props.order_id) {
SessionUser.objects.orders.functions.fetchAttachments(props.order_id).then((response) => {
/**
* [
* {
* "id": 4396,
* "object_type": "`orders`",
* "object_id": 30242,
* "content": {
* "image": null,
* "document": "pdf_6912ea1886f15.pdf",
* "relation": null,
* "other": "wash_certificate"
* },
* "created_at": "2025-11-11 08:47:36",
* "updated_at": null,
* "deleted_at": null
* }
* ]
*/
// Validate the response is an array
if (!Array.isArray(response)) {
response = [];
}
attachmentsFromOrder.value = response;
}).catch((error) => {
const parsedErrorMessage = SessionUser.functions.parseErrorMessage(error);
console.warn('Error fetching attachments from order:', error, parsedErrorMessage);
// Check if there's an error message in the response
attachmentsFromOrderError.value = `ERROR: ${parsedErrorMessage || 'Failed to fetch attachments'}`;
});
}
});
const onShowImpersonationQRCode = (src, directLink) => {
Swal.fire({
title: t('admin.pos.settings_wheel.scan_qr_to_login'),
html: `<img src="${src}" alt="QR-kode" />`,
showCloseButton: true,
showConfirmButton: !!directLink,
showCancelButton: true,
focusConfirm: false,
confirmButtonText: t('admin.pos.settings_wheel.copy_link'),
cancelButtonText: t('admin.pos.settings_wheel.close'),
preConfirm: () => {
navigator.clipboard.writeText(directLink);
Swal.showValidationMessage(t('admin.pos.settings_wheel.link_copied'));
}
});
}
const onClickShowImpersonationQRCode = async (userId) => {
// Get the session token
const newToken = await SessionUser.superUser.intimidate.getImpersonationToken(userId);
// Create the impersonation link
const link = `${window.location.protocol}//${window.location.host}/login/qr?token=${newToken}`;
// Generate a QR code for the impersonation link
const response = await SessionUser.superUser.intimidate.showImpersonationQRCode(link);
// Show the QR code in a modal
onShowImpersonationQRCode(response.value, link);
}
const defaultActions = computed(() => {
return [
/**
* Example action
{
icon: 'fas fa-eye',
label: 'View Order',
clickAction: () => {
console.warn('View Order clicked');
},
showFunction: () => {
return true; // Logic to determine if action should be shown
},
disabled: false,
template: 'success' // Optional: Set the template to 'success' or 'danger'
isLabel: false // Optional: Set to true to render the label as a button
}
*/
/** Bookings actions */
{
label: t('admin.pos.settings_wheel.booking'),
isLabel: true,
showFunction: () => {
return !!props.order_booking_id;
}
},
{
icon: 'fas fa-check-circle',
label: t('admin.pos.settings_wheel.mark_as_completed'),
template: 'success',
clickAction: () => {
return SessionUser.objects.order_bookings.functions.showCompleteConfirmationModal(props.order_booking_id, () => {
props.refreshFunction();
});
},
showFunction: () => {
return !!props.order_booking_id && SessionUser.canAccessAdmin() && !props.order_id;
},
disabled: false,
},
{
icon: 'fas fa-external-link-alt',
label: t('admin.pos.settings_wheel.view_booking_new_tab'),
clickAction: () => {
return SessionUser.functions.redirectTo.department(props.department_id, 'modules/bookings/order/' + props.order_booking_id, true);
},
showFunction: () => {
return !!props.order_booking_id;
},
},
{
icon: 'fas fa-edit',
label: t('admin.pos.settings_wheel.change_association'),
clickAction: () => {
return SessionUser.objects.order_bookings.showEditObjectFieldForm(props.order_booking_id, 'order_id', props.order_id, () => {
props.refreshFunction();
}, {
filters: {
...(props.customer_number ? {customer_id: props.customer_number} : {}),
...(props.department_id ? {department_id: props.department_id} : {})
},
pagination: {
page: 1,
limit: 100
}
});
},
showFunction: () => {
return !!props.order_booking_id && SessionUser.canAccessAdmin();
},
disabled: false,
},
{
icon: 'fas fa-trash-alt',
label: t('admin.pos.settings_wheel.delete_booking'),
clickAction: () => {
return SessionUser.objects.order_bookings.functions.showDeleteConfirmationModal(props.order_booking_id, () => {
props.refreshFunction();
});
},
showFunction: () => {
return !!props.order_booking_id && SessionUser.canAccessAdmin() && !props.order_id;
},
disabled: false,
template: 'danger'
},
/** Department lane actions */
{
label: SessionUser.objects.department_lanes.meta.labels.single,
isLabel: true,
showFunction: () => {
return !!props.department_lane_id;
}
},
{
icon: `fas fa-external-link-alt`,
label: t('admin.pos.settings_wheel.view_lane_new_tab'),
clickAction: () => {
return SessionUser.functions.redirectTo.superUser('/department/lanes/' + props.department_lane_id, !SessionUser.functions.device.isMobile());
},
showFunction: () => {
return !!props.department_lane_id && SessionUser.canAccessSuperUser();
},
},
/** Orders actions */
{
label: SessionUser.functions.ucFirst(SessionUser.objects.orders.meta.labels.single),
isLabel: true,
showFunction: () => {
return !!props.order_id;
}
},
{
icon: 'fas fa-external-link-alt',
label: t('admin.pos.settings_wheel.view_order_new_tab'),
clickAction: async () => {
return redirectDepartmentOrderPage(props.order_id, true);
},
showFunction: () => {
return !!props.order_id && (SessionUser.canAccessAdmin() || SessionUser.canAccessSuperUser());
},
},
{
icon: 'fas fa-paperclip',
label: t('admin.pos.settings_wheel.attach_wash_certificate'),
clickAction: () => {
return SessionUser.objects.orders.functions.showAttachWashCertificateForm(props.order_id, () => {
props.refreshFunction();
});
},
showFunction: () => {
return !!props.order_id && (SessionUser.canAccessAdmin() || SessionUser.canAccessSuperUser());
},
disabled: false,
},
{
icon: 'fas fa-user-edit',
label: t('admin.pos.settings_wheel.change_customer'),
clickAction: () => {
return SessionUser.objects.orders.functions.showChangeCustomerForm(props.order_id);
},
showFunction: () => {
return !!props.order_id && SessionUser.canAccessSuperUser();
},
disabled: false,
},
{
icon: 'fas fa-file-invoice-dollar',
label: t('admin.pos.settings_wheel.change_invoice_collection'),
clickAction: () => {
return SessionUser.objects.orders.functions.showChangeInvoiceCollectionForm(props.order_id);
},
showFunction: () => {
return !!props.order_id && SessionUser.canAccessSuperUser();
},
disabled: false,
},
{
icon: 'fas fa-download',
label: t('admin.pos.settings_wheel.download_invoice'),
clickAction: () => {
return SessionUser.objects.collectedOrderInvoices.functions.download(props.invoice_collection_id);
},
showFunction: () => {
return true;
},
disabled: false,
},
{
icon: 'fas fa-trash-alt',
label: t('admin.pos.settings_wheel.delete_order'),
clickAction: () => {
return SessionUser.objects.orders.functions.showDeleteConfirmationModal(props.order_id, () => {
emitDeleted();
});
},
showFunction: () => {
return !!props.order_id && (SessionUser.canAccessAdmin() || SessionUser.canAccessSuperUser());
},
disabled: false,
template: 'danger'
},
{
icon: 'fas fa-file-invoice-dollar',
label: t('admin.pos.settings_wheel.view_invoice_collection_new_tab'),
clickAction: () => {
return redirectSuperUserInvoiceCollectionPage(props.invoice_collection_id);
},
showFunction: () => {
return !!props.invoice_collection_id && SessionUser.canAccessSuperUser();
},
},
/** Vehicle actions */
{
label: props.reg_1 && props.reg_2 ? `${SessionUser.objects.vehicles.meta.labels.multiple}` : `${SessionUser.objects.vehicles.meta.labels.single}`,
isLabel: true,
showFunction: () => {
return (!!props.reg_1 || !!props.reg_2) && SessionUser.canAccessSuperUser();
}
},
{
icon: 'fas fa-car',
label: t('admin.pos.settings_wheel.view_vehicle_new_tab', { reg: props.reg_1 ? props.reg_1 : '-' }),
clickAction: () => {
return SessionUser.functions.redirectTo.superUser('/vehicles/' + props.reg_1, true);
},
showFunction: () => {
return !!props.reg_1 && SessionUser.canAccessSuperUser();
},
},
{
icon: 'fas fa-car',
label: t('admin.pos.settings_wheel.view_vehicle_new_tab', { reg: props.reg_2 ? props.reg_2 : '-' }),
clickAction: () => {
return SessionUser.functions.redirectTo.superUser('/vehicles/' + props.reg_2, true);
},
showFunction: () => {
return !!props.reg_2 && SessionUser.canAccessSuperUser();
},
},
/** User actions */
{ label: SessionUser.objects.global.language.customer,
isLabel: true,
showFunction: () => {
return hasUser() && SessionUser.canAccessSuperUser();
}
},
{
icon: 'fas fa-user-edit',
label: t('admin.pos.settings_wheel.view_customer_new_tab'),
clickAction: () => {
return SessionUser.functions.redirectTo.superUser('/users/' + userIdFromCustomerNumber.value, true);
},
showFunction: () => {
return hasUser() && SessionUser.canAccessSuperUser() && userIdFromCustomerNumber.value;
},
},
{
icon: 'fas fa-user-shield',
label: t('admin.pos.settings_wheel.login_as_customer'),
clickAction: () => {
return SessionUser.superUser.intimidate.intimidateUser(userIdFromCustomerNumber.value);
},
showFunction: () => {
return hasUser() && SessionUser.canAccessSuperUser();
},
},
{
icon: 'fas fa-qrcode',
label: t('admin.pos.settings_wheel.show_qr_code'),
clickAction: () => {
return onClickShowImpersonationQRCode(userIdFromCustomerNumber.value);
},
showFunction: () => {
return hasUser() && SessionUser.canAccessSuperUser();
}
},
{
icon: 'fas fa-key',
label: t('admin.pos.settings_wheel.change_password'),
clickAction: () => {
return showSetCustomerPassword(userIdFromCustomerNumber.value);
},
showFunction: () => {
return hasUser() && SessionUser.canAccessSuperUser();
},
},
{
icon: 'fas fa-user',
label: t('admin.pos.settings_wheel.show_customer'),
clickAction: () => {
customerModalVisible.value = true;
},
showFunction: () => {
return hasUser() && SessionUser.canAccessSuperUser();
},
},
/** Subuser grant actions */
{
icon: 'fas fa-edit',
label: t('admin.pos.settings_wheel.edit_permissions'),
clickAction: () => {
return SessionUser.objects.subuser_grants.functions.showPermissionEditForm(props.subuserGrant, () => {
props.refreshFunction();
});
},
showFunction: () => {
return !!props.subuserGrant && SessionUser.canAccessUser();
},
},
];
});
</script>
<template>
<div class="dropdown is-right is-hoverable">
<div class="dropdown-trigger">
<button class="button is-small is-dark" aria-haspopup="true" aria-controls="dropdown-menu">
<span class="icon">
<i :class="props.icon"></i>
</span>
<span v-if="props.label.length > 0" class="ml-2">{{ props.label }}</span>
</button>
</div>
<div class="dropdown-menu" id="dropdown-menu" role="menu">
<div class="dropdown-content">
<!-- Actions (If defined) -->
<template v-if="useSlots().actions">
<slot name="actions"></slot>
<!-- If there's a user_id, show the user actions -->
<template v-if="props.user_id">
<!-- Label -->
<ActionSettingsWheelItemLabel
:label="SessionUser.objects.global.language.customer"
<div>
<template v-if="displayActionsDirectly">
<!-- Error -->
<template v-if="attachmentsFromOrderError">
<div class="message is-danger">
<div class="message-body">
{{ attachmentsFromOrderError }}
</div>
</div>
</template>
<template v-for="(action, index) in [ ...defaultActions, ...(useSlots().actions ? [] : []) ]" :key="index">
<ActionSettingsWheelItem
v-if="(!action.showFunction || action.showFunction()) && (!action.isLabel || action.isLabel === false)"
:icon="action.icon"
:label="action.label"
:template="action.template"
:click-action="action.clickAction"
:show-function="action.showFunction"
:disabled="action.disabled"
/>
<ActionSettingsWheelItemLabel
v-else-if="action.isLabel && action.isLabel === true && (!action.showFunction || action.showFunction())"
:label="action.label"
/>
</template>
<!-- If there are attachments, show the attachments -->
<template v-if="attachmentsFromOrder.length > 0">
<ActionSettingsWheelItemLabel :label="t('admin.pos.settings_wheel.attached_files')"/>
<ActionSettingsWheelItem
v-for="attachment in attachmentsFromOrder"
:key="attachment.id"
:icon="'fas fa-paperclip'"
:label="t('admin.pos.settings_wheel.open_attached_file', { id: attachment.id })"
:click-action="() => SessionUser.objects.orders.functions.downloadAttachment(props.order_id, attachment.id, true)
.catch((error) => {
Swal.fire({
title: t('admin.pos.settings_wheel.error'),
text: t('admin.pos.settings_wheel.error_downloading_attachment'),
icon: 'error',
});
})"
/>
</template>
</template>
<div class="dropdown is-right is-hoverable" v-else>
<div class="dropdown-trigger">
<button class="button is-small is-dark" aria-haspopup="true" aria-controls="dropdown-menu">
<span class="icon">
<i :class="props.icon"></i>
</span>
<span v-if="props.label.length > 0" class="ml-2">{{ props.label }}</span>
</button>
</div>
<div class="dropdown-menu" id="dropdown-menu" role="menu">
<div class="dropdown-content">
<!-- Actions (If defined) -->
<template v-if="useSlots().actions">
<slot name="actions"></slot>
<!-- If there is an order_booking_id, show the order booking actions -->
<template v-if="props.order_booking_id">
<ActionSettingsWheelItemLabel :label="t('admin.pos.settings_wheel.booking')"/>
<ActionSettingsWheelItem
v-if="(SessionUser.canAccessAdmin() || SessionUser.canAccessSuperUser()) && !props.order_id"
:icon="'fas fa-check-circle'"
:label="t('admin.pos.settings_wheel.mark_as_completed')"
template="success"
:click-action="() => SessionUser.objects.order_bookings.functions.showCompleteConfirmationModal(props.order_booking_id, () => {
props.refreshFunction();
})"
/>
<ActionSettingsWheelItem
:icon="'fas fa-external-link-alt'"
:label="t('admin.pos.settings_wheel.view_booking_new_tab')"
:click-action="() => SessionUser.functions.redirectTo.department(props.department_id, 'modules/bookings/order/' + props.order_booking_id, true)"
/>
<ActionSettingsWheelItem
v-if="SessionUser.canAccessAdmin() || SessionUser.canAccessSuperUser()"
:icon="'fas fa-edit'"
:label="(!props.order_id) ? t('admin.pos.settings_wheel.associate_order', { order: SessionUser.objects.orders.meta.labels.single.toLowerCase() }) : t('admin.pos.settings_wheel.change_order', { order: SessionUser.objects.orders.meta.labels.single.toLowerCase() })"
:click-action="() => SessionUser.objects.order_bookings.showEditObjectFieldForm(props.order_booking_id, 'order_id', props.order_id, () => {
props.refreshFunction();
}, {
filters: {
...(customer_number ? {customer_id: props.customer_number} : {}),
...(props.department_id ? {department_id: props.department_id} : {})
},
pagination: {
page: 1,
limit: 100
}
})"
/>
<ActionSettingsWheelItem
:icon="'fas fa-trash-alt'"
:label="t('admin.pos.settings_wheel.delete_booking')"
template="danger"
v-show="!props.order_id"
:click-action="() => SessionUser.objects.order_bookings.functions.showDeleteConfirmationModal(props.order_booking_id, () => {
props.refreshFunction();
})"
/>
</template>
<!-- If there's an order_id, show the order actions -->
<template v-if="props.order_id">
<!-- Label -->
<action-settings-wheel-item-label
:label="SessionUser.functions.ucFirst(SessionUser.objects.orders.meta.labels.single)"
/>
<!-- Go to the order page, in a new tab -->
<action-settings-wheel-item
v-if="SessionUser.canAccessAdmin() || SessionUser.canAccessSuperUser()"
:label="t('admin.pos.settings_wheel.view_order_new_tab')"
icon="fas fa-external-link-alt"
:click-action="async () => redirectDepartmentOrderPage(props.order_id, true)"
:disabled="false"
/>
<!-- Go to the user/order/:order_id page, in a new tab -->
<action-settings-wheel-item
v-if="!SessionUser.canAccessAdmin() && !SessionUser.canAccessSuperUser()"
:label="t('admin.pos.settings_wheel.view_order_new_tab')"
icon="fas fa-external-link-alt"
:click-action="() => SessionUser.functions.redirectTo.user('/orders/' + props.order_id, true)"
:disabled="false"
/>
<!-- Attach wash certificate -->
<action-settings-wheel-item
v-if="SessionUser.canAccessAdmin() || SessionUser.canAccessSuperUser()"
:label="t('admin.pos.settings_wheel.attach_wash_certificate')"
icon="fas fa-paperclip"
:click-action="() => SessionUser.objects.orders.functions.showAttachWashCertificateForm(props.order_id, () => {
props.refreshFunction();
})"
:disabled="false"
/>
<!-- Change the customer (of the order) -->
<action-settings-wheel-item
v-if="SessionUser.canAccessSuperUser()"
:label="t('admin.pos.settings_wheel.change_customer')"
icon="fas fa-user-edit"
:click-action="() => SessionUser.objects.orders.functions.showChangeCustomerForm(props.order_id)"
:disabled="false"
/>
<!-- Change the invoice collection (of the order) -->
<action-settings-wheel-item
v-if="SessionUser.canAccessSuperUser()"
:label="t('admin.pos.settings_wheel.change_invoice_collection')"
icon="fas fa-file-invoice-dollar"
:click-action="() => SessionUser.objects.orders.functions.showChangeInvoiceCollectionForm(props.order_id)"
:disabled="false"
/>
<!-- Delete the order -->
<action-settings-wheel-item
v-if="SessionUser.canAccessAdmin() || SessionUser.canAccessSuperUser()"
:label="t('admin.pos.settings_wheel.delete_order')"
template="danger"
icon="fas fa-trash-alt"
:click-action="() => SessionUser.objects.orders.functions.showDeleteConfirmationModal(props.order_id, () => {
emitDeleted();
})"
:disabled="false"
/>
</template>
<!-- If there is an invoice collection id, show the invoice actions -->
<template v-if="props.invoice_collection_id">
<!-- Label -->
<action-settings-wheel-item-label
:label="SessionUser.objects.collectedOrderInvoices.meta.title"
v-show="SessionUser.canAccessSuperUser()"
/>
<!-- Se bruger -->
<ActionSettingsWheelItem
label="Se kunde i ny fane"
icon="fas fa-user-edit"
:click-action="() => SessionUser.functions.redirectTo.superUser('/users/' + props.user_id, true)"
v-if="SessionUser.canAccessSuperUser()"
/>
<!-- Log ind som kunde -->
<ActionSettingsWheelItem
label="Log ind som kunde"
icon="fas fa-user-shield"
:click-action="() => SessionUser.superUser.intimidate.intimidateUser(props.user_id)"
v-if="SessionUser.canAccessSuperUser()"
/>
/>
<!-- Show the invoice collection, in a new tab -->
<action-settings-wheel-item
v-if="SessionUser.canAccessSuperUser()"
:label="t('admin.pos.settings_wheel.view_invoice_collection_new_tab')"
icon="fas fa-file-invoice-dollar"
:click-action="() => redirectSuperUserInvoiceCollectionPage(props.invoice_collection_id)"
/>
</template>
<!-- if there is a department_lane_id, show the department lane actions -->
<template v-if="props.department_lane_id">
<!-- Label -->
<action-settings-wheel-item-label
:label="SessionUser.objects.department_lanes.meta.labels.single"
v-show="SessionUser.canAccessSuperUser() || SessionUser.canAccessAdmin()"
/>
<!-- Show the department lane (Admin), in a new tab -->
<action-settings-wheel-item
v-if="(SessionUser.canAccessAdmin() || SessionUser.canAccessSuperUser()) && props.department_id"
:label="t('admin.pos.settings_wheel.view_lane_new_tab')"
icon="fas fa-external-link-alt"
:click-action="() => SessionUser.functions.redirectTo.department(props.department_id, 'modules/wash-lanes/' + props.department_lane_id, !SessionUser.functions.device.isMobile())"
/>
<!-- Show the department lane (SuperUser), in a new tab -->
<action-settings-wheel-item
v-if="SessionUser.canAccessSuperUser()"
:label="t('admin.pos.settings_wheel.view_lane_setup_new_tab')"
icon="fas fa-external-link-alt"
:click-action="() => SessionUser.functions.redirectTo.superUser('/department/lanes/' + props.department_lane_id, true)"
/>
</template>
<!-- If there's a user_id, show the user actions -->
<template v-if="hasUser()">
<!-- Label -->
<ActionSettingsWheelItemLabel
:label="SessionUser.objects.global.language.customer"
v-show="SessionUser.canAccessSuperUser()"
/>
<!-- Se bruger -->
<ActionSettingsWheelItem
:label="t('admin.pos.settings_wheel.view_customer_new_tab')"
icon="fas fa-user-edit"
:click-action="() => SessionUser.functions.redirectTo.superUser('/users/' + userIdFromCustomerNumber, true)"
v-if="SessionUser.canAccessSuperUser() && userIdFromCustomerNumber"
/>
<!-- Log ind som kunde -->
<ActionSettingsWheelItem
:label="t('admin.pos.settings_wheel.login_as_user')"
icon="fas fa-user-shield"
:click-action="() => SessionUser.superUser.intimidate.intimidateUser(userIdFromCustomerNumber)"
v-if="SessionUser.canAccessSuperUser() && userIdFromCustomerNumber"
/>
<!-- Log ind som kunde (QR) -->
<ActionSettingsWheelItem
@click="onClickShowImpersonationQRCode(userIdFromCustomerNumber)"
icon="fas fa-user-shield"
:label="t('admin.pos.settings_wheel.login_as_user_qr')"
v-if="SessionUser.canAccessSuperUser() && userIdFromCustomerNumber"
/>
<!-- Set password -->
<ActionSettingsWheelItem
label="Skift kodeord"
icon="fas fa-key"
:click-action="() => showSetCustomerPassword(props.user_id)"
v-if="SessionUser.canAccessSuperUser()"
/>
<!-- Show customer modal -->
<ActionSettingsWheelItem
label="Vis kunde"
icon="fas fa-user"
:click-action="() => customerModalVisible = true"
v-if="SessionUser.canAccessSuperUser()"
/>
</template>
<!-- If there's an order_id, show the order actions -->
<template v-if="props.order_id">
<!-- Label, only if the user is a superuser (Otherwise, the label is redundant) -->
<action-settings-wheel-item-label
:label="SessionUser.functions.ucFirst(SessionUser.objects.orders.meta.labels.single)"
v-show="SessionUser.canAccessSuperUser()"
/>
<!-- Go to the order page, in a new tab -->
<action-settings-wheel-item
v-if="SessionUser.canAccessAdmin() || SessionUser.canAccessSuperUser()"
label="Se transaktion i ny fane"
icon="fas fa-external-link-alt"
:click-action="async () => redirectDepartmentOrderPage(props.order_id)"
:disabled="false"
/>
<!-- Go to the user/order/:order_id page, in a new tab -->
<action-settings-wheel-item
v-if="!SessionUser.canAccessAdmin() && !SessionUser.canAccessSuperUser()"
label="Se transaktion i ny fane"
icon="fas fa-external-link-alt"
:click-action="() => SessionUser.functions.redirectTo.user('/orders/' + props.order_id, true)"
:disabled="false"
<!-- Set password -->
<ActionSettingsWheelItem
:label="t('admin.pos.settings_wheel.change_password')"
icon="fas fa-key"
:click-action="() => showSetCustomerPassword(userIdFromCustomerNumber)"
v-if="SessionUser.canAccessSuperUser()"
/>
<!-- Show customer modal -->
<ActionSettingsWheelItem
:label="t('admin.pos.settings_wheel.show_customer')"
icon="fas fa-user"
:click-action="() => customerModalVisible = true"
v-if="SessionUser.canAccessSuperUser()"
/>
</template>
<!-- If there is a reg_1, show the reg_1 actions -->
<template v-if="props.reg_1 || props.reg_2">
<!-- Label -->
<action-settings-wheel-item-label
:label="props.reg_1 && props.reg_2 ? `${SessionUser.objects.vehicles.meta.labels.multiple}` : `${SessionUser.objects.vehicles.meta.labels.single}`"
v-show="SessionUser.canAccessSuperUser()"
/>
<!-- Show the vehicle, in a new tab -->
<action-settings-wheel-item
:label="t('admin.pos.settings_wheel.view_vehicle_new_tab', { reg: props.reg_1 })"
icon="fas fa-car"
v-if="props.reg_1 && SessionUser.canAccessSuperUser()"
:click-action="() => SessionUser.functions.redirectTo.superUser('/vehicles/' + props.reg_1, true)"
/>
<action-settings-wheel-item
:label="t('admin.pos.settings_wheel.view_vehicle_new_tab', { reg: props.reg_2 })"
icon="fas fa-car"
v-if="props.reg_2 && SessionUser.canAccessSuperUser()"
:click-action="() => SessionUser.functions.redirectTo.superUser('/vehicles/' + props.reg_2, true)"
/>
<!-- If there are any attachments, show the attachments actions -->
<template v-if="attachmentsFromOrder.length > 0">
<action-settings-wheel-item-label
:label="t('admin.pos.settings_wheel.attached_files')"
/>
<!-- Delete the order -->
<action-settings-wheel-item
v-if="SessionUser.canAccessAdmin() || SessionUser.canAccessSuperUser()"
label="Slet transaktion"
icon="fas fa-trash-alt"
:click-action="() => SessionUser.objects.orders.functions.showDeleteConfirmationModal(props.order_id, () => {
emitDeleted();
})"
:disabled="false"
/>
<!-- Change the customer (of the order) -->
<action-settings-wheel-item
v-if="SessionUser.canAccessSuperUser()"
label="Skift kunde"
icon="fas fa-user-edit"
:click-action="() => SessionUser.objects.orders.functions.showChangeCustomerForm(props.order_id)"
:disabled="false"
/>
<!-- Change the invoice collection (of the order) -->
<action-settings-wheel-item
v-if="SessionUser.canAccessSuperUser()"
label="Skift faktura samling"
icon="fas fa-file-invoice-dollar"
:click-action="() => SessionUser.objects.orders.functions.showChangeInvoiceCollectionForm(props.order_id)"
:disabled="false"
/>
<template v-for="(attachment, index) in attachmentsFromOrder" :key="index">
<action-settings-wheel-item
:label="`${attachment.content?.document || attachment.content?.other}`"
icon="fas fa-paperclip"
:click-action="() => SessionUser.objects.orders.functions.downloadAttachment(props.order_id, attachment.id, true)
.catch((error) => {
Swal.fire({
title: t('admin.pos.settings_wheel.error'),
text: t('admin.pos.settings_wheel.error_downloading_attachment'),
icon: 'error',
});
})"
/>
</template>
</template>
</template>
<!-- Collected order invoice actions -->
<template v-if="props.invoice_collection_id">
<ActionSettingsWheelItemLabel
:label="SessionUser.objects.collectedOrderInvoices.meta.title"
v-show="props.invoice_collection_id"
/>
<!-- Download invoice -->
<ActionSettingsWheelItem
:label="t('admin.pos.settings_wheel.download_invoice')"
icon="fas fa-download"
:click-action="() => SessionUser.objects.collectedOrderInvoices.functions.download(props.invoice_collection_id)"
/>
</template>
<!-- Subuser grant actions -->
<template v-if="props.subuserGrant">
<ActionSettingsWheelItem
:label="t('admin.pos.settings_wheel.edit_permissions')"
icon="fas fa-edit"
:click-action="() => SessionUser.objects.subuser_grants.functions.showPermissionEditForm(props.subuserGrant, () => {
props.refreshFunction();
})"
v-if="SessionUser.canAccessUser()"
/>
<ActionSettingsWheelItem
:label="t('admin.pos.settings_wheel.delete_user')"
icon="fas fa-trash"
:click-action="() => SessionUser.objects.subuser_grants.delete(props.subuserGrant.id, () => {
props.refreshFunction();
})"
v-if="SessionUser.canAccessUser()"
/>
</template>
</template>
<!-- If there is an invoice collection id, show the invoice actions -->
<template v-if="props.invoice_collection_id">
<!-- Label -->
<action-settings-wheel-item-label
:label="SessionUser.objects.collectedOrderInvoices.meta.title"
v-show="SessionUser.canAccessSuperUser()"
/>
<!-- Show the invoice collection, in a new tab -->
<action-settings-wheel-item
v-if="SessionUser.canAccessSuperUser()"
label="Vis faktura samling i ny fane"
icon="fas fa-file-invoice-dollar"
:click-action="() => redirectSuperUserInvoiceCollectionPage(props.invoice_collection_id)"
/>
<!-- If no actions are defined, then show that no actions are defined -->
<template v-else>
<div class="dropdown-item">
<p class="has-text-centered">{{ t('admin.pos.settings_wheel.no_actions_defined') }}</p>
</div>
</template>
<!-- If there is a reg_1, show the reg_1 actions -->
<template v-if="props.reg_1">
<!-- Label -->
<action-settings-wheel-item-label
:label="SessionUser.objects.vehicles.meta.labels.single"
v-show="SessionUser.canAccessSuperUser()"
/>
<!-- Show the vehicle, in a new tab -->
<action-settings-wheel-item
label="Vis køretøj i ny fane"
icon="fas fa-car"
v-if="SessionUser.canAccessSuperUser()"
:click-action="() => SessionUser.functions.redirectTo.superUser('/vehicles/' + props.reg_1, true)"
/>
</template>
</template>
<!-- If no actions are defined, then show that no actions are defined -->
<template v-else>
<div class="dropdown-item">
<p class="has-text-centered">Ingen handlinger defineret</p>
</div>
</template>
</div>
</div>
</div>
<CustomerModal
v-if="customerModalVisible"
:user_id="props.user_id"
:user_id="userIdFromCustomerNumber"
@closeModal="customerModalVisible = false"
/>
</div>
@@ -1,7 +1,8 @@
<script setup lang="ts">
import { IS_DEV } from '@/main';
import { IS_DEV } from '@/config.js';
import { ref, computed, defineProps, defineEmits, watch } from 'vue';
import { SessionUser } from '@/components/session/token/SessionUser.vue';
import {BField, BSelect} from "buefy";
const props = defineProps({
selection: {
@@ -39,6 +40,7 @@ const props = defineProps({
showMonthSelector?: boolean, // Whether to show the month select
showMultipleMonthWarning?: boolean, // Whether to show a warning if the selection spans multiple months
showYearSelector?: boolean, // Whether to show the year selector
showShortCuts?: boolean, // Whether to show the shortcuts
},
required: false,
default: () => ({
@@ -87,6 +89,7 @@ const visibility = computed(() => {
showYearSelector: true,
showDailySelector: false,
showWeeklySelector: false,
showShortCuts: true,
// Override with props visibility settings
...props.visibility,
};
@@ -200,7 +203,10 @@ type shortcut = {
label: string,
getRange: () => { startDate: Date, endDate: Date },
};
const dailyShortcuts: shortcut[] = [
// Use computed to make shortcuts reactive to language changes
const shortcuts = computed<shortcut[]>(() => {
const dailyShortcuts: shortcut[] = [
// Today
{
label: SessionUser.objects.global.language.text.today,
@@ -224,8 +230,8 @@ const dailyShortcuts: shortcut[] = [
};
},
},
];
const weeklyShortcuts: shortcut[] = [
];
const weeklyShortcuts: shortcut[] = [
// This week
{
label: SessionUser.objects.global.language.text.this_week,
@@ -313,16 +319,107 @@ const weeklyShortcuts: shortcut[] = [
};
},
},
];
const shortcuts = ref<shortcut[]>([
...(visibility.value.showDailySelector ? dailyShortcuts : []),
...(visibility.value.showWeeklySelector ? weeklyShortcuts : []),
]);
];
return [
...((visibility.value.showDailySelector || visibility.value.showShortCuts) ? dailyShortcuts : []),
...((visibility.value.showWeeklySelector || visibility.value.showShortCuts) ? weeklyShortcuts : []),
];
});
</script>
<template>
<div class="level is-mobile is-align-items-center">
<!-- Mobile date selector -->
<div class="columns is-mobile is-multiline" v-show="SessionUser.functions.device.isMobile()">
<!-- Date from & Date to -->
<div class="column is-half">
<label class="label is-small" v-if="visibility.showStartDate">{{ SessionUser.objects.global.language.text.date_from }}</label>
<input
type="date"
class="input"
v-model="props.selection.startDate.toISOString().split('T')[0]"
@change="handleStartDateChange"
v-bind:disabled="props.isDisabled || props.isReadonly"
:class="{ 'is-danger': !isSelectionValid }"
/>
</div>
<div class="column is-half">
<label class="label is-small" v-if="visibility.showEndDate">{{ SessionUser.objects.global.language.text.date_to }}</label>
<input
type="date"
class="input"
v-model="props.selection.endDate.toISOString().split('T')[0]"
@change="handleEndDateChange"
v-bind:disabled="props.isDisabled || props.isReadonly"
:class="{ 'is-danger': !isSelectionValid }"
/>
</div>
<!-- Month selector -->
<div class="column is-half" v-if="visibility.showMonthSelector">
<div class="field has-addons is-centered">
<p class="control is-expanded">
<span class="select is-fullwidth">
<select
class="input"
v-model="currentMonthSelection"
@change="setMonth(currentMonthSelection)"
v-bind:disabled="props.isDisabled || props.isReadonly"
>
<!-- Default option: Custom -->
<option value="" disabled selected>Select Month</option>
<option v-for="month in months" :key="month.value" :value="month.value">
{{ SessionUser.functions.ucFirst(month.label) }}
</option>
</select>
</span>
</p>
</div>
</div>
<!-- Year selector -->
<div class="column is-half" v-if="visibility.showYearSelector">
<div class="field has-addons is-centered">
<p class="control is-expanded">
<span class="select is-fullwidth">
<select
class="input"
:value="startDate.getFullYear()"
@change="(e) => { const year = parseInt(e.target.value); if (!isNaN(year)) { const newStartDate = new Date(startDate); newStartDate.setFullYear(year); startDate = newStartDate; const newEndDate = new Date(endDate); newEndDate.setFullYear(year); endDate = newEndDate; isPendingEmit = true; if (props.autoEmitChange) { updateSelection(); } } }"
v-bind:disabled="props.isDisabled || props.isReadonly"
>
<!-- Generate a range of years from current year - 10 to current year + 10 -->
<option v-for="year in Array.from({ length: 21 }, (_, i) => new Date().getFullYear() - 10 + i)" :key="year" :value="year">
{{ year }}
</option>
</select>
</span>
</p>
<p class="control">
<a class="button is-static">
{{ SessionUser.objects.global.language.text.year }}
</a>
</p>
</div>
</div>
<!-- Shortcuts -->
<div class="column is-full">
<b-field label="Periode">
<b-select placeholder="Vælg periode"
:expandable="true"
:model-value="shortcuts.find(s => (s.getRange().startDate.toISOString().split('T')[0] === startDate.toISOString().split('T')[0]) && (s.getRange().endDate.toISOString().split('T')[0] === endDate.toISOString().split('T')[0]))?.label || null"
:expanded="true"
@update:modelValue="(value) => { const selectedShortcut = shortcuts.find(s => s.label === value); if (selectedShortcut) { const range = selectedShortcut.getRange(); startDate = range.startDate; endDate = range.endDate; isPendingEmit = true; if (props.autoEmitChange) { updateSelection(); } } }" v-bind:disabled="props.isDisabled || props.isReadonly">
<option
v-for="(option, index) in shortcuts"
:key="index"
:value="option.label"
>{{ option.label }}
</option>
</b-select>
</b-field>
</div>
</div>
<!-- Desktop date selector -->
<div class="level is-align-items-center" v-show="!SessionUser.functions.device.isMobile()">
<div class="level-left">
<!-- Start Date Input -->
<div class="level-item" v-if="visibility.showStartDate">
@@ -350,7 +447,7 @@ const shortcuts = ref<shortcut[]>([
/>
</div>
<!-- Display a message if neither start nor end date is shown -->
<div class="level-item" v-if="!visibility.showStartDate && !visibility.showEndDate">
<div class="level-item" v-if="!visibility.showStartDate && !visibility.showEndDate && visibility.showToLabel">
<span class="label">No date selection available</span>
</div>
<!-- Month and Year selectors -->
@@ -438,7 +535,7 @@ const shortcuts = ref<shortcut[]>([
</div>
</div>
</div>
<div class="navbar is-white">
<div class="navbar is-white" v-show="!SessionUser.functions.device.isMobile()">
<div class="navbar-menu">
<!-- Date shortcuts -->
<div class="navbar-start">
@@ -1,6 +1,7 @@
<script setup>
import {defineProps, ref} from 'vue';
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import {BTooltip} from "buefy";
const props = defineProps({
object: Object,
loadList: Function,
@@ -8,6 +9,12 @@ const props = defineProps({
column: String,
parseFunction: Function,
virtualColumn: Boolean,
hoverText: {
type: String,
default: () => {
return null;
}
},
permissionCheckFunction: {
type: Function,
default: () => {
@@ -20,6 +27,23 @@ const props = defineProps({
default: () => {
return null; // Example: <td>
}
},
// Theme can be 'divided' or 'simple'
theme: {
type: String,
validator(value, props) {
return ['divided', 'simple'].includes(value);
},
default: () => {
return 'simple';
}
},
// Hover HTML content
hoverHtml: {
type: Boolean,
default: () => {
return false;
}
}
});
@@ -52,19 +76,62 @@ const isLoading = ref(false);
</script>
<template>
<component :is="props.componentWrapper ? props.componentWrapper : 'td'">
<span v-if="!props.virtualColumn">{{ parseFunction ? parseFunction(props.object[props.column]) : props.object[props.column] }}</span>
<span v-else>{{ parseFunction ? parseFunction(props.object) : props.object }}</span>
<span class="ml-2" v-if="props.permissionCheckFunction()">
<button class="button is-small is-transparent is-text" style="text-decoration-line: none;" @click="clickEdit" v-if="props.editFunction" :class="{'is-loading': isLoading}" :disabled="isLoading">
<span class="icon">
<i class="fas fa-edit"></i>
</span>
</button>
</span>
<component :is="props.componentWrapper ? props.componentWrapper : 'td'"
@click="clickEdit"
class="hover-illustration"
:class="{'is-clickable': props.permissionCheckFunction() && props.editFunction}">
<div>
<!-- Divided display ( value | edit button ) -->
<template v-if="props.theme === 'divided'">
<nav class="level is-mobile" style="min-width: -webkit-fill-available; width: -webkit-fill-available; overflow: visible; max-width: -webkit-fill-available;">
<div class="level-left" style="overflow: hidden; max-width: calc(100% - 2.5em); text-overflow: ellipsis; white-space: nowrap;">
<div class="level-item" :title="props.hoverText ? props.hoverText : ( !props.virtualColumn ? (parseFunction ? parseFunction(props.object[props.column]) : props.object[props.column]) : (parseFunction ? parseFunction(props.object) : props.object) )">
<span class="" v-if="!props.virtualColumn">{{ parseFunction ? parseFunction(props.object[props.column]) : props.object[props.column] }}</span>
<span class="" v-else>{{ parseFunction ? parseFunction(props.object) : props.object }}</span>
</div>
</div>
<div class="level-right">
<div class="level-item">
<span class="ml-2 is-float-right" v-if="props.permissionCheckFunction()">
<button class="button is-small is-transparent is-text" style="text-decoration-line: none;" @click="clickEdit" v-if="props.editFunction" :class="{'is-loading': isLoading}" :disabled="isLoading">
<span class="icon">
<i class="fas fa-edit"></i>
</span>
</button>
</span>
</div>
</div>
</nav>
</template>
<!-- Simple display ( value ) with edit button on hover -->
<template v-else>
<!-- Hover Edit icon -->
<b-tooltip
type="is-light"
position="is-bottom"
:multilined="$slots.hoverHtml"
square>
<!-- Value Display -->
<span class="" v-if="!props.virtualColumn">{{ parseFunction ? parseFunction(props.object[props.column]) : props.object[props.column] }}</span>
<span class="" v-else>{{ parseFunction ? parseFunction(props.object) : props.object }}</span>
<template v-slot:content>
<slot name="hoverHtml">
<div v-html="props.hoverText" v-if="props.hoverText"></div>
<div v-else>
{{ !props.virtualColumn ? (parseFunction ? parseFunction(props.object[props.column]) : props.object[props.column]) : (parseFunction ? parseFunction(props.object) : props.object) }}
</div>
</slot>
</template>
</b-tooltip>
</template>
</div>
</component>
</template>
<style scoped>
.hover-illustration:hover {
background-color: #f5f5f5;
border-radius: 4px;
}
</style>
@@ -1,4 +1,8 @@
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
defineProps(['bookings']);
import { ref } from 'vue';
import { departments, getDepartments, isLoading, getDepartmentName} from "@/components/pagination/departmentTabs.vue";
@@ -18,19 +22,19 @@ if (departments.value.length === 0) {
<table class="table is-fullwidth">
<thead>
<tr>
<th>ID</th>
<th>Customer Number</th>
<th>Type</th>
<th>Department</th>
<th>Reg. nr. Trækker</th>
<th>Reg. nr. Trailer</th>
<th>Reference</th>
<th>Notes</th>
<th>Date</th>
<th>Status</th>
<th>Contact email</th>
<th>Wash certificate email</th>
<th>Actions</th>
<th>{{ $t('objects.columns.id') }}</th>
<th>{{ $t('objects.columns.customer_number') }}</th>
<th>{{ $t('objects.columns.type') }}</th>
<th>{{ $t('objects.columns.department') }}</th>
<th>{{ $t('tables.bookings.reg_tractor') }}</th>
<th>{{ $t('tables.bookings.reg_trailer') }}</th>
<th>{{ $t('objects.columns.reference') }}</th>
<th>{{ $t('objects.columns.notes') }}</th>
<th>{{ $t('objects.columns.date') }}</th>
<th>{{ $t('objects.columns.status') }}</th>
<th>{{ $t('objects.bookings.columns.contact_email') }}</th>
<th>{{ $t('objects.bookings.columns.wash_certificate_email') }}</th>
<th>{{ $t('tables.actions') }}</th>
</tr>
</thead>
<tbody>
@@ -49,14 +53,14 @@ if (departments.value.length === 0) {
<td>{{ booking.washCertificateEmail }}</td>
<td>
<div class="buttons">
<button class="button is-small is-dark" @click="redirectDepartmentOrderPage(booking.id, booking.department_id)">Show</button>
<button class="button is-small is-dark" @click="redirectDepartmentOrderPage(booking.id, booking.department_id)">{{ $t('global.show') }}</button>
</div>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="12">Showing {{ bookings.length }} bookings</td>
<td colspan="12">{{ $t('tables.showing') }} {{ bookings.length }} {{ $t('objects.bookings.multiple') }}</td>
</tr>
</tfoot>
</table>
@@ -1,15 +1,23 @@
<script setup>
import {ref, watch} from 'vue';
import {computed, onMounted, ref, watch} from 'vue';
import { useRouter } from 'vue-router';
import { authenticatedRequest } from "@/components/session/authenticatedRequest.vue";
import {BSwitch} from "buefy";
import {useToast} from 'vue-toast-notification';
import {useI18n} from 'vue-i18n';
const router = useRouter();
const toast = useToast();
const { t } = useI18n();
const modules = [
const selfServeEnabled = ref(false);
const bookingsCount = ref(0);
const modules = computed(() => [
{
id: 1,
name: "Kassesystem",
description: "Gå til kassesystemet",
name: t('admin.department_modules.pos.name'),
description: t('admin.department_modules.pos.description'),
icon: "fas fa-book",
onClick: () => {
// Get the departmentId from the URL
@@ -20,12 +28,12 @@ const modules = [
onClickCount: () => {
this.onClick();
},
count: ref(0)
count: 0
},
{
id: 2,
name: "Bookinger",
description: "Gå til bookinger",
name: t('admin.department_modules.bookings.name'),
description: t('admin.department_modules.bookings.description'),
icon: "fas fa-calendar-alt",
onClick: () => {
// Get the departmentId from the URL
@@ -39,12 +47,12 @@ const modules = [
// Redirect to the bookings
router.push(`/admin/${departmentId}/modules/bookings?search=pending`);
},
count: ref(0)
count: bookingsCount.value
},
{
id: 3,
name: "Dagsopgørelse",
description: "Gå til dagsopgørelse og regnskab",
name: t('admin.department_modules.daily_report.name'),
description: t('admin.department_modules.daily_report.description'),
icon: "fas fa-book",
onClick: () => {
// Get the departmentId from the URL
@@ -55,41 +63,94 @@ const modules = [
onClickCount: () => {
this.onClick();
},
count: ref(0)
},
{
id: 4,
name: "Notifikationer",
description: "Gå til notifikationer",
icon: "fas fa-bell",
onClick: () => {
// Get the departmentId from the URL
const departmentId = router.currentRoute.value.params.departmentId;
// Redirect to the notifications
router.push(`/admin/${departmentId}/modules/notifications`);
},
onClickCount: () => {
this.onClick();
},
count: ref(0)
count: 0
},
{
id: 5,
name: "Tidsbookinger",
description: "Gå til tidsbookinger",
icon: "fas fa-clock",
name: t('admin.department_modules.goals.name'),
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 time bookings
router.push(`/admin/${departmentId}/modules/time-bookings`);
// Redirect to the goals
router.push(`/admin/${departmentId}/modules/goals`);
},
onClickCount: () => {
this.onClick();
},
count: ref(0)
count: 0
},
{
id: 4,
name: t('admin.department_modules.self_wash.name'),
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`);
},
onClickCount: () => {
this.onClick();
},
count: 0,
hasSwitch: true,
switchValue: selfServeEnabled,
onToggle: async (newValue) => {
const departmentId = getDepartmentId();
if (!departmentId) return;
try {
await authenticatedRequest(`/departments/self-serve/enabled?id=${departmentId}&enabled=${newValue}`, 'PUT');
selfServeEnabled.value = newValue;
toast.success(newValue ? t('admin.department_modules.self_wash.enabled') : t('admin.department_modules.self_wash.disabled'));
} catch (error) {
console.error("Failed to update self-serve status", error);
// Revert switch value on error
selfServeEnabled.value = !newValue;
toast.error(t('admin.department_modules.self_wash.update_error'));
}
}
}
];
/**
* {
* id: 4,
* name: "Notifikationer",
* description: "Gå til notifikationer",
* icon: "fas fa-bell",
* onClick: () => {
* // Get the departmentId from the URL
* const departmentId = router.currentRoute.value.params.departmentId;
* // Redirect to the notifications
* router.push(`/admin/${departmentId}/modules/notifications`);
* },
* onClickCount: () => {
* this.onClick();
* },
* count: ref(0)
* },
*/
/**
* {
* id: 5,
* name: "Tidsbookinger",
* description: "Gå til tidsbookinger",
* icon: "fas fa-clock",
* onClick: () => {
* // Get the departmentId from the URL
* const departmentId = router.currentRoute.value.params.departmentId;
* // Redirect to the time bookings
* router.push(`/admin/${departmentId}/modules/time-bookings`);
* },
* onClickCount: () => {
* this.onClick();
* },
* count: ref(0)
* }
*/
]);
// Get the departmentId from the URL
const getDepartmentId = () => {
@@ -106,16 +167,34 @@ const getTodaysBookings = async () => {
'GET', {
department_id: getDepartmentId()
});
modules[1].count.value = response.data.data.message;
bookingsCount.value = response.data.data.message;
};
// Get the bookings
getTodaysBookings();
// Get the self-serve status
const getSelfServeStatus = async () => {
const departmentId = getDepartmentId();
if (!departmentId) return;
try {
const response = await authenticatedRequest(`/departments/self-serve/enabled?id=${departmentId}`, 'GET');
selfServeEnabled.value = response.data.data.enabled;
} catch (error) {
console.error("Failed to fetch self-serve status", error);
}
};
onMounted(() => {
getSelfServeStatus();
});
// Watch the departmentId
watch(() => router.currentRoute.value.params.departmentId, () => {
getTodaysBookings();
getSelfServeStatus();
});
// Update the bookings every 30 seconds
@@ -126,7 +205,7 @@ setInterval(() => {
<template>
<div class="columns is-multiline">
<div class="column is-4" v-for="module in modules" :key="module.id">
<div class="column is-3" v-for="module in modules" :key="module.id">
<div class="card is-clickable" @click="module.onClick">
<header class="card-header">
<div class="card-header-icon">
@@ -135,12 +214,15 @@ setInterval(() => {
</span>
</div>
<p class="card-header-title">{{ module.name }}</p>
<div class="is-pulled-right">
<span class="button is-small is-dark" @click="module.onClickCount" v-if="module.count.value > 0">{{ module.count.value }}</span>
<div class="is-pulled-right card-header-title pr-0">
<span class="button is-small is-dark" @click="module.onClickCount" v-if="module.count > 0">{{ module.count }}</span>
<span class="button is-small is-outlined" v-else-if="module?.hasSwitch" @click.stop>
<b-switch size="is-small" type="is-link" :model-value="module.switchValue" @update:model-value="module.onToggle"/>
</span>
</div>
</header>
<div class="card-content">
<p class="title">{{ module.name }}</p>
<p class="title is-4">{{ module.name }}</p>
<p class="subtitle">{{ module.description }}</p>
</div>
</div>
@@ -1,4 +1,8 @@
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
import EditableTableColumn from "@/components/displays/buttons/EditableTableColumn.vue";
import { loadList } from "@/components/pagination/paginatedList.vue";
const props = defineProps({
@@ -66,27 +70,27 @@ const hasUpdatePermission = () => {
:editFunction="su_object.showEditObjectFieldForm"
column="enabled"
:parse-function="(value) => {
return value ? SessionUser.objects.global.language.yes : SessionUser.objects.global.language.no;
return value ? t('global.yes') : t('global.no');
}"
:permission-check-function="hasUpdatePermission"
/>
<!-- Actions -->
<td class="is-float-right">
<ActionSettingsWheelButton>
<td>
<ActionSettingsWheelButton class="is-float-right">
<template #actions>
<RequiresPermission :permission="'department_notification_sms_delete'">
<template #default>
<ActionSettingsWheelItem
@click="su_object.functions.showDeleteObjectForm(obj.id, loadList)"
:icon="SessionUser.objects.global.icons.delete"
:label="SessionUser.objects.global.language.delete"
:label="t('global.delete')"
template="danger"
/>
</template>
<template #fail>
<ActionSettingsWheelItem
:icon="SessionUser.objects.global.icons.delete"
:label="SessionUser.objects.global.language.delete"
:label="t('global.delete')"
template="danger"
disabled
/>
@@ -99,7 +103,7 @@ const hasUpdatePermission = () => {
</tbody>
<tfoot>
<tr>
<td colspan="12">{{SessionUser.objects.global.language.showing}} {{ objects.length }} {{SessionUser.objects.department_notification_sms.meta.labels.multiple}}</td>
<td colspan="6">{{ $t('tables.showing') }} {{ objects.length }} {{ SessionUser.objects.department_notification_sms.meta.labels.multiple }}</td>
</tr>
</tfoot>
</table>
@@ -1,4 +1,8 @@
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
import { getDepartment, selectScan } from "@/components/shop/POSDepartmentProcess.vue";
import {
isLoaded,
@@ -26,11 +30,11 @@ const activeTab = ref(null);
const panel_tabs = ref([
{
name: 'Seneste scanninger',
name: t('tables.pos.scanned_plates'),
active: true
},
{
name: 'Søg efter nummerplade',
name: t('global.search'),
active: false
}
]);
@@ -310,12 +314,12 @@ setTimeout(() => {
class="is-size-4 title"
style="font-weight: 400;"
>
Nummerplader
{{ $t('objects.orders.columns.license_plate') }}
</p>
<p
class="is-size-6 subtitle mb-1"
>
Seneste scanninger
{{ $t('tables.pos.scanned_plates') }}
</p>
<p class="control mb-2">
@@ -323,7 +327,7 @@ setTimeout(() => {
@input="onChanged(); search($event.target.value);"
class="input mt-2 has-sharp-edges has-placeholder-italic"
type="text"
placeholder="Søg efter nummerplade"
:placeholder="$t('global.search')"
style="border-color: transparent;"
:style="{
'background-color': Colors.inputs.default.backgroundColor,
@@ -336,9 +340,9 @@ setTimeout(() => {
<table class="table is-fullwidth is-hoverable">
<thead>
<tr>
<th>Nummerplade</th>
<th class="is-narrow">Scanner</th>
<th class="is-narrow">{{ SessionUser.objects.global.language.time.at_hour }}</th>
<th>{{ $t('objects.orders.columns.license_plate') }}</th>
<th class="is-narrow">{{ $t('global.scanning') }}</th>
<th class="is-narrow">{{ $t('global.time.at_hour') }}</th>
</tr>
</thead>
<tbody>
@@ -374,7 +378,7 @@ setTimeout(() => {
<tr v-if="scansInfinityScroll !== null && scansInfinityScroll.length === 0">
<td colspan="3">
<div class="has-text-centered">
Ingen flere nummerplader
{{ $t('global.no_data') }}
</div>
</td>
</tr>
@@ -388,7 +392,7 @@ setTimeout(() => {
<span class="icon">
<i class="fas fa-check"></i>
</span>
<span>{{ SessionUser.objects.global.language.nothing_left_to_show }}</span>
<span>{{ $t('global.nothing_left_to_show') }}</span>
</div>
</td>
</tr>
@@ -398,7 +402,7 @@ setTimeout(() => {
<!-- If there's no scans, and the infinity scroll is also empty or null -->
<div class="panel-block" v-if="list.length === 0 && getDepartment() !== '' && (scansInfinityScroll === null || scansInfinityScroll.length === 0)">
<p>
Ingen nummerplader blev fundet
{{ $t('global.no_data') }}
</p>
</div>
<!-- Reload button -->
@@ -407,7 +411,7 @@ setTimeout(() => {
<span class="icon">
<i class="fas fa-redo"></i>
</span>
<span>Prøv igen</span>
<span>{{ $t('global.reload') }}</span>
</button>
</div>
</article>
@@ -1,4 +1,5 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { getDepartment, selectScan, department_id } from "@/components/shop/POSDepartmentProcess.vue";
import {
isLoaded,
@@ -23,15 +24,17 @@ import {popperBox, showPopper, showPopperWithContent, removePopperIfOpen} from "
import { Colors } from "@/ThemeConfig.vue";
import ColorIndicator from "@/components/displays/buttons/ColorIndicator.vue";
const { t } = useI18n();
const activeTab = ref(null);
const panel_tabs = ref([
{
name: 'Seneste scanninger',
name: t('admin.pos.recent_scans'),
active: true
},
{
name: 'Søg efter nummerplade',
name: t('admin.pos.search_license_plate'),
active: false
}
]);
@@ -40,10 +43,10 @@ const pendingBookings = ref([]);
// Get the department booking list
const loadPendingBookings = () => {
SessionUser.request(
SessionUser.objects.bookings.meta.endpoint,
SessionUser.objects.order_bookings.meta.endpoint,
'GET',
{
filters: 'department:' + department_id.value + ',status:pending',
filters: 'department:' + department_id.value + ',order_id:is null',
page: 1,
limit: 100,
}
@@ -72,12 +75,12 @@ const getVehiclePlateBooking = (vehiclePlate) => {
return null;
}
// Find the booking for the given vehicle plate
return pendingBookings.value.find(booking => booking.regNrTraekker === vehiclePlate || booking.regNrTrailer === vehiclePlate);
return pendingBookings.value.find(booking => booking.reg_1 === vehiclePlate || booking.reg_2 === vehiclePlate);
};
const doesVehiclePlateHaveBooking = (vehiclePlate) => {
// Check if the vehicle has a booking in the pending bookings list
return pendingBookings.value.some(booking => booking.regNrTraekker === vehiclePlate || booking.regNrTrailer === vehiclePlate);
return pendingBookings.value.some(booking => booking.reg_1 === vehiclePlate || booking.reg_2 === vehiclePlate);
};
@@ -232,12 +235,12 @@ const generatePopperPlate = (scan, motorapi_data) => {
}
return `
<div class="numberplate-popper" data-plate="${scan.plate}">
<p><strong>Mærke:</strong> ${data.make}</p>
<p><strong>Model:</strong> ${data.model}</p>
<p><strong>Variant:</strong> ${data.variant}</p>
<p><strong>Type:</strong> ${data.type}</p>
<p><strong>Use:</strong> ${data.use}</p>
<p><strong>Scannet:</strong> kl. ${data.time}</p>
<p><strong>${t('admin.pos.make')}:</strong> ${data.make}</p>
<p><strong>${t('admin.pos.model')}:</strong> ${data.model}</p>
<p><strong>${t('admin.pos.variant')}:</strong> ${data.variant}</p>
<p><strong>${t('admin.pos.type')}:</strong> ${data.type}</p>
<p><strong>${t('admin.pos.use')}:</strong> ${data.use}</p>
<p><strong>${t('admin.pos.scanned')}:</strong> kl. ${data.time}</p>
</div>
`;
};
@@ -269,7 +272,7 @@ const showPopperPlate = (scan, element) => {
let content = generatePopperPlate(scan, motorapi_data);
const uniqueId = "plate-popper-" + scan.plate;
showPopper(popperBox(
'Nummerplade: ' + scan.plate,
t('admin.pos.license_plate') + ': ' + scan.plate,
content,
uniqueId
), element);
@@ -485,7 +488,7 @@ const dropdown_content = (scan, moterapi_data = null) => {
}] : []),
// Make, variant
...(tmp_motorapi.make ? [{
text: 'Mærke',
text: t('admin.pos.make'),
action: () => {
},
classes: ['has-text-grey'],
@@ -495,7 +498,7 @@ const dropdown_content = (scan, moterapi_data = null) => {
v_centered: true,
}] : []),
...(tmp_motorapi.model ? [{
text: 'Model',
text: t('admin.pos.model'),
action: () => {
},
classes: ['has-text-grey'],
@@ -505,7 +508,7 @@ const dropdown_content = (scan, moterapi_data = null) => {
v_centered: true,
}] : []),
...(tmp_motorapi.variant && !isMotorAPIDataEmpty(tmp_motorapi.variant) ? [{
text: 'Variant',
text: t('admin.pos.variant'),
action: () => {
},
classes: ['has-text-grey'],
@@ -515,7 +518,7 @@ const dropdown_content = (scan, moterapi_data = null) => {
v_centered: true,
}] : []),
...(tmp_motorapi.use ? [{
text: 'Brug',
text: t('admin.pos.use'),
action: () => {
},
classes: ['has-text-grey'],
@@ -525,7 +528,7 @@ const dropdown_content = (scan, moterapi_data = null) => {
v_centered: true,
}] : []),
...(tmp_motorapi.type ? [{
text: 'Type',
text: t('admin.pos.type'),
action: () => {
},
classes: ['has-text-grey'],
@@ -599,7 +602,7 @@ const isDropdownContentLoaded = (scan) => {
<p
class="is-size-6 subtitle mb-1"
>
Seneste scanninger
{{ $t('admin.pos.recent_scans') }}
</p>
<p class="control mb-2">
@@ -607,7 +610,7 @@ const isDropdownContentLoaded = (scan) => {
@input="onChanged(); search($event.target.value);"
class="input mt-2 has-sharp-edges has-placeholder-italic"
type="text"
placeholder="Søg efter nummerplade"
:placeholder="$t('global.search_license_plate')"
style="border-color: transparent;"
:style="{
'background-color': Colors.inputs.default.backgroundColor,
@@ -621,7 +624,7 @@ const isDropdownContentLoaded = (scan) => {
<thead>
<tr>
<th>{{ SessionUser.objects.orders.columns.reg_1.label }}</th>
<th class="is-narrow">Scanner</th>
<th class="is-narrow">{{ $t('admin.pos.scanner') }}</th>
<th class="is-narrow">{{ SessionUser.objects.global.language.time.at_hour }}</th>
</tr>
</thead>
@@ -681,7 +684,7 @@ const isDropdownContentLoaded = (scan) => {
<tr v-if="scansInfinityScroll !== null && scansInfinityScroll.length === 0">
<td colspan="3">
<div class="has-text-centered">
Ingen flere nummerplader
{{ $t('admin.pos.no_plates_found') }}
</div>
</td>
</tr>
@@ -695,6 +698,7 @@ const isDropdownContentLoaded = (scan) => {
<span class="icon">
<i class="fas fa-check"></i>
</span>
<span>&nbsp;</span>
<span>{{ SessionUser.objects.global.language.nothing_left_to_show }}</span>
</div>
</td>
@@ -702,19 +706,13 @@ const isDropdownContentLoaded = (scan) => {
</tbody>
</table>
</div>
<!-- If there's no scans, and the infinity scroll is also empty or null -->
<div class="panel-block" v-if="list.length === 0 && getDepartment() !== '' && (scansInfinityScroll === null || scansInfinityScroll.length === 0)">
<p>
Ingen nummerplader blev fundet
</p>
</div>
<!-- Reload button -->
<div class="panel-block" v-if="list.length === 0 && getDepartment() !== '' && scansInfinityScroll === null">
<button class="button is-dark is-fullwidth" @click="loadList">
<span class="icon">
<i class="fas fa-redo"></i>
</span>
<span>Prøv igen</span>
<span>{{ $t('admin.pos.try_again') }}</span>
</button>
</div>
</article>
@@ -5,7 +5,9 @@ const props = defineProps(['notes', 'isAddFormVisible', 'isOldNotesVisible', 'is
import { ref } from 'vue';
import { addCustomerNote, isCustomerSelected, showDeleteNoteDialog } from "@/components/shop/POSDepartmentProcess.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const newNote = ref('');
const showLabel = ref(false);
showLabel.value = props.isLabelVisible || false;
@@ -37,7 +39,7 @@ showLabel.value = props.isLabelVisible || false;
@click="addCustomerNote(newNote)"
v-if="isCustomerSelected() && newNote.length > 0"
>
Tilføj bemærkning
{{ t('pos.add_note') }}
</button>
</div>
</RequiresPermission>
@@ -1,4 +1,5 @@
<script setup>
import { useI18n } from 'vue-i18n';
import LoadButtonWhileAwait from "@/components/request/LoadButtonWhileAwait.vue";
import { showEditOrderItemForm } from "@/components/forms/superUser/editOrderItemForm.vue";
import { removeOrderItem } from "@/components/shop/OrdersItems.vue";
@@ -16,17 +17,19 @@ import PosOrderLicensePlates from "@/components/displays/department/pos/order/Po
import POSOrderReference from "@/components/displays/department/pos/order/POSOrderReference.vue";
import PosNotes from "@/components/displays/department/pos/PosNotes.vue";
import POSOrderNote from "@/components/displays/department/pos/order/POSOrderNote.vue";
const { t } = useI18n();
// Tabs
const panel_tabs = ref([
{
icon: 'fas fa-shopping-cart',
name: 'Kurv',
name: t('admin.pos.cart'),
active: true,
},
{
icon: 'fas fa-paperclip',
name: 'Bilag',
name: t('admin.pos.attachments'),
active: false,
}
]);
@@ -39,7 +42,7 @@ const hasOtherSlot = useSlots().other !== undefined;
// If so, add the other tab
if (hasOtherSlot) {
panel_tabs.value.push({
name: 'Indstillinger',
name: t('admin.pos.settings'),
active: false,
icon: 'fas fa-cog',
});
@@ -50,7 +53,7 @@ const hasDetailsSlot = useSlots().details !== undefined;
// If so, add the other tab
if (hasDetailsSlot) {
panel_tabs.value.push({
name: 'Detaljer',
name: t('admin.pos.details'),
active: false,
icon: 'fas fa-info',
});
@@ -149,7 +152,7 @@ const removeItem = async (id) => {
>
{{ customer_name }}
</SpanSkeleton>
<p class="is-7 subtitle mb-2">Transaktion #{{ orderId }}</p><slot name="headingcenter"></slot><span class="is-float-right"><slot></slot></span>
<p class="is-7 subtitle mb-2">{{ $t('admin.pos.transaction') }} #{{ orderId }}</p><slot name="headingcenter"></slot><span class="is-float-right"><slot></slot></span>
<p class="panel-tabs is-hidden-touch" v-if="panel_tabs.length > 1">
<a v-for="(tab, index) in panel_tabs" :key="index" :class="{'is-active': tab.active}" @click="panel_tabs.forEach(tab => tab.active = false); tab.active = true" >
<!-- If the tab has an icon, show it -->
@@ -182,8 +185,8 @@ const removeItem = async (id) => {
<table class="table is-fullwidth">
<thead>
<tr>
<th>Produkt</th>
<th style="width: 20%; text-align: right;">Pris (DKK)</th>
<th>{{ $t('admin.pos.product') }}</th>
<th style="width: 20%; text-align: right;">{{ $t('admin.pos.price_dkk') }}</th>
<th v-if="editPossible" style="width: 15%;"></th>
</tr>
</thead>
@@ -212,7 +215,7 @@ const removeItem = async (id) => {
@click="showEditOrderItemForm(orderItem.id, orderItem.product.name, orderItem.price, orderItem.notes, orderItem.reference, orderItem.quantity, editPossible).then(() => loadOrderItems())"
class="ml-2 is-size-7 has-text-grey is-clickable">
<i class="fas fa-comment"></i>
Se note
{{ $t('admin.pos.see_note') }}
</span>
<!-- Reference (If there are any) -->
<span v-if="orderItem.reference"
@@ -227,7 +230,7 @@ const removeItem = async (id) => {
@click="showEditOrderItemForm(orderItem.id, orderItem.product.name, orderItem.price, orderItem.notes, orderItem.reference, orderItem.quantity, editPossible).then(() => loadOrderItems())"
class="ml-2 is-size-7 has-text-grey is-clickable">
<i class="fas fa-link"></i>
Se reference
{{ $t('admin.pos.see_reference') }}
</span>
<!-- Quantity (If it is more than 1) -->
<span v-if="orderItem.quantity > 1"
@@ -242,7 +245,7 @@ const removeItem = async (id) => {
@click="showEditOrderItemForm(orderItem.id, orderItem.product.name, orderItem.price, orderItem.notes, orderItem.reference, orderItem.quantity, editPossible).then(() => loadOrderItems())"
class="ml-2 is-size-7 has-text-grey is-clickable">
<i class="fas fa-box"></i>
Antal: {{ orderItem.quantity }}
{{ $t('admin.pos.quantity') }}: {{ orderItem.quantity }}
</span>
</td>
<!-- If the price has been changed, show the new price (With a strike-through) and the old price -->
@@ -277,13 +280,13 @@ const removeItem = async (id) => {
<ActionSettingsWheelButton>
<template #actions>
<ActionSettingsWheelItem
label="Slet"
:label="$t('admin.pos.delete')"
icon="fas fa-trash"
template="danger"
@click="hideOrderItemWhileRemoving(orderItem.id);removeItem(orderItem.id).then(() => loadOrderItems())"
/>
<ActionSettingsWheelItem
label="Rediger"
:label="$t('admin.pos.edit')"
icon="fas fa-edit"
template="warning"
:disabled="!SessionUser.hasPermission('edit_order_items') || props.disallowItemEdit"
@@ -318,7 +321,7 @@ const removeItem = async (id) => {
@click="showEditOrderItemForm(relatedItem.id, relatedItem.product.name, relatedItem.price, relatedItem.notes, relatedItem.reference, relatedItem.quantity, editPossible).then(() => loadOrderItems())"
class="ml-2 is-size-7 has-text-grey is-clickable">
<i class="fas fa-comment"></i>
Se note
{{ $t('admin.pos.see_note') }}
</span>
<!-- Reference (If there are any) -->
<span v-if="relatedItem.reference"
@@ -333,7 +336,7 @@ const removeItem = async (id) => {
@click="showEditOrderItemForm(relatedItem.id, relatedItem.product.name, relatedItem.price, relatedItem.notes, relatedItem.reference, relatedItem.quantity, editPossible).then(() => loadOrderItems())"
class="ml-2 is-size-7 has-text-grey is-clickable">
<i class="fas fa-link"></i>
Se reference
{{ $t('admin.pos.see_reference') }}
</span>
</span>
</td>
@@ -370,13 +373,13 @@ const removeItem = async (id) => {
<ActionSettingsWheelButton>
<template #actions>
<ActionSettingsWheelItem
label="Slet"
:label="$t('admin.pos.delete')"
icon="fas fa-trash"
template="danger"
@click="hideOrderItemWhileRemoving(relatedItem.id);removeItem(relatedItem.id).then(() => loadOrderItems())"
/>
<ActionSettingsWheelItem
label="Rediger"
:label="$t('admin.pos.edit')"
icon="fas fa-edit"
template="warning"
:disabled="!SessionUser.hasPermission('edit_order_items') || props.disallowItemEdit"
@@ -393,7 +396,7 @@ const removeItem = async (id) => {
</tbody>
<tfoot>
<tr>
<td>Total</td>
<td>{{ $t('admin.pos.total') }}</td>
<td colspan="1" style="width: 20%; text-align: right;">
<span
@mouseover="showPopper(
@@ -442,7 +445,7 @@ const removeItem = async (id) => {
<div class="columns is-multiline" :class="{ 'is-flex-direction-row-reverse': isLarge}" v-if="isTabZeroExpanded">
<div class="column" :class="isLarge ? 'is-6' : 'is-12'">
<!-- License plates -->
<div v-if="isLarge" class="divider is-size-7">Nummerplader</div>
<div v-if="isLarge" class="divider is-size-7">{{ $t('admin.pos.license_plates') }}</div>
<PosOrderLicensePlates
v-bind:registration_numbers="registration_numbers"
v-bind:loadOrder="loadOrder"
@@ -451,7 +454,7 @@ const removeItem = async (id) => {
</div>
<div class="column" :class="isLarge ? 'is-6' : 'is-12'">
<!-- Reference -->
<div v-if="isLarge" class="divider is-size-7">Reference</div>
<div v-if="isLarge" class="divider is-size-7">{{ $t('admin.pos.reference') }}</div>
<POSOrderReference
v-bind:order_id="order_id"
v-bind:loadOrder="loadOrder"
@@ -460,7 +463,7 @@ const removeItem = async (id) => {
</div>
<div class="column is-12">
<!-- Notes -->
<div class="divider is-size-7">Ordre note</div>
<div class="divider is-size-7">{{ $t('admin.pos.order_note') }}</div>
<POSOrderNote
v-bind:order_id="order_id"
v-bind:loadOrder="loadOrder"
@@ -143,6 +143,12 @@ const attributes = ref([
description: 'Når denne er sat, kan kunden angive et PO nummer på ordrer.',
icon: 'fas fa-cogs'
},
{
name: 'Undtaget fra månedligt administrations- og miljøgebyr',
prop: 'exemptFromAdministrationFee',
description: 'Når denne er sat, bliver kunden undtaget fra månedligt administrations- og miljøgebyr.',
icon: 'fas fa-cogs'
},
]);
const shortcuts = ref([
@@ -5,23 +5,38 @@ import productImage02 from "/src/assets/piktogrammer/02.png";
import productImage03 from "/src/assets/piktogrammer/03.png"; // Replaced by 3
import productImage3 from "/src/assets/piktogrammer/3.png"; // Sættevognstræk
import productImage04 from "/src/assets/piktogrammer/04.png";
import productImage05 from "/src/assets/piktogrammer/05.png";
import productImage06 from "/src/assets/piktogrammer/06.png";
import productImage05 from "/src/assets/piktogrammer/05.png"; // Deprecated - use 5
import productImage5 from "/src/assets/piktogrammer/5.png"; // Forvogn
import productImage06 from "/src/assets/piktogrammer/06.png"; // Deprecated - use 6
import productImage6 from "/src/assets/piktogrammer/6.png"; // Forvogn med hænger
import productImage07 from "/src/assets/piktogrammer/07.png";
import productImage08 from "/src/assets/piktogrammer/08.png";
import productImage09 from "/src/assets/piktogrammer/09.png";
import productImage10 from "/src/assets/piktogrammer/10.png";
import productImage11 from "/src/assets/piktogrammer/11.png";
import productImage12 from "/src/assets/piktogrammer/12.png";
import productImage13 from "/src/assets/piktogrammer/13.png";
import productImage14 from "/src/assets/piktogrammer/14.png";
import productImage15 from "/src/assets/piktogrammer/15.png";
import productImage16 from "/src/assets/piktogrammer/16.png"; // Autocamper / Campingvogn
import productImage18 from "/src/assets/piktogrammer/18.png";
import productImage17 from "/src/assets/piktogrammer/17.png"; // Bus
import productImage18 from "/src/assets/piktogrammer/18.png"; // Entreprenør forvogn
import productImage19 from "/src/assets/piktogrammer/19.png"; // Entreprenør forvogn med hænger
import productImage20 from "/src/assets/piktogrammer/20.png"; // Entreprenør sættevogn
import productImage21 from "/src/assets/piktogrammer/21.png"; // Undervognsskyld.
import productImage23 from "/src/assets/piktogrammer/23.png"; // Spotfree
import productImage24 from "/src/assets/piktogrammer/24.png"; // Spotfree
import productImage25 from "/src/assets/piktogrammer/25.png"; // Fælg flex
import productImage26 from "/src/assets/piktogrammer/26.png"; // Højglans
import productImage27 from "/src/assets/piktogrammer/27.png"; // 10 Minutter
import productImage39 from "/src/assets/piktogrammer/39.png"; // Trailer - Varevogn
import productImage43 from "/src/assets/piktogrammer/43.png"; // Handling of materials
import productImage44 from "/src/assets/piktogrammer/44.png"; // "Christmas Tree"
import productImage45 from "/src/assets/piktogrammer/45.png"; // "Christmas Tree" - Sticker roll
import productImage46 from "/src/assets/piktogrammer/46.png"; // Kødkroge, vask
import productImage47 from "/src/assets/piktogrammer/47.png"; // Kødkroge med mærker, vask
import productImage48 from "/src/assets/piktogrammer/48.png"; // E2
import productImage49 from "/src/assets/piktogrammer/49.png"; // Interior wash forvogn hænger
import productImage56 from "/src/assets/piktogrammer/56.png"; // Safety Seal
// Get the picture of the product
export const getPicture = (piktogram) => {
@@ -46,9 +61,11 @@ export const getPicture = (piktogram) => {
case "04":
return productImage04;
case "05":
return productImage05;
case "5": // Forvogn
return productImage5;
case "06":
return productImage06;
case "6": // Forvogn med hænger
return productImage6;
case "07":
return productImage07;
case "08":
@@ -57,16 +74,40 @@ export const getPicture = (piktogram) => {
return productImage09;
case "10":
return productImage10;
case "11":
return productImage11;
case "12":
return productImage12;
case "13":
return productImage13;
case "14":
return productImage14;
case "15":
return productImage15;
case "16":
return productImage16;
case "17":
return productImage17;
case "18":
return productImage18;
case "19":
return productImage19;
case "20":
return productImage20;
case "21":
return productImage21;
case "23":
return productImage23;
case "24":
return productImage24;
case "25":
return productImage25;
case "26":
return productImage26;
case "27":
return productImage27;
case "39":
return productImage39;
case "43":
return productImage43;
case "44":
@@ -79,6 +120,8 @@ export const getPicture = (piktogram) => {
return productImage47;
case "48":
return productImage48;
case "49":
return productImage49;
case "56":
return productImage56;
default:
@@ -1,4 +1,8 @@
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import EditableTableColumn from "@/components/displays/buttons/EditableTableColumn.vue";
import ConfigurationInput from "@/components/displays/superuser/configuration/ConfigurationInput.vue";
@@ -62,11 +66,11 @@ const calculateTotalPrice = (price, quantity) => {
<table class="table is-fullwidth is-hoverable is-striped is-bordered">
<thead>
<tr>
<th>{{SessionUser.functions.ucFirst(SessionUser.objects.products.meta.labels.single)}}</th>
<th>{{SessionUser.objects.global.language.note}}</th>
<th>{{SessionUser.objects.orders.columns.reference.label}}</th>
<th>{{SessionUser.objects.global.language.quantity}}</th>
<th style="text-align: right;">{{SessionUser.objects.global.language.price}}</th>
<th>{{ $t('tables.products.name') }}</th>
<th>{{ $t('global.note') }}</th>
<th>{{ $t('objects.columns.reference') }}</th>
<th>{{ $t('global.quantity') }}</th>
<th style="text-align: right;">{{ $t('global.price') }}</th>
</tr>
</thead>
<tbody>
@@ -99,7 +103,7 @@ const calculateTotalPrice = (price, quantity) => {
<tfoot>
<!-- Tax -->
<tr v-if="props.taxPercentage > 0">
<td colspan="4">Moms ({{ props.taxPercentage }}%)</td>
<td colspan="4">{{ $t('global.tax') }} ({{ props.taxPercentage }}%)</td>
<td style="text-align: right;" v-if="props.isLoading">
<!-- Skeleton-lines 1 row -->
<div class="skeleton-lines"><div style="width: 100%;"></div></div>
@@ -107,7 +111,7 @@ const calculateTotalPrice = (price, quantity) => {
<td style="text-align: right;" v-else>{{ SessionUser.functions.currency.toLocal(calculateTax(props.orderItems.reduce((acc, orderItem) => acc + orderItem.price * orderItem.quantity, 0))) }}</td>
</tr>
<tr v-if="props.taxPercentage > 0">
<td colspan="4">Total ekskl. moms</td>
<td colspan="4">{{ $t('tables.orders.total') }} {{ $t('global.excl_tax') }}</td>
<td style="text-align: right;" v-if="props.isLoading">
<!-- Skeleton-lines 1 row -->
<div class="skeleton-lines"><div style="width: 100%;"></div></div>
@@ -115,7 +119,7 @@ const calculateTotalPrice = (price, quantity) => {
<td style="text-align: right;" v-else>{{ SessionUser.functions.currency.toLocal(props.orderItems.reduce((acc, orderItem) => acc + orderItem.price * orderItem.quantity, 0)) }}</td>
</tr>
<tr>
<td colspan="4">Total {{ props.taxPercentage > 0 ? 'inkl. moms' : '' }}</td>
<td colspan="4">{{ $t('tables.orders.total') }} {{ props.taxPercentage > 0 ? $t('global.incl_tax') : '' }}</td>
<td style="text-align: right;" v-if="props.isLoading">
<!-- Skeleton-lines 1 row -->
<div class="skeleton-lines"><div style="width: 100%;"></div></div>
@@ -123,12 +127,12 @@ const calculateTotalPrice = (price, quantity) => {
<td style="text-align: right;" v-else>{{ SessionUser.functions.currency.toLocal(props.orderItems.reduce((acc, orderItem) => acc + orderItem.price * orderItem.quantity, 0) + calculateTax(props.orderItems.reduce((acc, orderItem) => acc + orderItem.price * orderItem.quantity, 0))) }}</td>
</tr>
<tr v-if="props.showPaymentStatus">
<td colspan="4">Payment Status</td>
<td colspan="4">{{ $t('global.payment_status') }}</td>
<td style="text-align: right;" v-if="props.isLoading">
<!-- Skeleton-lines 1 row -->
<div class="skeleton-lines"><div style="width: 100%;"></div></div>
</td>
<td style="text-align: right;" :class="props.paid ? 'has-text-success' : 'has-text-danger'" v-else>{{ props.paid ? 'Paid' : 'Unpaid' }}</td>
<td style="text-align: right;" :class="props.paid ? 'has-text-success' : 'has-text-danger'" v-else>{{ props.paid ? $t('tables.invoices.paid') : $t('tables.invoices.unpaid') }}</td>
</tr>
</tfoot>
</table>
File diff suppressed because it is too large Load Diff
@@ -1,4 +1,5 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { Colors } from "@/ThemeConfig.vue";
import NextStep from "@/components/forms/department/pos/buttons/NextStep.vue";
import NextStepError from "@/components/forms/department/pos/error/NextStepError.vue";
@@ -10,7 +11,7 @@ import ButtonsBox from "@/components/displays/boxes/ButtonsBox.vue";
import { isCustomerSelected, department_id, getDepartment, reg_1 } from "@/components/shop/POSDepartmentProcess.vue";
import {computed, onMounted, ref} from "vue";
import ElementTabsBox from "@/components/displays/boxes/ElementTabsBox.vue";
import {POS_STEP_1_VERSION} from "@/main.js";
import {POS_STEP_1_VERSION} from "@/config.js";
import SelectVehicleFormPOS from "@/components/forms/department/pos/SelectVehicleFormPOS.vue";
import PosLastScannedLicensePlatesV2 from "@/components/displays/department/pos/PosLastScannedLicensePlatesV2.vue";
import { deleteEverything } from "@/components/shop/POSDepartmentProcess.vue";
@@ -19,6 +20,8 @@ import Swal from "sweetalert2";
import DefaultObjectSelector from "@/components/displays/modals/DefaultObjectSelector.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
const { t } = useI18n();
// Clear cache
clearCache();
@@ -26,13 +29,13 @@ clearCache();
// Define the tabs
const right_tabs = ref([
{
name: "Nummerplader",
name: t('admin.pos.license_plates'),
slot: "license_plates",
icon: "fas fa-car",
hidden: false,
},
{
name: "Kunde",
name: t('admin.pos.customer'),
slot: "customer",
icon: "fas fa-user",
hidden: () => !isCustomerSelected(), // Hide the tab if no customer is selected
@@ -142,11 +145,11 @@ const checkDuplicateOrders = (reg) => {
};
potentialDuplicateOrders.value.objects.push({
id: order.id,
label: `Ordre #${order.id} - ${order.created_at}`,
label: `${t('admin.pos.order')} #${order.id} - ${order.created_at}`,
content: order, // Store the order content
buttons: [
{
label: "Vis detaljer",
label: t('admin.pos.show_details'),
action: () => {
// Action to show order details
console.log("Show details for order #" + order.id);
@@ -184,18 +187,16 @@ const showDuplicateOrdersWarning = () => {
}
}
Swal.fire({
title: 'Advarsel',
text: 'Er du sikker på, at denne ikke allerede er skrevet? ' +
'Der er fundet potentielle dubletter af denne ordre. ' +
'Du kan fortsætte med at skrive ordren, eller se ordre detaljer.',
title: t('admin.pos.warning'),
text: t('admin.pos.duplicate_order_warning'),
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Fortsæt',
confirmButtonText: t('admin.pos.continue'),
confirmButtonColor: Colors.buttons.default.backgroundColor,
cancelButtonText: 'Annuller',
cancelButtonText: t('admin.pos.cancel'),
cancelButtonColor: Colors.buttons.warning.activeColor,
showDenyButton: true,
denyButtonText: 'Vis ordre detaljer',
denyButtonText: t('admin.pos.show_order_details'),
denyButtonColor: Colors.buttons.default.secondaryBackgroundColor,
}).then((result) => {
if (result.isConfirmed) {
@@ -215,13 +216,13 @@ const showDuplicateOrdersDetails = () => {
const orderIds = Object.keys(potentialDuplicateOrderObjects.value);
potentialDuplicateOrders.value.objects = orderIds.map(id => ({
id: parseInt(id),
label: `Ordre #${id} - ${potentialDuplicateOrderObjects.value[id].loaded ? potentialDuplicateOrderObjects.value[id].created_at : "Indlæser..."}`,
label: `${t('admin.pos.order')} #${id} - ${potentialDuplicateOrderObjects.value[id].loaded ? potentialDuplicateOrderObjects.value[id].created_at : t('admin.pos.loading')}`,
content: potentialDuplicateOrderObjects.value[id]
? parseOrderContent(potentialDuplicateOrderObjects.value[id])
: "Indlæser...",
: t('admin.pos.loading'),
buttons: [
{
label: "Vis detaljer",
label: t('admin.pos.show_details'),
action: () => {
// Action to show order details
console.log("Show details for order #" + id);
@@ -242,7 +243,7 @@ const showDuplicateOrdersDetails = () => {
const parseOrderContent = (order) => {
// Parse the order content to display in the modal
if (!order) {
return "Ingen indhold tilgængeligt.";
return t('admin.pos.no_content_available');
}
return ``;
};
@@ -371,4 +372,4 @@ const getPotentialDuplicateOrders = computed(() => {
<style scoped>
</style>
</style>

Some files were not shown because too many files have changed in this diff Show More