Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fc889dd75 | ||
|
|
8aa6fcf17f | ||
|
|
9f5fba3400 | ||
|
|
ca26751b9f | ||
|
|
66298d7d45 | ||
|
|
76e5b03bc2 | ||
|
|
731ba09696 | ||
|
|
fe3f4b10f0 | ||
|
|
2266e3908f | ||
|
|
b0a2c0216f | ||
|
|
16d63a2710 | ||
|
|
fe3318e6ac | ||
|
|
d6be146d91 | ||
|
|
27f6de8b42 | ||
|
|
6c2ca85dee | ||
|
|
fd8b86987f | ||
|
|
665dc61d98 | ||
|
|
33c64659c7 | ||
|
|
bb8ae993eb | ||
|
|
3f71fb85f4 | ||
|
|
7d3630fa62 | ||
|
|
32a2725de2 | ||
|
|
f15c07e43e | ||
|
|
c4deaa8e86 | ||
|
|
1ed8a30826 | ||
|
|
5dc35d0a1b | ||
|
|
63a10dd5c4 | ||
|
|
19be7ef797 | ||
|
|
ecf65b47a1 | ||
|
|
3e1f8ed6d6 | ||
|
|
5ccfb5f050 | ||
|
|
8ae010b55c | ||
|
|
702b599490 | ||
|
|
c24eeca6b4 | ||
|
|
2156d55650 | ||
|
|
ba63aab53b | ||
|
|
caff98690c | ||
|
|
22f9a486c6 | ||
|
|
85477916d5 | ||
|
|
b173b14e86 | ||
|
|
66c9936d32 | ||
|
|
316cca9ca3 | ||
|
|
393a46ae34 | ||
|
|
b41d839258 | ||
|
|
1f27f3b069 | ||
|
|
ec292bce73 | ||
|
|
48896d2b83 | ||
|
|
6829955f2b | ||
|
|
ec93989002 | ||
|
|
08a6f4fefd | ||
|
|
ccf4e139f6 | ||
|
|
cb1e007fda | ||
|
|
7c8f79f13b | ||
|
|
d2e0467605 | ||
|
|
8805247dbc | ||
|
|
b79ce07cda | ||
|
|
44f78e817e | ||
|
|
1666c3de45 | ||
|
|
da0bae5bf2 | ||
|
|
b7bc4ec907 | ||
|
|
c9fee21d24 | ||
|
|
40a6a4f664 | ||
|
|
710c05bf52 | ||
|
|
0b7f79d59c | ||
|
|
5606f2d5f7 | ||
|
|
9172a95221 | ||
|
|
4af3f2a997 | ||
|
|
314462345d | ||
|
|
a08ef03bb8 | ||
|
|
59d1272e98 | ||
|
|
71c549df87 | ||
|
|
b779d9b479 | ||
|
|
55e85215ce | ||
|
|
931b473efe | ||
|
|
ec1114b1cd | ||
|
|
344acf4a13 | ||
|
|
95470794f5 | ||
|
|
ae7c1dac5b | ||
|
|
e15e94545c | ||
|
|
a7d4373abc | ||
|
|
14be8db1a6 | ||
|
|
194c5a8703 | ||
|
|
083c53e75f | ||
|
|
bffde4efc7 | ||
|
|
f8c0925fef | ||
|
|
be37edaa15 | ||
|
|
28357df51d | ||
|
|
7775f58a29 | ||
|
|
1d61efbe73 | ||
|
|
28e7b5f23e | ||
|
|
d812e57be7 | ||
|
|
a68d7a0b95 | ||
|
|
39b71e5f2b | ||
|
|
8257568573 | ||
|
|
46afda9fb7 | ||
|
|
09b0b6c80b | ||
|
|
159470d921 | ||
|
|
4b158042af | ||
|
|
bf16481b29 | ||
|
|
9c236afdeb | ||
|
|
40846dcf2a | ||
|
|
d9516f0e17 | ||
|
|
3e5c5b893c | ||
|
|
9ba2141d42 | ||
|
|
a51fb8baff | ||
|
|
1daa7d6660 | ||
|
|
b99fdd620d | ||
|
|
777dd9ef3c | ||
|
|
ac2d71a202 | ||
|
|
70815ad401 | ||
|
|
a2aede936e | ||
|
|
28d01cb4dd | ||
|
|
1bd3feab66 | ||
|
|
bf573a9ca9 | ||
|
|
825219e264 | ||
|
|
670324c7fb | ||
|
|
0cf1cc4498 | ||
|
|
c3f99d15c7 | ||
|
|
7371d235b7 | ||
|
|
b828fb9c77 | ||
|
|
e29813e1a3 | ||
|
|
21ae3b23b7 | ||
|
|
c0ac599776 | ||
|
|
dffd93fe14 | ||
|
|
52ae521ad6 | ||
|
|
809540e0ff | ||
|
|
781b419a28 | ||
|
|
ec5ff41bfa | ||
|
|
4c9199e66d | ||
|
|
74f35b3008 | ||
|
|
df716ccbaf | ||
|
|
e55b21acae | ||
|
|
00b9932c40 | ||
|
|
6e02618a38 | ||
|
|
943a143a19 | ||
|
|
d0ddf29235 | ||
|
|
80c4d16eff | ||
|
|
fbf55c6d18 | ||
|
|
11dde044e0 | ||
|
|
0bef66201b | ||
|
|
c4ab40c76f | ||
|
|
5de72344f8 | ||
|
|
241efe34aa | ||
|
|
017737df19 | ||
|
|
10fe180ad2 | ||
|
|
ac4f3912ff | ||
|
|
632cd83a4a | ||
|
|
35ffc82aa0 | ||
|
|
989ee5acce | ||
|
|
603bef2d44 | ||
|
|
d770da869f | ||
|
|
479962e755 | ||
|
|
e65f4b2fc6 | ||
|
|
85b84df7f7 | ||
|
|
e7ccb29b05 | ||
|
|
3ecaee7aed | ||
|
|
2435a85ff9 | ||
|
|
f2e82f8812 | ||
|
|
143ca80eb4 | ||
|
|
40884fc9e6 | ||
|
|
52529fec3e | ||
|
|
2c7d69c458 | ||
|
|
4bac68f098 | ||
|
|
3cf3dc4146 | ||
|
|
c1873d76c9 | ||
|
|
3618c570b5 | ||
|
|
b637b9dc5f | ||
|
|
21e763b25a | ||
|
|
b59d4cff01 | ||
|
|
4300f5c646 | ||
|
|
2b7f29b6ef | ||
|
|
70029d72d9 | ||
|
|
1749f52f24 | ||
|
|
a6867643fc | ||
|
|
4ea93ff0c8 | ||
|
|
36b60038b9 | ||
|
|
b20953e51b | ||
|
|
0591d39a1d | ||
|
|
66307a9aad | ||
|
|
32e557a9df | ||
|
|
585e56a87f | ||
|
|
1c13e2c80d | ||
|
|
aaa0298bae | ||
|
|
b1ffa45bc5 | ||
|
|
cecd7eb64a | ||
|
|
b2cac639ce | ||
|
|
672e6407fd | ||
|
|
67e00e2c99 | ||
|
|
f332ad1f4f | ||
|
|
9da48a36c0 | ||
|
|
2d7d3265c3 | ||
|
|
e2c31b1db6 | ||
|
|
7962c805e0 | ||
|
|
467365531d | ||
|
|
09f0b76724 | ||
|
|
e2492b63b9 | ||
|
|
3a415a8fd5 | ||
|
|
626c1d56bb | ||
|
|
745a70621a | ||
|
|
af21e028ea | ||
|
|
549b6208ce | ||
|
|
8fe1ee2189 | ||
|
|
0bf96e7f21 | ||
|
|
178ad88661 | ||
|
|
17df279797 | ||
|
|
e338b3ced1 | ||
|
|
9e3d4cfccb | ||
|
|
9ff0bcec17 | ||
|
|
79ce34af5f | ||
|
|
67bcbf45f5 | ||
|
|
193915e5f1 | ||
|
|
9ac322151b | ||
|
|
33ee8c1803 | ||
|
|
9c6ffd2297 | ||
|
|
1751d98cd3 | ||
|
|
a27ab7c2a1 | ||
|
|
a5b7fa7cd3 | ||
|
|
f45c676a14 | ||
|
|
b9cd4bf1fd | ||
|
|
e64f936b3b | ||
|
|
6f4d7f8699 | ||
|
|
ff98b84acb | ||
|
|
2dad26078e | ||
|
|
c0adca9558 | ||
|
|
010b0d8af7 | ||
|
|
ee0d486101 | ||
|
|
0b6d2e4045 | ||
|
|
10526cfe6e | ||
|
|
4a0edb59db | ||
|
|
2f16a91007 | ||
|
|
d5c3f44da7 | ||
|
|
de25e9c5f0 | ||
|
|
e9d72918a7 | ||
|
|
5276322dcc | ||
|
|
cb018cc2ec | ||
|
|
429709e270 | ||
|
|
7f511dd205 | ||
|
|
0fd65e5eaf | ||
|
|
2dd5263087 | ||
|
|
3330f20db4 | ||
|
|
eb6863e4a8 | ||
|
|
21b8324933 | ||
|
|
ead0ceca39 | ||
|
|
815f47004b | ||
|
|
895ab6ffd2 | ||
|
|
16d8b120f7 | ||
|
|
aac14e474a | ||
|
|
fba30ba2ac | ||
|
|
7a6d2e0b8d | ||
|
|
16ccda0c33 | ||
|
|
096b878d3d | ||
|
|
aab494b239 | ||
|
|
8a2b30b117 | ||
|
|
e66b7c8853 | ||
|
|
203e39aa6c | ||
|
|
ce2c313f63 | ||
|
|
da45e23324 | ||
|
|
4c0cd44a82 | ||
|
|
3370bf7469 | ||
|
|
b860417cc4 | ||
|
|
58c6a528e3 | ||
|
|
8c88052784 | ||
|
|
7b5262cebb | ||
|
|
7e64e9f1aa | ||
|
|
32ae94069b | ||
|
|
333fecd2c4 | ||
|
|
6cc425dd1e | ||
|
|
6e300cd5c3 | ||
|
|
e3db468d0b | ||
|
|
dc5533ccb0 | ||
|
|
08ad376e6d | ||
|
|
f6784bc815 | ||
|
|
6e2eb81d5b | ||
|
|
7300cb5722 | ||
|
|
617793830b | ||
|
|
72edbf338d | ||
|
|
a9f8e59433 | ||
|
|
b50f07b409 | ||
|
|
35439fb963 | ||
|
|
16866155c1 | ||
|
|
e94e2f0541 | ||
|
|
a861af5a9c | ||
|
|
d5b83f64cc | ||
|
|
c84d0d2851 | ||
|
|
bfad04aa85 | ||
|
|
97809d2e43 | ||
|
|
94f1528d4a | ||
|
|
f71451ad88 | ||
|
|
b035da7984 | ||
|
|
3071e7f777 | ||
|
|
824840d729 | ||
|
|
d17c84a42c | ||
|
|
2314f02239 | ||
|
|
b34bb0b0dc | ||
|
|
65d6e510d1 | ||
|
|
d63b28977f | ||
|
|
4c6f8df45d | ||
|
|
4a76137a90 | ||
|
|
60dcf3e227 | ||
|
|
27d3200c3b | ||
|
|
1c67f64c7e | ||
|
|
39adc6536d | ||
|
|
f7c75f75fa | ||
|
|
06d84872ff | ||
|
|
f019c43853 | ||
|
|
75ccc4ab1e | ||
|
|
03e28c6070 | ||
|
|
3831471f54 | ||
|
|
f808caff31 | ||
|
|
3ddb6c168e | ||
|
|
40b90ff2d1 | ||
|
|
66f458014c | ||
|
|
45c9e87380 | ||
|
|
7945e0996b | ||
|
|
a74d4ab042 | ||
|
|
00995e38aa | ||
|
|
f2f9d24652 | ||
|
|
d2de664186 | ||
|
|
70949630fb | ||
|
|
bc6c382b9e | ||
|
|
5021b99ca1 | ||
|
|
dd2dcc9043 | ||
|
|
5329b62bc5 | ||
|
|
83bc1246fb | ||
|
|
54cc7c298c | ||
|
|
7a43669ed5 | ||
|
|
d1446f1435 | ||
|
|
b548582c7c | ||
|
|
933eba04aa | ||
|
|
ac7ddc37fb | ||
|
|
eed5d6aa3b | ||
|
|
e9a83700fa | ||
|
|
61c828c051 | ||
|
|
fdc5b845be | ||
|
|
aeb57e70e2 | ||
|
|
9920455728 | ||
|
|
e91f8f2a4b | ||
|
|
f8a57c9654 | ||
|
|
2ce314590e | ||
|
|
8110bff6db | ||
|
|
e22092ada2 | ||
|
|
284379e534 | ||
|
|
f8b171dc71 | ||
|
|
ba9a5551a3 | ||
|
|
00b5a63477 | ||
|
|
a87526fa64 | ||
|
|
8fbbdf5fd0 | ||
|
|
1be833462a | ||
|
|
b4b85d6a9b | ||
|
|
6a16c5aa7b | ||
|
|
5e4713338e | ||
|
|
ac29d026e4 | ||
|
|
b07a7febb2 | ||
|
|
eec5aca983 | ||
|
|
e7befaa981 | ||
|
|
023743f80d | ||
|
|
db996f093e | ||
|
|
85f0901cc2 | ||
|
|
36325fb7b5 | ||
|
|
b9e752444b | ||
|
|
f790532eae | ||
|
|
b69e4a2c3b | ||
|
|
71c80c0c18 | ||
|
|
a92ee51464 | ||
|
|
ed23743f06 | ||
|
|
6e3ea19366 | ||
|
|
0378f2c956 | ||
|
|
77d1ab8a83 | ||
|
|
d3870b2d0d | ||
|
|
b139e606d1 | ||
|
|
0b1b588d17 | ||
|
|
d3210f2a8d | ||
|
|
03caca314b | ||
|
|
443774a0cb | ||
|
|
75849e3575 | ||
|
|
9761bde75b | ||
|
|
ff3cfebe3a | ||
|
|
84cbd432a8 | ||
|
|
f59568a3f9 | ||
|
|
5bdd774849 | ||
|
|
ac1765f1c0 | ||
|
|
7ea0c19caf | ||
|
|
97f5338f74 | ||
|
|
d25d167903 | ||
|
|
32bf34658d | ||
|
|
3ae84cac1c | ||
|
|
9c35e34ce6 | ||
|
|
6cac0b205a | ||
|
|
1550138a43 | ||
|
|
34cb298f24 | ||
|
|
017437fecf | ||
|
|
40127ac64a | ||
|
|
6859bfda30 | ||
|
|
145501b883 | ||
|
|
126179e9b4 | ||
|
|
86174a6485 | ||
|
|
1f31c12810 | ||
|
|
c91f7acc2a | ||
|
|
f5568f849b | ||
|
|
b10634e190 | ||
|
|
d6c47ad85c | ||
|
|
7a3d1d1b91 | ||
|
|
3a859074ca | ||
|
|
f2cc8e6a55 | ||
|
|
4b0195d26b | ||
|
|
bcc9198394 | ||
|
|
5431da663d | ||
|
|
a957919986 | ||
|
|
96a44b47ef | ||
|
|
1716e959f3 | ||
|
|
46ff0a938b | ||
|
|
7fbca5de56 | ||
|
|
b7cb9ddbca | ||
|
|
f1233a8830 | ||
|
|
723a3447d2 | ||
|
|
f68791d552 | ||
|
|
6b25180fe3 | ||
|
|
745d8d93cb | ||
|
|
d18c8b0ccd | ||
|
|
74be373e7c | ||
|
|
61d500e7a1 | ||
|
|
facf9c001c | ||
|
|
30e88c7076 | ||
|
|
7eaf14179d | ||
|
|
1a2754dbac | ||
|
|
9fc19f9b23 | ||
|
|
1da17440b4 | ||
|
|
b3ea80060b | ||
|
|
19708bf20f | ||
|
|
2ea876da03 | ||
|
|
624f4fb35d | ||
|
|
954a35db5c | ||
|
|
f040534f2e | ||
|
|
61484f01bd | ||
|
|
e14456ba4c | ||
|
|
c33be13048 | ||
|
|
48b76e35f9 | ||
|
|
66415447c7 | ||
|
|
c627d71f73 | ||
|
|
3aed75fb3f | ||
|
|
e3002b458e | ||
|
|
99ba771d4e | ||
|
|
f3bf3b9279 | ||
|
|
652cfdd4fc | ||
|
|
82b07defb2 | ||
|
|
9e36bda547 | ||
|
|
836423cae4 | ||
|
|
75fb34afa3 | ||
|
|
ac6b38b694 | ||
|
|
b3e3ddce25 | ||
|
|
2cbe709c35 | ||
|
|
357087a072 | ||
|
|
9e959c4899 | ||
|
|
2968ecc084 | ||
|
|
b6d2acc3a2 | ||
|
|
1c16f47eaa | ||
|
|
bccc989157 | ||
|
|
e1d362e78e | ||
|
|
33ce23c610 | ||
|
|
719cb85012 | ||
|
|
d6c5c03e85 | ||
|
|
c3c10736cb | ||
|
|
e6f732c1c0 | ||
|
|
bf788b5fbb | ||
|
|
7e7fbdc962 | ||
|
|
a9369e9d22 | ||
|
|
05791bb54a | ||
|
|
c3d9bef414 | ||
|
|
35cd9b6682 | ||
|
|
e3b9286a5d | ||
|
|
89eb3ea660 | ||
|
|
3fb6ebe095 | ||
|
|
2e535fc00e | ||
|
|
f22c5204f3 |
@@ -13,6 +13,7 @@ dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
dev-dist
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
@@ -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`
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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",
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 60 KiB |
@@ -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": "/"
|
||||
}
|
||||
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -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
|
||||
@@ -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>
|
||||
|
||||
|
After Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 303 KiB |
|
After Width: | Height: | Size: 219 B |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 357 B |
|
After Width: | Height: | Size: 394 B |
|
After Width: | Height: | Size: 7.1 MiB |
|
After Width: | Height: | Size: 303 KiB |
|
After Width: | Height: | Size: 4.1 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 248 B |
|
After Width: | Height: | Size: 329 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 224 KiB |
@@ -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;*/
|
||||
/*}*/
|
||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 468 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 266 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 514 KiB |
|
After Width: | Height: | Size: 423 KiB |
|
After Width: | Height: | Size: 111 KiB |
@@ -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,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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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> </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>
|
||||
|
||||
@@ -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>
|
||||
|
||||