Add ticket validation feature for employees
Introduces ticket validation and result components for employee access control, including UI for scanning and manual code entry. Updates routing, guards, and navigation for mobile support and improves ticket status handling. Also adds ngx-scanner-qrcode dependency and minor UI fixes.
This commit is contained in:
71
package-lock.json
generated
71
package-lock.json
generated
@@ -23,6 +23,7 @@
|
|||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"jspdf": "^3.0.4",
|
"jspdf": "^3.0.4",
|
||||||
"ngx-mask": "^20.0.3",
|
"ngx-mask": "^20.0.3",
|
||||||
|
"ngx-scanner-qrcode": "^1.7.6",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tailwindcss": "^4.1.14",
|
"tailwindcss": "^4.1.14",
|
||||||
@@ -446,7 +447,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.14.tgz",
|
||||||
"integrity": "sha512-7bZxc01URbiPiIBWThQ69XwOxVduqEKN4PhpbF2AAyfMc/W8Hcr4VoIJOwL0O1Nkq5beS8pCAqoOeIgFyXd/kg==",
|
"integrity": "sha512-7bZxc01URbiPiIBWThQ69XwOxVduqEKN4PhpbF2AAyfMc/W8Hcr4VoIJOwL0O1Nkq5beS8pCAqoOeIgFyXd/kg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"parse5": "^8.0.0",
|
"parse5": "^8.0.0",
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
@@ -497,7 +497,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.13.tgz",
|
||||||
"integrity": "sha512-Jy+Qu6760TZyiDJX0+fNzkc70+lwF9ojdkIyCso/Lvbx1v3Fki0+9Wui7Vge56hknkr05xXg1aEUeqMN0966Lg==",
|
"integrity": "sha512-Jy+Qu6760TZyiDJX0+fNzkc70+lwF9ojdkIyCso/Lvbx1v3Fki0+9Wui7Vge56hknkr05xXg1aEUeqMN0966Lg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
},
|
},
|
||||||
@@ -514,7 +513,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.13.tgz",
|
||||||
"integrity": "sha512-YEjzHxz9laEcC2YPBA7L09Ys8UIuPrRiBZcGCrOXzXmPATHGYuxqYuhZ8iKmKV0PG/4pP2fxD3Mv5wN0cBaOWg==",
|
"integrity": "sha512-YEjzHxz9laEcC2YPBA7L09Ys8UIuPrRiBZcGCrOXzXmPATHGYuxqYuhZ8iKmKV0PG/4pP2fxD3Mv5wN0cBaOWg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
},
|
},
|
||||||
@@ -528,7 +526,6 @@
|
|||||||
"integrity": "sha512-Cou3G8C60eKpD93SKBJRG5pa/xpmMHe6sc2aanWjneGWjZq1kR4v5eQwwr8LUByIsafcqxHGT7+q1bYXT2p2DQ==",
|
"integrity": "sha512-Cou3G8C60eKpD93SKBJRG5pa/xpmMHe6sc2aanWjneGWjZq1kR4v5eQwwr8LUByIsafcqxHGT7+q1bYXT2p2DQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "7.28.3",
|
"@babel/core": "7.28.3",
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.14",
|
"@jridgewell/sourcemap-codec": "^1.4.14",
|
||||||
@@ -561,7 +558,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.13.tgz",
|
||||||
"integrity": "sha512-12Kou+WAIjAUSG5TkDbypV2kreJ105VylAjlQ09bCvsGNTHjezGgahFa/tLz7iyrozhuivtGiQtiDaYsc79ysw==",
|
"integrity": "sha512-12Kou+WAIjAUSG5TkDbypV2kreJ105VylAjlQ09bCvsGNTHjezGgahFa/tLz7iyrozhuivtGiQtiDaYsc79ysw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
},
|
},
|
||||||
@@ -587,7 +583,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.13.tgz",
|
||||||
"integrity": "sha512-9vu9MCHJtgXvgPH+ZgXN46N3gpBBAckcmG62P7U+9BKivWvv3rEvkgX+4HvO+Pm2D6x/Jy1xbiQuVq9EDGPSNA==",
|
"integrity": "sha512-9vu9MCHJtgXvgPH+ZgXN46N3gpBBAckcmG62P7U+9BKivWvv3rEvkgX+4HvO+Pm2D6x/Jy1xbiQuVq9EDGPSNA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
},
|
},
|
||||||
@@ -623,7 +618,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.13.tgz",
|
||||||
"integrity": "sha512-KyJzzpD4jMPGotDgVHF0cz9psjlVg6wYQrhuWcLeE97VUvp+CdwdOJ9tlxDlGE5tYZ0JrQxAT0l5qdcr6K9iNQ==",
|
"integrity": "sha512-KyJzzpD4jMPGotDgVHF0cz9psjlVg6wYQrhuWcLeE97VUvp+CdwdOJ9tlxDlGE5tYZ0JrQxAT0l5qdcr6K9iNQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
},
|
},
|
||||||
@@ -690,7 +684,6 @@
|
|||||||
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
|
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
@@ -1656,7 +1649,6 @@
|
|||||||
"integrity": "sha512-nqhDw2ZcAUrKNPwhjinJny903bRhI0rQhiDz1LksjeRxqa36i3l75+4iXbOy0rlDpLJGxqtgoPavQjmmyS5UJw==",
|
"integrity": "sha512-nqhDw2ZcAUrKNPwhjinJny903bRhI0rQhiDz1LksjeRxqa36i3l75+4iXbOy0rlDpLJGxqtgoPavQjmmyS5UJw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@inquirer/checkbox": "^4.2.1",
|
"@inquirer/checkbox": "^4.2.1",
|
||||||
"@inquirer/confirm": "^5.1.14",
|
"@inquirer/confirm": "^5.1.14",
|
||||||
@@ -3909,7 +3901,6 @@
|
|||||||
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
|
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.16.0"
|
"undici-types": "~7.16.0"
|
||||||
}
|
}
|
||||||
@@ -4208,37 +4199,28 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/body-parser": {
|
"node_modules/body-parser": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz",
|
||||||
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
|
"integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bytes": "^3.1.2",
|
"bytes": "^3.1.2",
|
||||||
"content-type": "^1.0.5",
|
"content-type": "^1.0.5",
|
||||||
"debug": "^4.4.0",
|
"debug": "^4.4.3",
|
||||||
"http-errors": "^2.0.0",
|
"http-errors": "^2.0.0",
|
||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "^0.7.0",
|
||||||
"on-finished": "^2.4.1",
|
"on-finished": "^2.4.1",
|
||||||
"qs": "^6.14.0",
|
"qs": "^6.14.0",
|
||||||
"raw-body": "^3.0.0",
|
"raw-body": "^3.0.1",
|
||||||
"type-is": "^2.0.0"
|
"type-is": "^2.0.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/body-parser/node_modules/iconv-lite": {
|
|
||||||
"version": "0.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
|
||||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"funding": {
|
||||||
"node": ">=0.10.0"
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/boolbase": {
|
"node_modules/boolbase": {
|
||||||
@@ -4292,7 +4274,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.8.25",
|
"baseline-browser-mapping": "^2.8.25",
|
||||||
"caniuse-lite": "^1.0.30001754",
|
"caniuse-lite": "^1.0.30001754",
|
||||||
@@ -5473,7 +5454,6 @@
|
|||||||
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
|
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "^2.0.0",
|
"accepts": "^2.0.0",
|
||||||
"body-parser": "^2.2.0",
|
"body-parser": "^2.2.0",
|
||||||
@@ -6473,8 +6453,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.9.0.tgz",
|
||||||
"integrity": "sha512-OMUvF1iI6+gSRYOhMrH4QYothVLN9C3EJ6wm4g7zLJlnaTl8zbaPOr0bTw70l7QxkoM7sVFOWo83u9B2Fe2Zng==",
|
"integrity": "sha512-OMUvF1iI6+gSRYOhMrH4QYothVLN9C3EJ6wm4g7zLJlnaTl8zbaPOr0bTw70l7QxkoM7sVFOWo83u9B2Fe2Zng==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/jiti": {
|
"node_modules/jiti": {
|
||||||
"version": "2.6.1",
|
"version": "2.6.1",
|
||||||
@@ -6585,7 +6564,6 @@
|
|||||||
"integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==",
|
"integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@colors/colors": "1.5.0",
|
"@colors/colors": "1.5.0",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
@@ -7302,7 +7280,6 @@
|
|||||||
"integrity": "sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g==",
|
"integrity": "sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cli-truncate": "^4.0.0",
|
"cli-truncate": "^4.0.0",
|
||||||
"colorette": "^2.0.20",
|
"colorette": "^2.0.20",
|
||||||
@@ -7971,6 +7948,19 @@
|
|||||||
"@angular/forms": ">=14.0.0"
|
"@angular/forms": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ngx-scanner-qrcode": {
|
||||||
|
"version": "1.7.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/ngx-scanner-qrcode/-/ngx-scanner-qrcode-1.7.6.tgz",
|
||||||
|
"integrity": "sha512-4AcRh+ozX0Arf97Xr1OmYRJUngHZDuU6b5pb9jsmM1Y/cpZX3rbI6mBQjsev65bm4UgDe+7naRgiVY07+K+vtw==",
|
||||||
|
"license": "LGPL-2.1+",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@angular/common": "^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0 || ^23.0.0 || ^24.0.0",
|
||||||
|
"@angular/core": "^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0 || ^23.0.0 || ^24.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-addon-api": {
|
"node_modules/node-addon-api": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
|
||||||
@@ -9208,7 +9198,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||||
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.1.0"
|
"tslib": "^2.1.0"
|
||||||
}
|
}
|
||||||
@@ -9244,7 +9233,6 @@
|
|||||||
"integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==",
|
"integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": "^4.0.0",
|
"chokidar": "^4.0.0",
|
||||||
"immutable": "^5.0.2",
|
"immutable": "^5.0.2",
|
||||||
@@ -10128,8 +10116,7 @@
|
|||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"license": "0BSD",
|
"license": "0BSD"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/tuf-js": {
|
"node_modules/tuf-js": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
@@ -10167,7 +10154,6 @@
|
|||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -10363,7 +10349,6 @@
|
|||||||
"integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==",
|
"integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@@ -10766,7 +10751,6 @@
|
|||||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
@@ -10785,8 +10769,7 @@
|
|||||||
"version": "0.15.1",
|
"version": "0.15.1",
|
||||||
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz",
|
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz",
|
||||||
"integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==",
|
"integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"jspdf": "^3.0.4",
|
"jspdf": "^3.0.4",
|
||||||
"ngx-mask": "^20.0.3",
|
"ngx-mask": "^20.0.3",
|
||||||
|
"ngx-scanner-qrcode": "^1.7.6",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tailwindcss": "^4.1.14",
|
"tailwindcss": "^4.1.14",
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import { MatBadgeModule } from '@angular/material/badge';
|
|||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import {MatSelectModule} from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import { MatSortModule } from '@angular/material/sort';
|
import { MatSortModule } from '@angular/material/sort';
|
||||||
|
|
||||||
import { HeaderComponent } from './header/header.component';
|
import { HeaderComponent } from './header/header.component';
|
||||||
@@ -79,7 +79,8 @@ import { PricelistComponent } from './pricelist/pricelist.component';
|
|||||||
import { TheaterLayoutDesignerComponent } from './theater-layout-designer/theater-layout-designer.component';
|
import { TheaterLayoutDesignerComponent } from './theater-layout-designer/theater-layout-designer.component';
|
||||||
import { PdfTicketComponent } from './pdf-ticket/pdf-ticket.component';
|
import { PdfTicketComponent } from './pdf-ticket/pdf-ticket.component';
|
||||||
import { TestComponent } from './test/test.component';
|
import { TestComponent } from './test/test.component';
|
||||||
|
import { TicketValidationComponent } from './ticket-validation/ticket-validation.component';
|
||||||
|
import { TicketValidationResultComponent } from './ticket-validation-result/ticket-validation-result.component';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -133,6 +134,8 @@ import { TestComponent } from './test/test.component';
|
|||||||
PricelistComponent,
|
PricelistComponent,
|
||||||
TheaterLayoutDesignerComponent,
|
TheaterLayoutDesignerComponent,
|
||||||
TestComponent,
|
TestComponent,
|
||||||
|
TicketValidationComponent,
|
||||||
|
TicketValidationResultComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
@@ -167,7 +170,7 @@ import { TestComponent } from './test/test.component';
|
|||||||
MatTableModule,
|
MatTableModule,
|
||||||
MatSelectModule,
|
MatSelectModule,
|
||||||
MatSortModule,
|
MatSortModule,
|
||||||
PdfTicketComponent
|
PdfTicketComponent,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
provideBrowserGlobalErrorListeners(),
|
provideBrowserGlobalErrorListeners(),
|
||||||
@@ -178,4 +181,5 @@ import { TestComponent } from './test/test.component';
|
|||||||
],
|
],
|
||||||
bootstrap: [App]
|
bootstrap: [App]
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule {
|
||||||
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ const routes: Routes = [
|
|||||||
path: 'employee/validation/ticket',
|
path: 'employee/validation/ticket',
|
||||||
component: TicketValidationComponent,
|
component: TicketValidationComponent,
|
||||||
canActivate: [AuthGuard],
|
canActivate: [AuthGuard],
|
||||||
data: { roles: ['employee'] },
|
data: { roles: ['employee'], allowMobile: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'employee/validation/ticket/:ticketId',
|
path: 'employee/validation/ticket/:ticketId',
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export class AuthGuard implements CanActivate {
|
|||||||
const dialogRef = this.dialog.open(LoginDialog, {
|
const dialogRef = this.dialog.open(LoginDialog, {
|
||||||
disableClose: true,
|
disableClose: true,
|
||||||
backdropClass: 'backdropBackground',
|
backdropClass: 'backdropBackground',
|
||||||
data: { user },
|
data: { user, allowMobile: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await firstValueFrom(dialogRef.afterClosed());
|
const result = await firstValueFrom(dialogRef.afterClosed());
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AuthService, User, UserRole } from './../auth.service';
|
import { AuthService, UserRole } from './../auth.service';
|
||||||
import { Component, inject, computed } from '@angular/core';
|
import { Component, inject, computed } from '@angular/core';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -13,9 +13,9 @@ export class NavbarComponent {
|
|||||||
{ label: 'Preise', path: '/prices', auth: null },
|
{ label: 'Preise', path: '/prices', auth: null },
|
||||||
{ label: 'Bezahlen', path: '/checkout/order', auth: null },
|
{ label: 'Bezahlen', path: '/checkout/order', auth: null },
|
||||||
{ label: 'Einlasskontrolle', path: '/employee/validation/ticket', auth: ['employee'] },
|
{ label: 'Einlasskontrolle', path: '/employee/validation/ticket', auth: ['employee'] },
|
||||||
{ label: 'Film importieren', path: '/admin/movie-importer', auth: ['admin']},
|
|
||||||
{ label: 'Statistiken', path: '/admin/statistics', auth: ['admin'] },
|
{ label: 'Statistiken', path: '/admin/statistics', auth: ['admin'] },
|
||||||
{ label: 'Saal-Designer', path: '/admin/designer', auth: ['admin'] },
|
{ label: 'Saal-Designer', path: '/admin/designer', auth: ['admin'] },
|
||||||
|
{ label: 'Film-Importer', path: '/admin/movie-importer', auth: ['admin']},
|
||||||
];
|
];
|
||||||
|
|
||||||
private auth = inject(AuthService);
|
private auth = inject(AuthService);
|
||||||
|
|||||||
@@ -88,14 +88,14 @@ export class PayForOrderComponent implements OnInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const order = orders[0];
|
const order = orders[0];
|
||||||
if (order.booked) {
|
|
||||||
this.formControl.setErrors({ alreadyBooked: true });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (order.cancelled) {
|
if (order.cancelled) {
|
||||||
this.formControl.setErrors({ cancelled: true });
|
this.formControl.setErrors({ cancelled: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (order.booked) {
|
||||||
|
this.formControl.setErrors({ alreadyBooked: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.router.navigate(['/checkout/order', order.code]);
|
this.router.navigate(['/checkout/order', order.code]);
|
||||||
}),
|
}),
|
||||||
catchError(err => {
|
catchError(err => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<h1 class="text-xl font-bold">Vielen Dank für Ihren Einkauf!</h1>
|
<h1 class="text-xl font-bold">Vielen Dank für Ihren Einkauf!</h1>
|
||||||
<p class="text-center">{{ infoText }}</p>
|
<p class="text-center">{{ infoText }}</p>
|
||||||
|
|
||||||
<app-ticket-list [tickets]="tickets()" class="w-8/10 my-4"></app-ticket-list>
|
<app-ticket-list [tickets]="tickets()" class="w-fit my-4"></app-ticket-list>
|
||||||
|
|
||||||
<button mat-button type="button" [disabled]="isGenerating" matButton="filled" class="success-button w-80 mt-4" (click)="downloadTickets()">{{ getButtonText() }}</button>
|
<button mat-button type="button" [disabled]="isGenerating" matButton="filled" class="success-button w-80 mt-4" (click)="downloadTickets()">{{ getButtonText() }}</button>
|
||||||
<button routerLink="/schedule" type="button" mat-button matButton="outlined" color="accent" class="success-button w-80 mt-1">Zur Programmauswahl</button>
|
<button routerLink="/schedule" type="button" mat-button matButton="outlined" color="accent" class="success-button w-80 mt-1">Zur Programmauswahl</button>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
<button routerLink="/checkout/order/{{ order().code }}" type="button" mat-button matButton="filled" color="accent" class="success-button mt-2 w-80">{{ buttonText }}</button>
|
<button routerLink="/checkout/order/{{ order().code }}" type="button" mat-button matButton="filled" color="accent" class="success-button mt-2 w-80">{{ buttonText }}</button>
|
||||||
<button routerLink="/schedule" type="button" mat-button matButton="outlined" class="success-button mb-4 w-80 mt-1">Zurück zur Programmauswahl</button>
|
<button routerLink="/schedule" type="button" mat-button matButton="outlined" class="success-button mb-4 w-80 mt-1">Zurück zur Programmauswahl</button>
|
||||||
<div [routerLink]="['/checkout/order', order().code]" [queryParams]="{ action: 'cancel' }" class="text-green-500 cursor-pointer w-fit mt-2">
|
<div [routerLink]="['/checkout/order', order().code]" [queryParams]="{ action: 'cancel' }" class="text-green-500 cursor-pointer w-fit mt-2">
|
||||||
Reservierung stornieren
|
Reservierung stornieren
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,7 @@
|
|||||||
<div class="flex flex-col flex-1 text-sm leading-tight ml-1">
|
<div class="flex flex-col flex-1 text-sm leading-tight ml-1">
|
||||||
<p>
|
<p>
|
||||||
{{ ticket().seat.row.category.name }}
|
{{ ticket().seat.row.category.name }}
|
||||||
@if (ticket().seat.row.category.name.length > 10) {
|
• Reihe
|
||||||
<br>
|
|
||||||
}
|
|
||||||
@else {
|
|
||||||
•
|
|
||||||
} Reihe
|
|
||||||
<span class="font-mono"><strong>{{ convertIntoRowName(ticket().seat.row.position) }}</strong></span>
|
<span class="font-mono"><strong>{{ convertIntoRowName(ticket().seat.row.position) }}</strong></span>
|
||||||
Platz
|
Platz
|
||||||
<span class="font-mono"><strong>{{ ticket().seat.position }}</strong></span>
|
<span class="font-mono"><strong>{{ ticket().seat.position }}</strong></span>
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
@switch (result()) {
|
||||||
|
|
||||||
|
@case ('nothing') {
|
||||||
|
<div class="w-full h-full p-6 py-8 items-center justify-center flex flex-col space-y-2 text-center">
|
||||||
|
<mat-icon class="mb-4 material-icons-outlined" style="font-size: 100px; width: 100px; height: 100px;">qr_code_scanner</mat-icon>
|
||||||
|
<h1 class="text-xl font-bold">Bitte Ticket scannen<br>oder Ticketcode eingeben</h1>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@case ('invalid') {
|
||||||
|
<div class="bg-red-200 text-red-600 rounded-md shadow-sm w-full h-full p-6 py-8 items-center justify-center flex flex-col space-y-2 text-center">
|
||||||
|
<mat-icon class="mb-4 material-icons-outlined" style="font-size: 100px; width: 100px; height: 100px;">cancel</mat-icon>
|
||||||
|
<h1 class="text-2xl font-bold">Ticket ungültig!</h1>
|
||||||
|
<p class="text-center">Unter der angegebenen Ticketnummer konnte keine gültige Eintrittskarte gefunden werden.</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@case ('unpaid') {
|
||||||
|
@if (performance()) {
|
||||||
|
<app-performance-info [performance]="performance()!" class="w-full"></app-performance-info>
|
||||||
|
<div class="h-3"></div>
|
||||||
|
}
|
||||||
|
<div class="bg-orange-200 text-orange-600 rounded-md shadow-sm w-full h-full p-6 py-8 items-center justify-center flex flex-col space-y-2">
|
||||||
|
<mat-icon class="mb-4 material-icons-outlined" style="font-size: 100px; width: 100px; height: 100px;">cancel</mat-icon>
|
||||||
|
<h1 class="text-2xl font-bold">Ticket nicht bezahlt!</h1>
|
||||||
|
<p class="text-center">Die Bestellung wurde noch nicht bezahlt und der Sitzplatz befindet sich noch im Status 'reserviert'.</p>
|
||||||
|
@if (ticket()) {
|
||||||
|
<app-ticket-small [ticket]="ticket()!" class="w-fit my-5"></app-ticket-small>
|
||||||
|
}
|
||||||
|
@if (order()) {
|
||||||
|
<div [routerLink]="['/checkout/order', order()?.code]" class="cursor-pointer w-fit my-2">
|
||||||
|
Ticket jetzt bezahlen
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@case ('expired') {
|
||||||
|
@if (performance()) {
|
||||||
|
<app-performance-info [performance]="performance()!" class="w-full"></app-performance-info>
|
||||||
|
<div class="h-3"></div>
|
||||||
|
}
|
||||||
|
<div class="bg-red-200 text-red-600 rounded-md shadow-sm w-full h-full p-6 py-8 items-center justify-center flex flex-col space-y-2">
|
||||||
|
<mat-icon class="mb-4 material-icons-outlined" style="font-size: 100px; width: 100px; height: 100px;">cancel</mat-icon>
|
||||||
|
<h1 class="text-2xl font-bold">Vorstellung beendet!</h1>
|
||||||
|
<p class="text-center">Die Filmvorführung hat bereits stattgefunden.</p>
|
||||||
|
@if (ticket()) {
|
||||||
|
<app-ticket-small [ticket]="ticket()!" class="w-fit my-5"></app-ticket-small>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@case ('valid') {
|
||||||
|
@if (performance()) {
|
||||||
|
<app-performance-info [performance]="performance()!" class="w-full"></app-performance-info>
|
||||||
|
<div class="h-3"></div>
|
||||||
|
}
|
||||||
|
<div class="bg-green-200 text-green-600 rounded-md shadow-sm w-full h-full p-6 py-8 items-center justify-center flex flex-col space-y-2">
|
||||||
|
<mat-icon class="mb-4 material-icons-outlined" style="font-size: 100px; width: 100px; height: 100px;">check_circle</mat-icon>
|
||||||
|
<h1 class="text-2xl font-bold">Ticket gültig!</h1>
|
||||||
|
@if (ticket()) {
|
||||||
|
<app-ticket-small [ticket]="ticket()!" class="w-fit my-5"></app-ticket-small>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@case ('early') {
|
||||||
|
@if (performance()) {
|
||||||
|
<app-performance-info [performance]="performance()!" class="w-full"></app-performance-info>
|
||||||
|
<div class="h-3"></div>
|
||||||
|
}
|
||||||
|
<div class="bg-purple-200 text-purple-600 rounded-md shadow-sm w-full h-full p-6 py-8 items-center justify-center flex flex-col space-y-2">
|
||||||
|
<mat-icon class="mb-4 material-icons-outlined" style="font-size: 100px; width: 100px; height: 100px;">schedule</mat-icon>
|
||||||
|
<h1 class="text-2xl font-bold">Noch kein Einlass!</h1>
|
||||||
|
<p class="text-center">Die Vorstellung beginnt in mehr als zwei Stunden.</p>
|
||||||
|
@if (ticket()) {
|
||||||
|
<app-ticket-small [ticket]="ticket()!" class="w-fit my-5"></app-ticket-small>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@case ('cancelled') {
|
||||||
|
@if (performance()) {
|
||||||
|
<app-performance-info [performance]="performance()!" class="w-full"></app-performance-info>
|
||||||
|
<div class="h-3"></div>
|
||||||
|
}
|
||||||
|
<div class="bg-red-200 text-red-600 rounded-md shadow-sm w-full h-full p-6 py-8 items-center justify-center flex flex-col space-y-2">
|
||||||
|
<mat-icon class="mb-4 material-icons-outlined" style="font-size: 100px; width: 100px; height: 100px;">cancel</mat-icon>
|
||||||
|
<h1 class="text-2xl font-bold">Ticket storniert!</h1>
|
||||||
|
@if (order()) {
|
||||||
|
<p class="text-center">
|
||||||
|
Die Bestellung wurde am {{ cancelledDate }} storniert.
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
@if (ticket()) {
|
||||||
|
<app-ticket-small [ticket]="ticket()!" class="w-fit my-5"></app-ticket-small>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@case ('error') {
|
||||||
|
<div class="bg-red-200 text-red-600 rounded-md shadow-sm w-full h-full p-6 py-8 items-center justify-center flex flex-col space-y-2">
|
||||||
|
<mat-icon class="mb-4 material-symbols-outlined" style="font-size: 100px; width: 100px; height: 100px;">error</mat-icon>
|
||||||
|
<h1 class="text-2xl font-bold">Überprüfung fehlgeschlagen!</h1>
|
||||||
|
<p class="text-center">Bei der Validierung des Tickets ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@default {
|
||||||
|
<div class="w-full h-full flex items-center justify-center">
|
||||||
|
<mat-progress-spinner
|
||||||
|
mode="indeterminate"
|
||||||
|
diameter="75"
|
||||||
|
></mat-progress-spinner>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Component, input } from '@angular/core';
|
||||||
|
import { Bestellung, Eintrittskarte, Vorstellung } from '@infinimotion/model-frontend';
|
||||||
|
|
||||||
|
export type ValidationResult = 'nothing' | 'invalid' | 'unpaid' | 'expired' | 'valid' | 'early' | 'cancelled' | 'loading' | 'error';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-ticket-validation-result',
|
||||||
|
standalone: false,
|
||||||
|
templateUrl: './ticket-validation-result.component.html',
|
||||||
|
styleUrl: './ticket-validation-result.component.css',
|
||||||
|
})
|
||||||
|
export class TicketValidationResultComponent {
|
||||||
|
result = input.required<ValidationResult>();
|
||||||
|
performance = input<Vorstellung>();
|
||||||
|
ticket = input<Eintrittskarte>();
|
||||||
|
order = input<Bestellung>();
|
||||||
|
|
||||||
|
get cancelledDate(): string {
|
||||||
|
if (!this.order()?.cancelled) return '';
|
||||||
|
return new Date(this.order()?.cancelled!).toLocaleDateString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
.middle {
|
||||||
|
position: relative;
|
||||||
|
top: 45%;
|
||||||
|
-webkit-transform: translateY(-50%);
|
||||||
|
-ms-transform: translateY(-50%);
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
61
src/app/ticket-validation/ticket-validation.component.html
Normal file
61
src/app/ticket-validation/ticket-validation.component.html
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<app-menu-header label="Einlasskontrolle: Ticketstatus prüfen" icon="security"></app-menu-header>
|
||||||
|
|
||||||
|
<div class="flex middle">
|
||||||
|
<div class="flex-1 flex justify-center my-auto p-20 pl-50">
|
||||||
|
<div class="w-full max-w-md">
|
||||||
|
<form class="ticket-search-form w-full" (ngSubmit)="DoSubmit()">
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<mat-form-field class="w-full" subscriptSizing="dynamic">
|
||||||
|
<mat-label>Ticketnummer eingeben</mat-label>
|
||||||
|
<input class="w-full" type="text"
|
||||||
|
matInput
|
||||||
|
[formControl]="formControl"
|
||||||
|
(input)="onInput($event)"
|
||||||
|
[mask]="'TXXXXXXX'"
|
||||||
|
[patterns]="ticketPattern"
|
||||||
|
placeholder="TXXXXXXX"
|
||||||
|
maxlength="8"
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
<!-- <mat-error>
|
||||||
|
@if (formControl.hasError('invalid')) {
|
||||||
|
Ungültiger Ticketcode
|
||||||
|
}
|
||||||
|
@else if (formControl.hasError('required')) {
|
||||||
|
Bitte geben Sie den Ticketcode eingeben
|
||||||
|
}
|
||||||
|
@else if (formControl.hasError('severalTickets')) {
|
||||||
|
Mehrere Tickets gefunden - bitte kontaktieren Sie den Support
|
||||||
|
}
|
||||||
|
@else if (formControl.hasError('serverError')) {
|
||||||
|
Fehler beim Laden des Tickets
|
||||||
|
}
|
||||||
|
</mat-error> -->
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- @if (formControl.valid || !formControl.touched) {
|
||||||
|
<div class="h-6"></div>
|
||||||
|
} -->
|
||||||
|
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
class="w-full mt-3"
|
||||||
|
matButton="filled"
|
||||||
|
color="accent"
|
||||||
|
[disabled]="(loadingService.loading$ | async) || (formControl.invalid && !formControl.hasError('serverError'))"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Ticket prüfen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-1 flex justify-center items-center p-20 pr-50">
|
||||||
|
<div class="w-full max-w-md">
|
||||||
|
<app-ticket-validation-result class="w-full h-100" [result]="result" [performance]="performance" [ticket]="ticket" [order]="order"></app-ticket-validation-result>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
147
src/app/ticket-validation/ticket-validation.component.ts
Normal file
147
src/app/ticket-validation/ticket-validation.component.ts
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import { Component, DestroyRef, inject, OnInit } from '@angular/core';
|
||||||
|
import { FormControl, Validators } from '@angular/forms';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { HttpService } from '../http.service';
|
||||||
|
import { LoadingService } from '../loading.service';
|
||||||
|
import { catchError, map, Observable, of, take } from 'rxjs';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
import { ValidationResult } from '../ticket-validation-result/ticket-validation-result.component';
|
||||||
|
import { Bestellung, Eintrittskarte, Vorstellung } from '@infinimotion/model-frontend';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-ticket-validation',
|
||||||
|
standalone: false,
|
||||||
|
templateUrl: './ticket-validation.component.html',
|
||||||
|
styleUrl: './ticket-validation.component.css',
|
||||||
|
})
|
||||||
|
export class TicketValidationComponent implements OnInit {
|
||||||
|
private httpService = inject(HttpService);
|
||||||
|
private route = inject(ActivatedRoute);
|
||||||
|
private destroyRef = inject(DestroyRef);
|
||||||
|
public loadingService = inject(LoadingService);
|
||||||
|
|
||||||
|
queryError?: string;
|
||||||
|
|
||||||
|
result: ValidationResult = 'nothing';
|
||||||
|
performance?: Vorstellung;
|
||||||
|
ticket?: Eintrittskarte;
|
||||||
|
order?: Bestellung;
|
||||||
|
|
||||||
|
public ticketPattern = {
|
||||||
|
'X': { pattern: /[A-Za-z0-9]/ },
|
||||||
|
'T': { pattern: /[Tt]/ },
|
||||||
|
};
|
||||||
|
|
||||||
|
formControl = new FormControl('', {
|
||||||
|
validators: [
|
||||||
|
Validators.required,
|
||||||
|
Validators.minLength(8),
|
||||||
|
Validators.maxLength(8)
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
const error = this.route.snapshot.queryParamMap.get('error');
|
||||||
|
const code = this.route.snapshot.queryParamMap.get('code');
|
||||||
|
|
||||||
|
if (code) {
|
||||||
|
this.formControl.setValue(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.formControl.clearValidators();
|
||||||
|
this.formControl.setErrors({ [error]: true });
|
||||||
|
this.formControl.markAsTouched();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.formControl.valueChanges.pipe(
|
||||||
|
take(1),
|
||||||
|
takeUntilDestroyed(this.destroyRef)
|
||||||
|
).subscribe(() => {
|
||||||
|
this.formControl.setValidators([
|
||||||
|
Validators.required,
|
||||||
|
Validators.minLength(8),
|
||||||
|
Validators.maxLength(8)
|
||||||
|
]);
|
||||||
|
this.formControl.updateValueAndValidity();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onInput(event: Event) {
|
||||||
|
this.queryError = undefined;
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
const filtered = input.value.toUpperCase().replace(/[^A-Z0-9]/g, '');
|
||||||
|
this.formControl.setValue(filtered, { emitEvent: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
DoSubmit() {
|
||||||
|
this.formControl.markAsTouched();
|
||||||
|
if (this.formControl.invalid) return;
|
||||||
|
|
||||||
|
const code = this.formControl.value?.trim();
|
||||||
|
if (!code || code.length !== 8) return;
|
||||||
|
|
||||||
|
this.result = 'loading';
|
||||||
|
this.loadingService.show();
|
||||||
|
const ticketFilter = [`eq;code;string;${code}`];
|
||||||
|
|
||||||
|
this.httpService.getTicketsByFilter(ticketFilter).pipe(
|
||||||
|
map(tickets => {
|
||||||
|
this.loadingService.hide();
|
||||||
|
|
||||||
|
if (tickets.length === 0) {
|
||||||
|
this.result = 'invalid';
|
||||||
|
this.formControl.setErrors({ invalid: true });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (tickets.length > 1) {
|
||||||
|
throw new Error("Für den Code existieren mehere Tickets");
|
||||||
|
// this.formControl.setErrors({ severalTickets: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ticket = tickets[0];
|
||||||
|
this.order = this.ticket.order;
|
||||||
|
this.performance = this.ticket.show;
|
||||||
|
const now = new Date;
|
||||||
|
|
||||||
|
if (this.ticket.order.cancelled) {
|
||||||
|
this.result = 'cancelled';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const showStart = new Date(this.ticket.show.start)
|
||||||
|
const showEnd = new Date(showStart.getTime() + this.ticket.show.movie.duration * 60 * 1000);
|
||||||
|
if (showEnd < now) {
|
||||||
|
this.result = 'expired';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.ticket.order.booked === null) {
|
||||||
|
this.result = 'unpaid';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const twoHoursInMs = 2 * 60 * 60 * 1000;
|
||||||
|
const twoHoursBeforeShow = new Date(showStart.getTime() - twoHoursInMs);
|
||||||
|
if (now < twoHoursBeforeShow) {
|
||||||
|
this.result = 'early';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.result = 'valid';
|
||||||
|
}),
|
||||||
|
catchError(err => {
|
||||||
|
this.result = 'error';
|
||||||
|
this.loadingService.hide();
|
||||||
|
this.loadingService.showError(err);
|
||||||
|
// this.formControl.setErrors({ serverError: true });
|
||||||
|
console.log(err);
|
||||||
|
return of(null);
|
||||||
|
}),
|
||||||
|
takeUntilDestroyed(this.destroyRef)
|
||||||
|
).subscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Two+Tone|Material+Icons+Round|Material+Icons+Sharp" rel="stylesheet">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
Reference in New Issue
Block a user