Compare commits
	
		
			10 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| d9870e03bc | |||
| 785b9e0b60 | |||
| fce2bc645e | |||
| 991716a7f5 | |||
| aa720f2460 | |||
| ac4ef8fb6d | |||
| eae0afda23 | |||
| 5291f8a4d1 | |||
| e2d7de1e9e | |||
| d7c9c27ec7 | 
							
								
								
									
										35
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -2,8 +2,43 @@ | ||||
|  | ||||
| All notable changes to this project will be documented in this file. Dates are displayed in UTC. | ||||
|  | ||||
| #### [1.10.2](https://git.odit.services/lfk/frontend/compare/1.10.1...1.10.2) | ||||
|  | ||||
| - refactor(runners): filter table for created_via [`785b9e0`](https://git.odit.services/lfk/frontend/commit/785b9e0b60a9961f99d0c519d6bb12dc735ac605) | ||||
|  | ||||
| #### [1.10.1](https://git.odit.services/lfk/frontend/compare/1.10.0...1.10.1) | ||||
|  | ||||
| > 9 April 2025 | ||||
|  | ||||
| - feat: runner list filtered by created_via [`991716a`](https://git.odit.services/lfk/frontend/commit/991716a7f55d0414111ad264ad1e93de9e82971a) | ||||
| - chore(release): 1.10.1 [`fce2bc6`](https://git.odit.services/lfk/frontend/commit/fce2bc645e040322f4d1b98a1ed1ab5df7227b6d) | ||||
|  | ||||
| #### [1.10.0](https://git.odit.services/lfk/frontend/compare/1.9.11...1.10.0) | ||||
|  | ||||
| > 9 April 2025 | ||||
|  | ||||
| - feat: working CardAssignment [`ac4ef8f`](https://git.odit.services/lfk/frontend/commit/ac4ef8fb6ded5c5d5678a651420e356b3ef45399) | ||||
| - chore(release): 1.10.0 [`aa720f2`](https://git.odit.services/lfk/frontend/commit/aa720f2460079e35eed9d87a2ac580a3305efbd5) | ||||
|  | ||||
| #### [1.9.11](https://git.odit.services/lfk/frontend/compare/1.9.10...1.9.11) | ||||
|  | ||||
| > 8 April 2025 | ||||
|  | ||||
| - feat(dash): add runnersViaKiosk [`5291f8a`](https://git.odit.services/lfk/frontend/commit/5291f8a4d1721cd0c745191ebc85f221c34a23c8) | ||||
| - chore(release): 1.9.11 [`eae0afd`](https://git.odit.services/lfk/frontend/commit/eae0afda23a54020e25821c0188d8cbec3139593) | ||||
|  | ||||
| #### [1.9.10](https://git.odit.services/lfk/frontend/compare/1.9.9...1.9.10) | ||||
|  | ||||
| > 8 April 2025 | ||||
|  | ||||
| - feat: add experimental ui for mobile card assignment [`d7c9c27`](https://git.odit.services/lfk/frontend/commit/d7c9c27ec7a1fea1cbaf26914843d044bbae32fe) | ||||
| - chore(release): 1.9.10 [`e2d7de1`](https://git.odit.services/lfk/frontend/commit/e2d7de1e9e1fd134f54876fa80f19f94fbea3672) | ||||
|  | ||||
| #### [1.9.9](https://git.odit.services/lfk/frontend/compare/1.9.8...1.9.9) | ||||
|  | ||||
| > 4 April 2025 | ||||
|  | ||||
| - chore(release): 1.9.9 [`153b1b3`](https://git.odit.services/lfk/frontend/commit/153b1b3c2badee4826be614c3dbaafc10e1fbfea) | ||||
| - fix(CopyScanStationTokenModal): code sizes [`ec63c7c`](https://git.odit.services/lfk/frontend/commit/ec63c7c1c51ccaf25bdd1eacffda66c820003a4c) | ||||
|  | ||||
| #### [1.9.8](https://git.odit.services/lfk/frontend/compare/1.9.7...1.9.8) | ||||
|   | ||||
| @@ -13,7 +13,7 @@ | ||||
|  | ||||
|   <body> | ||||
|     <span style="display: none; visibility: hidden" id="buildinfo" | ||||
|       >RELEASE_INFO-1.9.9-RELEASE_INFO</span | ||||
|       >RELEASE_INFO-1.10.2-RELEASE_INFO</span | ||||
|     > | ||||
|     <noscript>You need to enable JavaScript to run this app.</noscript> | ||||
|     <script src="/env.js"></script> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "@odit/lfk-frontend", | ||||
|   "version": "1.9.9", | ||||
|   "version": "1.10.2", | ||||
|   "type": "module", | ||||
|   "scripts": { | ||||
|     "i18n-order": "node order.js", | ||||
| @@ -43,12 +43,13 @@ | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@fontsource/athiti": "^5.2.5", | ||||
|     "@odit/lfk-client-js": "1.2.0", | ||||
|     "@odit/lfk-client-js": "1.2.3", | ||||
|     "@paralleldrive/cuid2": "2.2.2", | ||||
|     "@tanstack/svelte-table": "8.9.1", | ||||
|     "bwip-js": "3.4.0", | ||||
|     "check-password-strength": "2.0.10", | ||||
|     "csvtojson": "2.0.10", | ||||
|     "html5-qrcode": "^2.3.8", | ||||
|     "localforage": "1.10.0", | ||||
|     "marked": "4.3.0", | ||||
|     "svelte": "3.58.0", | ||||
|   | ||||
							
								
								
									
										36
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										36
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							| @@ -12,8 +12,8 @@ importers: | ||||
|         specifier: ^5.2.5 | ||||
|         version: 5.2.5 | ||||
|       '@odit/lfk-client-js': | ||||
|         specifier: 1.2.0 | ||||
|         version: 1.2.0 | ||||
|         specifier: 1.2.3 | ||||
|         version: 1.2.3 | ||||
|       '@paralleldrive/cuid2': | ||||
|         specifier: 2.2.2 | ||||
|         version: 2.2.2 | ||||
| @@ -29,6 +29,9 @@ importers: | ||||
|       csvtojson: | ||||
|         specifier: 2.0.10 | ||||
|         version: 2.0.10 | ||||
|       html5-qrcode: | ||||
|         specifier: ^2.3.8 | ||||
|         version: 2.3.8 | ||||
|       localforage: | ||||
|         specifier: 1.10.0 | ||||
|         version: 1.10.0 | ||||
| @@ -77,7 +80,7 @@ importers: | ||||
|         version: 3.3.3(prettier@3.5.3)(svelte@3.58.0) | ||||
|       release-it: | ||||
|         specifier: 17.10.0 | ||||
|         version: 17.10.0 | ||||
|         version: 17.10.0(typescript@5.8.3) | ||||
|       svelte-select: | ||||
|         specifier: 3.17.0 | ||||
|         version: 3.17.0 | ||||
| @@ -352,8 +355,8 @@ packages: | ||||
|   '@octokit/types@13.6.1': | ||||
|     resolution: {integrity: sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==} | ||||
|  | ||||
|   '@odit/lfk-client-js@1.2.0': | ||||
|     resolution: {integrity: sha512-RR/Ij3PDMF840VJtphO51k+3voJcTlvRIxzTFBkvrwriBmLJwchBQxq40K4/kyVIFH7lLwO3uJy0PaxgEoTbFQ==} | ||||
|   '@odit/lfk-client-js@1.2.3': | ||||
|     resolution: {integrity: sha512-QXJUVfH57iwykKNHQQC4z18/f7rnpCNCjvxijrOmuBMLsBNuYSzvrrqXyno4ce1rr5f0PD4IYtKmhcgJsk49wQ==} | ||||
|  | ||||
|   '@odit/license-exporter@0.2.0': | ||||
|     resolution: {integrity: sha512-RRyfQzDLoyLQlGSd8ThJQ3h0fiCe4tkmm935AUvSVQWP+p88FcnI4iaktKBJJVBnIpDhkv/7sDSA5dFc/QMM5w==} | ||||
| @@ -924,6 +927,9 @@ packages: | ||||
|     resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} | ||||
|     engines: {node: '>= 0.4'} | ||||
|  | ||||
|   html5-qrcode@2.3.8: | ||||
|     resolution: {integrity: sha512-jsr4vafJhwoLVEDW3n1KvPnCCXWaQfRng0/EEYk1vNcQGcG/htAdhJX0be8YyqMoSz7+hZvOZSTAepsabiuhiQ==} | ||||
|  | ||||
|   http-proxy-agent@7.0.2: | ||||
|     resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} | ||||
|     engines: {node: '>= 14'} | ||||
| @@ -1794,6 +1800,11 @@ packages: | ||||
|   type@2.7.2: | ||||
|     resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==} | ||||
|  | ||||
|   typescript@5.8.3: | ||||
|     resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} | ||||
|     engines: {node: '>=14.17'} | ||||
|     hasBin: true | ||||
|  | ||||
|   uglify-js@3.19.3: | ||||
|     resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} | ||||
|     engines: {node: '>=0.8.0'} | ||||
| @@ -2165,7 +2176,7 @@ snapshots: | ||||
|     dependencies: | ||||
|       '@octokit/openapi-types': 22.2.0 | ||||
|  | ||||
|   '@odit/lfk-client-js@1.2.0': {} | ||||
|   '@odit/lfk-client-js@1.2.3': {} | ||||
|  | ||||
|   '@odit/license-exporter@0.2.0': | ||||
|     dependencies: | ||||
| @@ -2439,12 +2450,14 @@ snapshots: | ||||
|       graceful-fs: 4.2.11 | ||||
|       xdg-basedir: 5.1.0 | ||||
|  | ||||
|   cosmiconfig@9.0.0: | ||||
|   cosmiconfig@9.0.0(typescript@5.8.3): | ||||
|     dependencies: | ||||
|       env-paths: 2.2.1 | ||||
|       import-fresh: 3.3.0 | ||||
|       js-yaml: 4.1.0 | ||||
|       parse-json: 5.2.0 | ||||
|     optionalDependencies: | ||||
|       typescript: 5.8.3 | ||||
|  | ||||
|   crc-32@1.2.2: {} | ||||
|  | ||||
| @@ -2765,6 +2778,8 @@ snapshots: | ||||
|     dependencies: | ||||
|       function-bind: 1.1.2 | ||||
|  | ||||
|   html5-qrcode@2.3.8: {} | ||||
|  | ||||
|   http-proxy-agent@7.0.2: | ||||
|     dependencies: | ||||
|       agent-base: 7.1.1 | ||||
| @@ -3322,14 +3337,14 @@ snapshots: | ||||
|     dependencies: | ||||
|       rc: 1.2.8 | ||||
|  | ||||
|   release-it@17.10.0: | ||||
|   release-it@17.10.0(typescript@5.8.3): | ||||
|     dependencies: | ||||
|       '@iarna/toml': 2.2.5 | ||||
|       '@octokit/rest': 20.1.1 | ||||
|       async-retry: 1.3.3 | ||||
|       chalk: 5.3.0 | ||||
|       ci-info: 4.1.0 | ||||
|       cosmiconfig: 9.0.0 | ||||
|       cosmiconfig: 9.0.0(typescript@5.8.3) | ||||
|       execa: 8.0.0 | ||||
|       git-url-parse: 14.0.0 | ||||
|       globby: 14.0.2 | ||||
| @@ -3607,6 +3622,9 @@ snapshots: | ||||
|  | ||||
|   type@2.7.2: {} | ||||
|  | ||||
|   typescript@5.8.3: | ||||
|     optional: true | ||||
|  | ||||
|   uglify-js@3.19.3: | ||||
|     optional: true | ||||
|  | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								public/beep.mp3
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/beep.mp3
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -41,6 +41,7 @@ | ||||
|   import Settings from "./components/settings/Settings.svelte"; | ||||
|   import Transition from "./components/base/Transition.svelte"; | ||||
|   import Orgs from "./components/orgs/Orgs.svelte"; | ||||
|   import CardAssignment from "./components/general/CardAssignment.svelte"; | ||||
|   import Runners from "./components/runners/Runners.svelte"; | ||||
|   import Footer from "./components/general/Footer.svelte"; | ||||
|   import TracksOverview from "./components/tracks/TracksOverview.svelte"; | ||||
| @@ -135,12 +136,17 @@ | ||||
|         </Route> | ||||
|         <Route path="/runners/*"> | ||||
|           <Route path="/"> | ||||
|             <Runners /> | ||||
|             <Runners created_via="all" /> | ||||
|           </Route> | ||||
|           <Route path="/:runnerid" let:params> | ||||
|             <RunnerDetail {params} /> | ||||
|           </Route> | ||||
|         </Route> | ||||
|         <Route path="/cardassignment/*"> | ||||
|           <Route path="/"> | ||||
|             <CardAssignment /> | ||||
|           </Route> | ||||
|         </Route> | ||||
|         <Route path="/teams/*"> | ||||
|           <Route path="/"> | ||||
|             <Teams /> | ||||
|   | ||||
| @@ -41,7 +41,27 @@ | ||||
| 				</svg> | ||||
| 				<span>{$_("dashboard-title")}</span> | ||||
| 			</a> | ||||
| 			{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:GET")} | ||||
| 			{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:GET") && store.state.jwtinfo.userdetails.permissions.includes("CARD:GET")} | ||||
| 				<a | ||||
| 					class:activenav={$router.path.includes("/cardassignment/")} | ||||
| 					class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold" | ||||
| 					href="/cardassignment/" | ||||
| 				> | ||||
| 					<svg | ||||
| 						xmlns="http://www.w3.org/2000/svg" | ||||
| 						viewBox="0 0 24 24" | ||||
| 						fill="currentColor" | ||||
| 						class="flex-shrink-0 w-5 h-5 mr-2 transition group-hover:text-gray-600" | ||||
| 					> | ||||
| 						<path | ||||
| 							fill-rule="evenodd" | ||||
| 							d="M14.615 1.595a.75.75 0 0 1 .359.852L12.982 9.75h7.268a.75.75 0 0 1 .548 1.262l-10.5 11.25a.75.75 0 0 1-1.272-.71l1.992-7.302H3.75a.75.75 0 0 1-.548-1.262l10.5-11.25a.75.75 0 0 1 .913-.143Z" | ||||
| 							clip-rule="evenodd" | ||||
| 						/> | ||||
| 					</svg> | ||||
|  | ||||
| 					<span>Card Assignment</span> | ||||
| 				</a> | ||||
| 				<a | ||||
| 					class:activenav={$router.path.includes("/runners/")} | ||||
| 					class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-200 hover:text-gray-900 w-full font-semibold" | ||||
|   | ||||
| @@ -234,6 +234,23 @@ | ||||
| 					/></svg | ||||
| 				> | ||||
| 			</StatCard> | ||||
| 			<StatCard | ||||
| 				title={$_('runners_via_kiosk')} | ||||
| 				value={stats.runnersViaKiosk} | ||||
| 				href="/runners/" | ||||
| 			> | ||||
| 				<svg | ||||
| 					height="24" | ||||
| 					width="24" | ||||
| 					fill="currentColor" | ||||
| 					xmlns="http://www.w3.org/2000/svg" | ||||
| 					viewBox="0 0 24 24" | ||||
| 					><path d="M0 0h24v24H0z" fill="none" /> | ||||
| 					<path | ||||
| 						d="M13.49 5.48c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm-3.6 13.9l1-4.4 2.1 2v6h2v-7.5l-2.1-2 .6-3c1.3 1.5 3.3 2.5 5.5 2.5v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-.4-.6-1-1-1.7-1-.3 0-.5.1-.8.1l-5.2 2.2v4.7h2v-3.4l1.8-.7-1.6 8.1-4.9-1-.4 2 7 1.4z" | ||||
| 					/></svg | ||||
| 				> | ||||
| 			</StatCard> | ||||
| 		</div> | ||||
| 	{:catch error} | ||||
| 		<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"> | ||||
|   | ||||
							
								
								
									
										123
									
								
								src/components/general/CardAssignment.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/components/general/CardAssignment.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | ||||
| <script> | ||||
| 	import { RunnerCardService, RunnerService } from "@odit/lfk-client-js"; | ||||
| 	import QrCodeScanner from "./QrCodeScanner.svelte"; | ||||
| 	let state = "scan_runner"; | ||||
| 	let runnerinfo = { id: 0, firstname: "", lastname: "" }; | ||||
| 	let cardCode = ""; | ||||
| 	$: scannerActive = state.includes("scan"); | ||||
| </script> | ||||
|  | ||||
| <div class="p-4"> | ||||
| 	<h3 class="text-3xl font-bold">Card Assignment for Mobile</h3> | ||||
| 	<!-- <p>state</p> | ||||
| 	<p>{state}</p> | ||||
| 	<p>scannerActive</p> | ||||
| 	<p>{scannerActive}</p> --> | ||||
| 	{#if state.includes("scan_")} | ||||
| 		<!--  --> | ||||
| 		{#if state === "scan_runner"} | ||||
| 			<h3 class="text-xl font-bold">Scan Runner (Selfservice QR)</h3> | ||||
| 		{/if} | ||||
| 		{#if state === "scan_card"} | ||||
| 			<h3 class="text-xl font-bold">Runner Scanned</h3> | ||||
| 			<p>{runnerinfo.firstname} {runnerinfo.lastname} [#{runnerinfo.id}]</p> | ||||
| 			<h3 class="text-xl font-bold">Scan Card (Code 128 Barcode)</h3> | ||||
| 		{/if} | ||||
| 		<QrCodeScanner | ||||
| 			paused={!scannerActive} | ||||
| 			on:detect={(e) => { | ||||
| 				console.log({ type: "DETECT", code: e.detail.decodedText }); | ||||
| 				if (state === "scan_runner") { | ||||
| 					if ( | ||||
| 						e.detail.decodedText.includes( | ||||
| 							"https://portal.lauf-fuer-kaya.de/profile/" | ||||
| 						) | ||||
| 					) { | ||||
| 						const runnerID = JSON.parse( | ||||
| 							atob( | ||||
| 								e.detail.decodedText | ||||
| 									.replace("https://portal.lauf-fuer-kaya.de/profile/", "") | ||||
| 									.split(".")[1] | ||||
| 							) | ||||
| 						).id; | ||||
| 						new Audio("/beep.mp3").play(); | ||||
| 						RunnerService.runnerControllerGetOne(runnerID).then((runner) => { | ||||
| 							console.log(runner); | ||||
| 							runnerinfo = runner; | ||||
| 						}); | ||||
| 						state = "scan_card"; | ||||
| 					} | ||||
| 				} | ||||
| 				if (state === "scan_card") { | ||||
| 					if ( | ||||
| 						!e.detail.decodedText.includes( | ||||
| 							"https://portal.lauf-fuer-kaya.de/profile/" | ||||
| 						) | ||||
| 					) { | ||||
| 						cardCode = e.detail.decodedText; | ||||
| 						new Audio("/beep.mp3").play(); | ||||
| 						state = "assigning"; | ||||
| 						RunnerCardService.runnerCardControllerGetAll().then((cards) => { | ||||
| 							console.log(cards); | ||||
| 							const card = cards.find((c) => c.code === cardCode); | ||||
| 							if (card) { | ||||
| 								console.log("card found", card); | ||||
| 								RunnerCardService.runnerCardControllerPut(card.id, { | ||||
| 									enabled: true, | ||||
| 									id: card.id, | ||||
| 									runner: runnerinfo.id, | ||||
| 								}).then(() => { | ||||
| 									state = "done"; | ||||
| 								}); | ||||
| 							} else { | ||||
| 								state = "error_card_404"; | ||||
| 							} | ||||
| 						}); | ||||
| 					} | ||||
| 				} | ||||
| 			}} | ||||
| 			width={320} | ||||
| 			height={320} | ||||
| 			class="w-full max-w-sm bg-neutral-300 rounded-lg overflow-hidden" | ||||
| 		/> | ||||
| 		{#if state === "scan_card"} | ||||
| 			<button | ||||
| 				on:click={() => { | ||||
| 					state = "scan_runner"; | ||||
| 					runnerinfo = { id: 0, firstname: "", lastname: "" }; | ||||
| 					cardCode = ""; | ||||
| 				}} | ||||
| 				type="button" | ||||
| 				class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-red-100 text-red-800 hover:bg-red-200 focus:outline-hidden focus:bg-red-200 disabled:opacity-50 disabled:pointer-events-none dark:text-red-500 dark:bg-red-800/30 dark:hover:bg-red-800/20 dark:focus:bg-red-800/20 w-full mt-2" | ||||
| 			> | ||||
| 				Cancel | ||||
| 			</button> | ||||
| 		{/if} | ||||
| 		<!--  --> | ||||
| 	{:else} | ||||
| 		<!--  --> | ||||
| 		{#if state === "assigning"} | ||||
| 			<p>Assigning Card {cardCode} ⌛</p> | ||||
| 			<p>Please wait a moment while we assign the card...</p> | ||||
| 		{/if} | ||||
| 		{#if state === "done"} | ||||
| 			<p> | ||||
| 				Assigned Card {cardCode} to {runnerinfo.firstname} | ||||
| 				{runnerinfo.lastname} [#{runnerinfo.id}] ✅ | ||||
| 			</p> | ||||
| 			<button | ||||
| 				on:click={() => { | ||||
| 					// state = "scan_runner"; | ||||
| 					// runnerinfo = { id: 0, firstname: "", lastname: "" }; | ||||
| 					// cardCode = ""; | ||||
| 					location.reload(); | ||||
| 				}} | ||||
| 				type="button" | ||||
| 				class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-100 text-blue-800 hover:bg-blue-200 focus:outline-hidden focus:bg-blue-200 disabled:opacity-50 disabled:pointer-events-none dark:text-blue-500 dark:bg-blue-800/30 dark:hover:bg-blue-800/20 dark:focus:bg-blue-800/20 w-full mt-2" | ||||
| 			> | ||||
| 				Done | ||||
| 			</button> | ||||
| 		{/if} | ||||
| 		<!--  --> | ||||
| 	{/if} | ||||
| </div> | ||||
							
								
								
									
										84
									
								
								src/components/general/QrCodeScanner.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/components/general/QrCodeScanner.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| <script> | ||||
| 	import { onMount, createEventDispatcher } from "svelte"; | ||||
| 	import { | ||||
| 		Html5QrcodeScanner, | ||||
| 		Html5QrcodeScanType, | ||||
| 		Html5QrcodeSupportedFormats, | ||||
| 		Html5QrcodeScannerState, | ||||
| 	} from "html5-qrcode"; | ||||
|  | ||||
| 	export let width; | ||||
| 	export let height; | ||||
| 	export let paused = false; | ||||
|  | ||||
| 	const dispatch = createEventDispatcher(); | ||||
|  | ||||
| 	function onScanSuccess(decodedText, decodedResult) { | ||||
| 		if (!paused) { | ||||
| 			dispatch("detect", { decodedText }); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// usually better to ignore and keep scanning | ||||
| 	function onScanFailure(message) { | ||||
| 		if (!paused) { | ||||
| 			dispatch("error", { message }); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	let scanner; | ||||
| 	onMount(() => { | ||||
| 		scanner = new Html5QrcodeScanner( | ||||
| 			"qr-scanner", | ||||
| 			{ | ||||
| 				fps: 10, | ||||
| 				rememberLastUsedCamera: true, | ||||
| 				qrbox: { width, height }, | ||||
| 				aspectRatio: 1, | ||||
| 				supportedScanTypes: [Html5QrcodeScanType.SCAN_TYPE_CAMERA], | ||||
| 				formatsToSupport: [ | ||||
| 					Html5QrcodeSupportedFormats.CODE_39, | ||||
| 					Html5QrcodeSupportedFormats.EAN_8, | ||||
| 					Html5QrcodeSupportedFormats.EAN_13, | ||||
| 					Html5QrcodeSupportedFormats.QR_CODE, | ||||
| 					Html5QrcodeSupportedFormats.CODE_128, | ||||
| 				], | ||||
| 			}, | ||||
| 			false // non-verbose | ||||
| 		); | ||||
| 		scanner.render(onScanSuccess, onScanFailure); | ||||
| 	}); | ||||
|  | ||||
| 	// pause/resume scanner to avoid unintended scans | ||||
| 	$: togglePause(paused); | ||||
| 	function togglePause(paused) { | ||||
| 		if (paused && scanner?.getState() === Html5QrcodeScannerState.SCANNING) { | ||||
| 			scanner?.pause(); | ||||
| 		} else if (scanner?.getState() === Html5QrcodeScannerState.PAUSED) { | ||||
| 			scanner?.resume(); | ||||
| 		} | ||||
| 	} | ||||
| </script> | ||||
|  | ||||
| <div id="qr-scanner" class={$$props.class} /> | ||||
|  | ||||
| <style> | ||||
| 	/* Hide unwanted icons */ | ||||
| 	#qr-scanner :global(img[alt="Info icon"]), | ||||
| 	#qr-scanner :global(img[alt="Camera based scan"]) { | ||||
| 		display: none; | ||||
| 	} | ||||
|  | ||||
| 	/* Change camera permission button text */ | ||||
| 	#qr-scanner :global(#html5-qrcode-button-camera-permission) { | ||||
| 		visibility: hidden; | ||||
| 	} | ||||
| 	#qr-scanner :global(#html5-qrcode-button-camera-permission::after) { | ||||
| 		position: absolute; | ||||
| 		inset: auto 0 0; | ||||
| 		display: block; | ||||
| 		content: "Allow camera access"; | ||||
| 		visibility: visible; | ||||
| 		padding: 10px 0; | ||||
| 	} | ||||
| </style> | ||||
| @@ -100,7 +100,7 @@ | ||||
| 				<nav class="w-full flex"> | ||||
| 					<ol class="list-none flex flex-row items-center justify-start"> | ||||
| 						<li class="flex items-center"> | ||||
| 							<a class="mr-2" href="./" | ||||
| 							<a class="mr-2" href="/runners/" | ||||
| 								><svg | ||||
| 									xmlns="http://www.w3.org/2000/svg" | ||||
| 									width="24" | ||||
|   | ||||
| @@ -32,6 +32,7 @@ | ||||
|  | ||||
| 	$: active_delete = undefined; | ||||
| 	let dataLoaded = false; | ||||
| 	export let created_via = "all"; | ||||
| 	export let current_runners = []; | ||||
| 	$: sponsoring_contracts_show = selected.length > 0; | ||||
| 	$: cards_show = selected.length > 0; | ||||
| @@ -75,6 +76,11 @@ | ||||
| 			header: () => $_("last-name"), | ||||
| 			filterFn: `includesString`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			accessorKey: "created_via", | ||||
| 			header: () => "created_via", | ||||
| 			filterFn: `includesString`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			accessorKey: "group", | ||||
| 			header: () => $_("group"), | ||||
| @@ -103,7 +109,7 @@ | ||||
| 			header: () => $_("action"), | ||||
| 			cell: (info) => { | ||||
| 				return renderComponent(TableActions, { | ||||
| 					detailsLink: `./${info.row.original.id}`, | ||||
| 					detailsLink: `/runners/${info.row.original.id}`, | ||||
| 					deleteAction: () => { | ||||
| 						active_delete = | ||||
| 							current_runners[ | ||||
| @@ -161,7 +167,11 @@ | ||||
|  | ||||
| 		let page = 0; | ||||
| 		while (page >= 0) { | ||||
| 			const runners = await RunnerService.runnerControllerGetAll(page, 500); | ||||
| 			const runners = await RunnerService.runnerControllerGetAll( | ||||
| 				page, | ||||
| 				500, | ||||
| 				created_via | ||||
| 			); | ||||
| 			if (runners.length == 0) { | ||||
| 				page = -2; | ||||
| 			} | ||||
| @@ -190,6 +200,9 @@ | ||||
| 	<h4 class="mb-1 text-3xl font-extrabold leading-tight"> | ||||
| 		{$_("runners")} | ||||
| 	</h4> | ||||
| 	{#if created_via !== "all"} | ||||
| 		<p>created_via={created_via}</p> | ||||
| 	{/if} | ||||
| 	{#if store.state.jwtinfo.userdetails.permissions.includes("RUNNER:CREATE")} | ||||
| 		<button | ||||
| 			on:click={() => { | ||||
|   | ||||
| @@ -382,6 +382,7 @@ | ||||
|     "runners": "Läufer", | ||||
|     "runners-are-being-imported": "Läufer werden importiert ...", | ||||
|     "runners-are-being-loaded": "Läufer werden geladen ...", | ||||
|     "runners_via_kiosk": "Läufer via Kiosk", | ||||
|     "save": "Speichern", | ||||
|     "save-changes": "Änderungen speichern", | ||||
|     "scan-added": "Scan hinzugefügt", | ||||
|   | ||||
| @@ -382,6 +382,7 @@ | ||||
|     "runners": "Runners", | ||||
|     "runners-are-being-imported": "Runners are being imported...", | ||||
|     "runners-are-being-loaded": "runners are being loaded...", | ||||
|     "runners_via_kiosk": "Runners via Kiosk", | ||||
|     "save": "Save", | ||||
|     "save-changes": "Save Changes", | ||||
|     "scan-added": "Scan added", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user